From 5c59e43aef288d2f7adcaac4c0ae7692d6c01ba3 Mon Sep 17 00:00:00 2001 From: Daniel Roseman Date: Thu, 31 May 2012 15:33:45 +0100 Subject: [PATCH 001/519] Use render shortcut in form example. --- docs/topics/forms/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index 18e55f5eb6..4870ffc094 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -99,7 +99,7 @@ The standard pattern for processing a form in a view looks like this: else: form = ContactForm() # An unbound form - return render_to_response('contact.html', { + return render(request, 'contact.html', { 'form': form, }) From f699641161a4ec8b6cbee938fd3a4379e7889ff2 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sun, 3 Jun 2012 17:33:09 -0700 Subject: [PATCH 002/519] Fixed #17138 -- Made the sensitive_variables decorator work with object methods. --- django/views/debug.py | 17 +++- django/views/decorators/debug.py | 12 +-- tests/regressiontests/views/tests/debug.py | 104 +++++++++++++-------- tests/regressiontests/views/views.py | 23 ++++- 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/django/views/debug.py b/django/views/debug.py index 7bdf0d25ee..d95cd62017 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter): Replaces the values of variables marked as sensitive with stars (*********). """ - func_name = tb_frame.f_code.co_name - func = tb_frame.f_globals.get(func_name) - sensitive_variables = getattr(func, 'sensitive_variables', []) + # Loop through the frame's callers to see if the sensitive_variables + # decorator was used. + current_frame = tb_frame.f_back + sensitive_variables = None + while current_frame is not None: + if (current_frame.f_code.co_name == 'sensitive_variables_wrapper' + and 'sensitive_variables_wrapper' in current_frame.f_locals): + # The sensitive_variables decorator was used, so we take note + # of the sensitive variables' names. + wrapper = current_frame.f_locals['sensitive_variables_wrapper'] + sensitive_variables = getattr(wrapper, 'sensitive_variables', None) + break + current_frame = current_frame.f_back + cleansed = [] if self.is_active(request) and sensitive_variables: if sensitive_variables == '__ALL__': diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py index d04967ef09..5c222963d3 100644 --- a/django/views/decorators/debug.py +++ b/django/views/decorators/debug.py @@ -26,13 +26,13 @@ def sensitive_variables(*variables): """ def decorator(func): @functools.wraps(func) - def wrapper(*args, **kwargs): + def sensitive_variables_wrapper(*args, **kwargs): if variables: - wrapper.sensitive_variables = variables + sensitive_variables_wrapper.sensitive_variables = variables else: - wrapper.sensitive_variables = '__ALL__' + sensitive_variables_wrapper.sensitive_variables = '__ALL__' return func(*args, **kwargs) - return wrapper + return sensitive_variables_wrapper return decorator @@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters): """ def decorator(view): @functools.wraps(view) - def wrapper(request, *args, **kwargs): + def sensitive_post_parameters_wrapper(request, *args, **kwargs): if parameters: request.sensitive_post_parameters = parameters else: request.sensitive_post_parameters = '__ALL__' return view(request, *args, **kwargs) - return wrapper + return sensitive_post_parameters_wrapper return decorator diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py index c8358d334f..e8a7d49e79 100644 --- a/tests/regressiontests/views/tests/debug.py +++ b/tests/regressiontests/views/tests/debug.py @@ -10,13 +10,12 @@ from django.test import TestCase, RequestFactory from django.test.utils import (setup_test_template_loader, restore_template_loaders) from django.core.urlresolvers import reverse -from django.template import TemplateSyntaxError from django.views.debug import ExceptionReporter from django.core import mail from .. import BrokenException, except_args from ..views import (sensitive_view, non_sensitive_view, paranoid_view, - custom_exception_reporter_filter_view) + custom_exception_reporter_filter_view, sensitive_method_view) class DebugViewTests(TestCase): @@ -238,7 +237,8 @@ class ExceptionReportTestMixin(object): 'hash-brown-key': 'hash-brown-value', 'bacon-key': 'bacon-value',} - def verify_unsafe_response(self, view, check_for_vars=True): + def verify_unsafe_response(self, view, check_for_vars=True, + check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the response. """ @@ -250,13 +250,14 @@ class ExceptionReportTestMixin(object): self.assertContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertContains(response, 'worcestershire', status_code=500) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters are shown. + self.assertContains(response, k, status_code=500) + self.assertContains(response, v, status_code=500) - for k, v in self.breakfast_data.items(): - # All POST parameters are shown. - self.assertContains(response, k, status_code=500) - self.assertContains(response, v, status_code=500) - - def verify_safe_response(self, view, check_for_vars=True): + def verify_safe_response(self, view, check_for_vars=True, + check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the response. """ @@ -269,18 +270,19 @@ class ExceptionReportTestMixin(object): # Sensitive variable's name is shown but not its value. self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters' names are shown. + self.assertContains(response, k, status_code=500) + # Non-sensitive POST parameters' values are shown. + self.assertContains(response, 'baked-beans-value', status_code=500) + self.assertContains(response, 'hash-brown-value', status_code=500) + # Sensitive POST parameters' values are not shown. + self.assertNotContains(response, 'sausage-value', status_code=500) + self.assertNotContains(response, 'bacon-value', status_code=500) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertContains(response, k, status_code=500) - # Non-sensitive POST parameters' values are shown. - self.assertContains(response, 'baked-beans-value', status_code=500) - self.assertContains(response, 'hash-brown-value', status_code=500) - # Sensitive POST parameters' values are not shown. - self.assertNotContains(response, 'sausage-value', status_code=500) - self.assertNotContains(response, 'bacon-value', status_code=500) - - def verify_paranoid_response(self, view, check_for_vars=True): + def verify_paranoid_response(self, view, check_for_vars=True, + check_for_POST_params=True): """ Asserts that no variables or POST parameters are displayed in the response. """ @@ -292,14 +294,14 @@ class ExceptionReportTestMixin(object): self.assertNotContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters' names are shown. + self.assertContains(response, k, status_code=500) + # No POST parameters' values are shown. + self.assertNotContains(response, v, status_code=500) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertContains(response, k, status_code=500) - # No POST parameters' values are shown. - self.assertNotContains(response, v, status_code=500) - - def verify_unsafe_email(self, view): + def verify_unsafe_email(self, view, check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the email report. """ @@ -314,12 +316,13 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) - for k, v in self.breakfast_data.items(): - # All POST parameters are shown. - self.assertIn(k, email.body) - self.assertIn(v, email.body) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters are shown. + self.assertIn(k, email.body) + self.assertIn(v, email.body) - def verify_safe_email(self, view): + def verify_safe_email(self, view, check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the email report. """ @@ -334,15 +337,16 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertIn(k, email.body) - # Non-sensitive POST parameters' values are shown. - self.assertIn('baked-beans-value', email.body) - self.assertIn('hash-brown-value', email.body) - # Sensitive POST parameters' values are not shown. - self.assertNotIn('sausage-value', email.body) - self.assertNotIn('bacon-value', email.body) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters' names are shown. + self.assertIn(k, email.body) + # Non-sensitive POST parameters' values are shown. + self.assertIn('baked-beans-value', email.body) + self.assertIn('hash-brown-value', email.body) + # Sensitive POST parameters' values are not shown. + self.assertNotIn('sausage-value', email.body) + self.assertNotIn('bacon-value', email.body) def verify_paranoid_email(self, view): """ @@ -425,6 +429,24 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): self.verify_unsafe_response(custom_exception_reporter_filter_view) self.verify_unsafe_email(custom_exception_reporter_filter_view) + def test_sensitive_method(self): + """ + Ensure that the sensitive_variables decorator works with object + methods. + Refs #18379. + """ + with self.settings(DEBUG=True): + self.verify_unsafe_response(sensitive_method_view, + check_for_POST_params=False) + self.verify_unsafe_email(sensitive_method_view, + check_for_POST_params=False) + + with self.settings(DEBUG=False): + self.verify_safe_response(sensitive_method_view, + check_for_POST_params=False) + self.verify_safe_email(sensitive_method_view, + check_for_POST_params=False) + class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin): """ diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py index 8e530cd2d8..2836d1bdde 100644 --- a/tests/regressiontests/views/views.py +++ b/tests/regressiontests/views/views.py @@ -2,7 +2,6 @@ from __future__ import absolute_import import sys -from django import forms from django.core.exceptions import PermissionDenied from django.core.urlresolvers import get_resolver from django.http import HttpResponse, HttpResponseRedirect @@ -14,7 +13,7 @@ from django.views.decorators.debug import (sensitive_post_parameters, from django.utils.log import getLogger from . import BrokenException, except_args -from .models import Article + def index_page(request): @@ -209,3 +208,23 @@ def custom_exception_reporter_filter_view(request): exc_info = sys.exc_info() send_log(request, exc_info) return technical_500_response(request, *exc_info) + + +class Klass(object): + + @sensitive_variables('sauce') + def method(self, request): + # Do not just use plain strings for the variables' values in the code + # so that the tests don't return false positives when the function's + # source is displayed in the exception report. + cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) + sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']) + try: + raise Exception + except Exception: + exc_info = sys.exc_info() + send_log(request, exc_info) + return technical_500_response(request, *exc_info) + +def sensitive_method_view(request): + return Klass().method(request) \ No newline at end of file From 5ef599c7b32a238bfda6f56cd149bacb61b85c77 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 4 Jun 2012 20:39:54 +0200 Subject: [PATCH 003/519] Used skipUnless decorator to skip tests in geos tests. --- django/contrib/gis/geos/tests/test_geos.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 9946495f54..f435cdaff1 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -1,6 +1,5 @@ import ctypes import random -import unittest from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, GeometryCollection, Point, MultiPoint, Polygon, MultiPolygon, LinearRing, @@ -9,6 +8,8 @@ from django.contrib.gis.geos.base import gdal, numpy, GEOSBase from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geometry.test_data import TestDataMixin +from django.utils import unittest + class GEOSTest(unittest.TestCase, TestDataMixin): @@ -195,9 +196,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export + @unittest.skipUnless(gdal.HAS_GDAL and gdal.GEOJSON, "gdal >= 1.5 is required") def test_json(self): "Testing GeoJSON input/output (via GDAL)." - if not gdal or not gdal.GEOJSON: return for g in self.geometries.json_geoms: geom = GEOSGeometry(g.wkt) if not hasattr(g, 'not_equal'): @@ -813,9 +814,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # And, they should be equal. self.assertEqual(gc1, gc2) + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_gdal(self): "Testing `ogr` and `srs` properties." - if not gdal.HAS_GDAL: return g1 = fromstr('POINT(5 23)') self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry)) self.assertEqual(g1.srs, None) @@ -835,9 +836,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertNotEqual(poly._ptr, cpy1._ptr) self.assertNotEqual(poly._ptr, cpy2._ptr) + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_transform(self): "Testing `transform` method." - if not gdal.HAS_GDAL: return orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774) @@ -963,9 +964,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(geom, tmpg) if not no_srid: self.assertEqual(geom.srid, tmpg.srid) + @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") def test_prepared(self): "Testing PreparedGeometry support." - if not GEOS_PREPARE: return # Creating a simple multipolygon and getting a prepared version. mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))') prep = mpoly.prepared @@ -990,10 +991,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): for geom, merged in zip(ref_geoms, ref_merged): self.assertEqual(merged, geom.merged) + @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") def test_valid_reason(self): "Testing IsValidReason support" - # Skipping tests if GEOS < v3.1. - if not GEOS_PREPARE: return g = GEOSGeometry("POINT(0 0)") self.assertTrue(g.valid) From 0199bdc0b411d4aba0173053417bbeea8224b26b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 4 Jun 2012 20:31:23 +0100 Subject: [PATCH 004/519] Rewrote security.txt SSL docs, noting SECURE_PROXY_SSL_HEADER. --- docs/topics/security.txt | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 193d0029a4..151853d4ac 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -122,22 +122,19 @@ transferred between client and server, and in some cases -- **active** network attackers -- to alter data that is sent in either direction. If you want the protection that HTTPS provides, and have enabled it on your -server, there are some additional steps to consider to ensure that sensitive -information is not leaked: +server, there are some additional steps you may need: + +* If necessary, set :setting:`SECURE_PROXY_SSL_HEADER`, ensuring that you have + understood the warnings there thoroughly. Failure to do this can result + in CSRF vulnerabilities, and failure to do it correctly can also be + dangerous! * Set up redirection so that requests over HTTP are redirected to HTTPS. - It is possible to do this with a piece of Django middleware. However, this has - problems for the common case of a Django app running behind a reverse - proxy. Often, reverse proxies are configured to set the ``X-Forwarded-SSL`` - header (or equivalent) if the incoming connection was HTTPS, and the absence - of this header could be used to detect a request that was not HTTPS. However, - this method usually cannot be relied on, as a client, or a malicious active - network attacker, could also set this header. - - So, for the case of a reverse proxy, it is recommended that the main Web - server should be configured to do the redirect to HTTPS, or configured to send - HTTP requests to an app that unconditionally redirects to HTTPS. + This could be done using a custom middleware. Please note the caveats under + :setting:`SECURE_PROXY_SSL_HEADER`. For the case of a reverse proxy, it may be + easier or more secure to configure the main Web server to do the redirect to + HTTPS. * Use 'secure' cookies. From 840ffd80baad85c05670d6b642f654cffaa93cc3 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 4 Jun 2012 20:39:57 +0100 Subject: [PATCH 005/519] Noted that SECURE_PROXY_SSL_HEADER is needed by CSRF protection. Both false positives and false negatives of HttpRequest.is_secure can be dangerous. --- docs/ref/settings.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 398c90661b..a1b76f65e1 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1605,7 +1605,8 @@ method. This takes some explanation. By default, ``is_secure()`` is able to determine whether a request is secure by looking at whether the requested URL uses -"https://". +"https://". This is important for Django's CSRF protection, and may be used +by your own code or third-party apps. If your Django app is behind a proxy, though, the proxy may be "swallowing" the fact that a request is HTTPS, using a non-HTTPS connection between the proxy @@ -1635,7 +1636,7 @@ available in ``request.META``.) .. warning:: - **You will probably open security holes in your site if you set this without knowing what you're doing. Seriously.** + **You will probably open security holes in your site if you set this without knowing what you're doing. And if you fail to set it when you should. Seriously.** Make sure ALL of the following are true before setting this (assuming the values from the example above): From fbb73894395b728ec96c661da6f87523718c5398 Mon Sep 17 00:00:00 2001 From: "martin.bohacek" Date: Tue, 5 Jun 2012 13:28:32 +0200 Subject: [PATCH 006/519] Ticket #17804 fix. --- django/contrib/admin/options.py | 2 +- django/contrib/admin/static/admin/css/forms.css | 4 ++++ django/contrib/admin/widgets.py | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 2071792bdb..3b899e552a 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -49,7 +49,7 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = { models.TextField: {'widget': widgets.AdminTextareaWidget}, models.URLField: {'widget': widgets.AdminURLFieldWidget}, models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, - models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget}, + models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget}, models.CharField: {'widget': widgets.AdminTextInputWidget}, models.ImageField: {'widget': widgets.AdminFileWidget}, models.FileField: {'widget': widgets.AdminFileWidget}, diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index 9d39b3fe89..efec04b670 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -226,6 +226,10 @@ body.popup .submit-row { width: 5em; } +.vBigIntegerField { + width: 10em; +} + .vForeignKeyRawIdAdminField { width: 5em; } diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 29958b27a4..980863ed84 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -303,12 +303,17 @@ class AdminURLFieldWidget(forms.TextInput): super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) class AdminIntegerFieldWidget(forms.TextInput): + class_name = 'vIntegerField' + def __init__(self, attrs=None): - final_attrs = {'class': 'vIntegerField'} + final_attrs = {'class': self.class_name} if attrs is not None: final_attrs.update(attrs) super(AdminIntegerFieldWidget, self).__init__(attrs=final_attrs) +class AdminBigIntegerFieldWidget(AdminIntegerFieldWidget): + class_name = 'vBigIntegerField' + class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput): def __init__(self, attrs=None): final_attrs = {'class': 'vCommaSeparatedIntegerField'} From fedac99c859c626544d38fc0ea07207ef3fd101a Mon Sep 17 00:00:00 2001 From: Honza Kral Date: Tue, 5 Jun 2012 16:46:15 +0200 Subject: [PATCH 007/519] Fixed #15926 -- Added option --no-initial-data to syncdb and flush. Thanks msiedlarek, jpaugh64 and vlinhart! --- django/core/management/commands/flush.py | 7 ++- django/core/management/commands/syncdb.py | 5 +- docs/ref/django-admin.txt | 15 ++++++ .../fixtures_model_package/tests.py | 54 +++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index ce3c6e856b..7eb71fd1bb 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -16,6 +16,8 @@ class Command(NoArgsCommand): make_option('--database', action='store', dest='database', default=DEFAULT_DB_ALIAS, help='Nominates a database to flush. ' 'Defaults to the "default" database.'), + make_option('--no-initial-data', action='store_false', dest='load_initial_data', default=True, + help='Tells Django not to load any initial data after database synchronization.'), ) help = ('Returns the database to the state it was in immediately after ' 'syncdb was executed. This means that all data will be removed ' @@ -79,7 +81,10 @@ The full error: %s""" % (connection.settings_dict['NAME'], e)) # Reinstall the initial_data fixture. kwargs = options.copy() kwargs['database'] = db - call_command('loaddata', 'initial_data', **kwargs) + if options.get('load_initial_data', True): + # Reinstall the initial_data fixture. + from django.core.management import call_command + call_command('loaddata', 'initial_data', **options) else: self.stdout.write("Flush cancelled.\n") diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index 88caea152c..f751a3768b 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -14,6 +14,8 @@ class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( make_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), + make_option('--no-initial-data', action='store_false', dest='load_initial_data', default=True, + help='Tells Django not to load any initial data after database synchronization.'), make_option('--database', action='store', dest='database', default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. ' 'Defaults to the "default" database.'), @@ -25,9 +27,6 @@ class Command(NoArgsCommand): verbosity = int(options.get('verbosity')) interactive = options.get('interactive') show_traceback = options.get('traceback') - - # Stealth option -- 'load_initial_data' is used by the testing setup - # process to disable initial fixture loading. load_initial_data = options.get('load_initial_data', True) self.style = no_style() diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 0ea8252c71..f04f9ee058 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -245,6 +245,14 @@ prompts. The :djadminopt:`--database` option may be used to specify the database to flush. +--no-initial-data +~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.5 + +Use ``--no-initial-data`` to avoid loading the initial_data fixture. + + inspectdb --------- @@ -1024,6 +1032,13 @@ prompts. The :djadminopt:`--database` option can be used to specify the database to synchronize. +--no-initial-data +~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.5 + +Use ``--no-initial-data`` to avoid loading the initial_data fixture. + test ----------------------------- diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/modeltests/fixtures_model_package/tests.py index 4da20697fe..0cc90381b8 100644 --- a/tests/modeltests/fixtures_model_package/tests.py +++ b/tests/modeltests/fixtures_model_package/tests.py @@ -20,6 +20,60 @@ class SampleTestCase(TestCase): ) +class TestNoInitialDataLoading(TestCase): + def test_syncdb(self): + Book.objects.all().delete() + + management.call_command( + 'syncdb', + verbosity=0, + commit=False + ) + self.assertQuerysetEqual( + Book.objects.all(), [ + u'Achieving self-awareness of Python programs' + ], + lambda a: a.name + ) + + Book.objects.all().delete() + + management.call_command( + 'syncdb', + verbosity=0, + commit=False, + load_initial_data=False + ) + self.assertQuerysetEqual(Book.objects.all(), []) + + def test_flush(self): + Book.objects.all().delete() + + management.call_command( + 'flush', + verbosity=0, + interactive=False, + commit=False + ) + self.assertQuerysetEqual( + Book.objects.all(), [ + u'Achieving self-awareness of Python programs' + ], + lambda a: a.name + ) + + Book.objects.all().delete() + + management.call_command( + 'flush', + verbosity=0, + commit=False, + interactive=False, + load_initial_data=False + ) + self.assertQuerysetEqual(Book.objects.all(), []) + + class FixtureTestCase(TestCase): def test_initial_data(self): "Fixtures can load initial data into models defined in packages" From 17824e2b74ca02b9914e853c818fa512a0f9ef34 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 6 Jun 2012 10:05:27 +0200 Subject: [PATCH 008/519] Fixed #17736 -- Kept maximal floating-point accuracy in from_bbox When constructing a polygon with Polygon.from_bbox, do not convert parameters to strings at this stage (str defaults to 12 significant digits). Thanks tdihp@hotmail.com for the report and David Eklung for the patch. --- AUTHORS | 1 + django/contrib/gis/geos/polygon.py | 7 +++++-- django/contrib/gis/geos/tests/test_geos.py | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 45a0544e65..ad0e886981 100644 --- a/AUTHORS +++ b/AUTHORS @@ -171,6 +171,7 @@ answer newbie questions, and generally made Django that much better: Clint Ecker Nick Efford eibaan@gmail.com + David Eklund Julia Elman enlight Enrico diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py index 77ce60cf16..2c0f90be3c 100644 --- a/django/contrib/gis/geos/polygon.py +++ b/django/contrib/gis/geos/polygon.py @@ -55,8 +55,11 @@ class Polygon(GEOSGeometry): def from_bbox(cls, bbox): "Constructs a Polygon from a bounding box (4-tuple)." x0, y0, x1, y1 = bbox - return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( - x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) ) + for z in bbox: + if not isinstance(z, (int, long, float)): + return GEOSGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % + (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)) + return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0))) ### These routines are needed for list-like operation w/ListMixin ### def _create_polygon(self, length, items): diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index f435cdaff1..ed38283dfc 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -384,6 +384,13 @@ class GEOSTest(unittest.TestCase, TestDataMixin): p = Polygon.from_bbox(bbox) self.assertEqual(bbox, p.extent) + # Testing numerical precision + x = 3.14159265358979323 + bbox = (0, 0, 1, x) + p = Polygon.from_bbox(bbox) + y = p.extent[-1] + self.assertEqual(format(x, '.13f'), format(y, '.13f')) + def test_polygons(self): "Testing Polygon objects." From 29a80354ab5e5b85fa37863f70b1cf95646dc699 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 6 Jun 2012 10:32:03 +0200 Subject: [PATCH 009/519] Added alt attribute to img tags in docs. This is a good practice for accessibility. Thanks Jessica McKellar for the report. --- docs/howto/static-files.txt | 8 ++++---- docs/intro/overview.txt | 2 +- docs/ref/contrib/staticfiles.txt | 4 ++-- docs/ref/models/fields.txt | 6 +++--- docs/ref/templates/builtins.txt | 13 +++++++------ 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 29a2c2f6e2..db76c24742 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -68,7 +68,7 @@ Basic usage .. code-block:: html+django - + Hi! See :ref:`staticfiles-in-templates` for more details, **including** an alternate method using a template tag. @@ -131,7 +131,7 @@ You could, of course, simply hardcode the path to you assets in the templates: .. code-block:: html - + Sample image Of course, there are some serious problems with this: it doesn't work well in development, and it makes it *very* hard to change where you've deployed your @@ -167,7 +167,7 @@ Once that's done, you can refer to :setting:`STATIC_URL` in your templates: .. code-block:: html+django - + Hi! If ``{{ STATIC_URL }}`` isn't working in your template, you're probably not using :class:`~django.template.RequestContext` when rendering the template. @@ -193,7 +193,7 @@ tag. It builds the URL for the given relative path by using the configured .. code-block:: html+django {% load staticfiles %} - + Hi! It is also able to consume standard context variables, e.g. assuming a ``user_stylesheet`` variable is passed to the template: diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index bd2fdfdb6c..49233fb8a7 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -279,7 +279,7 @@ Here's what the "base.html" template might look like: {% block title %}{% endblock %} - Logo + Logo {% block content %}{% endblock %} diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 2bdf825316..126bcdd4e6 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -380,10 +380,10 @@ full URL for the given relative path, e.g.: .. code-block:: html+django {% load static from staticfiles %} - + Hi! The previous example is equal to calling the ``url`` method of an instance of -:setting:`STATICFILES_STORAGE` with ``"css/base.css"``. This is especially +:setting:`STATICFILES_STORAGE` with ``"images/hi.jpg"``. This is especially useful when using a non-local storage backend to deploy files as documented in :ref:`staticfiles-from-cdn`. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index eab7ad91e1..86894effea 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -683,7 +683,7 @@ directory on the filesystem. Has three special arguments, of which the first is Optional. A regular expression, as a string, that :class:`FilePathField` will use to filter filenames. Note that the regex will be applied to the base filename, not the full path. Example: ``"foo.*\.txt$"``, which will - match a file called ``foo23.txt`` but not ``bar.txt`` or ``foo23.gif``. + match a file called ``foo23.txt`` but not ``bar.txt`` or ``foo23.png``. .. attribute:: FilePathField.recursive @@ -714,9 +714,9 @@ base filename, not the full path. So, this example:: FilePathField(path="/home/images", match="foo.*", recursive=True) -...will match ``/home/images/foo.gif`` but not ``/home/images/foo/bar.gif`` +...will match ``/home/images/foo.png`` but not ``/home/images/foo/bar.png`` because the :attr:`~FilePathField.match` applies to the base filename -(``foo.gif`` and ``bar.gif``). +(``foo.png`` and ``bar.png``). By default, :class:`FilePathField` instances are created as ``varchar(100)`` columns in your database. As with other fields, you diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 5019963f60..bc86cb6919 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1069,7 +1069,8 @@ value to a maximum value, and then applies that ratio to a constant. For example:: - + Bar Above, if ``this_value`` is 175 and ``max_value`` is 200, the image in the above example will be 88 pixels wide (because 175/200 = .875; .875 * 100 = 87.5 @@ -2361,7 +2362,7 @@ using :class:`~django.template.RequestContext` or not. .. code-block:: html+django {% load static %} - + Hi! It is also able to consume standard context variables, e.g. assuming a ``user_stylesheet`` variable is passed to the template: @@ -2380,7 +2381,7 @@ It is also able to consume standard context variables, e.g. assuming a :ref:`using a cloud service to serve static files`:: {% load static from staticfiles %} - + Hi! .. templatetag:: get_static_prefix @@ -2395,7 +2396,7 @@ into the template, you can use the :ttag:`get_static_prefix` template tag instead:: {% load static %} - + Hi! There's also a second form you can use to avoid extra processing if you need the value multiple times:: @@ -2403,8 +2404,8 @@ the value multiple times:: {% load static %} {% get_static_prefix as STATIC_PREFIX %} - - + Hi! + Hello! .. templatetag:: get_media_prefix From 9c096ab981d57123a0774f9ddf229084e0bb54a1 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 6 Jun 2012 10:40:11 +0200 Subject: [PATCH 010/519] Fixed #17328 -- Added OpenLayersWidget _has_changed method Thanks Will Hardy for the report and the patch. --- AUTHORS | 1 + django/contrib/gis/admin/widgets.py | 24 +++++++++++++++++++++- django/contrib/gis/tests/geoadmin/tests.py | 22 ++++++++++++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index ad0e886981..5e1314bdd8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -230,6 +230,7 @@ answer newbie questions, and generally made Django that much better: Scot Hacker dAniel hAhler hambaloney + Will Hardy Brian Harring Brant Harris Ronny Haryanto diff --git a/django/contrib/gis/admin/widgets.py b/django/contrib/gis/admin/widgets.py index aaffae8f5f..c7b48e4263 100644 --- a/django/contrib/gis/admin/widgets.py +++ b/django/contrib/gis/admin/widgets.py @@ -4,7 +4,7 @@ from django.templatetags.static import static from django.utils import translation from django.contrib.gis.gdal import OGRException -from django.contrib.gis.geos import GEOSGeometry, GEOSException +from django.contrib.gis.geos import GEOSGeometry, GEOSException, fromstr # Creating a template context that contains Django settings # values needed by admin map templates. @@ -104,3 +104,25 @@ class OpenLayersWidget(Textarea): raise TypeError map_options[js_name] = value return map_options + + def _has_changed(self, initial, data): + """ Compare geographic value of data with its initial value. """ + + # Ensure we are dealing with a geographic object + if isinstance(initial, basestring): + try: + initial = GEOSGeometry(initial) + except (GEOSException, ValueError): + initial = None + + # Only do a geographic comparison if both values are available + if initial and data: + data = fromstr(data) + data.transform(initial.srid) + # If the initial value was not added by the browser, the geometry + # provided may be slightly different, the first time it is saved. + # The comparison is done with a very low tolerance. + return not initial.equals_exact(data, tolerance=0.000001) + else: + # Check for change of state of existence + return bool(initial) != bool(data) diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py index 53c00c6588..6fadebdb9a 100644 --- a/django/contrib/gis/tests/geoadmin/tests.py +++ b/django/contrib/gis/tests/geoadmin/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from django.test import TestCase from django.contrib.gis import admin -from django.contrib.gis.geos import Point +from django.contrib.gis.geos import GEOSGeometry, Point from .models import City @@ -10,7 +10,7 @@ from .models import City class GeoAdminTest(TestCase): urls = 'django.contrib.gis.tests.geoadmin.urls' - def test01_ensure_geographic_media(self): + def test_ensure_geographic_media(self): geoadmin = admin.site._registry[City] admin_js = geoadmin.media.render_js() self.assertTrue(any([geoadmin.openlayers_url in js for js in admin_js])) @@ -33,3 +33,21 @@ class GeoAdminTest(TestCase): self.assertIn( """geodjango_point.layers.base = new OpenLayers.Layer.WMS("OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: \'basic\', format: 'image/jpeg'});""", result) + + def test_olwidget_has_changed(self): + """ Check that changes are accurately noticed by OpenLayersWidget. """ + geoadmin = admin.site._registry[City] + form = geoadmin.get_changelist_form(None)() + has_changed = form.fields['point'].widget._has_changed + + initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326) + data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)" + data_almost_same = "SRID=3857;POINT(1493879.2754093990 6894592.019687590)" + data_changed = "SRID=3857;POINT(1493884.0527237 6894593.8111804)" + + self.assertTrue(has_changed(None, data_changed)) + self.assertTrue(has_changed(initial, "")) + self.assertFalse(has_changed(None, "")) + self.assertFalse(has_changed(initial, data_same)) + self.assertFalse(has_changed(initial, data_almost_same)) + self.assertTrue(has_changed(initial, data_changed)) From eb286aa22f2a83a091b856ab2ecda1bded4203e3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 6 Jun 2012 10:53:16 +0200 Subject: [PATCH 011/519] Delayed encoding of password and salt in password checking. Applied the rule that string encoding should happen as late as possible. This is also a preparation for Python 3 compatibility. --- django/contrib/auth/forms.py | 3 --- django/contrib/auth/hashers.py | 13 ++++--------- django/utils/crypto.py | 3 +++ docs/releases/1.5.txt | 9 +++++++++ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index f0ef124b20..deb6cb9414 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,7 +1,6 @@ from django import forms from django.forms.util import flatatt from django.template import loader -from django.utils.encoding import smart_str from django.utils.http import int_to_base36 from django.utils.safestring import mark_safe from django.utils.translation import ugettext, ugettext_lazy as _ @@ -26,8 +25,6 @@ class ReadOnlyPasswordHashWidget(forms.Widget): final_attrs = self.build_attrs(attrs) - encoded = smart_str(encoded) - if len(encoded) == 32 and '$' not in encoded: algorithm = 'unsalted_md5' else: diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 52d204cce6..11a6313ed7 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -40,9 +40,6 @@ def check_password(password, encoded, setter=None, preferred='default'): return False preferred = get_hasher(preferred) - raw_password = password - password = smart_str(password) - encoded = smart_str(encoded) if len(encoded) == 32 and '$' not in encoded: hasher = get_hasher('unsalted_md5') @@ -53,7 +50,7 @@ def check_password(password, encoded, setter=None, preferred='default'): must_update = hasher.algorithm != preferred.algorithm is_correct = hasher.verify(password, encoded) if setter and is_correct and must_update: - setter(raw_password) + setter(password) return is_correct @@ -69,11 +66,9 @@ def make_password(password, salt=None, hasher='default'): return UNUSABLE_PASSWORD hasher = get_hasher(hasher) - password = smart_str(password) if not salt: salt = hasher.salt() - salt = smart_str(salt) return hasher.encode(password, salt) @@ -291,7 +286,7 @@ class SHA1PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.sha1(salt + password).hexdigest() + hash = hashlib.sha1(smart_str(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -319,7 +314,7 @@ class MD5PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.md5(salt + password).hexdigest() + hash = hashlib.md5(smart_str(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -353,7 +348,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): - return hashlib.md5(password).hexdigest() + return hashlib.md5(smart_str(password)).hexdigest() def verify(self, password, encoded): encoded_2 = self.encode(password, '') diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 9d6486c601..0fce06012b 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -22,6 +22,7 @@ except NotImplementedError: using_sysrandom = False from django.conf import settings +from django.utils.encoding import smart_str _trans_5c = b"".join([chr(x ^ 0x5C) for x in xrange(256)]) @@ -137,6 +138,8 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None): assert iterations > 0 if not digest: digest = hashlib.sha256 + password = smart_str(password) + salt = smart_str(salt) hlen = digest().digest_size if not dklen: dklen = hlen diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 0d86a52670..696f332285 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -128,6 +128,15 @@ 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. +String types of hasher method parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have written a :ref:`custom password hasher `, +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_str` utility to encode the strings. + Features deprecated in 1.5 ========================== From 70a0351fefaab4bce8c1085008ec5f12ba6cf279 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 6 Jun 2012 11:06:33 +0200 Subject: [PATCH 012/519] Fixed #18184 -- Moved algorithm identification code to hashers module Thanks Eli Collins for the report and the patch. --- django/contrib/auth/forms.py | 9 ++------- django/contrib/auth/hashers.py | 22 ++++++++++++++++------ django/contrib/auth/tests/hashers.py | 10 +++++++++- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index deb6cb9414..780b0c015e 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _ from django.contrib.auth import authenticate from django.contrib.auth.models import User -from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher +from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, identify_hasher from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import get_current_site @@ -25,13 +25,8 @@ class ReadOnlyPasswordHashWidget(forms.Widget): final_attrs = self.build_attrs(attrs) - if len(encoded) == 32 and '$' not in encoded: - algorithm = 'unsalted_md5' - else: - algorithm = encoded.split('$', 1)[0] - try: - hasher = get_hasher(algorithm) + hasher = identify_hasher(encoded) except ValueError: summary = "Invalid password format or unknown hashing algorithm." else: diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 11a6313ed7..0897de8d84 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -40,12 +40,7 @@ def check_password(password, encoded, setter=None, preferred='default'): return False preferred = get_hasher(preferred) - - if len(encoded) == 32 and '$' not in encoded: - hasher = get_hasher('unsalted_md5') - else: - algorithm = encoded.split('$', 1)[0] - hasher = get_hasher(algorithm) + hasher = identify_hasher(encoded) must_update = hasher.algorithm != preferred.algorithm is_correct = hasher.verify(password, encoded) @@ -120,6 +115,21 @@ def get_hasher(algorithm='default'): return HASHERS[algorithm] +def identify_hasher(encoded): + """ + Returns an instance of a loaded password hasher. + + Identifies hasher algorithm by examining encoded hash, and calls + get_hasher() to return hasher. Raises ValueError if + algorithm cannot be identified, or if hasher is not loaded. + """ + if len(encoded) == 32 and '$' not in encoded: + algorithm = 'unsalted_md5' + else: + algorithm = encoded.split('$', 1)[0] + return get_hasher(algorithm) + + def mask_hash(hash, show=6, char="*"): """ Returns the given hash, with only the first ``show`` number shown. The diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py index 8a11511688..15fc1d1da7 100644 --- a/django/contrib/auth/tests/hashers.py +++ b/django/contrib/auth/tests/hashers.py @@ -1,7 +1,7 @@ from django.conf.global_settings import PASSWORD_HASHERS as default_hashers from django.contrib.auth.hashers import (is_password_usable, check_password, make_password, PBKDF2PasswordHasher, load_hashers, - PBKDF2SHA1PasswordHasher, get_hasher, UNUSABLE_PASSWORD) + PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD) from django.utils import unittest from django.utils.unittest import skipUnless @@ -36,6 +36,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") def test_sha1(self): encoded = make_password('letmein', 'seasalt', 'sha1') @@ -44,6 +45,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "sha1") def test_md5(self): encoded = make_password('letmein', 'seasalt', 'md5') @@ -52,6 +54,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "md5") def test_unsalted_md5(self): encoded = make_password('letmein', 'seasalt', 'unsalted_md5') @@ -59,6 +62,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") @skipUnless(crypt, "no crypt module to generate password.") def test_crypt(self): @@ -67,6 +71,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "crypt") @skipUnless(bcrypt, "py-bcrypt not installed") def test_bcrypt(self): @@ -75,6 +80,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(encoded.startswith('bcrypt$')) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt") def test_unusable(self): encoded = make_password(None) @@ -84,11 +90,13 @@ class TestUtilsHashPass(unittest.TestCase): self.assertFalse(check_password('', encoded)) self.assertFalse(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertRaises(ValueError, identify_hasher, encoded) def test_bad_algorithm(self): def doit(): make_password('letmein', hasher='lolcat') self.assertRaises(ValueError, doit) + self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash") def test_low_level_pkbdf2(self): hasher = PBKDF2PasswordHasher() From 7edf231d4666604a455621c4800c6b031a2f0b5b Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Wed, 6 Jun 2012 11:54:26 +0200 Subject: [PATCH 013/519] Replaced documentation snippets using "gender" with less sensitive examples. --- docs/howto/custom-model-fields.txt | 1 - docs/ref/forms/widgets.txt | 2 - docs/ref/models/fields.txt | 22 +++-- docs/ref/models/instances.txt | 36 ++++---- docs/ref/templates/builtins.txt | 127 ++++++++++++----------------- docs/topics/db/models.txt | 19 +++-- 6 files changed, 98 insertions(+), 109 deletions(-) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 5c2fed3b34..f1dd9dc56c 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -334,7 +334,6 @@ Once you have ``MytypeField``, you can use it in any model, just like any other class Person(models.Model): name = models.CharField(max_length=80) - gender = models.CharField(max_length=1) something_else = MytypeField() If you aim to build a database-agnostic application, you should account for diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index ac110c2ee8..88d0d706cd 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -47,14 +47,12 @@ widget on the field. In the following example, the from django.forms.extras.widgets import SelectDateWidget BIRTH_YEAR_CHOICES = ('1980', '1981', '1982') - GENDER_CHOICES = (('m', 'Male'), ('f', 'Female')) FAVORITE_COLORS_CHOICES = (('blue', 'Blue'), ('green', 'Green'), ('black', 'Black')) class SimpleForm(forms.Form): birth_year = DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES)) - gender = ChoiceField(widget=RadioSelect, choices=GENDER_CHOICES) favorite_colors = forms.MultipleChoiceField(required=False, widget=CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 86894effea..629efc0432 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -102,20 +102,26 @@ element is the human-readable name for the option. The choices list can be defined either as part of your model class:: class Foo(models.Model): - GENDER_CHOICES = ( - ('M', 'Male'), - ('F', 'Female'), + YEAR_IN_SCHOOL_CHOICES = ( + ('FR', 'Freshman'), + ('SO', 'Sophomore'), + ('JR', 'Junior'), + ('SR', 'Senior'), + ('GR', 'Graduate'), ) - gender = models.CharField(max_length=1, choices=GENDER_CHOICES) + year_in_school = models.CharField(max_length=2, choices=YEAR_IN_SCHOOL_CHOICES) or outside your model class altogether:: - GENDER_CHOICES = ( - ('M', 'Male'), - ('F', 'Female'), + YEAR_IN_SCHOOL_CHOICES = ( + ('FR', 'Freshman'), + ('SO', 'Sophomore'), + ('JR', 'Junior'), + ('SR', 'Senior'), + ('GR', 'Graduate'), ) class Foo(models.Model): - gender = models.CharField(max_length=1, choices=GENDER_CHOICES) + year_in_school = models.CharField(max_length=2, choices=YEAR_IN_SCHOOL_CHOICES) You can also collect your available choices into named groups that can be used for organizational purposes:: diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index fb0fcc046d..50fb085d38 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -572,25 +572,29 @@ might have some of the following methods: For every field that has :attr:`~django.db.models.Field.choices` set, the object will have a ``get_FOO_display()`` method, where ``FOO`` is the name of -the field. This method returns the "human-readable" value of the field. For -example, in the following model:: +the field. This method returns the "human-readable" value of the field. - GENDER_CHOICES = ( - ('M', 'Male'), - ('F', 'Female'), - ) - class Person(models.Model): - name = models.CharField(max_length=20) - gender = models.CharField(max_length=1, choices=GENDER_CHOICES) +For example:: -...each ``Person`` instance will have a ``get_gender_display()`` method. Example:: + from django.db import models - >>> p = Person(name='John', gender='M') - >>> p.save() - >>> p.gender - 'M' - >>> p.get_gender_display() - 'Male' + class Person(models.Model): + SHIRT_SIZES = ( + (u'S', u'Small'), + (u'M', u'Medium'), + (u'L', u'Large'), + ) + name = models.CharField(max_length=60) + shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) + + :: + + >>> p = Person(name="Fred Flintstone", shirt_size="L") + >>> p.save() + >>> p.shirt_size + u'L' + >>> p.get_shirt_size_display() + u'Large' .. method:: Model.get_next_by_FOO(\**kwargs) .. method:: Model.get_previous_by_FOO(\**kwargs) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index bc86cb6919..805aaf7f6f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -771,48 +771,41 @@ regroup Regroups a list of alike objects by a common attribute. -This complex tag is best illustrated by use of an example: say that ``people`` -is a list of people represented by dictionaries with ``first_name``, -``last_name``, and ``gender`` keys: +This complex tag is best illustrated by way of an example: say that "places" is a list of cities represented by dictionaries containing ``"name"``, ``"population"``, and ``"country"`` keys: .. code-block:: python - people = [ - {'first_name': 'George', 'last_name': 'Bush', 'gender': 'Male'}, - {'first_name': 'Bill', 'last_name': 'Clinton', 'gender': 'Male'}, - {'first_name': 'Margaret', 'last_name': 'Thatcher', 'gender': 'Female'}, - {'first_name': 'Condoleezza', 'last_name': 'Rice', 'gender': 'Female'}, - {'first_name': 'Pat', 'last_name': 'Smith', 'gender': 'Unknown'}, + cities = [ + {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'}, + {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'}, + {'name': 'New York', 'population': '20,000,000', 'country': 'USA'}, + {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'}, + {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'}, ] -...and you'd like to display a hierarchical list that is ordered by gender, -like this: +...and you'd like to display a hierarchical list that is ordered by country, like this: -* Male: +* India + * Mumbai: 19,000,000 + * Calcutta: 15,000,000 +* USA + * New York: 20,000,000 + * Chicago: 7,000,000 +* Japan + * Tokyo: 33,000,000 - * George Bush - * Bill Clinton -* Female: - - * Margaret Thatcher - * Condoleezza Rice - -* Unknown: - - * Pat Smith - -You can use the ``{% regroup %}`` tag to group the list of people by gender. +You can use the ``{% regroup %}`` tag to group the list of cities by country. The following snippet of template code would accomplish this:: - {% regroup people by gender as gender_list %} + {% regroup cities by country as country_list %}
    - {% for gender in gender_list %} -
  • {{ gender.grouper }} + {% for country in country_list %} +
  • {{ country.grouper }}
      - {% for item in gender.list %} -
    • {{ item.first_name }} {{ item.last_name }}
    • + {% for item in country.list %} +
    • {{ item.name }}: {{ item.population }}
    • {% endfor %}
  • @@ -821,56 +814,45 @@ The following snippet of template code would accomplish this:: Let's walk through this example. ``{% regroup %}`` takes three arguments: the list you want to regroup, the attribute to group by, and the name of the -resulting list. Here, we're regrouping the ``people`` list by the ``gender`` -attribute and calling the result ``gender_list``. +resulting list. Here, we're regrouping the ``cities`` list by the ``country`` +attribute and calling the result ``country_list``. -``{% regroup %}`` produces a list (in this case, ``gender_list``) of +``{% regroup %}`` produces a list (in this case, ``country_list``) of **group objects**. Each group object has two attributes: -* ``grouper`` -- the item that was grouped by (e.g., the string "Male" or - "Female"). -* ``list`` -- a list of all items in this group (e.g., a list of all people - with gender='Male'). +* ``grouper`` -- the item that was grouped by (e.g., the string "India" or + "Japan"). +* ``list`` -- a list of all items in this group (e.g., a list of all cities + with country='India'). Note that ``{% regroup %}`` does not order its input! Our example relies on -the fact that the ``people`` list was ordered by ``gender`` in the first place. -If the ``people`` list did *not* order its members by ``gender``, the -regrouping would naively display more than one group for a single gender. For -example, say the ``people`` list was set to this (note that the males are not +the fact that the ``cities`` list was ordered by ``country`` in the first place. +If the ``cities`` list did *not* order its members by ``country``, the +regrouping would naively display more than one group for a single country. For +example, say the ``cities`` list was set to this (note that the countries are not grouped together): .. code-block:: python - people = [ - {'first_name': 'Bill', 'last_name': 'Clinton', 'gender': 'Male'}, - {'first_name': 'Pat', 'last_name': 'Smith', 'gender': 'Unknown'}, - {'first_name': 'Margaret', 'last_name': 'Thatcher', 'gender': 'Female'}, - {'first_name': 'George', 'last_name': 'Bush', 'gender': 'Male'}, - {'first_name': 'Condoleezza', 'last_name': 'Rice', 'gender': 'Female'}, + cities = [ + {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'}, + {'name': 'New York', 'population': '20,000,000', 'country': 'USA'}, + {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'}, + {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'}, + {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'}, ] -With this input for ``people``, the example ``{% regroup %}`` template code +With this input for ``cities``, the example ``{% regroup %}`` template code above would result in the following output: -* Male: - - * Bill Clinton - -* Unknown: - - * Pat Smith - -* Female: - - * Margaret Thatcher - -* Male: - - * George Bush - -* Female: - - * Condoleezza Rice +* India + * Mumbai: 19,000,000 +* USA + * New York: 20,000,000 +* India + * Calcutta: 15,000,000 +* Japan + * Tokyo: 33,000,000 The easiest solution to this gotcha is to make sure in your view code that the data is ordered according to how you want to display it. @@ -878,27 +860,26 @@ data is ordered according to how you want to display it. Another solution is to sort the data in the template using the :tfilter:`dictsort` filter, if your data is in a list of dictionaries:: - {% regroup people|dictsort:"gender" by gender as gender_list %} - + {% regroup cities|dictsort:"country" by country as country_list %} Grouping on other properties ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Any valid template lookup is a legal grouping attribute for the regroup tag, including methods, attributes, dictionary keys and list items. For -example, if the "gender" field is a foreign key to a class with +example, if the "country" field is a foreign key to a class with an attribute "description," you could use:: - {% regroup people by gender.description as gender_list %} + {% regroup cities by country.description as country_list %} -Or, if ``gender`` is a field with ``choices``, it will have a +Or, if ``country`` is a field with ``choices``, it will have a :meth:`^django.db.models.Model.get_FOO_display` method available as an attribute, allowing you to group on the display string rather than the ``choices`` key:: - {% regroup people by get_gender_display as gender_list %} + {% regroup cities by get_country_display as country_list %} -``{{ gender.grouper }}`` will now display the value fields from the +``{{ country.grouper }}`` will now display the value fields from the ``choices`` set rather than the keys. .. templatetag:: spaceless diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 9d2ba82c34..c336faed2b 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -172,21 +172,22 @@ ones: from django.db import models class Person(models.Model): - GENDER_CHOICES = ( - (u'M', u'Male'), - (u'F', u'Female'), + SHIRT_SIZES = ( + (u'S', u'Small'), + (u'M', u'Medium'), + (u'L', u'Large'), ) name = models.CharField(max_length=60) - gender = models.CharField(max_length=2, choices=GENDER_CHOICES) + shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) :: - >>> p = Person(name="Fred Flintstone", gender="M") + >>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() - >>> p.gender - u'M' - >>> p.get_gender_display() - u'Male' + >>> p.shirt_size + u'L' + >>> p.get_shirt_size_display() + u'Large' :attr:`~Field.default` The default value for the field. This can be a value or a callable From f0664dc8ae541b21e3cf421725e7933a9b3a799e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 6 Jun 2012 13:53:12 +0200 Subject: [PATCH 014/519] Made TestNoInitialDataLoading pass with MySQL (Refs #15926) --- django/core/management/commands/flush.py | 5 ++-- django/core/management/commands/syncdb.py | 4 +-- .../fixtures_model_package/tests.py | 27 +------------------ 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 7eb71fd1bb..2fc2e7ed26 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -81,9 +81,8 @@ The full error: %s""" % (connection.settings_dict['NAME'], e)) # Reinstall the initial_data fixture. kwargs = options.copy() kwargs['database'] = db - if options.get('load_initial_data', True): - # Reinstall the initial_data fixture. - from django.core.management import call_command + if options.get('load_initial_data'): + # Reinstall the initial_data fixture. call_command('loaddata', 'initial_data', **options) else: diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index f751a3768b..cf26e754ae 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -2,6 +2,7 @@ from optparse import make_option import traceback from django.conf import settings +from django.core.management import call_command from django.core.management.base import NoArgsCommand from django.core.management.color import no_style from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal @@ -27,7 +28,7 @@ class Command(NoArgsCommand): verbosity = int(options.get('verbosity')) interactive = options.get('interactive') show_traceback = options.get('traceback') - load_initial_data = options.get('load_initial_data', True) + load_initial_data = options.get('load_initial_data') self.style = no_style() @@ -158,6 +159,5 @@ class Command(NoArgsCommand): # Load initial_data fixtures (unless that has been disabled) if load_initial_data: - from django.core.management import call_command call_command('loaddata', 'initial_data', verbosity=verbosity, database=db, skip_validation=True) diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/modeltests/fixtures_model_package/tests.py index 0cc90381b8..1d2cc23577 100644 --- a/tests/modeltests/fixtures_model_package/tests.py +++ b/tests/modeltests/fixtures_model_package/tests.py @@ -27,34 +27,12 @@ class TestNoInitialDataLoading(TestCase): management.call_command( 'syncdb', verbosity=0, - commit=False - ) - self.assertQuerysetEqual( - Book.objects.all(), [ - u'Achieving self-awareness of Python programs' - ], - lambda a: a.name - ) - - Book.objects.all().delete() - - management.call_command( - 'syncdb', - verbosity=0, - commit=False, load_initial_data=False ) self.assertQuerysetEqual(Book.objects.all(), []) def test_flush(self): - Book.objects.all().delete() - - management.call_command( - 'flush', - verbosity=0, - interactive=False, - commit=False - ) + # Test presence of fixture (flush called by TransactionTestCase) self.assertQuerysetEqual( Book.objects.all(), [ u'Achieving self-awareness of Python programs' @@ -62,12 +40,9 @@ class TestNoInitialDataLoading(TestCase): lambda a: a.name ) - Book.objects.all().delete() - management.call_command( 'flush', verbosity=0, - commit=False, interactive=False, load_initial_data=False ) From 4fea46a030689d7ae774ee0217dbce28de5a1b29 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 6 Jun 2012 14:17:32 +0100 Subject: [PATCH 015/519] Fixed #18309 - Prefetch related does not work for fkey to multitable inherited model Thanks to milosu for the report, tests and initial patch. --- django/db/models/fields/related.py | 4 ++-- tests/modeltests/prefetch_related/models.py | 3 +++ tests/modeltests/prefetch_related/tests.py | 12 +++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index c4f95a12d2..312a3822f6 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -329,10 +329,10 @@ class ReverseSingleRelatedObjectDescriptor(object): return QuerySet(self.field.rel.to).using(db) def get_prefetch_query_set(self, instances): - rel_obj_attr = attrgetter(self.field.rel.field_name) + other_field = self.field.rel.get_related_field() + rel_obj_attr = attrgetter(other_field.attname) instance_attr = attrgetter(self.field.attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) - other_field = self.field.rel.get_related_field() if other_field.rel: params = {'%s__pk__in' % self.field.rel.field_name: instances_dict.keys()} else: diff --git a/tests/modeltests/prefetch_related/models.py b/tests/modeltests/prefetch_related/models.py index 1dc034fa6c..589f78c7d3 100644 --- a/tests/modeltests/prefetch_related/models.py +++ b/tests/modeltests/prefetch_related/models.py @@ -68,6 +68,9 @@ class Reader(models.Model): class Meta: ordering = ['id'] +class BookReview(models.Model): + book = models.ForeignKey(BookWithYear) + notes = models.TextField(null=True, blank=True) ## Models for default manager tests diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py index 5cd42a75c0..b21244b6d9 100644 --- a/tests/modeltests/prefetch_related/tests.py +++ b/tests/modeltests/prefetch_related/tests.py @@ -7,7 +7,7 @@ from django.test.utils import override_settings from .models import (Author, Book, Reader, Qualification, Teacher, Department, TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge, - BookWithYear, Person, House, Room, Employee, Comment) + BookWithYear, BookReview, Person, House, Room, Employee, Comment) class PrefetchRelatedTests(TestCase): @@ -335,6 +335,10 @@ class MultiTableInheritanceTest(TestCase): self.authorAddress = AuthorAddress.objects.create( author=self.author1, address='SomeStreet 1') self.book2.aged_authors.add(self.author2, self.author3) + self.br1 = BookReview.objects.create( + book=self.book1, notes="review book1") + self.br2 = BookReview.objects.create( + book=self.book2, notes="review book2") def test_foreignkey(self): with self.assertNumQueries(2): @@ -343,6 +347,12 @@ class MultiTableInheritanceTest(TestCase): for obj in qs] self.assertEqual(addresses, [[unicode(self.authorAddress)], [], []]) + def test_foreignkey_to_inherited(self): + with self.assertNumQueries(2): + qs = BookReview.objects.prefetch_related('book') + titles = [obj.book.title for obj in qs] + self.assertEquals(titles, ["Poems", "More poems"]) + def test_m2m_to_inheriting_model(self): qs = AuthorWithAge.objects.prefetch_related('books_with_year') with self.assertNumQueries(2): From f5ce1793a88e6aca1ccefd7bdec622587357df19 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 6 Jun 2012 14:19:06 +0100 Subject: [PATCH 016/519] Small cleanup in prefetch_related code --- django/db/models/fields/related.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 312a3822f6..c4cd1c4d83 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -483,7 +483,7 @@ class ForeignRelatedObjectsDescriptor(object): return qs def get_prefetch_query_set(self, instances): - rel_obj_attr = attrgetter(rel_field.get_attname()) + rel_obj_attr = attrgetter(rel_field.attname) instance_attr = attrgetter(attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) db = self._db or router.db_for_read(self.model, instance=instances[0]) From 2c57809a560cb67c79b9e8a77cc713e8a2424c8e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 6 Jun 2012 15:45:28 +0200 Subject: [PATCH 017/519] Prevented TestNoInitialDataLoading to pollute other tests (Refs #15926) Tests were still failing with MySQL. It seems a rollback is solving the issue. --- .../fixtures_model_package/tests.py | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/modeltests/fixtures_model_package/tests.py index 1d2cc23577..a415fdf6a7 100644 --- a/tests/modeltests/fixtures_model_package/tests.py +++ b/tests/modeltests/fixtures_model_package/tests.py @@ -1,5 +1,6 @@ from django.core import management -from django.test import TestCase +from django.db import transaction +from django.test import TestCase, TransactionTestCase from .models import Article, Book @@ -20,16 +21,18 @@ class SampleTestCase(TestCase): ) -class TestNoInitialDataLoading(TestCase): +class TestNoInitialDataLoading(TransactionTestCase): def test_syncdb(self): - Book.objects.all().delete() + with transaction.commit_manually(): + Book.objects.all().delete() - management.call_command( - 'syncdb', - verbosity=0, - load_initial_data=False - ) - self.assertQuerysetEqual(Book.objects.all(), []) + management.call_command( + 'syncdb', + verbosity=0, + load_initial_data=False + ) + self.assertQuerysetEqual(Book.objects.all(), []) + transaction.rollback() def test_flush(self): # Test presence of fixture (flush called by TransactionTestCase) @@ -40,13 +43,16 @@ class TestNoInitialDataLoading(TestCase): lambda a: a.name ) - management.call_command( - 'flush', - verbosity=0, - interactive=False, - load_initial_data=False - ) - self.assertQuerysetEqual(Book.objects.all(), []) + with transaction.commit_manually(): + management.call_command( + 'flush', + verbosity=0, + interactive=False, + commit=False, + load_initial_data=False + ) + self.assertQuerysetEqual(Book.objects.all(), []) + transaction.rollback() class FixtureTestCase(TestCase): From 4464bbba15d50ed32beb5995c13d26791ca61fe4 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 7 Jun 2012 09:59:14 +0200 Subject: [PATCH 018/519] Fixed #14502 -- Added a verbatim template tag. Thanks SmileyChris for the patch. --- django/template/base.py | 16 +++++++++-- django/template/defaulttags.py | 35 +++++++++++++++++++++++- docs/ref/templates/builtins.txt | 23 ++++++++++++++++ docs/releases/1.5.txt | 7 +++++ tests/regressiontests/templates/tests.py | 8 ++++++ 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 7cb9807bab..9b2404cdb3 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -184,6 +184,7 @@ class Lexer(object): self.template_string = template_string self.origin = origin self.lineno = 1 + self.verbatim = False def tokenize(self): """ @@ -203,15 +204,26 @@ class Lexer(object): If in_tag is True, we are processing something that matched a tag, otherwise it should be treated as a literal string. """ - if in_tag: + if in_tag and token_string.startswith(BLOCK_TAG_START): # The [2:-2] ranges below strip off *_TAG_START and *_TAG_END. # We could do len(BLOCK_TAG_START) to be more "correct", but we've # hard-coded the 2s here for performance. And it's not like # the TAG_START values are going to change anytime, anyway. + block_content = token_string[2:-2].strip() + if self.verbatim and block_content == self.verbatim: + self.verbatim = False + if in_tag and not self.verbatim: if token_string.startswith(VARIABLE_TAG_START): token = Token(TOKEN_VAR, token_string[2:-2].strip()) elif token_string.startswith(BLOCK_TAG_START): - token = Token(TOKEN_BLOCK, token_string[2:-2].strip()) + if block_content.startswith('verbatim'): + bits = block_content.split(' ', 1) + if bits[0] == 'verbatim': + if len(bits) > 1: + self.verbatim = bits[1] + else: + self.verbatim = 'endverbatim' + token = Token(TOKEN_BLOCK, block_content) elif token_string.startswith(COMMENT_TAG_START): content = '' if token_string.find(TRANSLATOR_COMMENT_MARK): diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 1e2198fc67..3073b41049 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -6,7 +6,7 @@ from datetime import datetime from itertools import groupby, cycle as itertools_cycle from django.conf import settings -from django.template.base import (Node, NodeList, Template, Library, +from django.template.base import (Node, NodeList, Template, Context, Library, TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END, @@ -425,6 +425,13 @@ class URLNode(Node): else: return url +class VerbatimNode(Node): + def __init__(self, content): + self.content = content + + def render(self, context): + return self.content + class WidthRatioNode(Node): def __init__(self, val_expr, max_expr, max_width): self.val_expr = val_expr @@ -1272,6 +1279,32 @@ def url(parser, token): return URLNode(viewname, args, kwargs, asvar) +@register.tag +def verbatim(parser, token): + """ + Stops the template engine from rendering the contents of this block tag. + + Usage:: + + {% verbatim %} + {% don't process this %} + {% endverbatim %} + + You can also specify an alternate closing tag:: + + {% verbatim -- %} + ... + {% -- %} + """ + bits = token.contents.split(' ', 1) + if len(bits) > 1: + closing_tag = bits[1] + else: + closing_tag = 'endverbatim' + nodelist = parser.parse((closing_tag,)) + parser.delete_first_token() + return VerbatimNode(nodelist.render(Context())) + @register.tag def widthratio(parser, token): """ diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 805aaf7f6f..af9004bbf1 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1040,6 +1040,29 @@ This will follow the normal :ref:`namespaced URL resolution strategy `, including using any hints provided by the context as to the current application. +.. templatetag:: verbatim + +verbatim +^^^^^^^^ + +.. versionadded:: 1.5 + +Stops the template engine from rendering the contents of this block tag. + +A common use is to allow a Javascript template layer that collides with +Django's syntax. For example:: + + {% verbatim %} + {{if dying}}Still alive.{{/if}} + {% endverbatim %} + +You can also specify an alternate closing tag:: + + {% verbatim finished %} + The verbatim tag looks like this: + {% verbatim %}{% endverbatim %} + {% finished %} + .. templatetag:: widthratio widthratio diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 696f332285..33f5003281 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -62,6 +62,13 @@ For one-to-one relationships, both sides can be cached. For many-to-one relationships, only the single side of the relationship can be cached. This is particularly helpful in combination with ``prefetch_related``. +``{% verbatim %}`` template tag +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To make it easier to deal with javascript templates which collide with Django's +syntax, you can now use the :ttag:`verbatim` block tag to avoid parsing the +tag's content. + Minor features ~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 8590dd25af..957ad2b9ba 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1616,6 +1616,14 @@ class Templates(unittest.TestCase): 'static-prefixtag04': ('{% load static %}{% get_media_prefix as media_prefix %}{{ media_prefix }}', {}, settings.MEDIA_URL), 'static-statictag01': ('{% load static %}{% static "admin/base.css" %}', {}, urljoin(settings.STATIC_URL, 'admin/base.css')), 'static-statictag02': ('{% load static %}{% static base_css %}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')), + + # Verbatim template tag outputs contents without rendering. + 'verbatim-tag01': ('{% verbatim %}{{bare }}{% endverbatim %}', {}, '{{bare }}'), + 'verbatim-tag02': ('{% verbatim %}{% endif %}{% endverbatim %}', {}, '{% endif %}'), + 'verbatim-tag03': ("{% verbatim %}It's the {% verbatim %} tag{% endverbatim %}", {}, "It's the {% verbatim %} tag"), + 'verbatim-tag04': ('{% verbatim %}{% verbatim %}{% endverbatim %}{% endverbatim %}', {}, template.TemplateSyntaxError), + 'verbatim-tag05': ('{% verbatim %}{% endverbatim %}{% verbatim %}{% endverbatim %}', {}, ''), + 'verbatim-tag06': ("{% verbatim -- %}Don't {% endverbatim %} just yet{% -- %}", {}, "Don't {% endverbatim %} just yet"), } return tests From 6fd1950a4e29c1b5ed071a880db64103715ead51 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 7 Jun 2012 10:31:08 +0200 Subject: [PATCH 019/519] Fixed #10200 -- Raised CommandError when errors happen in loaddata. --- django/core/management/commands/loaddata.py | 43 ++---- tests/modeltests/fixtures/tests.py | 25 ++- .../regressiontests/fixtures_regress/tests.py | 142 ++++++++---------- 3 files changed, 81 insertions(+), 129 deletions(-) diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index f44edf7ade..078fd6fa27 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -7,7 +7,7 @@ import traceback from django.conf import settings from django.core import serializers -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError from django.core.management.color import no_style from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS, IntegrityError, DatabaseError) @@ -36,11 +36,10 @@ class Command(BaseCommand): connection = connections[using] if not len(fixture_labels): - self.stderr.write( + raise CommandError( "No database fixture specified. Please provide the path of at " "least one fixture in the command line." ) - return verbosity = int(options.get('verbosity')) show_traceback = options.get('traceback') @@ -126,13 +125,9 @@ class Command(BaseCommand): if verbosity >= 2: self.stdout.write("Loading '%s' fixtures..." % fixture_name) else: - self.stderr.write( + raise CommandError( "Problem installing fixture '%s': %s is not a known serialization format." % (fixture_name, format)) - if commit: - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) - return if os.path.isabs(fixture_name): fixture_dirs = [fixture_name] @@ -167,12 +162,8 @@ class Command(BaseCommand): else: try: if label_found: - self.stderr.write("Multiple fixtures named '%s' in %s. Aborting." % + raise CommandError("Multiple fixtures named '%s' in %s. Aborting." % (fixture_name, humanize(fixture_dir))) - if commit: - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) - return fixture_count += 1 objects_in_fixture = 0 @@ -191,13 +182,13 @@ class Command(BaseCommand): try: obj.save(using=using) except (DatabaseError, IntegrityError) as e: - msg = "Could not load %(app_label)s.%(object_name)s(pk=%(pk)s): %(error_msg)s" % { + e.args = ("Could not load %(app_label)s.%(object_name)s(pk=%(pk)s): %(error_msg)s" % { 'app_label': obj.object._meta.app_label, 'object_name': obj.object._meta.object_name, 'pk': obj.object.pk, 'error_msg': e - } - raise e.__class__, e.__class__(msg), sys.exc_info()[2] + },) + raise loaded_object_count += loaded_objects_in_fixture fixture_object_count += objects_in_fixture @@ -208,13 +199,9 @@ class Command(BaseCommand): # If the fixture we loaded contains 0 objects, assume that an # error was encountered during fixture loading. if objects_in_fixture == 0: - self.stderr.write( + raise CommandError( "No fixture data found for '%s'. (File format may be invalid.)" % (fixture_name)) - if commit: - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) - return # Since we disabled constraint checks, we must manually check for # any invalid keys that might have been added @@ -223,19 +210,13 @@ class Command(BaseCommand): except (SystemExit, KeyboardInterrupt): raise - except Exception: + except Exception as e: if commit: transaction.rollback(using=using) transaction.leave_transaction_management(using=using) - if show_traceback: - traceback.print_exc() - else: - self.stderr.write( - "Problem installing fixture '%s': %s" % - (full_path, ''.join(traceback.format_exception(sys.exc_type, - sys.exc_value, sys.exc_traceback)))) - return - + 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 # database sequences. diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index 478bbe9129..f5176dab0c 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -4,7 +4,7 @@ import StringIO from django.contrib.sites.models import Site from django.core import management -from django.db import connection +from django.db import connection, IntegrityError from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from .models import Article, Book, Spy, Tag, Visa @@ -232,11 +232,9 @@ class FixtureLoadingTests(TestCase): def test_ambiguous_compressed_fixture(self): # The name "fixture5" is ambigous, so loading it will raise an error - new_io = StringIO.StringIO() - management.call_command('loaddata', 'fixture5', verbosity=0, stderr=new_io, commit=False) - output = new_io.getvalue().strip().split('\n') - self.assertEqual(len(output), 1) - self.assertTrue(output[0].startswith("Multiple fixtures named 'fixture5'")) + with self.assertRaisesRegexp(management.CommandError, + "Multiple fixtures named 'fixture5'"): + management.call_command('loaddata', 'fixture5', verbosity=0, commit=False) def test_db_loading(self): # Load db fixtures 1 and 2. These will load using the 'default' database identifier implicitly @@ -258,10 +256,9 @@ class FixtureLoadingTests(TestCase): # is closed at the end of each test. if connection.vendor == 'mysql': connection.cursor().execute("SET sql_mode = 'TRADITIONAL'") - new_io = StringIO.StringIO() - management.call_command('loaddata', 'invalid.json', verbosity=0, stderr=new_io, commit=False) - output = new_io.getvalue().strip().split('\n') - self.assertRegexpMatches(output[-1], "Error: Could not load fixtures.Article\(pk=1\): .*$") + with self.assertRaisesRegexp(IntegrityError, + "Could not load fixtures.Article\(pk=1\): .*$"): + management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False) def test_loading_using(self): # Load db fixtures 1 and 2. These will load using the 'default' database identifier explicitly @@ -316,11 +313,9 @@ class FixtureTransactionTests(TransactionTestCase): # Try to load fixture 2 using format discovery; this will fail # because there are two fixture2's in the fixtures directory - new_io = StringIO.StringIO() - management.call_command('loaddata', 'fixture2', verbosity=0, stderr=new_io) - output = new_io.getvalue().strip().split('\n') - self.assertEqual(len(output), 1) - self.assertTrue(output[0].startswith("Multiple fixtures named 'fixture2'")) + with self.assertRaisesRegexp(management.CommandError, + "Multiple fixtures named 'fixture2'"): + management.call_command('loaddata', 'fixture2', verbosity=0) # object list is unaffected self.assertQuerysetEqual(Article.objects.all(), [ diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index 0e2cc3a9b2..c0b811bea2 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -9,7 +9,7 @@ from io import BytesIO from django.core import management from django.core.management.base import CommandError from django.core.management.commands.dumpdata import sort_dependencies -from django.db import transaction +from django.db import transaction, IntegrityError from django.db.models import signals from django.test import (TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature) @@ -116,18 +116,15 @@ class TestFixtures(TestCase): Test for ticket #4371 -- Loading data of an unknown format should fail Validate that error conditions are caught correctly """ - stderr = BytesIO() - management.call_command( - 'loaddata', - 'bad_fixture1.unkn', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertEqual( - stderr.getvalue(), - "Problem installing fixture 'bad_fixture1': unkn is not a known serialization format.\n" - ) + with self.assertRaisesRegexp(management.CommandError, + "Problem installing fixture 'bad_fixture1': " + "unkn is not a known serialization format."): + management.call_command( + 'loaddata', + 'bad_fixture1.unkn', + verbosity=0, + commit=False, + ) def test_invalid_data(self): """ @@ -135,18 +132,14 @@ class TestFixtures(TestCase): using explicit filename. Validate that error conditions are caught correctly """ - stderr = BytesIO() - management.call_command( - 'loaddata', - 'bad_fixture2.xml', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertEqual( - stderr.getvalue(), - "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n" - ) + with self.assertRaisesRegexp(management.CommandError, + "No fixture data found for 'bad_fixture2'. \(File format may be invalid.\)"): + management.call_command( + 'loaddata', + 'bad_fixture2.xml', + verbosity=0, + commit=False, + ) def test_invalid_data_no_ext(self): """ @@ -154,54 +147,42 @@ class TestFixtures(TestCase): without file extension. Validate that error conditions are caught correctly """ - stderr = BytesIO() - management.call_command( - 'loaddata', - 'bad_fixture2', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertEqual( - stderr.getvalue(), - "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n" - ) + with self.assertRaisesRegexp(management.CommandError, + "No fixture data found for 'bad_fixture2'. \(File format may be invalid.\)"): + management.call_command( + 'loaddata', + 'bad_fixture2', + verbosity=0, + commit=False, + ) def test_empty(self): """ Test for ticket #4371 -- Loading a fixture file with no data returns an error. Validate that error conditions are caught correctly """ - stderr = BytesIO() - management.call_command( - 'loaddata', - 'empty', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertEqual( - stderr.getvalue(), - "No fixture data found for 'empty'. (File format may be invalid.)\n" - ) + with self.assertRaisesRegexp(management.CommandError, + "No fixture data found for 'empty'. \(File format may be invalid.\)"): + management.call_command( + 'loaddata', + 'empty', + verbosity=0, + commit=False, + ) def test_error_message(self): """ (Regression for #9011 - error message is correct) """ - stderr = BytesIO() - management.call_command( - 'loaddata', - 'bad_fixture2', - 'animal', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertEqual( - stderr.getvalue(), - "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n" - ) + with self.assertRaisesRegexp(management.CommandError, + "^No fixture data found for 'bad_fixture2'. \(File format may be invalid.\)$"): + management.call_command( + 'loaddata', + 'bad_fixture2', + 'animal', + verbosity=0, + commit=False, + ) def test_pg_sequence_resetting_checks(self): """ @@ -357,17 +338,14 @@ class TestFixtures(TestCase): """ Regression for #3615 - Ensure data with nonexistent child key references raises error """ - stderr = BytesIO() - management.call_command( - 'loaddata', - 'forward_ref_bad_data.json', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertTrue( - stderr.getvalue().startswith('Problem installing fixture') - ) + with self.assertRaisesRegexp(IntegrityError, + "Problem installing fixture"): + management.call_command( + 'loaddata', + 'forward_ref_bad_data.json', + verbosity=0, + commit=False, + ) _cur_dir = os.path.dirname(os.path.abspath(__file__)) @@ -392,16 +370,14 @@ class TestFixtures(TestCase): """ Regression for #7043 - Error is quickly reported when no fixtures is provided in the command line. """ - stderr = BytesIO() - management.call_command( - 'loaddata', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertEqual( - stderr.getvalue(), 'No database fixture specified. Please provide the path of at least one fixture in the command line.\n' - ) + with self.assertRaisesRegexp(management.CommandError, + "No database fixture specified. Please provide the path of " + "at least one fixture in the command line."): + management.call_command( + 'loaddata', + verbosity=0, + commit=False, + ) def test_loaddata_not_existant_fixture_file(self): stdout_output = BytesIO() From b48432201bae813027c045e412e28ba4c1806d92 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 7 Jun 2012 11:01:49 +0200 Subject: [PATCH 020/519] Fixed #18411 -- Clarified HttpRequest doc slightly. Thanks torkel.bjornson AT gmail.com for the report. --- docs/ref/request-response.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index ae736b1c75..d2b6f35b84 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -28,7 +28,8 @@ HttpRequest objects Attributes ---------- -All attributes except ``session`` should be considered read-only. +All attributes should be considered read-only, unless stated otherwise below. +``session`` is a notable exception. .. attribute:: HttpRequest.body From 4ce5a5fe7806e3dd0ea0d7c53c0d6d831a0edac8 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 7 Jun 2012 11:19:10 +0200 Subject: [PATCH 021/519] Fixed #18396 -- Changed Spatialite download URLs. Thanks gabrielcw AT gmail.com for the report. --- docs/ref/contrib/gis/install.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 9cb945f76a..eefbefc956 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -418,8 +418,8 @@ SpatiaLite library (``libspatialite``) and tools (``spatialite``) 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/spatialite/libspatialite-amalgamation-2.3.1.tar.gz - $ wget http://www.gaia-gis.it/spatialite/spatialite-tools-2.3.1.tar.gz + $ 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 $ tar xzf libspatialite-amalgamation-2.3.1.tar.gz $ tar xzf spatialite-tools-2.3.1.tar.gz From 17f3e9258e6050c274a7294213bd08bb71f2d2da Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 7 Jun 2012 11:50:20 +0200 Subject: [PATCH 022/519] Fixed #18397 -- Avoided referencing lawrence.com. This commit includes multiple small related changes, see the ticket for a full discussion. --- django/conf/global_settings.py | 12 ++++++------ .../conf/project_template/project_name/settings.py | 8 ++++---- django/contrib/messages/tests/cookie.py | 6 +++--- docs/ref/contrib/csrf.txt | 2 +- docs/ref/settings.txt | 12 ++++++------ docs/topics/http/sessions.txt | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 4711baad66..13f7991b57 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -270,19 +270,19 @@ SECRET_KEY = '' DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' # Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" +# Example: "/var/www/example.com/media/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. -# Example: "http://media.lawrence.com/media/" +# Examples: "http://example.com/media/", "http://media.example.com/" MEDIA_URL = '' -# Absolute path to the directory that holds static files. -# Example: "/home/media/media.lawrence.com/static/" +# Absolute path to the directory static files should be collected to. +# Example: "/var/www/example.com/static/" STATIC_ROOT = '' # URL that handles the static files served from STATIC_ROOT. -# Example: "http://media.lawrence.com/static/" +# Example: "http://example.com/static/", "http://static.example.com/" STATIC_URL = None # List of upload handler classes to be applied in order. @@ -451,7 +451,7 @@ MIDDLEWARE_CLASSES = ( SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want. SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). -SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie. +SESSION_COOKIE_DOMAIN = None # A string like ".example.com", or None for standard domain cookie. SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). SESSION_COOKIE_PATH = '/' # The path of the session cookie. SESSION_COOKIE_HTTPONLY = True # Whether to use the non-RFC standard httpOnly flag (IE, FF3+, others) diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py index 0eccc4eaf5..99590d6fd5 100644 --- a/django/conf/project_template/project_name/settings.py +++ b/django/conf/project_template/project_name/settings.py @@ -44,22 +44,22 @@ USE_L10N = True USE_TZ = True # Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" +# Example: "/var/www/example.com/media/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. -# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +# Examples: "http://example.com/media/", "http://media.example.com/" MEDIA_URL = '' # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/home/media/media.lawrence.com/static/" +# Example: "/var/www/example.com/static/" STATIC_ROOT = '' # URL prefix for static files. -# Example: "http://media.lawrence.com/static/" +# Example: "http://example.com/static/", "http://static.example.com/" STATIC_URL = '/static/' # Additional locations of static files diff --git a/django/contrib/messages/tests/cookie.py b/django/contrib/messages/tests/cookie.py index 19d0e08384..477eb72e56 100644 --- a/django/contrib/messages/tests/cookie.py +++ b/django/contrib/messages/tests/cookie.py @@ -39,7 +39,7 @@ def stored_cookie_messages_count(storage, response): return len(data) -@override_settings(SESSION_COOKIE_DOMAIN='.lawrence.com') +@override_settings(SESSION_COOKIE_DOMAIN='.example.com') class CookieTest(BaseTest): storage_class = CookieStorage @@ -65,7 +65,7 @@ class CookieTest(BaseTest): storage.add(constants.INFO, 'test') storage.update(response) self.assertTrue('test' in response.cookies['messages'].value) - self.assertEqual(response.cookies['messages']['domain'], '.lawrence.com') + self.assertEqual(response.cookies['messages']['domain'], '.example.com') self.assertEqual(response.cookies['messages']['expires'], '') # Test after the messages have been consumed @@ -76,7 +76,7 @@ class CookieTest(BaseTest): pass # Iterate through the storage to simulate consumption of messages. storage.update(response) self.assertEqual(response.cookies['messages'].value, '') - self.assertEqual(response.cookies['messages']['domain'], '.lawrence.com') + self.assertEqual(response.cookies['messages']['domain'], '.example.com') self.assertEqual(response.cookies['messages']['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT') def test_get_bad_cookie(self): diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index 0ff9bd1a51..b11af3be28 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -441,7 +441,7 @@ Default: ``None`` The domain to be used when setting the CSRF cookie. This can be useful for easily allowing cross-subdomain requests to be excluded from the normal cross site request forgery protection. It should be set to a string such as -``".lawrence.com"`` to allow a POST request from a form on one subdomain to be +``".example.com"`` to allow a POST request from a form on one subdomain to be accepted by a view served from another subdomain. Please note that, with or without use of this setting, this CSRF protection diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index a1b76f65e1..566c167fec 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -314,7 +314,7 @@ Default: ``None`` The domain to be used when setting the CSRF cookie. This can be useful for easily allowing cross-subdomain requests to be excluded from the normal cross site request forgery protection. It should be set to a string such as -``".lawrence.com"`` to allow a POST request from a form on one subdomain to be +``".example.com"`` to allow a POST request from a form on one subdomain to be accepted by accepted by a view served from another subdomain. Please note that the presence of this setting does not imply that Django's CSRF @@ -1404,7 +1404,7 @@ Default: ``''`` (Empty string) Absolute path to the directory that holds media for this installation, used for :doc:`managing stored files `. -Example: ``"/home/media/media.lawrence.com/"`` +Example: ``"/var/www/example.com/media/"`` See also :setting:`MEDIA_URL`. @@ -1418,7 +1418,7 @@ Default: ``''`` (Empty string) URL that handles the media served from :setting:`MEDIA_ROOT`, used for :doc:`managing stored files `. -Example: ``"http://media.lawrence.com/"`` +Example: ``"http://media.example.com/"`` .. versionchanged:: 1.3 It must end in a slash if set to a non-empty value. @@ -1704,7 +1704,7 @@ SESSION_COOKIE_DOMAIN Default: ``None`` The domain to use for session cookies. Set this to a string such as -``".lawrence.com"`` for cross-domain cookies, or use ``None`` for a standard +``".example.com"`` for cross-domain cookies, or use ``None`` for a standard domain cookie. See the :doc:`/topics/http/sessions`. .. setting:: SESSION_COOKIE_HTTPONLY @@ -1885,7 +1885,7 @@ Default: ``''`` (Empty string) The absolute path to the directory where :djadmin:`collectstatic` will collect static files for deployment. -Example: ``"/home/example.com/static/"`` +Example: ``"/var/www/example.com/static/"`` If the :doc:`staticfiles` contrib app is enabled (default) the :djadmin:`collectstatic` management command will collect static @@ -1915,7 +1915,7 @@ Default: ``None`` URL to use when referring to static files located in :setting:`STATIC_ROOT`. -Example: ``"/site_media/static/"`` or ``"http://static.example.com/"`` +Example: ``"/static/"`` or ``"http://static.example.com/"`` If not ``None``, this will be used as the base path for :ref:`media definitions` and the diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 4b0bbe4ed5..20dc61a222 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -503,7 +503,7 @@ SESSION_COOKIE_DOMAIN Default: ``None`` The domain to use for session cookies. Set this to a string such as -``".lawrence.com"`` (note the leading dot!) for cross-domain cookies, or use +``".example.com"`` (note the leading dot!) for cross-domain cookies, or use ``None`` for a standard domain cookie. SESSION_COOKIE_HTTPONLY From 875c76251710c9b643a4e6164b5570713fe024f5 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 7 Jun 2012 12:51:48 +0200 Subject: [PATCH 023/519] Fixed #16916 -- Documented default headers for the test client. Thanks sailorfred AT yahoo.com for the report and raulcd for the initial version of the patch. --- AUTHORS | 5 +++-- docs/topics/testing.txt | 21 ++++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 5e1314bdd8..b389bb9a95 100644 --- a/AUTHORS +++ b/AUTHORS @@ -136,9 +136,10 @@ answer newbie questions, and generally made Django that much better: Robert Coup Pete Crosier Matt Croydon - Leah Culver - flavio.curella@gmail.com Jure Cuhalev + Leah Culver + Raúl Cumplido + flavio.curella@gmail.com John D'Agostino dackze+django@gmail.com Jim Dalton diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index f35c545c30..3d9504d193 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -650,8 +650,6 @@ Note a few important things about how the test client works: * By default, the test client will disable any CSRF checks performed by your site. - .. versionadded:: 1.2.2 - If, for some reason, you *want* the test client to perform CSRF checks, you can create an instance of the test client that enforces CSRF checks. To do this, pass in the @@ -664,10 +662,23 @@ Note a few important things about how the test client works: Making requests ~~~~~~~~~~~~~~~ -Use the ``django.test.client.Client`` class to make requests. It requires no -arguments at time of construction: +Use the ``django.test.client.Client`` class to make requests. -.. class:: Client() +.. class:: Client(enforce_csrf_checks=False, **defaults) + + It requires no arguments at time of construction. However, you can use + keywords arguments to specify some default headers. For example, this will + send a ``User-Agent`` HTTP header in each request:: + + >>> c = Client(HTTP_USER_AGENT='Mozilla/5.0') + + The values from the ``extra`` keywords arguments passed to + :meth:`~django.test.client.Client.get()`, + :meth:`~django.test.client.Client.post()`, etc. have precedence over + the defaults passed to the class constructor. + + The ``enforce_csrf_checks`` argument can be used to test CSRF + protection (see above). Once you have a ``Client`` instance, you can call any of the following methods: From bac6a68064309eb8e9ad81ad10e983b40273a52f Mon Sep 17 00:00:00 2001 From: Juan Riaza Date: Thu, 7 Jun 2012 13:35:04 +0200 Subject: [PATCH 024/519] Fixed #18380 -- Improve installation instructions for MySQLdb --- docs/topics/install.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 5e2eefe139..0cdb8a49e7 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -107,7 +107,7 @@ database bindings are installed. If you're on Windows, check out the unofficial `compiled Windows version`_. -* If you're using MySQL, you'll need MySQLdb_, version 1.2.1p2 or higher. You +* If you're using MySQL, you'll need the ``MySQL-python`` package, version 1.2.1p2 or higher. You will also want to read the database-specific :ref:`notes for the MySQL backend `. @@ -135,7 +135,6 @@ Django will need permission to create a test database. .. _MySQL: http://www.mysql.com/ .. _psycopg: http://initd.org/pub/software/psycopg/ .. _compiled Windows version: http://stickpeople.com/projects/python/win-psycopg/ -.. _MySQLdb: http://sourceforge.net/projects/mysql-python .. _SQLite: http://www.sqlite.org/ .. _pysqlite: http://trac.edgewall.org/wiki/PySqlite .. _cx_Oracle: http://cx-oracle.sourceforge.net/ From 6492e8e5e6643c5a90d71727af7f17a362b9362b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 7 Jun 2012 14:36:54 +0200 Subject: [PATCH 025/519] Added more precise assertions on two fixture tests. --- tests/modeltests/fixtures/tests.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index f5176dab0c..1efa035e8a 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -185,18 +185,14 @@ class FixtureLoadingTests(TestCase): exclude_list=['fixtures.Article', 'fixtures.Book', 'sites']) # Excluding a bogus app should throw an error - self.assertRaises(management.CommandError, - self._dumpdata_assert, - ['fixtures', 'sites'], - '', - exclude_list=['foo_app']) + with self.assertRaisesRegexp(management.CommandError, + "Unknown app in excludes: foo_app"): + self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app']) # Excluding a bogus model should throw an error - self.assertRaises(management.CommandError, - self._dumpdata_assert, - ['fixtures', 'sites'], - '', - exclude_list=['fixtures.FooModel']) + with self.assertRaisesRegexp(management.CommandError, + "Unknown model in excludes: fixtures.FooModel"): + self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel']) def test_dumpdata_with_filtering_manager(self): spy1 = Spy.objects.create(name='Paul') From c28e700c7e54e3071f173b34a12eb1c4f6e39552 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 7 Jun 2012 15:02:35 +0200 Subject: [PATCH 026/519] Removed references to changes made in 1.2. Thanks Florian Apolloner for the patch. --- docs/faq/models.txt | 2 - docs/howto/custom-model-fields.txt | 18 ---- docs/howto/custom-template-tags.txt | 2 - .../writing-code/submitting-patches.txt | 2 - docs/ref/contrib/admin/index.txt | 40 ++------- docs/ref/contrib/comments/example.txt | 2 - docs/ref/contrib/comments/index.txt | 2 - docs/ref/contrib/csrf.txt | 6 -- docs/ref/contrib/formtools/form-preview.txt | 2 - docs/ref/contrib/gis/db-api.txt | 6 +- docs/ref/contrib/gis/gdal.txt | 10 --- docs/ref/contrib/gis/geoquerysets.txt | 12 --- docs/ref/contrib/gis/geos.txt | 4 - docs/ref/contrib/gis/install.txt | 2 - docs/ref/contrib/gis/model-api.txt | 4 - docs/ref/contrib/gis/testing.txt | 12 +-- docs/ref/contrib/index.txt | 3 - docs/ref/contrib/messages.txt | 3 - docs/ref/contrib/syndication.txt | 7 -- docs/ref/databases.txt | 9 -- docs/ref/django-admin.txt | 37 -------- docs/ref/forms/api.txt | 4 - docs/ref/forms/fields.txt | 14 --- docs/ref/forms/validation.txt | 3 - docs/ref/middleware.txt | 3 - docs/ref/models/fields.txt | 11 --- docs/ref/models/instances.txt | 8 -- docs/ref/models/querysets.txt | 6 -- docs/ref/settings.txt | 86 ------------------- docs/ref/signals.txt | 5 -- docs/ref/templates/api.txt | 42 ++------- docs/ref/templates/builtins.txt | 34 ++------ docs/ref/validators.txt | 1 - docs/topics/auth.txt | 24 +----- docs/topics/cache.txt | 14 --- docs/topics/db/models.txt | 2 - docs/topics/db/multi-db.txt | 2 - docs/topics/db/sql.txt | 2 - docs/topics/email.txt | 2 - docs/topics/forms/formsets.txt | 7 +- docs/topics/forms/modelforms.txt | 10 --- docs/topics/http/urls.txt | 6 -- docs/topics/i18n/formatting.txt | 2 - docs/topics/i18n/translation.txt | 2 - docs/topics/serialization.txt | 5 -- docs/topics/testing.txt | 16 ---- 46 files changed, 22 insertions(+), 474 deletions(-) diff --git a/docs/faq/models.txt b/docs/faq/models.txt index c9f1a03fb1..d34a26a82e 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -26,8 +26,6 @@ SELECTs, etc. Each time your app hits the database, the query will be recorded. Note that the SQL recorded here may be :ref:`incorrectly quoted under SQLite `. -.. versionadded:: 1.2 - If you are using :doc:`multiple databases`, you can use the same interface on each member of the ``connections`` dictionary:: diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index f1dd9dc56c..1377a62c89 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -313,9 +313,6 @@ Custom database types .. method:: Field.db_type(self, connection) -.. versionadded:: 1.2 - The ``connection`` argument was added to support multiple databases. - Returns the database column data type for the :class:`~django.db.models.Field`, taking into account the connection object, and the settings associated with it. @@ -452,9 +449,6 @@ Converting Python objects to query values .. method:: Field.get_prep_value(self, value) -.. versionadded:: 1.2 - This method was factored out of ``get_db_prep_value()`` - This is the reverse of :meth:`.to_python` when working with the database backends (as opposed to serialization). The ``value`` parameter is the current value of the model's attribute (a field has @@ -480,9 +474,6 @@ Converting query values to database values .. method:: Field.get_db_prep_value(self, value, connection, prepared=False) -.. versionadded:: 1.2 - The ``connection`` and ``prepared`` arguments were added to support multiple databases. - Some data types (for example, dates) need to be in a specific format before they can be used by a database backend. :meth:`.get_db_prep_value` is the method where those conversions should @@ -499,9 +490,6 @@ processing. .. method:: Field.get_db_prep_save(self, value, connection) -.. versionadded:: 1.2 - The ``connection`` argument was added to support multiple databases. - Same as the above, but called when the Field value must be *saved* to the database. As the default implementation just calls :meth:`.get_db_prep_value`, you shouldn't need to implement this method @@ -540,9 +528,6 @@ two phase process. .. method:: Field.get_prep_lookup(self, lookup_type, value) -.. versionadded:: 1.2 - This method was factored out of ``get_db_prep_lookup()`` - :meth:`.get_prep_lookup` performs the first phase of lookup preparation, performing generic data validity checks @@ -591,9 +576,6 @@ accepted lookup types to ``exact`` and ``in``:: .. method:: Field.get_db_prep_lookup(self, lookup_type, value, connection, prepared=False) -.. versionadded:: 1.2 - The ``connection`` and ``prepared`` arguments were added to support multiple databases. - Performs any database-specific data conversions required by a lookup. As with :meth:`.get_db_prep_value`, the specific connection that will be used for the query is passed as the ``connection`` parameter. diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 1d933fe3da..054c831fad 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -540,8 +540,6 @@ tag is used inside a :ttag:`{% autoescape off %}` block. Thread-safety considerations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - Once a node is parsed, its ``render`` method may be called any number of times. Since Django is sometimes run in multi-threaded environments, a single node may be simultaneously rendering with different contexts in response to two separate diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index 9b6cf40183..1e28dc4491 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -127,8 +127,6 @@ should be considered non-trivial, just ask. Javascript patches ------------------ -.. versionadded:: 1.2 - Django's admin system leverages the jQuery framework to increase the capabilities of the admin interface. In conjunction, there is an emphasis on admin javascript performance and minimizing overall admin media file size. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f3e39b9c40..3ef9abe6da 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -115,8 +115,6 @@ subclass:: .. attribute:: ModelAdmin.actions_selection_counter - .. versionadded:: 1.2 - Controls whether a selection counter is display next to the action dropdown. By default, the admin changelist will display it (``actions_selection_counter = True``). @@ -177,12 +175,9 @@ subclass:: fields = ('url', 'title', 'content') In the above example, only the fields ``url``, ``title`` and ``content`` - will be displayed, sequentially, in the form. - - .. versionadded:: 1.2 - - ``fields`` can contain values defined in :attr:`ModelAdmin.readonly_fields` - to be displayed as read-only. + will be displayed, sequentially, in the form. ``fields`` can contain + values defined in :attr:`ModelAdmin.readonly_fields` to be displayed as + read-only. .. versionadded:: 1.4 @@ -262,8 +257,6 @@ subclass:: 'fields': (('first_name', 'last_name'), 'address', 'city', 'state'), } - .. versionadded:: 1.2 - ``fields`` can contain values defined in :attr:`~ModelAdmin.readonly_fields` to be displayed as read-only. @@ -380,8 +373,6 @@ subclass:: .. attribute:: ModelAdmin.get_changelist - .. versionchanged:: 1.2 - Returns the Changelist class to be used for listing. By default, ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this class you can change the behavior of the listing. @@ -819,8 +810,6 @@ subclass:: .. attribute:: ModelAdmin.readonly_fields - .. versionadded:: 1.2 - By default the admin shows all fields as editable. Any fields in this option (which should be a ``list`` or ``tuple``) will display its data as-is and non-editable. This option behaves nearly identical to @@ -928,8 +917,6 @@ templates used by the :class:`ModelAdmin` views: .. attribute:: ModelAdmin.add_form_template - .. versionadded:: 1.2 - Path to a custom template, used by :meth:`add_view`. .. attribute:: ModelAdmin.change_form_template @@ -947,8 +934,6 @@ templates used by the :class:`ModelAdmin` views: .. attribute:: ModelAdmin.delete_selected_confirmation_template - .. versionadded:: 1.2 - Path to a custom template, used by the :meth:`delete_selected` action method for displaying a confirmation page when deleting one or more objects. See the :doc:`actions @@ -1035,8 +1020,6 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.get_readonly_fields(self, request, obj=None) - .. versionadded:: 1.2 - The ``get_readonly_fields`` method is given the ``HttpRequest`` and the ``obj`` being edited (or ``None`` on an add form) and is expected to return a ``list`` or ``tuple`` of field names that will be displayed as read-only, @@ -1405,20 +1388,17 @@ adds some of its own (the shared features are actually defined in the - :attr:`~InlineModelAdmin.form` - :attr:`~ModelAdmin.fieldsets` - :attr:`~ModelAdmin.fields` +- :attr:`~ModelAdmin.formfield_overrides` - :attr:`~ModelAdmin.exclude` - :attr:`~ModelAdmin.filter_horizontal` - :attr:`~ModelAdmin.filter_vertical` - :attr:`~ModelAdmin.prepopulated_fields` - :attr:`~ModelAdmin.radio_fields` +- :attr:`~ModelAdmin.readonly_fields` - :attr:`~InlineModelAdmin.raw_id_fields` - :meth:`~ModelAdmin.formfield_for_foreignkey` - :meth:`~ModelAdmin.formfield_for_manytomany` -.. versionadded:: 1.2 - -- :attr:`~ModelAdmin.readonly_fields` -- :attr:`~ModelAdmin.formfield_overrides` - .. versionadded:: 1.3 - :attr:`~ModelAdmin.ordering` @@ -1463,8 +1443,6 @@ The ``InlineModelAdmin`` class adds: :doc:`formsets documentation ` for more information. - .. versionadded:: 1.2 - For users with JavaScript-enabled browsers, an "Add another" link is provided to enable any number of additional inlines to be added in addition to those provided as a result of the ``extra`` argument. @@ -1541,8 +1519,6 @@ automatically:: Working with many-to-many models -------------------------------- -.. versionadded:: 1.2 - By default, admin widgets for many-to-many relations will be displayed on whichever model contains the actual reference to the :class:`~django.db.models.ManyToManyField`. Depending on your ``ModelAdmin`` @@ -1842,21 +1818,15 @@ Templates can override or extend base admin templates as described in .. attribute:: AdminSite.logout_template - .. versionadded:: 1.2 - Path to a custom template that will be used by the admin site logout view. .. attribute:: AdminSite.password_change_template - .. versionadded:: 1.2 - Path to a custom template that will be used by the admin site password change view. .. attribute:: AdminSite.password_change_done_template - .. versionadded:: 1.2 - Path to a custom template that will be used by the admin site password change done view. diff --git a/docs/ref/contrib/comments/example.txt b/docs/ref/contrib/comments/example.txt index f6c118a02d..e78d83c35d 100644 --- a/docs/ref/contrib/comments/example.txt +++ b/docs/ref/contrib/comments/example.txt @@ -37,8 +37,6 @@ available in the context, then you can refer to it directly:: {% get_comment_count for entry as comment_count %}

    {{ comment_count }} comments have been posted.

    -.. versionadded:: 1.2 - Next, we can use the :ttag:`render_comment_list` tag, to render all comments to the given instance (``entry``) by using the ``comments/list.html`` template:: diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 61e7bd9f46..40b1b662b7 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -130,8 +130,6 @@ details. Linking to comments ------------------- -.. versionadded:: 1.2 - To provide a permalink to a specific comment, use :ttag:`get_comment_permalink`:: {% get_comment_permalink comment_obj [format_string] %} diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index b11af3be28..f25cb31e4b 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -434,8 +434,6 @@ A number of settings can be used to control Django's CSRF behavior. CSRF_COOKIE_DOMAIN ------------------ -.. versionadded:: 1.2 - Default: ``None`` The domain to be used when setting the CSRF cookie. This can be useful for @@ -450,8 +448,6 @@ mechanism is not safe against cross-subdomain attacks -- see `Limitations`_. CSRF_COOKIE_NAME ---------------- -.. versionadded:: 1.2 - Default: ``'csrftoken'`` The name of the cookie to use for the CSRF authentication token. This can be @@ -485,8 +481,6 @@ cookie is only sent under an HTTPS connection. CSRF_FAILURE_VIEW ----------------- -.. versionadded:: 1.2 - Default: ``'django.views.csrf.csrf_failure'`` A dotted path to the view function to be used when an incoming request diff --git a/docs/ref/contrib/formtools/form-preview.txt b/docs/ref/contrib/formtools/form-preview.txt index f9a1feb262..784213ecba 100644 --- a/docs/ref/contrib/formtools/form-preview.txt +++ b/docs/ref/contrib/formtools/form-preview.txt @@ -110,8 +110,6 @@ default templates. Advanced ``FormPreview`` methods ================================ -.. versionadded:: 1.2 - .. method:: FormPreview.process_preview Given a validated form, performs any extra processing before displaying the diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 9c6eb033eb..318110ef04 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -12,11 +12,7 @@ GeoDjango Database API Spatial Backends ================ -.. versionadded:: 1.2 - -In Django 1.2, support for :doc:`multiple databases ` was -introduced. In order to support multiple databases, GeoDjango has segregated -its functionality into full-fledged spatial database backends: +GeoDjango currently provides the following spatial database backends: * :mod:`django.contrib.gis.db.backends.postgis` * :mod:`django.contrib.gis.db.backends.mysql` diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index 5cd6187c6c..619f23fba2 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -212,8 +212,6 @@ __ http://www.gdal.org/ogr/ogr_formats.html .. attribute:: spatial_filter - .. versionadded:: 1.2 - Property that may be used to retrieve or set a spatial filter for this layer. A spatial filter can only be set with an :class:`OGRGeometry` instance, a 4-tuple extent, or ``None``. When set with something @@ -490,15 +488,9 @@ systems and coordinate transformation:: .. attribute:: coord_dim - .. versionchanged:: 1.2 - Returns or sets the coordinate dimension of this geometry. For example, the value would be 2 for two-dimensional geometries. - .. note:: - - Setting this property is only available in versions 1.2 and above. - .. attribute:: geom_count Returns the number of elements in this geometry:: @@ -619,8 +611,6 @@ systems and coordinate transformation:: .. attribute:: ewkt - .. versionadded:: 1.2 - Returns the EWKT representation of this geometry. .. method:: clone() diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index b9e3a7acd3..da00aa97f8 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -117,8 +117,6 @@ SpatiaLite ``Contains(poly, geom)`` contains_properly ----------------- -.. versionadded:: 1.2 - *Availability*: PostGIS Returns true if the lookup geometry intersects the interior of the @@ -803,8 +801,6 @@ Geometry Editors .. method:: GeoQuerySet.force_rhr(**kwargs) -.. versionadded:: 1.2 - *Availability*: PostGIS Returns a modified version of the polygon/multipolygon in which all @@ -816,8 +812,6 @@ of the vertices follow the Right-Hand-Rule, and attaches as a .. method:: GeoQuerySet.reverse_geom(**kwargs) -.. versionadded:: 1.2 - *Availability*: PostGIS, Oracle Reverse the coordinate order of the geometry field, and attaches as a @@ -943,8 +937,6 @@ of the geometry field in each model converted to the requested output format. .. method:: GeoQuerySet.geohash(precision=20, **kwargs) -.. versionadded:: 1.2 - Attaches a ``geohash`` attribute to every model the queryset containing the `GeoHash`__ representation of the geometry. @@ -1136,8 +1128,6 @@ Example:: .. method:: GeoQuerySet.extent3d(**kwargs) -.. versionadded:: 1.2 - *Availability*: PostGIS Returns the 3D extent of the ``GeoQuerySet`` as a six-tuple, comprising @@ -1224,8 +1214,6 @@ Returns the same as the :meth:`GeoQuerySet.extent` aggregate method. .. class:: Extent3D(geo_field) -.. versionadded:: 1.2 - Returns the same as the :meth:`GeoQuerySet.extent3d` aggregate method. ``MakeLine`` diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index d3d9fe4442..1b32265e55 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -265,8 +265,6 @@ because it is not a part of the OGC specification (use the .. attribute:: GEOSGeometry.hexewkb -.. versionadded:: 1.2 - Returns the EWKB of this Geometry in hexadecimal form. This is an extension of the WKB specification that includes SRID and Z values that are a part of this geometry. @@ -314,8 +312,6 @@ as a Python buffer. SRID and Z values are not included, use the .. attribute:: GEOSGeometry.ewkb -.. versionadded:: 1.2 - Return the EWKB representation of this Geometry as a Python buffer. This is an extension of the WKB specification that includes any SRID and Z values that are a part of this geometry. diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index eefbefc956..4b7ee89a52 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -624,8 +624,6 @@ features such as the geographic admin or KML sitemaps will not function properly Add Google projection to ``spatial_ref_sys`` table -------------------------------------------------- -.. versionchanged:: 1.2 - .. note:: If you're running PostGIS 1.4 or above, you can skip this step. The entry diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index bbbf148106..462df50d64 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -163,8 +163,6 @@ field. ``dim`` ------- -.. versionadded:: 1.2 - .. attribute:: GeometryField.dim This option may be used for customizing the coordinate dimension of the @@ -180,8 +178,6 @@ three-dimensonal support. ``geography`` ------------- -.. versionadded:: 1.2 - .. attribute:: GeometryField.geography If set to ``True``, this option will create a database column of diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index b21ba7b407..78663b967c 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -2,12 +2,6 @@ Testing GeoDjango apps ====================== -.. versionchanged:: 1.2 - -In Django 1.2, the addition of :ref:`spatial-backends` simplified the -process of testing GeoDjango applications. The process is now the -same as :doc:`/topics/testing`. - Included in this documentation are some additional notes and settings for :ref:`testing-postgis` and :ref:`testing-spatialite` users. @@ -28,11 +22,9 @@ Settings ``POSTGIS_TEMPLATE`` ^^^^^^^^^^^^^^^^^^^^ -.. versionchanged:: 1.2 - This setting may be used to customize the name of the PostGIS template -database to use. In Django versions 1.2 and above, it automatically -defaults to ``'template_postgis'`` (the same name used in the +database to use. It automatically defaults to ``'template_postgis'`` +(the same name used in the :ref:`installation documentation `). .. setting:: POSTGIS_VERSION diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt index 37216468b9..efe4393f64 100644 --- a/docs/ref/contrib/index.txt +++ b/docs/ref/contrib/index.txt @@ -142,9 +142,6 @@ See the :doc:`markup documentation `. messages ======== -.. versionchanged:: 1.2 - The messages framework was added. - A framework for storing and retrieving temporary cookie- or session-based messages diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 4ab9734aa4..da6336e832 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -12,9 +12,6 @@ in a subsequent request (usually the next one). Every message is tagged with a specific ``level`` that determines its priority (e.g., ``info``, ``warning``, or ``error``). -.. versionadded:: 1.2 - The messages framework was added. - Enabling messages ================= diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index 6f89ee187c..5653397748 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -22,10 +22,6 @@ lower-level way. The high-level framework ======================== -.. versionchanged:: 1.2 - The high-level feeds framework was refactored in Django 1.2. The - pre-1.2 interface has been removed in Django 1.4. - Overview -------- @@ -176,9 +172,6 @@ These can be matched with a :doc:`URLconf ` line such as:: Like a view, the arguments in the URL are passed to the ``get_object()`` method along with the request object. -.. versionchanged:: 1.2 - Prior to version 1.2, ``get_object()`` only accepted a ``bits`` argument. - Here's the code for these beat-specific feeds:: from django.contrib.syndication.views import FeedDoesNotExist diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 2dea337d2b..600de8ed3a 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -373,15 +373,6 @@ these methods in no-op's based in the results of such detection. Notes on specific fields ------------------------ -Boolean fields -~~~~~~~~~~~~~~ - -.. versionchanged:: 1.2 - -In previous versions of Django when running under MySQL ``BooleanFields`` would -return their data as ``ints``, instead of true ``bools``. See the release -notes for a complete description of the change. - Character fields ~~~~~~~~~~~~~~~~ diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index f04f9ee058..7a2ba33fda 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -119,8 +119,6 @@ createcachetable Creates a cache table named ``tablename`` for use with the database cache backend. See :doc:`/topics/cache` for more information. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database onto which the cachetable will be installed. @@ -142,8 +140,6 @@ the program name (``psql``, ``mysql``, ``sqlite3``) will find the program in the right place. There's no way to specify the location of the program manually. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database onto which to open a shell. @@ -212,15 +208,11 @@ name to ``dumpdata``, the dumped output will be restricted to that model, rather than the entire application. You can also mix application names and model names. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database from which data will be dumped. .. django-admin-option:: --natural -.. versionadded:: 1.2 - Use :ref:`natural keys ` to represent any foreign key and many-to-many relationship with a model that provides a natural key definition. If you are dumping ``contrib.auth`` ``Permission`` @@ -240,8 +232,6 @@ fixture will be re-installed. The :djadminopt:`--noinput` option may be provided to suppress all user prompts. -.. versionadded:: 1.2 - The :djadminopt:`--database` option may be used to specify the database to flush. @@ -296,8 +286,6 @@ needed. ``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection only works in PostgreSQL and with certain types of MySQL tables. -.. versionadded:: 1.2 - The :djadminopt:`--database` option may be used to specify the database to introspect. @@ -308,8 +296,6 @@ loaddata Searches for and loads the contents of the named fixture into the database. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database onto which the data will be loaded. @@ -452,8 +438,6 @@ Currently supported: .. django-admin-option:: --symlinks -.. versionadded:: 1.2 - Use the ``--symlinks`` or ``-s`` option to follow symlinks to directories when looking for new translation strings. @@ -768,8 +752,6 @@ sql Prints the CREATE TABLE SQL statements for the given app name(s). -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -783,8 +765,6 @@ Prints the CREATE TABLE and initial-data SQL statements for the given app name(s Refer to the description of ``sqlcustom`` for an explanation of how to specify initial data. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -795,8 +775,6 @@ sqlclear Prints the DROP TABLE SQL statements for the given app name(s). -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -821,8 +799,6 @@ table modifications, or insert any SQL functions into the database. Note that the order in which the SQL files are processed is undefined. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -834,8 +810,6 @@ sqlflush Prints the SQL statements that would be executed for the :djadmin:`flush` command. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -846,8 +820,6 @@ sqlindexes Prints the CREATE INDEX SQL statements for the given app name(s). -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -864,8 +836,6 @@ number for automatically incremented fields. Use this command to generate SQL which will fix cases where a sequence is out of sync with its automatically incremented field data. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -1027,8 +997,6 @@ data files. The :djadminopt:`--noinput` option may be provided to suppress all user prompts. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database to synchronize. @@ -1047,7 +1015,6 @@ test Runs tests for all installed models. See :doc:`/topics/testing` for more information. -.. versionadded:: 1.2 .. django-admin-option:: --failfast The ``--failfast`` option can be used to stop running tests and report the @@ -1155,8 +1122,6 @@ changepassword .. django-admin:: changepassword -.. versionadded:: 1.2 - This command is only available if Django's :doc:`authentication system ` (``django.contrib.auth``) is installed. @@ -1317,8 +1282,6 @@ to a number of commands. .. django-admin-option:: --database -.. versionadded:: 1.2 - Used to specify the database on which a command will operate. If not specified, this option will default to an alias of ``default``. diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index a866fc550c..50488b026a 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -377,8 +377,6 @@ a form object, and each rendering method returns a Unicode object. Styling required or erroneous form rows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - It's pretty common to style form rows and fields that are required or have errors. For example, you might want to present required form rows in bold and highlight errors in red. @@ -638,8 +636,6 @@ For a field's list of errors, access the field's ``errors`` attribute. .. method:: BoundField.css_classes() - .. versionadded:: 1.2 - When you use Django's rendering shortcuts, CSS classes are used to indicate required form fields or fields that contain errors. If you're manually rendering a form, you can access these CSS classes using the diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 6e7d85f586..d9bfbc5e45 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -256,8 +256,6 @@ error message keys it uses. ``validators`` ~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - .. attribute:: Field.validators The ``validators`` argument lets you provide a list of validation functions @@ -268,8 +266,6 @@ See the :doc:`validators documentation ` for more information. ``localize`` ~~~~~~~~~~~~ -.. versionadded:: 1.2 - .. attribute:: Field.localize The ``localize`` argument enables the localization of form data, input as well @@ -492,11 +488,6 @@ For each field, we describe the default widget used if you don't specify If provided, these arguments ensure that the string is at most or at least the given length. -.. versionchanged:: 1.2 - The EmailField previously did not recognize email addresses as valid that - contained an IDN (Internationalized Domain Name; a domain containing - unicode characters) domain part. This has now been corrected. - ``FileField`` ~~~~~~~~~~~~~ @@ -815,11 +806,6 @@ For each field, we describe the default widget used if you don't specify These are the same as ``CharField.max_length`` and ``CharField.min_length``. -.. versionchanged:: 1.2 - The URLField previously did not recognize URLs as valid that contained an IDN - (Internationalized Domain Name; a domain name containing unicode characters) - domain name. This has now been corrected. - Slightly complex built-in ``Field`` classes ------------------------------------------- diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 42006bba90..f1642148b5 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -1,8 +1,6 @@ Form and field validation ========================= -.. versionchanged:: 1.2 - Form validation happens when the data is cleaned. If you want to customize this process, there are various places you can change, each one serving a different purpose. Three types of cleaning methods are run during form @@ -175,7 +173,6 @@ previous features. Using validators ~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 Django's form (and model) fields support use of simple utility functions and classes known as validators. These can be passed to a field's constructor, via diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 99e2ae3838..c280202ebf 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -156,9 +156,6 @@ Message middleware .. class:: MessageMiddleware -.. versionadded:: 1.2 - ``MessageMiddleware`` was added. - Enables cookie- and session-based message support. See the :doc:`messages documentation `. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 629efc0432..23dcf4bd9f 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -208,8 +208,6 @@ automatically generated from the model class. Default is ``True``. ``error_messages`` ------------------ -.. versionadded:: 1.2 - .. attribute:: Field.error_messages The ``error_messages`` argument lets you override the default messages that the @@ -309,8 +307,6 @@ underscores to spaces. See :ref:`Verbose field names `. ``validators`` ------------------- -.. versionadded:: 1.2 - .. attribute:: Field.validators A list of validators to run for this field. See the :doc:`validators @@ -336,8 +332,6 @@ otherwise. See :ref:`automatic-primary-key-fields`. ``BigIntegerField`` ------------------- -.. versionadded:: 1.2 - .. class:: BigIntegerField([**options]) A 64 bit integer, much like an :class:`IntegerField` except that it is @@ -357,11 +351,6 @@ The admin represents this as a checkbox. If you need to accept :attr:`~Field.null` values then use :class:`NullBooleanField` instead. -.. versionchanged:: 1.2 - In previous versions of Django when running under MySQL ``BooleanFields`` - would return their data as ``ints``, instead of true ``bools``. See the - release notes for a complete description of the change. - ``CharField`` ------------- diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 50fb085d38..3ae994bc5b 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -30,8 +30,6 @@ that, you need to :meth:`~Model.save()`. Validating objects ================== -.. versionadded:: 1.2 - There are three steps involved in validating a model: 1. Validate the model fields @@ -137,9 +135,6 @@ To save an object back to the database, call ``save()``: .. method:: Model.save([force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None]) -.. versionadded:: 1.2 - The ``using`` argument was added. - If you want customized saving behavior, you can override this ``save()`` method. See :ref:`overriding-model-methods` for more details. @@ -361,9 +356,6 @@ Deleting objects .. method:: Model.delete([using=DEFAULT_DB_ALIAS]) -.. versionadded:: 1.2 - The ``using`` argument was added. - Issues a SQL ``DELETE`` for the object. This only deletes the object in the database; the Python instance will still exist and will still have data in its fields. diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 050598a532..eef22728ab 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -727,8 +727,6 @@ passed to ``select_related()``. This includes foreign keys that have It's an error to use both a list of fields and the ``depth`` parameter in the same ``select_related()`` call; they are conflicting options. -.. versionchanged:: 1.2 - You can also refer to the reverse direction of a :class:`~django.db.models.OneToOneField` in the list of fields passed to ``select_related`` — that is, you can traverse a @@ -1156,8 +1154,6 @@ using .. method:: using(alias) -.. versionadded:: 1.2 - This method is for controlling which database the ``QuerySet`` will be evaluated against if you are using more than one database. The only argument this method takes is the alias of a database, as defined in @@ -1512,8 +1508,6 @@ exists .. method:: exists() -.. versionadded:: 1.2 - Returns ``True`` if the :class:`.QuerySet` contains any results, and ``False`` if not. This tries to perform the query in the simplest and fastest way possible, but it *does* execute nearly the same query. This means that calling diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 566c167fec..627aa5007f 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -307,8 +307,6 @@ See :doc:`/topics/cache`. CSRF_COOKIE_DOMAIN ------------------ -.. versionadded:: 1.2 - Default: ``None`` The domain to be used when setting the CSRF cookie. This can be useful for @@ -326,8 +324,6 @@ protection is safe from cross-subdomain attacks by default - please see the CSRF_COOKIE_NAME ---------------- -.. versionadded:: 1.2 - Default: ``'csrftoken'`` The name of the cookie to use for the CSRF authentication token. This can be whatever you @@ -367,8 +363,6 @@ cookie is only sent under an HTTPS connection. CSRF_FAILURE_VIEW ----------------- -.. versionadded:: 1.2 - Default: ``'django.views.csrf.csrf_failure'`` A dotted path to the view function to be used when an incoming request @@ -386,8 +380,6 @@ end users) indicating the reason the request was rejected. See DATABASES --------- -.. versionadded:: 1.2 - Default: ``{}`` (Empty dictionary) A dictionary containing the settings for all databases to be used with @@ -430,12 +422,6 @@ You can use a database backend that doesn't ship with Django by setting scratch is left as an exercise to the reader; see the other backends for examples. -.. note:: - Prior to Django 1.2, you could use a short version of the backend name - to reference the built-in database backends (e.g., you could use - ``'sqlite3'`` to refer to the SQLite backend). This format has been - deprecated, and will be removed in Django 1.4. - .. setting:: HOST HOST @@ -664,8 +650,6 @@ not provided, Django will use ``'test_' + NAME + '_temp'``. DATABASE_ROUTERS ---------------- -.. versionadded:: 1.2 - Default: ``[]`` (Empty list) The list of routers that will be used to determine which database @@ -686,9 +670,6 @@ system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. -.. versionchanged:: 1.2 - This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. - See also :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATE_FORMAT`. .. setting:: DATE_INPUT_FORMATS @@ -696,8 +677,6 @@ See also :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_ DATE_INPUT_FORMATS ------------------ -.. versionadded:: 1.2 - Default:: ('%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%b %d %Y', @@ -728,9 +707,6 @@ system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. -.. versionchanged:: 1.2 - This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. - See also :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATETIME_FORMAT`. .. setting:: DATETIME_INPUT_FORMATS @@ -738,8 +714,6 @@ See also :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATE DATETIME_INPUT_FORMATS ---------------------- -.. versionadded:: 1.2 - Default:: ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d', @@ -823,8 +797,6 @@ site. DECIMAL_SEPARATOR ----------------- -.. versionadded:: 1.2 - Default: ``'.'`` (Dot) Default decimal separator used when formatting decimal numbers. @@ -926,8 +898,6 @@ This is only used if ``CommonMiddleware`` is installed (see EMAIL_BACKEND ------------- -.. versionadded:: 1.2 - Default: ``'django.core.mail.backends.smtp.EmailBackend'`` The backend to use for sending emails. For the list of available backends see @@ -938,8 +908,6 @@ The backend to use for sending emails. For the list of available backends see EMAIL_FILE_PATH --------------- -.. versionadded:: 1.2 - Default: Not defined The directory used by the ``file`` email backend to store output files. @@ -1086,8 +1054,6 @@ See :doc:`/topics/files` for details. FIRST_DAY_OF_WEEK ----------------- -.. versionadded:: 1.2 - Default: ``0`` (Sunday) Number representing the first day of the week. This is especially useful @@ -1127,8 +1093,6 @@ of the preferred value or not supplied at all. FORMAT_MODULE_PATH ------------------ -.. versionadded:: 1.2 - Default: ``None`` A full Python path to a Python package that contains format definitions for @@ -1426,8 +1390,6 @@ Example: ``"http://media.example.com/"`` MESSAGE_LEVEL ------------- -.. versionadded:: 1.2 - Default: `messages.INFO` Sets the minimum message level that will be recorded by the messages @@ -1437,8 +1399,6 @@ more details. MESSAGE_STORAGE --------------- -.. versionadded:: 1.2 - Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` Controls where Django stores message data. See the @@ -1447,8 +1407,6 @@ Controls where Django stores message data. See the MESSAGE_TAGS ------------ -.. versionadded:: 1.2 - Default:: {messages.DEBUG: 'debug', @@ -1475,11 +1433,6 @@ Default:: A tuple of middleware classes to use. See :doc:`/topics/http/middleware`. -.. versionchanged:: 1.2 - ``'django.contrib.messages.middleware.MessageMiddleware'`` was added to the - default. For more information, see the :doc:`messages documentation - `. - .. setting:: MONTH_DAY_FORMAT MONTH_DAY_FORMAT @@ -1505,8 +1458,6 @@ See :tfilter:`allowed date format strings `. See also NUMBER_GROUPING ---------------- -.. versionadded:: 1.2 - Default: ``0`` Number of digits grouped together on the integer part of a number. @@ -1820,8 +1771,6 @@ Whether to save the session data on every request. See SHORT_DATE_FORMAT ----------------- -.. versionadded:: 1.2 - Default: ``m/d/Y`` (e.g. ``12/31/2003``) An available formatting that can be used for displaying date fields on @@ -1836,8 +1785,6 @@ See also :setting:`DATE_FORMAT` and :setting:`SHORT_DATETIME_FORMAT`. SHORT_DATETIME_FORMAT --------------------- -.. versionadded:: 1.2 - Default: ``m/d/Y P`` (e.g. ``12/31/2003 4 p.m.``) An available formatting that can be used for displaying datetime fields on @@ -1944,16 +1891,6 @@ A tuple of callables that are used to populate the context in ``RequestContext`` These callables take a request object as their argument and return a dictionary of items to be merged into the context. -.. versionchanged:: 1.2 - ``django.contrib.messages.context_processors.messages`` was added to the - default. For more information, see the :doc:`messages documentation - `. - -.. versionchanged:: 1.2 - The auth context processor was moved in this release from its old location - ``django.core.context_processors.auth`` to - ``django.contrib.auth.context_processors.auth``. - .. versionadded:: 1.3 The ``django.core.context_processors.static`` context processor was added in this release. @@ -2009,12 +1946,6 @@ used instead of a string. The first item in the tuple should be the ``Loader``'s module, subsequent items are passed to the ``Loader`` during initialization. See :doc:`/ref/templates/api`. -.. versionchanged:: 1.2 - The class-based API for template loaders was introduced in Django 1.2 - although the :setting:`TEMPLATE_LOADERS` setting will accept strings - that specify function-based loaders until compatibility with them is - completely removed in Django 1.4. - .. setting:: TEMPLATE_STRING_IF_INVALID TEMPLATE_STRING_IF_INVALID @@ -2032,9 +1963,6 @@ TEST_RUNNER Default: ``'django.test.simple.DjangoTestSuiteRunner'`` -.. versionchanged:: 1.2 - Prior to 1.2, test runners were a function, not a class. - The name of the class to use for starting the test suite. See :doc:`/topics/testing`. @@ -2045,8 +1973,6 @@ The name of the class to use for starting the test suite. See THOUSAND_SEPARATOR ------------------ -.. versionadded:: 1.2 - Default: ``,`` (Comma) Default thousand separator used when formatting numbers. This setting is @@ -2071,9 +1997,6 @@ system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. -.. versionchanged:: 1.2 - This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. - See also :setting:`DATE_FORMAT` and :setting:`DATETIME_FORMAT`. .. setting:: TIME_INPUT_FORMATS @@ -2081,8 +2004,6 @@ See also :setting:`DATE_FORMAT` and :setting:`DATETIME_FORMAT`. TIME_INPUT_FORMATS ------------------ -.. versionadded:: 1.2 - Default: ``('%H:%M:%S', '%H:%M')`` A tuple of formats that will be accepted when inputting data on a time field. @@ -2104,9 +2025,6 @@ TIME_ZONE Default: ``'America/Chicago'`` -.. versionchanged:: 1.2 - ``None`` was added as an allowed value. - .. versionchanged:: 1.4 The meaning of this setting now depends on the value of :setting:`USE_TZ`. @@ -2183,8 +2101,6 @@ See also :setting:`LANGUAGE_CODE`, :setting:`USE_L10N` and :setting:`USE_TZ`. USE_L10N -------- -.. versionadded:: 1.2 - Default: ``False`` A boolean that specifies if localized formatting of data will be enabled by @@ -2203,8 +2119,6 @@ See also :setting:`LANGUAGE_CODE`, :setting:`USE_I18N` and :setting:`USE_TZ`. USE_THOUSAND_SEPARATOR ---------------------- -.. versionadded:: 1.2 - Default: ``False`` A boolean that specifies whether to display numbers using a thousand separator. diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 136eee935c..b2f2e85abc 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -220,8 +220,6 @@ m2m_changed .. data:: django.db.models.signals.m2m_changed :module: -.. versionadded:: 1.2 - Sent when a :class:`ManyToManyField` is changed on a model instance. Strictly speaking, this is not a model signal since it is sent by the :class:`ManyToManyField`, but since it complements the @@ -544,9 +542,6 @@ connection_created .. data:: django.db.backends.signals.connection_created :module: -.. versionchanged:: 1.2 - The connection argument was added - Sent when the database wrapper makes the initial connection to the database. This is particularly useful if you'd like to send any post connection commands to the SQL backend. diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index e945e0d4ca..6142dd7017 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -372,21 +372,11 @@ and return a dictionary of items to be merged into the context. By default, "django.core.context_processors.static", "django.contrib.messages.context_processors.messages") -.. versionadded:: 1.2 - In addition to these, ``RequestContext`` always uses - ``django.core.context_processors.csrf``. This is a security - related context processor required by the admin and other contrib apps, and, - in case of accidental misconfiguration, it is deliberately hardcoded in and - cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. - -.. versionadded:: 1.2 - The ``'messages'`` context processor was added. For more information, see - the :doc:`messages documentation `. - -.. versionchanged:: 1.2 - The auth context processor was moved in this release from its old location - ``django.core.context_processors.auth`` to - ``django.contrib.auth.context_processors.auth``. +In addition to these, ``RequestContext`` always uses +``django.core.context_processors.csrf``. This is a security +related context processor required by the admin and other contrib apps, and, +in case of accidental misconfiguration, it is deliberately hardcoded in and +cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. Each processor is applied in order. That means, if one processor adds a variable to the context and a second processor adds a variable with the same @@ -447,10 +437,6 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every ``django.contrib.auth.context_processors.PermWrapper``, representing the permissions that the currently logged-in user has. -.. versionchanged:: 1.2 - This context processor was moved in this release from - ``django.core.context_processors.auth`` to its current location. - .. versionchanged:: 1.3 Prior to version 1.3, ``PermWrapper`` was located in ``django.contrib.auth.context_processors``. @@ -503,8 +489,6 @@ value of the :setting:`STATIC_URL` setting. django.core.context_processors.csrf ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - This processor adds a token that is needed by the :ttag:`csrf_token` template tag for protection against :doc:`Cross Site Request Forgeries `. @@ -527,15 +511,6 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every via the user model (using ``user.message_set.create``) or through the :doc:`messages framework `. -.. versionadded:: 1.2 - This template context variable was previously supplied by the ``'auth'`` - context processor. For backwards compatibility the ``'auth'`` context - processor will continue to supply the ``messages`` variable until Django - 1.4. If you use the ``messages`` variable, your project will work with - either (or both) context processors, but it is recommended to add - ``django.contrib.messages.context_processors.messages`` so your project - will be prepared for the future upgrade. - Writing your own context processors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -657,11 +632,6 @@ by editing your :setting:`TEMPLATE_LOADERS` setting. :setting:`TEMPLATE_LOADERS` should be a tuple of strings, where each string represents a template loader class. Here are the template loaders that come with Django: -.. versionchanged:: 1.2 - Template loaders were based on callables (usually functions) before Django - 1.2, starting with the 1.2 release there is a new class-based API, all the - loaders described below implement this new API. - ``django.template.loaders.filesystem.Loader`` Loads templates from the filesystem, according to :setting:`TEMPLATE_DIRS`. This loader is enabled by default. @@ -800,8 +770,6 @@ and any setting starting with ``TEMPLATE_`` is of obvious interest. Using an alternative template language ====================================== -.. versionadded:: 1.2 - The Django ``Template`` and ``Loader`` classes implement a simple API for loading and rendering templates. By providing some simple wrapper classes that implement this API we can use third party template systems like `Jinja2 diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index af9004bbf1..6f341e9f97 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -58,10 +58,8 @@ Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. csrf_token ^^^^^^^^^^ -In the Django 1.1.X series, this is a no-op tag that returns an empty string -for future compatibility purposes. In Django 1.2 and later, it is used for -CSRF protection, as described in the documentation for :doc:`Cross Site Request -Forgeries `. +This tag is used for CSRF protection, as described in the documentation for +:doc:`Cross Site Request Forgeries `. .. templatetag:: cycle @@ -410,8 +408,6 @@ variables or to negate a given variable:: There are some athletes and absolutely no coaches. {% endif %} -.. versionchanged:: 1.2 - Use of both ``and`` and ``or`` clauses within the same tag is allowed, with ``and`` having higher precedence than ``or`` e.g.:: @@ -426,9 +422,6 @@ will be interpreted like: Use of actual brackets in the :ttag:`if` tag is invalid syntax. If you need them to indicate precedence, you should use nested :ttag:`if` tags. -.. versionadded:: 1.2 - - :ttag:`if` tags may also use the operators ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=`` and ``in`` which work as follows: @@ -637,9 +630,8 @@ You cannot check for equality with Python objects such as ``True`` or ``False``. If you need to test if something is true or false, use the :ttag:`if` tag instead. -.. versionadded:: 1.2 - An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the - ``==`` operator. +An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the +``==`` operator. .. templatetag:: ifnotequal @@ -649,9 +641,8 @@ ifnotequal Just like :ttag:`ifequal`, except it tests that the two arguments are not equal. -.. versionadded:: 1.2 - An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and - the ``!=`` operator. +An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and +the ``!=`` operator. .. templatetag:: include @@ -1127,9 +1118,6 @@ For example:: If ``value`` is ``4``, then the output will be ``6``. -.. versionchanged:: 1.2 - The following behavior didn't exist in previous Django versions. - This filter will first try to coerce both values to integers. If this fails, it'll attempt to add the values together anyway. This will work on some data types (strings, list, etc.) and fail on others. If it fails, the result will @@ -1289,10 +1277,6 @@ Z Time zone offset in seconds. The ``-43200`` to ``4320 UTC is always positive. ================ ======================================== ===================== -.. versionadded:: 1.2 - -The ``c`` and ``u`` format specification characters were added in Django 1.2. - .. versionadded:: 1.4 The ``e`` and ``o`` format specification characters were added in Django 1.4. @@ -1326,9 +1310,6 @@ When used without a format string:: ...the formatting string defined in the :setting:`DATE_FORMAT` setting will be used, without applying any localization. -.. versionchanged:: 1.2 - Predefined formats can now be influenced by the current locale. - .. templatefilter:: default default @@ -1985,9 +1966,6 @@ When used without a format string:: ...the formatting string defined in the :setting:`TIME_FORMAT` setting will be used, without applying any localization. -.. versionchanged:: 1.2 - Predefined formats can now be influenced by the current locale. - .. templatefilter:: timesince timesince diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index 5b83d26d87..b68d6f2772 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -2,7 +2,6 @@ Validators ========== -.. versionadded:: 1.2 .. module:: django.core.validators :synopsis: Validation utilities and base classes diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index c3a2636aaf..63986ea70f 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -64,11 +64,8 @@ Fields .. attribute:: models.User.username - Required. 30 characters or fewer. Alphanumeric characters only - (letters, digits and underscores). - - .. versionchanged:: 1.2 - Usernames may now contain ``@``, ``+``, ``.`` and ``-`` characters. + Required. 30 characters or fewer. Usernames may contain alphanumeric, + ``_``, ``@``, ``+``, ``.`` and ``-`` characters. .. attribute:: models.User.first_name @@ -207,8 +204,6 @@ Methods Returns a set of permission strings that the user has, through his/her groups. - .. versionadded:: 1.2 - If ``obj`` is passed in, only returns the group permissions for this specific object. @@ -217,8 +212,6 @@ Methods Returns a set of permission strings that the user has, both through group and user permissions. - .. versionadded:: 1.2 - If ``obj`` is passed in, only returns the permissions for this specific object. @@ -229,8 +222,6 @@ Methods `permissions`_ section below). If the user is inactive, this method will always return ``False``. - .. versionadded:: 1.2 - If ``obj`` is passed in, this method won't check for a permission for the model, but for this specific object. @@ -241,8 +232,6 @@ Methods ``"."``. If the user is inactive, this method will always return ``False``. - .. versionadded:: 1.2 - If ``obj`` is passed in, this method won't check for permissions for the model, but for the specific object. @@ -349,9 +338,6 @@ Django requires add *and* change permissions as a slight security measure. Changing passwords ~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - The ``manage.py changepassword`` command was added. - :djadmin:`manage.py changepassword *username* ` offers a method of changing a User's password from the command line. It prompts you to change the password of a given user which you must enter twice. If @@ -1046,8 +1032,6 @@ The login_required decorator {% endblock %} - .. versionadded:: 1.2 - If you are using alternate authentication (see :ref:`authentication-backends`) you can pass a custom authentication form to the login view via the ``authentication_form`` parameter. This form must @@ -1140,8 +1124,6 @@ includes a few other useful built-in views located in * ``post_change_redirect``: The URL to redirect to after a successful password change. - .. versionadded:: 1.2 - * ``password_change_form``: A custom "change password" form which must accept a ``user`` keyword argument. The form is responsible for actually changing the user's password. Defaults to @@ -1899,8 +1881,6 @@ the ``auth_permission`` table most of the time. Authorization for anonymous users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionchanged:: 1.2 - An anonymous user is one that is not authenticated i.e. they have provided no valid authentication details. However, that does not necessarily mean they are not authorized to do anything. At the most basic level, most Web sites diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 99d764b60d..03afa86647 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -83,12 +83,6 @@ two most common are `python-memcached`_ and `pylibmc`_. .. _`python-memcached`: ftp://ftp.tummy.com/pub/python-memcached/ .. _`pylibmc`: http://sendapatch.se/projects/pylibmc/ -.. versionchanged:: 1.2 - In Django 1.0 and 1.1, you could also use ``cmemcache`` as a binding. - However, support for this library was deprecated in 1.2 due to - a lack of maintenance on the ``cmemcache`` library itself. Support for - ``cmemcache`` will be removed completely in Django 1.4. - .. versionchanged:: 1.3 Support for ``pylibmc`` was added. @@ -493,8 +487,6 @@ more on these decorators. .. _i18n-cache-key: -.. versionadded:: 1.2 - If :setting:`USE_I18N` is set to ``True`` then the generated cache key will include the name of the active :term:`language` -- see also :ref:`how-django-discovers-language-preference`). This allows you to easily @@ -737,8 +729,6 @@ actually exist in the cache (and haven't expired):: >>> cache.get_many(['a', 'b', 'c']) {'a': 1, 'b': 2, 'c': 3} -.. versionadded:: 1.2 - To set multiple values more efficiently, use ``set_many()`` to pass a dictionary of key-value pairs:: @@ -753,15 +743,11 @@ clearing the cache for a particular object:: >>> cache.delete('a') -.. versionadded:: 1.2 - If you want to clear a bunch of keys at once, ``delete_many()`` can take a list of keys to be cleared:: >>> cache.delete_many(['a', 'b', 'c']) -.. versionadded:: 1.2 - Finally, if you want to delete all the keys in the cache, use ``cache.clear()``. Be careful with this; ``clear()`` will remove *everything* from the cache, not just the keys set by your application. :: diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index c336faed2b..cba2c98b0d 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -881,8 +881,6 @@ field. This would normally cause a problem in abstract base classes, since the fields on this class are included into each of the child classes, with exactly the same values for the attributes (including :attr:`~django.db.models.ForeignKey.related_name`) each time. -.. versionchanged:: 1.2 - To work around this problem, when you are using :attr:`~django.db.models.ForeignKey.related_name` in an abstract base class (only), part of the name should contain ``'%(app_label)s'`` and ``'%(class)s'``. diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index ee542b9196..86e785a19d 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -2,8 +2,6 @@ Multiple databases ================== -.. versionadded:: 1.2 - This topic guide describes Django's support for interacting with multiple databases. Most of the rest of Django's documentation assumes you are interacting with a single database. If you want to interact diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 2ac47170aa..19daffd464 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -18,8 +18,6 @@ __ `executing custom SQL directly`_ Performing raw queries ====================== -.. versionadded:: 1.2 - The ``raw()`` manager method can be used to perform raw SQL queries that return model instances: diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 2069773f8a..3a78a83ce5 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -365,8 +365,6 @@ subtype. For example:: Email backends ============== -.. versionadded:: 1.2 - The actual sending of an email is handled by the email backend. The email backend class has the following methods: diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 42b9d1f490..2a83172e17 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -102,15 +102,12 @@ limit the maximum number of empty forms the formset will display:: -.. versionchanged:: 1.2 - If the value of ``max_num`` is greater than the number of existing objects, up to ``extra`` additional blank forms will be added to the formset, so long as the total number of forms does not exceed ``max_num``. A ``max_num`` value of ``None`` (the default) puts no limit on the number of -forms displayed. Please note that the default value of ``max_num`` was changed -from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value. +forms displayed. Formset validation ------------------ @@ -210,8 +207,6 @@ pre-filled, and is also used to determine how many forms are required. You will probably never need to override either of these methods, so please be sure you understand what they do before doing so. -.. versionadded:: 1.2 - ``empty_form`` ~~~~~~~~~~~~~~ diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index df76b8094f..7e00f6a41c 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -109,9 +109,6 @@ Model field Form field ``URLField`` ``URLField`` =============================== ======================================== -.. versionadded:: 1.2 - The ``BigIntegerField`` is new in Django 1.2. - As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field types are special cases: @@ -362,9 +359,6 @@ Since the Author model has only 3 fields, 'name', 'title', and Overriding the default field types or widgets --------------------------------------------- -.. versionadded:: 1.2 - The ``widgets`` attribute is new in Django 1.2. - The default field types, as described in the `Field types`_ table above, are sensible defaults. If you have a ``DateField`` in your model, chances are you'd want that to be represented as a ``DateField`` in your form. But @@ -670,8 +664,6 @@ are saved properly. Limiting the number of editable objects --------------------------------------- -.. versionchanged:: 1.2 - As with regular formsets, you can use the ``max_num`` and ``extra`` parameters to ``modelformset_factory`` to limit the number of extra forms displayed. @@ -698,8 +690,6 @@ so long as the total number of forms does not exceed ``max_num``:: -.. versionchanged:: 1.2 - A ``max_num`` value of ``None`` (the default) puts no limit on the number of forms displayed. diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 2f272c5095..2310fac413 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -314,9 +314,6 @@ 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. -.. versionchanged:: 1.2 - Previous versions of Django only accepted strings representing import paths. - handler500 ---------- @@ -329,9 +326,6 @@ have runtime errors in view code. By default, this is ``'django.views.defaults.server_error'``. That default value should suffice. -.. versionchanged:: 1.2 - Previous versions of Django only accepted strings representing import paths. - Notes on capturing text in URLs =============================== diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt index a459b95042..d18781c0a9 100644 --- a/docs/topics/i18n/formatting.txt +++ b/docs/topics/i18n/formatting.txt @@ -4,8 +4,6 @@ Format localization =================== -.. versionadded:: 1.2 - Overview ======== diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 4f56b251cf..a375ef9a47 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1039,8 +1039,6 @@ Django comes with a tool, :djadmin:`django-admin.py makemessages commands from the GNU gettext toolset: ``xgettext``, ``msgfmt``, ``msgmerge`` and ``msguniq``. - .. versionchanged:: 1.2 - The minimum version of the ``gettext`` utilities supported is 0.15. To create or update a message file, run this command:: diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 73239cb483..505ac17f09 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -188,11 +188,6 @@ In particular, :ref:`lazy translation objects ` need a Natural keys ------------ -.. versionadded:: 1.2 - - The ability to use natural keys when serializing/deserializing data was - added in the 1.2 release. - The default serialization strategy for foreign keys and many-to-many relations is to serialize the value of the primary key(s) of the objects in the relation. This strategy works well for most objects, but it can cause difficulty in some diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 3d9504d193..4cc566b5dd 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -291,9 +291,6 @@ label:: $ ./manage.py test animals.AnimalTestCase.test_animals_can_speak -.. versionadded:: 1.2 - The ability to select individual doctests was added. - You can use the same rules if you're using doctests. Django will use the test label as a path to the test method or class that you want to run. If your ``models.py`` or ``tests.py`` has a function with a doctest, or @@ -311,9 +308,6 @@ If you're using a ``__test__`` dictionary to specify doctests for a module, Django will use the label as a key in the ``__test__`` dictionary for defined in ``models.py`` and ``tests.py``. -.. versionadded:: 1.2 - You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``. - If you press ``Ctrl-C`` while the tests are running, the test runner will wait for the currently running test to complete and then exit gracefully. During a graceful exit the test runner will output details of any test @@ -392,8 +386,6 @@ advanced settings. Testing master/slave configurations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - If you're testing a multiple database configuration with master/slave replication, this strategy of creating test databases poses a problem. When the test databases are created, there won't be any replication, @@ -1387,8 +1379,6 @@ Multi-database support .. attribute:: TestCase.multi_db -.. versionadded:: 1.2 - Django sets up a test database corresponding to every database that is defined in the :setting:`DATABASES` definition in your settings file. However, a big part of the time taken to run a Django TestCase @@ -1512,9 +1502,6 @@ Assertions .. currentmodule:: django.test -.. versionchanged:: 1.2 - Added ``msg_prefix`` argument. - As Python's normal :class:`unittest.TestCase` class implements assertion methods such as :meth:`~unittest.TestCase.assertTrue` and :meth:`~unittest.TestCase.assertEqual`, Django's custom :class:`TestCase` class @@ -2022,9 +2009,6 @@ process to satisfy whatever testing requirements you may have. Defining a test runner ---------------------- -.. versionchanged:: 1.2 - Prior to 1.2, test runners were a single function, not a class. - .. currentmodule:: django.test.simple A test runner is a class defining a ``run_tests()`` method. Django ships From 0e36948f627983bbf2b3cc4c4d2ec4a5a64e6e0a Mon Sep 17 00:00:00 2001 From: jakul Date: Thu, 7 Jun 2012 17:24:48 +0200 Subject: [PATCH 027/519] fix bad locale names --- docs/ref/django-admin.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 7a2ba33fda..360c0ae4d3 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -109,7 +109,7 @@ If not provided, all locales are processed. Example usage:: - django-admin.py compilemessages --locale=br_PT + django-admin.py compilemessages --locale=pt_BR createcachetable ---------------- @@ -426,7 +426,7 @@ Use the :djadminopt:`--locale` option to specify the locale to process. Example usage:: - django-admin.py makemessages --locale=br_PT + django-admin.py makemessages --locale=pt_BR .. django-admin-option:: --domain From e1643e3535c4160c1a56c80f4d4db9848dc65e7a Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Thu, 7 Jun 2012 17:34:25 +0200 Subject: [PATCH 028/519] Don't escape object ids when passing to the contenttypes.shortcut view. This commit also changes the string pk to string_pk instead of id, to test if the admin uses .pk throughout the codebase. --- .../contrib/admin/templates/admin/change_form.html | 2 +- .../admin_views/fixtures/string-primary-key.xml | 6 +++--- tests/regressiontests/admin_views/models.py | 7 +++++-- tests/regressiontests/admin_views/tests.py | 14 +++++++++++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 79c4f1bdf4..ead37b187f 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -31,7 +31,7 @@ {% endif %}{% endif %} diff --git a/tests/regressiontests/admin_views/fixtures/string-primary-key.xml b/tests/regressiontests/admin_views/fixtures/string-primary-key.xml index 8e1dbf047f..1792cb2d7e 100644 --- a/tests/regressiontests/admin_views/fixtures/string-primary-key.xml +++ b/tests/regressiontests/admin_views/fixtures/string-primary-key.xml @@ -1,6 +1,6 @@ - #%" {}|\^[]`]]> - - \ No newline at end of file + #%" {}|\^[]`]]> + + diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index 17533f9f80..2da86e2099 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -93,10 +93,13 @@ class CustomArticle(models.Model): class ModelWithStringPrimaryKey(models.Model): - id = models.CharField(max_length=255, primary_key=True) + string_pk = models.CharField(max_length=255, primary_key=True) def __unicode__(self): - return self.id + return self.string_pk + + def get_absolute_url(self): + return u'/dummy/%s/' % self.string_pk class Color(models.Model): diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index f9a9b15114..e8d05f78ff 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -1403,7 +1403,7 @@ class AdminViewStringPrimaryKeyTest(TestCase): def test_url_conflicts_with_add(self): "A model with a primary key that ends with add should be visible" - add_model = ModelWithStringPrimaryKey(id="i have something to add") + add_model = ModelWithStringPrimaryKey(pk="i have something to add") add_model.save() response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(add_model.pk)) should_contain = """

    Change model with string primary key

    """ @@ -1411,7 +1411,7 @@ class AdminViewStringPrimaryKeyTest(TestCase): def test_url_conflicts_with_delete(self): "A model with a primary key that ends with delete should be visible" - delete_model = ModelWithStringPrimaryKey(id="delete") + delete_model = ModelWithStringPrimaryKey(pk="delete") delete_model.save() response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(delete_model.pk)) should_contain = """

    Change model with string primary key

    """ @@ -1419,12 +1419,20 @@ class AdminViewStringPrimaryKeyTest(TestCase): def test_url_conflicts_with_history(self): "A model with a primary key that ends with history should be visible" - history_model = ModelWithStringPrimaryKey(id="history") + history_model = ModelWithStringPrimaryKey(pk="history") history_model.save() response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(history_model.pk)) should_contain = """

    Change model with string primary key

    """ self.assertContains(response, should_contain) + def test_shortcut_view_with_escaping(self): + "'View on site should' work properly with char fields" + model = ModelWithStringPrimaryKey(pk='abc_123') + model.save() + response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(model.pk)) + should_contain = '/%s/" class="viewsitelink">' % model.pk + self.assertContains(response, should_contain) + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class SecureViewTests(TestCase): From 4a103086d5c67fa4fcc53c106c9fdf644c742dd8 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 7 Jun 2012 18:08:47 +0200 Subject: [PATCH 029/519] Fixed #18269 -- Applied unicode_literals for Python 3 compatibility. Thanks Vinay Sajip for the support of his django3 branch and Jannis Leidel for the review. --- django/conf/locale/__init__.py | 146 +-- django/conf/locale/bg/formats.py | 3 +- django/conf/locale/cs/formats.py | 3 +- django/conf/locale/en/formats.py | 4 +- django/conf/locale/es_MX/formats.py | 3 +- django/conf/locale/et/formats.py | 3 +- django/conf/locale/fi/formats.py | 3 +- django/conf/locale/fr/formats.py | 3 +- django/conf/locale/he/formats.py | 1 + django/conf/locale/hu/formats.py | 3 +- django/conf/locale/ja/formats.py | 1 + django/conf/locale/km/formats.py | 1 + django/conf/locale/ko/formats.py | 1 + django/conf/locale/lv/formats.py | 3 +- django/conf/locale/nb/formats.py | 3 +- django/conf/locale/nn/formats.py | 3 +- django/conf/locale/pl/formats.py | 3 +- django/conf/locale/pt_BR/formats.py | 1 + django/conf/locale/ru/formats.py | 3 +- django/conf/locale/sk/formats.py | 3 +- django/conf/locale/sv/formats.py | 3 +- django/conf/locale/uk/formats.py | 4 +- django/conf/locale/vi/formats.py | 1 + django/contrib/admin/forms.py | 5 +- django/contrib/admin/helpers.py | 18 +- django/contrib/admin/models.py | 4 +- .../contrib/admin/templatetags/admin_list.py | 18 +- django/contrib/admin/util.py | 6 +- django/contrib/admin/widgets.py | 36 +- django/contrib/admindocs/tests/__init__.py | 8 +- django/contrib/auth/backends.py | 4 +- django/contrib/auth/hashers.py | 2 + django/contrib/auth/management/__init__.py | 8 +- django/contrib/auth/models.py | 6 +- django/contrib/auth/tests/auth_backends.py | 10 +- django/contrib/auth/tests/forms.py | 6 +- django/contrib/auth/tests/hashers.py | 18 +- django/contrib/auth/tests/management.py | 8 +- django/contrib/comments/admin.py | 6 +- django/contrib/contenttypes/generic.py | 3 +- django/contrib/contenttypes/tests.py | 4 +- django/contrib/contenttypes/views.py | 6 +- django/contrib/databrowse/datastructures.py | 11 +- .../contrib/databrowse/plugins/calendars.py | 10 +- .../databrowse/plugins/fieldchoices.py | 10 +- django/contrib/databrowse/sites.py | 4 +- django/contrib/flatpages/models.py | 4 +- django/contrib/flatpages/tests/forms.py | 6 +- django/contrib/formtools/tests/__init__.py | 58 +- .../contrib/formtools/tests/wizard/forms.py | 10 +- .../tests/wizard/namedwizardtests/tests.py | 24 +- .../tests/wizard/wizardtests/tests.py | 26 +- django/contrib/gis/feeds.py | 22 +- django/contrib/gis/forms/fields.py | 10 +- .../contrib/gis/gdal/tests/test_envelope.py | 2 +- django/contrib/gis/geoip/tests.py | 8 +- django/contrib/gis/sitemaps/views.py | 8 +- django/contrib/gis/tests/geoapp/test_feeds.py | 6 +- .../contrib/gis/tests/geoapp/test_sitemaps.py | 10 +- django/contrib/gis/views.py | 6 +- .../contrib/humanize/templatetags/humanize.py | 27 +- django/contrib/humanize/tests.py | 11 +- django/contrib/localflavor/ar/ar_provinces.py | 49 +- django/contrib/localflavor/ar/forms.py | 12 +- django/contrib/localflavor/at/forms.py | 8 +- django/contrib/localflavor/au/forms.py | 8 +- django/contrib/localflavor/br/br_states.py | 23 +- django/contrib/localflavor/br/forms.py | 16 +- django/contrib/localflavor/ca/forms.py | 22 +- django/contrib/localflavor/ch/forms.py | 10 +- django/contrib/localflavor/cl/cl_regions.py | 31 +- django/contrib/localflavor/cl/forms.py | 10 +- django/contrib/localflavor/cn/cn_provinces.py | 70 +- django/contrib/localflavor/cn/forms.py | 20 +- .../contrib/localflavor/co/co_departments.py | 67 +- django/contrib/localflavor/cz/forms.py | 20 +- django/contrib/localflavor/de/forms.py | 8 +- django/contrib/localflavor/de_CH/formats.py | 4 +- django/contrib/localflavor/ec/ec_provinces.py | 49 +- django/contrib/localflavor/es/forms.py | 4 +- .../localflavor/fi/fi_municipalities.py | 688 ++++++------- django/contrib/localflavor/fi/forms.py | 6 +- django/contrib/localflavor/fr/forms.py | 6 +- .../contrib/localflavor/fr/fr_department.py | 219 +++-- django/contrib/localflavor/gb/forms.py | 6 +- django/contrib/localflavor/hk/forms.py | 6 +- django/contrib/localflavor/hr/forms.py | 22 +- django/contrib/localflavor/hr/hr_choices.py | 54 +- django/contrib/localflavor/id/forms.py | 12 +- django/contrib/localflavor/il/forms.py | 7 +- django/contrib/localflavor/in_/forms.py | 14 +- django/contrib/localflavor/is_/forms.py | 8 +- .../contrib/localflavor/is_/is_postalcodes.py | 295 +++--- django/contrib/localflavor/it/forms.py | 12 +- django/contrib/localflavor/it/it_province.py | 3 +- django/contrib/localflavor/it/it_region.py | 3 +- django/contrib/localflavor/it/util.py | 4 +- django/contrib/localflavor/kw/forms.py | 4 +- django/contrib/localflavor/mk/forms.py | 14 +- django/contrib/localflavor/mk/mk_choices.py | 170 ++-- django/contrib/localflavor/mx/forms.py | 61 +- django/contrib/localflavor/mx/mx_states.py | 65 +- django/contrib/localflavor/nl/forms.py | 10 +- django/contrib/localflavor/no/forms.py | 6 +- .../localflavor/no/no_municipalities.py | 43 +- django/contrib/localflavor/pe/forms.py | 6 +- django/contrib/localflavor/pe/pe_region.py | 51 +- django/contrib/localflavor/pl/forms.py | 36 +- .../localflavor/pl/pl_administrativeunits.py | 753 +++++++------- django/contrib/localflavor/pt/forms.py | 8 +- .../contrib/localflavor/py/py_department.py | 73 +- django/contrib/localflavor/ro/forms.py | 14 +- django/contrib/localflavor/ro/ro_counties.py | 85 +- django/contrib/localflavor/ru/forms.py | 8 +- django/contrib/localflavor/se/forms.py | 6 +- django/contrib/localflavor/se/se_counties.py | 43 +- django/contrib/localflavor/si/forms.py | 18 +- .../contrib/localflavor/si/si_postalcodes.py | 927 +++++++++--------- django/contrib/localflavor/sk/forms.py | 4 +- django/contrib/localflavor/tr/forms.py | 18 +- django/contrib/localflavor/us/forms.py | 12 +- django/contrib/localflavor/uy/forms.py | 4 +- .../contrib/localflavor/uy/uy_departaments.py | 39 +- django/contrib/localflavor/za/forms.py | 7 +- django/contrib/messages/storage/base.py | 4 +- django/contrib/sitemaps/tests/flatpages.py | 10 +- django/contrib/sitemaps/tests/http.py | 4 +- django/contrib/sites/tests.py | 8 +- .../management/commands/collectstatic.py | 30 +- .../management/commands/findstatic.py | 8 +- django/contrib/staticfiles/storage.py | 11 +- django/contrib/syndication/views.py | 4 +- django/contrib/webdesign/lorem_ipsum.py | 12 +- .../webdesign/templatetags/webdesign.py | 4 +- django/contrib/webdesign/tests.py | 5 +- django/core/context_processors.py | 3 +- django/core/files/base.py | 4 +- django/core/files/uploadhandler.py | 6 +- django/core/handlers/base.py | 8 +- django/core/handlers/wsgi.py | 12 +- django/core/mail/__init__.py | 5 +- django/core/mail/message.py | 8 +- .../management/commands/createcachetable.py | 4 +- django/core/management/commands/loaddata.py | 5 +- django/core/management/commands/sql.py | 4 +- django/core/management/commands/sqlall.py | 4 +- django/core/management/commands/sqlclear.py | 4 +- django/core/management/commands/sqlcustom.py | 4 +- django/core/management/commands/sqlflush.py | 4 +- django/core/management/commands/sqlindexes.py | 4 +- .../management/commands/sqlsequencereset.py | 4 +- django/core/management/sql.py | 6 +- django/core/management/validation.py | 3 +- django/core/serializers/json.py | 5 +- django/core/serializers/python.py | 3 +- django/core/serializers/xml_serializer.py | 4 +- django/core/urlresolvers.py | 9 +- django/core/validators.py | 32 +- django/db/backends/mysql/base.py | 5 +- django/db/backends/oracle/base.py | 10 +- .../postgresql_psycopg2/introspection.py | 4 +- .../postgresql_psycopg2/operations.py | 14 +- django/db/backends/sqlite3/base.py | 7 +- django/db/backends/util.py | 6 +- django/db/models/base.py | 8 +- django/db/models/fields/__init__.py | 60 +- django/db/models/fields/files.py | 2 +- django/forms/extras/widgets.py | 3 +- django/forms/fields.py | 72 +- django/forms/forms.py | 36 +- django/forms/formsets.py | 22 +- django/forms/models.py | 20 +- django/forms/util.py | 22 +- django/forms/widgets.py | 90 +- django/http/__init__.py | 8 +- django/http/multipartparser.py | 14 +- django/template/base.py | 8 +- django/template/defaultfilters.py | 81 +- django/template/defaulttags.py | 15 +- django/templatetags/cache.py | 6 +- django/templatetags/i18n.py | 9 +- django/test/client.py | 2 +- django/test/html.py | 19 +- django/test/testcases.py | 52 +- django/test/utils.py | 2 + django/utils/crypto.py | 6 +- django/utils/dateformat.py | 37 +- django/utils/encoding.py | 6 +- django/utils/feedgenerator.py | 161 +-- django/utils/html.py | 42 +- django/utils/regex_helper.py | 33 +- django/utils/text.py | 46 +- django/utils/timesince.py | 4 +- django/utils/translation/__init__.py | 4 +- django/utils/translation/trans_real.py | 43 +- django/utils/tzinfo.py | 4 +- django/utils/version.py | 2 + django/views/debug.py | 6 +- django/views/generic/base.py | 8 +- django/views/generic/dates.py | 20 +- django/views/generic/detail.py | 14 +- django/views/generic/list.py | 10 +- django/views/static.py | 7 +- docs/conf.py | 14 +- docs/ref/files/file.txt | 3 +- docs/ref/forms/fields.txt | 2 - docs/ref/forms/validation.txt | 4 +- docs/ref/unicode.txt | 44 +- docs/topics/db/models.txt | 16 +- docs/topics/i18n/translation.txt | 4 +- tests/modeltests/aggregation/tests.py | 100 +- tests/modeltests/basic/tests.py | 14 +- tests/modeltests/custom_columns/models.py | 4 +- tests/modeltests/custom_managers/models.py | 4 +- tests/modeltests/custom_pk/models.py | 4 +- tests/modeltests/custom_pk/tests.py | 4 +- tests/modeltests/distinct_on_fields/models.py | 4 +- tests/modeltests/distinct_on_fields/tests.py | 4 +- tests/modeltests/expressions/models.py | 3 +- tests/modeltests/expressions/tests.py | 20 +- tests/modeltests/field_subclassing/fields.py | 4 +- tests/modeltests/files/tests.py | 26 +- .../fixtures_model_package/tests.py | 26 +- tests/modeltests/generic_relations/models.py | 4 +- tests/modeltests/generic_relations/tests.py | 52 +- tests/modeltests/get_or_create/models.py | 4 +- .../invalid_models/invalid_models/models.py | 8 +- tests/modeltests/lookup/models.py | 6 +- tests/modeltests/lookup/tests.py | 44 +- tests/modeltests/m2m_and_m2o/models.py | 3 +- tests/modeltests/m2m_intermediary/models.py | 5 +- tests/modeltests/m2m_through/tests.py | 2 +- tests/modeltests/many_to_one/models.py | 3 +- tests/modeltests/many_to_one/tests.py | 6 +- tests/modeltests/model_forms/models.py | 3 +- tests/modeltests/model_forms/tests.py | 226 ++--- tests/modeltests/model_formsets/models.py | 10 +- tests/modeltests/model_formsets/tests.py | 82 +- tests/modeltests/model_inheritance/models.py | 15 +- tests/modeltests/one_to_one/models.py | 9 +- tests/modeltests/pagination/tests.py | 8 +- tests/modeltests/prefetch_related/tests.py | 38 +- tests/modeltests/proxy_models/tests.py | 6 +- tests/modeltests/save_delete_hooks/models.py | 3 +- tests/modeltests/select_related/tests.py | 6 +- tests/modeltests/serializers/models.py | 5 +- tests/modeltests/serializers/tests.py | 6 +- tests/modeltests/signals/models.py | 5 +- tests/modeltests/str/tests.py | 4 +- tests/modeltests/test_client/models.py | 6 +- tests/modeltests/timezones/tests.py | 32 +- tests/modeltests/transactions/models.py | 3 +- tests/modeltests/update/tests.py | 10 +- tests/modeltests/validation/models.py | 8 +- .../validation/test_error_messages.py | 70 +- tests/modeltests/validation/test_unique.py | 14 +- tests/modeltests/validation/tests.py | 4 +- tests/modeltests/validation/validators.py | 4 +- tests/modeltests/validators/tests.py | 15 +- .../admin_custom_urls/tests.py | 8 +- tests/regressiontests/admin_filters/models.py | 6 +- tests/regressiontests/admin_filters/tests.py | 92 +- tests/regressiontests/admin_inlines/models.py | 6 +- tests/regressiontests/admin_inlines/tests.py | 10 +- tests/regressiontests/admin_ordering/tests.py | 14 +- tests/regressiontests/admin_util/tests.py | 4 +- tests/regressiontests/admin_views/admin.py | 6 +- tests/regressiontests/admin_views/models.py | 40 +- tests/regressiontests/admin_views/tests.py | 267 +++-- tests/regressiontests/admin_widgets/models.py | 4 +- tests/regressiontests/admin_widgets/tests.py | 12 +- .../aggregation_regress/tests.py | 84 +- tests/regressiontests/backends/models.py | 6 +- tests/regressiontests/backends/tests.py | 8 +- tests/regressiontests/cache/tests.py | 22 +- .../comment_tests/tests/comment_view_tests.py | 6 +- tests/regressiontests/csrf_tests/tests.py | 9 +- .../custom_columns_regress/models.py | 3 +- tests/regressiontests/datatypes/tests.py | 6 +- tests/regressiontests/defaultfilters/tests.py | 651 ++++++------ .../expressions_regress/models.py | 3 +- tests/regressiontests/extra_regress/models.py | 6 +- tests/regressiontests/extra_regress/tests.py | 36 +- tests/regressiontests/file_storage/tests.py | 8 +- tests/regressiontests/file_uploads/tests.py | 23 +- tests/regressiontests/file_uploads/views.py | 4 +- .../fixtures_regress/models.py | 10 +- .../regressiontests/fixtures_regress/tests.py | 4 +- tests/regressiontests/forms/models.py | 6 +- .../forms/tests/error_messages.py | 117 ++- tests/regressiontests/forms/tests/extra.py | 175 ++-- tests/regressiontests/forms/tests/fields.py | 568 +++++------ tests/regressiontests/forms/tests/forms.py | 255 ++--- tests/regressiontests/forms/tests/formsets.py | 88 +- tests/regressiontests/forms/tests/models.py | 18 +- .../forms/tests/regressions.py | 34 +- tests/regressiontests/forms/tests/util.py | 16 +- tests/regressiontests/forms/tests/widgets.py | 325 +++--- .../generic_inline_admin/tests.py | 41 +- tests/regressiontests/httpwrappers/tests.py | 58 +- .../i18n/contenttypes/tests.py | 5 +- tests/regressiontests/i18n/patterns/tests.py | 10 +- tests/regressiontests/i18n/tests.py | 296 +++--- .../regressiontests/inline_formsets/tests.py | 46 +- tests/regressiontests/introspection/models.py | 4 +- tests/regressiontests/introspection/tests.py | 6 +- tests/regressiontests/localflavor/ar/tests.py | 28 +- tests/regressiontests/localflavor/at/tests.py | 8 +- tests/regressiontests/localflavor/au/tests.py | 14 +- tests/regressiontests/localflavor/be/tests.py | 34 +- tests/regressiontests/localflavor/br/tests.py | 38 +- tests/regressiontests/localflavor/ca/tests.py | 12 +- tests/regressiontests/localflavor/ch/tests.py | 10 +- tests/regressiontests/localflavor/cl/tests.py | 8 +- tests/regressiontests/localflavor/cn/tests.py | 37 +- tests/regressiontests/localflavor/co/tests.py | 4 +- tests/regressiontests/localflavor/cz/tests.py | 12 +- tests/regressiontests/localflavor/de/tests.py | 8 +- tests/regressiontests/localflavor/ec/tests.py | 4 +- tests/regressiontests/localflavor/es/tests.py | 22 +- tests/regressiontests/localflavor/fi/tests.py | 8 +- tests/regressiontests/localflavor/fr/tests.py | 8 +- tests/regressiontests/localflavor/gb/tests.py | 6 +- .../localflavor/generic/tests.py | 6 +- tests/regressiontests/localflavor/hr/tests.py | 38 +- tests/regressiontests/localflavor/id/tests.py | 50 +- tests/regressiontests/localflavor/ie/tests.py | 4 +- tests/regressiontests/localflavor/il/tests.py | 6 +- .../regressiontests/localflavor/in_/tests.py | 10 +- .../regressiontests/localflavor/is_/tests.py | 18 +- tests/regressiontests/localflavor/it/tests.py | 10 +- tests/regressiontests/localflavor/jp/tests.py | 6 +- tests/regressiontests/localflavor/kw/tests.py | 4 +- tests/regressiontests/localflavor/mk/tests.py | 24 +- tests/regressiontests/localflavor/mx/tests.py | 66 +- tests/regressiontests/localflavor/nl/tests.py | 10 +- tests/regressiontests/localflavor/pl/tests.py | 26 +- tests/regressiontests/localflavor/pt/tests.py | 6 +- tests/regressiontests/localflavor/py/tests.py | 6 +- tests/regressiontests/localflavor/ro/tests.py | 42 +- tests/regressiontests/localflavor/ru/tests.py | 12 +- tests/regressiontests/localflavor/se/tests.py | 12 +- tests/regressiontests/localflavor/si/tests.py | 17 +- tests/regressiontests/localflavor/sk/tests.py | 8 +- tests/regressiontests/localflavor/us/tests.py | 14 +- tests/regressiontests/localflavor/uy/tests.py | 14 +- tests/regressiontests/localflavor/za/tests.py | 6 +- tests/regressiontests/logging_tests/tests.py | 6 +- .../m2m_through_regress/models.py | 4 +- tests/regressiontests/mail/tests.py | 16 +- .../many_to_one_regress/models.py | 3 +- tests/regressiontests/model_fields/tests.py | 10 +- .../model_forms_regress/models.py | 4 +- .../model_forms_regress/tests.py | 20 +- .../model_formsets_regress/tests.py | 114 +-- .../model_inheritance_regress/models.py | 12 +- .../model_inheritance_regress/tests.py | 19 +- .../models.py | 5 +- tests/regressiontests/model_regress/tests.py | 4 +- tests/regressiontests/modeladmin/tests.py | 16 +- .../multiple_database/tests.py | 108 +- tests/regressiontests/null_fk/tests.py | 10 +- .../null_fk_ordering/models.py | 3 +- tests/regressiontests/null_queries/models.py | 6 +- .../one_to_one_regress/models.py | 10 +- .../pagination_regress/tests.py | 6 +- tests/regressiontests/queries/models.py | 5 +- tests/regressiontests/queries/tests.py | 21 +- tests/regressiontests/requests/tests.py | 93 +- .../select_related_regress/models.py | 10 +- .../select_related_regress/tests.py | 26 +- .../serializers_regress/tests.py | 4 +- .../signed_cookies_tests/tests.py | 8 +- tests/regressiontests/signing/tests.py | 10 +- .../staticfiles_tests/tests.py | 11 +- tests/regressiontests/string_lookup/tests.py | 6 +- tests/regressiontests/syndication/feeds.py | 10 +- tests/regressiontests/syndication/tests.py | 4 +- tests/regressiontests/templates/callables.py | 18 +- tests/regressiontests/templates/custom.py | 126 +-- tests/regressiontests/templates/filters.py | 136 +-- tests/regressiontests/templates/parser.py | 32 +- tests/regressiontests/templates/tests.py | 78 +- tests/regressiontests/templates/unicode.py | 10 +- tests/regressiontests/templates/urls.py | 6 +- .../test_client_regress/tests.py | 24 +- tests/regressiontests/test_utils/tests.py | 10 +- tests/regressiontests/text/tests.py | 87 +- tests/regressiontests/utils/dateformat.py | 76 +- tests/regressiontests/utils/feedgenerator.py | 6 +- tests/regressiontests/utils/html.py | 20 +- tests/regressiontests/utils/ipv6.py | 20 +- tests/regressiontests/utils/jslex.py | 2 +- tests/regressiontests/utils/regex_helper.py | 16 +- .../regressiontests/utils/simplelazyobject.py | 8 +- tests/regressiontests/utils/text.py | 84 +- tests/regressiontests/utils/timesince.py | 72 +- tests/regressiontests/views/__init__.py | 9 +- tests/regressiontests/views/generic_urls.py | 10 +- tests/regressiontests/views/tests/specials.py | 4 +- tests/regressiontests/wsgi/tests.py | 4 +- 401 files changed, 6647 insertions(+), 6157 deletions(-) diff --git a/django/conf/locale/__init__.py b/django/conf/locale/__init__.py index af08c00e98..93e98194a4 100644 --- a/django/conf/locale/__init__.py +++ b/django/conf/locale/__init__.py @@ -1,434 +1,436 @@ +from __future__ import unicode_literals + LANG_INFO = { 'ar': { 'bidi': True, 'code': 'ar', 'name': 'Arabic', - 'name_local': u'\u0627\u0644\u0639\u0631\u0628\u064a\u0651\u0629', + 'name_local': '\u0627\u0644\u0639\u0631\u0628\u064a\u0651\u0629', }, 'az': { 'bidi': True, 'code': 'az', 'name': 'Azerbaijani', - 'name_local': u'az\u0259rbaycan dili', + 'name_local': 'az\u0259rbaycan dili', }, 'bg': { 'bidi': False, 'code': 'bg', 'name': 'Bulgarian', - 'name_local': u'\u0431\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438', + 'name_local': '\u0431\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438', }, 'bn': { 'bidi': False, 'code': 'bn', 'name': 'Bengali', - 'name_local': u'\u09ac\u09be\u0982\u09b2\u09be', + 'name_local': '\u09ac\u09be\u0982\u09b2\u09be', }, 'bs': { 'bidi': False, 'code': 'bs', 'name': 'Bosnian', - 'name_local': u'bosanski', + 'name_local': 'bosanski', }, 'ca': { 'bidi': False, 'code': 'ca', 'name': 'Catalan', - 'name_local': u'catal\xe0', + 'name_local': 'catal\xe0', }, 'cs': { 'bidi': False, 'code': 'cs', 'name': 'Czech', - 'name_local': u'\u010desky', + 'name_local': '\u010desky', }, 'cy': { 'bidi': False, 'code': 'cy', 'name': 'Welsh', - 'name_local': u'Cymraeg', + 'name_local': 'Cymraeg', }, 'da': { 'bidi': False, 'code': 'da', 'name': 'Danish', - 'name_local': u'Dansk', + 'name_local': 'Dansk', }, 'de': { 'bidi': False, 'code': 'de', 'name': 'German', - 'name_local': u'Deutsch', + 'name_local': 'Deutsch', }, 'el': { 'bidi': False, 'code': 'el', 'name': 'Greek', - 'name_local': u'\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac', + 'name_local': '\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac', }, 'en': { 'bidi': False, 'code': 'en', 'name': 'English', - 'name_local': u'English', + 'name_local': 'English', }, 'en-gb': { 'bidi': False, 'code': 'en-gb', 'name': 'British English', - 'name_local': u'British English', + 'name_local': 'British English', }, 'eo': { 'bidi': False, 'code': 'eo', 'name': 'Esperanto', - 'name_local': u'Esperanto', + 'name_local': 'Esperanto', }, 'es': { 'bidi': False, 'code': 'es', 'name': 'Spanish', - 'name_local': u'espa\xf1ol', + 'name_local': 'espa\xf1ol', }, 'es-ar': { 'bidi': False, 'code': 'es-ar', 'name': 'Argentinian Spanish', - 'name_local': u'espa\xf1ol de Argentina', + 'name_local': 'espa\xf1ol de Argentina', }, 'es-mx': { 'bidi': False, 'code': 'es-mx', 'name': 'Mexican Spanish', - 'name_local': u'espa\xf1ol de Mexico', + 'name_local': 'espa\xf1ol de Mexico', }, 'es-ni': { 'bidi': False, 'code': 'es-ni', 'name': 'Nicaraguan Spanish', - 'name_local': u'espa\xf1ol de Nicaragua', + 'name_local': 'espa\xf1ol de Nicaragua', }, 'et': { 'bidi': False, 'code': 'et', 'name': 'Estonian', - 'name_local': u'eesti', + 'name_local': 'eesti', }, 'eu': { 'bidi': False, 'code': 'eu', 'name': 'Basque', - 'name_local': u'Basque', + 'name_local': 'Basque', }, 'fa': { 'bidi': True, 'code': 'fa', 'name': 'Persian', - 'name_local': u'\u0641\u0627\u0631\u0633\u06cc', + 'name_local': '\u0641\u0627\u0631\u0633\u06cc', }, 'fi': { 'bidi': False, 'code': 'fi', 'name': 'Finnish', - 'name_local': u'suomi', + 'name_local': 'suomi', }, 'fr': { 'bidi': False, 'code': 'fr', 'name': 'French', - 'name_local': u'Fran\xe7ais', + 'name_local': 'Fran\xe7ais', }, 'fy-nl': { 'bidi': False, 'code': 'fy-nl', 'name': 'Frisian', - 'name_local': u'Frisian', + 'name_local': 'Frisian', }, 'ga': { 'bidi': False, 'code': 'ga', 'name': 'Irish', - 'name_local': u'Gaeilge', + 'name_local': 'Gaeilge', }, 'gl': { 'bidi': False, 'code': 'gl', 'name': 'Galician', - 'name_local': u'galego', + 'name_local': 'galego', }, 'he': { 'bidi': True, 'code': 'he', 'name': 'Hebrew', - 'name_local': u'\u05e2\u05d1\u05e8\u05d9\u05ea', + 'name_local': '\u05e2\u05d1\u05e8\u05d9\u05ea', }, 'hi': { 'bidi': False, 'code': 'hi', 'name': 'Hindi', - 'name_local': u'Hindi', + 'name_local': 'Hindi', }, 'hr': { 'bidi': False, 'code': 'hr', 'name': 'Croatian', - 'name_local': u'Hrvatski', + 'name_local': 'Hrvatski', }, 'hu': { 'bidi': False, 'code': 'hu', 'name': 'Hungarian', - 'name_local': u'Magyar', + 'name_local': 'Magyar', }, 'id': { 'bidi': False, 'code': 'id', 'name': 'Indonesian', - 'name_local': u'Bahasa Indonesia', + 'name_local': 'Bahasa Indonesia', }, 'is': { 'bidi': False, 'code': 'is', 'name': 'Icelandic', - 'name_local': u'\xcdslenska', + 'name_local': '\xcdslenska', }, 'it': { 'bidi': False, 'code': 'it', 'name': 'Italian', - 'name_local': u'italiano', + 'name_local': 'italiano', }, 'ja': { 'bidi': False, 'code': 'ja', 'name': 'Japanese', - 'name_local': u'\u65e5\u672c\u8a9e', + 'name_local': '\u65e5\u672c\u8a9e', }, 'ka': { 'bidi': False, 'code': 'ka', 'name': 'Georgian', - 'name_local': u'\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8', + 'name_local': '\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8', }, 'kk': { 'bidi': False, 'code': 'kk', 'name': 'Kazakh', - 'name_local': u'\u049a\u0430\u0437\u0430\u049b', + 'name_local': '\u049a\u0430\u0437\u0430\u049b', }, 'km': { 'bidi': False, 'code': 'km', 'name': 'Khmer', - 'name_local': u'Khmer', + 'name_local': 'Khmer', }, 'kn': { 'bidi': False, 'code': 'kn', 'name': 'Kannada', - 'name_local': u'Kannada', + 'name_local': 'Kannada', }, 'ko': { 'bidi': False, 'code': 'ko', 'name': 'Korean', - 'name_local': u'\ud55c\uad6d\uc5b4', + 'name_local': '\ud55c\uad6d\uc5b4', }, 'lt': { 'bidi': False, 'code': 'lt', 'name': 'Lithuanian', - 'name_local': u'Lithuanian', + 'name_local': 'Lithuanian', }, 'lv': { 'bidi': False, 'code': 'lv', 'name': 'Latvian', - 'name_local': u'latvie\u0161u', + 'name_local': 'latvie\u0161u', }, 'mk': { 'bidi': False, 'code': 'mk', 'name': 'Macedonian', - 'name_local': u'\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438', + 'name_local': '\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438', }, 'ml': { 'bidi': False, 'code': 'ml', 'name': 'Malayalam', - 'name_local': u'Malayalam', + 'name_local': 'Malayalam', }, 'mn': { 'bidi': False, 'code': 'mn', 'name': 'Mongolian', - 'name_local': u'Mongolian', + 'name_local': 'Mongolian', }, 'nb': { 'bidi': False, 'code': 'nb', 'name': 'Norwegian Bokmal', - 'name_local': u'Norsk (bokm\xe5l)', + 'name_local': 'Norsk (bokm\xe5l)', }, 'ne': { 'bidi': False, 'code': 'ne', 'name': 'Nepali', - 'name_local': u'\u0928\u0947\u092a\u093e\u0932\u0940', + 'name_local': '\u0928\u0947\u092a\u093e\u0932\u0940', }, 'nl': { 'bidi': False, 'code': 'nl', 'name': 'Dutch', - 'name_local': u'Nederlands', + 'name_local': 'Nederlands', }, 'nn': { 'bidi': False, 'code': 'nn', 'name': 'Norwegian Nynorsk', - 'name_local': u'Norsk (nynorsk)', + 'name_local': 'Norsk (nynorsk)', }, 'no': { 'bidi': False, 'code': 'no', 'name': 'Norwegian', - 'name_local': u'Norsk', + 'name_local': 'Norsk', }, 'pa': { 'bidi': False, 'code': 'pa', 'name': 'Punjabi', - 'name_local': u'Punjabi', + 'name_local': 'Punjabi', }, 'pl': { 'bidi': False, 'code': 'pl', 'name': 'Polish', - 'name_local': u'polski', + 'name_local': 'polski', }, 'pt': { 'bidi': False, 'code': 'pt', 'name': 'Portuguese', - 'name_local': u'Portugu\xeas', + 'name_local': 'Portugu\xeas', }, 'pt-br': { 'bidi': False, 'code': 'pt-br', 'name': 'Brazilian Portuguese', - 'name_local': u'Portugu\xeas Brasileiro', + 'name_local': 'Portugu\xeas Brasileiro', }, 'ro': { 'bidi': False, 'code': 'ro', 'name': 'Romanian', - 'name_local': u'Rom\xe2n\u0103', + 'name_local': 'Rom\xe2n\u0103', }, 'ru': { 'bidi': False, 'code': 'ru', 'name': 'Russian', - 'name_local': u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439', + 'name_local': '\u0420\u0443\u0441\u0441\u043a\u0438\u0439', }, 'sk': { 'bidi': False, 'code': 'sk', 'name': 'Slovak', - 'name_local': u'slovensk\xfd', + 'name_local': 'slovensk\xfd', }, 'sl': { 'bidi': False, 'code': 'sl', 'name': 'Slovenian', - 'name_local': u'Sloven\u0161\u010dina', + 'name_local': 'Sloven\u0161\u010dina', }, 'sq': { 'bidi': False, 'code': 'sq', 'name': 'Albanian', - 'name_local': u'Albanian', + 'name_local': 'Albanian', }, 'sr': { 'bidi': False, 'code': 'sr', 'name': 'Serbian', - 'name_local': u'\u0441\u0440\u043f\u0441\u043a\u0438', + 'name_local': '\u0441\u0440\u043f\u0441\u043a\u0438', }, 'sr-latn': { 'bidi': False, 'code': 'sr-latn', 'name': 'Serbian Latin', - 'name_local': u'srpski (latinica)', + 'name_local': 'srpski (latinica)', }, 'sv': { 'bidi': False, 'code': 'sv', 'name': 'Swedish', - 'name_local': u'Svenska', + 'name_local': 'Svenska', }, 'sw': { 'bidi': False, 'code': 'sw', 'name': 'Swahili', - 'name_local': u'Kiswahili', + 'name_local': 'Kiswahili', }, 'ta': { 'bidi': False, 'code': 'ta', 'name': 'Tamil', - 'name_local': u'\u0ba4\u0bae\u0bbf\u0bb4\u0bcd', + 'name_local': '\u0ba4\u0bae\u0bbf\u0bb4\u0bcd', }, 'te': { 'bidi': False, 'code': 'te', 'name': 'Telugu', - 'name_local': u'\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41', + 'name_local': '\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41', }, 'th': { 'bidi': False, 'code': 'th', 'name': 'Thai', - 'name_local': u'Thai', + 'name_local': 'Thai', }, 'tr': { 'bidi': False, 'code': 'tr', 'name': 'Turkish', - 'name_local': u'T\xfcrk\xe7e', + 'name_local': 'T\xfcrk\xe7e', }, 'tt': { 'bidi': False, 'code': 'tt', 'name': 'Tatar', - 'name_local': u'\u0422\u0430\u0442\u0430\u0440\u0447\u0430', + 'name_local': '\u0422\u0430\u0442\u0430\u0440\u0447\u0430', }, 'uk': { 'bidi': False, 'code': 'uk', 'name': 'Ukrainian', - 'name_local': u'\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430', + 'name_local': '\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430', }, 'ur': { 'bidi': False, 'code': 'ur', 'name': 'Urdu', - 'name_local': u'\u0627\u0631\u062f\u0648', + 'name_local': '\u0627\u0631\u062f\u0648', }, 'vi': { 'bidi': False, 'code': 'vi', 'name': 'Vietnamese', - 'name_local': u'Vietnamese', + 'name_local': 'Vietnamese', }, 'zh-cn': { 'bidi': False, 'code': 'zh-cn', 'name': 'Simplified Chinese', - 'name_local': u'\u7b80\u4f53\u4e2d\u6587', + 'name_local': '\u7b80\u4f53\u4e2d\u6587', }, 'zh-tw': { 'bidi': False, 'code': 'zh-tw', 'name': 'Traditional Chinese', - 'name_local': u'\u7e41\u9ad4\u4e2d\u6587', + 'name_local': '\u7e41\u9ad4\u4e2d\u6587', } } diff --git a/django/conf/locale/bg/formats.py b/django/conf/locale/bg/formats.py index 3dc4704972..c5e89737b6 100644 --- a/django/conf/locale/bg/formats.py +++ b/django/conf/locale/bg/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -19,5 +20,5 @@ SHORT_DATE_FORMAT = 'd.m.Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space # NUMBER_GROUPING = diff --git a/django/conf/locale/cs/formats.py b/django/conf/locale/cs/formats.py index 56e9e73547..f0b674227b 100644 --- a/django/conf/locale/cs/formats.py +++ b/django/conf/locale/cs/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -33,5 +34,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/en/formats.py b/django/conf/locale/en/formats.py index 68a9276328..6cf2335f5e 100644 --- a/django/conf/locale/en/formats.py +++ b/django/conf/locale/en/formats.py @@ -37,7 +37,7 @@ DATETIME_INPUT_FORMATS = ( '%m/%d/%y %H:%M', # '10/25/06 14:30' '%m/%d/%y', # '10/25/06' ) -DECIMAL_SEPARATOR = u'.' -THOUSAND_SEPARATOR = u',' +DECIMAL_SEPARATOR = '.' +THOUSAND_SEPARATOR = ',' NUMBER_GROUPING = 3 diff --git a/django/conf/locale/es_MX/formats.py b/django/conf/locale/es_MX/formats.py index af534e60ad..cef6b4e2f9 100644 --- a/django/conf/locale/es_MX/formats.py +++ b/django/conf/locale/es_MX/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i:s' @@ -24,5 +25,5 @@ DATETIME_INPUT_FORMATS = ( '%d/%m/%y %H:%M', ) DECIMAL_SEPARATOR = '.' # ',' is also official (less common): NOM-008-SCFI-2002 -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/et/formats.py b/django/conf/locale/et/formats.py index 7de0cd44b5..dd0d1a696e 100644 --- a/django/conf/locale/et/formats.py +++ b/django/conf/locale/et/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -19,5 +20,5 @@ SHORT_DATE_FORMAT = 'd.m.Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space # NUMBER_GROUPING = diff --git a/django/conf/locale/fi/formats.py b/django/conf/locale/fi/formats.py index 198ece66b0..9a658eed40 100644 --- a/django/conf/locale/fi/formats.py +++ b/django/conf/locale/fi/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -19,5 +20,5 @@ SHORT_DATE_FORMAT = 'j.n.Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space # NUMBER_GROUPING = diff --git a/django/conf/locale/fr/formats.py b/django/conf/locale/fr/formats.py index 6d8e334f09..3b5e8861d8 100644 --- a/django/conf/locale/fr/formats.py +++ b/django/conf/locale/fr/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -37,5 +38,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/he/formats.py b/django/conf/locale/he/formats.py index e83d7085a9..1c8b1b5566 100644 --- a/django/conf/locale/he/formats.py +++ b/django/conf/locale/he/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/hu/formats.py b/django/conf/locale/hu/formats.py index 53a8fc7cb0..a9298bf022 100644 --- a/django/conf/locale/hu/formats.py +++ b/django/conf/locale/hu/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -28,5 +29,5 @@ DATETIME_INPUT_FORMATS = ( '%Y.%m.%d.', # '2006.10.25.' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/ja/formats.py b/django/conf/locale/ja/formats.py index ce9d1acb9a..263aa0a344 100644 --- a/django/conf/locale/ja/formats.py +++ b/django/conf/locale/ja/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/km/formats.py b/django/conf/locale/km/formats.py index 70afa27c1d..ace2fc1eea 100644 --- a/django/conf/locale/km/formats.py +++ b/django/conf/locale/km/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/ko/formats.py b/django/conf/locale/ko/formats.py index af65392934..3c0431e4bf 100644 --- a/django/conf/locale/ko/formats.py +++ b/django/conf/locale/ko/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/lv/formats.py b/django/conf/locale/lv/formats.py index 316d777e8b..e4ef524adf 100644 --- a/django/conf/locale/lv/formats.py +++ b/django/conf/locale/lv/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -37,5 +38,5 @@ DATETIME_INPUT_FORMATS = ( '%d.%m.%y', # '25.10.06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/nb/formats.py b/django/conf/locale/nb/formats.py index 8de88512a0..4a896dd80d 100644 --- a/django/conf/locale/nb/formats.py +++ b/django/conf/locale/nb/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -39,5 +40,5 @@ DATETIME_INPUT_FORMATS = ( '%d.%m.%y', # '25.10.06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/nn/formats.py b/django/conf/locale/nn/formats.py index 8de88512a0..4a896dd80d 100644 --- a/django/conf/locale/nn/formats.py +++ b/django/conf/locale/nn/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -39,5 +40,5 @@ DATETIME_INPUT_FORMATS = ( '%d.%m.%y', # '25.10.06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/pl/formats.py b/django/conf/locale/pl/formats.py index 1c538b26ab..021063d474 100644 --- a/django/conf/locale/pl/formats.py +++ b/django/conf/locale/pl/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -33,5 +34,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' +THOUSAND_SEPARATOR = ' ' NUMBER_GROUPING = 3 diff --git a/django/conf/locale/pt_BR/formats.py b/django/conf/locale/pt_BR/formats.py index 6a44d38651..6d9edeecce 100644 --- a/django/conf/locale/pt_BR/formats.py +++ b/django/conf/locale/pt_BR/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/ru/formats.py b/django/conf/locale/ru/formats.py index e825824751..ec46bff400 100644 --- a/django/conf/locale/ru/formats.py +++ b/django/conf/locale/ru/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -36,5 +37,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/sk/formats.py b/django/conf/locale/sk/formats.py index 0443efb376..4b2201f49a 100644 --- a/django/conf/locale/sk/formats.py +++ b/django/conf/locale/sk/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -33,5 +34,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/sv/formats.py b/django/conf/locale/sv/formats.py index ad7d3b3fee..767dbe8d3e 100644 --- a/django/conf/locale/sv/formats.py +++ b/django/conf/locale/sv/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -36,5 +37,5 @@ DATETIME_INPUT_FORMATS = ( '%m/%d/%y', # '10/25/06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/uk/formats.py b/django/conf/locale/uk/formats.py index 8b4606fb64..11293f047a 100644 --- a/django/conf/locale/uk/formats.py +++ b/django/conf/locale/uk/formats.py @@ -2,6 +2,8 @@ # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals + # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j E Y р.' @@ -19,5 +21,5 @@ SHORT_DATE_FORMAT = 'j M Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' +THOUSAND_SEPARATOR = ' ' # NUMBER_GROUPING = diff --git a/django/conf/locale/vi/formats.py b/django/conf/locale/vi/formats.py index 27b4efca25..b491c2d20c 100644 --- a/django/conf/locale/vi/formats.py +++ b/django/conf/locale/vi/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/contrib/admin/forms.py b/django/contrib/admin/forms.py index e790e2e3b6..398af075b1 100644 --- a/django/contrib/admin/forms.py +++ b/django/contrib/admin/forms.py @@ -1,9 +1,10 @@ +from __future__ import unicode_literals + from django import forms from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User - from django.utils.translation import ugettext_lazy, ugettext as _ ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password " @@ -25,7 +26,7 @@ class AdminAuthenticationForm(AuthenticationForm): if username and password: self.user_cache = authenticate(username=username, password=password) if self.user_cache is None: - if u'@' in username: + if '@' in username: # Mistakenly entered e-mail address instead of username? Look it up. try: user = User.objects.get(email=username) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 58130574ee..6c648ecb4a 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import forms from django.contrib.admin.util import (flatten_fieldsets, lookup_field, display_for_field, label_for_field, help_text_for_field) @@ -69,7 +71,7 @@ class Fieldset(object): description=None, model_admin=None): self.form = form self.name, self.fields = name, fields - self.classes = u' '.join(classes) + self.classes = ' '.join(classes) self.description = description self.model_admin = model_admin self.readonly_fields = readonly_fields @@ -109,7 +111,7 @@ class Fieldline(object): yield AdminField(self.form, field, is_first=(i == 0)) def errors(self): - return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields if f not in self.readonly_fields]).strip('\n')) + return mark_safe('\n'.join([self.form[f].errors.as_ul() for f in self.fields if f not in self.readonly_fields]).strip('\n')) class AdminField(object): def __init__(self, form, field, is_first): @@ -121,14 +123,14 @@ class AdminField(object): classes = [] contents = conditional_escape(force_unicode(self.field.label)) if self.is_checkbox: - classes.append(u'vCheckboxLabel') + classes.append('vCheckboxLabel') else: - contents += u':' + contents += ':' if self.field.field.required: - classes.append(u'required') + classes.append('required') if not self.is_first: - classes.append(u'inline') - attrs = classes and {'class': u' '.join(classes)} or {} + classes.append('inline') + attrs = classes and {'class': ' '.join(classes)} or {} return self.field.label_tag(contents=mark_safe(contents), attrs=attrs) def errors(self): @@ -161,7 +163,7 @@ class AdminReadonlyField(object): if not self.is_first: attrs["class"] = "inline" label = self.field['label'] - contents = capfirst(force_unicode(escape(label))) + u":" + contents = capfirst(force_unicode(escape(label))) + ":" return mark_safe('%(contents)s' % { "attrs": flatatt(attrs), "contents": contents, diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 0e5b8a79da..93d8f307c0 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import User @@ -64,5 +66,5 @@ class LogEntry(models.Model): This is relative to the Django admin index page. """ if self.content_type and self.object_id: - return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))) + return mark_safe("%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))) return None diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index bda8d4b4cd..30a85ab7f7 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime from django.contrib.admin.util import (lookup_field, display_for_field, @@ -27,11 +29,11 @@ def paginator_number(cl,i): Generates an individual page index link in a paginated list. """ if i == DOT: - return u'... ' + return '... ' elif i == cl.page_num: - return mark_safe(u'%d ' % (i+1)) + return mark_safe('%d ' % (i+1)) else: - return mark_safe(u'%d ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) + return mark_safe('%d ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) @register.inclusion_tag('admin/pagination.html') def pagination(cl): @@ -163,7 +165,7 @@ def result_headers(cl): def _boolean_icon(field_val): icon_url = static('admin/img/icon-%s.gif' % {True: 'yes', False: 'no', None: 'unknown'}[field_val]) - return mark_safe(u'%s' % (icon_url, field_val)) + return mark_safe('%s' % (icon_url, field_val)) def items_for_result(cl, result, form): """ @@ -179,7 +181,7 @@ def items_for_result(cl, result, form): result_repr = EMPTY_CHANGELIST_VALUE else: if f is None: - if field_name == u'action_checkbox': + if field_name == 'action_checkbox': row_class = ' class="action-checkbox"' allow_tags = getattr(attr, 'allow_tags', False) boolean = getattr(attr, 'boolean', False) @@ -220,7 +222,7 @@ def items_for_result(cl, result, form): attr = pk value = result.serializable_value(attr) result_id = repr(force_unicode(value))[1:] - yield mark_safe(u'<%s%s>%s' % \ + yield mark_safe('<%s%s>%s' % \ (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag)) else: # By default the fields come from ModelAdmin.list_editable, but if we pull @@ -233,9 +235,9 @@ def items_for_result(cl, result, form): result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf)) else: result_repr = conditional_escape(result_repr) - yield mark_safe(u'%s' % (row_class, result_repr)) + yield mark_safe('%s' % (row_class, result_repr)) if form and not form[cl.model._meta.pk.name].is_hidden: - yield mark_safe(u'%s' % force_unicode(form[cl.model._meta.pk.name])) + yield mark_safe('%s' % force_unicode(form[cl.model._meta.pk.name])) class ResultList(list): # Wrapper class used to return items in a list_editable diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 9611b00050..18b10f3cfa 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime import decimal @@ -122,14 +124,14 @@ def get_deleted_objects(objs, opts, user, admin_site, using): if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. - return mark_safe(u'%s: %s' % + return mark_safe('%s: %s' % (escape(capfirst(opts.verbose_name)), admin_url, escape(obj))) else: # Don't display link to edit, because it either has no # admin or is edited inline. - return u'%s: %s' % (capfirst(opts.verbose_name), + return '%s: %s' % (capfirst(opts.verbose_name), force_unicode(obj)) to_delete = collector.nested(format_callback) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 980863ed84..18897bdeb1 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -1,8 +1,10 @@ """ Form Widget classes specific to the Django admin site. """ +from __future__ import unicode_literals import copy + from django import forms from django.contrib.admin.templatetags.admin_static import static from django.core.urlresolvers import reverse @@ -39,12 +41,12 @@ class FilteredSelectMultiple(forms.SelectMultiple): if self.is_stacked: attrs['class'] += 'stacked' output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)] - output.append(u'\n' + output.append('SelectFilter.init("id_%s", "%s", %s, "%s"); });\n' % (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), static('admin/'))) - return mark_safe(u''.join(output)) + return mark_safe(''.join(output)) class AdminDateWidget(forms.DateInput): @@ -83,24 +85,24 @@ class AdminSplitDateTime(forms.SplitDateTimeWidget): forms.MultiWidget.__init__(self, widgets, attrs) def format_output(self, rendered_widgets): - return mark_safe(u'

    %s %s
    %s %s

    ' % \ + return mark_safe('

    %s %s
    %s %s

    ' % \ (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1])) class AdminRadioFieldRenderer(RadioFieldRenderer): def render(self): """Outputs a
      for this set of radio fields.""" - return mark_safe(u'\n%s\n
    ' % ( + return mark_safe('\n%s\n
' % ( flatatt(self.attrs), - u'\n'.join([u'
  • %s
  • ' % force_unicode(w) for w in self])) + '\n'.join(['
  • %s
  • ' % force_unicode(w) for w in self])) ) class AdminRadioSelect(forms.RadioSelect): renderer = AdminRadioFieldRenderer class AdminFileWidget(forms.ClearableFileInput): - template_with_initial = (u'

    %s

    ' + template_with_initial = ('

    %s

    ' % forms.ClearableFileInput.template_with_initial) - template_with_clear = (u'%s' + template_with_clear = ('%s' % forms.ClearableFileInput.template_with_clear) def url_params_from_lookup_dict(lookups): @@ -113,7 +115,7 @@ def url_params_from_lookup_dict(lookups): items = [] for k, v in lookups.items(): if isinstance(v, (tuple, list)): - v = u','.join([str(x) for x in v]) + v = ','.join([str(x) for x in v]) elif isinstance(v, bool): # See django.db.fields.BooleanField.get_prep_lookup v = ('0', '1')[v] @@ -148,21 +150,21 @@ class ForeignKeyRawIdWidget(forms.TextInput): params = self.url_parameters() if params: - url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()]) + url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()]) else: - url = u'' + url = '' if "class" not in attrs: attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript code looks for this hook. # TODO: "lookup_id_" is hard-coded here. This should instead use # the correct API to determine the ID dynamically. - extra.append(u' ' + extra.append(' ' % (related_url, url, name)) - extra.append(u'%s' + extra.append('%s' % (static('admin/img/selector-search.gif'), _('Lookup'))) output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra if value: output.append(self.label_for_value(value)) - return mark_safe(u''.join(output)) + return mark_safe(''.join(output)) def base_url_parameters(self): return url_params_from_lookup_dict(self.rel.limit_choices_to) @@ -261,11 +263,11 @@ class RelatedFieldWidgetWrapper(forms.Widget): related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name) # TODO: "add_id_" is hard-coded here. This should instead use the # correct API to determine the ID dynamically. - output.append(u' ' + output.append(' ' % (related_url, name)) - output.append(u'%s' + output.append('%s' % (static('admin/img/icon_addlink.gif'), _('Add Another'))) - return mark_safe(u''.join(output)) + return mark_safe(''.join(output)) def build_attrs(self, extra_attrs=None, **kwargs): "Helper function for building an attribute dictionary." diff --git a/django/contrib/admindocs/tests/__init__.py b/django/contrib/admindocs/tests/__init__.py index 49a925a453..306475beb1 100644 --- a/django/contrib/admindocs/tests/__init__.py +++ b/django/contrib/admindocs/tests/__init__.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.admindocs import views from django.db.models import fields as builtin_fields @@ -20,17 +20,17 @@ class TestFieldType(unittest.TestCase): def test_builtin_fields(self): self.assertEqual( views.get_readable_field_data_type(builtin_fields.BooleanField()), - _(u'Boolean (Either True or False)') + _('Boolean (Either True or False)') ) def test_custom_fields(self): self.assertEqual( views.get_readable_field_data_type(fields.CustomField()), - _(u'A custom field type') + _('A custom field type') ) self.assertEqual( views.get_readable_field_data_type(fields.DescriptionLackingField()), - _(u'Field of type: %(field_type)s') % { + _('Field of type: %(field_type)s') % { 'field_type': 'DescriptionLackingField' } ) diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index 04fbef4788..9088e2fbf6 100644 --- a/django/contrib/auth/backends.py +++ b/django/contrib/auth/backends.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib.auth.models import User, Permission @@ -36,7 +38,7 @@ class ModelBackend(object): if user_obj.is_anonymous() or obj is not None: return set() if not hasattr(user_obj, '_perm_cache'): - user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()]) + user_obj._perm_cache = set(["%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()]) user_obj._perm_cache.update(self.get_group_permissions(user_obj)) return user_obj._perm_cache diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 0897de8d84..96ec40ba60 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import hashlib from django.dispatch import receiver diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 66f54f18a8..100acb6c5b 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -1,6 +1,8 @@ """ Creates permissions for all installed apps that need permissions. """ +from __future__ import unicode_literals + import getpass import locale import unicodedata @@ -10,14 +12,14 @@ from django.contrib.auth.models import User def _get_permission_codename(action, opts): - return u'%s_%s' % (action, opts.object_name.lower()) + return '%s_%s' % (action, opts.object_name.lower()) def _get_all_permissions(opts): "Returns (codename, name) for all permissions in the given opts." perms = [] for action in ('add', 'change', 'delete'): - perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw))) + perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name_raw))) return perms + list(opts.permissions) @@ -88,7 +90,7 @@ def get_system_username(): # 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 u'' + return '' def get_default_username(check_db=True): diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 090b6d40c8..244721065d 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import urllib from django.core.exceptions import ImproperlyConfigured @@ -76,7 +78,7 @@ class Permission(models.Model): 'codename') def __unicode__(self): - return u"%s | %s | %s" % ( + return "%s | %s | %s" % ( unicode(self.content_type.app_label), unicode(self.content_type), unicode(self.name)) @@ -285,7 +287,7 @@ class User(models.Model): """ Returns the first_name plus the last_name, with a space in between. """ - full_name = u'%s %s' % (self.first_name, self.last_name) + full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip() def set_password(self, raw_password): diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py index 7b38acfa50..9a4d8f9b3a 100644 --- a/django/contrib/auth/tests/auth_backends.py +++ b/django/contrib/auth/tests/auth_backends.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.auth.models import User, Group, Permission, AnonymousUser from django.contrib.contenttypes.models import ContentType @@ -51,7 +53,7 @@ class BackendTest(TestCase): # reloading user to purge the _perm_cache user = User.objects.get(username='test') - self.assertEqual(user.get_all_permissions() == set([u'auth.test']), True) + self.assertEqual(user.get_all_permissions() == set(['auth.test']), True) self.assertEqual(user.get_group_permissions(), set([])) self.assertEqual(user.has_module_perms('Group'), False) self.assertEqual(user.has_module_perms('auth'), True) @@ -62,7 +64,7 @@ class BackendTest(TestCase): user.user_permissions.add(perm) user.save() user = User.objects.get(username='test') - self.assertEqual(user.get_all_permissions(), set([u'auth.test2', u'auth.test', u'auth.test3'])) + self.assertEqual(user.get_all_permissions(), set(['auth.test2', 'auth.test', 'auth.test3'])) self.assertEqual(user.has_perm('test'), False) self.assertEqual(user.has_perm('auth.test'), True) self.assertEqual(user.has_perms(['auth.test2', 'auth.test3']), True) @@ -72,9 +74,9 @@ class BackendTest(TestCase): group.save() user.groups.add(group) user = User.objects.get(username='test') - exp = set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group']) + exp = set(['auth.test2', 'auth.test', 'auth.test3', 'auth.test_group']) self.assertEqual(user.get_all_permissions(), exp) - self.assertEqual(user.get_group_permissions(), set([u'auth.test_group'])) + self.assertEqual(user.get_group_permissions(), set(['auth.test_group'])) self.assertEqual(user.has_perms(['auth.test3', 'auth.test_group']), True) user = AnonymousUser() diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index fd2b526f17..2ab895804f 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from django.contrib.auth.models import User from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm, @@ -299,7 +301,7 @@ class PasswordResetFormTest(TestCase): # potential case where contrib.sites is not installed. Refs #16412. form.save(domain_override='example.com') self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, u'Custom password reset on example.com') + self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com') def test_bug_5605(self): # bug #5605, preserve the case of the user name (before the @ in the @@ -328,4 +330,4 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["email"].errors, - [_(u"The user account associated with this e-mail address cannot reset the password.")]) + [_("The user account associated with this e-mail address cannot reset the password.")]) diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py index 15fc1d1da7..673263b566 100644 --- a/django/contrib/auth/tests/hashers.py +++ b/django/contrib/auth/tests/hashers.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf.global_settings import PASSWORD_HASHERS as default_hashers from django.contrib.auth.hashers import (is_password_usable, check_password, make_password, PBKDF2PasswordHasher, load_hashers, @@ -26,7 +28,7 @@ class TestUtilsHashPass(unittest.TestCase): encoded = make_password('letmein') self.assertTrue(encoded.startswith('pbkdf2_sha256$')) self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) def test_pkbdf2(self): @@ -34,7 +36,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertEqual(encoded, 'pbkdf2_sha256$10000$seasalt$FQCNpiZpTb0zub+HBsH6TOwyRxJ19FwvjbweatNmK/Y=') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") @@ -43,7 +45,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertEqual(encoded, 'sha1$seasalt$fec3530984afba6bade3347b7140d1a7da7da8c7') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "sha1") @@ -52,7 +54,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertEqual(encoded, 'md5$seasalt$f5531bef9f3687d0ccf0f617f0e25573') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "md5") @@ -60,7 +62,7 @@ class TestUtilsHashPass(unittest.TestCase): encoded = make_password('letmein', 'seasalt', 'unsalted_md5') self.assertEqual(encoded, '0d107d09f5bbe40cade3de5c71e9e9b7') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") @@ -69,7 +71,7 @@ class TestUtilsHashPass(unittest.TestCase): encoded = make_password('letmein', 'ab', 'crypt') self.assertEqual(encoded, 'crypt$$abN/qM.L/H8EQ') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "crypt") @@ -78,7 +80,7 @@ class TestUtilsHashPass(unittest.TestCase): encoded = make_password('letmein', hasher='bcrypt') self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith('bcrypt$')) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt") @@ -88,7 +90,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertFalse(check_password(None, encoded)) self.assertFalse(check_password(UNUSABLE_PASSWORD, encoded)) self.assertFalse(check_password('', encoded)) - self.assertFalse(check_password(u'letmein', encoded)) + self.assertFalse(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) self.assertRaises(ValueError, identify_hasher, encoded) diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py index 81ab0aa052..8d1f7c7965 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from StringIO import StringIO from django.contrib.auth import models, management @@ -15,19 +17,19 @@ class GetDefaultUsernameTestCase(TestCase): management.get_system_username = self._getpass_getuser def test_simple(self): - management.get_system_username = lambda: u'joe' + management.get_system_username = lambda: 'joe' self.assertEqual(management.get_default_username(), 'joe') def test_existing(self): models.User.objects.create(username='joe') - management.get_system_username = lambda: u'joe' + management.get_system_username = lambda: 'joe' self.assertEqual(management.get_default_username(), '') self.assertEqual( management.get_default_username(check_db=False), 'joe') def test_i18n(self): # 'Julia' with accented 'u': - management.get_system_username = lambda: u'J\xfalia' + management.get_system_username = lambda: 'J\xfalia' self.assertEqual(management.get_default_username(), 'julia') diff --git a/django/contrib/comments/admin.py b/django/contrib/comments/admin.py index 4cb90663a0..0024a1d1b5 100644 --- a/django/contrib/comments/admin.py +++ b/django/contrib/comments/admin.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib import admin from django.contrib.comments.models import Comment from django.utils.translation import ugettext_lazy as _, ungettext @@ -62,8 +64,8 @@ class CommentsAdmin(admin.ModelAdmin): action(request, comment) n_comments += 1 - msg = ungettext(u'1 comment was successfully %(action)s.', - u'%(count)s comments were successfully %(action)s.', + msg = ungettext('1 comment was successfully %(action)s.', + '%(count)s comments were successfully %(action)s.', n_comments) self.message_user(request, msg % {'count': n_comments, 'action': done_message(n_comments)}) diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index a6732d7a2e..d5062cabf3 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -1,6 +1,7 @@ """ Classes allowing "generic" relations through ContentType and object-id fields. """ +from __future__ import unicode_literals from collections import defaultdict from functools import partial @@ -131,7 +132,7 @@ class GenericForeignKey(object): def __set__(self, instance, value): if instance is None: - raise AttributeError(u"%s must be accessed via instance" % self.related.opts.object_name) + raise AttributeError("%s must be accessed via instance" % self.related.opts.object_name) ct = None fk = None diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py index 0057dd535d..2eaa4c182e 100644 --- a/django/contrib/contenttypes/tests.py +++ b/django/contrib/contenttypes/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import urllib from django.db import models @@ -181,4 +183,4 @@ class ContentTypesTests(TestCase): app_label = 'contenttypes', model = 'OldModel', ) - self.assertEqual(unicode(ct), u'Old model') + self.assertEqual(unicode(ct), 'Old model') diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index 8d8e1c7a3e..5c22cb6672 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import http from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site, get_current_site @@ -12,11 +14,11 @@ def shortcut(request, content_type_id, object_id): try: content_type = ContentType.objects.get(pk=content_type_id) if not content_type.model_class(): - raise http.Http404(_(u"Content type %(ct_id)s object has no associated model") % + raise http.Http404(_("Content type %(ct_id)s object has no associated model") % {'ct_id': content_type_id}) obj = content_type.get_object_for_this_type(pk=object_id) except (ObjectDoesNotExist, ValueError): - raise http.Http404(_(u"Content type %(ct_id)s object %(obj_id)s doesn't exist") % + raise http.Http404(_("Content type %(ct_id)s object %(obj_id)s doesn't exist") % {'ct_id': content_type_id, 'obj_id': object_id}) try: diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 9a4ba17e19..6a78b3688b 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -2,6 +2,7 @@ These classes are light wrappers around Django's database API that provide convenience functionality and permalink functions for the databrowse app. """ +from __future__ import unicode_literals from django.db import models from django.utils import formats @@ -61,7 +62,7 @@ class EasyField(object): self.model, self.field = easy_model, field def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.field.name)) + return smart_str('' % (self.model.model._meta.object_name, self.field.name)) def choices(self): for value, label in self.field.choices: @@ -79,7 +80,7 @@ class EasyChoice(object): self.value, self.label = value, label def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.field.name)) + return smart_str('' % (self.model.model._meta.object_name, self.field.name)) def url(self): return mark_safe('%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))) @@ -89,12 +90,12 @@ class EasyInstance(object): self.model, self.instance = easy_model, instance def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.instance._get_pk_val())) + return smart_str('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) def __unicode__(self): val = smart_unicode(self.instance) if len(val) > DISPLAY_SIZE: - return val[:DISPLAY_SIZE] + u'...' + return val[:DISPLAY_SIZE] + '...' return val def __str__(self): @@ -136,7 +137,7 @@ class EasyInstanceField(object): self.raw_value = getattr(instance.instance, field.name) def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.field.name)) + return smart_str('' % (self.model.model._meta.object_name, self.field.name)) def values(self): """ diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 3416f88ca7..587c752a94 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import http from django.db import models from django.contrib.databrowse.datastructures import EasyModel @@ -61,14 +63,14 @@ class CalendarPlugin(DatabrowsePlugin): def model_index_html(self, request, model, site): fields = self.field_dict(model) if not fields: - return u'' - return mark_safe(u'

    View calendar by: %s

    ' % \ - u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) + return '' + return mark_safe('

    View calendar by: %s

    ' % \ + ', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) def urls(self, plugin_name, easy_instance_field): if isinstance(easy_instance_field.field, models.DateField): d = easy_instance_field.raw_value - return [mark_safe(u'%s%s/%s/%s/%s/%s/' % ( + return [mark_safe('%s%s/%s/%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, str(d.year), diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index b3210681e9..5a13252ab3 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import http from django.db import models from django.contrib.databrowse.datastructures import EasyModel @@ -29,14 +31,14 @@ class FieldChoicePlugin(DatabrowsePlugin): def model_index_html(self, request, model, site): fields = self.field_dict(model) if not fields: - return u'' - return mark_safe(u'

    View by: %s

    ' % \ - u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) + return '' + return mark_safe('

    View by: %s

    ' % \ + ', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): field_value = smart_str(easy_instance_field.raw_value) - return [mark_safe(u'%s%s/%s/%s/' % ( + return [mark_safe('%s%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, urllib.quote(field_value, safe='')))] diff --git a/django/contrib/databrowse/sites.py b/django/contrib/databrowse/sites.py index d90bb562b2..b5cb2639d6 100644 --- a/django/contrib/databrowse/sites.py +++ b/django/contrib/databrowse/sites.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import http from django.db import models from django.contrib.databrowse.datastructures import EasyModel @@ -61,7 +63,7 @@ class ModelDatabrowse(object): def main_view(self, request): easy_model = EasyModel(self.site, self.model) - html_snippets = mark_safe(u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])) + html_snippets = mark_safe('\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])) return render_to_response('databrowse/model_detail.html', { 'model': easy_model, 'root_url': self.site.root_url, diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py index 85873ac7b1..42ec155f34 100644 --- a/django/contrib/flatpages/models.py +++ b/django/contrib/flatpages/models.py @@ -1,3 +1,5 @@ +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 _ @@ -20,7 +22,7 @@ class FlatPage(models.Model): ordering = ('url',) def __unicode__(self): - return u"%s -- %s" % (self.url, self.title) + return "%s -- %s" % (self.url, self.title) def get_absolute_url(self): return self.url diff --git a/django/contrib/flatpages/tests/forms.py b/django/contrib/flatpages/tests/forms.py index 0e54176aa2..3bdfa15dfe 100644 --- a/django/contrib/flatpages/tests/forms.py +++ b/django/contrib/flatpages/tests/forms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.flatpages.forms import FlatpageForm from django.contrib.flatpages.models import FlatPage @@ -60,7 +62,7 @@ class FlatpageAdminFormTests(TestCase): self.assertEqual( f.errors, - {'__all__': [u'Flatpage with url /myflatpage1/ already exists for site example.com']}) + {'__all__': ['Flatpage with url /myflatpage1/ already exists for site example.com']}) def test_flatpage_admin_form_edit(self): """ @@ -92,5 +94,5 @@ class FlatpageAdminFormTests(TestCase): self.assertEqual( f.errors, - {'sites': [translation.ugettext(u'This field is required.')]}) + {'sites': [translation.ugettext('This field is required.')]}) diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py index b5d905071f..3bccb55034 100644 --- a/django/contrib/formtools/tests/__init__.py +++ b/django/contrib/formtools/tests/__init__.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os import re import warnings @@ -41,7 +43,7 @@ class PreviewTests(TestCase): self.preview = preview.FormPreview(TestForm) input_template = '' self.input = input_template % (self.preview.unused_name('stage'), "%d") - self.test_data = {'field1':u'foo', 'field1_':u'asdf'} + self.test_data = {'field1': 'foo', 'field1_': 'asdf'} def test_unused_name(self): """ @@ -117,7 +119,7 @@ class PreviewTests(TestCase): """ self.test_data.update({'stage':2}) hash = self.preview.security_hash(None, TestForm(self.test_data)) - self.test_data.update({'hash':hash, 'bool1':u'False'}) + 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) @@ -163,8 +165,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': u'joe', 'bio': u'Nothing notable.'}) - f2 = HashTestForm({'name': u' joe', 'bio': u'Nothing notable. '}) + f1 = HashTestForm({'name': 'joe', 'bio': 'Nothing notable.'}) + f2 = HashTestForm({'name': ' joe', 'bio': 'Nothing notable. '}) hash1 = utils.form_hmac(f1) hash2 = utils.form_hmac(f2) self.assertEqual(hash1, hash2) @@ -266,10 +268,10 @@ class WizardTests(TestCase): Form should advance if the hash is present and good, as calculated using current method. """ - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + "wizard_step": "1"} response = self.client.post('/wizard1/', data) self.assertEqual(2, response.context['step0']) @@ -291,18 +293,18 @@ class WizardTests(TestCase): reached[0] = True wizard = WizardWithProcessStep([WizardPageOneForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) - data = {"0-field": u"test", - "1-field": u"test2", + data = {"0-field": "test", + "1-field": "test2", "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "hash_1": u"1e6f6315da42e62f33a30640ec7e007ad3fbf1a1", - "wizard_step": u"2"} + "hash_1": "1e6f6315da42e62f33a30640ec7e007ad3fbf1a1", + "wizard_step": "2"} self.assertRaises(http.Http404, wizard, DummyRequest(POST=data)) def test_14498(self): @@ -321,10 +323,10 @@ class WizardTests(TestCase): wizard = WizardWithProcessStep([WizardPageOneForm, WizardPageTwoForm, WizardPageThreeForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) @@ -345,10 +347,10 @@ class WizardTests(TestCase): wizard = Wizard([WizardPageOneForm, WizardPageTwoForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) @@ -371,10 +373,10 @@ class WizardTests(TestCase): wizard = WizardWithProcessStep([WizardPageOneForm, WizardPageTwoForm, WizardPageThreeForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) diff --git a/django/contrib/formtools/tests/wizard/forms.py b/django/contrib/formtools/tests/wizard/forms.py index 8afbd30721..51cfaa661b 100644 --- a/django/contrib/formtools/tests/wizard/forms.py +++ b/django/contrib/formtools/tests/wizard/forms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import forms, http from django.conf import settings from django.test import TestCase @@ -64,22 +66,22 @@ class TestWizard(WizardView): class FormTests(TestCase): def test_form_init(self): testform = TestWizard.get_initkwargs([Step1, Step2]) - self.assertEqual(testform['form_list'], {u'0': Step1, u'1': Step2}) + self.assertEqual(testform['form_list'], {'0': Step1, '1': Step2}) testform = TestWizard.get_initkwargs([('start', Step1), ('step2', Step2)]) self.assertEqual( - testform['form_list'], {u'start': Step1, u'step2': Step2}) + testform['form_list'], {'start': Step1, 'step2': Step2}) testform = TestWizard.get_initkwargs([Step1, Step2, ('finish', Step3)]) self.assertEqual( - testform['form_list'], {u'0': Step1, u'1': Step2, u'finish': Step3}) + testform['form_list'], {'0': Step1, '1': Step2, 'finish': Step3}) def test_first_step(self): request = get_request() testform = TestWizard.as_view([Step1, Step2]) response, instance = testform(request) - self.assertEqual(instance.steps.current, u'0') + self.assertEqual(instance.steps.current, '0') testform = TestWizard.as_view([('start', Step1), ('step2', Step2)]) response, instance = testform(request) diff --git a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py index 37913fa078..a860edd9e9 100644 --- a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py +++ b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.core.urlresolvers import reverse from django.http import QueryDict from django.test import TestCase @@ -51,8 +53,8 @@ class NamedWizardTests(object): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['wizard']['steps'].current, 'form1') self.assertEqual(response.context['wizard']['form'].errors, - {'name': [u'This field is required.'], - 'user': [u'This field is required.']}) + {'name': ['This field is required.'], + 'user': ['This field is required.']}) def test_form_post_success(self): response = self.client.post( @@ -150,10 +152,10 @@ class NamedWizardTests(object): self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read()) del all_data[1]['file1'] self.assertEqual(all_data, [ - {'name': u'Pony', 'thirsty': True, 'user': self.testuser}, - {'address1': u'123 Main St', 'address2': u'Djangoland'}, - {'random_crap': u'blah blah'}, - [{'random_crap': u'blah blah'}, {'random_crap': u'blah blah'}]]) + {'name': 'Pony', 'thirsty': True, 'user': self.testuser}, + {'address1': '123 Main St', 'address2': 'Djangoland'}, + {'random_crap': 'blah blah'}, + [{'random_crap': 'blah blah'}, {'random_crap': 'blah blah'}]]) def test_cleaned_data(self): response = self.client.get( @@ -203,11 +205,11 @@ class NamedWizardTests(object): del all_data['file1'] self.assertEqual( all_data, - {'name': u'Pony', 'thirsty': True, 'user': self.testuser, - 'address1': u'123 Main St', 'address2': u'Djangoland', - 'random_crap': u'blah blah', 'formset-form4': [ - {'random_crap': u'blah blah'}, - {'random_crap': u'blah blah'} + {'name': 'Pony', 'thirsty': True, 'user': self.testuser, + 'address1': '123 Main St', 'address2': 'Djangoland', + 'random_crap': 'blah blah', 'formset-form4': [ + {'random_crap': 'blah blah'}, + {'random_crap': 'blah blah'} ]}) def test_manipulated_data(self): diff --git a/django/contrib/formtools/tests/wizard/wizardtests/tests.py b/django/contrib/formtools/tests/wizard/wizardtests/tests.py index a9a81ba70f..a35664b322 100644 --- a/django/contrib/formtools/tests/wizard/wizardtests/tests.py +++ b/django/contrib/formtools/tests/wizard/wizardtests/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from django import forms @@ -33,8 +35,8 @@ class WizardTests(object): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['wizard']['steps'].current, 'form1') self.assertEqual(response.context['wizard']['form'].errors, - {'name': [u'This field is required.'], - 'user': [u'This field is required.']}) + {'name': ['This field is required.'], + 'user': ['This field is required.']}) def test_form_post_success(self): response = self.client.post(self.wizard_url, self.wizard_step_data[0]) @@ -96,11 +98,11 @@ class WizardTests(object): self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read()) del all_data[1]['file1'] self.assertEqual(all_data, [ - {'name': u'Pony', 'thirsty': True, 'user': self.testuser}, - {'address1': u'123 Main St', 'address2': u'Djangoland'}, - {'random_crap': u'blah blah'}, - [{'random_crap': u'blah blah'}, - {'random_crap': u'blah blah'}]]) + {'name': 'Pony', 'thirsty': True, 'user': self.testuser}, + {'address1': '123 Main St', 'address2': 'Djangoland'}, + {'random_crap': 'blah blah'}, + [{'random_crap': 'blah blah'}, + {'random_crap': 'blah blah'}]]) def test_cleaned_data(self): response = self.client.get(self.wizard_url) @@ -124,11 +126,11 @@ class WizardTests(object): self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read()) del all_data['file1'] self.assertEqual(all_data, { - 'name': u'Pony', 'thirsty': True, 'user': self.testuser, - 'address1': u'123 Main St', 'address2': u'Djangoland', - 'random_crap': u'blah blah', 'formset-form4': [ - {'random_crap': u'blah blah'}, - {'random_crap': u'blah blah'}]}) + 'name': 'Pony', 'thirsty': True, 'user': self.testuser, + 'address1': '123 Main St', 'address2': 'Djangoland', + 'random_crap': 'blah blah', 'formset-form4': [ + {'random_crap': 'blah blah'}, + {'random_crap': 'blah blah'}]}) def test_manipulated_data(self): response = self.client.get(self.wizard_url) diff --git a/django/contrib/gis/feeds.py b/django/contrib/gis/feeds.py index c14fb858a5..d7c52bf019 100644 --- a/django/contrib/gis/feeds.py +++ b/django/contrib/gis/feeds.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib.syndication.views import Feed as BaseFeed from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed @@ -13,7 +15,7 @@ class GeoFeedMixin(object): a single white space. Given a tuple of coordinates, this will return a unicode GeoRSS representation. """ - return u' '.join([u'%f %f' % (coord[1], coord[0]) for coord in coords]) + return ' '.join(['%f %f' % (coord[1], coord[0]) for coord in coords]) def add_georss_point(self, handler, coords, w3c_geo=False): """ @@ -23,10 +25,10 @@ class GeoFeedMixin(object): """ if w3c_geo: lon, lat = coords[:2] - handler.addQuickElement(u'geo:lat', u'%f' % lat) - handler.addQuickElement(u'geo:lon', u'%f' % lon) + handler.addQuickElement('geo:lat', '%f' % lat) + handler.addQuickElement('geo:lon', '%f' % lon) else: - handler.addQuickElement(u'georss:point', self.georss_coords((coords,))) + handler.addQuickElement('georss:point', self.georss_coords((coords,))) def add_georss_element(self, handler, item, w3c_geo=False): """ @@ -57,7 +59,7 @@ class GeoFeedMixin(object): # If a GeoRSS box was given via tuple. if not box_coords is None: if w3c_geo: raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.') - handler.addQuickElement(u'georss:box', self.georss_coords(box_coords)) + handler.addQuickElement('georss:box', self.georss_coords(box_coords)) else: # Getting the lower-case geometry type. gtype = str(geom.geom_type).lower() @@ -68,10 +70,10 @@ class GeoFeedMixin(object): # For formatting consistent w/the GeoRSS simple standard: # http://georss.org/1.0#simple if gtype in ('linestring', 'linearring'): - handler.addQuickElement(u'georss:line', self.georss_coords(geom.coords)) + handler.addQuickElement('georss:line', self.georss_coords(geom.coords)) elif gtype in ('polygon',): # Only support the exterior ring. - handler.addQuickElement(u'georss:polygon', self.georss_coords(geom[0].coords)) + handler.addQuickElement('georss:polygon', self.georss_coords(geom[0].coords)) else: raise ValueError('Geometry type "%s" not supported.' % geom.geom_type) @@ -79,7 +81,7 @@ class GeoFeedMixin(object): class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): def rss_attributes(self): attrs = super(GeoRSSFeed, self).rss_attributes() - attrs[u'xmlns:georss'] = u'http://www.georss.org/georss' + attrs['xmlns:georss'] = 'http://www.georss.org/georss' return attrs def add_item_elements(self, handler, item): @@ -93,7 +95,7 @@ class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): class GeoAtom1Feed(Atom1Feed, GeoFeedMixin): def root_attributes(self): attrs = super(GeoAtom1Feed, self).root_attributes() - attrs[u'xmlns:georss'] = u'http://www.georss.org/georss' + attrs['xmlns:georss'] = 'http://www.georss.org/georss' return attrs def add_item_elements(self, handler, item): @@ -107,7 +109,7 @@ class GeoAtom1Feed(Atom1Feed, GeoFeedMixin): class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin): def rss_attributes(self): attrs = super(W3CGeoFeed, self).rss_attributes() - attrs[u'xmlns:geo'] = u'http://www.w3.org/2003/01/geo/wgs84_pos#' + attrs['xmlns:geo'] = 'http://www.w3.org/2003/01/geo/wgs84_pos#' return attrs def add_item_elements(self, handler, item): diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index f806dcb3c6..432f0e1872 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import forms from django.utils.translation import ugettext_lazy as _ @@ -14,10 +16,10 @@ class GeometryField(forms.Field): widget = forms.Textarea default_error_messages = { - 'no_geom' : _(u'No geometry value provided.'), - 'invalid_geom' : _(u'Invalid geometry value.'), - 'invalid_geom_type' : _(u'Invalid geometry type.'), - 'transform_error' : _(u'An error occurred when transforming the geometry ' + 'no_geom' : _('No geometry value provided.'), + 'invalid_geom' : _('Invalid geometry value.'), + 'invalid_geom_type' : _('Invalid geometry type.'), + 'transform_error' : _('An error occurred when transforming the geometry ' 'to the SRID of the geometry form field.'), } diff --git a/django/contrib/gis/gdal/tests/test_envelope.py b/django/contrib/gis/gdal/tests/test_envelope.py index 7574284bba..07e12c0ca7 100644 --- a/django/contrib/gis/gdal/tests/test_envelope.py +++ b/django/contrib/gis/gdal/tests/test_envelope.py @@ -23,7 +23,7 @@ class EnvelopeTest(unittest.TestCase): self.assertRaises(OGRException, Envelope, (0, 0, 5, 5, 3)) self.assertRaises(OGRException, Envelope, ()) self.assertRaises(ValueError, Envelope, 0, 'a', 5, 5) - self.assertRaises(TypeError, Envelope, u'foo') + self.assertRaises(TypeError, Envelope, 'foo') self.assertRaises(OGRException, Envelope, (1, 1, 0, 0)) try: Envelope(0, 0, 0, 0) diff --git a/django/contrib/gis/geoip/tests.py b/django/contrib/gis/geoip/tests.py index a629d86bbf..1d9132ba6f 100644 --- a/django/contrib/gis/geoip/tests.py +++ b/django/contrib/gis/geoip/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from django.conf import settings from django.contrib.gis.geos import GEOSGeometry @@ -99,13 +101,13 @@ class GeoIPTest(unittest.TestCase): "Testing that GeoIP strings are properly encoded, see #16553." g = GeoIP() d = g.city('62.224.93.23') - self.assertEqual(u'Sch\xf6mberg', d['city']) + self.assertEqual('Schümberg', d['city']) def test06_unicode_query(self): "Testing that GeoIP accepts unicode string queries, see #17059." g = GeoIP() - d = g.country(u'whitehouse.gov') - self.assertEqual(u'US', d['country_code']) + d = g.country('whitehouse.gov') + self.assertEqual('US', d['country_code']) def suite(): diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index 3a9acad7b0..eb42d0cae8 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.http import HttpResponse, Http404 from django.template import loader from django.contrib.sites.models import get_current_site @@ -41,7 +43,7 @@ def sitemap(request, sitemaps, section=None): maps, urls = [], [] if section is not None: if section not in sitemaps: - raise Http404(_(u"No sitemap available for section: %r") % section) + raise Http404(_("No sitemap available for section: %r") % section) maps.append(sitemaps[section]) else: maps = sitemaps.values() @@ -55,9 +57,9 @@ def sitemap(request, sitemaps, section=None): else: urls.extend(site.get_urls(page=page, site=current_site)) except EmptyPage: - raise Http404(_(u"Page %s empty") % page) + raise Http404(_("Page %s empty") % page) except PageNotAnInteger: - raise Http404(_(u"No page '%s'") % page) + raise Http404(_("No page '%s'") % page) xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})) return HttpResponse(xml, content_type='application/xml') diff --git a/django/contrib/gis/tests/geoapp/test_feeds.py b/django/contrib/gis/tests/geoapp/test_feeds.py index 936c1ded46..85e777ae78 100644 --- a/django/contrib/gis/tests/geoapp/test_feeds.py +++ b/django/contrib/gis/tests/geoapp/test_feeds.py @@ -44,7 +44,7 @@ class GeoFeedTest(TestCase): # Incrementing through the feeds. for feed in [feed1, feed2]: # Ensuring the georss namespace was added to the element. - self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss') + self.assertEqual(feed.getAttribute('xmlns:georss'), 'http://www.georss.org/georss') chan = feed.getElementsByTagName('channel')[0] items = chan.getElementsByTagName('item') self.assertEqual(len(items), City.objects.count()) @@ -64,7 +64,7 @@ class GeoFeedTest(TestCase): for feed in [feed1, feed2]: # Ensuring the georsss namespace was added to the element. - self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss') + self.assertEqual(feed.getAttribute('xmlns:georss'), 'http://www.georss.org/georss') entries = feed.getElementsByTagName('entry') self.assertEqual(len(entries), City.objects.count()) @@ -77,7 +77,7 @@ class GeoFeedTest(TestCase): doc = minidom.parseString(self.client.get('/feeds/w3cgeo1/').content) feed = doc.firstChild # Ensuring the geo namespace was added to the element. - self.assertEqual(feed.getAttribute(u'xmlns:geo'), u'http://www.w3.org/2003/01/geo/wgs84_pos#') + self.assertEqual(feed.getAttribute('xmlns:geo'), 'http://www.w3.org/2003/01/geo/wgs84_pos#') chan = feed.getElementsByTagName('channel')[0] items = chan.getElementsByTagName('item') self.assertEqual(len(items), City.objects.count()) diff --git a/django/contrib/gis/tests/geoapp/test_sitemaps.py b/django/contrib/gis/tests/geoapp/test_sitemaps.py index a5c8f41ba4..5f063dfba3 100644 --- a/django/contrib/gis/tests/geoapp/test_sitemaps.py +++ b/django/contrib/gis/tests/geoapp/test_sitemaps.py @@ -34,7 +34,7 @@ class GeoSitemapTest(TestCase): # Getting the geo index. doc = minidom.parseString(self.client.get('/sitemap.xml').content) index = doc.firstChild - self.assertEqual(index.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9') + self.assertEqual(index.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9') self.assertEqual(3, len(index.getElementsByTagName('sitemap'))) def test_geositemap_kml(self): @@ -44,8 +44,8 @@ class GeoSitemapTest(TestCase): # Ensuring the right sitemaps namespaces are present. urlset = doc.firstChild - self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9') - self.assertEqual(urlset.getAttribute(u'xmlns:geo'), u'http://www.google.com/geo/schemas/sitemap/1.0') + self.assertEqual(urlset.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9') + self.assertEqual(urlset.getAttribute('xmlns:geo'), 'http://www.google.com/geo/schemas/sitemap/1.0') urls = urlset.getElementsByTagName('url') self.assertEqual(2, len(urls)) # Should only be 2 sitemaps. @@ -84,8 +84,8 @@ class GeoSitemapTest(TestCase): # Ensuring the right sitemaps namespaces are present. urlset = doc.firstChild - self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9') - self.assertEqual(urlset.getAttribute(u'xmlns:geo'), u'http://www.google.com/geo/schemas/sitemap/1.0') + self.assertEqual(urlset.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9') + self.assertEqual(urlset.getAttribute('xmlns:geo'), 'http://www.google.com/geo/schemas/sitemap/1.0') # Making sure the correct number of feed URLs were included. urls = urlset.getElementsByTagName('url') diff --git a/django/contrib/gis/views.py b/django/contrib/gis/views.py index fc51a0bf3a..3fa8f044de 100644 --- a/django/contrib/gis/views.py +++ b/django/contrib/gis/views.py @@ -1,10 +1,12 @@ +from __future__ import unicode_literals + from django.http import Http404 from django.utils.translation import ugettext as _ def feed(request, url, feed_dict=None): """Provided for backwards compatibility.""" if not feed_dict: - raise Http404(_(u"No feeds are registered.")) + raise Http404(_("No feeds are registered.")) try: slug, param = url.split('/', 1) @@ -14,7 +16,7 @@ def feed(request, url, feed_dict=None): try: f = feed_dict[slug] except KeyError: - raise Http404(_(u"Slug %r isn't registered.") % slug) + raise Http404(_("Slug %r isn't registered.") % slug) instance = f() instance.feed_url = getattr(f, 'feed_url', None) or request.path diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index b075ff05c7..7a2e5147d8 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import re from datetime import date, datetime, timedelta @@ -23,8 +24,8 @@ def ordinal(value): return value suffixes = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th')) if value % 100 in (11, 12, 13): # special case - return u"%d%s" % (value, suffixes[0]) - return u"%d%s" % (value, suffixes[value % 10]) + return "%d%s" % (value, suffixes[0]) + return "%d%s" % (value, suffixes[value % 10]) @register.filter(is_safe=True) def intcomma(value, use_l10n=True): @@ -161,11 +162,11 @@ def naturalday(value, arg=None): today = datetime.now(tzinfo).date() delta = value - today if delta.days == 0: - return _(u'today') + return _('today') elif delta.days == 1: - return _(u'tomorrow') + return _('tomorrow') elif delta.days == -1: - return _(u'yesterday') + return _('yesterday') return defaultfilters.date(value, arg) @register.filter @@ -185,20 +186,20 @@ def naturaltime(value): 'naturaltime', '%(delta)s ago' ) % {'delta': defaultfilters.timesince(value)} elif delta.seconds == 0: - return _(u'now') + return _('now') elif delta.seconds < 60: return ungettext( - u'a second ago', u'%(count)s seconds ago', delta.seconds + 'a second ago', '%(count)s seconds ago', delta.seconds ) % {'count': delta.seconds} elif delta.seconds // 60 < 60: count = delta.seconds // 60 return ungettext( - u'a minute ago', u'%(count)s minutes ago', count + 'a minute ago', '%(count)s minutes ago', count ) % {'count': count} else: count = delta.seconds // 60 // 60 return ungettext( - u'an hour ago', u'%(count)s hours ago', count + 'an hour ago', '%(count)s hours ago', count ) % {'count': count} else: delta = value - now @@ -207,18 +208,18 @@ def naturaltime(value): 'naturaltime', '%(delta)s from now' ) % {'delta': defaultfilters.timeuntil(value)} elif delta.seconds == 0: - return _(u'now') + return _('now') elif delta.seconds < 60: return ungettext( - u'a second from now', u'%(count)s seconds from now', delta.seconds + 'a second from now', '%(count)s seconds from now', delta.seconds ) % {'count': delta.seconds} elif delta.seconds // 60 < 60: count = delta.seconds // 60 return ungettext( - u'a minute from now', u'%(count)s minutes from now', count + 'a minute from now', '%(count)s minutes from now', count ) % {'count': count} else: count = delta.seconds // 60 // 60 return ungettext( - u'an hour from now', u'%(count)s hours from now', count + 'an hour from now', '%(count)s hours from now', count ) % {'count': count} diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py index bf6684766b..ecf4b83d6d 100644 --- a/django/contrib/humanize/tests.py +++ b/django/contrib/humanize/tests.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import datetime import new @@ -89,8 +90,8 @@ class HumanizeTests(TestCase): def test_apnumber(self): test_list = [str(x) for x in range(1, 11)] test_list.append(None) - result_list = (u'one', u'two', u'three', u'four', u'five', u'six', - u'seven', u'eight', u'nine', u'10', None) + result_list = ('one', 'two', 'three', 'four', 'five', 'six', + 'seven', 'eight', 'nine', '10', None) self.humanize_tester(test_list, result_list, 'apnumber') @@ -99,12 +100,12 @@ class HumanizeTests(TestCase): yesterday = today - datetime.timedelta(days=1) tomorrow = today + datetime.timedelta(days=1) someday = today - datetime.timedelta(days=10) - notdate = u"I'm not a date value" + notdate = "I'm not a date value" test_list = (today, yesterday, tomorrow, someday, notdate, None) someday_result = defaultfilters.date(someday) - result_list = (_(u'today'), _(u'yesterday'), _(u'tomorrow'), - someday_result, u"I'm not a date value", None) + result_list = (_('today'), _('yesterday'), _('tomorrow'), + someday_result, "I'm not a date value", None) self.humanize_tester(test_list, result_list, 'naturalday') def test_naturalday_tz(self): diff --git a/django/contrib/localflavor/ar/ar_provinces.py b/django/contrib/localflavor/ar/ar_provinces.py index a0efd4ba33..600ef1eb16 100644 --- a/django/contrib/localflavor/ar/ar_provinces.py +++ b/django/contrib/localflavor/ar/ar_provinces.py @@ -7,30 +7,31 @@ http://www.argentina.gov.ar/argentina/portal/paginas.dhtml?pagina=425 This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals PROVINCE_CHOICES = ( - ('B', u'Buenos Aires'), - ('K', u'Catamarca'), - ('H', u'Chaco'), - ('U', u'Chubut'), - ('C', u'Ciudad Autónoma de Buenos Aires'), - ('X', u'Córdoba'), - ('W', u'Corrientes'), - ('E', u'Entre Ríos'), - ('P', u'Formosa'), - ('Y', u'Jujuy'), - ('L', u'La Pampa'), - ('F', u'La Rioja'), - ('M', u'Mendoza'), - ('N', u'Misiones'), - ('Q', u'Neuquén'), - ('R', u'Río Negro'), - ('A', u'Salta'), - ('J', u'San Juan'), - ('D', u'San Luis'), - ('Z', u'Santa Cruz'), - ('S', u'Santa Fe'), - ('G', u'Santiago del Estero'), - ('V', u'Tierra del Fuego, Antártida e Islas del Atlántico Sur'), - ('T', u'Tucumán'), + ('B', 'Buenos Aires'), + ('K', 'Catamarca'), + ('H', 'Chaco'), + ('U', 'Chubut'), + ('C', 'Ciudad Autónoma de Buenos Aires'), + ('X', 'Córdoba'), + ('W', 'Corrientes'), + ('E', 'Entre Ríos'), + ('P', 'Formosa'), + ('Y', 'Jujuy'), + ('L', 'La Pampa'), + ('F', 'La Rioja'), + ('M', 'Mendoza'), + ('N', 'Misiones'), + ('Q', 'Neuquén'), + ('R', 'Río Negro'), + ('A', 'Salta'), + ('J', 'San Juan'), + ('D', 'San Luis'), + ('Z', 'Santa Cruz'), + ('S', 'Santa Fe'), + ('G', 'Santiago del Estero'), + ('V', 'Tierra del Fuego, Antártida e Islas del Atlántico Sur'), + ('T', 'Tucumán'), ) diff --git a/django/contrib/localflavor/ar/forms.py b/django/contrib/localflavor/ar/forms.py index 1805839ce4..8e252beec9 100644 --- a/django/contrib/localflavor/ar/forms.py +++ b/django/contrib/localflavor/ar/forms.py @@ -3,7 +3,7 @@ AR-specific Form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.ar.ar_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES @@ -37,11 +37,11 @@ class ARPostalCodeField(RegexField): def clean(self, value): value = super(ARPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if len(value) not in (4, 8): raise ValidationError(self.error_messages['invalid']) if len(value) == 8: - return u'%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper()) + return '%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper()) return value class ARDNIField(CharField): @@ -63,7 +63,7 @@ class ARDNIField(CharField): """ value = super(ARDNIField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not value.isdigit(): value = value.replace('.', '') if not value.isdigit(): @@ -94,7 +94,7 @@ class ARCUITField(RegexField): """ value = super(ARCUITField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value, cd = self._canon(value) if self._calc_cd(value) != cd: raise ValidationError(self.error_messages['checksum']) @@ -113,5 +113,5 @@ class ARCUITField(RegexField): if check_digit == None: check_digit = cuit[-1] cuit = cuit[:-1] - return u'%s-%s-%s' % (cuit[:2], cuit[2:], check_digit) + return '%s-%s-%s' % (cuit[:2], cuit[2:], check_digit) diff --git a/django/contrib/localflavor/at/forms.py b/django/contrib/localflavor/at/forms.py index 262641bf1f..c531bec2e9 100644 --- a/django/contrib/localflavor/at/forms.py +++ b/django/contrib/localflavor/at/forms.py @@ -1,7 +1,7 @@ """ AT-specific Form helpers """ - +from __future__ import unicode_literals import re from django.core.validators import EMPTY_VALUES @@ -47,13 +47,13 @@ class ATSocialSecurityNumberField(Field): """ default_error_messages = { - 'invalid': _(u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'), + 'invalid': _('Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'), } def clean(self, value): value = super(ATSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u"" + return "" if not re_ssn.search(value): raise ValidationError(self.error_messages['invalid']) sqnr, date = value.split(" ") @@ -66,4 +66,4 @@ class ATSocialSecurityNumberField(Field): res = res % 11 if res != int(check): raise ValidationError(self.error_messages['invalid']) - return u'%s%s %s'%(sqnr, check, date,) + return '%s%s %s'%(sqnr, check, date,) diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py index 19df98dc33..34170fabc8 100644 --- a/django/contrib/localflavor/au/forms.py +++ b/django/contrib/localflavor/au/forms.py @@ -2,7 +2,7 @@ Australian-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -34,7 +34,7 @@ class AUPostCodeField(RegexField): class AUPhoneNumberField(Field): """Australian phone number field.""" default_error_messages = { - 'invalid': u'Phone numbers must contain 10 digits.', + 'invalid': 'Phone numbers must contain 10 digits.', } def clean(self, value): @@ -43,11 +43,11 @@ class AUPhoneNumberField(Field): """ super(AUPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\(|\)|\s+|-)', '', smart_unicode(value)) phone_match = PHONE_DIGITS_RE.search(value) if phone_match: - return u'%s' % phone_match.group(1) + return '%s' % phone_match.group(1) raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/br/br_states.py b/django/contrib/localflavor/br/br_states.py index 98e54bca2c..ab37b1d223 100644 --- a/django/contrib/localflavor/br/br_states.py +++ b/django/contrib/localflavor/br/br_states.py @@ -5,33 +5,34 @@ An alphabetical list of Brazilian states for use as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals STATE_CHOICES = ( ('AC', 'Acre'), ('AL', 'Alagoas'), - ('AP', u'Amapá'), + ('AP', 'Amapá'), ('AM', 'Amazonas'), ('BA', 'Bahia'), - ('CE', u'Ceará'), + ('CE', 'Ceará'), ('DF', 'Distrito Federal'), - ('ES', u'Espírito Santo'), - ('GO', u'Goiás'), - ('MA', u'Maranhão'), + ('ES', 'Espírito Santo'), + ('GO', 'Goiás'), + ('MA', 'Maranhão'), ('MT', 'Mato Grosso'), ('MS', 'Mato Grosso do Sul'), ('MG', 'Minas Gerais'), - ('PA', u'Pará'), - ('PB', u'Paraíba'), - ('PR', u'Paraná'), + ('PA', 'Pará'), + ('PB', 'Paraíba'), + ('PR', 'Paraná'), ('PE', 'Pernambuco'), - ('PI', u'Piauí'), + ('PI', 'Piauí'), ('RJ', 'Rio de Janeiro'), ('RN', 'Rio Grande do Norte'), ('RS', 'Rio Grande do Sul'), - ('RO', u'Rondônia'), + ('RO', 'Rondônia'), ('RR', 'Roraima'), ('SC', 'Santa Catarina'), - ('SP', u'São Paulo'), + ('SP', 'São Paulo'), ('SE', 'Sergipe'), ('TO', 'Tocantins'), ) diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py index 88c7f2efcc..f287d46a9a 100644 --- a/django/contrib/localflavor/br/forms.py +++ b/django/contrib/localflavor/br/forms.py @@ -3,7 +3,7 @@ BR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -34,11 +34,11 @@ class BRPhoneNumberField(Field): def clean(self, value): super(BRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: - return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) + return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) raise ValidationError(self.error_messages['invalid']) class BRStateSelect(Select): @@ -55,7 +55,7 @@ class BRStateChoiceField(Field): """ widget = Select default_error_messages = { - 'invalid': _(u'Select a valid brazilian state. That state is not one of the available states.'), + 'invalid': _('Select a valid brazilian state. That state is not one of the available states.'), } def __init__(self, required=True, widget=None, label=None, @@ -67,9 +67,9 @@ class BRStateChoiceField(Field): def clean(self, value): value = super(BRStateChoiceField, self).clean(value) if value in EMPTY_VALUES: - value = u'' + value = '' value = smart_unicode(value) - if value == u'': + if value == '': return value valid_values = set([smart_unicode(k) for k, v in self.widget.choices]) if value not in valid_values: @@ -105,7 +105,7 @@ class BRCPFField(CharField): """ value = super(BRCPFField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' orig_value = value[:] if not value.isdigit(): value = re.sub("[-\.]", "", value) @@ -142,7 +142,7 @@ class BRCNPJField(Field): """ value = super(BRCNPJField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' orig_value = value[:] if not value.isdigit(): value = re.sub("[-/\.]", "", value) diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py index c3be79968f..ec66e66539 100644 --- a/django/contrib/localflavor/ca/forms.py +++ b/django/contrib/localflavor/ca/forms.py @@ -2,7 +2,7 @@ Canada-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -26,7 +26,7 @@ class CAPostalCodeField(CharField): http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1402170 """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXX XXX.'), + 'invalid': _('Enter a postal code in the format XXX XXX.'), } postcode_regex = re.compile(r'^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ]) *(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$') @@ -34,7 +34,7 @@ class CAPostalCodeField(CharField): def clean(self, value): value = super(CAPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' postcode = value.upper().strip() m = self.postcode_regex.match(postcode) if not m: @@ -44,7 +44,7 @@ class CAPostalCodeField(CharField): class CAPhoneNumberField(Field): """Canadian phone number field.""" default_error_messages = { - 'invalid': _(u'Phone numbers must be in XXX-XXX-XXXX format.'), + 'invalid': _('Phone numbers must be in XXX-XXX-XXXX format.'), } def clean(self, value): @@ -52,11 +52,11 @@ class CAPhoneNumberField(Field): """ super(CAPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: - return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) + return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) raise ValidationError(self.error_messages['invalid']) class CAProvinceField(Field): @@ -66,13 +66,13 @@ class CAProvinceField(Field): abbreviation for the given province. """ default_error_messages = { - 'invalid': _(u'Enter a Canadian province or territory.'), + 'invalid': _('Enter a Canadian province or territory.'), } def clean(self, value): super(CAProvinceField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().lower() except AttributeError: @@ -113,14 +113,14 @@ class CASocialInsuranceNumberField(Field): def clean(self, value): super(CASocialInsuranceNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(sin_re, value) if not match: raise ValidationError(self.error_messages['invalid']) - number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) - check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3)) + number = '%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) + check_number = '%s%s%s' % (match.group(1), match.group(2), match.group(3)) if not self.luhn_checksum_is_valid(check_number): raise ValidationError(self.error_messages['invalid']) return number diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py index 649a98ab71..e844a3c57c 100644 --- a/django/contrib/localflavor/ch/forms.py +++ b/django/contrib/localflavor/ch/forms.py @@ -2,7 +2,7 @@ Swiss-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -40,11 +40,11 @@ class CHPhoneNumberField(Field): def clean(self, value): super(CHPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\.|\s|/|-)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: - return u'%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) + return '%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) raise ValidationError(self.error_messages['invalid']) class CHStateSelect(Select): @@ -102,7 +102,7 @@ class CHIdentityCardNumberField(Field): def clean(self, value): super(CHIdentityCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(id_re, value) if not match: @@ -118,5 +118,5 @@ class CHIdentityCardNumberField(Field): if not self.has_valid_checksum(all_digits): raise ValidationError(self.error_messages['invalid']) - return u'%s%s%s' % (idnumber, pos9, checksum) + return '%s%s%s' % (idnumber, pos9, checksum) diff --git a/django/contrib/localflavor/cl/cl_regions.py b/django/contrib/localflavor/cl/cl_regions.py index 47db6d3912..d76f6ad834 100644 --- a/django/contrib/localflavor/cl/cl_regions.py +++ b/django/contrib/localflavor/cl/cl_regions.py @@ -5,21 +5,22 @@ A list of Chilean regions as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals REGION_CHOICES = ( - ('RM', u'Región Metropolitana de Santiago'), - ('I', u'Región de Tarapacá'), - ('II', u'Región de Antofagasta'), - ('III', u'Región de Atacama'), - ('IV', u'Región de Coquimbo'), - ('V', u'Región de Valparaíso'), - ('VI', u'Región del Libertador Bernardo O\'Higgins'), - ('VII', u'Región del Maule'), - ('VIII',u'Región del Bío Bío'), - ('IX', u'Región de la Araucanía'), - ('X', u'Región de los Lagos'), - ('XI', u'Región de Aysén del General Carlos Ibáñez del Campo'), - ('XII', u'Región de Magallanes y la Antártica Chilena'), - ('XIV', u'Región de Los Ríos'), - ('XV', u'Región de Arica-Parinacota'), + ('RM', 'Región Metropolitana de Santiago'), + ('I', 'Región de Tarapacá'), + ('II', 'Región de Antofagasta'), + ('III', 'Región de Atacama'), + ('IV', 'Región de Coquimbo'), + ('V', 'Región de Valparaíso'), + ('VI', 'Región del Libertador Bernardo O\'Higgins'), + ('VII', 'Región del Maule'), + ('VIII','Región del Bío Bío'), + ('IX', 'Región de la Araucanía'), + ('X', 'Región de los Lagos'), + ('XI', 'Región de Aysén del General Carlos Ibáñez del Campo'), + ('XII', 'Región de Magallanes y la Antártica Chilena'), + ('XIV', 'Región de Los Ríos'), + ('XV', 'Región de Arica-Parinacota'), ) diff --git a/django/contrib/localflavor/cl/forms.py b/django/contrib/localflavor/cl/forms.py index 7a9aa2da8c..5991176382 100644 --- a/django/contrib/localflavor/cl/forms.py +++ b/django/contrib/localflavor/cl/forms.py @@ -2,15 +2,15 @@ Chile specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals -from django.contrib.localflavor.cl.cl_regions import REGION_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import RegexField, Select from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode +from .cl_regions import REGION_CHOICES class CLRegionSelect(Select): """ @@ -50,7 +50,7 @@ class CLRutField(RegexField): """ super(CLRutField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' rut, verificador = self._canonify(value) if self._algorithm(rut) == verificador: return self._format(rut, verificador) @@ -68,7 +68,7 @@ class CLRutField(RegexField): multi += 1 if multi == 8: multi = 2 - return u'0123456789K0'[11 - suma % 11] + return '0123456789K0'[11 - suma % 11] def _canonify(self, rut): """ @@ -93,5 +93,5 @@ class CLRutField(RegexField): else: new_dot = pos - 3 code = code[:new_dot] + '.' + code[new_dot:] - return u'%s-%s' % (code, verifier) + return '%s-%s' % (code, verifier) diff --git a/django/contrib/localflavor/cn/cn_provinces.py b/django/contrib/localflavor/cn/cn_provinces.py index fe0aa37cb6..c27cba6423 100644 --- a/django/contrib/localflavor/cn/cn_provinces.py +++ b/django/contrib/localflavor/cn/cn_provinces.py @@ -9,41 +9,41 @@ http://en.wikipedia.org/wiki/Province_%28China%29 http://en.wikipedia.org/wiki/Direct-controlled_municipality http://en.wikipedia.org/wiki/Autonomous_regions_of_China """ - +from __future__ import unicode_literals CN_PROVINCE_CHOICES = ( - ("anhui", u"安徽"), - ("beijing", u"北京"), - ("chongqing", u"重庆"), - ("fujian", u"福建"), - ("gansu", u"甘肃"), - ("guangdong", u"广东"), - ("guangxi", u"广西壮族自治区"), - ("guizhou", u"贵州"), - ("hainan", u"海南"), - ("hebei", u"河北"), - ("heilongjiang", u"黑龙江"), - ("henan", u"河南"), - ("hongkong", u"香港"), - ("hubei", u"湖北"), - ("hunan", u"湖南"), - ("jiangsu", u"江苏"), - ("jiangxi", u"江西"), - ("jilin", u"吉林"), - ("liaoning", u"辽宁"), - ("macao", u"澳门"), - ("neimongol", u"内蒙古自治区"), - ("ningxia", u"宁夏回族自治区"), - ("qinghai", u"青海"), - ("shaanxi", u"陕西"), - ("shandong", u"山东"), - ("shanghai", u"上海"), - ("shanxi", u"山西"), - ("sichuan", u"四川"), - ("taiwan", u"台湾"), - ("tianjin", u"天津"), - ("xinjiang", u"新疆维吾尔自治区"), - ("xizang", u"西藏自治区"), - ("yunnan", u"云南"), - ("zhejiang", u"浙江"), + ("anhui", "安徽"), + ("beijing", "北京"), + ("chongqing", "重庆"), + ("fujian", "福建"), + ("gansu", "甘肃"), + ("guangdong", "广东"), + ("guangxi", "广西壮族自治区"), + ("guizhou", "贵州"), + ("hainan", "海南"), + ("hebei", "河北"), + ("heilongjiang", "黑龙江"), + ("henan", "河南"), + ("hongkong", "香港"), + ("hubei", "湖北"), + ("hunan", "湖南"), + ("jiangsu", "江苏"), + ("jiangxi", "江西"), + ("jilin", "吉林"), + ("liaoning", "辽宁"), + ("macao", "澳门"), + ("neimongol", "内蒙古自治区"), + ("ningxia", "宁夏回族自治区"), + ("qinghai", "青海"), + ("shaanxi", "陕西"), + ("shandong", "山东"), + ("shanghai", "上海"), + ("shanxi", "山西"), + ("sichuan", "四川"), + ("taiwan", "台湾"), + ("tianjin", "天津"), + ("xinjiang", "新疆维吾尔自治区"), + ("xizang", "西藏自治区"), + ("yunnan", "云南"), + ("zhejiang", "浙江"), ) diff --git a/django/contrib/localflavor/cn/forms.py b/django/contrib/localflavor/cn/forms.py index af92ba06ec..43adcf3f01 100644 --- a/django/contrib/localflavor/cn/forms.py +++ b/django/contrib/localflavor/cn/forms.py @@ -3,7 +3,7 @@ """ Chinese-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -81,7 +81,7 @@ class CNPostCodeField(RegexField): Valid code is XXXXXX where X is digit. """ default_error_messages = { - 'invalid': _(u'Enter a post code in the format XXXXXX.'), + 'invalid': _('Enter a post code in the format XXXXXX.'), } def __init__(self, *args, **kwargs): @@ -102,10 +102,10 @@ class CNIDCardField(CharField): The checksum algorithm is described in GB11643-1999. """ default_error_messages = { - 'invalid': _(u'ID Card Number consists of 15 or 18 digits.'), - 'checksum': _(u'Invalid ID Card Number: Wrong checksum'), - 'birthday': _(u'Invalid ID Card Number: Wrong birthdate'), - 'location': _(u'Invalid ID Card Number: Wrong location code'), + 'invalid': _('ID Card Number consists of 15 or 18 digits.'), + 'checksum': _('Invalid ID Card Number: Wrong checksum'), + 'birthday': _('Invalid ID Card Number: Wrong birthdate'), + 'location': _('Invalid ID Card Number: Wrong location code'), } def __init__(self, max_length=18, min_length=15, *args, **kwargs): @@ -119,7 +119,7 @@ class CNIDCardField(CharField): # Check the length of the ID card number. super(CNIDCardField, self).clean(value) if not value: - return u"" + return "" # Check whether this ID card number has valid format if not re.match(ID_CARD_RE, value): raise ValidationError(self.error_messages['invalid']) @@ -133,7 +133,7 @@ class CNIDCardField(CharField): value = value.upper() if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_birthday(self, value): """ @@ -190,7 +190,7 @@ class CNPhoneNumberField(RegexField): 010-55555555-35 """ default_error_messages = { - 'invalid': _(u'Enter a valid phone number.'), + 'invalid': _('Enter a valid phone number.'), } def __init__(self, *args, **kwargs): @@ -207,7 +207,7 @@ class CNCellNumberField(RegexField): The length of the cell number should be 11. """ default_error_messages = { - 'invalid': _(u'Enter a valid cell number.'), + 'invalid': _('Enter a valid cell number.'), } def __init__(self, *args, **kwargs): diff --git a/django/contrib/localflavor/co/co_departments.py b/django/contrib/localflavor/co/co_departments.py index f0989b6ba6..7168f54cc2 100644 --- a/django/contrib/localflavor/co/co_departments.py +++ b/django/contrib/localflavor/co/co_departments.py @@ -6,39 +6,40 @@ formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals DEPARTMENT_CHOICES = ( - ('AMA', u'Amazonas'), - ('ANT', u'Antioquia'), - ('ARA', u'Arauca'), - ('ATL', u'Atlántico'), - ('DC', u'Bogotá'), - ('BOL', u'Bolívar'), - ('BOY', u'Boyacá'), - ('CAL', u'Caldas'), - ('CAQ', u'Caquetá'), - ('CAS', u'Casanare'), - ('CAU', u'Cauca'), - ('CES', u'Cesar'), - ('CHO', u'Chocó'), - ('COR', u'Córdoba'), - ('CUN', u'Cundinamarca'), - ('GUA', u'Guainía'), - ('GUV', u'Guaviare'), - ('HUI', u'Huila'), - ('LAG', u'La Guajira'), - ('MAG', u'Magdalena'), - ('MET', u'Meta'), - ('NAR', u'Nariño'), - ('NSA', u'Norte de Santander'), - ('PUT', u'Putumayo'), - ('QUI', u'Quindío'), - ('RIS', u'Risaralda'), - ('SAP', u'San Andrés and Providencia'), - ('SAN', u'Santander'), - ('SUC', u'Sucre'), - ('TOL', u'Tolima'), - ('VAC', u'Valle del Cauca'), - ('VAU', u'Vaupés'), - ('VID', u'Vichada'), + ('AMA', 'Amazonas'), + ('ANT', 'Antioquia'), + ('ARA', 'Arauca'), + ('ATL', 'Atlántico'), + ('DC', 'Bogotá'), + ('BOL', 'Bolívar'), + ('BOY', 'Boyacá'), + ('CAL', 'Caldas'), + ('CAQ', 'Caquetá'), + ('CAS', 'Casanare'), + ('CAU', 'Cauca'), + ('CES', 'Cesar'), + ('CHO', 'Chocó'), + ('COR', 'Córdoba'), + ('CUN', 'Cundinamarca'), + ('GUA', 'Guainía'), + ('GUV', 'Guaviare'), + ('HUI', 'Huila'), + ('LAG', 'La Guajira'), + ('MAG', 'Magdalena'), + ('MET', 'Meta'), + ('NAR', 'Nariño'), + ('NSA', 'Norte de Santander'), + ('PUT', 'Putumayo'), + ('QUI', 'Quindío'), + ('RIS', 'Risaralda'), + ('SAP', 'San Andrés and Providencia'), + ('SAN', 'Santander'), + ('SUC', 'Sucre'), + ('TOL', 'Tolima'), + ('VAC', 'Valle del Cauca'), + ('VAU', 'Vaupés'), + ('VID', 'Vichada'), ) diff --git a/django/contrib/localflavor/cz/forms.py b/django/contrib/localflavor/cz/forms.py index f3676b230b..c7e81e4037 100644 --- a/django/contrib/localflavor/cz/forms.py +++ b/django/contrib/localflavor/cz/forms.py @@ -2,7 +2,7 @@ Czech-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -29,7 +29,7 @@ class CZPostalCodeField(RegexField): Valid form is XXXXX or XXX XX, where X represents integer. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX or XXX XX.'), + 'invalid': _('Enter a postal code in the format XXXXX or XXX XX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -49,15 +49,15 @@ class CZBirthNumberField(Field): Czech birth number field. """ default_error_messages = { - 'invalid_format': _(u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'), - 'invalid': _(u'Enter a valid birth number.'), + 'invalid_format': _('Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'), + 'invalid': _('Enter a valid birth number.'), } def clean(self, value, gender=None): super(CZBirthNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(birth_number, value) if not match: @@ -67,7 +67,7 @@ class CZBirthNumberField(Field): # Three digits for verification number were used until 1. january 1954 if len(id) == 3: - return u'%s' % value + return '%s' % value # Birth number is in format YYMMDD. Females have month value raised by 50. # In case that all possible number are already used (for given date), @@ -90,7 +90,7 @@ class CZBirthNumberField(Field): modulo = int(birth + id[:3]) % 11 if (modulo == int(id[-1])) or (modulo == 10 and id[-1] == '0'): - return u'%s' % value + return '%s' % value else: raise ValidationError(self.error_messages['invalid']) @@ -99,14 +99,14 @@ class CZICNumberField(Field): Czech IC number field. """ default_error_messages = { - 'invalid': _(u'Enter a valid IC number.'), + 'invalid': _('Enter a valid IC number.'), } def clean(self, value): super(CZICNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(ic_number, value) if not match: @@ -130,7 +130,7 @@ class CZICNumberField(Field): if (not remainder % 10 and check == 1) or \ (remainder == 1 and check == 0) or \ (check == (11 - remainder)): - return u'%s' % value + return '%s' % value raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/de/forms.py b/django/contrib/localflavor/de/forms.py index 80b6248ed4..a7891d117f 100644 --- a/django/contrib/localflavor/de/forms.py +++ b/django/contrib/localflavor/de/forms.py @@ -2,7 +2,7 @@ DE-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -68,7 +68,7 @@ class DEIdentityCardNumberField(Field): def clean(self, value): super(DEIdentityCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(id_re, value) if not match: raise ValidationError(self.error_messages['invalid']) @@ -80,9 +80,9 @@ class DEIdentityCardNumberField(Field): if residence == '0000000000' or birthday == '0000000' or validity == '0000000': raise ValidationError(self.error_messages['invalid']) - all_digits = u"%s%s%s%s" % (residence, birthday, validity, checksum) + all_digits = "%s%s%s%s" % (residence, birthday, validity, checksum) if not self.has_valid_checksum(residence) or not self.has_valid_checksum(birthday) or \ not self.has_valid_checksum(validity) or not self.has_valid_checksum(all_digits): raise ValidationError(self.error_messages['invalid']) - return u'%s%s-%s-%s-%s' % (residence, origin, birthday, validity, checksum) + return '%s%s-%s-%s-%s' % (residence, origin, birthday, validity, checksum) diff --git a/django/contrib/localflavor/de_CH/formats.py b/django/contrib/localflavor/de_CH/formats.py index d4f324a422..9d56f9f298 100644 --- a/django/contrib/localflavor/de_CH/formats.py +++ b/django/contrib/localflavor/de_CH/formats.py @@ -4,6 +4,8 @@ # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +from __future__ import unicode_literals + DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i:s' DATETIME_FORMAT = 'j. F Y H:i:s' @@ -39,5 +41,5 @@ DATETIME_INPUT_FORMATS = ( # For details, please refer to http://www.bk.admin.ch/dokumentation/sprachen/04915/05016/index.html?lang=de # (in German) and the documentation DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/contrib/localflavor/ec/ec_provinces.py b/django/contrib/localflavor/ec/ec_provinces.py index 7e55078be0..db25d26ff8 100644 --- a/django/contrib/localflavor/ec/ec_provinces.py +++ b/django/contrib/localflavor/ec/ec_provinces.py @@ -6,30 +6,31 @@ formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals PROVINCE_CHOICES = ( - ('A', u'Azuay'), - ('B', u'Bolívar'), - ('F', u'Cañar'), - ('C', u'Carchi'), - ('H', u'Chimborazo'), - ('X', u'Cotopaxi'), - ('O', u'El Oro'), - ('E', u'Esmeraldas'), - ('W', u'Galápagos'), - ('G', u'Guayas'), - ('I', u'Imbabura'), - ('L', u'Loja'), - ('R', u'Los Ríos'), - ('M', u'Manabí'), - ('S', u'Morona Santiago'), - ('N', u'Napo'), - ('D', u'Orellana'), - ('Y', u'Pastaza'), - ('P', u'Pichincha'), - ('SE', u'Santa Elena'), - ('SD', u'Santo Domingo de los Tsáchilas'), - ('U', u'Sucumbíos'), - ('T', u'Tungurahua'), - ('Z', u'Zamora Chinchipe'), + ('A', 'Azuay'), + ('B', 'Bolívar'), + ('F', 'Cañar'), + ('C', 'Carchi'), + ('H', 'Chimborazo'), + ('X', 'Cotopaxi'), + ('O', 'El Oro'), + ('E', 'Esmeraldas'), + ('W', 'Galápagos'), + ('G', 'Guayas'), + ('I', 'Imbabura'), + ('L', 'Loja'), + ('R', 'Los Ríos'), + ('M', 'Manabí'), + ('S', 'Morona Santiago'), + ('N', 'Napo'), + ('D', 'Orellana'), + ('Y', 'Pastaza'), + ('P', 'Pichincha'), + ('SE', 'Santa Elena'), + ('SD', 'Santo Domingo de los Tsáchilas'), + ('U', 'Sucumbíos'), + ('T', 'Tungurahua'), + ('Z', 'Zamora Chinchipe'), ) diff --git a/django/contrib/localflavor/es/forms.py b/django/contrib/localflavor/es/forms.py index fe237270f5..b5e256ca44 100644 --- a/django/contrib/localflavor/es/forms.py +++ b/django/contrib/localflavor/es/forms.py @@ -91,7 +91,7 @@ class ESIdentityCardNumberField(RegexField): def clean(self, value): super(ESIdentityCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' nif_get_checksum = lambda d: self.nif_control[int(d)%23] value = value.upper().replace(' ', '').replace('-', '') @@ -157,7 +157,7 @@ class ESCCCField(RegexField): def clean(self, value): super(ESCCCField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' control_str = [1, 2, 4, 8, 5, 10, 9, 7, 3, 6] m = re.match(r'^(\d{4})[ -]?(\d{4})[ -]?(\d{2})[ -]?(\d{10})$', value) entity, office, checksum, account = m.groups() diff --git a/django/contrib/localflavor/fi/fi_municipalities.py b/django/contrib/localflavor/fi/fi_municipalities.py index 4b8b886b8b..6f90e5e02b 100644 --- a/django/contrib/localflavor/fi/fi_municipalities.py +++ b/django/contrib/localflavor/fi/fi_municipalities.py @@ -7,347 +7,349 @@ This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals + MUNICIPALITY_CHOICES = ( - ('akaa', u"Akaa"), - ('alajarvi', u"Alajärvi"), - ('alavieska', u"Alavieska"), - ('alavus', u"Alavus"), - ('artjarvi', u"Artjärvi"), - ('asikkala', u"Asikkala"), - ('askola', u"Askola"), - ('aura', u"Aura"), - ('brando', u"Brändö"), - ('eckero', u"Eckerö"), - ('enonkoski', u"Enonkoski"), - ('enontekio', u"Enontekiö"), - ('espoo', u"Espoo"), - ('eura', u"Eura"), - ('eurajoki', u"Eurajoki"), - ('evijarvi', u"Evijärvi"), - ('finstrom', u"Finström"), - ('forssa', u"Forssa"), - ('foglo', u"Föglö"), - ('geta', u"Geta"), - ('haapajarvi', u"Haapajärvi"), - ('haapavesi', u"Haapavesi"), - ('hailuoto', u"Hailuoto"), - ('halsua', u"Halsua"), - ('hamina', u"Hamina"), - ('hammarland', u"Hammarland"), - ('hankasalmi', u"Hankasalmi"), - ('hanko', u"Hanko"), - ('harjavalta', u"Harjavalta"), - ('hartola', u"Hartola"), - ('hattula', u"Hattula"), - ('haukipudas', u"Haukipudas"), - ('hausjarvi', u"Hausjärvi"), - ('heinola', u"Heinola"), - ('heinavesi', u"Heinävesi"), - ('helsinki', u"Helsinki"), - ('hirvensalmi', u"Hirvensalmi"), - ('hollola', u"Hollola"), - ('honkajoki', u"Honkajoki"), - ('huittinen', u"Huittinen"), - ('humppila', u"Humppila"), - ('hyrynsalmi', u"Hyrynsalmi"), - ('hyvinkaa', u"Hyvinkää"), - ('hameenkoski', u"Hämeenkoski"), - ('hameenkyro', u"Hämeenkyrö"), - ('hameenlinna', u"Hämeenlinna"), - ('ii', u"Ii"), - ('iisalmi', u"Iisalmi"), - ('iitti', u"Iitti"), - ('ikaalinen', u"Ikaalinen"), - ('ilmajoki', u"Ilmajoki"), - ('ilomantsi', u"Ilomantsi"), - ('imatra', u"Imatra"), - ('inari', u"Inari"), - ('inkoo', u"Inkoo"), - ('isojoki', u"Isojoki"), - ('isokyro', u"Isokyrö"), - ('jalasjarvi', u"Jalasjärvi"), - ('janakkala', u"Janakkala"), - ('joensuu', u"Joensuu"), - ('jokioinen', u"Jokioinen"), - ('jomala', u"Jomala"), - ('joroinen', u"Joroinen"), - ('joutsa', u"Joutsa"), - ('juankoski', u"Juankoski"), - ('juuka', u"Juuka"), - ('juupajoki', u"Juupajoki"), - ('juva', u"Juva"), - ('jyvaskyla', u"Jyväskylä"), - ('jamijarvi', u"Jämijärvi"), - ('jamsa', u"Jämsä"), - ('jarvenpaa', u"Järvenpää"), - ('kaarina', u"Kaarina"), - ('kaavi', u"Kaavi"), - ('kajaani', u"Kajaani"), - ('kalajoki', u"Kalajoki"), - ('kangasala', u"Kangasala"), - ('kangasniemi', u"Kangasniemi"), - ('kankaanpaa', u"Kankaanpää"), - ('kannonkoski', u"Kannonkoski"), - ('kannus', u"Kannus"), - ('karijoki', u"Karijoki"), - ('karjalohja', u"Karjalohja"), - ('karkkila', u"Karkkila"), - ('karstula', u"Karstula"), - ('karttula', u"Karttula"), - ('karvia', u"Karvia"), - ('kaskinen', u"Kaskinen"), - ('kauhajoki', u"Kauhajoki"), - ('kauhava', u"Kauhava"), - ('kauniainen', u"Kauniainen"), - ('kaustinen', u"Kaustinen"), - ('keitele', u"Keitele"), - ('kemi', u"Kemi"), - ('kemijarvi', u"Kemijärvi"), - ('keminmaa', u"Keminmaa"), - ('kemionsaari', u"Kemiönsaari"), - ('kempele', u"Kempele"), - ('kerava', u"Kerava"), - ('kerimaki', u"Kerimäki"), - ('kesalahti', u"Kesälahti"), - ('keuruu', u"Keuruu"), - ('kihnio', u"Kihniö"), - ('kiikoinen', u"Kiikoinen"), - ('kiiminki', u"Kiiminki"), - ('kinnula', u"Kinnula"), - ('kirkkonummi', u"Kirkkonummi"), - ('kitee', u"Kitee"), - ('kittila', u"Kittilä"), - ('kiuruvesi', u"Kiuruvesi"), - ('kivijarvi', u"Kivijärvi"), - ('kokemaki', u"Kokemäki"), - ('kokkola', u"Kokkola"), - ('kolari', u"Kolari"), - ('konnevesi', u"Konnevesi"), - ('kontiolahti', u"Kontiolahti"), - ('korsnas', u"Korsnäs"), - ('koskitl', u"Koski Tl"), - ('kotka', u"Kotka"), - ('kouvola', u"Kouvola"), - ('kristiinankaupunki', u"Kristiinankaupunki"), - ('kruunupyy', u"Kruunupyy"), - ('kuhmalahti', u"Kuhmalahti"), - ('kuhmo', u"Kuhmo"), - ('kuhmoinen', u"Kuhmoinen"), - ('kumlinge', u"Kumlinge"), - ('kuopio', u"Kuopio"), - ('kuortane', u"Kuortane"), - ('kurikka', u"Kurikka"), - ('kustavi', u"Kustavi"), - ('kuusamo', u"Kuusamo"), - ('kylmakoski', u"Kylmäkoski"), - ('kyyjarvi', u"Kyyjärvi"), - ('karkola', u"Kärkölä"), - ('karsamaki', u"Kärsämäki"), - ('kokar', u"Kökar"), - ('koylio', u"Köyliö"), - ('lahti', u"Lahti"), - ('laihia', u"Laihia"), - ('laitila', u"Laitila"), - ('lapinjarvi', u"Lapinjärvi"), - ('lapinlahti', u"Lapinlahti"), - ('lappajarvi', u"Lappajärvi"), - ('lappeenranta', u"Lappeenranta"), - ('lapua', u"Lapua"), - ('laukaa', u"Laukaa"), - ('lavia', u"Lavia"), - ('lemi', u"Lemi"), - ('lemland', u"Lemland"), - ('lempaala', u"Lempäälä"), - ('leppavirta', u"Leppävirta"), - ('lestijarvi', u"Lestijärvi"), - ('lieksa', u"Lieksa"), - ('lieto', u"Lieto"), - ('liminka', u"Liminka"), - ('liperi', u"Liperi"), - ('lohja', u"Lohja"), - ('loimaa', u"Loimaa"), - ('loppi', u"Loppi"), - ('loviisa', u"Loviisa"), - ('luhanka', u"Luhanka"), - ('lumijoki', u"Lumijoki"), - ('lumparland', u"Lumparland"), - ('luoto', u"Luoto"), - ('luumaki', u"Luumäki"), - ('luvia', u"Luvia"), - ('lansi-turunmaa', u"Länsi-Turunmaa"), - ('maalahti', u"Maalahti"), - ('maaninka', u"Maaninka"), - ('maarianhamina', u"Maarianhamina"), - ('marttila', u"Marttila"), - ('masku', u"Masku"), - ('merijarvi', u"Merijärvi"), - ('merikarvia', u"Merikarvia"), - ('miehikkala', u"Miehikkälä"), - ('mikkeli', u"Mikkeli"), - ('muhos', u"Muhos"), - ('multia', u"Multia"), - ('muonio', u"Muonio"), - ('mustasaari', u"Mustasaari"), - ('muurame', u"Muurame"), - ('mynamaki', u"Mynämäki"), - ('myrskyla', u"Myrskylä"), - ('mantsala', u"Mäntsälä"), - ('mantta-vilppula', u"Mänttä-Vilppula"), - ('mantyharju', u"Mäntyharju"), - ('naantali', u"Naantali"), - ('nakkila', u"Nakkila"), - ('nastola', u"Nastola"), - ('nilsia', u"Nilsiä"), - ('nivala', u"Nivala"), - ('nokia', u"Nokia"), - ('nousiainen', u"Nousiainen"), - ('nummi-pusula', u"Nummi-Pusula"), - ('nurmes', u"Nurmes"), - ('nurmijarvi', u"Nurmijärvi"), - ('narpio', u"Närpiö"), - ('oravainen', u"Oravainen"), - ('orimattila', u"Orimattila"), - ('oripaa', u"Oripää"), - ('orivesi', u"Orivesi"), - ('oulainen', u"Oulainen"), - ('oulu', u"Oulu"), - ('oulunsalo', u"Oulunsalo"), - ('outokumpu', u"Outokumpu"), - ('padasjoki', u"Padasjoki"), - ('paimio', u"Paimio"), - ('paltamo', u"Paltamo"), - ('parikkala', u"Parikkala"), - ('parkano', u"Parkano"), - ('pedersore', u"Pedersöre"), - ('pelkosenniemi', u"Pelkosenniemi"), - ('pello', u"Pello"), - ('perho', u"Perho"), - ('pertunmaa', u"Pertunmaa"), - ('petajavesi', u"Petäjävesi"), - ('pieksamaki', u"Pieksämäki"), - ('pielavesi', u"Pielavesi"), - ('pietarsaari', u"Pietarsaari"), - ('pihtipudas', u"Pihtipudas"), - ('pirkkala', u"Pirkkala"), - ('polvijarvi', u"Polvijärvi"), - ('pomarkku', u"Pomarkku"), - ('pori', u"Pori"), - ('pornainen', u"Pornainen"), - ('porvoo', u"Porvoo"), - ('posio', u"Posio"), - ('pudasjarvi', u"Pudasjärvi"), - ('pukkila', u"Pukkila"), - ('punkaharju', u"Punkaharju"), - ('punkalaidun', u"Punkalaidun"), - ('puolanka', u"Puolanka"), - ('puumala', u"Puumala"), - ('pyhtaa', u"Pyhtää"), - ('pyhajoki', u"Pyhäjoki"), - ('pyhajarvi', u"Pyhäjärvi"), - ('pyhanta', u"Pyhäntä"), - ('pyharanta', u"Pyhäranta"), - ('palkane', u"Pälkäne"), - ('poytya', u"Pöytyä"), - ('raahe', u"Raahe"), - ('raasepori', u"Raasepori"), - ('raisio', u"Raisio"), - ('rantasalmi', u"Rantasalmi"), - ('ranua', u"Ranua"), - ('rauma', u"Rauma"), - ('rautalampi', u"Rautalampi"), - ('rautavaara', u"Rautavaara"), - ('rautjarvi', u"Rautjärvi"), - ('reisjarvi', u"Reisjärvi"), - ('riihimaki', u"Riihimäki"), - ('ristiina', u"Ristiina"), - ('ristijarvi', u"Ristijärvi"), - ('rovaniemi', u"Rovaniemi"), - ('ruokolahti', u"Ruokolahti"), - ('ruovesi', u"Ruovesi"), - ('rusko', u"Rusko"), - ('raakkyla', u"Rääkkylä"), - ('saarijarvi', u"Saarijärvi"), - ('salla', u"Salla"), - ('salo', u"Salo"), - ('saltvik', u"Saltvik"), - ('sastamala', u"Sastamala"), - ('sauvo', u"Sauvo"), - ('savitaipale', u"Savitaipale"), - ('savonlinna', u"Savonlinna"), - ('savukoski', u"Savukoski"), - ('seinajoki', u"Seinäjoki"), - ('sievi', u"Sievi"), - ('siikainen', u"Siikainen"), - ('siikajoki', u"Siikajoki"), - ('siikalatva', u"Siikalatva"), - ('siilinjarvi', u"Siilinjärvi"), - ('simo', u"Simo"), - ('sipoo', u"Sipoo"), - ('siuntio', u"Siuntio"), - ('sodankyla', u"Sodankylä"), - ('soini', u"Soini"), - ('somero', u"Somero"), - ('sonkajarvi', u"Sonkajärvi"), - ('sotkamo', u"Sotkamo"), - ('sottunga', u"Sottunga"), - ('sulkava', u"Sulkava"), - ('sund', u"Sund"), - ('suomenniemi', u"Suomenniemi"), - ('suomussalmi', u"Suomussalmi"), - ('suonenjoki', u"Suonenjoki"), - ('sysma', u"Sysmä"), - ('sakyla', u"Säkylä"), - ('taipalsaari', u"Taipalsaari"), - ('taivalkoski', u"Taivalkoski"), - ('taivassalo', u"Taivassalo"), - ('tammela', u"Tammela"), - ('tampere', u"Tampere"), - ('tarvasjoki', u"Tarvasjoki"), - ('tervo', u"Tervo"), - ('tervola', u"Tervola"), - ('teuva', u"Teuva"), - ('tohmajarvi', u"Tohmajärvi"), - ('toholampi', u"Toholampi"), - ('toivakka', u"Toivakka"), - ('tornio', u"Tornio"), - ('turku', u"Turku"), - ('tuusniemi', u"Tuusniemi"), - ('tuusula', u"Tuusula"), - ('tyrnava', u"Tyrnävä"), - ('toysa', u"Töysä"), - ('ulvila', u"Ulvila"), - ('urjala', u"Urjala"), - ('utajarvi', u"Utajärvi"), - ('utsjoki', u"Utsjoki"), - ('uurainen', u"Uurainen"), - ('uusikaarlepyy', u"Uusikaarlepyy"), - ('uusikaupunki', u"Uusikaupunki"), - ('vaala', u"Vaala"), - ('vaasa', u"Vaasa"), - ('valkeakoski', u"Valkeakoski"), - ('valtimo', u"Valtimo"), - ('vantaa', u"Vantaa"), - ('varkaus', u"Varkaus"), - ('varpaisjarvi', u"Varpaisjärvi"), - ('vehmaa', u"Vehmaa"), - ('vesanto', u"Vesanto"), - ('vesilahti', u"Vesilahti"), - ('veteli', u"Veteli"), - ('vierema', u"Vieremä"), - ('vihanti', u"Vihanti"), - ('vihti', u"Vihti"), - ('viitasaari', u"Viitasaari"), - ('vimpeli', u"Vimpeli"), - ('virolahti', u"Virolahti"), - ('virrat', u"Virrat"), - ('vardo', u"Vårdö"), - ('vahakyro', u"Vähäkyrö"), - ('voyri-maksamaa', u"Vöyri-Maksamaa"), - ('yli-ii', u"Yli-Ii"), - ('ylitornio', u"Ylitornio"), - ('ylivieska', u"Ylivieska"), - ('ylojarvi', u"Ylöjärvi"), - ('ypaja', u"Ypäjä"), - ('ahtari', u"Ähtäri"), - ('aanekoski', u"Äänekoski") -) \ No newline at end of file + ('akaa', "Akaa"), + ('alajarvi', "Alajärvi"), + ('alavieska', "Alavieska"), + ('alavus', "Alavus"), + ('artjarvi', "Artjärvi"), + ('asikkala', "Asikkala"), + ('askola', "Askola"), + ('aura', "Aura"), + ('brando', "Brändö"), + ('eckero', "Eckerö"), + ('enonkoski', "Enonkoski"), + ('enontekio', "Enontekiö"), + ('espoo', "Espoo"), + ('eura', "Eura"), + ('eurajoki', "Eurajoki"), + ('evijarvi', "Evijärvi"), + ('finstrom', "Finström"), + ('forssa', "Forssa"), + ('foglo', "Föglö"), + ('geta', "Geta"), + ('haapajarvi', "Haapajärvi"), + ('haapavesi', "Haapavesi"), + ('hailuoto', "Hailuoto"), + ('halsua', "Halsua"), + ('hamina', "Hamina"), + ('hammarland', "Hammarland"), + ('hankasalmi', "Hankasalmi"), + ('hanko', "Hanko"), + ('harjavalta', "Harjavalta"), + ('hartola', "Hartola"), + ('hattula', "Hattula"), + ('haukipudas', "Haukipudas"), + ('hausjarvi', "Hausjärvi"), + ('heinola', "Heinola"), + ('heinavesi', "Heinävesi"), + ('helsinki', "Helsinki"), + ('hirvensalmi', "Hirvensalmi"), + ('hollola', "Hollola"), + ('honkajoki', "Honkajoki"), + ('huittinen', "Huittinen"), + ('humppila', "Humppila"), + ('hyrynsalmi', "Hyrynsalmi"), + ('hyvinkaa', "Hyvinkää"), + ('hameenkoski', "Hämeenkoski"), + ('hameenkyro', "Hämeenkyrö"), + ('hameenlinna', "Hämeenlinna"), + ('ii', "Ii"), + ('iisalmi', "Iisalmi"), + ('iitti', "Iitti"), + ('ikaalinen', "Ikaalinen"), + ('ilmajoki', "Ilmajoki"), + ('ilomantsi', "Ilomantsi"), + ('imatra', "Imatra"), + ('inari', "Inari"), + ('inkoo', "Inkoo"), + ('isojoki', "Isojoki"), + ('isokyro', "Isokyrö"), + ('jalasjarvi', "Jalasjärvi"), + ('janakkala', "Janakkala"), + ('joensuu', "Joensuu"), + ('jokioinen', "Jokioinen"), + ('jomala', "Jomala"), + ('joroinen', "Joroinen"), + ('joutsa', "Joutsa"), + ('juankoski', "Juankoski"), + ('juuka', "Juuka"), + ('juupajoki', "Juupajoki"), + ('juva', "Juva"), + ('jyvaskyla', "Jyväskylä"), + ('jamijarvi', "Jämijärvi"), + ('jamsa', "Jämsä"), + ('jarvenpaa', "Järvenpää"), + ('kaarina', "Kaarina"), + ('kaavi', "Kaavi"), + ('kajaani', "Kajaani"), + ('kalajoki', "Kalajoki"), + ('kangasala', "Kangasala"), + ('kangasniemi', "Kangasniemi"), + ('kankaanpaa', "Kankaanpää"), + ('kannonkoski', "Kannonkoski"), + ('kannus', "Kannus"), + ('karijoki', "Karijoki"), + ('karjalohja', "Karjalohja"), + ('karkkila', "Karkkila"), + ('karstula', "Karstula"), + ('karttula', "Karttula"), + ('karvia', "Karvia"), + ('kaskinen', "Kaskinen"), + ('kauhajoki', "Kauhajoki"), + ('kauhava', "Kauhava"), + ('kauniainen', "Kauniainen"), + ('kaustinen', "Kaustinen"), + ('keitele', "Keitele"), + ('kemi', "Kemi"), + ('kemijarvi', "Kemijärvi"), + ('keminmaa', "Keminmaa"), + ('kemionsaari', "Kemiönsaari"), + ('kempele', "Kempele"), + ('kerava', "Kerava"), + ('kerimaki', "Kerimäki"), + ('kesalahti', "Kesälahti"), + ('keuruu', "Keuruu"), + ('kihnio', "Kihniö"), + ('kiikoinen', "Kiikoinen"), + ('kiiminki', "Kiiminki"), + ('kinnula', "Kinnula"), + ('kirkkonummi', "Kirkkonummi"), + ('kitee', "Kitee"), + ('kittila', "Kittilä"), + ('kiuruvesi', "Kiuruvesi"), + ('kivijarvi', "Kivijärvi"), + ('kokemaki', "Kokemäki"), + ('kokkola', "Kokkola"), + ('kolari', "Kolari"), + ('konnevesi', "Konnevesi"), + ('kontiolahti', "Kontiolahti"), + ('korsnas', "Korsnäs"), + ('koskitl', "Koski Tl"), + ('kotka', "Kotka"), + ('kouvola', "Kouvola"), + ('kristiinankaupunki', "Kristiinankaupunki"), + ('kruunupyy', "Kruunupyy"), + ('kuhmalahti', "Kuhmalahti"), + ('kuhmo', "Kuhmo"), + ('kuhmoinen', "Kuhmoinen"), + ('kumlinge', "Kumlinge"), + ('kuopio', "Kuopio"), + ('kuortane', "Kuortane"), + ('kurikka', "Kurikka"), + ('kustavi', "Kustavi"), + ('kuusamo', "Kuusamo"), + ('kylmakoski', "Kylmäkoski"), + ('kyyjarvi', "Kyyjärvi"), + ('karkola', "Kärkölä"), + ('karsamaki', "Kärsämäki"), + ('kokar', "Kökar"), + ('koylio', "Köyliö"), + ('lahti', "Lahti"), + ('laihia', "Laihia"), + ('laitila', "Laitila"), + ('lapinjarvi', "Lapinjärvi"), + ('lapinlahti', "Lapinlahti"), + ('lappajarvi', "Lappajärvi"), + ('lappeenranta', "Lappeenranta"), + ('lapua', "Lapua"), + ('laukaa', "Laukaa"), + ('lavia', "Lavia"), + ('lemi', "Lemi"), + ('lemland', "Lemland"), + ('lempaala', "Lempäälä"), + ('leppavirta', "Leppävirta"), + ('lestijarvi', "Lestijärvi"), + ('lieksa', "Lieksa"), + ('lieto', "Lieto"), + ('liminka', "Liminka"), + ('liperi', "Liperi"), + ('lohja', "Lohja"), + ('loimaa', "Loimaa"), + ('loppi', "Loppi"), + ('loviisa', "Loviisa"), + ('luhanka', "Luhanka"), + ('lumijoki', "Lumijoki"), + ('lumparland', "Lumparland"), + ('luoto', "Luoto"), + ('luumaki', "Luumäki"), + ('luvia', "Luvia"), + ('lansi-turunmaa', "Länsi-Turunmaa"), + ('maalahti', "Maalahti"), + ('maaninka', "Maaninka"), + ('maarianhamina', "Maarianhamina"), + ('marttila', "Marttila"), + ('masku', "Masku"), + ('merijarvi', "Merijärvi"), + ('merikarvia', "Merikarvia"), + ('miehikkala', "Miehikkälä"), + ('mikkeli', "Mikkeli"), + ('muhos', "Muhos"), + ('multia', "Multia"), + ('muonio', "Muonio"), + ('mustasaari', "Mustasaari"), + ('muurame', "Muurame"), + ('mynamaki', "Mynämäki"), + ('myrskyla', "Myrskylä"), + ('mantsala', "Mäntsälä"), + ('mantta-vilppula', "Mänttä-Vilppula"), + ('mantyharju', "Mäntyharju"), + ('naantali', "Naantali"), + ('nakkila', "Nakkila"), + ('nastola', "Nastola"), + ('nilsia', "Nilsiä"), + ('nivala', "Nivala"), + ('nokia', "Nokia"), + ('nousiainen', "Nousiainen"), + ('nummi-pusula', "Nummi-Pusula"), + ('nurmes', "Nurmes"), + ('nurmijarvi', "Nurmijärvi"), + ('narpio', "Närpiö"), + ('oravainen', "Oravainen"), + ('orimattila', "Orimattila"), + ('oripaa', "Oripää"), + ('orivesi', "Orivesi"), + ('oulainen', "Oulainen"), + ('oulu', "Oulu"), + ('oulunsalo', "Oulunsalo"), + ('outokumpu', "Outokumpu"), + ('padasjoki', "Padasjoki"), + ('paimio', "Paimio"), + ('paltamo', "Paltamo"), + ('parikkala', "Parikkala"), + ('parkano', "Parkano"), + ('pedersore', "Pedersöre"), + ('pelkosenniemi', "Pelkosenniemi"), + ('pello', "Pello"), + ('perho', "Perho"), + ('pertunmaa', "Pertunmaa"), + ('petajavesi', "Petäjävesi"), + ('pieksamaki', "Pieksämäki"), + ('pielavesi', "Pielavesi"), + ('pietarsaari', "Pietarsaari"), + ('pihtipudas', "Pihtipudas"), + ('pirkkala', "Pirkkala"), + ('polvijarvi', "Polvijärvi"), + ('pomarkku', "Pomarkku"), + ('pori', "Pori"), + ('pornainen', "Pornainen"), + ('porvoo', "Porvoo"), + ('posio', "Posio"), + ('pudasjarvi', "Pudasjärvi"), + ('pukkila', "Pukkila"), + ('punkaharju', "Punkaharju"), + ('punkalaidun', "Punkalaidun"), + ('puolanka', "Puolanka"), + ('puumala', "Puumala"), + ('pyhtaa', "Pyhtää"), + ('pyhajoki', "Pyhäjoki"), + ('pyhajarvi', "Pyhäjärvi"), + ('pyhanta', "Pyhäntä"), + ('pyharanta', "Pyhäranta"), + ('palkane', "Pälkäne"), + ('poytya', "Pöytyä"), + ('raahe', "Raahe"), + ('raasepori', "Raasepori"), + ('raisio', "Raisio"), + ('rantasalmi', "Rantasalmi"), + ('ranua', "Ranua"), + ('rauma', "Rauma"), + ('rautalampi', "Rautalampi"), + ('rautavaara', "Rautavaara"), + ('rautjarvi', "Rautjärvi"), + ('reisjarvi', "Reisjärvi"), + ('riihimaki', "Riihimäki"), + ('ristiina', "Ristiina"), + ('ristijarvi', "Ristijärvi"), + ('rovaniemi', "Rovaniemi"), + ('ruokolahti', "Ruokolahti"), + ('ruovesi', "Ruovesi"), + ('rusko', "Rusko"), + ('raakkyla', "Rääkkylä"), + ('saarijarvi', "Saarijärvi"), + ('salla', "Salla"), + ('salo', "Salo"), + ('saltvik', "Saltvik"), + ('sastamala', "Sastamala"), + ('sauvo', "Sauvo"), + ('savitaipale', "Savitaipale"), + ('savonlinna', "Savonlinna"), + ('savukoski', "Savukoski"), + ('seinajoki', "Seinäjoki"), + ('sievi', "Sievi"), + ('siikainen', "Siikainen"), + ('siikajoki', "Siikajoki"), + ('siikalatva', "Siikalatva"), + ('siilinjarvi', "Siilinjärvi"), + ('simo', "Simo"), + ('sipoo', "Sipoo"), + ('siuntio', "Siuntio"), + ('sodankyla', "Sodankylä"), + ('soini', "Soini"), + ('somero', "Somero"), + ('sonkajarvi', "Sonkajärvi"), + ('sotkamo', "Sotkamo"), + ('sottunga', "Sottunga"), + ('sulkava', "Sulkava"), + ('sund', "Sund"), + ('suomenniemi', "Suomenniemi"), + ('suomussalmi', "Suomussalmi"), + ('suonenjoki', "Suonenjoki"), + ('sysma', "Sysmä"), + ('sakyla', "Säkylä"), + ('taipalsaari', "Taipalsaari"), + ('taivalkoski', "Taivalkoski"), + ('taivassalo', "Taivassalo"), + ('tammela', "Tammela"), + ('tampere', "Tampere"), + ('tarvasjoki', "Tarvasjoki"), + ('tervo', "Tervo"), + ('tervola', "Tervola"), + ('teuva', "Teuva"), + ('tohmajarvi', "Tohmajärvi"), + ('toholampi', "Toholampi"), + ('toivakka', "Toivakka"), + ('tornio', "Tornio"), + ('turku', "Turku"), + ('tuusniemi', "Tuusniemi"), + ('tuusula', "Tuusula"), + ('tyrnava', "Tyrnävä"), + ('toysa', "Töysä"), + ('ulvila', "Ulvila"), + ('urjala', "Urjala"), + ('utajarvi', "Utajärvi"), + ('utsjoki', "Utsjoki"), + ('uurainen', "Uurainen"), + ('uusikaarlepyy', "Uusikaarlepyy"), + ('uusikaupunki', "Uusikaupunki"), + ('vaala', "Vaala"), + ('vaasa', "Vaasa"), + ('valkeakoski', "Valkeakoski"), + ('valtimo', "Valtimo"), + ('vantaa', "Vantaa"), + ('varkaus', "Varkaus"), + ('varpaisjarvi', "Varpaisjärvi"), + ('vehmaa', "Vehmaa"), + ('vesanto', "Vesanto"), + ('vesilahti', "Vesilahti"), + ('veteli', "Veteli"), + ('vierema', "Vieremä"), + ('vihanti', "Vihanti"), + ('vihti', "Vihti"), + ('viitasaari', "Viitasaari"), + ('vimpeli', "Vimpeli"), + ('virolahti', "Virolahti"), + ('virrat', "Virrat"), + ('vardo', "Vårdö"), + ('vahakyro', "Vähäkyrö"), + ('voyri-maksamaa', "Vöyri-Maksamaa"), + ('yli-ii', "Yli-Ii"), + ('ylitornio', "Ylitornio"), + ('ylivieska', "Ylivieska"), + ('ylojarvi', "Ylöjärvi"), + ('ypaja', "Ypäjä"), + ('ahtari', "Ähtäri"), + ('aanekoski', "Äänekoski") +) diff --git a/django/contrib/localflavor/fi/forms.py b/django/contrib/localflavor/fi/forms.py index ddc3b48c54..633f3e5f1b 100644 --- a/django/contrib/localflavor/fi/forms.py +++ b/django/contrib/localflavor/fi/forms.py @@ -2,7 +2,7 @@ FI-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -36,7 +36,7 @@ class FISocialSecurityNumber(Field): def clean(self, value): super(FISocialSecurityNumber, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' checkmarks = "0123456789ABCDEFHJKLMNPRSTUVWXY" result = re.match(r"""^ @@ -51,5 +51,5 @@ class FISocialSecurityNumber(Field): gd = result.groupdict() checksum = int(gd['date'] + gd['serial']) if checkmarks[checksum % len(checkmarks)] == gd['checksum'].upper(): - return u'%s' % value.upper() + return '%s' % value.upper() raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index 34e4a96bf4..47177db685 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -1,7 +1,7 @@ """ FR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -38,11 +38,11 @@ class FRPhoneNumberField(Field): def clean(self, value): super(FRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\.|\s)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: - return u'%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) + return '%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) raise ValidationError(self.error_messages['invalid']) class FRDepartmentSelect(Select): diff --git a/django/contrib/localflavor/fr/fr_department.py b/django/contrib/localflavor/fr/fr_department.py index 9f146ac8f9..a2cca957c2 100644 --- a/django/contrib/localflavor/fr/fr_department.py +++ b/django/contrib/localflavor/fr/fr_department.py @@ -1,117 +1,118 @@ # -*- coding: utf-8 -*- # See the "Code officiel géographique" on the INSEE website . +from __future__ import unicode_literals DEPARTMENT_CHOICES = ( # Metropolitan departments - ('01', u'01 - Ain'), - ('02', u'02 - Aisne'), - ('03', u'03 - Allier'), - ('04', u'04 - Alpes-de-Haute-Provence'), - ('05', u'05 - Hautes-Alpes'), - ('06', u'06 - Alpes-Maritimes'), - ('07', u'07 - Ardèche'), - ('08', u'08 - Ardennes'), - ('09', u'09 - Ariège'), - ('10', u'10 - Aube'), - ('11', u'11 - Aude'), - ('12', u'12 - Aveyron'), - ('13', u'13 - Bouches-du-Rhône'), - ('14', u'14 - Calvados'), - ('15', u'15 - Cantal'), - ('16', u'16 - Charente'), - ('17', u'17 - Charente-Maritime'), - ('18', u'18 - Cher'), - ('19', u'19 - Corrèze'), - ('2A', u'2A - Corse-du-Sud'), - ('2B', u'2B - Haute-Corse'), - ('21', u'21 - Côte-d\'Or'), - ('22', u'22 - Côtes-d\'Armor'), - ('23', u'23 - Creuse'), - ('24', u'24 - Dordogne'), - ('25', u'25 - Doubs'), - ('26', u'26 - Drôme'), - ('27', u'27 - Eure'), - ('28', u'28 - Eure-et-Loir'), - ('29', u'29 - Finistère'), - ('30', u'30 - Gard'), - ('31', u'31 - Haute-Garonne'), - ('32', u'32 - Gers'), - ('33', u'33 - Gironde'), - ('34', u'34 - Hérault'), - ('35', u'35 - Ille-et-Vilaine'), - ('36', u'36 - Indre'), - ('37', u'37 - Indre-et-Loire'), - ('38', u'38 - Isère'), - ('39', u'39 - Jura'), - ('40', u'40 - Landes'), - ('41', u'41 - Loir-et-Cher'), - ('42', u'42 - Loire'), - ('43', u'43 - Haute-Loire'), - ('44', u'44 - Loire-Atlantique'), - ('45', u'45 - Loiret'), - ('46', u'46 - Lot'), - ('47', u'47 - Lot-et-Garonne'), - ('48', u'48 - Lozère'), - ('49', u'49 - Maine-et-Loire'), - ('50', u'50 - Manche'), - ('51', u'51 - Marne'), - ('52', u'52 - Haute-Marne'), - ('53', u'53 - Mayenne'), - ('54', u'54 - Meurthe-et-Moselle'), - ('55', u'55 - Meuse'), - ('56', u'56 - Morbihan'), - ('57', u'57 - Moselle'), - ('58', u'58 - Nièvre'), - ('59', u'59 - Nord'), - ('60', u'60 - Oise'), - ('61', u'61 - Orne'), - ('62', u'62 - Pas-de-Calais'), - ('63', u'63 - Puy-de-Dôme'), - ('64', u'64 - Pyrénées-Atlantiques'), - ('65', u'65 - Hautes-Pyrénées'), - ('66', u'66 - Pyrénées-Orientales'), - ('67', u'67 - Bas-Rhin'), - ('68', u'68 - Haut-Rhin'), - ('69', u'69 - Rhône'), - ('70', u'70 - Haute-Saône'), - ('71', u'71 - Saône-et-Loire'), - ('72', u'72 - Sarthe'), - ('73', u'73 - Savoie'), - ('74', u'74 - Haute-Savoie'), - ('75', u'75 - Paris'), - ('76', u'76 - Seine-Maritime'), - ('77', u'77 - Seine-et-Marne'), - ('78', u'78 - Yvelines'), - ('79', u'79 - Deux-Sèvres'), - ('80', u'80 - Somme'), - ('81', u'81 - Tarn'), - ('82', u'82 - Tarn-et-Garonne'), - ('83', u'83 - Var'), - ('84', u'84 - Vaucluse'), - ('85', u'85 - Vendée'), - ('86', u'86 - Vienne'), - ('87', u'87 - Haute-Vienne'), - ('88', u'88 - Vosges'), - ('89', u'89 - Yonne'), - ('90', u'90 - Territoire de Belfort'), - ('91', u'91 - Essonne'), - ('92', u'92 - Hauts-de-Seine'), - ('93', u'93 - Seine-Saint-Denis'), - ('94', u'94 - Val-de-Marne'), - ('95', u'95 - Val-d\'Oise'), + ('01', '01 - Ain'), + ('02', '02 - Aisne'), + ('03', '03 - Allier'), + ('04', '04 - Alpes-de-Haute-Provence'), + ('05', '05 - Hautes-Alpes'), + ('06', '06 - Alpes-Maritimes'), + ('07', '07 - Ardèche'), + ('08', '08 - Ardennes'), + ('09', '09 - Ariège'), + ('10', '10 - Aube'), + ('11', '11 - Aude'), + ('12', '12 - Aveyron'), + ('13', '13 - Bouches-du-Rhône'), + ('14', '14 - Calvados'), + ('15', '15 - Cantal'), + ('16', '16 - Charente'), + ('17', '17 - Charente-Maritime'), + ('18', '18 - Cher'), + ('19', '19 - Corrèze'), + ('2A', '2A - Corse-du-Sud'), + ('2B', '2B - Haute-Corse'), + ('21', '21 - Côte-d\'Or'), + ('22', '22 - Côtes-d\'Armor'), + ('23', '23 - Creuse'), + ('24', '24 - Dordogne'), + ('25', '25 - Doubs'), + ('26', '26 - Drôme'), + ('27', '27 - Eure'), + ('28', '28 - Eure-et-Loir'), + ('29', '29 - Finistère'), + ('30', '30 - Gard'), + ('31', '31 - Haute-Garonne'), + ('32', '32 - Gers'), + ('33', '33 - Gironde'), + ('34', '34 - Hérault'), + ('35', '35 - Ille-et-Vilaine'), + ('36', '36 - Indre'), + ('37', '37 - Indre-et-Loire'), + ('38', '38 - Isère'), + ('39', '39 - Jura'), + ('40', '40 - Landes'), + ('41', '41 - Loir-et-Cher'), + ('42', '42 - Loire'), + ('43', '43 - Haute-Loire'), + ('44', '44 - Loire-Atlantique'), + ('45', '45 - Loiret'), + ('46', '46 - Lot'), + ('47', '47 - Lot-et-Garonne'), + ('48', '48 - Lozère'), + ('49', '49 - Maine-et-Loire'), + ('50', '50 - Manche'), + ('51', '51 - Marne'), + ('52', '52 - Haute-Marne'), + ('53', '53 - Mayenne'), + ('54', '54 - Meurthe-et-Moselle'), + ('55', '55 - Meuse'), + ('56', '56 - Morbihan'), + ('57', '57 - Moselle'), + ('58', '58 - Nièvre'), + ('59', '59 - Nord'), + ('60', '60 - Oise'), + ('61', '61 - Orne'), + ('62', '62 - Pas-de-Calais'), + ('63', '63 - Puy-de-Dôme'), + ('64', '64 - Pyrénées-Atlantiques'), + ('65', '65 - Hautes-Pyrénées'), + ('66', '66 - Pyrénées-Orientales'), + ('67', '67 - Bas-Rhin'), + ('68', '68 - Haut-Rhin'), + ('69', '69 - Rhône'), + ('70', '70 - Haute-Saône'), + ('71', '71 - Saône-et-Loire'), + ('72', '72 - Sarthe'), + ('73', '73 - Savoie'), + ('74', '74 - Haute-Savoie'), + ('75', '75 - Paris'), + ('76', '76 - Seine-Maritime'), + ('77', '77 - Seine-et-Marne'), + ('78', '78 - Yvelines'), + ('79', '79 - Deux-Sèvres'), + ('80', '80 - Somme'), + ('81', '81 - Tarn'), + ('82', '82 - Tarn-et-Garonne'), + ('83', '83 - Var'), + ('84', '84 - Vaucluse'), + ('85', '85 - Vendée'), + ('86', '86 - Vienne'), + ('87', '87 - Haute-Vienne'), + ('88', '88 - Vosges'), + ('89', '89 - Yonne'), + ('90', '90 - Territoire de Belfort'), + ('91', '91 - Essonne'), + ('92', '92 - Hauts-de-Seine'), + ('93', '93 - Seine-Saint-Denis'), + ('94', '94 - Val-de-Marne'), + ('95', '95 - Val-d\'Oise'), # Overseas departments, communities, and other territories - ('971', u'971 - Guadeloupe'), - ('972', u'972 - Martinique'), - ('973', u'973 - Guyane'), - ('974', u'974 - La Réunion'), - ('975', u'975 - Saint-Pierre-et-Miquelon'), - ('976', u'976 - Mayotte'), - ('977', u'977 - Saint-Barthélemy'), - ('978', u'978 - Saint-Martin'), - ('984', u'984 - Terres australes et antarctiques françaises'), - ('986', u'986 - Wallis et Futuna'), - ('987', u'987 - Polynésie française'), - ('988', u'988 - Nouvelle-Calédonie'), - ('989', u'989 - Île de Clipperton'), + ('971', '971 - Guadeloupe'), + ('972', '972 - Martinique'), + ('973', '973 - Guyane'), + ('974', '974 - La Réunion'), + ('975', '975 - Saint-Pierre-et-Miquelon'), + ('976', '976 - Mayotte'), + ('977', '977 - Saint-Barthélemy'), + ('978', '978 - Saint-Martin'), + ('984', '984 - Terres australes et antarctiques françaises'), + ('986', '986 - Wallis et Futuna'), + ('987', '987 - Polynésie française'), + ('988', '988 - Nouvelle-Calédonie'), + ('989', '989 - Île de Clipperton'), ) diff --git a/django/contrib/localflavor/gb/forms.py b/django/contrib/localflavor/gb/forms.py index a6658578b1..bf90f80281 100644 --- a/django/contrib/localflavor/gb/forms.py +++ b/django/contrib/localflavor/gb/forms.py @@ -2,7 +2,7 @@ GB-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -22,7 +22,7 @@ class GBPostcodeField(CharField): The value is uppercased and a space added in the correct place, if required. """ default_error_messages = { - 'invalid': _(u'Enter a valid postcode.'), + 'invalid': _('Enter a valid postcode.'), } outcode_pattern = '[A-PR-UWYZ]([0-9]{1,2}|([A-HIK-Y][0-9](|[0-9]|[ABEHMNPRVWXY]))|[0-9][A-HJKSTUW])' incode_pattern = '[0-9][ABD-HJLNP-UW-Z]{2}' @@ -31,7 +31,7 @@ class GBPostcodeField(CharField): def clean(self, value): value = super(GBPostcodeField, self).clean(value) - if value == u'': + if value == '': return value postcode = value.upper().strip() # Put a single space before the incode (second part). diff --git a/django/contrib/localflavor/hk/forms.py b/django/contrib/localflavor/hk/forms.py index 852ef7d4b2..8cf9360e19 100644 --- a/django/contrib/localflavor/hk/forms.py +++ b/django/contrib/localflavor/hk/forms.py @@ -1,7 +1,7 @@ """ Hong Kong specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -51,14 +51,14 @@ class HKPhoneNumberField(CharField): super(HKPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\(|\)|\s+|\+)', '', smart_unicode(value)) m = hk_phone_digits_re.search(value) if not m: raise ValidationError(self.error_messages['invalid']) - value = u'%s-%s' % (m.group(1), m.group(2)) + value = '%s-%s' % (m.group(1), m.group(2)) for special in hk_special_numbers: if value.startswith(special): raise ValidationError(self.error_messages['disguise']) diff --git a/django/contrib/localflavor/hr/forms.py b/django/contrib/localflavor/hr/forms.py index 0ff283d6c8..eb4436a78c 100644 --- a/django/contrib/localflavor/hr/forms.py +++ b/django/contrib/localflavor/hr/forms.py @@ -2,7 +2,7 @@ """ HR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -19,8 +19,8 @@ from django.utils.translation import ugettext_lazy as _ jmbg_re = re.compile(r'^(?P
    \d{2})(?P\d{2})(?P\d{3})' + \ r'(?P\d{2})(?P\d{3})(?P\d{1})$') oib_re = re.compile(r'^\d{11}$') -plate_re = re.compile(ur'^(?P[A-ZČŠŽ]{2})' + \ - ur'(?P\d{3,4})(?P[ABCDEFGHIJKLMNOPRSTUVZ]{1,2})$') +plate_re = re.compile(r'^(?P[A-ZČŠŽ]{2})' + \ + r'(?P\d{3,4})(?P[ABCDEFGHIJKLMNOPRSTUVZ]{1,2})$') postal_code_re = re.compile(r'^\d{5}$') phone_re = re.compile(r'^(\+385|00385|0)(?P\d{2})(?P\d{6,7})$') jmbag_re = re.compile(r'^601983(?P\d{1})1(?P\d{10})(?P\d{1})$') @@ -79,7 +79,7 @@ class HRJMBGField(Field): def clean(self, value): super(HRJMBGField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() @@ -110,7 +110,7 @@ class HRJMBGField(Field): if not str(m) == k: raise ValidationError(self.error_messages['invalid']) - return u'%s' % (value, ) + return '%s' % (value, ) class HROIBField(RegexField): @@ -130,7 +130,7 @@ class HROIBField(RegexField): def clean(self, value): super(HROIBField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' return '%s' % (value, ) @@ -157,7 +157,7 @@ class HRLicensePlateField(Field): def clean(self, value): super(HRLicensePlateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub(r'[\s\-]+', '', smart_unicode(value.strip())).upper() @@ -175,7 +175,7 @@ class HRLicensePlateField(Field): if int(number) == 0: raise ValidationError(self.error_messages['number']) - return u'%s %s-%s' % (prefix,number,matches.group('suffix'), ) + return '%s %s-%s' % (prefix,number,matches.group('suffix'), ) class HRPostalCodeField(Field): @@ -193,7 +193,7 @@ class HRPostalCodeField(Field): def clean(self, value): super(HRPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() if not postal_code_re.search(value): @@ -223,7 +223,7 @@ class HRPhoneNumberField(Field): def clean(self, value): super(HRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) @@ -262,7 +262,7 @@ class HRJMBAGField(Field): def clean(self, value): super(HRJMBAGField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub(r'[\-\s]', '', value.strip()) diff --git a/django/contrib/localflavor/hr/hr_choices.py b/django/contrib/localflavor/hr/hr_choices.py index 24d3d42c5b..beb2969405 100644 --- a/django/contrib/localflavor/hr/hr_choices.py +++ b/django/contrib/localflavor/hr/hr_choices.py @@ -6,30 +6,32 @@ Sources: Croatia doesn't have official abbreviations for counties. The ones provided are in common use. """ +from __future__ import unicode_literals + from django.utils.translation import ugettext_lazy as _ HR_COUNTY_CHOICES = ( ('GZG', _('Grad Zagreb')), - (u'BBŽ', _(u'Bjelovarsko-bilogorska županija')), - (u'BPŽ', _(u'Brodsko-posavska županija')), - (u'DNŽ', _(u'Dubrovačko-neretvanska županija')), - (u'IŽ', _(u'Istarska županija')), - (u'KŽ', _(u'Karlovačka županija')), - (u'KKŽ', _(u'Koprivničko-križevačka županija')), - (u'KZŽ', _(u'Krapinsko-zagorska županija')), - (u'LSŽ', _(u'Ličko-senjska županija')), - (u'MŽ', _(u'Međimurska županija')), - (u'OBŽ', _(u'Osječko-baranjska županija')), - (u'PSŽ', _(u'Požeško-slavonska županija')), - (u'PGŽ', _(u'Primorsko-goranska županija')), - (u'SMŽ', _(u'Sisačko-moslavačka županija')), - (u'SDŽ', _(u'Splitsko-dalmatinska županija')), - (u'ŠKŽ', _(u'Šibensko-kninska županija')), - (u'VŽ', _(u'Varaždinska županija')), - (u'VPŽ', _(u'Virovitičko-podravska županija')), - (u'VSŽ', _(u'Vukovarsko-srijemska županija')), - (u'ZDŽ', _(u'Zadarska županija')), - (u'ZGŽ', _(u'Zagrebačka županija')), + ('BBŽ', _('Bjelovarsko-bilogorska županija')), + ('BPŽ', _('Brodsko-posavska županija')), + ('DNŽ', _('Dubrovačko-neretvanska županija')), + ('IŽ', _('Istarska županija')), + ('KŽ', _('Karlovačka županija')), + ('KKŽ', _('Koprivničko-križevačka županija')), + ('KZŽ', _('Krapinsko-zagorska županija')), + ('LSŽ', _('Ličko-senjska županija')), + ('MŽ', _('Međimurska županija')), + ('OBŽ', _('Osječko-baranjska županija')), + ('PSŽ', _('Požeško-slavonska županija')), + ('PGŽ', _('Primorsko-goranska županija')), + ('SMŽ', _('Sisačko-moslavačka županija')), + ('SDŽ', _('Splitsko-dalmatinska županija')), + ('ŠKŽ', _('Šibensko-kninska županija')), + ('VŽ', _('Varaždinska županija')), + ('VPŽ', _('Virovitičko-podravska županija')), + ('VSŽ', _('Vukovarsko-srijemska županija')), + ('ZDŽ', _('Zadarska županija')), + ('ZGŽ', _('Zagrebačka županija')), ) """ @@ -42,7 +44,7 @@ Only common license plate prefixes are provided. Special cases and obsolete pref HR_LICENSE_PLATE_PREFIX_CHOICES = ( ('BJ', 'BJ'), ('BM', 'BM'), - (u'ČK', u'ČK'), + ('ČK', 'ČK'), ('DA', 'DA'), ('DE', 'DE'), ('DJ', 'DJ'), @@ -53,27 +55,27 @@ HR_LICENSE_PLATE_PREFIX_CHOICES = ( ('KC', 'KC'), ('KR', 'KR'), ('KT', 'KT'), - (u'KŽ', u'KŽ'), + ('KŽ', 'KŽ'), ('MA', 'MA'), ('NA', 'NA'), ('NG', 'NG'), ('OG', 'OG'), ('OS', 'OS'), ('PU', 'PU'), - (u'PŽ', u'PŽ'), + ('PŽ', 'PŽ'), ('RI', 'RI'), ('SB', 'SB'), ('SK', 'SK'), ('SL', 'SL'), ('ST', 'ST'), - (u'ŠI', u'ŠI'), + ('ŠI', 'ŠI'), ('VK', 'VK'), ('VT', 'VT'), ('VU', 'VU'), - (u'VŽ', u'VŽ'), + ('VŽ', 'VŽ'), ('ZD', 'ZD'), ('ZG', 'ZG'), - (u'ŽU', u'ŽU'), + ('ŽU', 'ŽU'), ) """ diff --git a/django/contrib/localflavor/id/forms.py b/django/contrib/localflavor/id/forms.py index 9439dba594..f22b06134e 100644 --- a/django/contrib/localflavor/id/forms.py +++ b/django/contrib/localflavor/id/forms.py @@ -2,7 +2,7 @@ ID-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re import time @@ -34,7 +34,7 @@ class IDPostCodeField(Field): def clean(self, value): super(IDPostCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() if not postcode_re.search(value): @@ -47,7 +47,7 @@ class IDPostCodeField(Field): if value[0] == '1' and value[4] != '0': raise ValidationError(self.error_messages['invalid']) - return u'%s' % (value, ) + return '%s' % (value, ) class IDProvinceSelect(Select): @@ -75,7 +75,7 @@ class IDPhoneNumberField(Field): def clean(self, value): super(IDPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' phone_number = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) @@ -117,7 +117,7 @@ class IDLicensePlateField(Field): from django.contrib.localflavor.id.id_choices import LICENSE_PLATE_PREFIX_CHOICES super(IDLicensePlateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' plate_number = re.sub(r'\s+', ' ', smart_unicode(value.strip())).upper() @@ -179,7 +179,7 @@ class IDNationalIdentityNumberField(Field): def clean(self, value): super(IDNationalIdentityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub(r'[\s.]', '', smart_unicode(value)) diff --git a/django/contrib/localflavor/il/forms.py b/django/contrib/localflavor/il/forms.py index a14358737e..de6ba6b23d 100644 --- a/django/contrib/localflavor/il/forms.py +++ b/django/contrib/localflavor/il/forms.py @@ -1,6 +1,7 @@ """ Israeli-specific form helpers """ +from __future__ import unicode_literals import re from django.core.exceptions import ValidationError @@ -28,7 +29,7 @@ class ILPostalCodeField(RegexField): """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX'), + 'invalid': _('Enter a postal code in the format XXXXX'), } def __init__(self, *args, **kwargs): @@ -47,14 +48,14 @@ class ILIDNumberField(Field): """ default_error_messages = { - 'invalid': _(u'Enter a valid ID number.'), + 'invalid': _('Enter a valid ID number.'), } def clean(self, value): value = super(ILIDNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = id_number_re.match(value) if not match: diff --git a/django/contrib/localflavor/in_/forms.py b/django/contrib/localflavor/in_/forms.py index 11011e1df2..b62ec7bdb2 100644 --- a/django/contrib/localflavor/in_/forms.py +++ b/django/contrib/localflavor/in_/forms.py @@ -2,7 +2,7 @@ India-specific Form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -38,7 +38,7 @@ phone_digits_re = re.compile(r""" class INZipCodeField(RegexField): default_error_messages = { - 'invalid': _(u'Enter a zip code in the format XXXXXX or XXX XXX.'), + 'invalid': _('Enter a zip code in the format XXXXXX or XXX XXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -48,7 +48,7 @@ class INZipCodeField(RegexField): def clean(self, value): super(INZipCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # Convert to "NNNNNN" if "NNN NNN" given value = re.sub(r'^(\d{3})\s(\d{3})$', r'\1\2', value) return value @@ -61,13 +61,13 @@ class INStateField(Field): registration abbreviation for the given state or union territory """ default_error_messages = { - 'invalid': _(u'Enter an Indian state or territory.'), + 'invalid': _('Enter an Indian state or territory.'), } def clean(self, value): super(INStateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().lower() except AttributeError: @@ -106,10 +106,10 @@ class INPhoneNumberField(CharField): def clean(self, value): super(INPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = smart_unicode(value) m = phone_digits_re.match(value) if m: - return u'%s' % (value) + return '%s' % (value) raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py index ca7bd5004e..7af9f51cfb 100644 --- a/django/contrib/localflavor/is_/forms.py +++ b/django/contrib/localflavor/is_/forms.py @@ -2,7 +2,7 @@ Iceland specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.is_.is_postalcodes import IS_POSTALCODES from django.core.validators import EMPTY_VALUES @@ -20,7 +20,7 @@ class ISIdNumberField(RegexField): """ default_error_messages = { 'invalid': _('Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.'), - 'checksum': _(u'The Icelandic identification number is not valid.'), + 'checksum': _('The Icelandic identification number is not valid.'), } def __init__(self, max_length=11, min_length=10, *args, **kwargs): @@ -31,7 +31,7 @@ class ISIdNumberField(RegexField): value = super(ISIdNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = self._canonify(value) if self._validate(value): @@ -73,7 +73,7 @@ class ISPhoneNumberField(RegexField): value = super(ISPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' return value.replace('-', '').replace(' ', '') diff --git a/django/contrib/localflavor/is_/is_postalcodes.py b/django/contrib/localflavor/is_/is_postalcodes.py index 4feca9c013..f1f3357c1c 100644 --- a/django/contrib/localflavor/is_/is_postalcodes.py +++ b/django/contrib/localflavor/is_/is_postalcodes.py @@ -1,151 +1,152 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals IS_POSTALCODES = ( - ('101', u'101 Reykjavík'), - ('103', u'103 Reykjavík'), - ('104', u'104 Reykjavík'), - ('105', u'105 Reykjavík'), - ('107', u'107 Reykjavík'), - ('108', u'108 Reykjavík'), - ('109', u'109 Reykjavík'), - ('110', u'110 Reykjavík'), - ('111', u'111 Reykjavík'), - ('112', u'112 Reykjavík'), - ('113', u'113 Reykjavík'), - ('116', u'116 Kjalarnes'), - ('121', u'121 Reykjavík'), - ('123', u'123 Reykjavík'), - ('124', u'124 Reykjavík'), - ('125', u'125 Reykjavík'), - ('127', u'127 Reykjavík'), - ('128', u'128 Reykjavík'), - ('129', u'129 Reykjavík'), - ('130', u'130 Reykjavík'), - ('132', u'132 Reykjavík'), - ('150', u'150 Reykjavík'), - ('155', u'155 Reykjavík'), - ('170', u'170 Seltjarnarnes'), - ('172', u'172 Seltjarnarnes'), - ('190', u'190 Vogar'), - ('200', u'200 Kópavogur'), - ('201', u'201 Kópavogur'), - ('202', u'202 Kópavogur'), - ('203', u'203 Kópavogur'), - ('210', u'210 Garðabær'), - ('212', u'212 Garðabær'), - ('220', u'220 Hafnarfjörður'), - ('221', u'221 Hafnarfjörður'), - ('222', u'222 Hafnarfjörður'), - ('225', u'225 Álftanes'), - ('230', u'230 Reykjanesbær'), - ('232', u'232 Reykjanesbær'), - ('233', u'233 Reykjanesbær'), - ('235', u'235 Keflavíkurflugvöllur'), - ('240', u'240 Grindavík'), - ('245', u'245 Sandgerði'), - ('250', u'250 Garður'), - ('260', u'260 Reykjanesbær'), - ('270', u'270 Mosfellsbær'), - ('300', u'300 Akranes'), - ('301', u'301 Akranes'), - ('302', u'302 Akranes'), - ('310', u'310 Borgarnes'), - ('311', u'311 Borgarnes'), - ('320', u'320 Reykholt í Borgarfirði'), - ('340', u'340 Stykkishólmur'), - ('345', u'345 Flatey á Breiðafirði'), - ('350', u'350 Grundarfjörður'), - ('355', u'355 Ólafsvík'), - ('356', u'356 Snæfellsbær'), - ('360', u'360 Hellissandur'), - ('370', u'370 Búðardalur'), - ('371', u'371 Búðardalur'), - ('380', u'380 Reykhólahreppur'), - ('400', u'400 Ísafjörður'), - ('401', u'401 Ísafjörður'), - ('410', u'410 Hnífsdalur'), - ('415', u'415 Bolungarvík'), - ('420', u'420 Súðavík'), - ('425', u'425 Flateyri'), - ('430', u'430 Suðureyri'), - ('450', u'450 Patreksfjörður'), - ('451', u'451 Patreksfjörður'), - ('460', u'460 Tálknafjörður'), - ('465', u'465 Bíldudalur'), - ('470', u'470 Þingeyri'), - ('471', u'471 Þingeyri'), - ('500', u'500 Staður'), - ('510', u'510 Hólmavík'), - ('512', u'512 Hólmavík'), - ('520', u'520 Drangsnes'), - ('522', u'522 Kjörvogur'), - ('523', u'523 Bær'), - ('524', u'524 Norðurfjörður'), - ('530', u'530 Hvammstangi'), - ('531', u'531 Hvammstangi'), - ('540', u'540 Blönduós'), - ('541', u'541 Blönduós'), - ('545', u'545 Skagaströnd'), - ('550', u'550 Sauðárkrókur'), - ('551', u'551 Sauðárkrókur'), - ('560', u'560 Varmahlíð'), - ('565', u'565 Hofsós'), - ('566', u'566 Hofsós'), - ('570', u'570 Fljót'), - ('580', u'580 Siglufjörður'), - ('600', u'600 Akureyri'), - ('601', u'601 Akureyri'), - ('602', u'602 Akureyri'), - ('603', u'603 Akureyri'), - ('610', u'610 Grenivík'), - ('611', u'611 Grímsey'), - ('620', u'620 Dalvík'), - ('621', u'621 Dalvík'), - ('625', u'625 Ólafsfjörður'), - ('630', u'630 Hrísey'), - ('640', u'640 Húsavík'), - ('641', u'641 Húsavík'), - ('645', u'645 Fosshóll'), - ('650', u'650 Laugar'), - ('660', u'660 Mývatn'), - ('670', u'670 Kópasker'), - ('671', u'671 Kópasker'), - ('675', u'675 Raufarhöfn'), - ('680', u'680 Þórshöfn'), - ('681', u'681 Þórshöfn'), - ('685', u'685 Bakkafjörður'), - ('690', u'690 Vopnafjörður'), - ('700', u'700 Egilsstaðir'), - ('701', u'701 Egilsstaðir'), - ('710', u'710 Seyðisfjörður'), - ('715', u'715 Mjóifjörður'), - ('720', u'720 Borgarfjörður eystri'), - ('730', u'730 Reyðarfjörður'), - ('735', u'735 Eskifjörður'), - ('740', u'740 Neskaupstaður'), - ('750', u'750 Fáskrúðsfjörður'), - ('755', u'755 Stöðvarfjörður'), - ('760', u'760 Breiðdalsvík'), - ('765', u'765 Djúpivogur'), - ('780', u'780 Höfn í Hornafirði'), - ('781', u'781 Höfn í Hornafirði'), - ('785', u'785 Öræfi'), - ('800', u'800 Selfoss'), - ('801', u'801 Selfoss'), - ('802', u'802 Selfoss'), - ('810', u'810 Hveragerði'), - ('815', u'815 Þorlákshöfn'), - ('820', u'820 Eyrarbakki'), - ('825', u'825 Stokkseyri'), - ('840', u'840 Laugarvatn'), - ('845', u'845 Flúðir'), - ('850', u'850 Hella'), - ('851', u'851 Hella'), - ('860', u'860 Hvolsvöllur'), - ('861', u'861 Hvolsvöllur'), - ('870', u'870 Vík'), - ('871', u'871 Vík'), - ('880', u'880 Kirkjubæjarklaustur'), - ('900', u'900 Vestmannaeyjar'), - ('902', u'902 Vestmannaeyjar') + ('101', '101 Reykjavík'), + ('103', '103 Reykjavík'), + ('104', '104 Reykjavík'), + ('105', '105 Reykjavík'), + ('107', '107 Reykjavík'), + ('108', '108 Reykjavík'), + ('109', '109 Reykjavík'), + ('110', '110 Reykjavík'), + ('111', '111 Reykjavík'), + ('112', '112 Reykjavík'), + ('113', '113 Reykjavík'), + ('116', '116 Kjalarnes'), + ('121', '121 Reykjavík'), + ('123', '123 Reykjavík'), + ('124', '124 Reykjavík'), + ('125', '125 Reykjavík'), + ('127', '127 Reykjavík'), + ('128', '128 Reykjavík'), + ('129', '129 Reykjavík'), + ('130', '130 Reykjavík'), + ('132', '132 Reykjavík'), + ('150', '150 Reykjavík'), + ('155', '155 Reykjavík'), + ('170', '170 Seltjarnarnes'), + ('172', '172 Seltjarnarnes'), + ('190', '190 Vogar'), + ('200', '200 Kópavogur'), + ('201', '201 Kópavogur'), + ('202', '202 Kópavogur'), + ('203', '203 Kópavogur'), + ('210', '210 Garðabær'), + ('212', '212 Garðabær'), + ('220', '220 Hafnarfjörður'), + ('221', '221 Hafnarfjörður'), + ('222', '222 Hafnarfjörður'), + ('225', '225 Álftanes'), + ('230', '230 Reykjanesbær'), + ('232', '232 Reykjanesbær'), + ('233', '233 Reykjanesbær'), + ('235', '235 Keflavíkurflugvöllur'), + ('240', '240 Grindavík'), + ('245', '245 Sandgerði'), + ('250', '250 Garður'), + ('260', '260 Reykjanesbær'), + ('270', '270 Mosfellsbær'), + ('300', '300 Akranes'), + ('301', '301 Akranes'), + ('302', '302 Akranes'), + ('310', '310 Borgarnes'), + ('311', '311 Borgarnes'), + ('320', '320 Reykholt í Borgarfirði'), + ('340', '340 Stykkishólmur'), + ('345', '345 Flatey á Breiðafirði'), + ('350', '350 Grundarfjörður'), + ('355', '355 Ólafsvík'), + ('356', '356 Snæfellsbær'), + ('360', '360 Hellissandur'), + ('370', '370 Búðardalur'), + ('371', '371 Búðardalur'), + ('380', '380 Reykhólahreppur'), + ('400', '400 Ísafjörður'), + ('401', '401 Ísafjörður'), + ('410', '410 Hnífsdalur'), + ('415', '415 Bolungarvík'), + ('420', '420 Súðavík'), + ('425', '425 Flateyri'), + ('430', '430 Suðureyri'), + ('450', '450 Patreksfjörður'), + ('451', '451 Patreksfjörður'), + ('460', '460 Tálknafjörður'), + ('465', '465 Bíldudalur'), + ('470', '470 Þingeyri'), + ('471', '471 Þingeyri'), + ('500', '500 Staður'), + ('510', '510 Hólmavík'), + ('512', '512 Hólmavík'), + ('520', '520 Drangsnes'), + ('522', '522 Kjörvogur'), + ('523', '523 Bær'), + ('524', '524 Norðurfjörður'), + ('530', '530 Hvammstangi'), + ('531', '531 Hvammstangi'), + ('540', '540 Blönduós'), + ('541', '541 Blönduós'), + ('545', '545 Skagaströnd'), + ('550', '550 Sauðárkrókur'), + ('551', '551 Sauðárkrókur'), + ('560', '560 Varmahlíð'), + ('565', '565 Hofsós'), + ('566', '566 Hofsós'), + ('570', '570 Fljót'), + ('580', '580 Siglufjörður'), + ('600', '600 Akureyri'), + ('601', '601 Akureyri'), + ('602', '602 Akureyri'), + ('603', '603 Akureyri'), + ('610', '610 Grenivík'), + ('611', '611 Grímsey'), + ('620', '620 Dalvík'), + ('621', '621 Dalvík'), + ('625', '625 Ólafsfjörður'), + ('630', '630 Hrísey'), + ('640', '640 Húsavík'), + ('641', '641 Húsavík'), + ('645', '645 Fosshóll'), + ('650', '650 Laugar'), + ('660', '660 Mývatn'), + ('670', '670 Kópasker'), + ('671', '671 Kópasker'), + ('675', '675 Raufarhöfn'), + ('680', '680 Þórshöfn'), + ('681', '681 Þórshöfn'), + ('685', '685 Bakkafjörður'), + ('690', '690 Vopnafjörður'), + ('700', '700 Egilsstaðir'), + ('701', '701 Egilsstaðir'), + ('710', '710 Seyðisfjörður'), + ('715', '715 Mjóifjörður'), + ('720', '720 Borgarfjörður eystri'), + ('730', '730 Reyðarfjörður'), + ('735', '735 Eskifjörður'), + ('740', '740 Neskaupstaður'), + ('750', '750 Fáskrúðsfjörður'), + ('755', '755 Stöðvarfjörður'), + ('760', '760 Breiðdalsvík'), + ('765', '765 Djúpivogur'), + ('780', '780 Höfn í Hornafirði'), + ('781', '781 Höfn í Hornafirði'), + ('785', '785 Öræfi'), + ('800', '800 Selfoss'), + ('801', '801 Selfoss'), + ('802', '802 Selfoss'), + ('810', '810 Hveragerði'), + ('815', '815 Þorlákshöfn'), + ('820', '820 Eyrarbakki'), + ('825', '825 Stokkseyri'), + ('840', '840 Laugarvatn'), + ('845', '845 Flúðir'), + ('850', '850 Hella'), + ('851', '851 Hella'), + ('860', '860 Hvolsvöllur'), + ('861', '861 Hvolsvöllur'), + ('870', '870 Vík'), + ('871', '871 Vík'), + ('880', '880 Kirkjubæjarklaustur'), + ('900', '900 Vestmannaeyjar'), + ('902', '902 Vestmannaeyjar') ) diff --git a/django/contrib/localflavor/it/forms.py b/django/contrib/localflavor/it/forms.py index 0060b486bd..60b1eff951 100644 --- a/django/contrib/localflavor/it/forms.py +++ b/django/contrib/localflavor/it/forms.py @@ -2,7 +2,7 @@ IT-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -45,7 +45,7 @@ class ITSocialSecurityNumberField(RegexField): 'Informazioni sulla codificazione delle persone fisiche'. """ default_error_messages = { - 'invalid': _(u'Enter a valid Social Security number.'), + 'invalid': _('Enter a valid Social Security number.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -55,8 +55,8 @@ class ITSocialSecurityNumberField(RegexField): def clean(self, value): value = super(ITSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('\s', u'', value).upper() + return '' + value = re.sub('\s', '', value).upper() try: check_digit = ssn_check_digit(value) except ValueError: @@ -70,13 +70,13 @@ class ITVatNumberField(Field): A form field that validates Italian VAT numbers (partita IVA). """ default_error_messages = { - 'invalid': _(u'Enter a valid VAT number.'), + 'invalid': _('Enter a valid VAT number.'), } def clean(self, value): value = super(ITVatNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: vat_number = int(value) except ValueError: diff --git a/django/contrib/localflavor/it/it_province.py b/django/contrib/localflavor/it/it_province.py index dcaad98c63..5aad1611dd 100644 --- a/django/contrib/localflavor/it/it_province.py +++ b/django/contrib/localflavor/it/it_province.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -* +from __future__ import unicode_literals PROVINCE_CHOICES = ( ('AG', 'Agrigento'), @@ -45,7 +46,7 @@ PROVINCE_CHOICES = ( ('IM', 'Imperia'), ('IS', 'Isernia'), ('SP', 'La Spezia'), - ('AQ', u'L’Aquila'), + ('AQ', 'L’Aquila'), ('LT', 'Latina'), ('LE', 'Lecce'), ('LC', 'Lecco'), diff --git a/django/contrib/localflavor/it/it_region.py b/django/contrib/localflavor/it/it_region.py index 0700b46ea8..e12a1e731b 100644 --- a/django/contrib/localflavor/it/it_region.py +++ b/django/contrib/localflavor/it/it_region.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -* +from __future__ import unicode_literals REGION_CHOICES = ( ('ABR', 'Abruzzo'), @@ -19,6 +20,6 @@ REGION_CHOICES = ( ('TOS', 'Toscana'), ('TAA', 'Trentino-Alto Adige'), ('UMB', 'Umbria'), - ('VAO', u'Valle d’Aosta'), + ('VAO', 'Valle d’Aosta'), ('VEN', 'Veneto'), ) diff --git a/django/contrib/localflavor/it/util.py b/django/contrib/localflavor/it/util.py index c162ff7eff..ec1b7e3f83 100644 --- a/django/contrib/localflavor/it/util.py +++ b/django/contrib/localflavor/it/util.py @@ -1,4 +1,4 @@ -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_unicode def ssn_check_digit(value): "Calculate Italian social security number check digit." @@ -34,7 +34,7 @@ def ssn_check_digit(value): def vat_number_check_digit(vat_number): "Calculate Italian VAT number check digit." - normalized_vat_number = smart_str(vat_number).zfill(10) + normalized_vat_number = smart_unicode(vat_number).zfill(10) total = 0 for i in range(0, 10, 2): total += int(normalized_vat_number[i]) diff --git a/django/contrib/localflavor/kw/forms.py b/django/contrib/localflavor/kw/forms.py index e671408ec8..2c2b023e70 100644 --- a/django/contrib/localflavor/kw/forms.py +++ b/django/contrib/localflavor/kw/forms.py @@ -1,6 +1,8 @@ """ Kuwait-specific Form helpers """ +from __future__ import unicode_literals + import re from datetime import date @@ -40,7 +42,7 @@ class KWCivilIDNumberField(Field): def clean(self, value): super(KWCivilIDNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not re.match(r'^\d{12}$', value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/mk/forms.py b/django/contrib/localflavor/mk/forms.py index 33dbfc71a0..3189f0dec6 100644 --- a/django/contrib/localflavor/mk/forms.py +++ b/django/contrib/localflavor/mk/forms.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -15,14 +15,14 @@ class MKIdentityCardNumberField(RegexField): A Macedonian ID card number. Accepts both old and new format. """ default_error_messages = { - 'invalid': _(u'Identity card numbers must contain' + 'invalid': _('Identity card numbers must contain' ' either 4 to 7 digits or an uppercase letter and 7 digits.'), } def __init__(self, *args, **kwargs): kwargs['min_length'] = None kwargs['max_length'] = 8 - regex = ur'(^[A-Z]{1}\d{7}$)|(^\d{4,7}$)' + regex = r'(^[A-Z]{1}\d{7}$)|(^\d{4,7}$)' super(MKIdentityCardNumberField, self).__init__(regex, *args, **kwargs) @@ -54,9 +54,9 @@ class UMCNField(RegexField): * The last digit of the UMCN passes a checksum test """ default_error_messages = { - 'invalid': _(u'This field should contain exactly 13 digits.'), - 'date': _(u'The first 7 digits of the UMCN must represent a valid past date.'), - 'checksum': _(u'The UMCN is not valid.'), + 'invalid': _('This field should contain exactly 13 digits.'), + 'date': _('The first 7 digits of the UMCN must represent a valid past date.'), + 'checksum': _('The UMCN is not valid.'), } def __init__(self, *args, **kwargs): @@ -68,7 +68,7 @@ class UMCNField(RegexField): value = super(UMCNField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not self._validate_date_part(value): raise ValidationError(self.error_messages['date']) diff --git a/django/contrib/localflavor/mk/mk_choices.py b/django/contrib/localflavor/mk/mk_choices.py index d6d1efa049..fb705ca820 100644 --- a/django/contrib/localflavor/mk/mk_choices.py +++ b/django/contrib/localflavor/mk/mk_choices.py @@ -2,91 +2,93 @@ """ Macedonian municipalities per the reorganization from 2004. """ +from __future__ import unicode_literals + from django.utils.translation import ugettext_lazy as _ MK_MUNICIPALITIES = ( - ('AD', _(u'Aerodrom')), - ('AR', _(u'Aračinovo')), - ('BR', _(u'Berovo')), - ('TL', _(u'Bitola')), - ('BG', _(u'Bogdanci')), - ('VJ', _(u'Bogovinje')), - ('BS', _(u'Bosilovo')), - ('BN', _(u'Brvenica')), - ('BU', _(u'Butel')), - ('VA', _(u'Valandovo')), - ('VL', _(u'Vasilevo')), - ('VV', _(u'Vevčani')), - ('VE', _(u'Veles')), - ('NI', _(u'Vinica')), - ('VC', _(u'Vraneštica')), - ('VH', _(u'Vrapčište')), - ('GB', _(u'Gazi Baba')), - ('GV', _(u'Gevgelija')), - ('GT', _(u'Gostivar')), - ('GR', _(u'Gradsko')), - ('DB', _(u'Debar')), - ('DA', _(u'Debarca')), - ('DL', _(u'Delčevo')), - ('DK', _(u'Demir Kapija')), - ('DM', _(u'Demir Hisar')), - ('DE', _(u'Dolneni')), - ('DR', _(u'Drugovo')), - ('GP', _(u'Gjorče Petrov')), - ('ZE', _(u'Želino')), - ('ZA', _(u'Zajas')), - ('ZK', _(u'Zelenikovo')), - ('ZR', _(u'Zrnovci')), - ('IL', _(u'Ilinden')), - ('JG', _(u'Jegunovce')), - ('AV', _(u'Kavadarci')), - ('KB', _(u'Karbinci')), - ('KX', _(u'Karpoš')), - ('VD', _(u'Kisela Voda')), - ('KH', _(u'Kičevo')), - ('KN', _(u'Konče')), - ('OC', _(u'Koćani')), - ('KY', _(u'Kratovo')), - ('KZ', _(u'Kriva Palanka')), - ('KG', _(u'Krivogaštani')), - ('KS', _(u'Kruševo')), - ('UM', _(u'Kumanovo')), - ('LI', _(u'Lipkovo')), - ('LO', _(u'Lozovo')), - ('MR', _(u'Mavrovo i Rostuša')), - ('MK', _(u'Makedonska Kamenica')), - ('MD', _(u'Makedonski Brod')), - ('MG', _(u'Mogila')), - ('NG', _(u'Negotino')), - ('NV', _(u'Novaci')), - ('NS', _(u'Novo Selo')), - ('OS', _(u'Oslomej')), - ('OD', _(u'Ohrid')), - ('PE', _(u'Petrovec')), - ('PH', _(u'Pehčevo')), - ('PN', _(u'Plasnica')), - ('PP', _(u'Prilep')), - ('PT', _(u'Probištip')), - ('RV', _(u'Radoviš')), - ('RN', _(u'Rankovce')), - ('RE', _(u'Resen')), - ('RO', _(u'Rosoman')), - ('AJ', _(u'Saraj')), - ('SL', _(u'Sveti Nikole')), - ('SS', _(u'Sopište')), - ('SD', _(u'Star Dojran')), - ('NA', _(u'Staro Nagoričane')), - ('UG', _(u'Struga')), - ('RU', _(u'Strumica')), - ('SU', _(u'Studeničani')), - ('TR', _(u'Tearce')), - ('ET', _(u'Tetovo')), - ('CE', _(u'Centar')), - ('CZ', _(u'Centar-Župa')), - ('CI', _(u'Čair')), - ('CA', _(u'Čaška')), - ('CH', _(u'Češinovo-Obleševo')), - ('CS', _(u'Čučer-Sandevo')), - ('ST', _(u'Štip')), - ('SO', _(u'Šuto Orizari')), + ('AD', _('Aerodrom')), + ('AR', _('Aračinovo')), + ('BR', _('Berovo')), + ('TL', _('Bitola')), + ('BG', _('Bogdanci')), + ('VJ', _('Bogovinje')), + ('BS', _('Bosilovo')), + ('BN', _('Brvenica')), + ('BU', _('Butel')), + ('VA', _('Valandovo')), + ('VL', _('Vasilevo')), + ('VV', _('Vevčani')), + ('VE', _('Veles')), + ('NI', _('Vinica')), + ('VC', _('Vraneštica')), + ('VH', _('Vrapčište')), + ('GB', _('Gazi Baba')), + ('GV', _('Gevgelija')), + ('GT', _('Gostivar')), + ('GR', _('Gradsko')), + ('DB', _('Debar')), + ('DA', _('Debarca')), + ('DL', _('Delčevo')), + ('DK', _('Demir Kapija')), + ('DM', _('Demir Hisar')), + ('DE', _('Dolneni')), + ('DR', _('Drugovo')), + ('GP', _('Gjorče Petrov')), + ('ZE', _('Želino')), + ('ZA', _('Zajas')), + ('ZK', _('Zelenikovo')), + ('ZR', _('Zrnovci')), + ('IL', _('Ilinden')), + ('JG', _('Jegunovce')), + ('AV', _('Kavadarci')), + ('KB', _('Karbinci')), + ('KX', _('Karpoš')), + ('VD', _('Kisela Voda')), + ('KH', _('Kičevo')), + ('KN', _('Konče')), + ('OC', _('Koćani')), + ('KY', _('Kratovo')), + ('KZ', _('Kriva Palanka')), + ('KG', _('Krivogaštani')), + ('KS', _('Kruševo')), + ('UM', _('Kumanovo')), + ('LI', _('Lipkovo')), + ('LO', _('Lozovo')), + ('MR', _('Mavrovo i Rostuša')), + ('MK', _('Makedonska Kamenica')), + ('MD', _('Makedonski Brod')), + ('MG', _('Mogila')), + ('NG', _('Negotino')), + ('NV', _('Novaci')), + ('NS', _('Novo Selo')), + ('OS', _('Oslomej')), + ('OD', _('Ohrid')), + ('PE', _('Petrovec')), + ('PH', _('Pehčevo')), + ('PN', _('Plasnica')), + ('PP', _('Prilep')), + ('PT', _('Probištip')), + ('RV', _('Radoviš')), + ('RN', _('Rankovce')), + ('RE', _('Resen')), + ('RO', _('Rosoman')), + ('AJ', _('Saraj')), + ('SL', _('Sveti Nikole')), + ('SS', _('Sopište')), + ('SD', _('Star Dojran')), + ('NA', _('Staro Nagoričane')), + ('UG', _('Struga')), + ('RU', _('Strumica')), + ('SU', _('Studeničani')), + ('TR', _('Tearce')), + ('ET', _('Tetovo')), + ('CE', _('Centar')), + ('CZ', _('Centar-Župa')), + ('CI', _('Čair')), + ('CA', _('Čaška')), + ('CH', _('Češinovo-Obleševo')), + ('CS', _('Čučer-Sandevo')), + ('ST', _('Štip')), + ('SO', _('Šuto Orizari')), ) diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py index deecb4ea41..2dcf17d26c 100644 --- a/django/contrib/localflavor/mx/forms.py +++ b/django/contrib/localflavor/mx/forms.py @@ -2,6 +2,7 @@ """ Mexican-specific form helpers. """ +from __future__ import unicode_literals import re from django.forms import ValidationError @@ -19,12 +20,12 @@ document described in the next link: """ RFC_INCONVENIENT_WORDS = [ - u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO', u'CAKA', u'CAKO', - u'COGE', u'COJA', u'COJE', u'COJI', u'COJO', u'CULO', u'FETO', u'GUEY', - u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KOGE', u'KOJO', u'KAKA', - u'KULO', u'MAME', u'MAMO', u'MEAR', u'MEAS', u'MEON', u'MION', u'MOCO', - u'MULA', u'PEDA', u'PEDO', u'PENE', u'PUTA', u'PUTO', u'QULO', u'RATA', - u'RUIN', + 'BUEI', 'BUEY', 'CACA', 'CACO', 'CAGA', 'CAGO', 'CAKA', 'CAKO', + 'COGE', 'COJA', 'COJE', 'COJI', 'COJO', 'CULO', 'FETO', 'GUEY', + 'JOTO', 'KACA', 'KACO', 'KAGA', 'KAGO', 'KOGE', 'KOJO', 'KAKA', + 'KULO', 'MAME', 'MAMO', 'MEAR', 'MEAS', 'MEON', 'MION', 'MOCO', + 'MULA', 'PEDA', 'PEDO', 'PENE', 'PUTA', 'PUTO', 'QULO', 'RATA', + 'RUIN', ] """ @@ -33,17 +34,17 @@ document described in the next link: http://portal.veracruz.gob.mx/pls/portal/url/ITEM/444112558A57C6E0E040A8C02E00695C """ CURP_INCONVENIENT_WORDS = [ - u'BACA', u'BAKA', u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO', - u'CAKA', u'CAKO', u'COGE', u'COGI', u'COJA', u'COJE', u'COJI', u'COJO', - u'COLA', u'CULO', u'FALO', u'FETO', u'GETA', u'GUEI', u'GUEY', u'JETA', - u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KAKA', u'KAKO', u'KOGE', - u'KOGI', u'KOJA', u'KOJE', u'KOJI', u'KOJO', u'KOLA', u'KULO', u'LILO', - u'LOCA', u'LOCO', u'LOKA', u'LOKO', u'MAME', u'MAMO', u'MEAR', u'MEAS', - u'MEON', u'MIAR', u'MION', u'MOCO', u'MOKO', u'MULA', u'MULO', u'NACA', - u'NACO', u'PEDA', u'PEDO', u'PENE', u'PIPI', u'PITO', u'POPO', u'PUTA', - u'PUTO', u'QULO', u'RATA', u'ROBA', u'ROBE', u'ROBO', u'RUIN', u'SENO', - u'TETA', u'VACA', u'VAGA', u'VAGO', u'VAKA', u'VUEI', u'VUEY', u'WUEI', - u'WUEY', + 'BACA', 'BAKA', 'BUEI', 'BUEY', 'CACA', 'CACO', 'CAGA', 'CAGO', + 'CAKA', 'CAKO', 'COGE', 'COGI', 'COJA', 'COJE', 'COJI', 'COJO', + 'COLA', 'CULO', 'FALO', 'FETO', 'GETA', 'GUEI', 'GUEY', 'JETA', + 'JOTO', 'KACA', 'KACO', 'KAGA', 'KAGO', 'KAKA', 'KAKO', 'KOGE', + 'KOGI', 'KOJA', 'KOJE', 'KOJI', 'KOJO', 'KOLA', 'KULO', 'LILO', + 'LOCA', 'LOCO', 'LOKA', 'LOKO', 'MAME', 'MAMO', 'MEAR', 'MEAS', + 'MEON', 'MIAR', 'MION', 'MOCO', 'MOKO', 'MULA', 'MULO', 'NACA', + 'NACO', 'PEDA', 'PEDO', 'PENE', 'PIPI', 'PITO', 'POPO', 'PUTA', + 'PUTO', 'QULO', 'RATA', 'ROBA', 'ROBE', 'ROBO', 'RUIN', 'SENO', + 'TETA', 'VACA', 'VAGA', 'VAGO', 'VAKA', 'VUEI', 'VUEY', 'WUEI', + 'WUEY', ] class MXStateSelect(Select): @@ -62,11 +63,11 @@ class MXZipCodeField(RegexField): http://en.wikipedia.org/wiki/List_of_postal_codes_in_Mexico """ default_error_messages = { - 'invalid': _(u'Enter a valid zip code in the format XXXXX.'), + 'invalid': _('Enter a valid zip code in the format XXXXX.'), } def __init__(self, *args, **kwargs): - zip_code_re = ur'^(0[1-9]|[1][0-6]|[2-9]\d)(\d{3})$' + zip_code_re = r'^(0[1-9]|[1][0-6]|[2-9]\d)(\d{3})$' super(MXZipCodeField, self).__init__(zip_code_re, *args, **kwargs) @@ -110,7 +111,7 @@ class MXRFCField(RegexField): } def __init__(self, min_length=9, max_length=13, *args, **kwargs): - rfc_re = re.compile(ur'^([A-Z&Ññ]{3}|[A-Z][AEIOU][A-Z]{2})%s([A-Z0-9]{2}[0-9A])?$' % DATE_RE, + rfc_re = re.compile(r'^([A-Z&Ññ]{3}|[A-Z][AEIOU][A-Z]{2})%s([A-Z0-9]{2}[0-9A])?$' % DATE_RE, re.IGNORECASE) super(MXRFCField, self).__init__(rfc_re, min_length=min_length, max_length=max_length, *args, **kwargs) @@ -118,7 +119,7 @@ class MXRFCField(RegexField): def clean(self, value): value = super(MXRFCField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.upper() if self._has_homoclave(value): if not value[-1] == self._checksum(value[:-1]): @@ -133,7 +134,7 @@ class MXRFCField(RegexField): since the current algorithm to calculate it had not been created for the first RFCs ever in Mexico. """ - rfc_without_homoclave_re = re.compile(ur'^[A-Z&Ññ]{3,4}%s$' % DATE_RE, + rfc_without_homoclave_re = re.compile(r'^[A-Z&Ññ]{3,4}%s$' % DATE_RE, re.IGNORECASE) return not rfc_without_homoclave_re.match(rfc) @@ -142,7 +143,7 @@ class MXRFCField(RegexField): More info about this procedure: www.sisi.org.mx/jspsi/documentos/2005/seguimiento/06101/0610100162005_065.doc """ - chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ-Ñ' + chars = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ-Ñ' if len(rfc) == 11: rfc = '-' + rfc @@ -150,9 +151,9 @@ class MXRFCField(RegexField): checksum = 11 - sum_ % 11 if checksum == 10: - return u'A' + return 'A' elif checksum == 11: - return u'0' + return '0' return unicode(checksum) @@ -187,13 +188,13 @@ class MXCURPField(RegexField): """ default_error_messages = { 'invalid': _('Enter a valid CURP.'), - 'invalid_checksum': _(u'Invalid checksum for CURP.'), + 'invalid_checksum': _('Invalid checksum for CURP.'), } def __init__(self, min_length=18, max_length=18, *args, **kwargs): states_re = r'(AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)' consonants_re = r'[B-DF-HJ-NP-TV-Z]' - curp_re = (ur'^[A-Z][AEIOU][A-Z]{2}%s[HM]%s%s{3}[0-9A-Z]\d$' % + curp_re = (r'^[A-Z][AEIOU][A-Z]{2}%s[HM]%s%s{3}[0-9A-Z]\d$' % (DATE_RE, states_re, consonants_re)) curp_re = re.compile(curp_re, re.IGNORECASE) super(MXCURPField, self).__init__(curp_re, min_length=min_length, @@ -202,7 +203,7 @@ class MXCURPField(RegexField): def clean(self, value): value = super(MXCURPField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.upper() if value[-1] != self._checksum(value[:-1]): raise ValidationError(self.default_error_messages['invalid_checksum']) @@ -211,13 +212,13 @@ class MXCURPField(RegexField): return value def _checksum(self, value): - chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ' + chars = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ' s = sum(i * chars.index(c) for i, c in zip(reversed(xrange(19)), value)) checksum = 10 - s % 10 if checksum == 10: - return u'0' + return '0' return unicode(checksum) def _has_inconvenient_word(self, curp): diff --git a/django/contrib/localflavor/mx/mx_states.py b/django/contrib/localflavor/mx/mx_states.py index 2aba63ef26..6ae08ccb12 100644 --- a/django/contrib/localflavor/mx/mx_states.py +++ b/django/contrib/localflavor/mx/mx_states.py @@ -5,41 +5,42 @@ A list of Mexican states for use as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ # All 31 states, plus the `Distrito Federal`. STATE_CHOICES = ( - ('AGU', _(u'Aguascalientes')), - ('BCN', _(u'Baja California')), - ('BCS', _(u'Baja California Sur')), - ('CAM', _(u'Campeche')), - ('CHH', _(u'Chihuahua')), - ('CHP', _(u'Chiapas')), - ('COA', _(u'Coahuila')), - ('COL', _(u'Colima')), - ('DIF', _(u'Distrito Federal')), - ('DUR', _(u'Durango')), - ('GRO', _(u'Guerrero')), - ('GUA', _(u'Guanajuato')), - ('HID', _(u'Hidalgo')), - ('JAL', _(u'Jalisco')), - ('MEX', _(u'Estado de México')), - ('MIC', _(u'Michoacán')), - ('MOR', _(u'Morelos')), - ('NAY', _(u'Nayarit')), - ('NLE', _(u'Nuevo León')), - ('OAX', _(u'Oaxaca')), - ('PUE', _(u'Puebla')), - ('QUE', _(u'Querétaro')), - ('ROO', _(u'Quintana Roo')), - ('SIN', _(u'Sinaloa')), - ('SLP', _(u'San Luis Potosí')), - ('SON', _(u'Sonora')), - ('TAB', _(u'Tabasco')), - ('TAM', _(u'Tamaulipas')), - ('TLA', _(u'Tlaxcala')), - ('VER', _(u'Veracruz')), - ('YUC', _(u'Yucatán')), - ('ZAC', _(u'Zacatecas')), + ('AGU', _('Aguascalientes')), + ('BCN', _('Baja California')), + ('BCS', _('Baja California Sur')), + ('CAM', _('Campeche')), + ('CHH', _('Chihuahua')), + ('CHP', _('Chiapas')), + ('COA', _('Coahuila')), + ('COL', _('Colima')), + ('DIF', _('Distrito Federal')), + ('DUR', _('Durango')), + ('GRO', _('Guerrero')), + ('GUA', _('Guanajuato')), + ('HID', _('Hidalgo')), + ('JAL', _('Jalisco')), + ('MEX', _('Estado de México')), + ('MIC', _('Michoacán')), + ('MOR', _('Morelos')), + ('NAY', _('Nayarit')), + ('NLE', _('Nuevo León')), + ('OAX', _('Oaxaca')), + ('PUE', _('Puebla')), + ('QUE', _('Querétaro')), + ('ROO', _('Quintana Roo')), + ('SIN', _('Sinaloa')), + ('SLP', _('San Luis Potosí')), + ('SON', _('Sonora')), + ('TAB', _('Tabasco')), + ('TAM', _('Tamaulipas')), + ('TLA', _('Tlaxcala')), + ('VER', _('Veracruz')), + ('YUC', _('Yucatán')), + ('ZAC', _('Zacatecas')), ) diff --git a/django/contrib/localflavor/nl/forms.py b/django/contrib/localflavor/nl/forms.py index 66900808c2..bdd769bd39 100644 --- a/django/contrib/localflavor/nl/forms.py +++ b/django/contrib/localflavor/nl/forms.py @@ -2,7 +2,7 @@ NL-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -29,7 +29,7 @@ class NLZipCodeField(Field): def clean(self, value): super(NLZipCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip().upper().replace(' ', '') if not pc_re.search(value): @@ -38,7 +38,7 @@ class NLZipCodeField(Field): if int(value[:4]) < 1000: raise ValidationError(self.error_messages['invalid']) - return u'%s %s' % (value[:4], value[4:]) + return '%s %s' % (value[:4], value[4:]) class NLProvinceSelect(Select): """ @@ -59,7 +59,7 @@ class NLPhoneNumberField(Field): def clean(self, value): super(NLPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' phone_nr = re.sub('[\-\s\(\)]', '', smart_unicode(value)) @@ -85,7 +85,7 @@ class NLSoFiNumberField(Field): def clean(self, value): super(NLSoFiNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not sofi_re.search(value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/no/forms.py b/django/contrib/localflavor/no/forms.py index 3e5e58bd1c..4bd780a312 100644 --- a/django/contrib/localflavor/no/forms.py +++ b/django/contrib/localflavor/no/forms.py @@ -2,7 +2,7 @@ Norwegian-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re import datetime @@ -36,13 +36,13 @@ class NOSocialSecurityNumber(Field): Algorithm is documented at http://no.wikipedia.org/wiki/Personnummer """ default_error_messages = { - 'invalid': _(u'Enter a valid Norwegian social security number.'), + 'invalid': _('Enter a valid Norwegian social security number.'), } def clean(self, value): super(NOSocialSecurityNumber, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not re.match(r'^\d{11}$', value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/no/no_municipalities.py b/django/contrib/localflavor/no/no_municipalities.py index d6bacda275..d84915caa2 100644 --- a/django/contrib/localflavor/no/no_municipalities.py +++ b/django/contrib/localflavor/no/no_municipalities.py @@ -6,27 +6,28 @@ in a formfield. This exists in this standalone file so that it's on ly imported into memory when explicitly needed. """ +from __future__ import unicode_literals MUNICIPALITY_CHOICES = ( - ('akershus', u'Akershus'), - ('austagder', u'Aust-Agder'), - ('buskerud', u'Buskerud'), - ('finnmark', u'Finnmark'), - ('hedmark', u'Hedmark'), - ('hordaland', u'Hordaland'), - ('janmayen', u'Jan Mayen'), - ('moreogromsdal', u'Møre og Romsdal'), - ('nordtrondelag', u'Nord-Trøndelag'), - ('nordland', u'Nordland'), - ('oppland', u'Oppland'), - ('oslo', u'Oslo'), - ('rogaland', u'Rogaland'), - ('sognogfjordane', u'Sogn og Fjordane'), - ('svalbard', u'Svalbard'), - ('sortrondelag', u'Sør-Trøndelag'), - ('telemark', u'Telemark'), - ('troms', u'Troms'), - ('vestagder', u'Vest-Agder'), - ('vestfold', u'Vestfold'), - ('ostfold', u'Østfold') + ('akershus', 'Akershus'), + ('austagder', 'Aust-Agder'), + ('buskerud', 'Buskerud'), + ('finnmark', 'Finnmark'), + ('hedmark', 'Hedmark'), + ('hordaland', 'Hordaland'), + ('janmayen', 'Jan Mayen'), + ('moreogromsdal', 'Møre og Romsdal'), + ('nordtrondelag', 'Nord-Trøndelag'), + ('nordland', 'Nordland'), + ('oppland', 'Oppland'), + ('oslo', 'Oslo'), + ('rogaland', 'Rogaland'), + ('sognogfjordane', 'Sogn og Fjordane'), + ('svalbard', 'Svalbard'), + ('sortrondelag', 'Sør-Trøndelag'), + ('telemark', 'Telemark'), + ('troms', 'Troms'), + ('vestagder', 'Vest-Agder'), + ('vestfold', 'Vestfold'), + ('ostfold', 'Østfold') ) diff --git a/django/contrib/localflavor/pe/forms.py b/django/contrib/localflavor/pe/forms.py index 0eca2b8ac7..5100bbf575 100644 --- a/django/contrib/localflavor/pe/forms.py +++ b/django/contrib/localflavor/pe/forms.py @@ -3,7 +3,7 @@ PE-specific Form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.pe.pe_region import REGION_CHOICES from django.core.validators import EMPTY_VALUES @@ -38,7 +38,7 @@ class PEDNIField(CharField): """ value = super(PEDNIField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not value.isdigit(): raise ValidationError(self.error_messages['invalid']) if len(value) != 8: @@ -66,7 +66,7 @@ class PERUCField(RegexField): """ value = super(PERUCField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not value.isdigit(): raise ValidationError(self.error_messages['invalid']) if len(value) != 11: diff --git a/django/contrib/localflavor/pe/pe_region.py b/django/contrib/localflavor/pe/pe_region.py index 9863bd3d15..9270bcecf1 100644 --- a/django/contrib/localflavor/pe/pe_region.py +++ b/django/contrib/localflavor/pe/pe_region.py @@ -5,31 +5,32 @@ A list of Peru regions as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals REGION_CHOICES = ( - ('AMA', u'Amazonas'), - ('ANC', u'Ancash'), - ('APU', u'Apurímac'), - ('ARE', u'Arequipa'), - ('AYA', u'Ayacucho'), - ('CAJ', u'Cajamarca'), - ('CAL', u'Callao'), - ('CUS', u'Cusco'), - ('HUV', u'Huancavelica'), - ('HUC', u'Huánuco'), - ('ICA', u'Ica'), - ('JUN', u'Junín'), - ('LAL', u'La Libertad'), - ('LAM', u'Lambayeque'), - ('LIM', u'Lima'), - ('LOR', u'Loreto'), - ('MDD', u'Madre de Dios'), - ('MOQ', u'Moquegua'), - ('PAS', u'Pasco'), - ('PIU', u'Piura'), - ('PUN', u'Puno'), - ('SAM', u'San Martín'), - ('TAC', u'Tacna'), - ('TUM', u'Tumbes'), - ('UCA', u'Ucayali'), + ('AMA', 'Amazonas'), + ('ANC', 'Ancash'), + ('APU', 'Apurímac'), + ('ARE', 'Arequipa'), + ('AYA', 'Ayacucho'), + ('CAJ', 'Cajamarca'), + ('CAL', 'Callao'), + ('CUS', 'Cusco'), + ('HUV', 'Huancavelica'), + ('HUC', 'Huánuco'), + ('ICA', 'Ica'), + ('JUN', 'Junín'), + ('LAL', 'La Libertad'), + ('LAM', 'Lambayeque'), + ('LIM', 'Lima'), + ('LOR', 'Loreto'), + ('MDD', 'Madre de Dios'), + ('MOQ', 'Moquegua'), + ('PAS', 'Pasco'), + ('PIU', 'Piura'), + ('PUN', 'Puno'), + ('SAM', 'San Martín'), + ('TAC', 'Tacna'), + ('TUM', 'Tumbes'), + ('UCA', 'Ucayali'), ) diff --git a/django/contrib/localflavor/pl/forms.py b/django/contrib/localflavor/pl/forms.py index 3e8d73f0f2..12d9f3d763 100644 --- a/django/contrib/localflavor/pl/forms.py +++ b/django/contrib/localflavor/pl/forms.py @@ -2,7 +2,7 @@ Polish-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -39,8 +39,8 @@ class PLPESELField(RegexField): The algorithm is documented at http://en.wikipedia.org/wiki/PESEL. """ default_error_messages = { - 'invalid': _(u'National Identification Number consists of 11 digits.'), - 'checksum': _(u'Wrong checksum for the National Identification Number.'), + 'invalid': _('National Identification Number consists of 11 digits.'), + 'checksum': _('Wrong checksum for the National Identification Number.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -50,10 +50,10 @@ class PLPESELField(RegexField): def clean(self, value): super(PLPESELField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -76,8 +76,8 @@ class PLNationalIDCardNumberField(RegexField): The algorithm is documented at http://en.wikipedia.org/wiki/Polish_identity_card. """ default_error_messages = { - 'invalid': _(u'National ID Card Number consists of 3 letters and 6 digits.'), - 'checksum': _(u'Wrong checksum for the National ID Card Number.'), + 'invalid': _('National ID Card Number consists of 3 letters and 6 digits.'), + 'checksum': _('Wrong checksum for the National ID Card Number.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -87,13 +87,13 @@ class PLNationalIDCardNumberField(RegexField): def clean(self,value): super(PLNationalIDCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.upper() if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -128,8 +128,8 @@ class PLNIPField(RegexField): http://wipos.p.lodz.pl/zylla/ut/nip-rego.html """ default_error_messages = { - 'invalid': _(u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX, XXX-XX-XX-XXX or XXXXXXXXXX.'), - 'checksum': _(u'Wrong checksum for the Tax Number (NIP).'), + 'invalid': _('Enter a tax number field (NIP) in the format XXX-XXX-XX-XX, XXX-XX-XX-XXX or XXXXXXXXXX.'), + 'checksum': _('Wrong checksum for the Tax Number (NIP).'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -139,11 +139,11 @@ class PLNIPField(RegexField): def clean(self,value): super(PLNIPField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub("[-]", "", value) if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -168,8 +168,8 @@ class PLREGONField(RegexField): See http://www.stat.gov.pl/bip/regon_ENG_HTML.htm for more information. """ default_error_messages = { - 'invalid': _(u'National Business Register Number (REGON) consists of 9 or 14 digits.'), - 'checksum': _(u'Wrong checksum for the National Business Register Number (REGON).'), + 'invalid': _('National Business Register Number (REGON) consists of 9 or 14 digits.'), + 'checksum': _('Wrong checksum for the National Business Register Number (REGON).'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -179,10 +179,10 @@ class PLREGONField(RegexField): def clean(self,value): super(PLREGONField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -209,7 +209,7 @@ class PLPostalCodeField(RegexField): Valid code is XX-XXX where X is digit. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XX-XXX.'), + 'invalid': _('Enter a postal code in the format XX-XXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): diff --git a/django/contrib/localflavor/pl/pl_administrativeunits.py b/django/contrib/localflavor/pl/pl_administrativeunits.py index 9777ea2b61..f5263f19d2 100644 --- a/django/contrib/localflavor/pl/pl_administrativeunits.py +++ b/django/contrib/localflavor/pl/pl_administrativeunits.py @@ -2,384 +2,385 @@ """ Polish administrative units as in http://pl.wikipedia.org/wiki/Podzia%C5%82_administracyjny_Polski """ +from __future__ import unicode_literals ADMINISTRATIVE_UNIT_CHOICES = ( - ('wroclaw', u'Wrocław'), - ('jeleniagora', u'Jelenia Góra'), - ('legnica', u'Legnica'), - ('boleslawiecki', u'bolesławiecki'), - ('dzierzoniowski', u'dzierżoniowski'), - ('glogowski', u'głogowski'), - ('gorowski', u'górowski'), - ('jaworski', u'jaworski'), - ('jeleniogorski', u'jeleniogórski'), - ('kamiennogorski', u'kamiennogórski'), - ('klodzki', u'kłodzki'), - ('legnicki', u'legnicki'), - ('lubanski', u'lubański'), - ('lubinski', u'lubiński'), - ('lwowecki', u'lwówecki'), - ('milicki', u'milicki'), - ('olesnicki', u'oleśnicki'), - ('olawski', u'oławski'), - ('polkowicki', u'polkowicki'), - ('strzelinski', u'strzeliński'), - ('sredzki', u'średzki'), - ('swidnicki', u'świdnicki'), - ('trzebnicki', u'trzebnicki'), - ('walbrzyski', u'wałbrzyski'), - ('wolowski', u'wołowski'), - ('wroclawski', u'wrocławski'), - ('zabkowicki', u'ząbkowicki'), - ('zgorzelecki', u'zgorzelecki'), - ('zlotoryjski', u'złotoryjski'), - ('bydgoszcz', u'Bydgoszcz'), - ('torun', u'Toruń'), - ('wloclawek', u'Włocławek'), - ('grudziadz', u'Grudziądz'), - ('aleksandrowski', u'aleksandrowski'), - ('brodnicki', u'brodnicki'), - ('bydgoski', u'bydgoski'), - ('chelminski', u'chełmiński'), - ('golubsko-dobrzynski', u'golubsko-dobrzyński'), - ('grudziadzki', u'grudziądzki'), - ('inowroclawski', u'inowrocławski'), - ('lipnowski', u'lipnowski'), - ('mogilenski', u'mogileński'), - ('nakielski', u'nakielski'), - ('radziejowski', u'radziejowski'), - ('rypinski', u'rypiński'), - ('sepolenski', u'sępoleński'), - ('swiecki', u'świecki'), - ('torunski', u'toruński'), - ('tucholski', u'tucholski'), - ('wabrzeski', u'wąbrzeski'), - ('wloclawski', u'wrocławski'), - ('zninski', u'źniński'), - ('lublin', u'Lublin'), - ('biala-podlaska', u'Biała Podlaska'), - ('chelm', u'Chełm'), - ('zamosc', u'Zamość'), - ('bialski', u'bialski'), - ('bilgorajski', u'biłgorajski'), - ('chelmski', u'chełmski'), - ('hrubieszowski', u'hrubieszowski'), - ('janowski', u'janowski'), - ('krasnostawski', u'krasnostawski'), - ('krasnicki', u'kraśnicki'), - ('lubartowski', u'lubartowski'), - ('lubelski', u'lubelski'), - ('leczynski', u'łęczyński'), - ('lukowski', u'łukowski'), - ('opolski', u'opolski'), - ('parczewski', u'parczewski'), - ('pulawski', u'puławski'), - ('radzynski', u'radzyński'), - ('rycki', u'rycki'), - ('swidnicki', u'świdnicki'), - ('tomaszowski', u'tomaszowski'), - ('wlodawski', u'włodawski'), - ('zamojski', u'zamojski'), - ('gorzow-wielkopolski', u'Gorzów Wielkopolski'), - ('zielona-gora', u'Zielona Góra'), - ('gorzowski', u'gorzowski'), - ('krosnienski', u'krośnieński'), - ('miedzyrzecki', u'międzyrzecki'), - ('nowosolski', u'nowosolski'), - ('slubicki', u'słubicki'), - ('strzelecko-drezdenecki', u'strzelecko-drezdenecki'), - ('sulecinski', u'suleńciński'), - ('swiebodzinski', u'świebodziński'), - ('wschowski', u'wschowski'), - ('zielonogorski', u'zielonogórski'), - ('zaganski', u'żagański'), - ('zarski', u'żarski'), - ('lodz', u'Łódź'), - ('piotrkow-trybunalski', u'Piotrków Trybunalski'), - ('skierniewice', u'Skierniewice'), - ('belchatowski', u'bełchatowski'), - ('brzezinski', u'brzeziński'), - ('kutnowski', u'kutnowski'), - ('laski', u'łaski'), - ('leczycki', u'łęczycki'), - ('lowicki', u'łowicki'), - ('lodzki wschodni', u'łódzki wschodni'), - ('opoczynski', u'opoczyński'), - ('pabianicki', u'pabianicki'), - ('pajeczanski', u'pajęczański'), - ('piotrkowski', u'piotrkowski'), - ('poddebicki', u'poddębicki'), - ('radomszczanski', u'radomszczański'), - ('rawski', u'rawski'), - ('sieradzki', u'sieradzki'), - ('skierniewicki', u'skierniewicki'), - ('tomaszowski', u'tomaszowski'), - ('wielunski', u'wieluński'), - ('wieruszowski', u'wieruszowski'), - ('zdunskowolski', u'zduńskowolski'), - ('zgierski', u'zgierski'), - ('krakow', u'Kraków'), - ('tarnow', u'Tarnów'), - ('nowy-sacz', u'Nowy Sącz'), - ('bochenski', u'bocheński'), - ('brzeski', u'brzeski'), - ('chrzanowski', u'chrzanowski'), - ('dabrowski', u'dąbrowski'), - ('gorlicki', u'gorlicki'), - ('krakowski', u'krakowski'), - ('limanowski', u'limanowski'), - ('miechowski', u'miechowski'), - ('myslenicki', u'myślenicki'), - ('nowosadecki', u'nowosądecki'), - ('nowotarski', u'nowotarski'), - ('olkuski', u'olkuski'), - ('oswiecimski', u'oświęcimski'), - ('proszowicki', u'proszowicki'), - ('suski', u'suski'), - ('tarnowski', u'tarnowski'), - ('tatrzanski', u'tatrzański'), - ('wadowicki', u'wadowicki'), - ('wielicki', u'wielicki'), - ('warszawa', u'Warszawa'), - ('ostroleka', u'Ostrołęka'), - ('plock', u'Płock'), - ('radom', u'Radom'), - ('siedlce', u'Siedlce'), - ('bialobrzeski', u'białobrzeski'), - ('ciechanowski', u'ciechanowski'), - ('garwolinski', u'garwoliński'), - ('gostyninski', u'gostyniński'), - ('grodziski', u'grodziski'), - ('grojecki', u'grójecki'), - ('kozienicki', u'kozenicki'), - ('legionowski', u'legionowski'), - ('lipski', u'lipski'), - ('losicki', u'łosicki'), - ('makowski', u'makowski'), - ('minski', u'miński'), - ('mlawski', u'mławski'), - ('nowodworski', u'nowodworski'), - ('ostrolecki', u'ostrołęcki'), - ('ostrowski', u'ostrowski'), - ('otwocki', u'otwocki'), - ('piaseczynski', u'piaseczyński'), - ('plocki', u'płocki'), - ('plonski', u'płoński'), - ('pruszkowski', u'pruszkowski'), - ('przasnyski', u'przasnyski'), - ('przysuski', u'przysuski'), - ('pultuski', u'pułtuski'), - ('radomski', u'radomski'), - ('siedlecki', u'siedlecki'), - ('sierpecki', u'sierpecki'), - ('sochaczewski', u'sochaczewski'), - ('sokolowski', u'sokołowski'), - ('szydlowiecki', u'szydłowiecki'), - ('warszawski-zachodni', u'warszawski zachodni'), - ('wegrowski', u'węgrowski'), - ('wolominski', u'wołomiński'), - ('wyszkowski', u'wyszkowski'), - ('zwolenski', u'zwoleński'), - ('zurominski', u'żuromiński'), - ('zyrardowski', u'żyrardowski'), - ('opole', u'Opole'), - ('brzeski', u'brzeski'), - ('glubczycki', u'głubczyski'), - ('kedzierzynsko-kozielski', u'kędzierzyński-kozielski'), - ('kluczborski', u'kluczborski'), - ('krapkowicki', u'krapkowicki'), - ('namyslowski', u'namysłowski'), - ('nyski', u'nyski'), - ('oleski', u'oleski'), - ('opolski', u'opolski'), - ('prudnicki', u'prudnicki'), - ('strzelecki', u'strzelecki'), - ('rzeszow', u'Rzeszów'), - ('krosno', u'Krosno'), - ('przemysl', u'Przemyśl'), - ('tarnobrzeg', u'Tarnobrzeg'), - ('bieszczadzki', u'bieszczadzki'), - ('brzozowski', u'brzozowski'), - ('debicki', u'dębicki'), - ('jaroslawski', u'jarosławski'), - ('jasielski', u'jasielski'), - ('kolbuszowski', u'kolbuszowski'), - ('krosnienski', u'krośnieński'), - ('leski', u'leski'), - ('lezajski', u'leżajski'), - ('lubaczowski', u'lubaczowski'), - ('lancucki', u'łańcucki'), - ('mielecki', u'mielecki'), - ('nizanski', u'niżański'), - ('przemyski', u'przemyski'), - ('przeworski', u'przeworski'), - ('ropczycko-sedziszowski', u'ropczycko-sędziszowski'), - ('rzeszowski', u'rzeszowski'), - ('sanocki', u'sanocki'), - ('stalowowolski', u'stalowowolski'), - ('strzyzowski', u'strzyżowski'), - ('tarnobrzeski', u'tarnobrzeski'), - ('bialystok', u'Białystok'), - ('lomza', u'Łomża'), - ('suwalki', u'Suwałki'), - ('augustowski', u'augustowski'), - ('bialostocki', u'białostocki'), - ('bielski', u'bielski'), - ('grajewski', u'grajewski'), - ('hajnowski', u'hajnowski'), - ('kolnenski', u'kolneński'), - ('łomzynski', u'łomżyński'), - ('moniecki', u'moniecki'), - ('sejnenski', u'sejneński'), - ('siemiatycki', u'siematycki'), - ('sokolski', u'sokólski'), - ('suwalski', u'suwalski'), - ('wysokomazowiecki', u'wysokomazowiecki'), - ('zambrowski', u'zambrowski'), - ('gdansk', u'Gdańsk'), - ('gdynia', u'Gdynia'), - ('slupsk', u'Słupsk'), - ('sopot', u'Sopot'), - ('bytowski', u'bytowski'), - ('chojnicki', u'chojnicki'), - ('czluchowski', u'człuchowski'), - ('kartuski', u'kartuski'), - ('koscierski', u'kościerski'), - ('kwidzynski', u'kwidzyński'), - ('leborski', u'lęborski'), - ('malborski', u'malborski'), - ('nowodworski', u'nowodworski'), - ('gdanski', u'gdański'), - ('pucki', u'pucki'), - ('slupski', u'słupski'), - ('starogardzki', u'starogardzki'), - ('sztumski', u'sztumski'), - ('tczewski', u'tczewski'), - ('wejherowski', u'wejcherowski'), - ('katowice', u'Katowice'), - ('bielsko-biala', u'Bielsko-Biała'), - ('bytom', u'Bytom'), - ('chorzow', u'Chorzów'), - ('czestochowa', u'Częstochowa'), - ('dabrowa-gornicza', u'Dąbrowa Górnicza'), - ('gliwice', u'Gliwice'), - ('jastrzebie-zdroj', u'Jastrzębie Zdrój'), - ('jaworzno', u'Jaworzno'), - ('myslowice', u'Mysłowice'), - ('piekary-slaskie', u'Piekary Śląskie'), - ('ruda-slaska', u'Ruda Śląska'), - ('rybnik', u'Rybnik'), - ('siemianowice-slaskie', u'Siemianowice Śląskie'), - ('sosnowiec', u'Sosnowiec'), - ('swietochlowice', u'Świętochłowice'), - ('tychy', u'Tychy'), - ('zabrze', u'Zabrze'), - ('zory', u'Żory'), - ('bedzinski', u'będziński'), - ('bielski', u'bielski'), - ('bierunsko-ledzinski', u'bieruńsko-lędziński'), - ('cieszynski', u'cieszyński'), - ('czestochowski', u'częstochowski'), - ('gliwicki', u'gliwicki'), - ('klobucki', u'kłobucki'), - ('lubliniecki', u'lubliniecki'), - ('mikolowski', u'mikołowski'), - ('myszkowski', u'myszkowski'), - ('pszczynski', u'pszczyński'), - ('raciborski', u'raciborski'), - ('rybnicki', u'rybnicki'), - ('tarnogorski', u'tarnogórski'), - ('wodzislawski', u'wodzisławski'), - ('zawiercianski', u'zawierciański'), - ('zywiecki', u'żywiecki'), - ('kielce', u'Kielce'), - ('buski', u'buski'), - ('jedrzejowski', u'jędrzejowski'), - ('kazimierski', u'kazimierski'), - ('kielecki', u'kielecki'), - ('konecki', u'konecki'), - ('opatowski', u'opatowski'), - ('ostrowiecki', u'ostrowiecki'), - ('pinczowski', u'pińczowski'), - ('sandomierski', u'sandomierski'), - ('skarzyski', u'skarżyski'), - ('starachowicki', u'starachowicki'), - ('staszowski', u'staszowski'), - ('wloszczowski', u'włoszczowski'), - ('olsztyn', u'Olsztyn'), - ('elblag', u'Elbląg'), - ('bartoszycki', u'bartoszycki'), - ('braniewski', u'braniewski'), - ('dzialdowski', u'działdowski'), - ('elblaski', u'elbląski'), - ('elcki', u'ełcki'), - ('gizycki', u'giżycki'), - ('goldapski', u'gołdapski'), - ('ilawski', u'iławski'), - ('ketrzynski', u'kętrzyński'), - ('lidzbarski', u'lidzbarski'), - ('mragowski', u'mrągowski'), - ('nidzicki', u'nidzicki'), - ('nowomiejski', u'nowomiejski'), - ('olecki', u'olecki'), - ('olsztynski', u'olsztyński'), - ('ostrodzki', u'ostródzki'), - ('piski', u'piski'), - ('szczycienski', u'szczycieński'), - ('wegorzewski', u'węgorzewski'), - ('poznan', u'Poznań'), - ('kalisz', u'Kalisz'), - ('konin', u'Konin'), - ('leszno', u'Leszno'), - ('chodzieski', u'chodziejski'), - ('czarnkowsko-trzcianecki', u'czarnkowsko-trzcianecki'), - ('gnieznienski', u'gnieźnieński'), - ('gostynski', u'gostyński'), - ('grodziski', u'grodziski'), - ('jarocinski', u'jarociński'), - ('kaliski', u'kaliski'), - ('kepinski', u'kępiński'), - ('kolski', u'kolski'), - ('koninski', u'koniński'), - ('koscianski', u'kościański'), - ('krotoszynski', u'krotoszyński'), - ('leszczynski', u'leszczyński'), - ('miedzychodzki', u'międzychodzki'), - ('nowotomyski', u'nowotomyski'), - ('obornicki', u'obornicki'), - ('ostrowski', u'ostrowski'), - ('ostrzeszowski', u'ostrzeszowski'), - ('pilski', u'pilski'), - ('pleszewski', u'pleszewski'), - ('poznanski', u'poznański'), - ('rawicki', u'rawicki'), - ('slupecki', u'słupecki'), - ('szamotulski', u'szamotulski'), - ('sredzki', u'średzki'), - ('sremski', u'śremski'), - ('turecki', u'turecki'), - ('wagrowiecki', u'wągrowiecki'), - ('wolsztynski', u'wolsztyński'), - ('wrzesinski', u'wrzesiński'), - ('zlotowski', u'złotowski'), - ('bialogardzki', u'białogardzki'), - ('choszczenski', u'choszczeński'), - ('drawski', u'drawski'), - ('goleniowski', u'goleniowski'), - ('gryficki', u'gryficki'), - ('gryfinski', u'gryfiński'), - ('kamienski', u'kamieński'), - ('kolobrzeski', u'kołobrzeski'), - ('koszalinski', u'koszaliński'), - ('lobeski', u'łobeski'), - ('mysliborski', u'myśliborski'), - ('policki', u'policki'), - ('pyrzycki', u'pyrzycki'), - ('slawienski', u'sławieński'), - ('stargardzki', u'stargardzki'), - ('szczecinecki', u'szczecinecki'), - ('swidwinski', u'świdwiński'), - ('walecki', u'wałecki'), + ('wroclaw', 'Wrocław'), + ('jeleniagora', 'Jelenia Góra'), + ('legnica', 'Legnica'), + ('boleslawiecki', 'bolesławiecki'), + ('dzierzoniowski', 'dzierżoniowski'), + ('glogowski', 'głogowski'), + ('gorowski', 'górowski'), + ('jaworski', 'jaworski'), + ('jeleniogorski', 'jeleniogórski'), + ('kamiennogorski', 'kamiennogórski'), + ('klodzki', 'kłodzki'), + ('legnicki', 'legnicki'), + ('lubanski', 'lubański'), + ('lubinski', 'lubiński'), + ('lwowecki', 'lwówecki'), + ('milicki', 'milicki'), + ('olesnicki', 'oleśnicki'), + ('olawski', 'oławski'), + ('polkowicki', 'polkowicki'), + ('strzelinski', 'strzeliński'), + ('sredzki', 'średzki'), + ('swidnicki', 'świdnicki'), + ('trzebnicki', 'trzebnicki'), + ('walbrzyski', 'wałbrzyski'), + ('wolowski', 'wołowski'), + ('wroclawski', 'wrocławski'), + ('zabkowicki', 'ząbkowicki'), + ('zgorzelecki', 'zgorzelecki'), + ('zlotoryjski', 'złotoryjski'), + ('bydgoszcz', 'Bydgoszcz'), + ('torun', 'Toruń'), + ('wloclawek', 'Włocławek'), + ('grudziadz', 'Grudziądz'), + ('aleksandrowski', 'aleksandrowski'), + ('brodnicki', 'brodnicki'), + ('bydgoski', 'bydgoski'), + ('chelminski', 'chełmiński'), + ('golubsko-dobrzynski', 'golubsko-dobrzyński'), + ('grudziadzki', 'grudziądzki'), + ('inowroclawski', 'inowrocławski'), + ('lipnowski', 'lipnowski'), + ('mogilenski', 'mogileński'), + ('nakielski', 'nakielski'), + ('radziejowski', 'radziejowski'), + ('rypinski', 'rypiński'), + ('sepolenski', 'sępoleński'), + ('swiecki', 'świecki'), + ('torunski', 'toruński'), + ('tucholski', 'tucholski'), + ('wabrzeski', 'wąbrzeski'), + ('wloclawski', 'wrocławski'), + ('zninski', 'źniński'), + ('lublin', 'Lublin'), + ('biala-podlaska', 'Biała Podlaska'), + ('chelm', 'Chełm'), + ('zamosc', 'Zamość'), + ('bialski', 'bialski'), + ('bilgorajski', 'biłgorajski'), + ('chelmski', 'chełmski'), + ('hrubieszowski', 'hrubieszowski'), + ('janowski', 'janowski'), + ('krasnostawski', 'krasnostawski'), + ('krasnicki', 'kraśnicki'), + ('lubartowski', 'lubartowski'), + ('lubelski', 'lubelski'), + ('leczynski', 'łęczyński'), + ('lukowski', 'łukowski'), + ('opolski', 'opolski'), + ('parczewski', 'parczewski'), + ('pulawski', 'puławski'), + ('radzynski', 'radzyński'), + ('rycki', 'rycki'), + ('swidnicki', 'świdnicki'), + ('tomaszowski', 'tomaszowski'), + ('wlodawski', 'włodawski'), + ('zamojski', 'zamojski'), + ('gorzow-wielkopolski', 'Gorzów Wielkopolski'), + ('zielona-gora', 'Zielona Góra'), + ('gorzowski', 'gorzowski'), + ('krosnienski', 'krośnieński'), + ('miedzyrzecki', 'międzyrzecki'), + ('nowosolski', 'nowosolski'), + ('slubicki', 'słubicki'), + ('strzelecko-drezdenecki', 'strzelecko-drezdenecki'), + ('sulecinski', 'suleńciński'), + ('swiebodzinski', 'świebodziński'), + ('wschowski', 'wschowski'), + ('zielonogorski', 'zielonogórski'), + ('zaganski', 'żagański'), + ('zarski', 'żarski'), + ('lodz', 'Łódź'), + ('piotrkow-trybunalski', 'Piotrków Trybunalski'), + ('skierniewice', 'Skierniewice'), + ('belchatowski', 'bełchatowski'), + ('brzezinski', 'brzeziński'), + ('kutnowski', 'kutnowski'), + ('laski', 'łaski'), + ('leczycki', 'łęczycki'), + ('lowicki', 'łowicki'), + ('lodzki wschodni', 'łódzki wschodni'), + ('opoczynski', 'opoczyński'), + ('pabianicki', 'pabianicki'), + ('pajeczanski', 'pajęczański'), + ('piotrkowski', 'piotrkowski'), + ('poddebicki', 'poddębicki'), + ('radomszczanski', 'radomszczański'), + ('rawski', 'rawski'), + ('sieradzki', 'sieradzki'), + ('skierniewicki', 'skierniewicki'), + ('tomaszowski', 'tomaszowski'), + ('wielunski', 'wieluński'), + ('wieruszowski', 'wieruszowski'), + ('zdunskowolski', 'zduńskowolski'), + ('zgierski', 'zgierski'), + ('krakow', 'Kraków'), + ('tarnow', 'Tarnów'), + ('nowy-sacz', 'Nowy Sącz'), + ('bochenski', 'bocheński'), + ('brzeski', 'brzeski'), + ('chrzanowski', 'chrzanowski'), + ('dabrowski', 'dąbrowski'), + ('gorlicki', 'gorlicki'), + ('krakowski', 'krakowski'), + ('limanowski', 'limanowski'), + ('miechowski', 'miechowski'), + ('myslenicki', 'myślenicki'), + ('nowosadecki', 'nowosądecki'), + ('nowotarski', 'nowotarski'), + ('olkuski', 'olkuski'), + ('oswiecimski', 'oświęcimski'), + ('proszowicki', 'proszowicki'), + ('suski', 'suski'), + ('tarnowski', 'tarnowski'), + ('tatrzanski', 'tatrzański'), + ('wadowicki', 'wadowicki'), + ('wielicki', 'wielicki'), + ('warszawa', 'Warszawa'), + ('ostroleka', 'Ostrołęka'), + ('plock', 'Płock'), + ('radom', 'Radom'), + ('siedlce', 'Siedlce'), + ('bialobrzeski', 'białobrzeski'), + ('ciechanowski', 'ciechanowski'), + ('garwolinski', 'garwoliński'), + ('gostyninski', 'gostyniński'), + ('grodziski', 'grodziski'), + ('grojecki', 'grójecki'), + ('kozienicki', 'kozenicki'), + ('legionowski', 'legionowski'), + ('lipski', 'lipski'), + ('losicki', 'łosicki'), + ('makowski', 'makowski'), + ('minski', 'miński'), + ('mlawski', 'mławski'), + ('nowodworski', 'nowodworski'), + ('ostrolecki', 'ostrołęcki'), + ('ostrowski', 'ostrowski'), + ('otwocki', 'otwocki'), + ('piaseczynski', 'piaseczyński'), + ('plocki', 'płocki'), + ('plonski', 'płoński'), + ('pruszkowski', 'pruszkowski'), + ('przasnyski', 'przasnyski'), + ('przysuski', 'przysuski'), + ('pultuski', 'pułtuski'), + ('radomski', 'radomski'), + ('siedlecki', 'siedlecki'), + ('sierpecki', 'sierpecki'), + ('sochaczewski', 'sochaczewski'), + ('sokolowski', 'sokołowski'), + ('szydlowiecki', 'szydłowiecki'), + ('warszawski-zachodni', 'warszawski zachodni'), + ('wegrowski', 'węgrowski'), + ('wolominski', 'wołomiński'), + ('wyszkowski', 'wyszkowski'), + ('zwolenski', 'zwoleński'), + ('zurominski', 'żuromiński'), + ('zyrardowski', 'żyrardowski'), + ('opole', 'Opole'), + ('brzeski', 'brzeski'), + ('glubczycki', 'głubczyski'), + ('kedzierzynsko-kozielski', 'kędzierzyński-kozielski'), + ('kluczborski', 'kluczborski'), + ('krapkowicki', 'krapkowicki'), + ('namyslowski', 'namysłowski'), + ('nyski', 'nyski'), + ('oleski', 'oleski'), + ('opolski', 'opolski'), + ('prudnicki', 'prudnicki'), + ('strzelecki', 'strzelecki'), + ('rzeszow', 'Rzeszów'), + ('krosno', 'Krosno'), + ('przemysl', 'Przemyśl'), + ('tarnobrzeg', 'Tarnobrzeg'), + ('bieszczadzki', 'bieszczadzki'), + ('brzozowski', 'brzozowski'), + ('debicki', 'dębicki'), + ('jaroslawski', 'jarosławski'), + ('jasielski', 'jasielski'), + ('kolbuszowski', 'kolbuszowski'), + ('krosnienski', 'krośnieński'), + ('leski', 'leski'), + ('lezajski', 'leżajski'), + ('lubaczowski', 'lubaczowski'), + ('lancucki', 'łańcucki'), + ('mielecki', 'mielecki'), + ('nizanski', 'niżański'), + ('przemyski', 'przemyski'), + ('przeworski', 'przeworski'), + ('ropczycko-sedziszowski', 'ropczycko-sędziszowski'), + ('rzeszowski', 'rzeszowski'), + ('sanocki', 'sanocki'), + ('stalowowolski', 'stalowowolski'), + ('strzyzowski', 'strzyżowski'), + ('tarnobrzeski', 'tarnobrzeski'), + ('bialystok', 'Białystok'), + ('lomza', 'Łomża'), + ('suwalki', 'Suwałki'), + ('augustowski', 'augustowski'), + ('bialostocki', 'białostocki'), + ('bielski', 'bielski'), + ('grajewski', 'grajewski'), + ('hajnowski', 'hajnowski'), + ('kolnenski', 'kolneński'), + ('łomzynski', 'łomżyński'), + ('moniecki', 'moniecki'), + ('sejnenski', 'sejneński'), + ('siemiatycki', 'siematycki'), + ('sokolski', 'sokólski'), + ('suwalski', 'suwalski'), + ('wysokomazowiecki', 'wysokomazowiecki'), + ('zambrowski', 'zambrowski'), + ('gdansk', 'Gdańsk'), + ('gdynia', 'Gdynia'), + ('slupsk', 'Słupsk'), + ('sopot', 'Sopot'), + ('bytowski', 'bytowski'), + ('chojnicki', 'chojnicki'), + ('czluchowski', 'człuchowski'), + ('kartuski', 'kartuski'), + ('koscierski', 'kościerski'), + ('kwidzynski', 'kwidzyński'), + ('leborski', 'lęborski'), + ('malborski', 'malborski'), + ('nowodworski', 'nowodworski'), + ('gdanski', 'gdański'), + ('pucki', 'pucki'), + ('slupski', 'słupski'), + ('starogardzki', 'starogardzki'), + ('sztumski', 'sztumski'), + ('tczewski', 'tczewski'), + ('wejherowski', 'wejcherowski'), + ('katowice', 'Katowice'), + ('bielsko-biala', 'Bielsko-Biała'), + ('bytom', 'Bytom'), + ('chorzow', 'Chorzów'), + ('czestochowa', 'Częstochowa'), + ('dabrowa-gornicza', 'Dąbrowa Górnicza'), + ('gliwice', 'Gliwice'), + ('jastrzebie-zdroj', 'Jastrzębie Zdrój'), + ('jaworzno', 'Jaworzno'), + ('myslowice', 'Mysłowice'), + ('piekary-slaskie', 'Piekary Śląskie'), + ('ruda-slaska', 'Ruda Śląska'), + ('rybnik', 'Rybnik'), + ('siemianowice-slaskie', 'Siemianowice Śląskie'), + ('sosnowiec', 'Sosnowiec'), + ('swietochlowice', 'Świętochłowice'), + ('tychy', 'Tychy'), + ('zabrze', 'Zabrze'), + ('zory', 'Żory'), + ('bedzinski', 'będziński'), + ('bielski', 'bielski'), + ('bierunsko-ledzinski', 'bieruńsko-lędziński'), + ('cieszynski', 'cieszyński'), + ('czestochowski', 'częstochowski'), + ('gliwicki', 'gliwicki'), + ('klobucki', 'kłobucki'), + ('lubliniecki', 'lubliniecki'), + ('mikolowski', 'mikołowski'), + ('myszkowski', 'myszkowski'), + ('pszczynski', 'pszczyński'), + ('raciborski', 'raciborski'), + ('rybnicki', 'rybnicki'), + ('tarnogorski', 'tarnogórski'), + ('wodzislawski', 'wodzisławski'), + ('zawiercianski', 'zawierciański'), + ('zywiecki', 'żywiecki'), + ('kielce', 'Kielce'), + ('buski', 'buski'), + ('jedrzejowski', 'jędrzejowski'), + ('kazimierski', 'kazimierski'), + ('kielecki', 'kielecki'), + ('konecki', 'konecki'), + ('opatowski', 'opatowski'), + ('ostrowiecki', 'ostrowiecki'), + ('pinczowski', 'pińczowski'), + ('sandomierski', 'sandomierski'), + ('skarzyski', 'skarżyski'), + ('starachowicki', 'starachowicki'), + ('staszowski', 'staszowski'), + ('wloszczowski', 'włoszczowski'), + ('olsztyn', 'Olsztyn'), + ('elblag', 'Elbląg'), + ('bartoszycki', 'bartoszycki'), + ('braniewski', 'braniewski'), + ('dzialdowski', 'działdowski'), + ('elblaski', 'elbląski'), + ('elcki', 'ełcki'), + ('gizycki', 'giżycki'), + ('goldapski', 'gołdapski'), + ('ilawski', 'iławski'), + ('ketrzynski', 'kętrzyński'), + ('lidzbarski', 'lidzbarski'), + ('mragowski', 'mrągowski'), + ('nidzicki', 'nidzicki'), + ('nowomiejski', 'nowomiejski'), + ('olecki', 'olecki'), + ('olsztynski', 'olsztyński'), + ('ostrodzki', 'ostródzki'), + ('piski', 'piski'), + ('szczycienski', 'szczycieński'), + ('wegorzewski', 'węgorzewski'), + ('poznan', 'Poznań'), + ('kalisz', 'Kalisz'), + ('konin', 'Konin'), + ('leszno', 'Leszno'), + ('chodzieski', 'chodziejski'), + ('czarnkowsko-trzcianecki', 'czarnkowsko-trzcianecki'), + ('gnieznienski', 'gnieźnieński'), + ('gostynski', 'gostyński'), + ('grodziski', 'grodziski'), + ('jarocinski', 'jarociński'), + ('kaliski', 'kaliski'), + ('kepinski', 'kępiński'), + ('kolski', 'kolski'), + ('koninski', 'koniński'), + ('koscianski', 'kościański'), + ('krotoszynski', 'krotoszyński'), + ('leszczynski', 'leszczyński'), + ('miedzychodzki', 'międzychodzki'), + ('nowotomyski', 'nowotomyski'), + ('obornicki', 'obornicki'), + ('ostrowski', 'ostrowski'), + ('ostrzeszowski', 'ostrzeszowski'), + ('pilski', 'pilski'), + ('pleszewski', 'pleszewski'), + ('poznanski', 'poznański'), + ('rawicki', 'rawicki'), + ('slupecki', 'słupecki'), + ('szamotulski', 'szamotulski'), + ('sredzki', 'średzki'), + ('sremski', 'śremski'), + ('turecki', 'turecki'), + ('wagrowiecki', 'wągrowiecki'), + ('wolsztynski', 'wolsztyński'), + ('wrzesinski', 'wrzesiński'), + ('zlotowski', 'złotowski'), + ('bialogardzki', 'białogardzki'), + ('choszczenski', 'choszczeński'), + ('drawski', 'drawski'), + ('goleniowski', 'goleniowski'), + ('gryficki', 'gryficki'), + ('gryfinski', 'gryfiński'), + ('kamienski', 'kamieński'), + ('kolobrzeski', 'kołobrzeski'), + ('koszalinski', 'koszaliński'), + ('lobeski', 'łobeski'), + ('mysliborski', 'myśliborski'), + ('policki', 'policki'), + ('pyrzycki', 'pyrzycki'), + ('slawienski', 'sławieński'), + ('stargardzki', 'stargardzki'), + ('szczecinecki', 'szczecinecki'), + ('swidwinski', 'świdwiński'), + ('walecki', 'wałecki'), ) diff --git a/django/contrib/localflavor/pt/forms.py b/django/contrib/localflavor/pt/forms.py index 3de7376eac..b27fb577bd 100644 --- a/django/contrib/localflavor/pt/forms.py +++ b/django/contrib/localflavor/pt/forms.py @@ -1,6 +1,8 @@ """ PT-specific Form helpers """ +from __future__ import unicode_literals + import re from django.core.validators import EMPTY_VALUES @@ -24,7 +26,7 @@ class PTZipCodeField(RegexField): def clean(self,value): cleaned = super(PTZipCodeField, self).clean(value) if len(cleaned) == 7: - return u'%s-%s' % (cleaned[:4],cleaned[4:]) + return '%s-%s' % (cleaned[:4],cleaned[4:]) else: return cleaned @@ -40,9 +42,9 @@ class PTPhoneNumberField(Field): def clean(self, value): super(PTPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\.|\s)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: - return u'%s' % value + return '%s' % value raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/py/py_department.py b/django/contrib/localflavor/py/py_department.py index 28c613b9a0..619bae3edb 100644 --- a/django/contrib/localflavor/py/py_department.py +++ b/django/contrib/localflavor/py/py_department.py @@ -1,45 +1,46 @@ # -*- coding: utf-8 -*- # http://www.statoids.com/upy.html +from __future__ import unicode_literals DEPARTMENT_CHOICES = ( - ('AG', u'Alto Paraguay'), - ('AA', u'Alto Paraná'), - ('AM', u'Amambay'), - ('AS', u'Asunción'), - ('BQ', u'Boquerón'), - ('CG', u'Caaguazú'), - ('CZ', u'Caazapá'), - ('CY', u'Canindeyú'), - ('CE', u'Central'), - ('CN', u'Concepción'), - ('CR', u'Cordillera'), - ('GU', u'Guairá'), - ('IT', u'Itapúa'), - ('MI', u'Misiones'), - ('NE', u'Ñeembucú'), - ('PG', u'Paraguarí'), - ('PH', u'Pdte. Hayes'), - ('SP', u'San Pedro'), + ('AG', 'Alto Paraguay'), + ('AA', 'Alto Paraná'), + ('AM', 'Amambay'), + ('AS', 'Asunción'), + ('BQ', 'Boquerón'), + ('CG', 'Caaguazú'), + ('CZ', 'Caazapá'), + ('CY', 'Canindeyú'), + ('CE', 'Central'), + ('CN', 'Concepción'), + ('CR', 'Cordillera'), + ('GU', 'Guairá'), + ('IT', 'Itapúa'), + ('MI', 'Misiones'), + ('NE', 'Ñeembucú'), + ('PG', 'Paraguarí'), + ('PH', 'Pdte. Hayes'), + ('SP', 'San Pedro'), ) DEPARTMENT_ROMAN_CHOICES = ( - ('CN', u'I Concepción'), - ('SP', u'II San Pedro'), - ('CR', u'III Cordillera'), - ('GU', u'IV Guairá'), - ('CG', u'V Caaguazú'), - ('CZ', u'VI Caazapá'), - ('IT', u'VII Itapúa'), - ('MI', u'VIII Misiones'), - ('PG', u'IX Paraguarí'), - ('AA', u'X Alto Paraná'), - ('CE', u'XI Central'), - ('NE', u'XII Ñeembucú'), - ('AM', u'XIII Amambay'), - ('CY', u'XIV Canindeyú'), - ('PH', u'XV Pdte. Hayes'), - ('AG', u'XVI Alto Paraguay'), - ('BQ', u'XVII Boquerón'), - ('AS', u'XVIII Asunción'), + ('CN', 'I Concepción'), + ('SP', 'II San Pedro'), + ('CR', 'III Cordillera'), + ('GU', 'IV Guairá'), + ('CG', 'V Caaguazú'), + ('CZ', 'VI Caazapá'), + ('IT', 'VII Itapúa'), + ('MI', 'VIII Misiones'), + ('PG', 'IX Paraguarí'), + ('AA', 'X Alto Paraná'), + ('CE', 'XI Central'), + ('NE', 'XII Ñeembucú'), + ('AM', 'XIII Amambay'), + ('CY', 'XIV Canindeyú'), + ('PH', 'XV Pdte. Hayes'), + ('AG', 'XVI Alto Paraguay'), + ('BQ', 'XVII Boquerón'), + ('AS', 'XVIII Asunción'), ) diff --git a/django/contrib/localflavor/ro/forms.py b/django/contrib/localflavor/ro/forms.py index 5c458390b2..bdbed5c476 100644 --- a/django/contrib/localflavor/ro/forms.py +++ b/django/contrib/localflavor/ro/forms.py @@ -2,7 +2,7 @@ """ Romanian specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.ro.ro_counties import COUNTIES_CHOICES from django.core.validators import EMPTY_VALUES @@ -30,7 +30,7 @@ class ROCIFField(RegexField): """ value = super(ROCIFField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # strip RO part if value[0:2] == 'RO': value = value[2:] @@ -67,7 +67,7 @@ class ROCNPField(RegexField): """ value = super(ROCNPField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # check birthdate digits import datetime try: @@ -100,13 +100,13 @@ class ROCountyField(Field): Arges => invalid """ default_error_messages = { - 'invalid': u'Enter a Romanian county code or name.', + 'invalid': 'Enter a Romanian county code or name.', } def clean(self, value): super(ROCountyField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().upper() except AttributeError: @@ -152,7 +152,7 @@ class ROIBANField(RegexField): """ value = super(ROIBANField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.replace('-','') value = value.replace(' ','') value = value.upper() @@ -184,7 +184,7 @@ class ROPhoneNumberField(RegexField): """ value = super(ROPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.replace('-','') value = value.replace('(','') value = value.replace(')','') diff --git a/django/contrib/localflavor/ro/ro_counties.py b/django/contrib/localflavor/ro/ro_counties.py index 40423ddc87..282cfc5193 100644 --- a/django/contrib/localflavor/ro/ro_counties.py +++ b/django/contrib/localflavor/ro/ro_counties.py @@ -5,48 +5,49 @@ A list of Romanian counties as `choices` in a formfield. This exists as a standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals COUNTIES_CHOICES = ( - ('AB', u'Alba'), - ('AR', u'Arad'), - ('AG', u'Argeş'), - ('BC', u'Bacău'), - ('BH', u'Bihor'), - ('BN', u'Bistriţa-Năsăud'), - ('BT', u'Botoşani'), - ('BV', u'Braşov'), - ('BR', u'Brăila'), - ('B', u'Bucureşti'), - ('BZ', u'Buzău'), - ('CS', u'Caraş-Severin'), - ('CL', u'Călăraşi'), - ('CJ', u'Cluj'), - ('CT', u'Constanţa'), - ('CV', u'Covasna'), - ('DB', u'Dâmboviţa'), - ('DJ', u'Dolj'), - ('GL', u'Galaţi'), - ('GR', u'Giurgiu'), - ('GJ', u'Gorj'), - ('HR', u'Harghita'), - ('HD', u'Hunedoara'), - ('IL', u'Ialomiţa'), - ('IS', u'Iaşi'), - ('IF', u'Ilfov'), - ('MM', u'Maramureş'), - ('MH', u'Mehedinţi'), - ('MS', u'Mureş'), - ('NT', u'Neamţ'), - ('OT', u'Olt'), - ('PH', u'Prahova'), - ('SM', u'Satu Mare'), - ('SJ', u'Sălaj'), - ('SB', u'Sibiu'), - ('SV', u'Suceava'), - ('TR', u'Teleorman'), - ('TM', u'Timiş'), - ('TL', u'Tulcea'), - ('VS', u'Vaslui'), - ('VL', u'Vâlcea'), - ('VN', u'Vrancea'), + ('AB', 'Alba'), + ('AR', 'Arad'), + ('AG', 'Argeş'), + ('BC', 'Bacău'), + ('BH', 'Bihor'), + ('BN', 'Bistriţa-Năsăud'), + ('BT', 'Botoşani'), + ('BV', 'Braşov'), + ('BR', 'Brăila'), + ('B', 'Bucureşti'), + ('BZ', 'Buzău'), + ('CS', 'Caraş-Severin'), + ('CL', 'Călăraşi'), + ('CJ', 'Cluj'), + ('CT', 'Constanţa'), + ('CV', 'Covasna'), + ('DB', 'Dâmboviţa'), + ('DJ', 'Dolj'), + ('GL', 'Galaţi'), + ('GR', 'Giurgiu'), + ('GJ', 'Gorj'), + ('HR', 'Harghita'), + ('HD', 'Hunedoara'), + ('IL', 'Ialomiţa'), + ('IS', 'Iaşi'), + ('IF', 'Ilfov'), + ('MM', 'Maramureş'), + ('MH', 'Mehedinţi'), + ('MS', 'Mureş'), + ('NT', 'Neamţ'), + ('OT', 'Olt'), + ('PH', 'Prahova'), + ('SM', 'Satu Mare'), + ('SJ', 'Sălaj'), + ('SB', 'Sibiu'), + ('SV', 'Suceava'), + ('TR', 'Teleorman'), + ('TM', 'Timiş'), + ('TL', 'Tulcea'), + ('VS', 'Vaslui'), + ('VL', 'Vâlcea'), + ('VN', 'Vrancea'), ) diff --git a/django/contrib/localflavor/ru/forms.py b/django/contrib/localflavor/ru/forms.py index d01f5a1e3b..03114d0629 100644 --- a/django/contrib/localflavor/ru/forms.py +++ b/django/contrib/localflavor/ru/forms.py @@ -1,7 +1,7 @@ """ Russian-specific forms helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -34,7 +34,7 @@ class RUPostalCodeField(RegexField): Format: XXXXXX, where X is any digit, and first digit is not zero. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXXX.'), + 'invalid': _('Enter a postal code in the format XXXXXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): super(RUPostalCodeField, self).__init__(r'^\d{6}$', @@ -47,7 +47,7 @@ class RUPassportNumberField(RegexField): XXXX XXXXXX where X - any digit. """ default_error_messages = { - 'invalid': _(u'Enter a passport number in the format XXXX XXXXXX.'), + 'invalid': _('Enter a passport number in the format XXXX XXXXXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): super(RUPassportNumberField, self).__init__(r'^\d{4} \d{6}$', @@ -60,7 +60,7 @@ class RUAlienPassportNumberField(RegexField): XX XXXXXXX where X - any digit. """ default_error_messages = { - 'invalid': _(u'Enter a passport number in the format XX XXXXXXX.'), + 'invalid': _('Enter a passport number in the format XX XXXXXXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): super(RUAlienPassportNumberField, self).__init__(r'^\d{2} \d{7}$', diff --git a/django/contrib/localflavor/se/forms.py b/django/contrib/localflavor/se/forms.py index 5c4e2325a9..43d06a08ec 100644 --- a/django/contrib/localflavor/se/forms.py +++ b/django/contrib/localflavor/se/forms.py @@ -2,7 +2,7 @@ """ Swedish specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -58,7 +58,7 @@ class SEOrganisationNumberField(forms.CharField): value = super(SEOrganisationNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = SWEDISH_ID_NUMBER.match(value) if not match: @@ -116,7 +116,7 @@ class SEPersonalIdentityNumberField(forms.CharField): value = super(SEPersonalIdentityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = SWEDISH_ID_NUMBER.match(value) if match is None: diff --git a/django/contrib/localflavor/se/se_counties.py b/django/contrib/localflavor/se/se_counties.py index db54fb9f39..20090d3b30 100644 --- a/django/contrib/localflavor/se/se_counties.py +++ b/django/contrib/localflavor/se/se_counties.py @@ -8,29 +8,30 @@ This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ COUNTY_CHOICES = ( - ('AB', _(u'Stockholm')), - ('AC', _(u'Västerbotten')), - ('BD', _(u'Norrbotten')), - ('C', _(u'Uppsala')), - ('D', _(u'Södermanland')), - ('E', _(u'Östergötland')), - ('F', _(u'Jönköping')), - ('G', _(u'Kronoberg')), - ('H', _(u'Kalmar')), - ('I', _(u'Gotland')), - ('K', _(u'Blekinge')), - ('M', _(u'Skåne')), - ('N', _(u'Halland')), - ('O', _(u'Västra Götaland')), - ('S', _(u'Värmland')), - ('T', _(u'Örebro')), - ('U', _(u'Västmanland')), - ('W', _(u'Dalarna')), - ('X', _(u'Gävleborg')), - ('Y', _(u'Västernorrland')), - ('Z', _(u'Jämtland')), + ('AB', _('Stockholm')), + ('AC', _('Västerbotten')), + ('BD', _('Norrbotten')), + ('C', _('Uppsala')), + ('D', _('Södermanland')), + ('E', _('Östergötland')), + ('F', _('Jönköping')), + ('G', _('Kronoberg')), + ('H', _('Kalmar')), + ('I', _('Gotland')), + ('K', _('Blekinge')), + ('M', _('Skåne')), + ('N', _('Halland')), + ('O', _('Västra Götaland')), + ('S', _('Värmland')), + ('T', _('Örebro')), + ('U', _('Västmanland')), + ('W', _('Dalarna')), + ('X', _('Gävleborg')), + ('Y', _('Västernorrland')), + ('Z', _('Jämtland')), ) diff --git a/django/contrib/localflavor/si/forms.py b/django/contrib/localflavor/si/forms.py index f1188dd222..aa4b9dac5a 100644 --- a/django/contrib/localflavor/si/forms.py +++ b/django/contrib/localflavor/si/forms.py @@ -2,7 +2,7 @@ Slovenian specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime import re @@ -21,16 +21,16 @@ class SIEMSOField(CharField): """ default_error_messages = { - 'invalid': _(u'This field should contain exactly 13 digits.'), - 'date': _(u'The first 7 digits of the EMSO must represent a valid past date.'), - 'checksum': _(u'The EMSO is not valid.'), + 'invalid': _('This field should contain exactly 13 digits.'), + 'date': _('The first 7 digits of the EMSO must represent a valid past date.'), + 'checksum': _('The EMSO is not valid.'), } emso_regex = re.compile('^(\d{2})(\d{2})(\d{3})(\d{2})(\d{3})(\d)$') def clean(self, value): super(SIEMSOField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() @@ -83,14 +83,14 @@ class SITaxNumberField(CharField): """ default_error_messages = { - 'invalid': _(u'Enter a valid tax number in form SIXXXXXXXX'), + 'invalid': _('Enter a valid tax number in form SIXXXXXXXX'), } sitax_regex = re.compile('^(?:SI)?([1-9]\d{7})$') def clean(self, value): super(SITaxNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() @@ -148,14 +148,14 @@ class SIPhoneNumberField(CharField): """ default_error_messages = { - 'invalid': _(u'Enter phone number in form +386XXXXXXXX or 0XXXXXXXX.'), + 'invalid': _('Enter phone number in form +386XXXXXXXX or 0XXXXXXXX.'), } phone_regex = re.compile('^(?:(?:00|\+)386|0)(\d{7,8})$') def clean(self, value): super(SIPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.replace(' ', '').replace('-', '').replace('/', '') m = self.phone_regex.match(value) diff --git a/django/contrib/localflavor/si/si_postalcodes.py b/django/contrib/localflavor/si/si_postalcodes.py index 7baac94e03..4d027afcff 100644 --- a/django/contrib/localflavor/si/si_postalcodes.py +++ b/django/contrib/localflavor/si/si_postalcodes.py @@ -1,469 +1,470 @@ # *-* coding: utf-8 *-* +from __future__ import unicode_literals SI_POSTALCODES = [ - (1000, u'Ljubljana'), - (1215, u'Medvode'), - (1216, u'Smlednik'), - (1217, u'Vodice'), - (1218, u'Komenda'), - (1219, u'Laze v Tuhinju'), - (1221, u'Motnik'), - (1222, u'Trojane'), - (1223, u'Blagovica'), - (1225, u'Lukovica'), - (1230, u'Dom\u017eale'), - (1233, u'Dob'), - (1234, u'Menge\u0161'), - (1235, u'Radomlje'), - (1236, u'Trzin'), - (1241, u'Kamnik'), - (1242, u'Stahovica'), - (1251, u'Morav\u010de'), - (1252, u'Va\u010de'), - (1262, u'Dol pri Ljubljani'), - (1270, u'Litija'), - (1272, u'Pol\u0161nik'), - (1273, u'Dole pri Litiji'), - (1274, u'Gabrovka'), - (1275, u'\u0160martno pri Litiji'), - (1276, u'Primskovo'), - (1281, u'Kresnice'), - (1282, u'Sava'), - (1290, u'Grosuplje'), - (1291, u'\u0160kofljica'), - (1292, u'Ig'), - (1293, u'\u0160marje - Sap'), - (1294, u'Vi\u0161nja Gora'), - (1295, u'Ivan\u010dna Gorica'), - (1296, u'\u0160entvid pri Sti\u010dni'), - (1301, u'Krka'), - (1303, u'Zagradec'), - (1310, u'Ribnica'), - (1311, u'Turjak'), - (1312, u'Videm - Dobrepolje'), - (1313, u'Struge'), - (1314, u'Rob'), - (1315, u'Velike La\u0161\u010de'), - (1316, u'Ortnek'), - (1317, u'Sodra\u017eica'), - (1318, u'Lo\u0161ki Potok'), - (1319, u'Draga'), - (1330, u'Ko\u010devje'), - (1331, u'Dolenja vas'), - (1332, u'Stara Cerkev'), - (1336, u'Kostel'), - (1337, u'Osilnica'), - (1338, u'Ko\u010devska Reka'), - (1351, u'Brezovica pri Ljubljani'), - (1352, u'Preserje'), - (1353, u'Borovnica'), - (1354, u'Horjul'), - (1355, u'Polhov Gradec'), - (1356, u'Dobrova'), - (1357, u'Notranje Gorice'), - (1358, u'Log pri Brezovici'), - (1360, u'Vrhnika'), - (1370, u'Logatec'), - (1372, u'Hotedr\u0161ica'), - (1373, u'Rovte'), - (1380, u'Cerknica'), - (1381, u'Rakek'), - (1382, u'Begunje pri Cerknici'), - (1384, u'Grahovo'), - (1385, u'Nova vas'), - (1386, u'Stari trg pri Lo\u017eu'), - (1410, u'Zagorje ob Savi'), - (1411, u'Izlake'), - (1412, u'Kisovec'), - (1413, u'\u010cem\u0161enik'), - (1414, u'Podkum'), - (1420, u'Trbovlje'), - (1423, u'Dobovec'), - (1430, u'Hrastnik'), - (1431, u'Dol pri Hrastniku'), - (1432, u'Zidani Most'), - (1433, u'Rade\u010de'), - (1434, u'Loka pri Zidanem Mostu'), - (2000, u'Maribor'), - (2201, u'Zgornja Kungota'), - (2204, u'Miklav\u017e na Dravskem polju'), - (2205, u'Star\u0161e'), - (2206, u'Marjeta na Dravskem polju'), - (2208, u'Pohorje'), - (2211, u'Pesnica pri Mariboru'), - (2212, u'\u0160entilj v Slovenskih goricah'), - (2213, u'Zgornja Velka'), - (2214, u'Sladki vrh'), - (2215, u'Cer\u0161ak'), - (2221, u'Jarenina'), - (2222, u'Jakobski Dol'), - (2223, u'Jurovski Dol'), - (2229, u'Male\u010dnik'), - (2230, u'Lenart v Slovenskih goricah'), - (2231, u'Pernica'), - (2232, u'Voli\u010dina'), - (2233, u'Sveta Ana v Slovenskih goricah'), - (2234, u'Benedikt'), - (2235, u'Sveta Trojica v Slovenskih goricah'), - (2236, u'Cerkvenjak'), - (2241, u'Spodnji Duplek'), - (2242, u'Zgornja Korena'), - (2250, u'Ptuj'), - (2252, u'Dornava'), - (2253, u'Destrnik'), - (2254, u'Trnovska vas'), - (2255, u'Vitomarci'), - (2256, u'Jur\u0161inci'), - (2257, u'Polen\u0161ak'), - (2258, u'Sveti Toma\u017e'), - (2259, u'Ivanjkovci'), - (2270, u'Ormo\u017e'), - (2272, u'Gori\u0161nica'), - (2273, u'Podgorci'), - (2274, u'Velika Nedelja'), - (2275, u'Miklav\u017e pri Ormo\u017eu'), - (2276, u'Kog'), - (2277, u'Sredi\u0161\u010de ob Dravi'), - (2281, u'Markovci'), - (2282, u'Cirkulane'), - (2283, u'Zavr\u010d'), - (2284, u'Videm pri Ptuju'), - (2285, u'Zgornji Leskovec'), - (2286, u'Podlehnik'), - (2287, u'\u017detale'), - (2288, u'Hajdina'), - (2289, u'Stoperce'), - (2310, u'Slovenska Bistrica'), - (2311, u'Ho\u010de'), - (2312, u'Orehova vas'), - (2313, u'Fram'), - (2314, u'Zgornja Polskava'), - (2315, u'\u0160martno na Pohorju'), - (2316, u'Zgornja Lo\u017enica'), - (2317, u'Oplotnica'), - (2318, u'Laporje'), - (2319, u'Polj\u010dane'), - (2321, u'Makole'), - (2322, u'Maj\u0161perk'), - (2323, u'Ptujska Gora'), - (2324, u'Lovrenc na Dravskem polju'), - (2325, u'Kidri\u010devo'), - (2326, u'Cirkovce'), - (2327, u'Ra\u010de'), - (2331, u'Pragersko'), - (2341, u'Limbu\u0161'), - (2342, u'Ru\u0161e'), - (2343, u'Fala'), - (2344, u'Lovrenc na Pohorju'), - (2345, u'Bistrica ob Dravi'), - (2351, u'Kamnica'), - (2352, u'Selnica ob Dravi'), - (2353, u'Sv. Duh na Ostrem Vrhu'), - (2354, u'Bresternica'), - (2360, u'Radlje ob Dravi'), - (2361, u'O\u017ebalt'), - (2362, u'Kapla'), - (2363, u'Podvelka'), - (2364, u'Ribnica na Pohorju'), - (2365, u'Vuhred'), - (2366, u'Muta'), - (2367, u'Vuzenica'), - (2370, u'Dravograd'), - (2371, u'Trbonje'), - (2372, u'Libeli\u010de'), - (2373, u'\u0160entjan\u017e pri Dravogradu'), - (2380, u'Slovenj Gradec'), - (2381, u'Podgorje pri Slovenj Gradcu'), - (2382, u'Mislinja'), - (2383, u'\u0160martno pri Slovenj Gradcu'), - (2390, u'Ravne na Koro\u0161kem'), - (2391, u'Prevalje'), - (2392, u'Me\u017eica'), - (2393, u'\u010crna na Koro\u0161kem'), - (2394, u'Kotlje'), - (3000, u'Celje'), - (3201, u'\u0160martno v Ro\u017eni dolini'), - (3202, u'Ljube\u010dna'), - (3203, u'Nova Cerkev'), - (3204, u'Dobrna'), - (3205, u'Vitanje'), - (3206, u'Stranice'), - (3210, u'Slovenske Konjice'), - (3211, u'\u0160kofja vas'), - (3212, u'Vojnik'), - (3213, u'Frankolovo'), - (3214, u'Zre\u010de'), - (3215, u'Lo\u010de'), - (3220, u'\u0160tore'), - (3221, u'Teharje'), - (3222, u'Dramlje'), - (3223, u'Loka pri \u017dusmu'), - (3224, u'Dobje pri Planini'), - (3225, u'Planina pri Sevnici'), - (3230, u'\u0160entjur'), - (3231, u'Grobelno'), - (3232, u'Ponikva'), - (3233, u'Kalobje'), - (3240, u'\u0160marje pri Jel\u0161ah'), - (3241, u'Podplat'), - (3250, u'Roga\u0161ka Slatina'), - (3252, u'Rogatec'), - (3253, u'Pristava pri Mestinju'), - (3254, u'Pod\u010detrtek'), - (3255, u'Bu\u010de'), - (3256, u'Bistrica ob Sotli'), - (3257, u'Podsreda'), - (3260, u'Kozje'), - (3261, u'Lesi\u010dno'), - (3262, u'Prevorje'), - (3263, u'Gorica pri Slivnici'), - (3264, u'Sveti \u0160tefan'), - (3270, u'La\u0161ko'), - (3271, u'\u0160entrupert'), - (3272, u'Rimske Toplice'), - (3273, u'Jurklo\u0161ter'), - (3301, u'Petrov\u010de'), - (3302, u'Gri\u017ee'), - (3303, u'Gomilsko'), - (3304, u'Tabor'), - (3305, u'Vransko'), - (3310, u'\u017dalec'), - (3311, u'\u0160empeter v Savinjski dolini'), - (3312, u'Prebold'), - (3313, u'Polzela'), - (3314, u'Braslov\u010de'), - (3320, u'Velenje - dostava'), - (3322, u'Velenje - po\u0161tni predali'), - (3325, u'\u0160o\u0161tanj'), - (3326, u'Topol\u0161ica'), - (3327, u'\u0160martno ob Paki'), - (3330, u'Mozirje'), - (3331, u'Nazarje'), - (3332, u'Re\u010dica ob Savinji'), - (3333, u'Ljubno ob Savinji'), - (3334, u'Lu\u010de'), - (3335, u'Sol\u010dava'), - (3341, u'\u0160martno ob Dreti'), - (3342, u'Gornji Grad'), - (4000, u'Kranj'), - (4201, u'Zgornja Besnica'), - (4202, u'Naklo'), - (4203, u'Duplje'), - (4204, u'Golnik'), - (4205, u'Preddvor'), - (4206, u'Zgornje Jezersko'), - (4207, u'Cerklje na Gorenjskem'), - (4208, u'\u0160en\u010dur'), - (4209, u'\u017dabnica'), - (4210, u'Brnik - aerodrom'), - (4211, u'Mav\u010di\u010de'), - (4212, u'Visoko'), - (4220, u'\u0160kofja Loka'), - (4223, u'Poljane nad \u0160kofjo Loko'), - (4224, u'Gorenja vas'), - (4225, u'Sovodenj'), - (4226, u'\u017diri'), - (4227, u'Selca'), - (4228, u'\u017delezniki'), - (4229, u'Sorica'), - (4240, u'Radovljica'), - (4243, u'Brezje'), - (4244, u'Podnart'), - (4245, u'Kropa'), - (4246, u'Kamna Gorica'), - (4247, u'Zgornje Gorje'), - (4248, u'Lesce'), - (4260, u'Bled'), - (4263, u'Bohinjska Bela'), - (4264, u'Bohinjska Bistrica'), - (4265, u'Bohinjsko jezero'), - (4267, u'Srednja vas v Bohinju'), - (4270, u'Jesenice'), - (4273, u'Blejska Dobrava'), - (4274, u'\u017dirovnica'), - (4275, u'Begunje na Gorenjskem'), - (4276, u'Hru\u0161ica'), - (4280, u'Kranjska Gora'), - (4281, u'Mojstrana'), - (4282, u'Gozd Martuljek'), - (4283, u'Rate\u010de - Planica'), - (4290, u'Tr\u017ei\u010d'), - (4294, u'Kri\u017ee'), - (5000, u'Nova Gorica'), - (5210, u'Deskle'), - (5211, u'Kojsko'), - (5212, u'Dobrovo v Brdih'), - (5213, u'Kanal'), - (5214, u'Kal nad Kanalom'), - (5215, u'Ro\u010dinj'), - (5216, u'Most na So\u010di'), - (5220, u'Tolmin'), - (5222, u'Kobarid'), - (5223, u'Breginj'), - (5224, u'Srpenica'), - (5230, u'Bovec'), - (5231, u'Log pod Mangartom'), - (5232, u'So\u010da'), - (5242, u'Grahovo ob Ba\u010di'), - (5243, u'Podbrdo'), - (5250, u'Solkan'), - (5251, u'Grgar'), - (5252, u'Trnovo pri Gorici'), - (5253, u'\u010cepovan'), - (5261, u'\u0160empas'), - (5262, u'\u010crni\u010de'), - (5263, u'Dobravlje'), - (5270, u'Ajdov\u0161\u010dina'), - (5271, u'Vipava'), - (5272, u'Podnanos'), - (5273, u'Col'), - (5274, u'\u010crni Vrh nad Idrijo'), - (5275, u'Godovi\u010d'), - (5280, u'Idrija'), - (5281, u'Spodnja Idrija'), - (5282, u'Cerkno'), - (5283, u'Slap ob Idrijci'), - (5290, u'\u0160empeter pri Gorici'), - (5291, u'Miren'), - (5292, u'Ren\u010de'), - (5293, u'Vol\u010dja Draga'), - (5294, u'Dornberk'), - (5295, u'Branik'), - (5296, u'Kostanjevica na Krasu'), - (5297, u'Prva\u010dina'), - (6000, u'Koper'), - (6210, u'Se\u017eana'), - (6215, u'Diva\u010da'), - (6216, u'Podgorje'), - (6217, u'Vremski Britof'), - (6219, u'Lokev'), - (6221, u'Dutovlje'), - (6222, u'\u0160tanjel'), - (6223, u'Komen'), - (6224, u'Seno\u017ee\u010de'), - (6225, u'Hru\u0161evje'), - (6230, u'Postojna'), - (6232, u'Planina'), - (6240, u'Kozina'), - (6242, u'Materija'), - (6243, u'Obrov'), - (6244, u'Podgrad'), - (6250, u'Ilirska Bistrica'), - (6251, u'Ilirska Bistrica - Trnovo'), - (6253, u'Kne\u017eak'), - (6254, u'Jel\u0161ane'), - (6255, u'Prem'), - (6256, u'Ko\u0161ana'), - (6257, u'Pivka'), - (6258, u'Prestranek'), - (6271, u'Dekani'), - (6272, u'Gra\u010di\u0161\u010de'), - (6273, u'Marezige'), - (6274, u'\u0160marje'), - (6275, u'\u010crni Kal'), - (6276, u'Pobegi'), - (6280, u'Ankaran - Ancarano'), - (6281, u'\u0160kofije'), - (6310, u'Izola - Isola'), - (6320, u'Portoro\u017e - Portorose'), - (6330, u'Piran - Pirano'), - (6333, u'Se\u010dovlje - Sicciole'), - (8000, u'Novo mesto'), - (8210, u'Trebnje'), - (8211, u'Dobrni\u010d'), - (8212, u'Velika Loka'), - (8213, u'Veliki Gaber'), - (8216, u'Mirna Pe\u010d'), - (8220, u'\u0160marje\u0161ke Toplice'), - (8222, u'Oto\u010dec'), - (8230, u'Mokronog'), - (8231, u'Trebelno'), - (8232, u'\u0160entrupert'), - (8233, u'Mirna'), - (8250, u'Bre\u017eice'), - (8251, u'\u010cate\u017e ob Savi'), - (8253, u'Arti\u010de'), - (8254, u'Globoko'), - (8255, u'Pi\u0161ece'), - (8256, u'Sromlje'), - (8257, u'Dobova'), - (8258, u'Kapele'), - (8259, u'Bizeljsko'), - (8261, u'Jesenice na Dolenjskem'), - (8262, u'Kr\u0161ka vas'), - (8263, u'Cerklje ob Krki'), - (8270, u'Kr\u0161ko'), - (8272, u'Zdole'), - (8273, u'Leskovec pri Kr\u0161kem'), - (8274, u'Raka'), - (8275, u'\u0160kocjan'), - (8276, u'Bu\u010dka'), - (8280, u'Brestanica'), - (8281, u'Senovo'), - (8282, u'Koprivnica'), - (8283, u'Blanca'), - (8290, u'Sevnica'), - (8292, u'Zabukovje'), - (8293, u'Studenec'), - (8294, u'Bo\u0161tanj'), - (8295, u'Tr\u017ei\u0161\u010de'), - (8296, u'Krmelj'), - (8297, u'\u0160entjan\u017e'), - (8310, u'\u0160entjernej'), - (8311, u'Kostanjevica na Krki'), - (8312, u'Podbo\u010dje'), - (8321, u'Brusnice'), - (8322, u'Stopi\u010de'), - (8323, u'Ur\u0161na sela'), - (8330, u'Metlika'), - (8331, u'Suhor'), - (8332, u'Gradac'), - (8333, u'Semi\u010d'), - (8340, u'\u010crnomelj'), - (8341, u'Adle\u0161i\u010di'), - (8342, u'Stari trg ob Kolpi'), - (8343, u'Dragatu\u0161'), - (8344, u'Vinica pri \u010crnomlju'), - (8350, u'Dolenjske Toplice'), - (8351, u'Stra\u017ea'), - (8360, u'\u017du\u017eemberk'), - (8361, u'Dvor'), - (8362, u'Hinje'), - (9000, u'Murska Sobota'), - (9201, u'Puconci'), - (9202, u'Ma\u010dkovci'), - (9203, u'Petrovci'), - (9204, u'\u0160alovci'), - (9205, u'Hodo\u0161 - Hodos'), - (9206, u'Kri\u017eevci'), - (9207, u'Prosenjakovci - Partosfalva'), - (9208, u'Fokovci'), - (9220, u'Lendava - Lendva'), - (9221, u'Martjanci'), - (9222, u'Bogojina'), - (9223, u'Dobrovnik - Dobronak'), - (9224, u'Turni\u0161\u010de'), - (9225, u'Velika Polana'), - (9226, u'Moravske Toplice'), - (9227, u'Kobilje'), - (9231, u'Beltinci'), - (9232, u'\u010cren\u0161ovci'), - (9233, u'Odranci'), - (9240, u'Ljutomer'), - (9241, u'Ver\u017eej'), - (9242, u'Kri\u017eevci pri Ljutomeru'), - (9243, u'Mala Nedelja'), - (9244, u'Sveti Jurij ob \u0160\u010davnici'), - (9245, u'Spodnji Ivanjci'), - (9250, u'Gornja Radgona'), - (9251, u'Ti\u0161ina'), - (9252, u'Radenci'), - (9253, u'Apa\u010de'), - (9261, u'Cankova'), - (9262, u'Roga\u0161ovci'), - (9263, u'Kuzma'), - (9264, u'Grad'), - (9265, u'Bodonci'), + (1000, 'Ljubljana'), + (1215, 'Medvode'), + (1216, 'Smlednik'), + (1217, 'Vodice'), + (1218, 'Komenda'), + (1219, 'Laze v Tuhinju'), + (1221, 'Motnik'), + (1222, 'Trojane'), + (1223, 'Blagovica'), + (1225, 'Lukovica'), + (1230, 'Dom\u017eale'), + (1233, 'Dob'), + (1234, 'Menge\u0161'), + (1235, 'Radomlje'), + (1236, 'Trzin'), + (1241, 'Kamnik'), + (1242, 'Stahovica'), + (1251, 'Morav\u010de'), + (1252, 'Va\u010de'), + (1262, 'Dol pri Ljubljani'), + (1270, 'Litija'), + (1272, 'Pol\u0161nik'), + (1273, 'Dole pri Litiji'), + (1274, 'Gabrovka'), + (1275, '\u0160martno pri Litiji'), + (1276, 'Primskovo'), + (1281, 'Kresnice'), + (1282, 'Sava'), + (1290, 'Grosuplje'), + (1291, '\u0160kofljica'), + (1292, 'Ig'), + (1293, '\u0160marje - Sap'), + (1294, 'Vi\u0161nja Gora'), + (1295, 'Ivan\u010dna Gorica'), + (1296, '\u0160entvid pri Sti\u010dni'), + (1301, 'Krka'), + (1303, 'Zagradec'), + (1310, 'Ribnica'), + (1311, 'Turjak'), + (1312, 'Videm - Dobrepolje'), + (1313, 'Struge'), + (1314, 'Rob'), + (1315, 'Velike La\u0161\u010de'), + (1316, 'Ortnek'), + (1317, 'Sodra\u017eica'), + (1318, 'Lo\u0161ki Potok'), + (1319, 'Draga'), + (1330, 'Ko\u010devje'), + (1331, 'Dolenja vas'), + (1332, 'Stara Cerkev'), + (1336, 'Kostel'), + (1337, 'Osilnica'), + (1338, 'Ko\u010devska Reka'), + (1351, 'Brezovica pri Ljubljani'), + (1352, 'Preserje'), + (1353, 'Borovnica'), + (1354, 'Horjul'), + (1355, 'Polhov Gradec'), + (1356, 'Dobrova'), + (1357, 'Notranje Gorice'), + (1358, 'Log pri Brezovici'), + (1360, 'Vrhnika'), + (1370, 'Logatec'), + (1372, 'Hotedr\u0161ica'), + (1373, 'Rovte'), + (1380, 'Cerknica'), + (1381, 'Rakek'), + (1382, 'Begunje pri Cerknici'), + (1384, 'Grahovo'), + (1385, 'Nova vas'), + (1386, 'Stari trg pri Lo\u017eu'), + (1410, 'Zagorje ob Savi'), + (1411, 'Izlake'), + (1412, 'Kisovec'), + (1413, '\u010cem\u0161enik'), + (1414, 'Podkum'), + (1420, 'Trbovlje'), + (1423, 'Dobovec'), + (1430, 'Hrastnik'), + (1431, 'Dol pri Hrastniku'), + (1432, 'Zidani Most'), + (1433, 'Rade\u010de'), + (1434, 'Loka pri Zidanem Mostu'), + (2000, 'Maribor'), + (2201, 'Zgornja Kungota'), + (2204, 'Miklav\u017e na Dravskem polju'), + (2205, 'Star\u0161e'), + (2206, 'Marjeta na Dravskem polju'), + (2208, 'Pohorje'), + (2211, 'Pesnica pri Mariboru'), + (2212, '\u0160entilj v Slovenskih goricah'), + (2213, 'Zgornja Velka'), + (2214, 'Sladki vrh'), + (2215, 'Cer\u0161ak'), + (2221, 'Jarenina'), + (2222, 'Jakobski Dol'), + (2223, 'Jurovski Dol'), + (2229, 'Male\u010dnik'), + (2230, 'Lenart v Slovenskih goricah'), + (2231, 'Pernica'), + (2232, 'Voli\u010dina'), + (2233, 'Sveta Ana v Slovenskih goricah'), + (2234, 'Benedikt'), + (2235, 'Sveta Trojica v Slovenskih goricah'), + (2236, 'Cerkvenjak'), + (2241, 'Spodnji Duplek'), + (2242, 'Zgornja Korena'), + (2250, 'Ptuj'), + (2252, 'Dornava'), + (2253, 'Destrnik'), + (2254, 'Trnovska vas'), + (2255, 'Vitomarci'), + (2256, 'Jur\u0161inci'), + (2257, 'Polen\u0161ak'), + (2258, 'Sveti Toma\u017e'), + (2259, 'Ivanjkovci'), + (2270, 'Ormo\u017e'), + (2272, 'Gori\u0161nica'), + (2273, 'Podgorci'), + (2274, 'Velika Nedelja'), + (2275, 'Miklav\u017e pri Ormo\u017eu'), + (2276, 'Kog'), + (2277, 'Sredi\u0161\u010de ob Dravi'), + (2281, 'Markovci'), + (2282, 'Cirkulane'), + (2283, 'Zavr\u010d'), + (2284, 'Videm pri Ptuju'), + (2285, 'Zgornji Leskovec'), + (2286, 'Podlehnik'), + (2287, '\u017detale'), + (2288, 'Hajdina'), + (2289, 'Stoperce'), + (2310, 'Slovenska Bistrica'), + (2311, 'Ho\u010de'), + (2312, 'Orehova vas'), + (2313, 'Fram'), + (2314, 'Zgornja Polskava'), + (2315, '\u0160martno na Pohorju'), + (2316, 'Zgornja Lo\u017enica'), + (2317, 'Oplotnica'), + (2318, 'Laporje'), + (2319, 'Polj\u010dane'), + (2321, 'Makole'), + (2322, 'Maj\u0161perk'), + (2323, 'Ptujska Gora'), + (2324, 'Lovrenc na Dravskem polju'), + (2325, 'Kidri\u010devo'), + (2326, 'Cirkovce'), + (2327, 'Ra\u010de'), + (2331, 'Pragersko'), + (2341, 'Limbu\u0161'), + (2342, 'Ru\u0161e'), + (2343, 'Fala'), + (2344, 'Lovrenc na Pohorju'), + (2345, 'Bistrica ob Dravi'), + (2351, 'Kamnica'), + (2352, 'Selnica ob Dravi'), + (2353, 'Sv. Duh na Ostrem Vrhu'), + (2354, 'Bresternica'), + (2360, 'Radlje ob Dravi'), + (2361, 'O\u017ebalt'), + (2362, 'Kapla'), + (2363, 'Podvelka'), + (2364, 'Ribnica na Pohorju'), + (2365, 'Vuhred'), + (2366, 'Muta'), + (2367, 'Vuzenica'), + (2370, 'Dravograd'), + (2371, 'Trbonje'), + (2372, 'Libeli\u010de'), + (2373, '\u0160entjan\u017e pri Dravogradu'), + (2380, 'Slovenj Gradec'), + (2381, 'Podgorje pri Slovenj Gradcu'), + (2382, 'Mislinja'), + (2383, '\u0160martno pri Slovenj Gradcu'), + (2390, 'Ravne na Koro\u0161kem'), + (2391, 'Prevalje'), + (2392, 'Me\u017eica'), + (2393, '\u010crna na Koro\u0161kem'), + (2394, 'Kotlje'), + (3000, 'Celje'), + (3201, '\u0160martno v Ro\u017eni dolini'), + (3202, 'Ljube\u010dna'), + (3203, 'Nova Cerkev'), + (3204, 'Dobrna'), + (3205, 'Vitanje'), + (3206, 'Stranice'), + (3210, 'Slovenske Konjice'), + (3211, '\u0160kofja vas'), + (3212, 'Vojnik'), + (3213, 'Frankolovo'), + (3214, 'Zre\u010de'), + (3215, 'Lo\u010de'), + (3220, '\u0160tore'), + (3221, 'Teharje'), + (3222, 'Dramlje'), + (3223, 'Loka pri \u017dusmu'), + (3224, 'Dobje pri Planini'), + (3225, 'Planina pri Sevnici'), + (3230, '\u0160entjur'), + (3231, 'Grobelno'), + (3232, 'Ponikva'), + (3233, 'Kalobje'), + (3240, '\u0160marje pri Jel\u0161ah'), + (3241, 'Podplat'), + (3250, 'Roga\u0161ka Slatina'), + (3252, 'Rogatec'), + (3253, 'Pristava pri Mestinju'), + (3254, 'Pod\u010detrtek'), + (3255, 'Bu\u010de'), + (3256, 'Bistrica ob Sotli'), + (3257, 'Podsreda'), + (3260, 'Kozje'), + (3261, 'Lesi\u010dno'), + (3262, 'Prevorje'), + (3263, 'Gorica pri Slivnici'), + (3264, 'Sveti \u0160tefan'), + (3270, 'La\u0161ko'), + (3271, '\u0160entrupert'), + (3272, 'Rimske Toplice'), + (3273, 'Jurklo\u0161ter'), + (3301, 'Petrov\u010de'), + (3302, 'Gri\u017ee'), + (3303, 'Gomilsko'), + (3304, 'Tabor'), + (3305, 'Vransko'), + (3310, '\u017dalec'), + (3311, '\u0160empeter v Savinjski dolini'), + (3312, 'Prebold'), + (3313, 'Polzela'), + (3314, 'Braslov\u010de'), + (3320, 'Velenje - dostava'), + (3322, 'Velenje - po\u0161tni predali'), + (3325, '\u0160o\u0161tanj'), + (3326, 'Topol\u0161ica'), + (3327, '\u0160martno ob Paki'), + (3330, 'Mozirje'), + (3331, 'Nazarje'), + (3332, 'Re\u010dica ob Savinji'), + (3333, 'Ljubno ob Savinji'), + (3334, 'Lu\u010de'), + (3335, 'Sol\u010dava'), + (3341, '\u0160martno ob Dreti'), + (3342, 'Gornji Grad'), + (4000, 'Kranj'), + (4201, 'Zgornja Besnica'), + (4202, 'Naklo'), + (4203, 'Duplje'), + (4204, 'Golnik'), + (4205, 'Preddvor'), + (4206, 'Zgornje Jezersko'), + (4207, 'Cerklje na Gorenjskem'), + (4208, '\u0160en\u010dur'), + (4209, '\u017dabnica'), + (4210, 'Brnik - aerodrom'), + (4211, 'Mav\u010di\u010de'), + (4212, 'Visoko'), + (4220, '\u0160kofja Loka'), + (4223, 'Poljane nad \u0160kofjo Loko'), + (4224, 'Gorenja vas'), + (4225, 'Sovodenj'), + (4226, '\u017diri'), + (4227, 'Selca'), + (4228, '\u017delezniki'), + (4229, 'Sorica'), + (4240, 'Radovljica'), + (4243, 'Brezje'), + (4244, 'Podnart'), + (4245, 'Kropa'), + (4246, 'Kamna Gorica'), + (4247, 'Zgornje Gorje'), + (4248, 'Lesce'), + (4260, 'Bled'), + (4263, 'Bohinjska Bela'), + (4264, 'Bohinjska Bistrica'), + (4265, 'Bohinjsko jezero'), + (4267, 'Srednja vas v Bohinju'), + (4270, 'Jesenice'), + (4273, 'Blejska Dobrava'), + (4274, '\u017dirovnica'), + (4275, 'Begunje na Gorenjskem'), + (4276, 'Hru\u0161ica'), + (4280, 'Kranjska Gora'), + (4281, 'Mojstrana'), + (4282, 'Gozd Martuljek'), + (4283, 'Rate\u010de - Planica'), + (4290, 'Tr\u017ei\u010d'), + (4294, 'Kri\u017ee'), + (5000, 'Nova Gorica'), + (5210, 'Deskle'), + (5211, 'Kojsko'), + (5212, 'Dobrovo v Brdih'), + (5213, 'Kanal'), + (5214, 'Kal nad Kanalom'), + (5215, 'Ro\u010dinj'), + (5216, 'Most na So\u010di'), + (5220, 'Tolmin'), + (5222, 'Kobarid'), + (5223, 'Breginj'), + (5224, 'Srpenica'), + (5230, 'Bovec'), + (5231, 'Log pod Mangartom'), + (5232, 'So\u010da'), + (5242, 'Grahovo ob Ba\u010di'), + (5243, 'Podbrdo'), + (5250, 'Solkan'), + (5251, 'Grgar'), + (5252, 'Trnovo pri Gorici'), + (5253, '\u010cepovan'), + (5261, '\u0160empas'), + (5262, '\u010crni\u010de'), + (5263, 'Dobravlje'), + (5270, 'Ajdov\u0161\u010dina'), + (5271, 'Vipava'), + (5272, 'Podnanos'), + (5273, 'Col'), + (5274, '\u010crni Vrh nad Idrijo'), + (5275, 'Godovi\u010d'), + (5280, 'Idrija'), + (5281, 'Spodnja Idrija'), + (5282, 'Cerkno'), + (5283, 'Slap ob Idrijci'), + (5290, '\u0160empeter pri Gorici'), + (5291, 'Miren'), + (5292, 'Ren\u010de'), + (5293, 'Vol\u010dja Draga'), + (5294, 'Dornberk'), + (5295, 'Branik'), + (5296, 'Kostanjevica na Krasu'), + (5297, 'Prva\u010dina'), + (6000, 'Koper'), + (6210, 'Se\u017eana'), + (6215, 'Diva\u010da'), + (6216, 'Podgorje'), + (6217, 'Vremski Britof'), + (6219, 'Lokev'), + (6221, 'Dutovlje'), + (6222, '\u0160tanjel'), + (6223, 'Komen'), + (6224, 'Seno\u017ee\u010de'), + (6225, 'Hru\u0161evje'), + (6230, 'Postojna'), + (6232, 'Planina'), + (6240, 'Kozina'), + (6242, 'Materija'), + (6243, 'Obrov'), + (6244, 'Podgrad'), + (6250, 'Ilirska Bistrica'), + (6251, 'Ilirska Bistrica - Trnovo'), + (6253, 'Kne\u017eak'), + (6254, 'Jel\u0161ane'), + (6255, 'Prem'), + (6256, 'Ko\u0161ana'), + (6257, 'Pivka'), + (6258, 'Prestranek'), + (6271, 'Dekani'), + (6272, 'Gra\u010di\u0161\u010de'), + (6273, 'Marezige'), + (6274, '\u0160marje'), + (6275, '\u010crni Kal'), + (6276, 'Pobegi'), + (6280, 'Ankaran - Ancarano'), + (6281, '\u0160kofije'), + (6310, 'Izola - Isola'), + (6320, 'Portoro\u017e - Portorose'), + (6330, 'Piran - Pirano'), + (6333, 'Se\u010dovlje - Sicciole'), + (8000, 'Novo mesto'), + (8210, 'Trebnje'), + (8211, 'Dobrni\u010d'), + (8212, 'Velika Loka'), + (8213, 'Veliki Gaber'), + (8216, 'Mirna Pe\u010d'), + (8220, '\u0160marje\u0161ke Toplice'), + (8222, 'Oto\u010dec'), + (8230, 'Mokronog'), + (8231, 'Trebelno'), + (8232, '\u0160entrupert'), + (8233, 'Mirna'), + (8250, 'Bre\u017eice'), + (8251, '\u010cate\u017e ob Savi'), + (8253, 'Arti\u010de'), + (8254, 'Globoko'), + (8255, 'Pi\u0161ece'), + (8256, 'Sromlje'), + (8257, 'Dobova'), + (8258, 'Kapele'), + (8259, 'Bizeljsko'), + (8261, 'Jesenice na Dolenjskem'), + (8262, 'Kr\u0161ka vas'), + (8263, 'Cerklje ob Krki'), + (8270, 'Kr\u0161ko'), + (8272, 'Zdole'), + (8273, 'Leskovec pri Kr\u0161kem'), + (8274, 'Raka'), + (8275, '\u0160kocjan'), + (8276, 'Bu\u010dka'), + (8280, 'Brestanica'), + (8281, 'Senovo'), + (8282, 'Koprivnica'), + (8283, 'Blanca'), + (8290, 'Sevnica'), + (8292, 'Zabukovje'), + (8293, 'Studenec'), + (8294, 'Bo\u0161tanj'), + (8295, 'Tr\u017ei\u0161\u010de'), + (8296, 'Krmelj'), + (8297, '\u0160entjan\u017e'), + (8310, '\u0160entjernej'), + (8311, 'Kostanjevica na Krki'), + (8312, 'Podbo\u010dje'), + (8321, 'Brusnice'), + (8322, 'Stopi\u010de'), + (8323, 'Ur\u0161na sela'), + (8330, 'Metlika'), + (8331, 'Suhor'), + (8332, 'Gradac'), + (8333, 'Semi\u010d'), + (8340, '\u010crnomelj'), + (8341, 'Adle\u0161i\u010di'), + (8342, 'Stari trg ob Kolpi'), + (8343, 'Dragatu\u0161'), + (8344, 'Vinica pri \u010crnomlju'), + (8350, 'Dolenjske Toplice'), + (8351, 'Stra\u017ea'), + (8360, '\u017du\u017eemberk'), + (8361, 'Dvor'), + (8362, 'Hinje'), + (9000, 'Murska Sobota'), + (9201, 'Puconci'), + (9202, 'Ma\u010dkovci'), + (9203, 'Petrovci'), + (9204, '\u0160alovci'), + (9205, 'Hodo\u0161 - Hodos'), + (9206, 'Kri\u017eevci'), + (9207, 'Prosenjakovci - Partosfalva'), + (9208, 'Fokovci'), + (9220, 'Lendava - Lendva'), + (9221, 'Martjanci'), + (9222, 'Bogojina'), + (9223, 'Dobrovnik - Dobronak'), + (9224, 'Turni\u0161\u010de'), + (9225, 'Velika Polana'), + (9226, 'Moravske Toplice'), + (9227, 'Kobilje'), + (9231, 'Beltinci'), + (9232, '\u010cren\u0161ovci'), + (9233, 'Odranci'), + (9240, 'Ljutomer'), + (9241, 'Ver\u017eej'), + (9242, 'Kri\u017eevci pri Ljutomeru'), + (9243, 'Mala Nedelja'), + (9244, 'Sveti Jurij ob \u0160\u010davnici'), + (9245, 'Spodnji Ivanjci'), + (9250, 'Gornja Radgona'), + (9251, 'Ti\u0161ina'), + (9252, 'Radenci'), + (9253, 'Apa\u010de'), + (9261, 'Cankova'), + (9262, 'Roga\u0161ovci'), + (9263, 'Kuzma'), + (9264, 'Grad'), + (9265, 'Bodonci'), ] SI_POSTALCODES_CHOICES = sorted(SI_POSTALCODES, key=lambda k: k[1]) diff --git a/django/contrib/localflavor/sk/forms.py b/django/contrib/localflavor/sk/forms.py index 83afeb41b9..11d44cc4d2 100644 --- a/django/contrib/localflavor/sk/forms.py +++ b/django/contrib/localflavor/sk/forms.py @@ -2,7 +2,7 @@ Slovak-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.sk.sk_districts import DISTRICT_CHOICES from django.contrib.localflavor.sk.sk_regions import REGION_CHOICES @@ -30,7 +30,7 @@ class SKPostalCodeField(RegexField): Valid form is XXXXX or XXX XX, where X represents integer. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX or XXX XX.'), + 'invalid': _('Enter a postal code in the format XXXXX or XXX XX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): diff --git a/django/contrib/localflavor/tr/forms.py b/django/contrib/localflavor/tr/forms.py index 77a2b41986..1ffbc17361 100644 --- a/django/contrib/localflavor/tr/forms.py +++ b/django/contrib/localflavor/tr/forms.py @@ -2,7 +2,7 @@ TR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -18,7 +18,7 @@ phone_digits_re = re.compile(r'^(\+90|0)? ?(([1-9]\d{2})|\([1-9]\d{2}\)) ?([2-9] class TRPostalCodeField(RegexField): default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX.'), + 'invalid': _('Enter a postal code in the format XXXXX.'), } def __init__(self, max_length=5, min_length=5, *args, **kwargs): @@ -28,7 +28,7 @@ class TRPostalCodeField(RegexField): def clean(self, value): value = super(TRPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if len(value) != 5: raise ValidationError(self.error_messages['invalid']) province_code = int(value[:2]) @@ -39,17 +39,17 @@ class TRPostalCodeField(RegexField): class TRPhoneNumberField(CharField): default_error_messages = { - 'invalid': _(u'Phone numbers must be in 0XXX XXX XXXX format.'), + 'invalid': _('Phone numbers must be in 0XXX XXX XXXX format.'), } def clean(self, value): super(TRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: - return u'%s%s' % (m.group(2), m.group(4)) + return '%s%s' % (m.group(2), m.group(4)) raise ValidationError(self.error_messages['invalid']) class TRIdentificationNumberField(Field): @@ -66,14 +66,14 @@ class TRIdentificationNumberField(Field): sum(1st to 10th) % 10 = 11th digit """ default_error_messages = { - 'invalid': _(u'Enter a valid Turkish Identification number.'), - 'not_11': _(u'Turkish Identification number must be 11 digits.'), + 'invalid': _('Enter a valid Turkish Identification number.'), + 'not_11': _('Turkish Identification number must be 11 digits.'), } def clean(self, value): super(TRIdentificationNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if len(value) != 11: raise ValidationError(self.error_messages['not_11']) if not re.match(r'^\d{11}$', value): diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py index 0a79c40a47..33e533b4ce 100644 --- a/django/contrib/localflavor/us/forms.py +++ b/django/contrib/localflavor/us/forms.py @@ -2,7 +2,7 @@ USA-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -33,11 +33,11 @@ class USPhoneNumberField(CharField): def clean(self, value): super(USPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: - return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) + return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) raise ValidationError(self.error_messages['invalid']) class USSocialSecurityNumberField(Field): @@ -62,7 +62,7 @@ class USSocialSecurityNumberField(Field): def clean(self, value): super(USSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(ssn_re, value) if not match: raise ValidationError(self.error_messages['invalid']) @@ -80,7 +80,7 @@ class USSocialSecurityNumberField(Field): value == '078-05-1120' or \ value == '219-09-9999': raise ValidationError(self.error_messages['invalid']) - return u'%s-%s-%s' % (area, group, serial) + return '%s-%s-%s' % (area, group, serial) class USStateField(Field): """ @@ -96,7 +96,7 @@ class USStateField(Field): from django.contrib.localflavor.us.us_states import STATES_NORMALIZED super(USStateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().lower() except AttributeError: diff --git a/django/contrib/localflavor/uy/forms.py b/django/contrib/localflavor/uy/forms.py index 211216222d..658defc0f0 100644 --- a/django/contrib/localflavor/uy/forms.py +++ b/django/contrib/localflavor/uy/forms.py @@ -3,7 +3,7 @@ UY-specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.core.validators import EMPTY_VALUES from django.forms.fields import Select, RegexField @@ -47,7 +47,7 @@ class UYCIField(RegexField): value = super(UYCIField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = self.regex.match(value) if not match: raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/uy/uy_departaments.py b/django/contrib/localflavor/uy/uy_departaments.py index 97795f8e82..800937c582 100644 --- a/django/contrib/localflavor/uy/uy_departaments.py +++ b/django/contrib/localflavor/uy/uy_departaments.py @@ -1,24 +1,25 @@ # -*- coding: utf-8 -*- """A list of Urguayan departaments as `choices` in a formfield.""" +from __future__ import unicode_literals DEPARTAMENT_CHOICES = ( - ('G', u'Artigas'), - ('A', u'Canelones'), - ('E', u'Cerro Largo'), - ('L', u'Colonia'), - ('Q', u'Durazno'), - ('N', u'Flores'), - ('O', u'Florida'), - ('P', u'Lavalleja'), - ('B', u'Maldonado'), - ('S', u'Montevideo'), - ('I', u'Paysandú'), - ('J', u'Río Negro'), - ('F', u'Rivera'), - ('C', u'Rocha'), - ('H', u'Salto'), - ('M', u'San José'), - ('K', u'Soriano'), - ('R', u'Tacuarembó'), - ('D', u'Treinta y Tres'), + ('G', 'Artigas'), + ('A', 'Canelones'), + ('E', 'Cerro Largo'), + ('L', 'Colonia'), + ('Q', 'Durazno'), + ('N', 'Flores'), + ('O', 'Florida'), + ('P', 'Lavalleja'), + ('B', 'Maldonado'), + ('S', 'Montevideo'), + ('I', 'Paysandú'), + ('J', 'Río Negro'), + ('F', 'Rivera'), + ('C', 'Rocha'), + ('H', 'Salto'), + ('M', 'San José'), + ('K', 'Soriano'), + ('R', 'Tacuarembó'), + ('D', 'Treinta y Tres'), ) diff --git a/django/contrib/localflavor/za/forms.py b/django/contrib/localflavor/za/forms.py index a9e2cd60c8..a818c14428 100644 --- a/django/contrib/localflavor/za/forms.py +++ b/django/contrib/localflavor/za/forms.py @@ -1,6 +1,7 @@ """ South Africa-specific Form helpers """ +from __future__ import unicode_literals from django.core.validators import EMPTY_VALUES from django.forms import ValidationError @@ -18,14 +19,14 @@ class ZAIDField(CharField): check for the birthdate """ default_error_messages = { - 'invalid': _(u'Enter a valid South African ID number'), + 'invalid': _('Enter a valid South African ID number'), } def clean(self, value): super(ZAIDField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # strip spaces and dashes value = value.strip().replace(' ', '').replace('-', '') @@ -52,7 +53,7 @@ class ZAIDField(CharField): class ZAPostCodeField(RegexField): default_error_messages = { - 'invalid': _(u'Enter a valid South African postal code'), + 'invalid': _('Enter a valid South African postal code'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): diff --git a/django/contrib/messages/storage/base.py b/django/contrib/messages/storage/base.py index 65e8526b5c..e80818e84e 100644 --- a/django/contrib/messages/storage/base.py +++ b/django/contrib/messages/storage/base.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.utils.encoding import force_unicode, StrAndUnicode from django.contrib.messages import constants, utils @@ -41,7 +43,7 @@ class Message(StrAndUnicode): strings_only=True) extra_tags = force_unicode(self.extra_tags, strings_only=True) if extra_tags and label_tag: - return u' '.join([extra_tags, label_tag]) + return ' '.join([extra_tags, label_tag]) elif extra_tags: return extra_tags elif label_tag: diff --git a/django/contrib/sitemaps/tests/flatpages.py b/django/contrib/sitemaps/tests/flatpages.py index a40876e859..930f24f22c 100644 --- a/django/contrib/sitemaps/tests/flatpages.py +++ b/django/contrib/sitemaps/tests/flatpages.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.utils.unittest import skipUnless @@ -17,15 +19,15 @@ class FlatpagesSitemapTests(SitemapTestsBase): from django.contrib.flatpages.models import FlatPage public = FlatPage.objects.create( - url=u'/public/', - title=u'Public Page', + url='/public/', + title='Public Page', enable_comments=True, registration_required=False, ) public.sites.add(settings.SITE_ID) private = FlatPage.objects.create( - url=u'/private/', - title=u'Private Page', + url='/private/', + title='Private Page', enable_comments=True, registration_required=True ) diff --git a/django/contrib/sitemaps/tests/http.py b/django/contrib/sitemaps/tests/http.py index 5786aa48d5..d71907e09a 100644 --- a/django/contrib/sitemaps/tests/http.py +++ b/django/contrib/sitemaps/tests/http.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from datetime import date @@ -74,7 +76,7 @@ class HTTPSitemapTests(SitemapTestsBase): def test_localized_priority(self): "The priority value should not be localized (Refs #14164)" activate('fr') - self.assertEqual(u'0,3', localize(0.3)) + self.assertEqual('0,3', localize(0.3)) # Retrieve the sitemap. Check that priorities # haven't been rendered in localized format diff --git a/django/contrib/sites/tests.py b/django/contrib/sites/tests.py index 828badb386..1bb2495e6b 100644 --- a/django/contrib/sites/tests.py +++ b/django/contrib/sites/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.sites.models import Site, RequestSite, get_current_site from django.core.exceptions import ObjectDoesNotExist @@ -32,12 +34,12 @@ class SitesFrameworkTests(TestCase): # After updating a Site object (e.g. via the admin), we shouldn't return a # bogus value from the SITE_CACHE. site = Site.objects.get_current() - self.assertEqual(u"example.com", site.name) + self.assertEqual("example.com", site.name) s2 = Site.objects.get(id=settings.SITE_ID) s2.name = "Example site" s2.save() site = Site.objects.get_current() - self.assertEqual(u"Example site", site.name) + self.assertEqual("Example site", site.name) def test_get_current_site(self): # Test that the correct Site object is returned @@ -59,4 +61,4 @@ class SitesFrameworkTests(TestCase): Site._meta.installed = False site = get_current_site(request) self.assertTrue(isinstance(site, RequestSite)) - self.assertEqual(site.name, u"example.com") + self.assertEqual(site.name, "example.com") diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 669c04043b..d3977213a9 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os import sys from optparse import make_option @@ -117,11 +119,11 @@ class Command(NoArgsCommand): dry_run=self.dry_run) for original_path, processed_path, processed in processor: if processed: - self.log(u"Post-processed '%s' as '%s" % + self.log("Post-processed '%s' as '%s" % (original_path, processed_path), level=1) self.post_processed_files.append(original_path) else: - self.log(u"Skipped post-processing '%s'" % original_path) + self.log("Skipped post-processing '%s'" % original_path) return { 'modified': self.copied_files + self.symlinked_files, @@ -146,7 +148,7 @@ class Command(NoArgsCommand): clear_display = 'This will overwrite existing files!' if self.interactive: - confirm = raw_input(u""" + confirm = raw_input(""" You have requested to collect static files at the destination location as specified in your settings%s @@ -195,10 +197,10 @@ Type 'yes' to continue, or 'no' to cancel: """ for f in files: fpath = os.path.join(path, f) if self.dry_run: - self.log(u"Pretending to delete '%s'" % + self.log("Pretending to delete '%s'" % smart_unicode(fpath), level=1) else: - self.log(u"Deleting '%s'" % smart_unicode(fpath), level=1) + self.log("Deleting '%s'" % smart_unicode(fpath), level=1) self.storage.delete(fpath) for d in dirs: self.clear_dir(os.path.join(path, d)) @@ -235,13 +237,13 @@ Type 'yes' to continue, or 'no' to cancel: """ and os.path.islink(full_path))): if prefixed_path not in self.unmodified_files: self.unmodified_files.append(prefixed_path) - self.log(u"Skipping '%s' (not modified)" % path) + self.log("Skipping '%s' (not modified)" % path) return False # Then delete the existing file if really needed if self.dry_run: - self.log(u"Pretending to delete '%s'" % path) + self.log("Pretending to delete '%s'" % path) else: - self.log(u"Deleting '%s'" % path) + self.log("Deleting '%s'" % path) self.storage.delete(prefixed_path) return True @@ -251,7 +253,7 @@ Type 'yes' to continue, or 'no' to cancel: """ """ # Skip this file if it was already copied earlier if prefixed_path in self.symlinked_files: - return self.log(u"Skipping '%s' (already linked earlier)" % path) + return self.log("Skipping '%s' (already linked earlier)" % path) # Delete the target file if needed or break if not self.delete_file(path, prefixed_path, source_storage): return @@ -259,9 +261,9 @@ Type 'yes' to continue, or 'no' to cancel: """ source_path = source_storage.path(path) # Finally link the file if self.dry_run: - self.log(u"Pretending to link '%s'" % source_path, level=1) + self.log("Pretending to link '%s'" % source_path, level=1) else: - self.log(u"Linking '%s'" % source_path, level=1) + self.log("Linking '%s'" % source_path, level=1) full_path = self.storage.path(prefixed_path) try: os.makedirs(os.path.dirname(full_path)) @@ -277,7 +279,7 @@ Type 'yes' to continue, or 'no' to cancel: """ """ # Skip this file if it was already copied earlier if prefixed_path in self.copied_files: - return self.log(u"Skipping '%s' (already copied earlier)" % path) + return self.log("Skipping '%s' (already copied earlier)" % path) # Delete the target file if needed or break if not self.delete_file(path, prefixed_path, source_storage): return @@ -285,9 +287,9 @@ Type 'yes' to continue, or 'no' to cancel: """ source_path = source_storage.path(path) # Finally start copying if self.dry_run: - self.log(u"Pretending to copy '%s'" % source_path, level=1) + self.log("Pretending to copy '%s'" % source_path, level=1) else: - self.log(u"Copying '%s'" % source_path, level=1) + self.log("Copying '%s'" % source_path, level=1) if self.local: full_path = self.storage.path(prefixed_path) try: diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py index dc4406e458..772220b342 100644 --- a/django/contrib/staticfiles/management/commands/findstatic.py +++ b/django/contrib/staticfiles/management/commands/findstatic.py @@ -1,7 +1,9 @@ +from __future__ import unicode_literals + import os from optparse import make_option from django.core.management.base import LabelCommand -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_unicode from django.contrib.staticfiles import finders @@ -21,9 +23,9 @@ class Command(LabelCommand): if result: if not isinstance(result, (list, tuple)): result = [result] - output = u'\n '.join( + output = '\n '.join( (smart_unicode(os.path.realpath(path)) for path in result)) - self.stdout.write(u"Found '%s' here:\n %s" % (path, output)) + self.stdout.write("Found '%s' here:\n %s" % (path, output)) else: if verbosity >= 1: self.stderr.write("No matching file found for '%s'." % path) diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index fd8f9efb02..e02fec8ec0 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import hashlib import os import posixpath @@ -46,8 +47,8 @@ class StaticFilesStorage(FileSystemStorage): class CachedFilesMixin(object): patterns = ( ("*.css", ( - r"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""", - r"""(@import\s*["']\s*(.*?)["'])""", + br"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""", + br"""(@import\s*["']\s*(.*?)["'])""", )), ) @@ -91,8 +92,8 @@ class CachedFilesMixin(object): root, ext = os.path.splitext(filename) file_hash = self.file_hash(clean_name, content) if file_hash is not None: - file_hash = u".%s" % file_hash - hashed_name = os.path.join(path, u"%s%s%s" % + file_hash = ".%s" % file_hash + hashed_name = os.path.join(path, "%s%s%s" % (root, file_hash, ext)) unparsed_name = list(parsed_name) unparsed_name[2] = hashed_name @@ -103,7 +104,7 @@ class CachedFilesMixin(object): return urlunsplit(unparsed_name) def cache_key(self, name): - return u'staticfiles:%s' % hashlib.md5(smart_str(name)).hexdigest() + return 'staticfiles:%s' % hashlib.md5(smart_str(name)).hexdigest() def url(self, name, force=False): """ diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 462b3e94cf..af767e1867 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.sites.models import get_current_site from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist @@ -18,7 +20,7 @@ def add_domain(domain, url, secure=False): or url.startswith('mailto:')): # 'url' must already be ASCII and URL-quoted, so no need for encoding # conversions here. - url = iri_to_uri(u'%s://%s%s' % (protocol, domain, url)) + url = iri_to_uri('%s://%s%s' % (protocol, domain, url)) return url class FeedDoesNotExist(ObjectDoesNotExist): diff --git a/django/contrib/webdesign/lorem_ipsum.py b/django/contrib/webdesign/lorem_ipsum.py index 4ad175d033..01d8f220f5 100644 --- a/django/contrib/webdesign/lorem_ipsum.py +++ b/django/contrib/webdesign/lorem_ipsum.py @@ -2,6 +2,8 @@ Utility functions for generating "lorem ipsum" Latin text. """ +from __future__ import unicode_literals + import random COMMON_P = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' @@ -49,10 +51,10 @@ def sentence(): """ # Determine the number of comma-separated sections and number of words in # each section for this sentence. - sections = [u' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))] - s = u', '.join(sections) + sections = [' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))] + s = ', '.join(sections) # Convert to sentence case and add end punctuation. - return u'%s%s%s' % (s[0].upper(), s[1:], random.choice('?.')) + return '%s%s%s' % (s[0].upper(), s[1:], random.choice('?.')) def paragraph(): """ @@ -60,7 +62,7 @@ def paragraph(): The paragraph consists of between 1 and 4 sentences, inclusive. """ - return u' '.join([sentence() for i in range(random.randint(1, 4))]) + return ' '.join([sentence() for i in range(random.randint(1, 4))]) def paragraphs(count, common=True): """ @@ -98,4 +100,4 @@ def words(count, common=True): word_list += random.sample(WORDS, c) else: word_list = word_list[:count] - return u' '.join(word_list) + return ' '.join(word_list) diff --git a/django/contrib/webdesign/templatetags/webdesign.py b/django/contrib/webdesign/templatetags/webdesign.py index 05d8dc7f54..b870299cda 100644 --- a/django/contrib/webdesign/templatetags/webdesign.py +++ b/django/contrib/webdesign/templatetags/webdesign.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib.webdesign.lorem_ipsum import words, paragraphs from django import template @@ -18,7 +20,7 @@ class LoremNode(template.Node): paras = paragraphs(count, common=self.common) if self.method == 'p': paras = ['

    %s

    ' % p for p in paras] - return u'\n\n'.join(paras) + return '\n\n'.join(paras) @register.tag def lorem(parser, token): diff --git a/django/contrib/webdesign/tests.py b/django/contrib/webdesign/tests.py index 8907ea3ba7..16ec501e44 100644 --- a/django/contrib/webdesign/tests.py +++ b/django/contrib/webdesign/tests.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals import unittest @@ -9,7 +10,7 @@ from django.template import loader, Context class WebdesignTest(unittest.TestCase): def test_words(self): - self.assertEqual(words(7), u'lorem ipsum dolor sit amet consectetur adipisicing') + self.assertEqual(words(7), 'lorem ipsum dolor sit amet consectetur adipisicing') def test_paragraphs(self): self.assertEqual(paragraphs(1), @@ -18,4 +19,4 @@ class WebdesignTest(unittest.TestCase): def test_lorem_tag(self): t = loader.get_template_from_string("{% load webdesign %}{% lorem 3 w %}") self.assertEqual(t.render(Context({})), - u'lorem ipsum dolor') + 'lorem ipsum dolor') diff --git a/django/core/context_processors.py b/django/core/context_processors.py index 3ba519188b..325f64d224 100644 --- a/django/core/context_processors.py +++ b/django/core/context_processors.py @@ -9,6 +9,7 @@ RequestContext. from django.conf import settings from django.middleware.csrf import get_token +from django.utils.encoding import smart_str from django.utils.functional import lazy def csrf(request): @@ -24,7 +25,7 @@ def csrf(request): # instead of returning an empty dict. return b'NOTPROVIDED' else: - return token + return smart_str(token) _get_val = lazy(_get_val, str) return {'csrf_token': _get_val() } diff --git a/django/core/files/base.py b/django/core/files/base.py index a2d703c963..04853fad0c 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from io import BytesIO @@ -19,7 +21,7 @@ class File(FileProxyMixin): return smart_str(self.name or '') def __unicode__(self): - return smart_unicode(self.name or u'') + return smart_unicode(self.name or '') def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self or "None") diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py index 88f78904bb..68d540e595 100644 --- a/django/core/files/uploadhandler.py +++ b/django/core/files/uploadhandler.py @@ -2,6 +2,8 @@ Base file upload handler classes, and the built-in concrete subclasses """ +from __future__ import unicode_literals + from io import BytesIO from django.conf import settings @@ -33,9 +35,9 @@ class StopUpload(UploadFileException): def __unicode__(self): if self.connection_reset: - return u'StopUpload: Halt current upload.' + return 'StopUpload: Halt current upload.' else: - return u'StopUpload: Consume request data, then halt.' + return 'StopUpload: Consume request data, then halt.' class SkipFile(UploadFileException): """ diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 4c9dfc07bc..4c07524ecc 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import sys from django import http @@ -254,9 +256,9 @@ def get_script_name(environ): # rewrites. Unfortunately not every Web server (lighttpd!) passes this # information through all the time, so FORCE_SCRIPT_NAME, above, is still # needed. - script_url = environ.get('SCRIPT_URL', u'') + script_url = environ.get('SCRIPT_URL', '') if not script_url: - script_url = environ.get('REDIRECT_URL', u'') + script_url = environ.get('REDIRECT_URL', '') if script_url: return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))]) - return force_unicode(environ.get('SCRIPT_NAME', u'')) + return force_unicode(environ.get('SCRIPT_NAME', '')) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 128ff7ac64..5215101a35 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import sys from io import BytesIO from threading import Lock @@ -7,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_unicode, iri_to_uri +from django.utils.encoding import force_unicode, smart_str, iri_to_uri from django.utils.log import getLogger logger = getLogger('django.request') @@ -125,7 +127,7 @@ class LimitedStream(object): class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = base.get_script_name(environ) - path_info = force_unicode(environ.get('PATH_INFO', u'/')) + path_info = force_unicode(environ.get('PATH_INFO', '/')) if not path_info or path_info == script_name: # Sometimes PATH_INFO exists, but is empty (e.g. accessing # the SCRIPT_NAME URL without a trailing slash). We really need to @@ -134,7 +136,7 @@ class WSGIRequest(http.HttpRequest): # # (The comparison of path_info to script_name is to work around an # apparent bug in flup 1.0.1. See Django ticket #8490). - path_info = u'/' + path_info = '/' self.environ = environ self.path_info = path_info self.path = '%s%s' % (script_name, path_info) @@ -246,6 +248,6 @@ class WSGIHandler(base.BaseHandler): status = '%s %s' % (response.status_code, status_text) response_headers = [(str(k), str(v)) for k, v in response.items()] for c in response.cookies.values(): - response_headers.append(('Set-Cookie', str(c.output(header='')))) - start_response(status, response_headers) + response_headers.append((b'Set-Cookie', str(c.output(header='')))) + start_response(smart_str(status), response_headers) return response diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py index 1bee0cac9f..08f9702934 100644 --- a/django/core/mail/__init__.py +++ b/django/core/mail/__init__.py @@ -1,6 +1,7 @@ """ Tools for sending email. """ +from __future__ import unicode_literals from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -89,7 +90,7 @@ def mail_admins(subject, message, fail_silently=False, connection=None, """Sends a message to the admins, as defined by the ADMINS setting.""" if not settings.ADMINS: return - mail = EmailMultiAlternatives(u'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), + mail = EmailMultiAlternatives('%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], connection=connection) if html_message: @@ -102,7 +103,7 @@ def mail_managers(subject, message, fail_silently=False, connection=None, """Sends a message to the managers, as defined by the MANAGERS setting.""" if not settings.MANAGERS: return - mail = EmailMultiAlternatives(u'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), + mail = EmailMultiAlternatives('%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], connection=connection) if html_message: diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 2618c7f17d..82f6b6900f 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import mimetypes import os import random @@ -90,7 +92,7 @@ def forbid_multi_line_headers(name, val, encoding): else: if name.lower() == 'subject': val = Header(val) - return name, val + return smart_str(name), val def sanitize_address(addr, encoding): @@ -101,8 +103,8 @@ def sanitize_address(addr, encoding): try: addr = addr.encode('ascii') except UnicodeEncodeError: # IDN - if u'@' in addr: - localpart, domain = addr.split(u'@', 1) + if '@' in addr: + localpart, domain = addr.split('@', 1) localpart = str(Header(localpart, encoding)) domain = domain.encode('idna') addr = '@'.join([localpart, domain]) diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index bcc47e17c8..fd6dbbbd2c 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -4,6 +4,8 @@ from django.core.cache.backends.db import BaseDatabaseCache from django.core.management.base import LabelCommand, CommandError from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.db.utils import DatabaseError +from django.utils.encoding import force_unicode + class Command(LabelCommand): help = "Creates the table needed to use the SQL cache backend." @@ -58,7 +60,7 @@ class Command(LabelCommand): transaction.rollback_unless_managed(using=db) raise CommandError( "Cache table '%s' could not be created.\nThe error was: %s." % - (tablename, e)) + (tablename, force_unicode(e))) for statement in index_output: curs.execute(statement) transaction.commit_unless_managed(using=db) diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 078fd6fa27..34f8041d33 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import sys import os import gzip @@ -12,6 +14,7 @@ from django.core.management.color import no_style from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS, IntegrityError, DatabaseError) from django.db.models import get_apps +from django.utils.encoding import force_unicode from itertools import product try: @@ -186,7 +189,7 @@ class Command(BaseCommand): 'app_label': obj.object._meta.app_label, 'object_name': obj.object._meta.object_name, 'pk': obj.object.pk, - 'error_msg': e + 'error_msg': force_unicode(e) },) raise diff --git a/django/core/management/commands/sql.py b/django/core/management/commands/sql.py index 59b2e77b69..52b2058650 100644 --- a/django/core/management/commands/sql.py +++ b/django/core/management/commands/sql.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -16,4 +18,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_create(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_create(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlall.py b/django/core/management/commands/sqlall.py index ab702bfffb..6d0735a6f9 100644 --- a/django/core/management/commands/sqlall.py +++ b/django/core/management/commands/sqlall.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -16,4 +18,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_all(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_all(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlclear.py b/django/core/management/commands/sqlclear.py index b8f491392b..ec2602d2a3 100644 --- a/django/core/management/commands/sqlclear.py +++ b/django/core/management/commands/sqlclear.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -16,4 +18,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_delete(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_delete(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlcustom.py b/django/core/management/commands/sqlcustom.py index 6a984560de..0d46c4ec70 100644 --- a/django/core/management/commands/sqlcustom.py +++ b/django/core/management/commands/sqlcustom.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -16,4 +18,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_custom(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_custom(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlflush.py b/django/core/management/commands/sqlflush.py index 19054fbfa9..b98ecfd8ff 100644 --- a/django/core/management/commands/sqlflush.py +++ b/django/core/management/commands/sqlflush.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import NoArgsCommand @@ -16,4 +18,4 @@ class Command(NoArgsCommand): output_transaction = True def handle_noargs(self, **options): - return u'\n'.join(sql_flush(self.style, connections[options.get('database')], only_django=True)).encode('utf-8') + return '\n'.join(sql_flush(self.style, connections[options.get('database')], only_django=True)) diff --git a/django/core/management/commands/sqlindexes.py b/django/core/management/commands/sqlindexes.py index bf55b0dffc..f95d4f158c 100644 --- a/django/core/management/commands/sqlindexes.py +++ b/django/core/management/commands/sqlindexes.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -17,4 +19,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_indexes(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_indexes(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlsequencereset.py b/django/core/management/commands/sqlsequencereset.py index 6460f00062..7b9e85a9ee 100644 --- a/django/core/management/commands/sqlsequencereset.py +++ b/django/core/management/commands/sqlsequencereset.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -17,4 +19,4 @@ class Command(AppCommand): def handle_app(self, app, **options): connection = connections[options.get('database')] - return u'\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app, include_auto_created=True))).encode('utf-8') + return '\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app, include_auto_created=True))) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 53d9736293..993d75aa6b 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os import re @@ -160,9 +162,9 @@ def custom_sql_for_model(model, style, connection): with open(sql_file, 'U') as fp: for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): # Remove any comments from the file - statement = re.sub(ur"--.*([\n\Z]|$)", "", statement) + statement = re.sub(r"--.*([\n\Z]|$)", "", statement) if statement.strip(): - output.append(statement + u";") + output.append(statement + ";") return output diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 272ac5be41..7cd6958abf 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -1,6 +1,7 @@ import sys from django.core.management.color import color_style +from django.utils.encoding import smart_str from django.utils.itercompat import is_iterable class ModelErrorCollection: @@ -11,7 +12,7 @@ class ModelErrorCollection: def add(self, context, error): self.errors.append((context, error)) - self.outfile.write(self.style.ERROR("%s: %s\n" % (context, error))) + self.outfile.write(self.style.ERROR(smart_str("%s: %s\n" % (context, error)))) def get_validation_errors(outfile, app=None): """ diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index fce00600f4..1b9a61750d 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -13,6 +13,7 @@ from io import BytesIO 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_str from django.utils.timezone import is_aware class Serializer(PythonSerializer): @@ -61,8 +62,10 @@ def Deserializer(stream_or_string, **options): """ Deserialize a stream or string of JSON data. """ - if isinstance(stream_or_string, basestring): + if isinstance(stream_or_string, bytes): stream = BytesIO(stream_or_string) + elif isinstance(stream_or_string, unicode): + stream = BytesIO(smart_str(stream_or_string)) else: stream = stream_or_string try: diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 49120434eb..333161c929 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -3,6 +3,7 @@ A Python "serializer". Doesn't do much serializing per se -- just converts to and from basic Python data types (lists, dicts, strings, etc.). Useful as a basis for other serializers. """ +from __future__ import unicode_literals from django.conf import settings from django.core.serializers import base @@ -138,5 +139,5 @@ def _get_model(model_identifier): except TypeError: Model = None if Model is None: - raise base.DeserializationError(u"Invalid model identifier: '%s'" % model_identifier) + raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier) return Model diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index a5edeac5af..9d9c023b64 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -2,6 +2,8 @@ XML serializer. """ +from __future__ import unicode_literals + from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS @@ -289,4 +291,4 @@ def getInnerText(node): inner_text.extend(getInnerText(child)) else: pass - return u"".join(inner_text) + return "".join(inner_text) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 78ce00511c..625ec6348b 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -6,6 +6,7 @@ a string) and returns a tuple in this format: (view_function, function_args, function_kwargs) """ +from __future__ import unicode_literals import re from threading import local @@ -182,7 +183,7 @@ class RegexURLPattern(LocaleRegexProvider): self.name = name def __repr__(self): - return smart_str(u'<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) + return smart_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) def add_prefix(self, prefix): """ @@ -232,7 +233,7 @@ class RegexURLResolver(LocaleRegexProvider): self._app_dict = {} def __repr__(self): - return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) + return smart_str('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) def _populate(self): lookups = MultiValueDict() @@ -379,7 +380,7 @@ class RegexURLResolver(LocaleRegexProvider): continue unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()]) candidate = (prefix_norm + result) % unicode_kwargs - if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE): + if re.search('^%s%s' % (_prefix, pattern), candidate, re.UNICODE): return candidate # lookup_view can be URL label, or dotted path, or callable, Any of # these can be passed in at the top, but callables are not friendly in @@ -497,7 +498,7 @@ def get_script_prefix(): wishes to construct their own URLs manually (although accessing the request instance is normally going to be a lot cleaner). """ - return getattr(_prefixes, "value", u'/') + return getattr(_prefixes, "value", '/') def set_urlconf(urlconf_name): """ diff --git a/django/core/validators.py b/django/core/validators.py index 3d4bcc86c8..c7c89786da 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import re import urlparse @@ -11,7 +13,7 @@ EMPTY_VALUES = (None, '', [], (), {}) class RegexValidator(object): regex = '' - message = _(u'Enter a valid value.') + message = _('Enter a valid value.') code = 'invalid' def __init__(self, regex=None, message=None, code=None): @@ -75,13 +77,13 @@ class EmailValidator(RegexValidator): super(EmailValidator, self).__call__(value) except ValidationError as e: # Trivial case failed. Try for possible IDN domain-part - if value and u'@' in value: - parts = value.split(u'@') + if value and '@' in value: + parts = value.split('@') try: parts[-1] = parts[-1].encode('idna') except UnicodeError: raise e - super(EmailValidator, self).__call__(u'@'.join(parts)) + super(EmailValidator, self).__call__('@'.join(parts)) else: raise @@ -91,17 +93,17 @@ email_re = re.compile( r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$)' # domain 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, _(u'Enter a valid e-mail address.'), 'invalid') +validate_email = EmailValidator(email_re, _('Enter a valid e-mail address.'), 'invalid') slug_re = re.compile(r'^[-\w]+$') -validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') +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}$') -validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid') +validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid') def validate_ipv6_address(value): if not is_valid_ipv6_address(value): - raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid') + raise ValidationError(_('Enter a valid IPv6 address.'), code='invalid') def validate_ipv46_address(value): try: @@ -110,7 +112,7 @@ def validate_ipv46_address(value): try: validate_ipv6_address(value) except ValidationError: - raise ValidationError(_(u'Enter a valid IPv4 or IPv6 address.'), code='invalid') + raise ValidationError(_('Enter a valid IPv4 or IPv6 address.'), code='invalid') ip_address_validator_map = { 'both': ([validate_ipv46_address], _('Enter a valid IPv4 or IPv6 address.')), @@ -135,13 +137,13 @@ def ip_address_validators(protocol, unpack_ipv4): % (protocol, ip_address_validator_map.keys())) comma_separated_int_list_re = re.compile('^[\d,]+$') -validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid') +validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid') class BaseValidator(object): compare = lambda self, a, b: a is not b clean = lambda self, x: x - message = _(u'Ensure this value is %(limit_value)s (it is %(show_value)s).') + message = _('Ensure this value is %(limit_value)s (it is %(show_value)s).') code = 'limit_value' def __init__(self, limit_value): @@ -159,23 +161,23 @@ class BaseValidator(object): class MaxValueValidator(BaseValidator): compare = lambda self, a, b: a > b - message = _(u'Ensure this value is less than or equal to %(limit_value)s.') + message = _('Ensure this value is less than or equal to %(limit_value)s.') code = 'max_value' class MinValueValidator(BaseValidator): compare = lambda self, a, b: a < b - message = _(u'Ensure this value is greater than or equal to %(limit_value)s.') + message = _('Ensure this value is greater than or equal to %(limit_value)s.') code = 'min_value' class MinLengthValidator(BaseValidator): compare = lambda self, a, b: a < b clean = lambda self, x: len(x) - message = _(u'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).') + message = _('Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).') code = 'min_length' class MaxLengthValidator(BaseValidator): compare = lambda self, a, b: a > b clean = lambda self, x: len(x) - message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') + message = _('Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') code = 'max_length' diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 1df487bd92..ff227b6e7e 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -3,6 +3,7 @@ MySQL database backend for Django. Requires MySQLdb: http://sourceforge.net/projects/mysql-python """ +from __future__ import unicode_literals import datetime import re @@ -61,8 +62,8 @@ def adapt_datetime_with_timezone_support(value, conv): # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL. if settings.USE_TZ: if timezone.is_naive(value): - warnings.warn(u"SQLite received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("SQLite received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index fe512dfee5..785e4b72a6 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -3,7 +3,7 @@ Oracle database backend for Django. Requires cx_Oracle: http://cx-oracle.sourceforge.net/ """ - +from __future__ import unicode_literals import datetime import decimal @@ -160,7 +160,7 @@ WHEN (new.%(col_name)s IS NULL) # string instead of null, but only if the field accepts the # empty string. if value is None and field and field.empty_strings_allowed: - value = u'' + value = '' # Convert 1 or 0 to True or False elif value in (1, 0) and field and field.get_internal_type() in ('BooleanField', 'NullBooleanField'): value = bool(value) @@ -235,7 +235,7 @@ WHEN (new.%(col_name)s IS NULL) def process_clob(self, value): if value is None: - return u'' + return '' return force_unicode(value.read()) def quote_name(self, name): @@ -567,8 +567,8 @@ class OracleParam(object): # without being converted by DateTimeField.get_db_prep_value. if settings.USE_TZ and isinstance(param, datetime.datetime): if timezone.is_naive(param): - warnings.warn(u"Oracle received a naive datetime (%s)" - u" while time zone support is active." % param, + warnings.warn("Oracle received a naive datetime (%s)" + " while time zone support is active." % param, RuntimeWarning) default_timezone = timezone.get_default_timezone() param = timezone.make_aware(param, default_timezone) diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py index fbf7a22769..99573b9019 100644 --- a/django/db/backends/postgresql_psycopg2/introspection.py +++ b/django/db/backends/postgresql_psycopg2/introspection.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.db.backends import BaseDatabaseIntrospection @@ -43,7 +45,7 @@ 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]]==u'YES']) + return [tuple([item for item in 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/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index 395cd92047..46b464b551 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.db.backends import BaseDatabaseOperations @@ -21,14 +23,14 @@ class DatabaseOperations(BaseDatabaseOperations): """ modifiers = [] if timedelta.days: - modifiers.append(u'%s days' % timedelta.days) + modifiers.append('%s days' % timedelta.days) if timedelta.seconds: - modifiers.append(u'%s seconds' % timedelta.seconds) + modifiers.append('%s seconds' % timedelta.seconds) if timedelta.microseconds: - modifiers.append(u'%s microseconds' % timedelta.microseconds) - mods = u' '.join(modifiers) - conn = u' %s ' % connector - return u'(%s)' % conn.join([sql, u'interval \'%s\'' % mods]) + modifiers.append('%s microseconds' % timedelta.microseconds) + mods = ' '.join(modifiers) + conn = ' %s ' % connector + return '(%s)' % conn.join([sql, 'interval \'%s\'' % mods]) def date_trunc_sql(self, lookup_type, field_name): # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 4fba304e23..75e1d9792c 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -4,6 +4,7 @@ SQLite3 backend for django. Works with either the pysqlite2 module or the sqlite3 module in the standard library. """ +from __future__ import unicode_literals import datetime import decimal @@ -45,8 +46,8 @@ def adapt_datetime_with_timezone_support(value): # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL. if settings.USE_TZ: if timezone.is_naive(value): - warnings.warn(u"SQLite received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("SQLite received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) @@ -118,7 +119,7 @@ class DatabaseOperations(BaseDatabaseOperations): # values differently. So instead we register our own function that # formats the datetime combined with the delta in a manner suitable # for comparisons. - return u'django_format_dtdelta(%s, "%s", "%d", "%d", "%d")' % (sql, + return 'django_format_dtdelta(%s, "%s", "%d", "%d", "%d")' % (sql, connector, timedelta.days, timedelta.seconds, timedelta.microseconds) def date_trunc_sql(self, lookup_type, field_name): diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 32e0f4f702..775bb3e182 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime import decimal import hashlib @@ -146,6 +148,6 @@ def format_number(value, max_digits, decimal_places): if isinstance(value, decimal.Decimal): context = decimal.getcontext().copy() context.prec = max_digits - return u'%s' % str(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)) + return '%s' % str(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)) else: - return u"%.*f" % (decimal_places, value) + return "%.*f" % (decimal_places, value) diff --git a/django/db/models/base.py b/django/db/models/base.py index 13238fc9dc..f52a626d8b 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import copy import sys from functools import update_wrapper @@ -373,7 +375,7 @@ class Model(object): u = unicode(self) except (UnicodeEncodeError, UnicodeDecodeError): u = '[Bad Unicode data]' - return smart_str(u'<%s: %s>' % (self.__class__.__name__, u)) + return smart_str('<%s: %s>' % (self.__class__.__name__, u)) def __str__(self): if hasattr(self, '__unicode__'): @@ -786,7 +788,7 @@ class Model(object): def date_error_message(self, lookup_type, field, unique_for): opts = self._meta - return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { + return _("%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { 'field_name': unicode(capfirst(opts.get_field(field).verbose_name)), 'date_field': unicode(capfirst(opts.get_field(unique_for).verbose_name)), 'lookup': lookup_type, @@ -810,7 +812,7 @@ class Model(object): else: field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check) field_labels = get_text_list(field_labels, _('and')) - return _(u"%(model_name)s with this %(field_label)s already exists.") % { + return _("%(model_name)s with this %(field_label)s already exists.") % { 'model_name': unicode(model_name), 'field_label': unicode(field_labels) } diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index d572cce28f..9db76a617b 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import collections import copy import datetime @@ -61,16 +63,16 @@ class Field(object): auto_creation_counter = -1 default_validators = [] # Default set of validators default_error_messages = { - 'invalid_choice': _(u'Value %r is not a valid choice.'), - 'null': _(u'This field cannot be null.'), - 'blank': _(u'This field cannot be blank.'), - 'unique': _(u'%(model_name)s with this %(field_label)s ' - u'already exists.'), + 'invalid_choice': _('Value %r is not a valid choice.'), + 'null': _('This field cannot be null.'), + 'blank': _('This field cannot be blank.'), + 'unique': _('%(model_name)s with this %(field_label)s ' + 'already exists.'), } # Generic field type description, usually overriden by subclasses def _description(self): - return _(u'Field of type: %(field_type)s') % { + return _('Field of type: %(field_type)s') % { 'field_type': self.__class__.__name__ } description = property(_description) @@ -512,7 +514,7 @@ class AutoField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value must be an integer."), + 'invalid': _("'%s' value must be an integer."), } def __init__(self, *args, **kwargs): @@ -560,7 +562,7 @@ class AutoField(Field): class BooleanField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value must be either True or False."), + 'invalid': _("'%s' value must be either True or False."), } description = _("Boolean (Either True or False)") @@ -646,7 +648,7 @@ class CommaSeparatedIntegerField(CharField): def formfield(self, **kwargs): defaults = { 'error_messages': { - 'invalid': _(u'Enter only digits separated by commas.'), + 'invalid': _('Enter only digits separated by commas.'), } } defaults.update(kwargs) @@ -655,10 +657,10 @@ class CommaSeparatedIntegerField(CharField): class DateField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value has an invalid date format. It must be " - u"in YYYY-MM-DD format."), - 'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) " - u"but it is an invalid date."), + 'invalid': _("'%s' value has an invalid date format. It must be " + "in YYYY-MM-DD format."), + 'invalid_date': _("'%s' value has the correct format (YYYY-MM-DD) " + "but it is an invalid date."), } description = _("Date (without time)") @@ -743,13 +745,13 @@ class DateField(Field): class DateTimeField(DateField): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value has an invalid format. It must be in " - u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."), - 'invalid_date': _(u"'%s' value has the correct format " - u"(YYYY-MM-DD) but it is an invalid date."), - 'invalid_datetime': _(u"'%s' value has the correct format " - u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " - u"but it is an invalid date/time."), + 'invalid': _("'%s' value has an invalid format. It must be in " + "YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."), + 'invalid_date': _("'%s' value has the correct format " + "(YYYY-MM-DD) but it is an invalid date."), + 'invalid_datetime': _("'%s' value has the correct format " + "(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " + "but it is an invalid date/time."), } description = _("Date (with time)") @@ -770,8 +772,8 @@ class DateTimeField(DateField): # local time. This won't work during DST change, but we can't # do much about it, so we let the exceptions percolate up the # call stack. - warnings.warn(u"DateTimeField received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("DateTimeField received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) @@ -815,8 +817,8 @@ class DateTimeField(DateField): # For backwards compatibility, interpret naive datetimes in local # time. This won't work during DST change, but we can't do much # about it, so we let the exceptions percolate up the call stack. - warnings.warn(u"DateTimeField received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("DateTimeField received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) @@ -840,7 +842,7 @@ class DateTimeField(DateField): class DecimalField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value must be a decimal number."), + 'invalid': _("'%s' value must be a decimal number."), } description = _("Decimal number") @@ -1195,10 +1197,10 @@ class TextField(Field): class TimeField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value has an invalid format. It must be in " - u"HH:MM[:ss[.uuuuuu]] format."), - 'invalid_time': _(u"'%s' value has the correct format " - u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."), + 'invalid': _("'%s' value has an invalid format. It must be in " + "HH:MM[:ss[.uuuuuu]] format."), + 'invalid_time': _("'%s' value has the correct format " + "(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."), } description = _("Time") diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 34cf84dbc0..e0c095974d 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -207,7 +207,7 @@ class FileDescriptor(object): class FileField(Field): default_error_messages = { - 'max_length': _(u'Filename is %(extra)d characters too long.') + 'max_length': _('Filename is %(extra)d characters too long.') } # The class to wrap instance attributes in. Accessing the file object off diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py index 0a2223bc3f..6c39b25a74 100644 --- a/django/forms/extras/widgets.py +++ b/django/forms/extras/widgets.py @@ -1,6 +1,7 @@ """ Extra HTML Widget classes """ +from __future__ import unicode_literals import datetime import re @@ -90,7 +91,7 @@ class SelectDateWidget(Widget): output.append(month_html) elif field == 'day': output.append(day_html) - return mark_safe(u'\n'.join(output)) + return mark_safe('\n'.join(output)) def id_for_label(self, id_): first_select = None diff --git a/django/forms/fields.py b/django/forms/fields.py index 3811510326..4668eade97 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -2,7 +2,7 @@ Field classes. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import copy import datetime @@ -20,7 +20,7 @@ from django.forms.widgets import (TextInput, PasswordInput, HiddenInput, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION) from django.utils import formats -from django.utils.encoding import smart_unicode, smart_str, force_unicode +from django.utils.encoding import smart_unicode, force_unicode from django.utils.ipv6 import clean_ipv6_address from django.utils.translation import ugettext_lazy as _ @@ -44,8 +44,8 @@ class Field(object): hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". default_validators = [] # Default set of validators default_error_messages = { - 'required': _(u'This field is required.'), - 'invalid': _(u'Enter a valid value.'), + 'required': _('This field is required.'), + 'invalid': _('Enter a valid value.'), } # Tracks each time a Field instance is created. Used to retain order. @@ -78,7 +78,7 @@ class Field(object): self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial if help_text is None: - self.help_text = u'' + self.help_text = '' else: self.help_text = smart_unicode(help_text) widget = widget or self.widget @@ -190,7 +190,7 @@ class CharField(Field): def to_python(self, value): "Returns a Unicode object." if value in validators.EMPTY_VALUES: - return u'' + return '' return smart_unicode(value) def widget_attrs(self, widget): @@ -202,9 +202,9 @@ class CharField(Field): class IntegerField(Field): default_error_messages = { - 'invalid': _(u'Enter a whole number.'), - 'max_value': _(u'Ensure this value is less than or equal to %(limit_value)s.'), - 'min_value': _(u'Ensure this value is greater than or equal to %(limit_value)s.'), + 'invalid': _('Enter a whole number.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), } def __init__(self, max_value=None, min_value=None, *args, **kwargs): @@ -234,7 +234,7 @@ class IntegerField(Field): class FloatField(IntegerField): default_error_messages = { - 'invalid': _(u'Enter a number.'), + 'invalid': _('Enter a number.'), } def to_python(self, value): @@ -255,9 +255,9 @@ class FloatField(IntegerField): class DecimalField(Field): default_error_messages = { - 'invalid': _(u'Enter a number.'), - 'max_value': _(u'Ensure this value is less than or equal to %(limit_value)s.'), - 'min_value': _(u'Ensure this value is greater than or equal to %(limit_value)s.'), + 'invalid': _('Enter a number.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 'max_digits': _('Ensure that there are no more than %s digits in total.'), 'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), 'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') @@ -284,7 +284,7 @@ class DecimalField(Field): return None if self.localize: value = formats.sanitize_separators(value) - value = smart_str(value).strip() + value = smart_unicode(value).strip() try: value = Decimal(value) except DecimalException: @@ -348,7 +348,7 @@ class DateField(BaseTemporalField): widget = DateInput input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') default_error_messages = { - 'invalid': _(u'Enter a valid date.'), + 'invalid': _('Enter a valid date.'), } def to_python(self, value): @@ -371,7 +371,7 @@ class TimeField(BaseTemporalField): widget = TimeInput input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS') default_error_messages = { - 'invalid': _(u'Enter a valid time.') + 'invalid': _('Enter a valid time.') } def to_python(self, value): @@ -392,7 +392,7 @@ class DateTimeField(BaseTemporalField): widget = DateTimeInput input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS') default_error_messages = { - 'invalid': _(u'Enter a valid date/time.'), + 'invalid': _('Enter a valid date/time.'), } def prepare_value(self, value): @@ -457,7 +457,7 @@ class RegexField(CharField): class EmailField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid e-mail address.'), + 'invalid': _('Enter a valid e-mail address.'), } default_validators = [validators.validate_email] @@ -468,11 +468,11 @@ class EmailField(CharField): class FileField(Field): widget = ClearableFileInput default_error_messages = { - 'invalid': _(u"No file was submitted. Check the encoding type on the form."), - 'missing': _(u"No file was submitted."), - 'empty': _(u"The submitted file is empty."), - 'max_length': _(u'Ensure this filename has at most %(max)d characters (it has %(length)d).'), - 'contradiction': _(u'Please either submit a file or check the clear checkbox, not both.') + 'invalid': _("No file was submitted. Check the encoding type on the form."), + 'missing': _("No file was submitted."), + 'empty': _("The submitted file is empty."), + 'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), + 'contradiction': _('Please either submit a file or check the clear checkbox, not both.') } def __init__(self, *args, **kwargs): @@ -527,7 +527,7 @@ class FileField(Field): class ImageField(FileField): default_error_messages = { - 'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), + 'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."), } def to_python(self, data): @@ -583,7 +583,7 @@ class ImageField(FileField): class URLField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid URL.'), + 'invalid': _('Enter a valid URL.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -669,7 +669,7 @@ class NullBooleanField(BooleanField): class ChoiceField(Field): widget = Select default_error_messages = { - 'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'), + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), } def __init__(self, choices=(), required=True, widget=None, label=None, @@ -697,7 +697,7 @@ class ChoiceField(Field): def to_python(self, value): "Returns a Unicode object." if value in validators.EMPTY_VALUES: - return u'' + return '' return smart_unicode(value) def validate(self, value): @@ -749,8 +749,8 @@ class MultipleChoiceField(ChoiceField): hidden_widget = MultipleHiddenInput widget = SelectMultiple default_error_messages = { - 'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'), - 'invalid_list': _(u'Enter a list of values.'), + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), + 'invalid_list': _('Enter a list of values.'), } def to_python(self, value): @@ -838,7 +838,7 @@ class MultiValueField(Field): You'll probably want to use this with MultiWidget. """ default_error_messages = { - 'invalid': _(u'Enter a list of values.'), + 'invalid': _('Enter a list of values.'), } def __init__(self, fields=(), *args, **kwargs): @@ -956,8 +956,8 @@ class SplitDateTimeField(MultiValueField): widget = SplitDateTimeWidget hidden_widget = SplitHiddenDateTimeWidget default_error_messages = { - 'invalid_date': _(u'Enter a valid date.'), - 'invalid_time': _(u'Enter a valid time.'), + 'invalid_date': _('Enter a valid date.'), + 'invalid_time': _('Enter a valid time.'), } def __init__(self, input_date_formats=None, input_time_formats=None, *args, **kwargs): @@ -990,7 +990,7 @@ class SplitDateTimeField(MultiValueField): class IPAddressField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid IPv4 address.'), + 'invalid': _('Enter a valid IPv4 address.'), } default_validators = [validators.validate_ipv4_address] @@ -1007,7 +1007,7 @@ class GenericIPAddressField(CharField): def to_python(self, value): if value in validators.EMPTY_VALUES: - return u'' + return '' if value and ':' in value: return clean_ipv6_address(value, self.unpack_ipv4, self.error_messages['invalid']) @@ -1016,7 +1016,7 @@ class GenericIPAddressField(CharField): class SlugField(CharField): default_error_messages = { - 'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers," - u" underscores or hyphens."), + 'invalid': _("Enter a valid 'slug' consisting of letters, numbers," + " underscores or hyphens."), } default_validators = [validators.validate_slug] diff --git a/django/forms/forms.py b/django/forms/forms.py index 09663d173c..22c3057c62 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -2,7 +2,7 @@ Form classes """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import copy @@ -23,7 +23,7 @@ NON_FIELD_ERRORS = '__all__' def pretty_name(name): """Converts 'first_name' to 'First name'""" if not name: - return u'' + return '' return name.replace('_', ' ').capitalize() def get_declared_fields(bases, attrs, with_base_fields=True): @@ -136,7 +136,7 @@ class BaseForm(StrAndUnicode): """ Add a 'initial' prefix for checking dynamic initial values """ - return u'initial-%s' % self.add_prefix(field_name) + return 'initial-%s' % self.add_prefix(field_name) def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." @@ -149,7 +149,7 @@ class BaseForm(StrAndUnicode): bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. if bf.is_hidden: if bf_errors: - top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) + top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) hidden_fields.append(unicode(bf)) else: # Create a 'class="..."' atribute if the row should have any @@ -175,7 +175,7 @@ class BaseForm(StrAndUnicode): if field.help_text: help_text = help_text_html % force_unicode(field.help_text) else: - help_text = u'' + help_text = '' output.append(normal_row % { 'errors': force_unicode(bf_errors), @@ -189,7 +189,7 @@ class BaseForm(StrAndUnicode): output.insert(0, error_row % force_unicode(top_errors)) if hidden_fields: # Insert any hidden fields in the last row. - str_hidden = u''.join(hidden_fields) + str_hidden = ''.join(hidden_fields) if output: last_row = output[-1] # Chop off the trailing row_ender (e.g. '') and @@ -208,33 +208,33 @@ class BaseForm(StrAndUnicode): # If there aren't any rows in the output, just append the # hidden fields. output.append(str_hidden) - return mark_safe(u'\n'.join(output)) + return mark_safe('\n'.join(output)) def as_table(self): "Returns this form rendered as HTML s -- excluding the
    ." return self._html_output( - normal_row = u'%(label)s%(errors)s%(field)s%(help_text)s', - error_row = u'%s', - row_ender = u'', - help_text_html = u'
    %s', + normal_row = '%(label)s%(errors)s%(field)s%(help_text)s', + error_row = '%s', + row_ender = '', + help_text_html = '
    %s', errors_on_separate_row = False) def as_ul(self): "Returns this form rendered as HTML
  • s -- excluding the
      ." return self._html_output( - normal_row = u'%(errors)s%(label)s %(field)s%(help_text)s
    • ', - error_row = u'
    • %s
    • ', + normal_row = '%(errors)s%(label)s %(field)s%(help_text)s', + error_row = '
    • %s
    • ', row_ender = '', - help_text_html = u' %s', + help_text_html = ' %s', errors_on_separate_row = False) def as_p(self): "Returns this form rendered as HTML

      s." return self._html_output( - normal_row = u'%(label)s %(field)s%(help_text)s

      ', - error_row = u'%s', + normal_row = '%(label)s %(field)s%(help_text)s

      ', + error_row = '%s', row_ender = '

      ', - help_text_html = u' %s', + help_text_html = ' %s', errors_on_separate_row = True) def non_field_errors(self): @@ -508,7 +508,7 @@ class BoundField(StrAndUnicode): id_ = widget.attrs.get('id') or self.auto_id if id_: attrs = attrs and flatatt(attrs) or '' - contents = u'' % (widget.id_for_label(id_), attrs, unicode(contents)) + contents = '' % (widget.id_for_label(id_), attrs, unicode(contents)) return mark_safe(contents) def css_classes(self, extra_classes=None): diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 739a9d44e3..31ca088bdb 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.core.exceptions import ValidationError from django.forms import Form @@ -314,11 +314,11 @@ class BaseFormSet(StrAndUnicode): if self.can_order: # Only pre-fill the ordering field for initial forms. if index is not None and index < self.initial_form_count(): - form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False) + form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), initial=index+1, required=False) else: - form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False) + form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), required=False) if self.can_delete: - form.fields[DELETION_FIELD_NAME] = BooleanField(label=_(u'Delete'), required=False) + form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False) def add_prefix(self, index): return '%s-%s' % (self.prefix, index) @@ -344,18 +344,18 @@ class BaseFormSet(StrAndUnicode): # XXX: there is no semantic division between forms here, there # probably should be. It might make sense to render each form as a # table row with each field as a td. - forms = u' '.join([form.as_table() for form in self]) - return mark_safe(u'\n'.join([unicode(self.management_form), forms])) + forms = ' '.join([form.as_table() for form in self]) + return mark_safe('\n'.join([unicode(self.management_form), forms])) def as_p(self): "Returns this formset rendered as HTML

      s." - forms = u' '.join([form.as_p() for form in self]) - return mark_safe(u'\n'.join([unicode(self.management_form), forms])) + forms = ' '.join([form.as_p() for form in self]) + return mark_safe('\n'.join([unicode(self.management_form), forms])) def as_ul(self): "Returns this formset rendered as HTML

    • s." - forms = u' '.join([form.as_ul() for form in self]) - return mark_safe(u'\n'.join([unicode(self.management_form), forms])) + forms = ' '.join([form.as_ul() for form in self]) + return mark_safe('\n'.join([unicode(self.management_form), forms])) def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None): @@ -363,7 +363,7 @@ def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete, 'max_num': max_num} - return type(form.__name__ + 'FormSet', (formset,), attrs) + return type(form.__name__ + b'FormSet', (formset,), attrs) def all_valid(formsets): """Returns true if every formset in formsets is valid.""" diff --git a/django/forms/models.py b/django/forms/models.py index 5fcf9590eb..26f869155b 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -3,7 +3,7 @@ Helper functions for creating Form classes from Django models and database field objects. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError from django.core.validators import EMPTY_VALUES @@ -846,7 +846,7 @@ class InlineForeignKeyField(Field): given parent instance in an inline. """ default_error_messages = { - 'invalid_choice': _(u'The inline foreign key did not match the parent instance primary key.'), + 'invalid_choice': _('The inline foreign key did not match the parent instance primary key.'), } def __init__(self, parent_instance, *args, **kwargs): @@ -884,7 +884,7 @@ class ModelChoiceIterator(object): def __iter__(self): if self.field.empty_label is not None: - yield (u"", self.field.empty_label) + yield ("", self.field.empty_label) if self.field.cache_choices: if self.field.choice_cache is None: self.field.choice_cache = [ @@ -907,11 +907,11 @@ class ModelChoiceField(ChoiceField): # This class is a subclass of ChoiceField for purity, but it doesn't # actually use any of ChoiceField's implementation. default_error_messages = { - 'invalid_choice': _(u'Select a valid choice. That choice is not one of' - u' the available choices.'), + 'invalid_choice': _('Select a valid choice. That choice is not one of' + ' the available choices.'), } - def __init__(self, queryset, empty_label=u"---------", cache_choices=False, + def __init__(self, queryset, empty_label="---------", cache_choices=False, required=True, widget=None, label=None, initial=None, help_text=None, to_field_name=None, *args, **kwargs): if required and (initial is not None): @@ -996,10 +996,10 @@ class ModelMultipleChoiceField(ModelChoiceField): widget = SelectMultiple hidden_widget = MultipleHiddenInput default_error_messages = { - 'list': _(u'Enter a list of values.'), - 'invalid_choice': _(u'Select a valid choice. %s is not one of the' - u' available choices.'), - 'invalid_pk_value': _(u'"%s" is not a valid value for a primary key.') + 'list': _('Enter a list of values.'), + 'invalid_choice': _('Select a valid choice. %s is not one of the' + ' available choices.'), + 'invalid_pk_value': _('"%s" is not a valid value for a primary key.') } def __init__(self, queryset, cache_choices=False, required=True, diff --git a/django/forms/util.py b/django/forms/util.py index 6690442534..ca53b1f3d4 100644 --- a/django/forms/util.py +++ b/django/forms/util.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.utils.html import conditional_escape from django.utils.encoding import StrAndUnicode, force_unicode @@ -16,7 +18,7 @@ def flatatt(attrs): XML-style pairs. It is assumed that the keys do not need to be XML-escaped. If the passed dictionary is empty, then return an empty string. """ - return u''.join([u' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()]) + return ''.join([' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()]) class ErrorDict(dict, StrAndUnicode): """ @@ -28,13 +30,13 @@ class ErrorDict(dict, StrAndUnicode): return self.as_ul() def as_ul(self): - if not self: return u'' - return mark_safe(u'
        %s
      ' - % ''.join([u'
    • %s%s
    • ' % (k, conditional_escape(force_unicode(v))) + if not self: return '' + return mark_safe('
        %s
      ' + % ''.join(['
    • %s%s
    • ' % (k, conditional_escape(force_unicode(v))) for k, v in self.items()])) def as_text(self): - return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % force_unicode(i) for i in v])) for k, v in self.items()]) + return '\n'.join(['* %s\n%s' % (k, '\n'.join([' * %s' % force_unicode(i) for i in v])) for k, v in self.items()]) class ErrorList(list, StrAndUnicode): """ @@ -44,13 +46,13 @@ class ErrorList(list, StrAndUnicode): return self.as_ul() def as_ul(self): - if not self: return u'' - return mark_safe(u'
        %s
      ' - % ''.join([u'
    • %s
    • ' % conditional_escape(force_unicode(e)) for e in self])) + if not self: return '' + return mark_safe('
        %s
      ' + % ''.join(['
    • %s
    • ' % conditional_escape(force_unicode(e)) for e in self])) def as_text(self): - if not self: return u'' - return u'\n'.join([u'* %s' % force_unicode(e) for e in self]) + if not self: return '' + return '\n'.join(['* %s' % force_unicode(e) for e in self]) def __repr__(self): return repr([force_unicode(e) for e in self]) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 4813af69e9..2fddee918a 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -2,7 +2,7 @@ HTML Widget classes """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import copy import datetime @@ -50,10 +50,10 @@ class Media(StrAndUnicode): return self.render() def render(self): - return mark_safe(u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))) + return mark_safe('\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))) def render_js(self): - return [u'' % self.absolute_path(path) for path in self._js] + return ['' % self.absolute_path(path) for path in self._js] def render_css(self): # To keep rendering order consistent, we can't just iterate over items(). @@ -61,12 +61,12 @@ class Media(StrAndUnicode): media = self._css.keys() media.sort() return chain(*[ - [u'' % (self.absolute_path(path), medium) + ['' % (self.absolute_path(path), medium) for path in self._css[medium]] for medium in media]) def absolute_path(self, path, prefix=None): - if path.startswith(u'http://') or path.startswith(u'https://') or path.startswith(u'/'): + if path.startswith(('http://', 'https://', '/')): return path if prefix is None: if settings.STATIC_URL is None: @@ -210,13 +210,13 @@ class Widget(object): """ # For purposes of seeing whether something has changed, None is # the same as an empty string, if the data or inital value we get - # is None, replace it w/ u''. + # is None, replace it w/ ''. if data is None: - data_value = u'' + data_value = '' else: data_value = data if initial is None: - initial_value = u'' + initial_value = '' else: initial_value = initial if force_unicode(initial_value) != force_unicode(data_value): @@ -254,7 +254,7 @@ class Input(Widget): if value != '': # Only add the 'value' attribute if a value is non-empty. final_attrs['value'] = force_unicode(self._format_value(value)) - return mark_safe(u'' % flatatt(final_attrs)) + return mark_safe('' % flatatt(final_attrs)) class TextInput(Input): input_type = 'text' @@ -295,8 +295,8 @@ class MultipleHiddenInput(HiddenInput): # An ID attribute was given. Add a numeric index as a suffix # so that the inputs don't all have the same ID attribute. input_attrs['id'] = '%s_%s' % (id_, i) - inputs.append(u'' % flatatt(input_attrs)) - return mark_safe(u'\n'.join(inputs)) + inputs.append('' % flatatt(input_attrs)) + return mark_safe('\n'.join(inputs)) def value_from_datadict(self, data, files, name): if isinstance(data, (MultiValueDict, MergeDict)): @@ -326,9 +326,9 @@ class ClearableFileInput(FileInput): input_text = ugettext_lazy('Change') clear_checkbox_label = ugettext_lazy('Clear') - template_with_initial = u'%(initial_text)s: %(initial)s %(clear_template)s
      %(input_text)s: %(input)s' + template_with_initial = '%(initial_text)s: %(initial)s %(clear_template)s
      %(input_text)s: %(input)s' - template_with_clear = u'%(clear)s ' + template_with_clear = '%(clear)s ' def clear_checkbox_name(self, name): """ @@ -350,12 +350,12 @@ class ClearableFileInput(FileInput): 'clear_template': '', 'clear_checkbox_label': self.clear_checkbox_label, } - template = u'%(input)s' + template = '%(input)s' substitutions['input'] = super(ClearableFileInput, self).render(name, value, attrs) if value and hasattr(value, "url"): template = self.template_with_initial - substitutions['initial'] = (u'%s' + substitutions['initial'] = ('%s' % (escape(value.url), escape(force_unicode(value)))) if not self.is_required: @@ -392,7 +392,7 @@ class Textarea(Widget): def render(self, name, value, attrs=None): if value is None: value = '' final_attrs = self.build_attrs(attrs, name=name) - return mark_safe(u'%s' % (flatatt(final_attrs), + return mark_safe('%s' % (flatatt(final_attrs), conditional_escape(force_unicode(value)))) class DateInput(Input): @@ -511,7 +511,7 @@ class CheckboxInput(Widget): if not (value is True or value is False or value is None or value == ''): # Only add the 'value' attribute if a value is non-empty. final_attrs['value'] = force_unicode(value) - return mark_safe(u'' % flatatt(final_attrs)) + return mark_safe('' % flatatt(final_attrs)) def value_from_datadict(self, data, files, name): if name not in data: @@ -526,7 +526,7 @@ class CheckboxInput(Widget): return value def _has_changed(self, initial, data): - # Sometimes data or initial could be None or u'' which should be the + # Sometimes data or initial could be None or '' which should be the # same thing as False. return bool(initial) != bool(data) @@ -543,23 +543,23 @@ class Select(Widget): def render(self, name, value, attrs=None, choices=()): if value is None: value = '' final_attrs = self.build_attrs(attrs, name=name) - output = [u'' % flatatt(final_attrs)] + output = ['' % flatatt(final_attrs)] options = self.render_options(choices, [value]) if options: output.append(options) - output.append(u'') - return mark_safe(u'\n'.join(output)) + output.append('') + return mark_safe('\n'.join(output)) def render_option(self, selected_choices, option_value, option_label): option_value = force_unicode(option_value) if option_value in selected_choices: - selected_html = u' selected="selected"' + selected_html = ' selected="selected"' if not self.allow_multiple_selected: # Only allow for a single selection. selected_choices.remove(option_value) else: selected_html = '' - return u'' % ( + return '' % ( escape(option_value), selected_html, conditional_escape(force_unicode(option_label))) @@ -569,37 +569,37 @@ class Select(Widget): output = [] for option_value, option_label in chain(self.choices, choices): if isinstance(option_label, (list, tuple)): - output.append(u'' % escape(force_unicode(option_value))) + output.append('' % escape(force_unicode(option_value))) for option in option_label: output.append(self.render_option(selected_choices, *option)) - output.append(u'') + output.append('') else: output.append(self.render_option(selected_choices, option_value, option_label)) - return u'\n'.join(output) + return '\n'.join(output) class NullBooleanSelect(Select): """ A Select Widget intended to be used with NullBooleanField. """ def __init__(self, attrs=None): - choices = ((u'1', ugettext_lazy('Unknown')), - (u'2', ugettext_lazy('Yes')), - (u'3', ugettext_lazy('No'))) + choices = (('1', ugettext_lazy('Unknown')), + ('2', ugettext_lazy('Yes')), + ('3', ugettext_lazy('No'))) super(NullBooleanSelect, self).__init__(attrs, choices) def render(self, name, value, attrs=None, choices=()): try: - value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value] + value = {True: '2', False: '3', '2': '2', '3': '3'}[value] except KeyError: - value = u'1' + value = '1' return super(NullBooleanSelect, self).render(name, value, attrs, choices) def value_from_datadict(self, data, files, name): value = data.get(name, None) - return {u'2': True, + return {'2': True, True: True, 'True': True, - u'3': False, + '3': False, 'False': False, False: False}.get(value, None) @@ -618,12 +618,12 @@ class SelectMultiple(Select): def render(self, name, value, attrs=None, choices=()): if value is None: value = [] final_attrs = self.build_attrs(attrs, name=name) - output = [u'' % flatatt(final_attrs)] options = self.render_options(choices, value) if options: output.append(options) output.append('') - return mark_safe(u'\n'.join(output)) + return mark_safe('\n'.join(output)) def value_from_datadict(self, data, files, name): if isinstance(data, (MultiValueDict, MergeDict)): @@ -666,7 +666,7 @@ class RadioInput(SubWidget): else: label_for = '' choice_label = conditional_escape(force_unicode(self.choice_label)) - return mark_safe(u'%s %s' % (label_for, self.tag(), choice_label)) + return mark_safe('%s %s' % (label_for, self.tag(), choice_label)) def is_checked(self): return self.value == self.choice_value @@ -677,7 +677,7 @@ class RadioInput(SubWidget): final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' - return mark_safe(u'' % flatatt(final_attrs)) + return mark_safe('' % flatatt(final_attrs)) class RadioFieldRenderer(StrAndUnicode): """ @@ -701,7 +701,7 @@ class RadioFieldRenderer(StrAndUnicode): def render(self): """Outputs a
        for this set of radio fields.""" - return mark_safe(u'
          \n%s\n
        ' % u'\n'.join([u'
      • %s
      • ' + return mark_safe('
          \n%s\n
        ' % '\n'.join(['
      • %s
      • ' % force_unicode(w) for w in self])) class RadioSelect(Select): @@ -743,7 +743,7 @@ class CheckboxSelectMultiple(SelectMultiple): if value is None: value = [] has_id = attrs and 'id' in attrs final_attrs = self.build_attrs(attrs, name=name) - output = [u'
          '] + output = ['
            '] # Normalize to strings str_values = set([force_unicode(v) for v in value]) for i, (option_value, option_label) in enumerate(chain(self.choices, choices)): @@ -751,7 +751,7 @@ class CheckboxSelectMultiple(SelectMultiple): # so that the checkboxes don't all have the same ID attribute. if has_id: final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i)) - label_for = u' for="%s"' % final_attrs['id'] + label_for = ' for="%s"' % final_attrs['id'] else: label_for = '' @@ -759,9 +759,9 @@ class CheckboxSelectMultiple(SelectMultiple): option_value = force_unicode(option_value) rendered_cb = cb.render(name, option_value) option_label = conditional_escape(force_unicode(option_label)) - output.append(u'
          • %s %s
          • ' % (label_for, rendered_cb, option_label)) - output.append(u'
          ') - return mark_safe(u'\n'.join(output)) + output.append('
        • %s %s
        • ' % (label_for, rendered_cb, option_label)) + output.append('
        ') + return mark_safe('\n'.join(output)) def id_for_label(self, id_): # See the comment for RadioSelect.id_for_label() @@ -832,7 +832,7 @@ class MultiWidget(Widget): def _has_changed(self, initial, data): if initial is None: - initial = [u'' for x in range(0, len(data))] + initial = ['' for x in range(0, len(data))] else: if not isinstance(initial, list): initial = self.decompress(initial) @@ -849,7 +849,7 @@ class MultiWidget(Widget): This hook allows you to format the HTML design of the widgets, if needed. """ - return u''.join(rendered_widgets) + return ''.join(rendered_widgets) def decompress(self, value): """ diff --git a/django/http/__init__.py b/django/http/__init__.py index bf848deb38..30d7e5dc2f 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime import os @@ -57,13 +57,14 @@ else: if not _cookie_allows_colon_in_names: def load(self, rawdata): self.bad_cookies = set() - super(SimpleCookie, self).load(rawdata) + super(SimpleCookie, self).load(smart_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_str(key) try: M = self.get(key, Morsel()) M.set(key, real_value, coded_value) @@ -131,7 +132,7 @@ def build_request_repr(request, path_override=None, GET_override=None, except: meta = '' path = path_override if path_override is not None else request.path - return smart_str(u'<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % + return smart_str('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % (request.__class__.__name__, path, unicode(get), @@ -488,6 +489,7 @@ class QueryDict(MultiValueDict): """ output = [] if safe: + safe = smart_str(safe, self.encoding) encode = lambda k, v: '%s=%s' % ((quote(k, safe), quote(v, safe))) else: encode = lambda k, v: urlencode({k: v}) diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index 162c08e38d..bbe4b052b7 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -4,6 +4,7 @@ Multi-part parsing for file uploads. Exposes one class, ``MultiPartParser``, which feeds chunks of uploaded data to file upload handlers for processing. """ +from __future__ import unicode_literals import cgi from django.conf import settings @@ -59,7 +60,7 @@ class MultiPartParser(object): raise MultiPartParserError('Invalid Content-Type: %s' % content_type) # Parse the header to get the boundary to split the parts. - ctypes, opts = parse_header(content_type) + ctypes, opts = parse_header(content_type.encode('ascii')) boundary = opts.get('boundary') if not boundary or not cgi.valid_boundary(boundary): raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary) @@ -76,6 +77,8 @@ class MultiPartParser(object): # This means we shouldn't continue...raise an error. raise MultiPartParserError("Invalid content length: %r" % content_length) + if isinstance(boundary, unicode): + boundary = boundary.encode('ascii') self._boundary = boundary self._input_data = input_data @@ -589,14 +592,17 @@ class Parser(object): yield parse_boundary_stream(sub_stream, 1024) def parse_header(line): - """ Parse the header into a key-value. """ + """ Parse the header into a key-value. + Input (line): bytes, output: unicode for key/name, bytes for value which + will be decoded later + """ plist = _parse_header_params(b';' + line) - key = plist.pop(0).lower() + key = plist.pop(0).lower().decode('ascii') pdict = {} for p in plist: i = p.find(b'=') if i >= 0: - name = p[:i].strip().lower() + name = p[:i].strip().lower().decode('ascii') value = p[i+1:].strip() if len(value) >= 2 and value[0] == value[-1] == b'"': value = value[1:-1] diff --git a/django/template/base.py b/django/template/base.py index 9b2404cdb3..5a91bfda99 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re from functools import partial @@ -836,7 +836,7 @@ class NodeList(list): else: bit = node bits.append(force_unicode(bit)) - return mark_safe(u''.join(bits)) + return mark_safe(''.join(bits)) def get_nodes_by_type(self, nodetype): "Return a list of all nodes of the given type" @@ -1006,8 +1006,8 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults, if unhandled_params: # Some positional arguments were not supplied raise TemplateSyntaxError( - u"'%s' did not receive value(s) for the argument(s): %s" % - (name, u", ".join([u"'%s'" % p for p in unhandled_params]))) + "'%s' did not receive value(s) for the argument(s): %s" % + (name, ", ".join(["'%s'" % p for p in unhandled_params]))) return args, kwargs def generic_tag_compiler(parser, token, params, varargs, varkw, defaults, diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 1a3976f404..d2c5b03f30 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -1,4 +1,5 @@ """Default variable filters.""" +from __future__ import unicode_literals import re import random as random_module @@ -140,14 +141,14 @@ def floatformat(text, arg=-1): input_val = force_unicode(text) d = Decimal(input_val) except UnicodeEncodeError: - return u'' + return '' except InvalidOperation: if input_val in special_floats: return input_val try: d = Decimal(force_unicode(float(text))) except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError): - return u'' + return '' try: p = int(arg) except ValueError: @@ -159,12 +160,12 @@ def floatformat(text, arg=-1): return input_val if not m and p < 0: - return mark_safe(formats.number_format(u'%d' % (int(d)), 0)) + return mark_safe(formats.number_format('%d' % (int(d)), 0)) if p == 0: exp = Decimal(1) else: - exp = Decimal(u'1.0') / (Decimal(10) ** abs(p)) + exp = Decimal('1.0') / (Decimal(10) ** abs(p)) try: # Set the precision high enough to avoid an exception, see #15789. tupl = d.as_tuple() @@ -177,11 +178,11 @@ def floatformat(text, arg=-1): Context(prec=prec)).as_tuple() digits = [unicode(digit) for digit in reversed(digits)] while len(digits) <= abs(exponent): - digits.append(u'0') - digits.insert(-exponent, u'.') + digits.append('0') + digits.insert(-exponent, '.') if sign: - digits.append(u'-') - number = u''.join(reversed(digits)) + digits.append('-') + number = ''.join(reversed(digits)) return mark_safe(formats.number_format(number, abs(p))) except InvalidOperation: return input_val @@ -196,17 +197,17 @@ def iriencode(value): @stringfilter def linenumbers(value, autoescape=None): """Displays text with line numbers.""" - lines = value.split(u'\n') + lines = value.split('\n') # Find the maximum width of the line count, for use with zero padding # string format command width = unicode(len(unicode(len(lines)))) if not autoescape or isinstance(value, SafeData): for i, line in enumerate(lines): - lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line) + lines[i] = ("%0" + width + "d. %s") % (i + 1, line) else: for i, line in enumerate(lines): - lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) - return mark_safe(u'\n'.join(lines)) + lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) + return mark_safe('\n'.join(lines)) @register.filter(is_safe=True) @stringfilter @@ -248,9 +249,9 @@ def stringformat(value, arg): of Python string formatting """ try: - return (u"%" + unicode(arg)) % value + return ("%" + unicode(arg)) % value except (ValueError, TypeError): - return u"" + return "" @register.filter(is_safe=True) @stringfilter @@ -394,7 +395,7 @@ def cut(value, arg): Removes all values of arg from the given string. """ safe = isinstance(value, SafeData) - value = value.replace(arg, u'') + value = value.replace(arg, '') if safe and arg != ';': return mark_safe(value) return value @@ -467,11 +468,11 @@ def safeseq(value): 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 = u'(%s)' % u'|'.join(tags) - starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) - endtag_re = re.compile(u'' % tags_re) - value = starttag_re.sub(u'', value) - value = endtag_re.sub(u'', value) + tags_re = '(%s)' % '|'.join(tags) + starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) + endtag_re = re.compile('' % tags_re) + value = starttag_re.sub('', value) + value = endtag_re.sub('', value) return value @register.filter(is_safe=True) @@ -493,7 +494,7 @@ def dictsort(value, arg): try: return sorted(value, key=Variable(arg).resolve) except (TypeError, VariableDoesNotExist): - return u'' + return '' @register.filter(is_safe=False) def dictsortreversed(value, arg): @@ -504,7 +505,7 @@ def dictsortreversed(value, arg): try: return sorted(value, key=Variable(arg).resolve, reverse=True) except (TypeError, VariableDoesNotExist): - return u'' + return '' @register.filter(is_safe=False) def first(value): @@ -512,7 +513,7 @@ def first(value): try: return value[0] except IndexError: - return u'' + return '' @register.filter(is_safe=True, needs_autoescape=True) def join(value, arg, autoescape=None): @@ -534,7 +535,7 @@ def last(value): try: return value[-1] except IndexError: - return u'' + return '' @register.filter(is_safe=True) def length(value): @@ -568,7 +569,7 @@ def slice_filter(value, arg): """ try: bits = [] - for x in arg.split(u':'): + for x in arg.split(':'): if len(x) == 0: bits.append(None) else: @@ -635,7 +636,7 @@ def unordered_list(value, autoescape=None): second_item = new_second_item return [first_item, second_item], old_style_list def _helper(list_, tabs=1): - indent = u'\t' * tabs + indent = '\t' * tabs output = [] list_length = len(list_) @@ -708,7 +709,7 @@ def get_digit(value, arg): def date(value, arg=None): """Formats a date according to the given format.""" if not value: - return u'' + return '' if arg is None: arg = settings.DATE_FORMAT try: @@ -722,8 +723,8 @@ def date(value, arg=None): @register.filter(expects_localtime=True, is_safe=False) def time(value, arg=None): """Formats a time according to the given format.""" - if value in (None, u''): - return u'' + if value in (None, ''): + return '' if arg is None: arg = settings.TIME_FORMAT try: @@ -738,23 +739,23 @@ def time(value, arg=None): def timesince_filter(value, arg=None): """Formats a date as the time since that date (i.e. "4 days, 6 hours").""" if not value: - return u'' + return '' try: if arg: return timesince(value, arg) return timesince(value) except (ValueError, TypeError): - return u'' + return '' @register.filter("timeuntil", is_safe=False) def timeuntil_filter(value, arg=None): """Formats a date as the time until that date (i.e. "4 days, 6 hours").""" if not value: - return u'' + return '' try: return timeuntil(value, arg) except (ValueError, TypeError): - return u'' + return '' ################### # LOGIC # @@ -795,7 +796,7 @@ def yesno(value, arg=None): """ if arg is None: arg = ugettext('yes,no,maybe') - bits = arg.split(u',') + bits = arg.split(',') if len(bits) < 2: return value # Invalid arg. try: @@ -839,7 +840,7 @@ def filesizeformat(bytes): return ugettext("%s PB") % filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024 * 1024)) @register.filter(is_safe=False) -def pluralize(value, arg=u's'): +def pluralize(value, arg='s'): """ Returns a plural suffix if the value is not 1. By default, 's' is used as the suffix: @@ -862,11 +863,11 @@ def pluralize(value, arg=u's'): * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy". * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies". """ - if not u',' in arg: - arg = u',' + arg - bits = arg.split(u',') + if not ',' in arg: + arg = ',' + arg + bits = arg.split(',') if len(bits) > 2: - return u'' + return '' singular_suffix, plural_suffix = bits[:2] try: @@ -893,4 +894,4 @@ def pprint(value): try: return pformat(value) except Exception as e: - return u"Error in formatting: %s" % force_unicode(e, errors="replace") + return "Error in formatting: %s" % force_unicode(e, errors="replace") diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 3073b41049..0de5d9e3db 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -1,4 +1,5 @@ """Default tags used by the template system, available to all templates.""" +from __future__ import unicode_literals import sys import re @@ -13,7 +14,7 @@ from django.template.base import (Node, NodeList, Template, Context, Library, VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re) from django.template.smartif import IfParser, Literal from django.template.defaultfilters import date -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_unicode from django.utils.safestring import mark_safe from django.utils import timezone @@ -43,9 +44,9 @@ class CsrfTokenNode(Node): csrf_token = context.get('csrf_token', None) if csrf_token: if csrf_token == 'NOTPROVIDED': - return mark_safe(u"") + return mark_safe("") else: - return mark_safe(u"
        " % csrf_token) + return mark_safe("
        " % csrf_token) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning @@ -53,7 +54,7 @@ class CsrfTokenNode(Node): if settings.DEBUG: import warnings warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") - return u'' + return '' class CycleNode(Node): def __init__(self, cyclevars, variable_name=None, silent=False): @@ -102,7 +103,7 @@ class FirstOfNode(Node): value = var.resolve(context, True) if value: return smart_unicode(value) - return u'' + return '' class ForNode(Node): child_nodelists = ('nodelist_loop', 'nodelist_empty') @@ -390,7 +391,7 @@ class URLNode(Node): def render(self, context): from django.core.urlresolvers import reverse, NoReverseMatch args = [arg.resolve(context) for arg in self.args] - kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) + kwargs = dict([(smart_unicode(k, 'ascii'), v.resolve(context)) for k, v in self.kwargs.items()]) view_name = self.view_name.resolve(context) @@ -486,7 +487,7 @@ def autoescape(parser, token): if len(args) != 2: raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.") arg = args[1] - if arg not in (u'on', u'off'): + if arg not in ('on', 'off'): raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'") nodelist = parser.parse(('endautoescape',)) parser.delete_first_token() diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index 8183452422..056eadc7de 100644 --- a/django/templatetags/cache.py +++ b/django/templatetags/cache.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import hashlib from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist from django.template import resolve_variable @@ -23,7 +25,7 @@ class CacheNode(Node): 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(u':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])) + args = hashlib.md5(':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])) cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest()) value = cache.get(cache_key) if value is None: @@ -57,5 +59,5 @@ def do_cache(parser, token): parser.delete_first_token() tokens = token.contents.split() if len(tokens) < 3: - raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) + raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % tokens[0]) return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:]) diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index e090781080..231b723d3a 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import re from django.template import (Node, Variable, TemplateSyntaxError, @@ -76,7 +77,7 @@ class TranslateNode(Node): self.message_context = message_context self.filter_expression = filter_expression if isinstance(self.filter_expression.var, basestring): - self.filter_expression.var = Variable(u"'%s'" % + self.filter_expression.var = Variable("'%s'" % self.filter_expression.var) def render(self, context): @@ -110,7 +111,7 @@ class BlockTranslateNode(Node): if token.token_type == TOKEN_TEXT: result.append(token.contents) elif token.token_type == TOKEN_VAR: - result.append(u'%%(%s)s' % token.contents) + result.append('%%(%s)s' % token.contents) vars.append(token.contents) return ''.join(result), vars @@ -127,12 +128,12 @@ class BlockTranslateNode(Node): context.update(tmp_context) singular, vars = self.render_token_list(self.singular) # Escape all isolated '%' - singular = re.sub(u'%(?!\()', u'%%', singular) + singular = re.sub('%(?!\()', '%%', singular) if self.plural and self.countervar and self.counter: count = self.counter.resolve(context) context[self.countervar] = count plural, plural_vars = self.render_token_list(self.plural) - plural = re.sub(u'%(?!\()', u'%%', plural) + plural = re.sub('%(?!\()', '%%', plural) if message_context: result = translation.npgettext(message_context, singular, plural, count) diff --git a/django/test/client.py b/django/test/client.py index 3cc6a6e537..79844426f1 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -146,7 +146,7 @@ def encode_file(boundary, key, file): if content_type is None: content_type = 'application/octet-stream' return [ - '--' + boundary, + '--' + 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, diff --git a/django/test/html.py b/django/test/html.py index f1e4897efd..79b198f1be 100644 --- a/django/test/html.py +++ b/django/test/html.py @@ -1,6 +1,9 @@ """ Comparing two html documents. """ + +from __future__ import unicode_literals + import re from HTMLParser import HTMLParseError from django.utils.encoding import force_unicode @@ -113,18 +116,18 @@ class Element(object): return self.children[key] def __unicode__(self): - output = u'<%s' % self.name + output = '<%s' % self.name for key, value in self.attributes: if value: - output += u' %s="%s"' % (key, value) + output += ' %s="%s"' % (key, value) else: - output += u' %s' % key + output += ' %s' % key if self.children: - output += u'>\n' - output += u''.join(unicode(c) for c in self.children) - output += u'\n' % self.name + output += '>\n' + output += ''.join(unicode(c) for c in self.children) + output += '\n' % self.name else: - output += u' />' + output += ' />' return output def __repr__(self): @@ -136,7 +139,7 @@ class RootElement(Element): super(RootElement, self).__init__(None, ()) def __unicode__(self): - return u''.join(unicode(c) for c in self.children) + return ''.join(unicode(c) for c in self.children) class Parser(HTMLParser): diff --git a/django/test/testcases.py b/django/test/testcases.py index e2d7b3f82e..95e751d384 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import difflib import json import os @@ -85,7 +87,7 @@ def assert_and_parse_html(self, html, user_msg, msg): try: dom = parse_html(html) except HTMLParseError as e: - standardMsg = u'%s\n%s' % (msg, e.msg) + standardMsg = '%s\n%s' % (msg, e.msg) self.fail(self._formatMessage(user_msg, standardMsg)) return dom @@ -205,10 +207,6 @@ class OutputChecker(doctest.OutputChecker): "foo" >>> o._strip_quotes('"foo"') "foo" - >>> o._strip_quotes("u'foo'") - "foo" - >>> o._strip_quotes('u"foo"') - "foo" """ def is_quoted_string(s): s = s.strip() @@ -292,7 +290,7 @@ class _AssertTemplateUsedContext(object): return self.template_name in self.rendered_template_names def message(self): - return u'%s was not rendered.' % self.template_name + return '%s was not rendered.' % self.template_name def __enter__(self): template_rendered.connect(self.on_template_render) @@ -306,9 +304,9 @@ class _AssertTemplateUsedContext(object): if not self.test(): message = self.message() if len(self.rendered_templates) == 0: - message += u' No template was rendered.' + message += ' No template was rendered.' else: - message += u' Following templates were rendered: %s' % ( + message += ' Following templates were rendered: %s' % ( ', '.join(self.rendered_template_names)) self.test_case.fail(message) @@ -318,7 +316,7 @@ class _AssertTemplateNotUsedContext(_AssertTemplateUsedContext): return self.template_name not in self.rendered_template_names def message(self): - return u'%s was rendered.' % self.template_name + return '%s was rendered.' % self.template_name class SimpleTestCase(ut2.TestCase): @@ -359,7 +357,7 @@ class SimpleTestCase(ut2.TestCase): re.escape(expected_message), callable_obj, *args, **kwargs) def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None, - field_kwargs=None, empty_value=u''): + field_kwargs=None, empty_value=''): """ Asserts that a form field behaves correctly with various inputs. @@ -415,9 +413,9 @@ class SimpleTestCase(ut2.TestCase): significant. The passed-in arguments must be valid HTML. """ dom1 = assert_and_parse_html(self, html1, msg, - u'First argument is not valid HTML:') + 'First argument is not valid HTML:') dom2 = assert_and_parse_html(self, html2, msg, - u'Second argument is not valid HTML:') + 'Second argument is not valid HTML:') if dom1 != dom2: standardMsg = '%s != %s' % ( @@ -431,9 +429,9 @@ class SimpleTestCase(ut2.TestCase): def assertHTMLNotEqual(self, html1, html2, msg=None): """Asserts that two HTML snippets are not semantically equivalent.""" dom1 = assert_and_parse_html(self, html1, msg, - u'First argument is not valid HTML:') + 'First argument is not valid HTML:') dom2 = assert_and_parse_html(self, html2, msg, - u'Second argument is not valid HTML:') + 'Second argument is not valid HTML:') if dom1 == dom2: standardMsg = '%s == %s' % ( @@ -620,14 +618,14 @@ 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)) - text = smart_str(text, response._charset) + enc_text = smart_str(text, response._charset) content = response.content if html: content = assert_and_parse_html(self, content, None, - u"Response's content is not valid HTML:") - text = assert_and_parse_html(self, text, None, - u"Second argument is not valid HTML:") - real_count = content.count(text) + "Response's content is not valid HTML:") + enc_text = assert_and_parse_html(self, enc_text, None, + "Second argument is not valid HTML:") + real_count = content.count(enc_text) if count is not None: self.assertEqual(real_count, count, msg_prefix + "Found %d instances of '%s' in response" @@ -656,14 +654,14 @@ 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)) - text = smart_str(text, response._charset) + enc_text = smart_str(text, response._charset) content = response.content if html: content = assert_and_parse_html(self, content, None, - u'Response\'s content is not valid HTML:') - text = assert_and_parse_html(self, text, None, - u'Second argument is not valid HTML:') - self.assertEqual(content.count(text), 0, + 'Response\'s content is not valid HTML:') + enc_text = assert_and_parse_html(self, enc_text, None, + 'Second argument is not valid HTML:') + self.assertEqual(content.count(enc_text), 0, msg_prefix + "Response should not contain '%s'" % text) def assertFormError(self, response, form, field, errors, msg_prefix=''): @@ -723,7 +721,7 @@ class TransactionTestCase(SimpleTestCase): the response. Also usable as context manager. """ if response is None and template_name is None: - raise TypeError(u'response and/or template_name argument must be provided') + raise TypeError('response and/or template_name argument must be provided') if msg_prefix: msg_prefix += ": " @@ -742,7 +740,7 @@ class TransactionTestCase(SimpleTestCase): self.assertTrue(template_name in template_names, msg_prefix + "Template '%s' was not a template used to render" " the response. Actual template(s) used: %s" % - (template_name, u', '.join(template_names))) + (template_name, ', '.join(template_names))) def assertTemplateNotUsed(self, response=None, template_name=None, msg_prefix=''): """ @@ -750,7 +748,7 @@ class TransactionTestCase(SimpleTestCase): rendering the response. Also usable as context manager. """ if response is None and template_name is None: - raise TypeError(u'response and/or template_name argument must be provided') + raise TypeError('response and/or template_name argument must be provided') if msg_prefix: msg_prefix += ": " diff --git a/django/test/utils.py b/django/test/utils.py index 805117df80..ca4a5e7474 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -219,3 +219,5 @@ class override_settings(object): setting_changed.send(sender=settings._wrapped.__class__, setting=key, value=new_value) +def str_prefix(s): + return s % {'_': 'u'} diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 0fce06012b..cc59e2e39f 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -1,6 +1,7 @@ """ Django's standard crypto functions and utilities. """ +from __future__ import unicode_literals import hmac import struct @@ -42,7 +43,7 @@ def salted_hmac(key_salt, value, secret=None): # We need to generate a derived key from our base key. We can do this by # passing the key_salt and our base key through a pseudo-random function and # SHA1 works nicely. - key = hashlib.sha1(key_salt + secret).digest() + key = hashlib.sha1((key_salt + secret).encode('utf-8')).digest() # If len(key_salt + secret) > sha_constructor().block_size, the above # line is redundant and could be replaced by key = key_salt + secret, since @@ -105,7 +106,7 @@ def _long_to_bin(x, hex_format_string): Convert a long integer into a binary string. hex_format_string is like "%020x" for padding 10 characters. """ - return binascii.unhexlify(hex_format_string % x) + return binascii.unhexlify((hex_format_string % x).encode('ascii')) def _fast_hmac(key, msg, digest): @@ -113,6 +114,7 @@ def _fast_hmac(key, msg, digest): A trimmed down version of Python's HMAC implementation """ dig1, dig2 = digest(), digest() + key = smart_str(key) if len(key) > dig1.block_size: key = digest(key).digest() key += chr(0) * (dig1.block_size - len(key)) diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index e16a2df1f2..d410bce63e 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -10,6 +10,7 @@ Usage: 7th October 2003 11:39 >>> """ +from __future__ import unicode_literals import re import time @@ -33,7 +34,7 @@ class Formatter(object): pieces.append(force_unicode(getattr(self, piece)())) elif piece: pieces.append(re_escaped.sub(r'\1', piece)) - return u''.join(pieces) + return ''.join(pieces) class TimeFormat(Formatter): def __init__(self, t): @@ -64,7 +65,7 @@ class TimeFormat(Formatter): """ if self.data.minute == 0: return self.g() - return u'%s:%s' % (self.g(), self.i()) + return '%s:%s' % (self.g(), self.i()) def g(self): "Hour, 12-hour format without leading zeros; i.e. '1' to '12'" @@ -80,15 +81,15 @@ class TimeFormat(Formatter): def h(self): "Hour, 12-hour format; i.e. '01' to '12'" - return u'%02d' % self.g() + return '%02d' % self.g() def H(self): "Hour, 24-hour format; i.e. '00' to '23'" - return u'%02d' % self.G() + return '%02d' % self.G() def i(self): "Minutes; i.e. '00' to '59'" - return u'%02d' % self.data.minute + return '%02d' % self.data.minute def P(self): """ @@ -101,11 +102,11 @@ class TimeFormat(Formatter): return _('midnight') if self.data.minute == 0 and self.data.hour == 12: return _('noon') - return u'%s %s' % (self.f(), self.a()) + return '%s %s' % (self.f(), self.a()) def s(self): "Seconds; i.e. '00' to '59'" - return u'%02d' % self.data.second + return '%02d' % self.data.second def u(self): "Microseconds" @@ -138,7 +139,7 @@ class DateFormat(TimeFormat): def d(self): "Day of the month, 2 digits with leading zeros; i.e. '01' to '31'" - return u'%02d' % self.data.day + return '%02d' % self.data.day def D(self): "Day of the week, textual, 3 letters; e.g. 'Fri'" @@ -166,9 +167,9 @@ class DateFormat(TimeFormat): def I(self): "'1' if Daylight Savings Time, '0' otherwise." if self.timezone and self.timezone.dst(self.data): - return u'1' + return '1' else: - return u'0' + return '0' def j(self): "Day of the month without leading zeros; i.e. '1' to '31'" @@ -184,7 +185,7 @@ class DateFormat(TimeFormat): def m(self): "Month; i.e. '01' to '12'" - return u'%02d' % self.data.month + return '%02d' % self.data.month def M(self): "Month, textual, 3 letters; e.g. 'Jan'" @@ -207,7 +208,7 @@ class DateFormat(TimeFormat): seconds = self.Z() sign = '-' if seconds < 0 else '+' seconds = abs(seconds) - return u"%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60) + return "%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60) def r(self): "RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'" @@ -216,19 +217,19 @@ class DateFormat(TimeFormat): def S(self): "English ordinal suffix for the day of the month, 2 characters; i.e. 'st', 'nd', 'rd' or 'th'" if self.data.day in (11, 12, 13): # Special case - return u'th' + return 'th' last = self.data.day % 10 if last == 1: - return u'st' + return 'st' if last == 2: - return u'nd' + return 'nd' if last == 3: - return u'rd' - return u'th' + return 'rd' + return 'th' def t(self): "Number of days in the given month; i.e. '28' to '31'" - return u'%02d' % calendar.monthrange(self.data.year, self.data.month)[1] + return '%02d' % calendar.monthrange(self.data.year, self.data.month)[1] def T(self): "Time zone of this machine; e.g. 'EST' or 'MDT'" diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 6b246ac59b..80e456ba2a 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import urllib import locale import datetime @@ -80,7 +82,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # without raising a further exception. We do an # approximation to what the Exception's standard str() # output should be. - s = u' '.join([force_unicode(arg, encoding, strings_only, + s = ' '.join([force_unicode(arg, encoding, strings_only, errors) for arg in s]) elif not isinstance(s, unicode): # Note: We use .decode() here, instead of unicode(s, encoding, @@ -96,7 +98,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # working unicode method. Try to handle this without raising a # further exception by individually forcing the exception args # to unicode. - s = u' '.join([force_unicode(arg, encoding, strings_only, + s = ' '.join([force_unicode(arg, encoding, strings_only, errors) for arg in s]) return s diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index 7cd15674d6..52f40fb5f1 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -5,14 +5,14 @@ Sample usage: >>> from django.utils import feedgenerator >>> feed = feedgenerator.Rss201rev2Feed( -... title=u"Poynter E-Media Tidbits", -... link=u"http://www.poynter.org/column.asp?id=31", -... description=u"A group Weblog by the sharpest minds in online media/journalism/publishing.", -... language=u"en", +... title="Poynter E-Media Tidbits", +... link="http://www.poynter.org/column.asp?id=31", +... description="A group Weblog by the sharpest minds in online media/journalism/publishing.", +... language="en", ... ) >>> feed.add_item( ... title="Hello", -... link=u"http://www.holovaty.com/test/", +... link="http://www.holovaty.com/test/", ... description="Testing." ... ) >>> with open('test.rss', 'w') as fp: @@ -21,6 +21,7 @@ Sample usage: For definitions of the different versions of RSS, see: http://diveintomark.org/archives/2004/02/04/incompatible-rss """ +from __future__ import unicode_literals import datetime import urlparse @@ -70,7 +71,7 @@ def get_tag_uri(url, date): d = '' if date is not None: d = ',%s' % datetime_safe.new_datetime(date).strftime('%Y-%m-%d') - return u'tag:%s%s:%s/%s' % (bits.hostname, d, bits.path, bits.fragment) + return 'tag:%s%s:%s/%s' % (bits.hostname, d, bits.path, bits.fragment) class SyndicationFeed(object): "Base class for all syndication feeds. Subclasses should provide write()" @@ -203,177 +204,177 @@ class RssFeed(SyndicationFeed): def write(self, outfile, encoding): handler = SimplerXMLGenerator(outfile, encoding) handler.startDocument() - handler.startElement(u"rss", self.rss_attributes()) - handler.startElement(u"channel", self.root_attributes()) + handler.startElement("rss", self.rss_attributes()) + handler.startElement("channel", self.root_attributes()) self.add_root_elements(handler) self.write_items(handler) self.endChannelElement(handler) - handler.endElement(u"rss") + handler.endElement("rss") def rss_attributes(self): - return {u"version": self._version, - u"xmlns:atom": u"http://www.w3.org/2005/Atom"} + return {"version": self._version, + "xmlns:atom": "http://www.w3.org/2005/Atom"} def write_items(self, handler): for item in self.items: - handler.startElement(u'item', self.item_attributes(item)) + handler.startElement('item', self.item_attributes(item)) self.add_item_elements(handler, item) - handler.endElement(u"item") + handler.endElement("item") def add_root_elements(self, handler): - handler.addQuickElement(u"title", self.feed['title']) - handler.addQuickElement(u"link", self.feed['link']) - handler.addQuickElement(u"description", self.feed['description']) + handler.addQuickElement("title", self.feed['title']) + handler.addQuickElement("link", self.feed['link']) + handler.addQuickElement("description", self.feed['description']) if self.feed['feed_url'] is not None: - handler.addQuickElement(u"atom:link", None, - {u"rel": u"self", u"href": self.feed['feed_url']}) + handler.addQuickElement("atom:link", None, + {"rel": "self", "href": self.feed['feed_url']}) if self.feed['language'] is not None: - handler.addQuickElement(u"language", self.feed['language']) + handler.addQuickElement("language", self.feed['language']) for cat in self.feed['categories']: - handler.addQuickElement(u"category", cat) + handler.addQuickElement("category", cat) if self.feed['feed_copyright'] is not None: - handler.addQuickElement(u"copyright", self.feed['feed_copyright']) - handler.addQuickElement(u"lastBuildDate", rfc2822_date(self.latest_post_date()).decode('utf-8')) + handler.addQuickElement("copyright", self.feed['feed_copyright']) + handler.addQuickElement("lastBuildDate", rfc2822_date(self.latest_post_date()).decode('utf-8')) if self.feed['ttl'] is not None: - handler.addQuickElement(u"ttl", self.feed['ttl']) + handler.addQuickElement("ttl", self.feed['ttl']) def endChannelElement(self, handler): - handler.endElement(u"channel") + handler.endElement("channel") class RssUserland091Feed(RssFeed): - _version = u"0.91" + _version = "0.91" def add_item_elements(self, handler, item): - handler.addQuickElement(u"title", item['title']) - handler.addQuickElement(u"link", item['link']) + handler.addQuickElement("title", item['title']) + handler.addQuickElement("link", item['link']) if item['description'] is not None: - handler.addQuickElement(u"description", item['description']) + handler.addQuickElement("description", item['description']) class Rss201rev2Feed(RssFeed): # Spec: http://blogs.law.harvard.edu/tech/rss - _version = u"2.0" + _version = "2.0" def add_item_elements(self, handler, item): - handler.addQuickElement(u"title", item['title']) - handler.addQuickElement(u"link", item['link']) + handler.addQuickElement("title", item['title']) + handler.addQuickElement("link", item['link']) if item['description'] is not None: - handler.addQuickElement(u"description", item['description']) + handler.addQuickElement("description", item['description']) # Author information. if item["author_name"] and item["author_email"]: - handler.addQuickElement(u"author", "%s (%s)" % \ + handler.addQuickElement("author", "%s (%s)" % \ (item['author_email'], item['author_name'])) elif item["author_email"]: - handler.addQuickElement(u"author", item["author_email"]) + handler.addQuickElement("author", item["author_email"]) elif item["author_name"]: - handler.addQuickElement(u"dc:creator", item["author_name"], {u"xmlns:dc": u"http://purl.org/dc/elements/1.1/"}) + handler.addQuickElement("dc:creator", item["author_name"], {"xmlns:dc": "http://purl.org/dc/elements/1.1/"}) if item['pubdate'] is not None: - handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('utf-8')) + handler.addQuickElement("pubDate", rfc2822_date(item['pubdate']).decode('utf-8')) if item['comments'] is not None: - handler.addQuickElement(u"comments", item['comments']) + handler.addQuickElement("comments", item['comments']) if item['unique_id'] is not None: - handler.addQuickElement(u"guid", item['unique_id']) + handler.addQuickElement("guid", item['unique_id']) if item['ttl'] is not None: - handler.addQuickElement(u"ttl", item['ttl']) + handler.addQuickElement("ttl", item['ttl']) # Enclosure. if item['enclosure'] is not None: - handler.addQuickElement(u"enclosure", '', - {u"url": item['enclosure'].url, u"length": item['enclosure'].length, - u"type": item['enclosure'].mime_type}) + handler.addQuickElement("enclosure", '', + {"url": item['enclosure'].url, "length": item['enclosure'].length, + "type": item['enclosure'].mime_type}) # Categories. for cat in item['categories']: - handler.addQuickElement(u"category", cat) + handler.addQuickElement("category", cat) class Atom1Feed(SyndicationFeed): # Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html mime_type = 'application/atom+xml; charset=utf-8' - ns = u"http://www.w3.org/2005/Atom" + ns = "http://www.w3.org/2005/Atom" def write(self, outfile, encoding): handler = SimplerXMLGenerator(outfile, encoding) handler.startDocument() - handler.startElement(u'feed', self.root_attributes()) + handler.startElement('feed', self.root_attributes()) self.add_root_elements(handler) self.write_items(handler) - handler.endElement(u"feed") + handler.endElement("feed") def root_attributes(self): if self.feed['language'] is not None: - return {u"xmlns": self.ns, u"xml:lang": self.feed['language']} + return {"xmlns": self.ns, "xml:lang": self.feed['language']} else: - return {u"xmlns": self.ns} + return {"xmlns": self.ns} def add_root_elements(self, handler): - handler.addQuickElement(u"title", self.feed['title']) - handler.addQuickElement(u"link", "", {u"rel": u"alternate", u"href": self.feed['link']}) + handler.addQuickElement("title", self.feed['title']) + handler.addQuickElement("link", "", {"rel": "alternate", "href": self.feed['link']}) if self.feed['feed_url'] is not None: - handler.addQuickElement(u"link", "", {u"rel": u"self", u"href": self.feed['feed_url']}) - handler.addQuickElement(u"id", self.feed['id']) - handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('utf-8')) + handler.addQuickElement("link", "", {"rel": "self", "href": self.feed['feed_url']}) + handler.addQuickElement("id", self.feed['id']) + handler.addQuickElement("updated", rfc3339_date(self.latest_post_date()).decode('utf-8')) if self.feed['author_name'] is not None: - handler.startElement(u"author", {}) - handler.addQuickElement(u"name", self.feed['author_name']) + handler.startElement("author", {}) + handler.addQuickElement("name", self.feed['author_name']) if self.feed['author_email'] is not None: - handler.addQuickElement(u"email", self.feed['author_email']) + handler.addQuickElement("email", self.feed['author_email']) if self.feed['author_link'] is not None: - handler.addQuickElement(u"uri", self.feed['author_link']) - handler.endElement(u"author") + handler.addQuickElement("uri", self.feed['author_link']) + handler.endElement("author") if self.feed['subtitle'] is not None: - handler.addQuickElement(u"subtitle", self.feed['subtitle']) + handler.addQuickElement("subtitle", self.feed['subtitle']) for cat in self.feed['categories']: - handler.addQuickElement(u"category", "", {u"term": cat}) + handler.addQuickElement("category", "", {"term": cat}) if self.feed['feed_copyright'] is not None: - handler.addQuickElement(u"rights", self.feed['feed_copyright']) + handler.addQuickElement("rights", self.feed['feed_copyright']) def write_items(self, handler): for item in self.items: - handler.startElement(u"entry", self.item_attributes(item)) + handler.startElement("entry", self.item_attributes(item)) self.add_item_elements(handler, item) - handler.endElement(u"entry") + handler.endElement("entry") def add_item_elements(self, handler, item): - handler.addQuickElement(u"title", item['title']) - handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"}) + handler.addQuickElement("title", item['title']) + handler.addQuickElement("link", "", {"href": item['link'], "rel": "alternate"}) if item['pubdate'] is not None: - handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8')) + handler.addQuickElement("updated", rfc3339_date(item['pubdate']).decode('utf-8')) # Author information. if item['author_name'] is not None: - handler.startElement(u"author", {}) - handler.addQuickElement(u"name", item['author_name']) + handler.startElement("author", {}) + handler.addQuickElement("name", item['author_name']) if item['author_email'] is not None: - handler.addQuickElement(u"email", item['author_email']) + handler.addQuickElement("email", item['author_email']) if item['author_link'] is not None: - handler.addQuickElement(u"uri", item['author_link']) - handler.endElement(u"author") + handler.addQuickElement("uri", item['author_link']) + handler.endElement("author") # Unique ID. if item['unique_id'] is not None: unique_id = item['unique_id'] else: unique_id = get_tag_uri(item['link'], item['pubdate']) - handler.addQuickElement(u"id", unique_id) + handler.addQuickElement("id", unique_id) # Summary. if item['description'] is not None: - handler.addQuickElement(u"summary", item['description'], {u"type": u"html"}) + handler.addQuickElement("summary", item['description'], {"type": "html"}) # Enclosure. if item['enclosure'] is not None: - handler.addQuickElement(u"link", '', - {u"rel": u"enclosure", - u"href": item['enclosure'].url, - u"length": item['enclosure'].length, - u"type": item['enclosure'].mime_type}) + handler.addQuickElement("link", '', + {"rel": "enclosure", + "href": item['enclosure'].url, + "length": item['enclosure'].length, + "type": item['enclosure'].mime_type}) # Categories. for cat in item['categories']: - handler.addQuickElement(u"category", u"", {u"term": cat}) + handler.addQuickElement("category", "", {"term": cat}) # Rights. if item['item_copyright'] is not None: - handler.addQuickElement(u"rights", item['item_copyright']) + handler.addQuickElement("rights", item['item_copyright']) # This isolates the decision of what the system default is, so calling code can # do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed". diff --git a/django/utils/html.py b/django/utils/html.py index 1b1a16a911..014d837bbb 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -1,5 +1,7 @@ """HTML utilities suitable for global use.""" +from __future__ import unicode_literals + import re import string import urllib @@ -15,7 +17,7 @@ TRAILING_PUNCTUATION = ['.', ',', ':', ';'] WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('<', '>')] # List of possible strings used for bullets in bulleted lists. -DOTS = [u'·', u'*', u'\u2022', u'•', u'•', u'•'] +DOTS = ['·', '*', '\u2022', '•', '•', '•'] unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)') unquoted_percents_re = re.compile(r'%(?![0-9A-Fa-f]{2})') @@ -37,17 +39,17 @@ def escape(html): escape = allow_lazy(escape, unicode) _base_js_escapes = ( - ('\\', r'\u005C'), - ('\'', r'\u0027'), - ('"', r'\u0022'), - ('>', r'\u003E'), - ('<', r'\u003C'), - ('&', r'\u0026'), - ('=', r'\u003D'), - ('-', r'\u002D'), - (';', r'\u003B'), - (u'\u2028', r'\u2028'), - (u'\u2029', r'\u2029') + ('\\', '\\u005C'), + ('\'', '\\u0027'), + ('"', '\\u0022'), + ('>', '\\u003E'), + ('<', '\\u003C'), + ('&', '\\u0026'), + ('=', '\\u003D'), + ('-', '\\u002D'), + (';', '\\u003B'), + ('\u2028', '\\u2028'), + ('\u2029', '\\u2029') ) # Escape every ASCII character with a value less than 32. @@ -75,10 +77,10 @@ def linebreaks(value, autoescape=False): value = normalize_newlines(value) paras = re.split('\n{2,}', value) if autoescape: - paras = [u'

        %s

        ' % escape(p).replace('\n', '
        ') for p in paras] + paras = ['

        %s

        ' % escape(p).replace('\n', '
        ') for p in paras] else: - paras = [u'

        %s

        ' % p.replace('\n', '
        ') for p in paras] - return u'\n\n'.join(paras) + paras = ['

        %s

        ' % p.replace('\n', '
        ') for p in paras] + return '\n\n'.join(paras) linebreaks = allow_lazy(linebreaks, unicode) def strip_tags(value): @@ -192,7 +194,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): words[i] = mark_safe(word) elif autoescape: words[i] = escape(word) - return u''.join(words) + return ''.join(words) urlize = allow_lazy(urlize, unicode) def clean_html(text): @@ -218,13 +220,13 @@ def clean_html(text): text = html_gunk_re.sub('', text) # Convert hard-coded bullets into HTML unordered lists. def replace_p_tags(match): - s = match.group().replace(u'

        ', u'') + s = match.group().replace('

        ', '') for d in DOTS: - s = s.replace(u'

        %s' % d, u'

      • ') - return u'
          \n%s\n
        ' % s + s = s.replace('

        %s' % d, '

      • ') + return '
          \n%s\n
        ' % s text = hard_coded_bullets_re.sub(replace_p_tags, text) # Remove stuff like "

          

        ", but only if it's at the bottom # of the text. - text = trailing_empty_content_re.sub(u'', text) + text = trailing_empty_content_re.sub('', text) return text clean_html = allow_lazy(clean_html, unicode) diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py index f17b263c37..4b8ecea721 100644 --- a/django/utils/regex_helper.py +++ b/django/utils/regex_helper.py @@ -5,6 +5,7 @@ Used internally by Django and not intended for external use. This is not, and is not intended to be, a complete reg-exp decompiler. It should be good enough for a large class of URLS, however. """ +from __future__ import unicode_literals # Mapping of an escape character to a representative of that class. So, e.g., # "\w" is replaced by "x" in a reverse URL. A value of None means to ignore @@ -13,12 +14,12 @@ ESCAPE_MAPPINGS = { "A": None, "b": None, "B": None, - "d": u"0", - "D": u"x", - "s": u" ", - "S": u"x", - "w": u"x", - "W": u"!", + "d": "0", + "D": "x", + "s": " ", + "S": "x", + "w": "x", + "W": "!", "Z": None, } @@ -77,7 +78,7 @@ def normalize(pattern): try: ch, escaped = next(pattern_iter) except StopIteration: - return zip([u''], [[]]) + return zip([''], [[]]) try: while True: @@ -85,7 +86,7 @@ def normalize(pattern): result.append(ch) elif ch == '.': # Replace "any character" with an arbitrary representative. - result.append(u".") + result.append(".") elif ch == '|': # FIXME: One day we'll should do this, but not in 1.0. raise NotImplementedError @@ -117,7 +118,7 @@ def normalize(pattern): # A positional group name = "_%d" % num_args num_args += 1 - result.append(Group(((u"%%(%s)s" % name), name))) + result.append(Group((("%%(%s)s" % name), name))) walk_to_end(ch, pattern_iter) else: ch, escaped = next(pattern_iter) @@ -152,10 +153,10 @@ def normalize(pattern): # Named backreferences have already consumed the # parenthesis. if terminal_char != ')': - result.append(Group(((u"%%(%s)s" % param), param))) + result.append(Group((("%%(%s)s" % param), param))) walk_to_end(ch, pattern_iter) else: - result.append(Group(((u"%%(%s)s" % param), None))) + result.append(Group((("%%(%s)s" % param), None))) elif ch in "*?+{": # Quanitifers affect the previous item in the result list. count, ch = get_quantifier(ch, pattern_iter) @@ -190,7 +191,7 @@ def normalize(pattern): pass except NotImplementedError: # A case of using the disjunctive form. No results for you! - return zip([u''], [[]]) + return zip([''], [[]]) return zip(*flatten_result(result)) @@ -290,20 +291,20 @@ def flatten_result(source): Each of the two lists will be of the same length. """ if source is None: - return [u''], [[]] + return [''], [[]] if isinstance(source, Group): if source[1] is None: params = [] else: params = [source[1]] return [source[0]], [params] - result = [u''] + result = [''] result_args = [[]] pos = last = 0 for pos, elt in enumerate(source): if isinstance(elt, basestring): continue - piece = u''.join(source[last:pos]) + piece = ''.join(source[last:pos]) if isinstance(elt, Group): piece += elt[0] param = elt[1] @@ -331,7 +332,7 @@ def flatten_result(source): result = new_result result_args = new_args if pos >= last: - piece = u''.join(source[last:]) + piece = ''.join(source[last:]) for i in range(len(result)): result[i] += piece return result, result_args diff --git a/django/utils/text.py b/django/utils/text.py index 8ae0bf7663..2546f770b5 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import re import unicodedata import warnings @@ -43,7 +45,7 @@ def wrap(text, width): if len(lines) > 1: pos = len(lines[-1]) yield word - return u''.join(_generator()) + return ''.join(_generator()) wrap = allow_lazy(wrap, unicode) @@ -58,7 +60,7 @@ class Truncator(SimpleLazyObject): if truncate is None: truncate = pgettext( 'String to return when truncating text', - u'%(truncated_text)s...') + '%(truncated_text)s...') truncate = force_unicode(truncate) if '%(truncated_text)s' in truncate: return truncate % {'truncated_text': text} @@ -130,8 +132,8 @@ class Truncator(SimpleLazyObject): words = self._wrapped.split() if len(words) > length: words = words[:length] - return self.add_truncation_text(u' '.join(words), truncate) - return u' '.join(words) + return self.add_truncation_text(' '.join(words), truncate) + return ' '.join(words) def _html_words(self, length, truncate): """ @@ -142,7 +144,7 @@ class Truncator(SimpleLazyObject): Newlines in the HTML are preserved. """ if length <= 0: - return u'' + return '' html4_singlets = ( 'br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input' @@ -221,28 +223,28 @@ def get_valid_filename(s): spaces are converted to underscores; and anything that is not a unicode alphanumeric, dash, underscore, or dot, is removed. >>> get_valid_filename("john's portrait in 2004.jpg") - u'johns_portrait_in_2004.jpg' + 'johns_portrait_in_2004.jpg' """ s = force_unicode(s).strip().replace(' ', '_') return re.sub(r'(?u)[^-\w.]', '', s) get_valid_filename = allow_lazy(get_valid_filename, unicode) -def get_text_list(list_, last_word=ugettext_lazy(u'or')): +def get_text_list(list_, last_word=ugettext_lazy('or')): """ >>> get_text_list(['a', 'b', 'c', 'd']) - u'a, b, c or d' + 'a, b, c or d' >>> get_text_list(['a', 'b', 'c'], 'and') - u'a, b and c' + 'a, b and c' >>> get_text_list(['a', 'b'], 'and') - u'a and b' + 'a and b' >>> get_text_list(['a']) - u'a' + 'a' >>> get_text_list([]) - u'' + '' """ - if len(list_) == 0: return u'' + if len(list_) == 0: return '' if len(list_) == 1: return force_unicode(list_[0]) - return u'%s %s %s' % ( + return '%s %s %s' % ( # Translators: This string is used as a separator between list elements _(', ').join([force_unicode(i) for i in list_][:-1]), force_unicode(last_word), force_unicode(list_[-1])) @@ -267,7 +269,7 @@ def phone2numeric(phone): 'n': '6', 'o': '6', 'p': '7', 'q': '7', 'r': '7', 's': '7', 't': '8', 'u': '8', 'v': '8', 'w': '9', 'x': '9', 'y': '9', 'z': '9', } - return u''.join(char2number.get(c, c) for c in phone.lower()) + return ''.join(char2number.get(c, c) for c in phone.lower()) phone2numeric = allow_lazy(phone2numeric) # From http://www.xhaus.com/alan/python/httpcomp.html#gzip @@ -279,12 +281,12 @@ def compress_string(s): zfile.close() return zbuf.getvalue() -ustring_re = re.compile(u"([\u0080-\uffff])") +ustring_re = re.compile("([\u0080-\uffff])") def javascript_quote(s, quote_double_quotes=False): def fix(match): - return r"\u%04x" % ord(match.group(1)) + return b"\u%04x" % ord(match.group(1)) if type(s) == str: s = s.decode('utf-8') @@ -321,11 +323,11 @@ def smart_split(text): be further processed with unescape_string_literal()). >>> list(smart_split(r'This is "a person\'s" test.')) - [u'This', u'is', u'"a person\\\'s"', u'test.'] + ['This', 'is', '"a person\\\'s"', 'test.'] >>> list(smart_split(r"Another 'person\'s' test.")) - [u'Another', u"'person\\'s'", u'test.'] + ['Another', "'person\\'s'", 'test.'] >>> list(smart_split(r'A "\"funky\" style" test.')) - [u'A', u'"\\"funky\\" style"', u'test.'] + ['A', '"\\"funky\\" style"', 'test.'] """ text = force_unicode(text) for bit in smart_split_re.finditer(text): @@ -334,10 +336,10 @@ smart_split = allow_lazy(smart_split, unicode) def _replace_entity(match): text = match.group(1) - if text[0] == u'#': + if text[0] == '#': text = text[1:] try: - if text[0] in u'xX': + if text[0] in 'xX': c = int(text[1:], 16) else: c = int(text) diff --git a/django/utils/timesince.py b/django/utils/timesince.py index 276108b55a..1721f097bd 100644 --- a/django/utils/timesince.py +++ b/django/utils/timesince.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime from django.utils.timezone import is_aware, utc @@ -38,7 +40,7 @@ def timesince(d, now=None, reversed=False): since = delta.days * 24 * 60 * 60 + delta.seconds if since <= 0: # d is in the future compared to now, stop processing. - return u'0 ' + ugettext('minutes') + return '0 ' + ugettext('minutes') for i, (seconds, name) in enumerate(chunks): count = since // seconds if count != 0: diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index ac062823a4..0f1f28e5c4 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -1,6 +1,8 @@ """ Internationalization support. """ +from __future__ import unicode_literals + from django.utils.encoding import force_unicode from django.utils.functional import lazy @@ -136,7 +138,7 @@ def _string_concat(*strings): Lazy variant of string concatenation, needed for translations that are constructed from multiple parts. """ - return u''.join([force_unicode(s) for s in strings]) + return ''.join([force_unicode(s) for s in strings]) string_concat = lazy(_string_concat, unicode) def get_language_info(lang_code): diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 135a8cf6d5..0cd13fd6f5 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -1,11 +1,12 @@ """Translation helper functions.""" +from __future__ import unicode_literals import locale import os import re import sys import gettext as gettext_module -from io import BytesIO +from io import StringIO from threading import local from django.utils.importlib import import_module @@ -25,7 +26,7 @@ _default = None _accepted = {} # magic gettext number to separate context from message -CONTEXT_SEPARATOR = u"\x04" +CONTEXT_SEPARATOR = "\x04" # Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9. accept_language_re = re.compile(r''' @@ -263,7 +264,7 @@ def ugettext(message): def pgettext(context, message): result = do_translate( - u"%s%s%s" % (context, CONTEXT_SEPARATOR, message), 'ugettext') + "%s%s%s" % (context, CONTEXT_SEPARATOR, message), 'ugettext') if CONTEXT_SEPARATOR in result: # Translation not found result = message @@ -304,8 +305,8 @@ def ungettext(singular, plural, number): return do_ntranslate(singular, plural, number, 'ungettext') def npgettext(context, singular, plural, number): - result = do_ntranslate(u"%s%s%s" % (context, CONTEXT_SEPARATOR, singular), - u"%s%s%s" % (context, CONTEXT_SEPARATOR, plural), + result = do_ntranslate("%s%s%s" % (context, CONTEXT_SEPARATOR, singular), + "%s%s%s" % (context, CONTEXT_SEPARATOR, plural), number, 'ungettext') if CONTEXT_SEPARATOR in result: # Translation not found @@ -436,9 +437,11 @@ def templatize(src, origin=None): does so by translating the Django translation tags into standard gettext function invocations. """ + from django.conf import settings from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK) - out = BytesIO() + src = src.decode(settings.FILE_CHARSET) + out = StringIO() message_context = None intrans = False inplural = False @@ -449,16 +452,16 @@ def templatize(src, origin=None): for t in Lexer(src, origin).tokenize(): if incomment: if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment': - content = b''.join(comment) + content = ''.join(comment) translators_comment_start = None for lineno, line in enumerate(content.splitlines(True)): 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: - out.write(b' # %s' % line) + out.write(' # %s' % line) else: - out.write(b' #\n') + out.write(' #\n') incomment = False comment = [] else: @@ -470,18 +473,18 @@ def templatize(src, origin=None): if endbmatch: if inplural: if message_context: - out.write(b' npgettext(%r, %r, %r,count) ' % (message_context, ''.join(singular), ''.join(plural))) + out.write(' npgettext(%r, %r, %r,count) ' % (message_context, ''.join(singular), ''.join(plural))) else: - out.write(b' ngettext(%r, %r, count) ' % (''.join(singular), ''.join(plural))) + out.write(' ngettext(%r, %r, count) ' % (''.join(singular), ''.join(plural))) for part in singular: out.write(blankout(part, 'S')) for part in plural: out.write(blankout(part, 'P')) else: if message_context: - out.write(b' pgettext(%r, %r) ' % (message_context, ''.join(singular))) + out.write(' pgettext(%r, %r) ' % (message_context, ''.join(singular))) else: - out.write(b' gettext(%r) ' % ''.join(singular)) + out.write(' gettext(%r) ' % ''.join(singular)) for part in singular: out.write(blankout(part, 'S')) message_context = None @@ -527,10 +530,10 @@ def templatize(src, origin=None): message_context = message_context.strip('"') elif message_context[0] == "'": message_context = message_context.strip("'") - out.write(b' pgettext(%r, %r) ' % (message_context, g)) + out.write(' pgettext(%r, %r) ' % (message_context, g)) message_context = None else: - out.write(b' gettext(%r) ' % g) + out.write(' gettext(%r) ' % g) elif bmatch: for fmatch in constant_re.findall(t.contents): out.write(' _(%s) ' % fmatch) @@ -548,7 +551,7 @@ def templatize(src, origin=None): plural = [] elif cmatches: for cmatch in cmatches: - out.write(b' _(%s) ' % cmatch) + out.write(' _(%s) ' % cmatch) elif t.contents == 'comment': incomment = True else: @@ -557,17 +560,17 @@ def templatize(src, origin=None): parts = t.contents.split('|') cmatch = constant_re.match(parts[0]) if cmatch: - out.write(b' _(%s) ' % cmatch.group(1)) + out.write(' _(%s) ' % cmatch.group(1)) for p in parts[1:]: if p.find(':_(') >= 0: - out.write(b' %s ' % p.split(':',1)[1]) + out.write(' %s ' % p.split(':',1)[1]) else: out.write(blankout(p, 'F')) elif t.token_type == TOKEN_COMMENT: - out.write(b' # %s' % t.contents) + out.write(' # %s' % t.contents) else: out.write(blankout(t.contents, 'X')) - return out.getvalue() + return out.getvalue().encode('utf-8') def parse_accept_lang_header(lang_string): """ diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py index a07b635a99..05f4aa6d2f 100644 --- a/django/utils/tzinfo.py +++ b/django/utils/tzinfo.py @@ -1,5 +1,7 @@ "Implementation of tzinfo classes for use with datetime.datetime." +from __future__ import unicode_literals + import time from datetime import timedelta, tzinfo @@ -20,7 +22,7 @@ class FixedOffset(tzinfo): self.__offset = timedelta(minutes=offset) sign = '-' if offset < 0 else '+' - self.__name = u"%s%02d%02d" % (sign, abs(offset) / 60., abs(offset) % 60) + self.__name = "%s%02d%02d" % (sign, abs(offset) / 60., abs(offset) % 60) def __repr__(self): return self.__name diff --git a/django/utils/version.py b/django/utils/version.py index 3f108b8af2..3d66b9ef52 100644 --- a/django/utils/version.py +++ b/django/utils/version.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime import os import subprocess diff --git a/django/views/debug.py b/django/views/debug.py index d95cd62017..25eee4a91a 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime import os import re @@ -16,7 +18,7 @@ from django.utils.encoding import smart_unicode, smart_str HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE') -CLEANSED_SUBSTITUTE = u'********************' +CLEANSED_SUBSTITUTE = '********************' def linebreak_iter(template_source): yield 0 @@ -355,7 +357,7 @@ class ExceptionReporter(object): for line in source[:2]: # File coding may be specified. Match pattern from PEP-263 # (http://www.python.org/dev/peps/pep-0263/) - match = re.search(r'coding[:=]\s*([-\w.]+)', line) + match = re.search(br'coding[:=]\s*([-\w.]+)', line) if match: encoding = match.group(1) break diff --git a/django/views/generic/base.py b/django/views/generic/base.py index 7d3649cbff..c45bc32bf5 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from functools import update_wrapper from django import http from django.core.exceptions import ImproperlyConfigured @@ -44,11 +46,11 @@ class View(object): # sanitize keyword arguments for key in initkwargs: if key in cls.http_method_names: - raise TypeError(u"You tried to pass in the %s method name as a " - u"keyword argument to %s(). Don't do that." + raise TypeError("You tried to pass in the %s method name as a " + "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): - raise TypeError(u"%s() received an invalid keyword %r" % ( + raise TypeError("%s() received an invalid keyword %r" % ( cls.__name__, key)) def view(request, *args, **kwargs): diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 3e6ab0fa0c..26b172231c 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime from django.conf import settings from django.db import models @@ -34,7 +36,7 @@ class YearMixin(object): try: year = self.request.GET['year'] except KeyError: - raise Http404(_(u"No year specified")) + raise Http404(_("No year specified")) return year def get_next_year(self, date): @@ -87,7 +89,7 @@ class MonthMixin(object): try: month = self.request.GET['month'] except KeyError: - raise Http404(_(u"No month specified")) + raise Http404(_("No month specified")) return month def get_next_month(self, date): @@ -143,7 +145,7 @@ class DayMixin(object): try: day = self.request.GET['day'] except KeyError: - raise Http404(_(u"No day specified")) + raise Http404(_("No day specified")) return day def get_next_day(self, date): @@ -196,7 +198,7 @@ class WeekMixin(object): try: week = self.request.GET['week'] except KeyError: - raise Http404(_(u"No week specified")) + raise Http404(_("No week specified")) return week def get_next_week(self, date): @@ -252,7 +254,7 @@ class DateMixin(object): Get the name of the date field to be used to filter by. """ if self.date_field is None: - raise ImproperlyConfigured(u"%s.date_field is required." % self.__class__.__name__) + raise ImproperlyConfigured("%s.date_field is required." % self.__class__.__name__) return self.date_field def get_allow_future(self): @@ -350,7 +352,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): # than to load the unpaginated queryset in memory. is_empty = len(qs) == 0 if paginate_by is None else not qs.exists() if is_empty: - raise Http404(_(u"No %(verbose_name_plural)s available") % { + raise Http404(_("No %(verbose_name_plural)s available") % { 'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural) }) @@ -367,7 +369,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): date_list = queryset.dates(date_field, date_type)[::-1] if date_list is not None and not date_list and not allow_empty: name = force_unicode(queryset.model._meta.verbose_name_plural) - raise Http404(_(u"No %(verbose_name_plural)s available") % + raise Http404(_("No %(verbose_name_plural)s available") % {'verbose_name_plural': name}) return date_list @@ -617,7 +619,7 @@ class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailV qs = queryset or self.get_queryset() if not self.get_allow_future() and date > datetime.date.today(): - raise Http404(_(u"Future %(verbose_name_plural)s not available because %(class_name)s.allow_future is False.") % { + raise Http404(_("Future %(verbose_name_plural)s not available because %(class_name)s.allow_future is False.") % { 'verbose_name_plural': qs.model._meta.verbose_name_plural, 'class_name': self.__class__.__name__, }) @@ -649,7 +651,7 @@ def _date_from_string(year, year_format, month='', month_format='', day='', day_ try: return datetime.datetime.strptime(datestr, format).date() except ValueError: - raise Http404(_(u"Invalid date string '%(datestr)s' given format '%(format)s'") % { + raise Http404(_("Invalid date string '%(datestr)s' given format '%(format)s'") % { 'datestr': datestr, 'format': format, }) diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py index 4b5281745e..8cc413aa65 100644 --- a/django/views/generic/detail.py +++ b/django/views/generic/detail.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import Http404 from django.utils.translation import ugettext as _ @@ -40,14 +42,14 @@ class SingleObjectMixin(ContextMixin): # If none of those are defined, it's an error. else: - raise AttributeError(u"Generic detail view %s must be called with " - u"either an object pk or a slug." + raise AttributeError("Generic detail view %s must be called with " + "either an object pk or a slug." % self.__class__.__name__) try: obj = queryset.get() except ObjectDoesNotExist: - raise Http404(_(u"No %(verbose_name)s found matching the query") % + raise Http404(_("No %(verbose_name)s found matching the query") % {'verbose_name': queryset.model._meta.verbose_name}) return obj @@ -60,9 +62,9 @@ class SingleObjectMixin(ContextMixin): if self.model: return self.model._default_manager.all() else: - raise ImproperlyConfigured(u"%(cls)s is missing a queryset. Define " - u"%(cls)s.model, %(cls)s.queryset, or override " - u"%(cls)s.get_object()." % { + raise ImproperlyConfigured("%(cls)s is missing a queryset. Define " + "%(cls)s.model, %(cls)s.queryset, or override " + "%(cls)s.get_object()." % { 'cls': self.__class__.__name__ }) return self.queryset._clone() diff --git a/django/views/generic/list.py b/django/views/generic/list.py index ae45fd2218..2e852699d5 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.core.paginator import Paginator, InvalidPage from django.core.exceptions import ImproperlyConfigured from django.http import Http404 @@ -25,7 +27,7 @@ class MultipleObjectMixin(ContextMixin): elif self.model is not None: queryset = self.model._default_manager.all() else: - raise ImproperlyConfigured(u"'%s' must define 'queryset' or 'model'" + raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" % self.__class__.__name__) return queryset @@ -41,12 +43,12 @@ class MultipleObjectMixin(ContextMixin): if page == 'last': page_number = paginator.num_pages else: - raise Http404(_(u"Page is not 'last', nor can it be converted to an int.")) + raise Http404(_("Page is not 'last', nor can it be converted to an int.")) try: page = paginator.page(page_number) return (paginator, page, page.object_list, page.has_other_pages()) except InvalidPage: - raise Http404(_(u'Invalid page (%(page_number)s)') % { + raise Http404(_('Invalid page (%(page_number)s)') % { 'page_number': page_number }) @@ -123,7 +125,7 @@ class BaseListView(MultipleObjectMixin, View): else: is_empty = len(self.object_list) == 0 if is_empty: - raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.") + raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {'class_name': self.__class__.__name__}) context = self.get_context_data(object_list=self.object_list) return self.render_to_response(context) diff --git a/django/views/static.py b/django/views/static.py index 2a756134a6..1d7891e3f4 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -2,6 +2,7 @@ Views and functions for serving static files. These are only to be used during development, and SHOULD NOT be used in a production setting. """ +from __future__ import unicode_literals import mimetypes import os @@ -48,9 +49,9 @@ def serve(request, path, document_root=None, show_indexes=False): if os.path.isdir(fullpath): if show_indexes: return directory_index(newpath, fullpath) - raise Http404(_(u"Directory indexes are not allowed here.")) + raise Http404(_("Directory indexes are not allowed here.")) if not os.path.exists(fullpath): - raise Http404(_(u'"%(path)s" does not exist') % {'path': fullpath}) + raise Http404(_('"%(path)s" does not exist') % {'path': fullpath}) # Respect the If-Modified-Since header. statobj = os.stat(fullpath) mimetype, encoding = mimetypes.guess_type(fullpath) @@ -91,7 +92,7 @@ DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ """ -template_translatable = ugettext_noop(u"Index of %(directory)s") +template_translatable = ugettext_noop("Index of %(directory)s") def directory_index(path, fullpath): try: diff --git a/docs/conf.py b/docs/conf.py index 2aa3a5c641..659115dfbd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,6 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. +from __future__ import unicode_literals + import sys import os @@ -197,8 +199,8 @@ modindex_common_prefix = ["django."] # (source start file, target name, title, author, document class [howto/manual]). #latex_documents = [] latex_documents = [ - ('contents', 'django.tex', u'Django Documentation', - u'Django Software Foundation', 'manual'), + ('contents', 'django.tex', 'Django Documentation', + 'Django Software Foundation', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -237,10 +239,10 @@ man_pages = [ # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. -epub_title = u'Django' -epub_author = u'Django Software Foundation' -epub_publisher = u'Django Software Foundation' -epub_copyright = u'2010, Django Software Foundation' +epub_title = 'Django' +epub_author = 'Django Software Foundation' +epub_publisher = 'Django Software Foundation' +epub_copyright = '2010, Django Software Foundation' # The language of the text. It defaults to the language option # or en if the language is not set. diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt index 10108d1f4f..99547f1c9e 100644 --- a/docs/ref/files/file.txt +++ b/docs/ref/files/file.txt @@ -94,10 +94,11 @@ The ``ContentFile`` Class but unlike :class:`~django.core.files.File` it operates on string content, 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(u"my unicode content encoded as UTF-8".encode('UTF-8')) + f2 = ContentFile("my unicode content encoded as UTF-8".encode('UTF-8')) .. currentmodule:: django.core.files.images diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index d9bfbc5e45..486d49d796 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -25,8 +25,6 @@ exception or returns the clean value:: >>> f = forms.EmailField() >>> f.clean('foo@example.com') u'foo@example.com' - >>> f.clean(u'foo@example.com') - u'foo@example.com' >>> f.clean('invalid email address') Traceback (most recent call last): ... diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index f1642148b5..f6cdfc8141 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -184,7 +184,7 @@ a look at Django's ``EmailField``:: class EmailField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid e-mail address.'), + 'invalid': _('Enter a valid e-mail address.'), } default_validators = [validators.validate_email] @@ -197,7 +197,7 @@ on field definition so:: is equivalent to:: email = forms.CharField(validators=[validators.validate_email], - error_messages={'invalid': _(u'Enter a valid e-mail address.')}) + error_messages={'invalid': _('Enter a valid e-mail address.')}) Form field default cleaning diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index 46ce4138a4..85e48ae15d 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -45,6 +45,28 @@ rendering or anywhere else -- you have two choices for encoding those strings. You can use Unicode strings, or you can use normal strings (sometimes called "bytestrings") that are encoded using UTF-8. +.. versionchanged:: 1.5 + +In Python 3, the logic is reversed, that is normal strings are Unicode, and +when you want to specifically create a bytestring, you have to prefix the +string with a 'b'. As we are doing in Django code from version 1.5, +we recommend that you import ``unicode_literals`` from the __future__ library +in your code. Then, when you specifically want to create a bytestring literal, +prefix the string with 'b'. + +Python 2 legacy:: + + my_string = "This is a bytestring" + my_unicode = u"This is an Unicode string" + +Python 2 with unicode literals or Python 3:: + + from __future__ import unicode_literals + + my_string = b"This is a bytestring" + my_unicode = "This is an Unicode string" + + .. admonition:: Warning A bytestring does not carry any information with it about its encoding. @@ -182,7 +204,7 @@ An example might clarify things here:: >>> urlquote(u'Paris & Orléans') u'Paris%20%26%20Orl%C3%A9ans' - >>> iri_to_uri(u'/favorites/François/%s' % urlquote(u'Paris & Orléans')) + >>> iri_to_uri(u'/favorites/François/%s' % urlquote('Paris & Orléans')) '/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans' If you look carefully, you can see that the portion that was generated by @@ -268,7 +290,9 @@ You can pass either Unicode strings or UTF-8 bytestrings as arguments to ``filter()`` methods and the like in the database API. The following two querysets are identical:: - qs = People.objects.filter(name__contains=u'Å') + from __future__ import unicode_literals + + qs = People.objects.filter(name__contains='Å') qs = People.objects.filter(name__contains=b'\xc3\x85') # UTF-8 encoding of Å Templates @@ -276,9 +300,10 @@ Templates You can use either Unicode or bytestrings when creating templates manually:: - from django.template import Template - t1 = Template(b'This is a bytestring template.') - t2 = Template(u'This is a Unicode template.') + from __future__ import unicode_literals + from django.template import Template + t1 = Template(b'This is a bytestring template.') + t2 = Template('This is a Unicode template.') But the common case is to read templates from the filesystem, and this creates a slight complication: not all filesystems store their data encoded as UTF-8. @@ -316,14 +341,15 @@ characters. The following code example demonstrates that everything except email addresses can be non-ASCII:: + from __future__ import unicode_literals from django.core.mail import EmailMessage - subject = u'My visit to Sør-Trøndelag' - sender = u'Arnbjörg Ráðormsdóttir ' + subject = 'My visit to Sør-Trøndelag' + sender = 'Arnbjörg Ráðormsdóttir ' recipients = ['Fred %s" % (self.tag, self.staff) + return "%s -> %s" % (self.tag, self.staff) diff --git a/tests/modeltests/distinct_on_fields/tests.py b/tests/modeltests/distinct_on_fields/tests.py index 4d827d15b6..f62a32e58d 100644 --- a/tests/modeltests/distinct_on_fields/tests.py +++ b/tests/modeltests/distinct_on_fields/tests.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from django.db.models import Max from django.test import TestCase, skipUnlessDBFeature +from django.test.utils import str_prefix from .models import Tag, Celebrity, Fan, Staff, StaffTag @@ -78,7 +79,8 @@ class DistinctOnTests(TestCase): ( (Staff.objects.distinct('id').order_by('id', 'coworkers__name'). values_list('id', 'coworkers__name')), - ["(1, u'p2')", "(2, u'p1')", "(3, u'p1')", "(4, None)"] + [str_prefix("(1, %(_)s'p2')"), str_prefix("(2, %(_)s'p1')"), + str_prefix("(3, %(_)s'p1')"), "(4, None)"] ), ) for qset, expected in qsets: diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py index dd504999ff..018a0cf795 100644 --- a/tests/modeltests/expressions/models.py +++ b/tests/modeltests/expressions/models.py @@ -1,6 +1,7 @@ """ Tests for F() query expression syntax. """ +from __future__ import unicode_literals from django.db import models @@ -10,7 +11,7 @@ class Employee(models.Model): lastname = models.CharField(max_length=50) def __unicode__(self): - return u'%s %s' % (self.firstname, self.lastname) + return '%s %s' % (self.firstname, self.lastname) class Company(models.Model): name = models.CharField(max_length=100) diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py index 8f4f5461a5..c4e2707109 100644 --- a/tests/modeltests/expressions/tests.py +++ b/tests/modeltests/expressions/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.core.exceptions import FieldError from django.db.models import F @@ -77,17 +77,17 @@ class ExpressionsTests(TestCase): company_query, [ { 'num_chairs': 2302, - 'name': u'Example Inc.', + 'name': 'Example Inc.', 'num_employees': 2300 }, { 'num_chairs': 5, - 'name': u'Foobar Ltd.', + 'name': 'Foobar Ltd.', 'num_employees': 3 }, { 'num_chairs': 34, - 'name': u'Test GmbH', + 'name': 'Test GmbH', 'num_employees': 32 } ], @@ -102,17 +102,17 @@ class ExpressionsTests(TestCase): company_query, [ { 'num_chairs': 6900, - 'name': u'Example Inc.', + 'name': 'Example Inc.', 'num_employees': 2300 }, { 'num_chairs': 9, - 'name': u'Foobar Ltd.', + 'name': 'Foobar Ltd.', 'num_employees': 3 }, { 'num_chairs': 96, - 'name': u'Test GmbH', + 'name': 'Test GmbH', 'num_employees': 32 } ], @@ -127,17 +127,17 @@ class ExpressionsTests(TestCase): company_query, [ { 'num_chairs': 5294600, - 'name': u'Example Inc.', + 'name': 'Example Inc.', 'num_employees': 2300 }, { 'num_chairs': 15, - 'name': u'Foobar Ltd.', + 'name': 'Foobar Ltd.', 'num_employees': 3 }, { 'num_chairs': 1088, - 'name': u'Test GmbH', + 'name': 'Test GmbH', 'num_employees': 32 } ], diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/modeltests/field_subclassing/fields.py index 4d809ba6f8..b9987c0fab 100644 --- a/tests/modeltests/field_subclassing/fields.py +++ b/tests/modeltests/field_subclassing/fields.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import json from django.db import models @@ -13,7 +15,7 @@ class Small(object): self.first, self.second = first, second def __unicode__(self): - return u'%s%s' % (force_unicode(self.first), force_unicode(self.second)) + return '%s%s' % (force_unicode(self.first), force_unicode(self.second)) def __str__(self): return unicode(self).encode('utf-8') diff --git a/tests/modeltests/files/tests.py b/tests/modeltests/files/tests.py index 837723a049..3e256f787f 100644 --- a/tests/modeltests/files/tests.py +++ b/tests/modeltests/files/tests.py @@ -19,7 +19,7 @@ class FileStorageTests(TestCase): shutil.rmtree(temp_storage_location) def test_files(self): - temp_storage.save('tests/default.txt', ContentFile('default content')) + temp_storage.save('tests/default.txt', ContentFile(b'default content')) # Attempting to access a FileField from the class raises a descriptive # error self.assertRaises(AttributeError, lambda: Storage.normal) @@ -30,15 +30,15 @@ class FileStorageTests(TestCase): self.assertRaises(ValueError, lambda: obj1.normal.size) # Saving a file enables full functionality. - obj1.normal.save("django_test.txt", ContentFile("content")) + obj1.normal.save("django_test.txt", ContentFile(b"content")) self.assertEqual(obj1.normal.name, "tests/django_test.txt") self.assertEqual(obj1.normal.size, 7) - self.assertEqual(obj1.normal.read(), "content") + self.assertEqual(obj1.normal.read(), b"content") obj1.normal.close() # File objects can be assigned to FileField attributes, but shouldn't # get committed until the model it's attached to is saved. - obj1.normal = SimpleUploadedFile("assignment.txt", "content") + obj1.normal = SimpleUploadedFile("assignment.txt", b"content") dirs, files = temp_storage.listdir("tests") self.assertEqual(dirs, []) self.assertEqual(sorted(files), ["default.txt", "django_test.txt"]) @@ -51,14 +51,14 @@ class FileStorageTests(TestCase): # Files can be read in a little at a time, if necessary. obj1.normal.open() - self.assertEqual(obj1.normal.read(3), "con") - self.assertEqual(obj1.normal.read(), "tent") - self.assertEqual(list(obj1.normal.chunks(chunk_size=2)), ["co", "nt", "en", "t"]) + self.assertEqual(obj1.normal.read(3), b"con") + self.assertEqual(obj1.normal.read(), b"tent") + self.assertEqual(list(obj1.normal.chunks(chunk_size=2)), [b"co", b"nt", b"en", b"t"]) obj1.normal.close() # Save another file with the same name. obj2 = Storage() - obj2.normal.save("django_test.txt", ContentFile("more content")) + obj2.normal.save("django_test.txt", ContentFile(b"more content")) self.assertEqual(obj2.normal.name, "tests/django_test_1.txt") self.assertEqual(obj2.normal.size, 12) @@ -69,13 +69,13 @@ class FileStorageTests(TestCase): # Deleting an object does not delete the file it uses. obj2.delete() - obj2.normal.save("django_test.txt", ContentFile("more content")) + obj2.normal.save("django_test.txt", ContentFile(b"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("Same Content")) + o.normal.save("multiple_files.txt", ContentFile(b"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"] @@ -86,20 +86,20 @@ class FileStorageTests(TestCase): # Default values allow an object to access a single file. obj3 = Storage.objects.create() self.assertEqual(obj3.default.name, "tests/default.txt") - self.assertEqual(obj3.default.read(), "default content") + self.assertEqual(obj3.default.read(), b"default content") obj3.default.close() # But it shouldn't be deleted, even if there are no more objects using # it. obj3.delete() obj3 = Storage() - self.assertEqual(obj3.default.read(), "default content") + self.assertEqual(obj3.default.read(), b"default content") obj3.default.close() # Verify the fix for #5655, making sure the directory is only # determined once. obj4 = Storage() - obj4.random.save("random_file", ContentFile("random content")) + obj4.random.save("random_file", ContentFile(b"random content")) self.assertTrue(obj4.random.name.endswith("/random_file")) # Clean up the temporary files and dir. diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/modeltests/fixtures_model_package/tests.py index a415fdf6a7..17538ed7e8 100644 --- a/tests/modeltests/fixtures_model_package/tests.py +++ b/tests/modeltests/fixtures_model_package/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.core import management from django.db import transaction from django.test import TestCase, TransactionTestCase @@ -13,9 +15,9 @@ class SampleTestCase(TestCase): self.assertEqual(Article.objects.count(), 3) self.assertQuerysetEqual( Article.objects.all(),[ - u"Django conquers world!", - u"Copyright is fine the way it is", - u"Poker has no place on ESPN", + "Django conquers world!", + "Copyright is fine the way it is", + "Poker has no place on ESPN", ], lambda a: a.headline ) @@ -61,7 +63,7 @@ class FixtureTestCase(TestCase): # syncdb introduces 1 initial data object from initial_data.json self.assertQuerysetEqual( Book.objects.all(), [ - u'Achieving self-awareness of Python programs' + 'Achieving self-awareness of Python programs' ], lambda a: a.name ) @@ -72,8 +74,8 @@ class FixtureTestCase(TestCase): management.call_command("loaddata", "fixture1.json", verbosity=0, commit=False) self.assertQuerysetEqual( Article.objects.all(), [ - u"Time to reform copyright", - u"Poker has no place on ESPN", + "Time to reform copyright", + "Poker has no place on ESPN", ], lambda a: a.headline, ) @@ -83,9 +85,9 @@ class FixtureTestCase(TestCase): management.call_command("loaddata", "fixture2.json", verbosity=0, commit=False) self.assertQuerysetEqual( Article.objects.all(), [ - u"Django conquers world!", - u"Copyright is fine the way it is", - u"Poker has no place on ESPN", + "Django conquers world!", + "Copyright is fine the way it is", + "Poker has no place on ESPN", ], lambda a: a.headline, ) @@ -94,9 +96,9 @@ class FixtureTestCase(TestCase): management.call_command("loaddata", "unknown.json", verbosity=0, commit=False) self.assertQuerysetEqual( Article.objects.all(), [ - u"Django conquers world!", - u"Copyright is fine the way it is", - u"Poker has no place on ESPN", + "Django conquers world!", + "Copyright is fine the way it is", + "Poker has no place on ESPN", ], lambda a: a.headline, ) diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py index f3e216edf5..f2dcf7db24 100644 --- a/tests/modeltests/generic_relations/models.py +++ b/tests/modeltests/generic_relations/models.py @@ -9,6 +9,8 @@ The canonical example is tags (although this example implementation is *far* from complete). """ +from __future__ import unicode_literals + from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType from django.db import models @@ -47,7 +49,7 @@ class Comparison(models.Model): other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2") def __unicode__(self): - return u"%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj) + return "%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj) class Animal(models.Model): common_name = models.CharField(max_length=150) diff --git a/tests/modeltests/generic_relations/tests.py b/tests/modeltests/generic_relations/tests.py index 0ac552cf77..d3de71d917 100644 --- a/tests/modeltests/generic_relations/tests.py +++ b/tests/modeltests/generic_relations/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django import forms from django.contrib.contenttypes.generic import generic_inlineformset_factory @@ -90,23 +90,23 @@ class GenericRelationsTests(TestCase): ) self.assertQuerysetEqual(TaggedItem.objects.all(), [ - (u'clearish', Mineral, quartz.pk), - (u'fatty', Animal, platypus.pk), - (u'fatty', Vegetable, bacon.pk), - (u'hairy', Animal, lion.pk), - (u'salty', Vegetable, bacon.pk), - (u'shiny', Animal, platypus.pk), - (u'yellow', Animal, lion.pk) + ('clearish', Mineral, quartz.pk), + ('fatty', Animal, platypus.pk), + ('fatty', Vegetable, bacon.pk), + ('hairy', Animal, lion.pk), + ('salty', Vegetable, bacon.pk), + ('shiny', Animal, platypus.pk), + ('yellow', Animal, lion.pk) ], comp_func ) lion.delete() self.assertQuerysetEqual(TaggedItem.objects.all(), [ - (u'clearish', Mineral, quartz.pk), - (u'fatty', Animal, platypus.pk), - (u'fatty', Vegetable, bacon.pk), - (u'salty', Vegetable, bacon.pk), - (u'shiny', Animal, platypus.pk) + ('clearish', Mineral, quartz.pk), + ('fatty', Animal, platypus.pk), + ('fatty', Vegetable, bacon.pk), + ('salty', Vegetable, bacon.pk), + ('shiny', Animal, platypus.pk) ], comp_func ) @@ -116,11 +116,11 @@ class GenericRelationsTests(TestCase): quartz_pk = quartz.pk quartz.delete() self.assertQuerysetEqual(TaggedItem.objects.all(), [ - (u'clearish', Mineral, quartz_pk), - (u'fatty', Animal, platypus.pk), - (u'fatty', Vegetable, bacon.pk), - (u'salty', Vegetable, bacon.pk), - (u'shiny', Animal, platypus.pk) + ('clearish', Mineral, quartz_pk), + ('fatty', Animal, platypus.pk), + ('fatty', Vegetable, bacon.pk), + ('salty', Vegetable, bacon.pk), + ('shiny', Animal, platypus.pk) ], comp_func ) @@ -130,10 +130,10 @@ class GenericRelationsTests(TestCase): tag.delete() self.assertQuerysetEqual(bacon.tags.all(), [""]) self.assertQuerysetEqual(TaggedItem.objects.all(), [ - (u'clearish', Mineral, quartz_pk), - (u'fatty', Animal, platypus.pk), - (u'salty', Vegetable, bacon.pk), - (u'shiny', Animal, platypus.pk) + ('clearish', Mineral, quartz_pk), + ('fatty', Animal, platypus.pk), + ('salty', Vegetable, bacon.pk), + ('shiny', Animal, platypus.pk) ], comp_func ) @@ -200,11 +200,11 @@ class GenericRelationsTests(TestCase): def test_generic_inline_formsets(self): GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1) formset = GenericFormSet() - self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""

        + self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """

        """) formset = GenericFormSet(instance=Animal()) - self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""

        + self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """

        """) platypus = Animal.objects.create( @@ -216,13 +216,13 @@ class GenericRelationsTests(TestCase): tagged_item_id = TaggedItem.objects.get( tag='shiny', object_id=platypus.id ).id - self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""

        + self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """

        """ % tagged_item_id) lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo") formset = GenericFormSet(instance=lion, prefix='x') - self.assertHTMLEqual(u''.join(form.as_p() for form in formset.forms), u"""

        + self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """

        """) def test_gfk_manager(self): diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py index 1de5a6ecec..78b92f09df 100644 --- a/tests/modeltests/get_or_create/models.py +++ b/tests/modeltests/get_or_create/models.py @@ -6,6 +6,8 @@ given parameters. If an object isn't found, it creates one with the given parameters. """ +from __future__ import unicode_literals + from django.db import models @@ -15,7 +17,7 @@ class Person(models.Model): birthday = models.DateField() def __unicode__(self): - return u'%s %s' % (self.first_name, self.last_name) + return '%s %s' % (self.first_name, self.last_name) class ManualPrimaryKeyTest(models.Model): id = models.IntegerField(primary_key=True) diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py index ed69fb60ee..b2ba253c5d 100644 --- a/tests/modeltests/invalid_models/invalid_models/models.py +++ b/tests/modeltests/invalid_models/invalid_models/models.py @@ -5,6 +5,8 @@ This example exists purely to point out errors in models. """ +from __future__ import unicode_literals + from django.db import connection, models @@ -223,12 +225,12 @@ class InvalidSetDefault(models.Model): class UnicodeForeignKeys(models.Model): """Foreign keys which can translate to ascii should be OK, but fail if they're not.""" - good = models.ForeignKey(u'FKTarget') - also_good = models.ManyToManyField(u'FKTarget', related_name='unicode2') + good = models.ForeignKey('FKTarget') + also_good = models.ManyToManyField('FKTarget', related_name='unicode2') # In Python 3 this should become legal, but currently causes unicode errors # when adding the errors in core/management/validation.py - #bad = models.ForeignKey(u'★') + #bad = models.ForeignKey('★') class PrimaryKeyNull(models.Model): my_pk_field = models.IntegerField(primary_key=True, null=True) diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index bcdd3d7c68..3e5d61538a 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -4,6 +4,8 @@ This demonstrates features of the database API. """ +from __future__ import unicode_literals + from django.db import models @@ -41,11 +43,11 @@ class Game(models.Model): away = models.CharField(max_length=100) def __unicode__(self): - return u"%s at %s" % (self.away, self.home) + return "%s at %s" % (self.away, self.home) class Player(models.Model): name = models.CharField(max_length=100) games = models.ManyToManyField(Game, related_name='players') def __unicode__(self): - return self.name \ No newline at end of file + return self.name diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py index 502e0d5f2b..98358e3d10 100644 --- a/tests/modeltests/lookup/tests.py +++ b/tests/modeltests/lookup/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from datetime import datetime from operator import attrgetter @@ -128,13 +128,13 @@ class LookupTests(TestCase): identity = lambda x:x self.assertQuerysetEqual(Article.objects.values('headline'), [ - {'headline': u'Article 5'}, - {'headline': u'Article 6'}, - {'headline': u'Article 4'}, - {'headline': u'Article 2'}, - {'headline': u'Article 3'}, - {'headline': u'Article 7'}, - {'headline': u'Article 1'}, + {'headline': 'Article 5'}, + {'headline': 'Article 6'}, + {'headline': 'Article 4'}, + {'headline': 'Article 2'}, + {'headline': 'Article 3'}, + {'headline': 'Article 7'}, + {'headline': 'Article 1'}, ], transform=identity) self.assertQuerysetEqual( @@ -156,13 +156,13 @@ class LookupTests(TestCase): # because iterator() uses database-level iteration. self.assertQuerysetEqual(Article.objects.values('id', 'headline').iterator(), [ - {'headline': u'Article 5', 'id': self.a5.id}, - {'headline': u'Article 6', 'id': self.a6.id}, - {'headline': u'Article 4', 'id': self.a4.id}, - {'headline': u'Article 2', 'id': self.a2.id}, - {'headline': u'Article 3', 'id': self.a3.id}, - {'headline': u'Article 7', 'id': self.a7.id}, - {'headline': u'Article 1', 'id': self.a1.id}, + {'headline': 'Article 5', 'id': self.a5.id}, + {'headline': 'Article 6', 'id': self.a6.id}, + {'headline': 'Article 4', 'id': self.a4.id}, + {'headline': 'Article 2', 'id': self.a2.id}, + {'headline': 'Article 3', 'id': self.a3.id}, + {'headline': 'Article 7', 'id': self.a7.id}, + {'headline': 'Article 1', 'id': self.a1.id}, ], transform=identity) # The values() method works with "extra" fields specified in extra(select). @@ -259,13 +259,13 @@ class LookupTests(TestCase): identity = lambda x:x self.assertQuerysetEqual(Article.objects.values_list('headline'), [ - (u'Article 5',), - (u'Article 6',), - (u'Article 4',), - (u'Article 2',), - (u'Article 3',), - (u'Article 7',), - (u'Article 1',), + ('Article 5',), + ('Article 6',), + ('Article 4',), + ('Article 2',), + ('Article 3',), + ('Article 7',), + ('Article 1',), ], transform=identity) self.assertQuerysetEqual(Article.objects.values_list('id').order_by('id'), [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)], diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py index 9368398f71..6c1f277811 100644 --- a/tests/modeltests/m2m_and_m2o/models.py +++ b/tests/modeltests/m2m_and_m2o/models.py @@ -3,6 +3,7 @@ Make sure to set ``related_name`` if you use relationships to the same table. """ +from __future__ import unicode_literals from django.db import models @@ -22,5 +23,5 @@ class Issue(models.Model): ordering = ('num',) class UnicodeReferenceModel(models.Model): - others = models.ManyToManyField(u"UnicodeReferenceModel") + others = models.ManyToManyField("UnicodeReferenceModel") diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py index ea9b83ea14..85786e8458 100644 --- a/tests/modeltests/m2m_intermediary/models.py +++ b/tests/modeltests/m2m_intermediary/models.py @@ -9,6 +9,7 @@ each ``Article``-``Reporter`` combination (a ``Writer``) has a ``position`` field, which specifies the ``Reporter``'s position for the given article (e.g. "Staff writer"). """ +from __future__ import unicode_literals from django.db import models @@ -18,7 +19,7 @@ class Reporter(models.Model): last_name = models.CharField(max_length=30) def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) + return "%s %s" % (self.first_name, self.last_name) class Article(models.Model): headline = models.CharField(max_length=100) @@ -33,5 +34,5 @@ class Writer(models.Model): position = models.CharField(max_length=100) def __unicode__(self): - return u'%s (%s)' % (self.reporter, self.position) + return '%s (%s)' % (self.reporter, self.position) diff --git a/tests/modeltests/m2m_through/tests.py b/tests/modeltests/m2m_through/tests.py index 94be628a42..259dc68a0b 100644 --- a/tests/modeltests/m2m_through/tests.py +++ b/tests/modeltests/m2m_through/tests.py @@ -333,7 +333,7 @@ class M2mThroughTests(TestCase): # Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): self.assertEqual( [(m.person.name, m.group.name) for m in Membership.objects.filter(date_joined__gt=datetime(2004, 1, 1))], - [(u'Jane', u'Rock'), (u'Jim', u'Rock'), (u'Jim', u'Roll')] + [('Jane', 'Rock'), ('Jim', 'Rock'), ('Jim', 'Roll')] ) # QuerySet's distinct() method can correct this problem. self.assertQuerysetEqual( diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index 1e4afcf22f..0d2688e8a4 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -3,6 +3,7 @@ To define a many-to-one relationship, use ``ForeignKey()``. """ +from __future__ import unicode_literals from django.db import models @@ -13,7 +14,7 @@ class Reporter(models.Model): email = models.EmailField() def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) + return "%s %s" % (self.first_name, self.last_name) class Article(models.Model): headline = models.CharField(max_length=100) diff --git a/tests/modeltests/many_to_one/tests.py b/tests/modeltests/many_to_one/tests.py index aed952fe2f..257025583b 100644 --- a/tests/modeltests/many_to_one/tests.py +++ b/tests/modeltests/many_to_one/tests.py @@ -177,7 +177,7 @@ class ManyToOneTests(TestCase): # ... and should work fine with the unicode that comes out of forms.Form.cleaned_data self.assertQuerysetEqual( Article.objects.filter(reporter__first_name__exact='John' - ).extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']), + ).extra(where=["many_to_one_reporter.last_name='%s'" % 'Smith']), [ "", "", @@ -300,7 +300,7 @@ class ManyToOneTests(TestCase): # It's possible to use values() calls across many-to-one relations. # (Note, too, that we clear the ordering here so as not to drag the # 'headline' field into the columns being used to determine uniqueness) - d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} + d = {'reporter__first_name': 'John', 'reporter__last_name': 'Smith'} self.assertEqual([d], list(Article.objects.filter(reporter=self.r).distinct().order_by() .values('reporter__first_name', 'reporter__last_name'))) @@ -418,7 +418,7 @@ class ManyToOneTests(TestCase): reporter = Reporter.objects.create(first_name='John', last_name='Smith', email='john.smith@example.com') - lazy = ugettext_lazy(u'test') + lazy = ugettext_lazy('test') reporter.article_set.create(headline=lazy, pub_date=datetime(2011, 6, 10)) notlazy = unicode(lazy) diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 35fc9a7bc1..a4ce86f184 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -6,6 +6,7 @@ tests to use ``ModelForm``. As such, the text may not make sense in all cases, and the examples are probably a poor fit for the ``ModelForm`` syntax. In other words, most of these tests should be rewritten. """ +from __future__ import unicode_literals import os import tempfile @@ -161,7 +162,7 @@ class Price(models.Model): quantity = models.PositiveIntegerField() def __unicode__(self): - return u"%s for %s" % (self.quantity, self.price) + return "%s for %s" % (self.quantity, self.price) class Meta: unique_together = (('price', 'quantity'),) diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py index af8bcbca4e..281316a28e 100644 --- a/tests/modeltests/model_forms/tests.py +++ b/tests/modeltests/model_forms/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime import os @@ -408,7 +408,7 @@ class UniqueTest(TestCase): obj = form.save() form = ProductForm({'slug': 'teddy-bear-blue'}) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['slug'], [u'Product with this Slug already exists.']) + self.assertEqual(form.errors['slug'], ['Product with this Slug already exists.']) form = ProductForm({'slug': 'teddy-bear-blue'}, instance=obj) self.assertTrue(form.is_valid()) @@ -420,7 +420,7 @@ class UniqueTest(TestCase): form = PriceForm({'price': '6.00', 'quantity': '1'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['__all__'], [u'Price with this Price and Quantity already exists.']) + self.assertEqual(form.errors['__all__'], ['Price with this Price and Quantity already exists.']) def test_unique_null(self): title = 'I May Be Wrong But I Doubt It' @@ -430,7 +430,7 @@ class UniqueTest(TestCase): form = BookForm({'title': title, 'author': self.writer.pk}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['__all__'], [u'Book with this Title and Author already exists.']) + self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.']) form = BookForm({'title': title}) self.assertTrue(form.is_valid()) form.save() @@ -440,10 +440,10 @@ class UniqueTest(TestCase): def test_inherited_unique(self): title = 'Boss' Book.objects.create(title=title, author=self.writer, special_id=1) - form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'special_id': u'1', 'isbn': '12345'}) + form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'special_id': '1', 'isbn': '12345'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['special_id'], [u'Book with this Special id already exists.']) + self.assertEqual(form.errors['special_id'], ['Book with this Special id already exists.']) def test_inherited_unique_together(self): title = 'Boss' @@ -453,7 +453,7 @@ class UniqueTest(TestCase): form = DerivedBookForm({'title': title, 'author': self.writer.pk, 'isbn': '12345'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['__all__'], [u'Book with this Title and Author already exists.']) + self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.']) def test_abstract_inherited_unique(self): title = 'Boss' @@ -462,7 +462,7 @@ class UniqueTest(TestCase): form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'isbn': isbn}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['isbn'], [u'Derived book with this Isbn already exists.']) + self.assertEqual(form.errors['isbn'], ['Derived book with this Isbn already exists.']) def test_abstract_inherited_unique_together(self): title = 'Boss' @@ -472,30 +472,30 @@ class UniqueTest(TestCase): 'title': 'Other', 'author': self.writer.pk, 'isbn': '9876', - 'suffix1': u'0', - 'suffix2': u'0' + 'suffix1': '0', + 'suffix2': '0' }) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['__all__'], - [u'Derived book with this Suffix1 and Suffix2 already exists.']) + ['Derived book with this Suffix1 and Suffix2 already exists.']) def test_explicitpk_unspecified(self): """Test for primary_key being in the form and failing validation.""" - form = ExplicitPKForm({'key': u'', 'desc': u'' }) + form = ExplicitPKForm({'key': '', 'desc': '' }) self.assertFalse(form.is_valid()) def test_explicitpk_unique(self): """Ensure keys and blank character strings are tested for uniqueness.""" - form = ExplicitPKForm({'key': u'key1', 'desc': u''}) + form = ExplicitPKForm({'key': 'key1', 'desc': ''}) self.assertTrue(form.is_valid()) form.save() - form = ExplicitPKForm({'key': u'key1', 'desc': u''}) + form = ExplicitPKForm({'key': 'key1', 'desc': ''}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 3) - self.assertEqual(form.errors['__all__'], [u'Explicit pk with this Key and Desc already exists.']) - self.assertEqual(form.errors['desc'], [u'Explicit pk with this Desc already exists.']) - self.assertEqual(form.errors['key'], [u'Explicit pk with this Key already exists.']) + self.assertEqual(form.errors['__all__'], ['Explicit pk with this Key and Desc already exists.']) + self.assertEqual(form.errors['desc'], ['Explicit pk with this Desc already exists.']) + self.assertEqual(form.errors['key'], ['Explicit pk with this Key already exists.']) def test_unique_for_date(self): p = Post.objects.create(title="Django 1.0 is released", @@ -503,7 +503,7 @@ class UniqueTest(TestCase): form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['title'], [u'Title must be unique for Posted date.']) + self.assertEqual(form.errors['title'], ['Title must be unique for Posted date.']) form = PostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'}) self.assertTrue(form.is_valid()) form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'}) @@ -511,17 +511,17 @@ class UniqueTest(TestCase): form = PostForm({'slug': "Django 1.0", 'posted': '2008-01-01'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['slug'], [u'Slug must be unique for Posted year.']) + self.assertEqual(form.errors['slug'], ['Slug must be unique for Posted year.']) form = PostForm({'subtitle': "Finally", 'posted': '2008-09-30'}) self.assertFalse(form.is_valid()) - self.assertEqual(form.errors['subtitle'], [u'Subtitle must be unique for Posted month.']) + self.assertEqual(form.errors['subtitle'], ['Subtitle must be unique for Posted month.']) form = PostForm({'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p) self.assertTrue(form.is_valid()) form = PostForm({'title': "Django 1.0 is released"}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['posted'], [u'This field is required.']) + self.assertEqual(form.errors['posted'], ['This field is required.']) def test_inherited_unique_for_date(self): p = Post.objects.create(title="Django 1.0 is released", @@ -529,7 +529,7 @@ class UniqueTest(TestCase): form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['title'], [u'Title must be unique for Posted date.']) + self.assertEqual(form.errors['title'], ['Title must be unique for Posted date.']) form = DerivedPostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'}) self.assertTrue(form.is_valid()) form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'}) @@ -537,10 +537,10 @@ class UniqueTest(TestCase): form = DerivedPostForm({'slug': "Django 1.0", 'posted': '2008-01-01'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['slug'], [u'Slug must be unique for Posted year.']) + self.assertEqual(form.errors['slug'], ['Slug must be unique for Posted year.']) form = DerivedPostForm({'subtitle': "Finally", 'posted': '2008-09-30'}) self.assertFalse(form.is_valid()) - self.assertEqual(form.errors['subtitle'], [u'Subtitle must be unique for Posted month.']) + self.assertEqual(form.errors['subtitle'], ['Subtitle must be unique for Posted month.']) form = DerivedPostForm({'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p) self.assertTrue(form.is_valid()) @@ -624,9 +624,9 @@ class OldFormForXTests(TestCase): # save() on the resulting model instance. f = BaseCategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) self.assertEqual(f.is_valid(), True) - self.assertEqual(f.cleaned_data['url'], u'third') - self.assertEqual(f.cleaned_data['name'], u'Third test') - self.assertEqual(f.cleaned_data['slug'], u'third-test') + self.assertEqual(f.cleaned_data['url'], 'third') + self.assertEqual(f.cleaned_data['name'], 'Third test') + self.assertEqual(f.cleaned_data['slug'], 'third-test') c3 = f.save(commit=False) self.assertEqual(c3.name, "Third test") self.assertEqual(Category.objects.count(), 2) @@ -635,8 +635,8 @@ class OldFormForXTests(TestCase): # If you call save() with invalid data, you'll get a ValueError. f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'}) - self.assertEqual(f.errors['name'], [u'This field is required.']) - self.assertEqual(f.errors['slug'], [u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]) + self.assertEqual(f.errors['name'], ['This field is required.']) + self.assertEqual(f.errors['slug'], ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]) with self.assertRaises(AttributeError): f.cleaned_data with self.assertRaises(ValueError): @@ -722,9 +722,9 @@ class OldFormForXTests(TestCase):
      • ''' % (w_woodward.pk, w_royko.pk, c1.pk, c2.pk, c3.pk)) f = TestArticleForm({ - 'headline': u'Test headline', + 'headline': 'Test headline', 'slug': 'test-headline', - 'pub_date': u'1984-02-06', + 'pub_date': '1984-02-06', 'writer': unicode(w_royko.pk), 'article': 'Hello.' }, instance=art) @@ -733,13 +733,13 @@ class OldFormForXTests(TestCase): test_art = f.save() self.assertEqual(test_art.id == art_id_1, True) test_art = Article.objects.get(id=art_id_1) - self.assertEqual(test_art.headline, u'Test headline') + self.assertEqual(test_art.headline, 'Test headline') # You can create a form over a subset of the available fields # by specifying a 'fields' argument to form_for_instance. f = PartialArticleFormWithSlug({ - 'headline': u'New headline', + 'headline': 'New headline', 'slug': 'new-headline', - 'pub_date': u'1988-01-04' + 'pub_date': '1988-01-04' }, auto_id=False, instance=art) self.assertHTMLEqual(f.as_ul(), '''
      • Headline:
      • Slug:
      • @@ -748,7 +748,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(new_art.headline, u'New headline') + 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()), []) @@ -805,11 +805,11 @@ class OldFormForXTests(TestCase): ''' % (w_woodward.pk, w_royko.pk, c1.pk, c2.pk, c3.pk)) f = TestArticleForm({ - 'headline': u'New headline', - 'slug': u'new-headline', - 'pub_date': u'1988-01-04', + 'headline': 'New headline', + 'slug': 'new-headline', + 'pub_date': '1988-01-04', 'writer': unicode(w_royko.pk), - 'article': u'Hello.', + 'article': 'Hello.', 'categories': [unicode(c1.id), unicode(c2.id)] }, instance=new_art) new_art = f.save() @@ -819,16 +819,16 @@ class OldFormForXTests(TestCase): ["Entertainment", "It's a test"]) # Now, submit form data with no categories. This deletes the existing categories. - f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', - 'writer': unicode(w_royko.pk), 'article': u'Hello.'}, instance=new_art) + f = TestArticleForm({'headline': 'New headline', 'slug': 'new-headline', 'pub_date': '1988-01-04', + 'writer': unicode(w_royko.pk), 'article': 'Hello.'}, instance=new_art) 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()), []) # Create a new article, with categories, via the form. - f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', - 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) + f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01', + 'writer': unicode(w_royko.pk), 'article': 'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) new_art = f.save() art_id_2 = new_art.id self.assertEqual(art_id_2 not in (None, art_id_1), True) @@ -836,8 +836,8 @@ class OldFormForXTests(TestCase): self.assertEqual(map(lambda o: o.name, new_art.categories.order_by('name')), ["Entertainment", "It's a test"]) # Create a new article, with no categories, via the form. - f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', - 'writer': unicode(w_royko.pk), 'article': u'Test.'}) + f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01', + 'writer': unicode(w_royko.pk), 'article': 'Test.'}) new_art = f.save() art_id_3 = new_art.id self.assertEqual(art_id_3 not in (None, art_id_1, art_id_2), True) @@ -846,8 +846,8 @@ class OldFormForXTests(TestCase): # 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. - f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', - 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) + f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01', + 'writer': unicode(w_royko.pk), 'article': 'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) new_art = f.save(commit=False) # Manually save the instance @@ -929,11 +929,11 @@ class OldFormForXTests(TestCase): f = forms.ModelChoiceField(Category.objects.all()) self.assertEqual(list(f.choices), [ - (u'', u'---------'), - (c1.pk, u'Entertainment'), - (c2.pk, u"It's a test"), - (c3.pk, u'Third'), - (c4.pk, u'Fourth')]) + ('', '---------'), + (c1.pk, 'Entertainment'), + (c2.pk, "It's a test"), + (c3.pk, 'Third'), + (c4.pk, 'Fourth')]) with self.assertRaises(ValidationError): f.clean('') with self.assertRaises(ValidationError): @@ -967,10 +967,10 @@ class OldFormForXTests(TestCase): # queryset can be changed after the field is created. f.queryset = Category.objects.exclude(name='Fourth') self.assertEqual(list(f.choices), [ - (u'', u'---------'), - (c1.pk, u'Entertainment'), - (c2.pk, u"It's a test"), - (c3.pk, u'Third')]) + ('', '---------'), + (c1.pk, 'Entertainment'), + (c2.pk, "It's a test"), + (c3.pk, 'Third')]) self.assertEqual(f.clean(c3.id).name, 'Third') with self.assertRaises(ValidationError): f.clean(c4.id) @@ -978,18 +978,18 @@ class OldFormForXTests(TestCase): # check that we can safely iterate choices repeatedly gen_one = list(f.choices) gen_two = f.choices - self.assertEqual(gen_one[2], (c2.pk, u"It's a test")) + self.assertEqual(gen_one[2], (c2.pk, "It's a test")) self.assertEqual(list(gen_two), [ - (u'', u'---------'), - (c1.pk, u'Entertainment'), - (c2.pk, u"It's a test"), - (c3.pk, u'Third')]) + ('', '---------'), + (c1.pk, 'Entertainment'), + (c2.pk, "It's a test"), + (c3.pk, 'Third')]) # check that we can override the label_from_instance method to print custom labels (#4620) f.queryset = Category.objects.all() f.label_from_instance = lambda obj: "category " + str(obj) self.assertEqual(list(f.choices), [ - (u'', u'---------'), + ('', '---------'), (c1.pk, 'category Entertainment'), (c2.pk, "category It's a test"), (c3.pk, 'category Third'), @@ -999,10 +999,10 @@ class OldFormForXTests(TestCase): f = forms.ModelMultipleChoiceField(Category.objects.all()) self.assertEqual(list(f.choices), [ - (c1.pk, u'Entertainment'), - (c2.pk, u"It's a test"), - (c3.pk, u'Third'), - (c4.pk, u'Fourth')]) + (c1.pk, 'Entertainment'), + (c2.pk, "It's a test"), + (c3.pk, 'Third'), + (c4.pk, 'Fourth')]) with self.assertRaises(ValidationError): f.clean(None) with self.assertRaises(ValidationError): @@ -1047,9 +1047,9 @@ class OldFormForXTests(TestCase): # queryset can be changed after the field is created. f.queryset = Category.objects.exclude(name='Fourth') self.assertEqual(list(f.choices), [ - (c1.pk, u'Entertainment'), - (c2.pk, u"It's a test"), - (c3.pk, u'Third')]) + (c1.pk, 'Entertainment'), + (c2.pk, "It's a test"), + (c3.pk, 'Third')]) self.assertEqual(map(lambda o: o.name, f.clean([c3.id])), ["Third"]) with self.assertRaises(ValidationError): f.clean([c4.id]) @@ -1070,7 +1070,7 @@ class OldFormForXTests(TestCase): self.assertEqual(ImprovedArticleWithParentLinkForm.base_fields.keys(), []) - bw = BetterWriter(name=u'Joe Better', score=10) + bw = BetterWriter(name='Joe Better', score=10) bw.save() self.assertEqual(sorted(model_to_dict(bw).keys()), ['id', 'name', 'score', 'writer_ptr']) @@ -1092,7 +1092,7 @@ class OldFormForXTests(TestCase): data = { 'writer': unicode(w_woodward.pk), - 'age': u'65', + 'age': '65', } form = WriterProfileForm(data) instance = form.save() @@ -1111,21 +1111,21 @@ class OldFormForXTests(TestCase): def test_phone_number_field(self): f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) self.assertEqual(f.is_valid(), True) - self.assertEqual(f.cleaned_data['phone'], u'312-555-1212') - self.assertEqual(f.cleaned_data['description'], u'Assistance') + self.assertEqual(f.cleaned_data['phone'], '312-555-1212') + self.assertEqual(f.cleaned_data['description'], 'Assistance') def test_file_field(self): # Test conditions when files is either not given or empty. - f = TextFileForm(data={'description': u'Assistance'}) + f = TextFileForm(data={'description': 'Assistance'}) self.assertEqual(f.is_valid(), False) - f = TextFileForm(data={'description': u'Assistance'}, files={}) + f = TextFileForm(data={'description': 'Assistance'}, files={}) self.assertEqual(f.is_valid(), False) # Upload a file and ensure it all works as expected. f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', b'hello world')}) self.assertEqual(f.is_valid(), True) self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile) @@ -1134,7 +1134,7 @@ class OldFormForXTests(TestCase): instance.file.delete() f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', b'hello world')}) self.assertEqual(f.is_valid(), True) self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile) @@ -1143,7 +1143,7 @@ class OldFormForXTests(TestCase): # Check if the max_length attribute has been inherited from the model. f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test-maxlength.txt', b'hello world')}) self.assertEqual(f.is_valid(), False) @@ -1151,7 +1151,7 @@ class OldFormForXTests(TestCase): # save the file again, but leave it exactly as it is. f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, instance=instance) self.assertEqual(f.is_valid(), True) self.assertEqual(f.cleaned_data['file'].name, 'tests/test1.txt') @@ -1164,7 +1164,7 @@ class OldFormForXTests(TestCase): # Override the file by uploading a new one. f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', b'hello world')}, instance=instance) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1173,7 +1173,7 @@ class OldFormForXTests(TestCase): # Delete the current file since this is not done by Django. instance.file.delete() f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', b'hello world')}) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1185,14 +1185,14 @@ class OldFormForXTests(TestCase): instance.delete() # Test the non-required FileField - f = TextFileForm(data={'description': u'Assistance'}) + f = TextFileForm(data={'description': 'Assistance'}) f.fields['file'].required = False self.assertEqual(f.is_valid(), True) instance = f.save() self.assertEqual(instance.file.name, '') f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', b'hello world')}, instance=instance) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1201,12 +1201,12 @@ class OldFormForXTests(TestCase): # Instance can be edited w/out re-uploading the file and existing file should be preserved. f = TextFileForm( - data={'description': u'New Description'}, + data={'description': 'New Description'}, instance=instance) f.fields['file'].required = False self.assertEqual(f.is_valid(), True) instance = f.save() - self.assertEqual(instance.description, u'New Description') + self.assertEqual(instance.description, 'New Description') self.assertEqual(instance.file.name, 'tests/test3.txt') # Delete the current file since this is not done by Django. @@ -1214,7 +1214,7 @@ class OldFormForXTests(TestCase): instance.delete() f = TextFileForm( - data={'description': u'Assistance'}, + data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', b'hello world')}) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1229,12 +1229,12 @@ class OldFormForXTests(TestCase): self.assertEqual(bif.is_valid(), True) bif = BigIntForm({'biggie': '-9223372036854775809'}) self.assertEqual(bif.is_valid(), False) - self.assertEqual(bif.errors, {'biggie': [u'Ensure this value is greater than or equal to -9223372036854775808.']}) + self.assertEqual(bif.errors, {'biggie': ['Ensure this value is greater than or equal to -9223372036854775808.']}) bif = BigIntForm({'biggie': '9223372036854775807'}) self.assertEqual(bif.is_valid(), True) bif = BigIntForm({'biggie': '9223372036854775808'}) self.assertEqual(bif.is_valid(), False) - self.assertEqual(bif.errors, {'biggie': [u'Ensure this value is less than or equal to 9223372036854775807.']}) + self.assertEqual(bif.errors, {'biggie': ['Ensure this value is less than or equal to 9223372036854775807.']}) @skipUnless(test_images, "PIL not installed") def test_image_field(self): @@ -1248,7 +1248,7 @@ class OldFormForXTests(TestCase): image_data2 = fp.read() f = ImageFileForm( - data={'description': u'An image'}, + data={'description': 'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) self.assertEqual(f.is_valid(), True) self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile) @@ -1261,7 +1261,7 @@ class OldFormForXTests(TestCase): # because the dimension fields are not null=True. instance.image.delete(save=False) f = ImageFileForm( - data={'description': u'An image'}, + data={'description': 'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) self.assertEqual(f.is_valid(), True) self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile) @@ -1273,7 +1273,7 @@ class OldFormForXTests(TestCase): # Edit an instance that already has the (required) image defined in the model. This will not # save the image again, but leave it exactly as it is. - f = ImageFileForm(data={'description': u'Look, it changed'}, instance=instance) + f = ImageFileForm(data={'description': 'Look, it changed'}, instance=instance) self.assertEqual(f.is_valid(), True) self.assertEqual(f.cleaned_data['image'].name, 'tests/test.png') instance = f.save() @@ -1287,7 +1287,7 @@ class OldFormForXTests(TestCase): # Override the file by uploading a new one. f = ImageFileForm( - data={'description': u'Changed it'}, + data={'description': 'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}, instance=instance) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1301,7 +1301,7 @@ class OldFormForXTests(TestCase): instance.delete() f = ImageFileForm( - data={'description': u'Changed it'}, + data={'description': 'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1315,14 +1315,14 @@ class OldFormForXTests(TestCase): instance.delete() # Test the non-required ImageField - # Note: In Oracle, we expect a null ImageField to return u'' instead of + # Note: In Oracle, we expect a null ImageField to return '' instead of # None. if connection.features.interprets_empty_strings_as_nulls: - expected_null_imagefield_repr = u'' + expected_null_imagefield_repr = '' else: expected_null_imagefield_repr = None - f = OptionalImageFileForm(data={'description': u'Test'}) + f = OptionalImageFileForm(data={'description': 'Test'}) self.assertEqual(f.is_valid(), True) instance = f.save() self.assertEqual(instance.image.name, expected_null_imagefield_repr) @@ -1330,7 +1330,7 @@ class OldFormForXTests(TestCase): self.assertEqual(instance.height, None) f = OptionalImageFileForm( - data={'description': u'And a final one'}, + data={'description': 'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1340,11 +1340,11 @@ class OldFormForXTests(TestCase): # Editing the instance without re-uploading the image should not affect the image or its width/height properties f = OptionalImageFileForm( - data={'description': u'New Description'}, + data={'description': 'New Description'}, instance=instance) self.assertEqual(f.is_valid(), True) instance = f.save() - self.assertEqual(instance.description, u'New Description') + self.assertEqual(instance.description, 'New Description') self.assertEqual(instance.image.name, 'tests/test3.png') self.assertEqual(instance.width, 16) self.assertEqual(instance.height, 16) @@ -1354,7 +1354,7 @@ class OldFormForXTests(TestCase): instance.delete() f = OptionalImageFileForm( - data={'description': u'And a final one'}, + data={'description': 'And a final one'}, files={'image': SimpleUploadedFile('test4.png', image_data2)} ) self.assertEqual(f.is_valid(), True) @@ -1365,7 +1365,7 @@ class OldFormForXTests(TestCase): instance.delete() # Test callable upload_to behavior that's dependent on the value of another field in the model f = ImageFileForm( - data={'description': u'And a final one', 'path': 'foo'}, + data={'description': 'And a final one', 'path': 'foo'}, files={'image': SimpleUploadedFile('test4.png', image_data)}) self.assertEqual(f.is_valid(), True) instance = f.save() @@ -1381,22 +1381,22 @@ class OldFormForXTests(TestCase): f = CommaSeparatedIntegerForm({'field': '1,2,3'}) self.assertEqual(f.is_valid(), True) - self.assertEqual(f.cleaned_data, {'field': u'1,2,3'}) + self.assertEqual(f.cleaned_data, {'field': '1,2,3'}) f = CommaSeparatedIntegerForm({'field': '1a,2'}) - self.assertEqual(f.errors, {'field': [u'Enter only digits separated by commas.']}) + self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) f = CommaSeparatedIntegerForm({'field': ',,,,'}) self.assertEqual(f.is_valid(), True) - self.assertEqual(f.cleaned_data, {'field': u',,,,'}) + self.assertEqual(f.cleaned_data, {'field': ',,,,'}) f = CommaSeparatedIntegerForm({'field': '1.2'}) - self.assertEqual(f.errors, {'field': [u'Enter only digits separated by commas.']}) + self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) f = CommaSeparatedIntegerForm({'field': '1,a,2'}) - self.assertEqual(f.errors, {'field': [u'Enter only digits separated by commas.']}) + self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) f = CommaSeparatedIntegerForm({'field': '1,,2'}) self.assertEqual(f.is_valid(), True) - self.assertEqual(f.cleaned_data, {'field': u'1,,2'}) + self.assertEqual(f.cleaned_data, {'field': '1,,2'}) f = CommaSeparatedIntegerForm({'field': '1'}) self.assertEqual(f.is_valid(), True) - self.assertEqual(f.cleaned_data, {'field': u'1'}) + self.assertEqual(f.cleaned_data, {'field': '1'}) # This Price instance generated by this form is not valid because the quantity # field is required, but the form is valid because the field is excluded from @@ -1439,10 +1439,10 @@ class OldFormForXTests(TestCase): field = forms.ModelChoiceField(Inventory.objects.all(), to_field_name='barcode') self.assertEqual(tuple(field.choices), ( - (u'', u'---------'), - (86, u'Apple'), - (87, u'Core'), - (22, u'Pear'))) + ('', '---------'), + (86, 'Apple'), + (87, 'Core'), + (22, 'Pear'))) form = InventoryForm(instance=core) self.assertHTMLEqual(unicode(form['parent']), '''

        ' % (owner1.auto_id, owner2.auto_id)) - owner1 = Owner.objects.get(name=u'Joe Perry') + owner1 = Owner.objects.get(name='Joe Perry') FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False) self.assertEqual(FormSet.max_num, 1) @@ -815,7 +815,7 @@ class ModelFormsetTest(TestCase): 'ownerprofile-INITIAL_FORMS': '0', 'ownerprofile-MAX_NUM_FORMS': '1', 'ownerprofile-0-owner': '', - 'ownerprofile-0-age': u'54', + 'ownerprofile-0-age': '54', } formset = FormSet(data, instance=owner1) self.assertTrue(formset.is_valid()) @@ -836,7 +836,7 @@ class ModelFormsetTest(TestCase): 'ownerprofile-INITIAL_FORMS': '1', 'ownerprofile-MAX_NUM_FORMS': '1', 'ownerprofile-0-owner': unicode(owner1.auto_id), - 'ownerprofile-0-age': u'55', + 'ownerprofile-0-age': '55', } formset = FormSet(data, instance=owner1) self.assertTrue(formset.is_valid()) @@ -849,7 +849,7 @@ class ModelFormsetTest(TestCase): def test_unique_true_enforces_max_num_one(self): # ForeignKey with unique=True should enforce max_num=1 - place = Place.objects.create(pk=1, name=u'Giordanos', city=u'Chicago') + place = Place.objects.create(pk=1, name='Giordanos', city='Chicago') FormSet = inlineformset_factory(Place, Location, can_delete=False) self.assertEqual(FormSet.max_num, 1) @@ -887,7 +887,7 @@ class ModelFormsetTest(TestCase): } formset = FormSet(data) self.assertFalse(formset.is_valid()) - self.assertEqual(formset.errors, [{'slug': [u'Product with this Slug already exists.']}]) + self.assertEqual(formset.errors, [{'slug': ['Product with this Slug already exists.']}]) def test_unique_together_validation(self): FormSet = modelformset_factory(Price, extra=1) @@ -895,7 +895,7 @@ class ModelFormsetTest(TestCase): 'form-TOTAL_FORMS': '1', 'form-INITIAL_FORMS': '0', 'form-MAX_NUM_FORMS': '', - 'form-0-price': u'12.00', + 'form-0-price': '12.00', 'form-0-quantity': '1', } formset = FormSet(data) @@ -910,17 +910,17 @@ class ModelFormsetTest(TestCase): 'form-TOTAL_FORMS': '1', 'form-INITIAL_FORMS': '0', 'form-MAX_NUM_FORMS': '', - 'form-0-price': u'12.00', + 'form-0-price': '12.00', 'form-0-quantity': '1', } formset = FormSet(data) self.assertFalse(formset.is_valid()) - self.assertEqual(formset.errors, [{'__all__': [u'Price with this Price and Quantity already exists.']}]) + self.assertEqual(formset.errors, [{'__all__': ['Price with this Price and Quantity already exists.']}]) def test_unique_together_with_inlineformset_factory(self): # Also see bug #8882. - repository = Repository.objects.create(name=u'Test Repo') + repository = Repository.objects.create(name='Test Repo') FormSet = inlineformset_factory(Repository, Revision, extra=1) data = { 'revision_set-TOTAL_FORMS': '1', @@ -949,7 +949,7 @@ class ModelFormsetTest(TestCase): } formset = FormSet(data, instance=repository) self.assertFalse(formset.is_valid()) - self.assertEqual(formset.errors, [{'__all__': [u'Revision with this Repository and Revision already exists.']}]) + self.assertEqual(formset.errors, [{'__all__': ['Revision with this Repository and Revision already exists.']}]) # unique_together with inlineformset_factory with overridden form fields # Also see #9494 @@ -1040,7 +1040,7 @@ class ModelFormsetTest(TestCase): def test_inlineformset_factory_with_null_fk(self): # inlineformset_factory tests with fk having null=True. see #9462. # create some data that will exbit the issue - team = Team.objects.create(name=u"Red Vipers") + team = Team.objects.create(name="Red Vipers") Player(name="Timmy").save() Player(name="Bobby", team=team).save() @@ -1073,7 +1073,7 @@ class ModelFormsetTest(TestCase): formset = FormSet(data) self.assertFalse(formset.is_valid()) self.assertEqual(formset._non_form_errors, - [u'Please correct the duplicate data for slug.']) + ['Please correct the duplicate data for slug.']) FormSet = modelformset_factory(Price, extra=2) data = { @@ -1088,7 +1088,7 @@ class ModelFormsetTest(TestCase): formset = FormSet(data) self.assertFalse(formset.is_valid()) self.assertEqual(formset._non_form_errors, - [u'Please correct the duplicate data for price and quantity, which must be unique.']) + ['Please correct the duplicate data for price and quantity, which must be unique.']) # Only the price field is specified, this should skip any unique checks since # the unique_together is not fulfilled. This will fail with a KeyError if broken. @@ -1126,9 +1126,9 @@ class ModelFormsetTest(TestCase): formset = FormSet(data=data, instance=author) self.assertFalse(formset.is_valid()) self.assertEqual(formset._non_form_errors, - [u'Please correct the duplicate data for title.']) + ['Please correct the duplicate data for title.']) self.assertEqual(formset.errors, - [{}, {'__all__': [u'Please correct the duplicate values below.']}]) + [{}, {'__all__': ['Please correct the duplicate values below.']}]) FormSet = modelformset_factory(Post, extra=2) data = { @@ -1148,9 +1148,9 @@ class ModelFormsetTest(TestCase): formset = FormSet(data) self.assertFalse(formset.is_valid()) self.assertEqual(formset._non_form_errors, - [u'Please correct the duplicate data for title which must be unique for the date in posted.']) + ['Please correct the duplicate data for title which must be unique for the date in posted.']) self.assertEqual(formset.errors, - [{}, {'__all__': [u'Please correct the duplicate values below.']}]) + [{}, {'__all__': ['Please correct the duplicate values below.']}]) data = { 'form-TOTAL_FORMS': '2', @@ -1169,7 +1169,7 @@ class ModelFormsetTest(TestCase): formset = FormSet(data) self.assertFalse(formset.is_valid()) self.assertEqual(formset._non_form_errors, - [u'Please correct the duplicate data for slug which must be unique for the year in posted.']) + ['Please correct the duplicate data for slug which must be unique for the year in posted.']) data = { 'form-TOTAL_FORMS': '2', @@ -1188,4 +1188,4 @@ class ModelFormsetTest(TestCase): formset = FormSet(data) self.assertFalse(formset.is_valid()) self.assertEqual(formset._non_form_errors, - [u'Please correct the duplicate data for subtitle which must be unique for the month in posted.']) + ['Please correct the duplicate data for subtitle which must be unique for the month in posted.']) diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py index a0fed8a5cc..37ca603021 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/modeltests/model_inheritance/models.py @@ -11,6 +11,7 @@ Model inheritance exists in two varieties: Both styles are demonstrated here. """ +from __future__ import unicode_literals from django.db import models @@ -27,7 +28,7 @@ class CommonInfo(models.Model): ordering = ['name'] def __unicode__(self): - return u'%s %s' % (self.__class__.__name__, self.name) + return '%s %s' % (self.__class__.__name__, self.name) class Worker(CommonInfo): job = models.CharField(max_length=50) @@ -72,14 +73,14 @@ class Chef(models.Model): name = models.CharField(max_length=50) def __unicode__(self): - return u"%s the chef" % self.name + return "%s the chef" % self.name class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) def __unicode__(self): - return u"%s the place" % self.name + return "%s the place" % self.name class Rating(models.Model): rating = models.IntegerField(null=True, blank=True) @@ -97,19 +98,19 @@ class Restaurant(Place, Rating): db_table = 'my_restaurant' def __unicode__(self): - return u"%s the restaurant" % self.name + return "%s the restaurant" % self.name class ItalianRestaurant(Restaurant): serves_gnocchi = models.BooleanField() def __unicode__(self): - return u"%s the italian restaurant" % self.name + return "%s the italian restaurant" % self.name class Supplier(Place): customers = models.ManyToManyField(Restaurant, related_name='provider') def __unicode__(self): - return u"%s the supplier" % self.name + return "%s the supplier" % self.name class ParkingLot(Place): # An explicit link to the parent (we can control the attribute name). @@ -117,7 +118,7 @@ class ParkingLot(Place): main_site = models.ForeignKey(Place, related_name='lot') def __unicode__(self): - return u"%s the parking lot" % self.name + return "%s the parking lot" % self.name # # Abstract base classes with related models where the sub-class has the diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py index 429c1d019f..8c6d56a795 100644 --- a/tests/modeltests/one_to_one/models.py +++ b/tests/modeltests/one_to_one/models.py @@ -5,6 +5,7 @@ To define a one-to-one relationship, use ``OneToOneField()``. In this example, a ``Place`` optionally can be a ``Restaurant``. """ +from __future__ import unicode_literals from django.db import models @@ -14,7 +15,7 @@ class Place(models.Model): address = models.CharField(max_length=80) def __unicode__(self): - return u"%s the place" % self.name + return "%s the place" % self.name class Restaurant(models.Model): place = models.OneToOneField(Place, primary_key=True) @@ -22,14 +23,14 @@ class Restaurant(models.Model): serves_pizza = models.BooleanField() def __unicode__(self): - return u"%s the restaurant" % self.place.name + return "%s the restaurant" % self.place.name class Waiter(models.Model): restaurant = models.ForeignKey(Restaurant) name = models.CharField(max_length=50) def __unicode__(self): - return u"%s the waiter at %s" % (self.name, self.restaurant) + return "%s the waiter at %s" % (self.name, self.restaurant) class ManualPrimaryKey(models.Model): primary_key = models.CharField(max_length=10, primary_key=True) @@ -45,4 +46,4 @@ class MultiModel(models.Model): name = models.CharField(max_length=50) def __unicode__(self): - return u"Multimodel %s" % self.name + return "Multimodel %s" % self.name diff --git a/tests/modeltests/pagination/tests.py b/tests/modeltests/pagination/tests.py index b7e217ebeb..9ced9edff2 100644 --- a/tests/modeltests/pagination/tests.py +++ b/tests/modeltests/pagination/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from datetime import datetime @@ -32,7 +32,7 @@ class PaginationTests(TestCase): def test_first_page(self): paginator = Paginator(Article.objects.all(), 5) p = paginator.page(1) - self.assertEqual(u"", unicode(p)) + self.assertEqual("", unicode(p)) self.assertQuerysetEqual(p.object_list, [ "", "", @@ -52,7 +52,7 @@ class PaginationTests(TestCase): def test_last_page(self): paginator = Paginator(Article.objects.all(), 5) p = paginator.page(2) - self.assertEqual(u"", unicode(p)) + self.assertEqual("", unicode(p)) self.assertQuerysetEqual(p.object_list, [ "", "", @@ -109,7 +109,7 @@ class PaginationTests(TestCase): self.assertEqual(2, paginator.num_pages) self.assertEqual([1, 2], paginator.page_range) p = paginator.page(1) - self.assertEqual(u"", unicode(p)) + self.assertEqual("", unicode(p)) self.assertEqual([1, 2, 3, 4, 5], p.object_list) self.assertTrue(p.has_next()) self.assertFalse(p.has_previous()) diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py index b21244b6d9..43f195d357 100644 --- a/tests/modeltests/prefetch_related/tests.py +++ b/tests/modeltests/prefetch_related/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.contenttypes.models import ContentType from django.db import connection @@ -66,7 +66,7 @@ class PrefetchRelatedTests(TestCase): lists = [list(b.first_time_authors.all()) for b in Book.objects.prefetch_related('first_time_authors')] - self.assertQuerysetEqual(self.book2.authors.all(), [u""]) + self.assertQuerysetEqual(self.book2.authors.all(), [""]) def test_onetoone_reverse_no_match(self): # Regression for #17439 @@ -125,10 +125,10 @@ class PrefetchRelatedTests(TestCase): for a in qs] self.assertEqual(lists, [ - [[u"Amy"], [u"Belinda"]], # Charlotte - Poems, Jane Eyre - [[u"Amy"]], # Anne - Poems - [[u"Amy"], []], # Emily - Poems, Wuthering Heights - [[u"Amy", u"Belinda"]], # Jane - Sense and Sense + [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre + [["Amy"]], # Anne - Poems + [["Amy"], []], # Emily - Poems, Wuthering Heights + [["Amy", "Belinda"]], # Jane - Sense and Sense ]) def test_overriding_prefetch(self): @@ -139,10 +139,10 @@ class PrefetchRelatedTests(TestCase): for a in qs] self.assertEqual(lists, [ - [[u"Amy"], [u"Belinda"]], # Charlotte - Poems, Jane Eyre - [[u"Amy"]], # Anne - Poems - [[u"Amy"], []], # Emily - Poems, Wuthering Heights - [[u"Amy", u"Belinda"]], # Jane - Sense and Sense + [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre + [["Amy"]], # Anne - Poems + [["Amy"], []], # Emily - Poems, Wuthering Heights + [["Amy", "Belinda"]], # Jane - Sense and Sense ]) with self.assertNumQueries(3): qs = Author.objects.prefetch_related('books__read_by', 'books') @@ -151,10 +151,10 @@ class PrefetchRelatedTests(TestCase): for a in qs] self.assertEqual(lists, [ - [[u"Amy"], [u"Belinda"]], # Charlotte - Poems, Jane Eyre - [[u"Amy"]], # Anne - Poems - [[u"Amy"], []], # Emily - Poems, Wuthering Heights - [[u"Amy", u"Belinda"]], # Jane - Sense and Sense + [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre + [["Amy"]], # Anne - Poems + [["Amy"], []], # Emily - Poems, Wuthering Heights + [["Amy", "Belinda"]], # Jane - Sense and Sense ]) def test_get(self): @@ -166,7 +166,7 @@ class PrefetchRelatedTests(TestCase): author = Author.objects.prefetch_related('books__read_by').get(name="Charlotte") lists = [[unicode(r) for r in b.read_by.all()] for b in author.books.all()] - self.assertEqual(lists, [[u"Amy"], [u"Belinda"]]) # Poems, Jane Eyre + self.assertEqual(lists, [["Amy"], ["Belinda"]]) # Poems, Jane Eyre def test_foreign_key_then_m2m(self): """ @@ -177,10 +177,10 @@ class PrefetchRelatedTests(TestCase): qs = Author.objects.select_related('first_book').prefetch_related('first_book__read_by') lists = [[unicode(r) for r in a.first_book.read_by.all()] for a in qs] - self.assertEqual(lists, [[u"Amy"], - [u"Amy"], - [u"Amy"], - [u"Amy", "Belinda"]]) + self.assertEqual(lists, [["Amy"], + ["Amy"], + ["Amy"], + ["Amy", "Belinda"]]) def test_attribute_error(self): qs = Reader.objects.all().prefetch_related('books_read__xyz') diff --git a/tests/modeltests/proxy_models/tests.py b/tests/modeltests/proxy_models/tests.py index 738e0dbce2..7ec86e9b22 100644 --- a/tests/modeltests/proxy_models/tests.py +++ b/tests/modeltests/proxy_models/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.contenttypes.models import ContentType from django.core import management @@ -195,7 +195,7 @@ class ProxyModelTests(TestCase): signals.pre_save.connect(h3, sender=Person) signals.post_save.connect(h4, sender=Person) - dino = MyPerson.objects.create(name=u"dino") + dino = MyPerson.objects.create(name="dino") self.assertEqual(output, [ 'MyPerson pre save', 'MyPerson post save' @@ -209,7 +209,7 @@ class ProxyModelTests(TestCase): signals.pre_save.connect(h5, sender=MyPersonProxy) signals.post_save.connect(h6, sender=MyPersonProxy) - dino = MyPersonProxy.objects.create(name=u"pebbles") + dino = MyPersonProxy.objects.create(name="pebbles") self.assertEqual(output, [ 'MyPersonProxy pre save', diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py index 515c7f6c91..bae60e4b86 100644 --- a/tests/modeltests/save_delete_hooks/models.py +++ b/tests/modeltests/save_delete_hooks/models.py @@ -4,6 +4,7 @@ To execute arbitrary code around ``save()`` and ``delete()``, just subclass the methods. """ +from __future__ import unicode_literals from django.db import models @@ -17,7 +18,7 @@ class Person(models.Model): self.data = [] def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) + return "%s %s" % (self.first_name, self.last_name) def save(self, *args, **kwargs): self.data.append("Before save") diff --git a/tests/modeltests/select_related/tests.py b/tests/modeltests/select_related/tests.py index ebe2f8c2be..557a5c318a 100644 --- a/tests/modeltests/select_related/tests.py +++ b/tests/modeltests/select_related/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.test import TestCase @@ -147,13 +147,13 @@ class SelectRelatedTests(TestCase): world = Species.objects.filter(genus__name='Amanita')\ .select_related('genus__family') orders = [o.genus.family.order.name for o in world] - self.assertEqual(orders, [u'Agaricales']) + self.assertEqual(orders, ['Agaricales']) def test_field_traversal(self): with self.assertNumQueries(1): s = Species.objects.all().select_related('genus__family__order' ).order_by('id')[0:1].get().genus.family.order.name - self.assertEqual(s, u'Diptera') + self.assertEqual(s, 'Diptera') def test_depth_fields_fails(self): self.assertRaises(TypeError, diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index 9948afd4e0..3549be1b4f 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -5,6 +5,7 @@ ``django.core.serializers`` provides interfaces to converting Django ``QuerySet`` objects to and from "flat" data (i.e. strings). """ +from __future__ import unicode_literals from decimal import Decimal @@ -49,7 +50,7 @@ class AuthorProfile(models.Model): date_of_birth = models.DateField() def __unicode__(self): - return u"Profile of %s" % self.author + return "Profile of %s" % self.author class Actor(models.Model): @@ -116,4 +117,4 @@ class Player(models.Model): team = TeamField() def __unicode__(self): - return u'%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string()) + 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 e792c944a1..13cf1e7e17 100644 --- a/tests/modeltests/serializers/tests.py +++ b/tests/modeltests/serializers/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals # -*- coding: utf-8 -*- import json @@ -162,8 +162,8 @@ class SerializersTestBase(object): def test_serialize_unicode(self): """Tests that unicode makes the roundtrip intact""" - actor_name = u"Za\u017c\u00f3\u0142\u0107" - movie_title = u'G\u0119\u015bl\u0105 ja\u017a\u0144' + actor_name = "Za\u017c\u00f3\u0142\u0107" + movie_title = 'G\u0119\u015bl\u0105 ja\u017a\u0144' ac = Actor(name=actor_name) mv = Movie(title=movie_title, actor=ac) ac.save() diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py index 53b8f26606..18ca467655 100644 --- a/tests/modeltests/signals/models.py +++ b/tests/modeltests/signals/models.py @@ -1,6 +1,7 @@ """ Testing signals before/after saving and deleting. """ +from __future__ import unicode_literals from django.db import models @@ -10,11 +11,11 @@ class Person(models.Model): last_name = models.CharField(max_length=20) def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) + return "%s %s" % (self.first_name, self.last_name) class Car(models.Model): make = models.CharField(max_length=20) model = models.CharField(max_length=20) def __unicode__(self): - return u"%s %s" % (self.make, self.model) + return "%s %s" % (self.make, self.model) diff --git a/tests/modeltests/str/tests.py b/tests/modeltests/str/tests.py index 1b0eb0972f..bed9ea719b 100644 --- a/tests/modeltests/str/tests.py +++ b/tests/modeltests/str/tests.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -19,7 +19,7 @@ class SimpleTests(TestCase): def test_international(self): a = InternationalArticle.objects.create( - headline=u'Girl wins €12.500 in lottery', + 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__(). diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index a533d6a280..399d2906a8 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -20,7 +20,7 @@ testing against the contexts and templates produced by a view, rather than the HTML rendered to the end-user. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.conf import settings from django.core import mail @@ -37,12 +37,12 @@ class ClientTest(TestCase): "GET a view" # The data is ignored, but let's check it doesn't crash the system # anyway. - data = {'var': u'\xf2'} + data = {'var': '\xf2'} response = self.client.get('/test_client/get_view/', data) # Check some response details self.assertContains(response, 'This is a test') - self.assertEqual(response.context['var'], u'\xf2') + self.assertEqual(response.context['var'], '\xf2') self.assertEqual(response.templates[0].name, 'GET Template') def test_get_post_view(self): diff --git a/tests/modeltests/timezones/tests.py b/tests/modeltests/timezones/tests.py index bd9c7658f9..aadb8ba842 100644 --- a/tests/modeltests/timezones/tests.py +++ b/tests/modeltests/timezones/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime import os import sys @@ -873,13 +875,13 @@ class TemplateTests(TestCase): class LegacyFormsTests(TestCase): def test_form(self): - form = EventForm({'dt': u'2011-09-01 13:20:30'}) + form = EventForm({'dt': '2011-09-01 13:20:30'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30)) @skipIf(pytz is None, "this test requires pytz") def test_form_with_non_existent_time(self): - form = EventForm({'dt': u'2011-03-27 02:30:00'}) + form = EventForm({'dt': '2011-03-27 02:30:00'}) with timezone.override(pytz.timezone('Europe/Paris')): # this is obviously a bug self.assertTrue(form.is_valid()) @@ -887,19 +889,19 @@ class LegacyFormsTests(TestCase): @skipIf(pytz is None, "this test requires pytz") def test_form_with_ambiguous_time(self): - form = EventForm({'dt': u'2011-10-30 02:30:00'}) + form = EventForm({'dt': '2011-10-30 02:30:00'}) with timezone.override(pytz.timezone('Europe/Paris')): # this is obviously a bug self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 10, 30, 2, 30, 0)) def test_split_form(self): - form = EventSplitForm({'dt_0': u'2011-09-01', 'dt_1': u'13:20:30'}) + form = EventSplitForm({'dt_0': '2011-09-01', 'dt_1': '13:20:30'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30)) def test_model_form(self): - EventModelForm({'dt': u'2011-09-01 13:20:30'}).save() + EventModelForm({'dt': '2011-09-01 13:20:30'}).save() e = Event.objects.get() self.assertEqual(e.dt, datetime.datetime(2011, 9, 1, 13, 20, 30)) @@ -909,12 +911,12 @@ class NewFormsTests(TestCase): @requires_tz_support def test_form(self): - form = EventForm({'dt': u'2011-09-01 13:20:30'}) + form = EventForm({'dt': '2011-09-01 13:20:30'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)) def test_form_with_other_timezone(self): - form = EventForm({'dt': u'2011-09-01 17:20:30'}) + form = EventForm({'dt': '2011-09-01 17:20:30'}) with timezone.override(ICT): self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)) @@ -922,30 +924,30 @@ class NewFormsTests(TestCase): @skipIf(pytz is None, "this test requires pytz") def test_form_with_non_existent_time(self): with timezone.override(pytz.timezone('Europe/Paris')): - form = EventForm({'dt': u'2011-03-27 02:30:00'}) + form = EventForm({'dt': '2011-03-27 02:30:00'}) self.assertFalse(form.is_valid()) self.assertEqual(form.errors['dt'], - [u"2011-03-27 02:30:00 couldn't be interpreted in time zone " - u"Europe/Paris; it may be ambiguous or it may not exist."]) + ["2011-03-27 02:30:00 couldn't be interpreted in time zone " + "Europe/Paris; it may be ambiguous or it may not exist."]) @skipIf(pytz is None, "this test requires pytz") def test_form_with_ambiguous_time(self): with timezone.override(pytz.timezone('Europe/Paris')): - form = EventForm({'dt': u'2011-10-30 02:30:00'}) + form = EventForm({'dt': '2011-10-30 02:30:00'}) self.assertFalse(form.is_valid()) self.assertEqual(form.errors['dt'], - [u"2011-10-30 02:30:00 couldn't be interpreted in time zone " - u"Europe/Paris; it may be ambiguous or it may not exist."]) + ["2011-10-30 02:30:00 couldn't be interpreted in time zone " + "Europe/Paris; it may be ambiguous or it may not exist."]) @requires_tz_support def test_split_form(self): - form = EventSplitForm({'dt_0': u'2011-09-01', 'dt_1': u'13:20:30'}) + form = EventSplitForm({'dt_0': '2011-09-01', 'dt_1': '13:20:30'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)) @requires_tz_support def test_model_form(self): - EventModelForm({'dt': u'2011-09-01 13:20:30'}).save() + EventModelForm({'dt': '2011-09-01 13:20:30'}).save() e = Event.objects.get() self.assertEqual(e.dt, datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)) diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py index 66acb9fc63..7a51b9ac1c 100644 --- a/tests/modeltests/transactions/models.py +++ b/tests/modeltests/transactions/models.py @@ -6,6 +6,7 @@ each transaction upon a write, but you can decorate a function to get commit-on-success behavior. Alternatively, you can manage the transaction manually. """ +from __future__ import unicode_literals from django.db import models @@ -19,4 +20,4 @@ class Reporter(models.Model): ordering = ('first_name', 'last_name') def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) \ No newline at end of file + return "%s %s" % (self.first_name, self.last_name) diff --git a/tests/modeltests/update/tests.py b/tests/modeltests/update/tests.py index c31f4dfaa9..7a1177c1fd 100644 --- a/tests/modeltests/update/tests.py +++ b/tests/modeltests/update/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.test import TestCase @@ -77,7 +77,7 @@ class AdvancedTests(TestCase): resp = DataPoint.objects.filter(value="banana").update( value="pineapple") self.assertEqual(resp, 2) - self.assertEqual(DataPoint.objects.get(name="d2").value, u'pineapple') + self.assertEqual(DataPoint.objects.get(name="d2").value, 'pineapple') def test_update_fk(self): """ @@ -97,8 +97,8 @@ class AdvancedTests(TestCase): value="fruit", another_value="peach") self.assertEqual(resp, 1) d = DataPoint.objects.get(name="d0") - self.assertEqual(d.value, u'fruit') - self.assertEqual(d.another_value, u'peach') + self.assertEqual(d.value, 'fruit') + self.assertEqual(d.another_value, 'peach') def test_update_all(self): """ @@ -107,7 +107,7 @@ class AdvancedTests(TestCase): """ self.assertEqual(DataPoint.objects.update(value='thing'), 3) resp = DataPoint.objects.values('value').distinct() - self.assertEqual(list(resp), [{'value': u'thing'}]) + self.assertEqual(list(resp), [{'value': 'thing'}]) def test_update_slice_fail(self): """ diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py index 1a6fdd7c16..0adc9fec32 100644 --- a/tests/modeltests/validation/models.py +++ b/tests/modeltests/validation/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from datetime import datetime from django.core.exceptions import ValidationError @@ -80,8 +82,8 @@ class FlexibleDatePost(models.Model): posted = models.DateField(blank=True, null=True) class UniqueErrorsModel(models.Model): - name = models.CharField(max_length=100, unique=True, error_messages={'unique': u'Custom unique name message.'}) - no = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'}) + name = models.CharField(max_length=100, unique=True, error_messages={'unique': 'Custom unique name message.'}) + no = models.IntegerField(unique=True, error_messages={'unique': 'Custom unique number message.'}) class GenericIPAddressTestModel(models.Model): generic_ip = models.GenericIPAddressField(blank=True, null=True, unique=True) @@ -101,4 +103,4 @@ try: auto2 = models.AutoField(primary_key=True) except AssertionError as assertion_error: pass # Fail silently -assert str(assertion_error) == u"A model can't have more than one AutoField." +assert str(assertion_error) == "A model can't have more than one AutoField." diff --git a/tests/modeltests/validation/test_error_messages.py b/tests/modeltests/validation/test_error_messages.py index 4aea3d52de..62ca4e95c6 100644 --- a/tests/modeltests/validation/test_error_messages.py +++ b/tests/modeltests/validation/test_error_messages.py @@ -1,4 +1,6 @@ # -*- encoding: utf-8 -*- +from __future__ import unicode_literals + from django.core.exceptions import ValidationError from django.db import models from django.utils.unittest import TestCase @@ -13,8 +15,8 @@ class ValidationMessagesTest(TestCase): def test_autofield_field_raises_error_message(self): f = models.AutoField(primary_key=True) - self._test_validation_messages(f, u'fõo', - [u"'fõo' value must be an integer."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value must be an integer."]) # primary_key must be True. Refs #12467. with self.assertRaisesRegexp(AssertionError, "AutoFields must have primary_key=True."): @@ -22,73 +24,73 @@ class ValidationMessagesTest(TestCase): def test_integer_field_raises_error_message(self): f = models.IntegerField() - self._test_validation_messages(f, u'fõo', - [u"'fõo' value must be an integer."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value must be an integer."]) def test_boolean_field_raises_error_message(self): f = models.BooleanField() - self._test_validation_messages(f, u'fõo', - [u"'fõo' value must be either True or False."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value must be either True or False."]) def test_float_field_raises_error_message(self): f = models.FloatField() - self._test_validation_messages(f, u'fõo', - [u"'fõo' value must be a float."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value must be a float."]) def test_decimal_field_raises_error_message(self): f = models.DecimalField() - self._test_validation_messages(f, u'fõo', - [u"'fõo' value must be a decimal number."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value must be a decimal number."]) def test_null_boolean_field_raises_error_message(self): f = models.NullBooleanField() - self._test_validation_messages(f, u'fõo', - [u"'fõo' value must be either None, True or False."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value must be either None, True or False."]) def test_date_field_raises_error_message(self): f = models.DateField() - self._test_validation_messages(f, u'fõo', - [u"'fõo' value has an invalid date format. " - u"It must be in YYYY-MM-DD format."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value has an invalid date format. " + "It must be in YYYY-MM-DD format."]) self._test_validation_messages(f, 'aaaa-10-10', - [u"'aaaa-10-10' value has an invalid date format. " - u"It must be in YYYY-MM-DD format."]) + ["'aaaa-10-10' value has an invalid date format. " + "It must be in YYYY-MM-DD format."]) self._test_validation_messages(f, '2011-13-10', - [u"'2011-13-10' value has the correct format (YYYY-MM-DD) " - u"but it is an invalid date."]) + ["'2011-13-10' value has the correct format (YYYY-MM-DD) " + "but it is an invalid date."]) self._test_validation_messages(f, '2011-10-32', - [u"'2011-10-32' value has the correct format (YYYY-MM-DD) " - u"but it is an invalid date."]) + ["'2011-10-32' value has the correct format (YYYY-MM-DD) " + "but it is an invalid date."]) def test_datetime_field_raises_error_message(self): f = models.DateTimeField() # Wrong format - self._test_validation_messages(f, u'fõo', - [u"'fõo' value has an invalid format. It must be " - u"in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value has an invalid format. It must be " + "in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."]) # Correct format but invalid date self._test_validation_messages(f, '2011-10-32', - [u"'2011-10-32' value has the correct format " - u"(YYYY-MM-DD) but it is an invalid date."]) + ["'2011-10-32' value has the correct format " + "(YYYY-MM-DD) but it is an invalid date."]) # Correct format but invalid date/time self._test_validation_messages(f, '2011-10-32 10:10', - [u"'2011-10-32 10:10' value has the correct format " - u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " - u"but it is an invalid date/time."]) + ["'2011-10-32 10:10' value has the correct format " + "(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " + "but it is an invalid date/time."]) def test_time_field_raises_error_message(self): f = models.TimeField() # Wrong format - self._test_validation_messages(f, u'fõo', - [u"'fõo' value has an invalid format. It must be in " - u"HH:MM[:ss[.uuuuuu]] format."]) + self._test_validation_messages(f, 'fõo', + ["'fõo' value has an invalid format. It must be in " + "HH:MM[:ss[.uuuuuu]] format."]) # Correct format but invalid time self._test_validation_messages(f, '25:50', - [u"'25:50' value has the correct format " - u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."]) + ["'25:50' value has the correct format " + "(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."]) diff --git a/tests/modeltests/validation/test_unique.py b/tests/modeltests/validation/test_unique.py index 8f819b9d9c..038da16494 100644 --- a/tests/modeltests/validation/test_unique.py +++ b/tests/modeltests/validation/test_unique.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -83,7 +83,7 @@ class PerformUniqueChecksTest(TestCase): p = Post(title="Django 1.0 is released", posted=datetime.date(2008, 9, 3)) with self.assertRaises(ValidationError) as cm: p.full_clean() - self.assertEqual(cm.exception.message_dict, {'title': [u'Title must be unique for Posted date.']}) + self.assertEqual(cm.exception.message_dict, {'title': ['Title must be unique for Posted date.']}) # Should work without errors p = Post(title="Work on Django 1.1 begins", posted=datetime.date(2008, 9, 3)) @@ -96,17 +96,17 @@ class PerformUniqueChecksTest(TestCase): p = Post(slug="Django 1.0", posted=datetime.datetime(2008, 1, 1)) with self.assertRaises(ValidationError) as cm: p.full_clean() - self.assertEqual(cm.exception.message_dict, {'slug': [u'Slug must be unique for Posted year.']}) + self.assertEqual(cm.exception.message_dict, {'slug': ['Slug must be unique for Posted year.']}) p = Post(subtitle="Finally", posted=datetime.datetime(2008, 9, 30)) with self.assertRaises(ValidationError) as cm: p.full_clean() - self.assertEqual(cm.exception.message_dict, {'subtitle': [u'Subtitle must be unique for Posted month.']}) + self.assertEqual(cm.exception.message_dict, {'subtitle': ['Subtitle must be unique for Posted month.']}) p = Post(title="Django 1.0 is released") with self.assertRaises(ValidationError) as cm: p.full_clean() - self.assertEqual(cm.exception.message_dict, {'posted': [u'This field cannot be null.']}) + self.assertEqual(cm.exception.message_dict, {'posted': ['This field cannot be null.']}) def test_unique_for_date_with_nullable_date(self): p1 = FlexibleDatePost.objects.create(title="Django 1.0 is released", @@ -135,10 +135,10 @@ class PerformUniqueChecksTest(TestCase): m = UniqueErrorsModel(name='Some Name', no=11) with self.assertRaises(ValidationError) as cm: m.full_clean() - self.assertEqual(cm.exception.message_dict, {'name': [u'Custom unique name message.']}) + self.assertEqual(cm.exception.message_dict, {'name': ['Custom unique name message.']}) m = UniqueErrorsModel(name='Some Other Name', no=10) with self.assertRaises(ValidationError) as cm: m.full_clean() - self.assertEqual(cm.exception.message_dict, {'no': [u'Custom unique number message.']}) + self.assertEqual(cm.exception.message_dict, {'no': ['Custom unique number message.']}) diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py index f255498306..4f658203df 100644 --- a/tests/modeltests/validation/tests.py +++ b/tests/modeltests/validation/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django import forms from django.core.exceptions import NON_FIELD_ERRORS @@ -55,7 +55,7 @@ class BaseModelValidationTests(ValidationTestCase): def test_wrong_url_value_raises_error(self): mtv = ModelToValidate(number=10, name='Some Name', url='not a url') - self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'Enter a valid value.']) + self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', ['Enter a valid value.']) def test_text_greater_that_charfields_max_length_raises_erros(self): mtv = ModelToValidate(number=10, name='Some Name'*100) diff --git a/tests/modeltests/validation/validators.py b/tests/modeltests/validation/validators.py index d82b95e3ad..e58d9fd4a6 100644 --- a/tests/modeltests/validation/validators.py +++ b/tests/modeltests/validation/validators.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from . import ValidationTestCase from .models import ModelToValidate @@ -15,5 +15,5 @@ class TestModelsWithValidators(ValidationTestCase): self.assertFieldFailsValidationWithMessage( mtv.full_clean, 'f_with_custom_validator', - [u'This is not the answer to life, universe and everything!'] + ['This is not the answer to life, universe and everything!'] ) diff --git a/tests/modeltests/validators/tests.py b/tests/modeltests/validators/tests.py index a1a48bf97c..018be6ae59 100644 --- a/tests/modeltests/validators/tests.py +++ b/tests/modeltests/validators/tests.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + import re import types from datetime import datetime, timedelta from django.core.exceptions import ValidationError from django.core.validators import * +from django.test.utils import str_prefix from django.utils.unittest import TestCase @@ -176,18 +179,18 @@ def create_simple_test_method(validator, expected, value, num): class TestSimpleValidators(TestCase): def test_single_message(self): v = ValidationError('Not Valid') - self.assertEqual(str(v), "[u'Not Valid']") - self.assertEqual(repr(v), "ValidationError([u'Not Valid'])") + self.assertEqual(str(v), str_prefix("[%(_)s'Not Valid']")) + self.assertEqual(repr(v), str_prefix("ValidationError([%(_)s'Not Valid'])")) def test_message_list(self): v = ValidationError(['First Problem', 'Second Problem']) - self.assertEqual(str(v), "[u'First Problem', u'Second Problem']") - self.assertEqual(repr(v), "ValidationError([u'First Problem', u'Second Problem'])") + self.assertEqual(str(v), str_prefix("[%(_)s'First Problem', %(_)s'Second Problem']")) + self.assertEqual(repr(v), str_prefix("ValidationError([%(_)s'First Problem', %(_)s'Second Problem'])")) def test_message_dict(self): v = ValidationError({'first': 'First Problem'}) - self.assertEqual(str(v), "{'first': 'First Problem'}") - self.assertEqual(repr(v), "ValidationError({'first': 'First Problem'})") + self.assertEqual(str(v), str_prefix("{%(_)s'first': %(_)s'First Problem'}")) + self.assertEqual(repr(v), str_prefix("ValidationError({%(_)s'first': %(_)s'First Problem'})")) test_counter = 0 for validator, value, expected in TEST_DATA: diff --git a/tests/regressiontests/admin_custom_urls/tests.py b/tests/regressiontests/admin_custom_urls/tests.py index 6197827793..4a6235e7d5 100644 --- a/tests/regressiontests/admin_custom_urls/tests.py +++ b/tests/regressiontests/admin_custom_urls/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.core.urlresolvers import reverse from django.template.response import TemplateResponse @@ -39,9 +39,9 @@ class AdminCustomUrlsTest(TestCase): A smoke test to ensure POST on add_view works. """ post_data = { - '_popup': u'1', - "name": u'Action added through a popup', - "description": u"Description of added action", + '_popup': '1', + "name": 'Action added through a popup', + "description": "Description of added action", } response = self.client.post('/custom_urls/admin/admin_custom_urls/action/!add/', post_data) self.assertEqual(response.status_code, 200) diff --git a/tests/regressiontests/admin_filters/models.py b/tests/regressiontests/admin_filters/models.py index deb8ee88c3..371c67061f 100644 --- a/tests/regressiontests/admin_filters/models.py +++ b/tests/regressiontests/admin_filters/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib.auth.models import User from django.db import models @@ -9,7 +11,7 @@ class Book(models.Model): contributors = models.ManyToManyField(User, verbose_name="Verbose Contributors", related_name='books_contributed', blank=True, null=True) is_best_seller = models.NullBooleanField(default=0) date_registered = models.DateField(null=True) - no = models.IntegerField(verbose_name=u'number', blank=True, null=True) # This field is intentionally 2 characters long. See #16080. + no = models.IntegerField(verbose_name='number', blank=True, null=True) # This field is intentionally 2 characters long. See #16080. def __unicode__(self): return self.title @@ -27,4 +29,4 @@ class Employee(models.Model): name = models.CharField(max_length=100) def __unicode__(self): - return self.name \ No newline at end of file + return self.name diff --git a/tests/regressiontests/admin_filters/tests.py b/tests/regressiontests/admin_filters/tests.py index 8c9788b985..72cc5d7ee5 100644 --- a/tests/regressiontests/admin_filters/tests.py +++ b/tests/regressiontests/admin_filters/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -160,7 +160,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_unicode(filterspec.title), u'date registered') + self.assertEqual(force_unicode(filterspec.title), 'date registered') choice = select_by(filterspec.choices(changelist), "display", "Today") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?date_registered__gte=%s' @@ -181,7 +181,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_unicode(filterspec.title), u'date registered') + self.assertEqual(force_unicode(filterspec.title), 'date registered') choice = select_by(filterspec.choices(changelist), "display", "This month") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?date_registered__gte=%s' @@ -202,7 +202,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_unicode(filterspec.title), u'date registered') + self.assertEqual(force_unicode(filterspec.title), 'date registered') choice = select_by(filterspec.choices(changelist), "display", "This year") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?date_registered__gte=%s' @@ -219,7 +219,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][4] - self.assertEqual(force_unicode(filterspec.title), u'date registered') + self.assertEqual(force_unicode(filterspec.title), 'date registered') choice = select_by(filterspec.choices(changelist), "display", "Past 7 days") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?date_registered__gte=%s' @@ -243,7 +243,7 @@ class ListFiltersTests(TestCase): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'year') + self.assertEqual(force_unicode(filterspec.title), 'year') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?year__isnull=True') @@ -253,7 +253,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'year') + self.assertEqual(force_unicode(filterspec.title), 'year') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]['selected'], True) self.assertEqual(choices[2]['query_string'], '?year=2002') @@ -270,7 +270,7 @@ class ListFiltersTests(TestCase): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'Verbose Author') + self.assertEqual(force_unicode(filterspec.title), 'Verbose Author') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?author__isnull=True') @@ -280,7 +280,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'Verbose Author') + self.assertEqual(force_unicode(filterspec.title), 'Verbose Author') # order of choices depends on User model, which has no order choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertEqual(choice['selected'], True) @@ -298,7 +298,7 @@ class ListFiltersTests(TestCase): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][2] - self.assertEqual(force_unicode(filterspec.title), u'Verbose Contributors') + self.assertEqual(force_unicode(filterspec.title), 'Verbose Contributors') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True') @@ -308,7 +308,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][2] - self.assertEqual(force_unicode(filterspec.title), u'Verbose Contributors') + self.assertEqual(force_unicode(filterspec.title), 'Verbose Contributors') choice = select_by(filterspec.choices(changelist), "display", "bob") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk) @@ -326,7 +326,7 @@ class ListFiltersTests(TestCase): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'book') + self.assertEqual(force_unicode(filterspec.title), 'book') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True') @@ -336,7 +336,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'book') + self.assertEqual(force_unicode(filterspec.title), 'book') choice = select_by(filterspec.choices(changelist), "display", self.bio_book.title) self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk) @@ -351,7 +351,7 @@ class ListFiltersTests(TestCase): # Make sure the last choice is None and is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'book') + self.assertEqual(force_unicode(filterspec.title), 'book') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[-1]['selected'], True) self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True') @@ -361,7 +361,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'book') + self.assertEqual(force_unicode(filterspec.title), 'book') choice = select_by(filterspec.choices(changelist), "display", self.django_book.title) self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) @@ -387,7 +387,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] - self.assertEqual(force_unicode(filterspec.title), u'is best seller') + self.assertEqual(force_unicode(filterspec.title), 'is best seller') choice = select_by(filterspec.choices(changelist), "display", "No") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?is_best_seller__exact=0') @@ -401,7 +401,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] - self.assertEqual(force_unicode(filterspec.title), u'is best seller') + self.assertEqual(force_unicode(filterspec.title), 'is best seller') choice = select_by(filterspec.choices(changelist), "display", "Yes") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?is_best_seller__exact=1') @@ -415,7 +415,7 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][3] - self.assertEqual(force_unicode(filterspec.title), u'is best seller') + self.assertEqual(force_unicode(filterspec.title), 'is best seller') choice = select_by(filterspec.choices(changelist), "display", "Unknown") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True') @@ -434,9 +434,9 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[0]['display'], u'All') + self.assertEqual(choices[0]['display'], 'All') self.assertEqual(choices[0]['selected'], True) self.assertEqual(choices[0]['query_string'], '?') @@ -451,9 +451,9 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[1]['display'], u'the 1980\'s') + self.assertEqual(choices[1]['display'], 'the 1980\'s') self.assertEqual(choices[1]['selected'], True) self.assertEqual(choices[1]['query_string'], '?publication-decade=the+80s') @@ -468,9 +468,9 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[2]['display'], u'the 1990\'s') + self.assertEqual(choices[2]['display'], 'the 1990\'s') self.assertEqual(choices[2]['selected'], True) self.assertEqual(choices[2]['query_string'], '?publication-decade=the+90s') @@ -485,9 +485,9 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[3]['display'], u'the 2000\'s') + self.assertEqual(choices[3]['display'], 'the 2000\'s') self.assertEqual(choices[3]['selected'], True) self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s') @@ -502,14 +502,14 @@ class ListFiltersTests(TestCase): # Make sure the correct choices are selected filterspec = changelist.get_filters(request)[0][1] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[3]['display'], u'the 2000\'s') + self.assertEqual(choices[3]['display'], 'the 2000\'s') self.assertEqual(choices[3]['selected'], True) self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'Verbose Author') + self.assertEqual(force_unicode(filterspec.title), 'Verbose Author') choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) @@ -561,19 +561,19 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) self.assertEqual(len(choices), 3) - self.assertEqual(choices[0]['display'], u'All') + self.assertEqual(choices[0]['display'], 'All') self.assertEqual(choices[0]['selected'], True) self.assertEqual(choices[0]['query_string'], '?') - self.assertEqual(choices[1]['display'], u'the 1990\'s') + self.assertEqual(choices[1]['display'], 'the 1990\'s') self.assertEqual(choices[1]['selected'], False) self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s') - self.assertEqual(choices[2]['display'], u'the 2000\'s') + self.assertEqual(choices[2]['display'], 'the 2000\'s') self.assertEqual(choices[2]['selected'], False) self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s') @@ -591,7 +591,7 @@ class ListFiltersTests(TestCase): self.assertEqual(list(queryset), [self.bio_book]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_unicode(filterspec.title), u'number') + self.assertEqual(force_unicode(filterspec.title), 'number') choices = list(filterspec.choices(changelist)) self.assertEqual(choices[2]['selected'], True) self.assertEqual(choices[2]['query_string'], '?no=207') @@ -614,9 +614,9 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[2]['display'], u'the 1990\'s') + self.assertEqual(choices[2]['display'], 'the 1990\'s') self.assertEqual(choices[2]['selected'], True) self.assertEqual(choices[2]['query_string'], '?decade__in=the+90s') @@ -631,9 +631,9 @@ class ListFiltersTests(TestCase): # Make sure the correct choice is selected filterspec = changelist.get_filters(request)[0][0] - self.assertEqual(force_unicode(filterspec.title), u'publication decade') + self.assertEqual(force_unicode(filterspec.title), 'publication decade') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[2]['display'], u'the 1990\'s') + self.assertEqual(choices[2]['display'], 'the 1990\'s') self.assertEqual(choices[2]['selected'], True) self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s') @@ -657,18 +657,18 @@ class ListFiltersTests(TestCase): self.assertEqual(list(queryset), [jack, john]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_unicode(filterspec.title), u'department') + self.assertEqual(force_unicode(filterspec.title), 'department') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[0]['display'], u'All') + self.assertEqual(choices[0]['display'], 'All') self.assertEqual(choices[0]['selected'], True) self.assertEqual(choices[0]['query_string'], '?') - self.assertEqual(choices[1]['display'], u'Development') + self.assertEqual(choices[1]['display'], 'Development') self.assertEqual(choices[1]['selected'], False) self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV') - self.assertEqual(choices[2]['display'], u'Design') + self.assertEqual(choices[2]['display'], 'Design') self.assertEqual(choices[2]['selected'], False) self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN') @@ -682,17 +682,17 @@ class ListFiltersTests(TestCase): self.assertEqual(list(queryset), [john]) filterspec = changelist.get_filters(request)[0][-1] - self.assertEqual(force_unicode(filterspec.title), u'department') + self.assertEqual(force_unicode(filterspec.title), 'department') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[0]['display'], u'All') + self.assertEqual(choices[0]['display'], 'All') self.assertEqual(choices[0]['selected'], False) self.assertEqual(choices[0]['query_string'], '?') - self.assertEqual(choices[1]['display'], u'Development') + self.assertEqual(choices[1]['display'], 'Development') self.assertEqual(choices[1]['selected'], True) self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV') - self.assertEqual(choices[2]['display'], u'Design') + self.assertEqual(choices[2]['display'], 'Design') self.assertEqual(choices[2]['selected'], False) self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN') diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py index f2add00288..d7526d6020 100644 --- a/tests/regressiontests/admin_inlines/models.py +++ b/tests/regressiontests/admin_inlines/models.py @@ -2,6 +2,8 @@ Testing of admin inline formsets. """ +from __future__ import unicode_literals + from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic @@ -30,7 +32,7 @@ class Child(models.Model): parent = generic.GenericForeignKey() def __unicode__(self): - return u'I am %s, a child of %s' % (self.name, self.parent) + return 'I am %s, a child of %s' % (self.name, self.parent) class Book(models.Model): @@ -145,4 +147,4 @@ class ProfileCollection(models.Model): class Profile(models.Model): collection = models.ForeignKey(ProfileCollection, blank=True, null=True) first_name = models.CharField(max_length=100) - last_name = models.CharField(max_length=100) \ No newline at end of file + last_name = models.CharField(max_length=100) diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py index 2cca67012f..94790af56c 100644 --- a/tests/regressiontests/admin_inlines/tests.py +++ b/tests/regressiontests/admin_inlines/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase from django.contrib.admin.helpers import InlineAdminForm @@ -67,7 +67,7 @@ class TestInline(TestCase): 'shoppingweakness_set-TOTAL_FORMS': 1, 'shoppingweakness_set-INITIAL_FORMS': 0, 'shoppingweakness_set-MAX_NUM_FORMS': 0, - '_save': u'Save', + '_save': 'Save', 'person': person.id, 'max_weight': 0, 'shoppingweakness_set-0-item': item.id, @@ -85,7 +85,7 @@ class TestInline(TestCase): 'title_set-TOTAL_FORMS': 1, 'title_set-INITIAL_FORMS': 0, 'title_set-MAX_NUM_FORMS': 0, - '_save': u'Save', + '_save': 'Save', 'title_set-0-title1': 'a title', 'title_set-0-title2': 'a different title', } @@ -235,8 +235,8 @@ class TestInlinePermissions(TestCase): permission = Permission.objects.get(codename='change_holder2', content_type=self.holder_ct) self.user.user_permissions.add(permission) - author = Author.objects.create(pk=1, name=u'The Author') - book = author.books.create(name=u'The inline Book') + author = Author.objects.create(pk=1, name='The Author') + book = author.books.create(name='The inline Book') self.author_change_url = '/admin/admin_inlines/author/%i/' % author.id # Get the ID of the automatically created intermediate model for thw Author-Book m2m author_book_auto_m2m_intermediate = Author.books.through.objects.get(author=author, book=book) diff --git a/tests/regressiontests/admin_ordering/tests.py b/tests/regressiontests/admin_ordering/tests.py index b82f150f40..faae834f93 100644 --- a/tests/regressiontests/admin_ordering/tests.py +++ b/tests/regressiontests/admin_ordering/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.test import TestCase, RequestFactory from django.contrib.admin.options import ModelAdmin @@ -42,7 +42,7 @@ class TestAdminOrdering(TestCase): """ ma = ModelAdmin(Band, None) names = [b.name for b in ma.queryset(request)] - self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names) + self.assertEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) def test_specified_ordering(self): """ @@ -53,7 +53,7 @@ class TestAdminOrdering(TestCase): ordering = ('rank',) # default ordering is ('name',) ma = BandAdmin(Band, None) names = [b.name for b in ma.queryset(request)] - self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names) + self.assertEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) def test_dynamic_ordering(self): """ @@ -65,10 +65,10 @@ class TestAdminOrdering(TestCase): request.user = super_user ma = DynOrderingBandAdmin(Band, None) names = [b.name for b in ma.queryset(request)] - self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names) + self.assertEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) request.user = other_user names = [b.name for b in ma.queryset(request)] - self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names) + self.assertEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) class TestInlineModelAdminOrdering(TestCase): @@ -95,7 +95,7 @@ class TestInlineModelAdminOrdering(TestCase): """ inline = SongInlineDefaultOrdering(self.b, None) names = [s.name for s in inline.queryset(request)] - self.assertEqual([u'Dude (Looks Like a Lady)', u'Jaded', u'Pink'], names) + self.assertEqual(['Dude (Looks Like a Lady)', 'Jaded', 'Pink'], names) def test_specified_ordering(self): """ @@ -103,4 +103,4 @@ class TestInlineModelAdminOrdering(TestCase): """ inline = SongInlineNewOrdering(self.b, None) names = [s.name for s in inline.queryset(request)] - self.assertEqual([u'Jaded', u'Pink', u'Dude (Looks Like a Lady)'], names) + self.assertEqual(['Jaded', 'Pink', 'Dude (Looks Like a Lady)'], names) diff --git a/tests/regressiontests/admin_util/tests.py b/tests/regressiontests/admin_util/tests.py index 055f494dcd..ba2be363ca 100644 --- a/tests/regressiontests/admin_util/tests.py +++ b/tests/regressiontests/admin_util/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from datetime import datetime @@ -138,7 +138,7 @@ class UtilTests(unittest.TestCase): # Regression test for #13071: NullBooleanField has special # handling. display_value = display_for_field(None, models.NullBooleanField()) - expected = u'None' % settings.STATIC_URL + expected = 'None' % settings.STATIC_URL self.assertEqual(display_value, expected) display_value = display_for_field(None, models.DecimalField()) diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py index d9607496c3..01a19e6373 100644 --- a/tests/regressiontests/admin_views/admin.py +++ b/tests/regressiontests/admin_views/admin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import tempfile import os @@ -171,12 +171,12 @@ class PersonAdmin(admin.ModelAdmin): class FooAccount(Account): """A service-specific account of type Foo.""" - servicename = u'foo' + servicename = 'foo' class BarAccount(Account): """A service-specific account of type Bar.""" - servicename = u'bar' + servicename = 'bar' class FooAccountAdmin(admin.StackedInline): diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index 17533f9f80..5aa775656b 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + import datetime import tempfile import os @@ -40,14 +42,14 @@ class Book(models.Model): """ A simple book that has chapters. """ - name = models.CharField(max_length=100, verbose_name=u'¿Name?') + name = models.CharField(max_length=100, verbose_name='¿Name?') def __unicode__(self): return self.name class Promo(models.Model): - name = models.CharField(max_length=100, verbose_name=u'¿Name?') + name = models.CharField(max_length=100, verbose_name='¿Name?') book = models.ForeignKey(Book) def __unicode__(self): @@ -55,7 +57,7 @@ class Promo(models.Model): class Chapter(models.Model): - title = models.CharField(max_length=100, verbose_name=u'¿Title?') + title = models.CharField(max_length=100, verbose_name='¿Title?') content = models.TextField() book = models.ForeignKey(Book) @@ -68,19 +70,19 @@ class Chapter(models.Model): class ChapterXtra1(models.Model): - chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?') - xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') + chap = models.OneToOneField(Chapter, verbose_name='¿Chap?') + xtra = models.CharField(max_length=100, verbose_name='¿Xtra?') def __unicode__(self): - return u'¿Xtra1: %s' % self.xtra + return '¿Xtra1: %s' % self.xtra class ChapterXtra2(models.Model): - chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?') - xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') + chap = models.OneToOneField(Chapter, verbose_name='¿Chap?') + xtra = models.CharField(max_length=100, verbose_name='¿Xtra?') def __unicode__(self): - return u'¿Xtra2: %s' % self.xtra + return '¿Xtra2: %s' % self.xtra class RowLevelChangePermissionModel(models.Model): @@ -130,7 +132,7 @@ class Inquisition(models.Model): country = models.CharField(max_length=20) def __unicode__(self): - return u"by %s from %s" % (self.leader, self.country) + return "by %s from %s" % (self.leader, self.country) class Sketch(models.Model): @@ -187,7 +189,7 @@ class Account(models.Model): """ username = models.CharField(blank=False, max_length=80) persona = models.ForeignKey(Persona, related_name="accounts") - servicename = u'generic service' + servicename = 'generic service' def __unicode__(self): return "%s: %s" % (self.servicename, self.username) @@ -195,12 +197,12 @@ class Account(models.Model): class FooAccount(Account): """A service-specific account of type Foo.""" - servicename = u'foo' + servicename = 'foo' class BarAccount(Account): """A service-specific account of type Bar.""" - servicename = u'bar' + servicename = 'bar' class Subscriber(models.Model): @@ -335,7 +337,7 @@ class Category(models.Model): ordering = ('order',) def __unicode__(self): - return u'%s:o%s' % (self.id, self.order) + return '%s:o%s' % (self.id, self.order) class Link(models.Model): @@ -493,14 +495,14 @@ class Reservation(models.Model): DRIVER_CHOICES = ( - (u'bill', 'Bill G'), - (u'steve', 'Steve J'), + ('bill', 'Bill G'), + ('steve', 'Steve J'), ) RESTAURANT_CHOICES = ( - (u'indian', u'A Taste of India'), - (u'thai', u'Thai Pography'), - (u'pizza', u'Pizza Mama'), + ('indian', 'A Taste of India'), + ('thai', 'Thai Pography'), + ('pizza', 'Pizza Mama'), ) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index f9a9b15114..e220011fdc 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -1,5 +1,5 @@ # coding: utf-8 -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import os import re @@ -119,11 +119,11 @@ class AdminViewBasicTest(TestCase): A smoke test to ensure POST on add_view works. """ post_data = { - "name": u"Another Section", + "name": "Another Section", # inline data - "article_set-TOTAL_FORMS": u"3", - "article_set-INITIAL_FORMS": u"0", - "article_set-MAX_NUM_FORMS": u"0", + "article_set-TOTAL_FORMS": "3", + "article_set-INITIAL_FORMS": "0", + "article_set-MAX_NUM_FORMS": "0", } response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data) self.assertEqual(response.status_code, 302) # redirect somewhere @@ -133,56 +133,56 @@ class AdminViewBasicTest(TestCase): Ensure http response from a popup is properly escaped. """ post_data = { - '_popup': u'1', - 'title': u'title with a new\nline', - 'content': u'some content', - 'date_0': u'2010-09-10', - 'date_1': u'14:55:39', + '_popup': '1', + 'title': 'title with a new\nline', + 'content': 'some content', + 'date_0': '2010-09-10', + 'date_1': '14:55:39', } response = self.client.post('/test_admin/%s/admin_views/article/add/' % self.urlbit, post_data) self.assertEqual(response.status_code, 200) self.assertContains(response, 'dismissAddAnotherPopup') - self.assertContains(response, 'title with a new\u000Aline') + self.assertContains(response, 'title with a new\\u000Aline') # Post data for edit inline inline_post_data = { - "name": u"Test section", + "name": "Test section", # inline data - "article_set-TOTAL_FORMS": u"6", - "article_set-INITIAL_FORMS": u"3", - "article_set-MAX_NUM_FORMS": u"0", - "article_set-0-id": u"1", + "article_set-TOTAL_FORMS": "6", + "article_set-INITIAL_FORMS": "3", + "article_set-MAX_NUM_FORMS": "0", + "article_set-0-id": "1", # there is no title in database, give one here or formset will fail. - "article_set-0-title": u"Norske bostaver æøå skaper problemer", - "article_set-0-content": u"<p>Middle content</p>", - "article_set-0-date_0": u"2008-03-18", - "article_set-0-date_1": u"11:54:58", - "article_set-0-section": u"1", - "article_set-1-id": u"2", - "article_set-1-title": u"Need a title.", - "article_set-1-content": u"<p>Oldest content</p>", - "article_set-1-date_0": u"2000-03-18", - "article_set-1-date_1": u"11:54:58", - "article_set-2-id": u"3", - "article_set-2-title": u"Need a title.", - "article_set-2-content": u"<p>Newest content</p>", - "article_set-2-date_0": u"2009-03-18", - "article_set-2-date_1": u"11:54:58", - "article_set-3-id": u"", - "article_set-3-title": u"", - "article_set-3-content": u"", - "article_set-3-date_0": u"", - "article_set-3-date_1": u"", - "article_set-4-id": u"", - "article_set-4-title": u"", - "article_set-4-content": u"", - "article_set-4-date_0": u"", - "article_set-4-date_1": u"", - "article_set-5-id": u"", - "article_set-5-title": u"", - "article_set-5-content": u"", - "article_set-5-date_0": u"", - "article_set-5-date_1": u"", + "article_set-0-title": "Norske bostaver æøå skaper problemer", + "article_set-0-content": "<p>Middle content</p>", + "article_set-0-date_0": "2008-03-18", + "article_set-0-date_1": "11:54:58", + "article_set-0-section": "1", + "article_set-1-id": "2", + "article_set-1-title": "Need a title.", + "article_set-1-content": "<p>Oldest content</p>", + "article_set-1-date_0": "2000-03-18", + "article_set-1-date_1": "11:54:58", + "article_set-2-id": "3", + "article_set-2-title": "Need a title.", + "article_set-2-content": "<p>Newest content</p>", + "article_set-2-date_0": "2009-03-18", + "article_set-2-date_1": "11:54:58", + "article_set-3-id": "", + "article_set-3-title": "", + "article_set-3-content": "", + "article_set-3-date_0": "", + "article_set-3-date_1": "", + "article_set-4-id": "", + "article_set-4-title": "", + "article_set-4-content": "", + "article_set-4-date_0": "", + "article_set-4-date_1": "", + "article_set-5-id": "", + "article_set-5-title": "", + "article_set-5-content": "", + "article_set-5-date_0": "", + "article_set-5-date_1": "", } def testBasicEditPost(self): @@ -198,12 +198,12 @@ class AdminViewBasicTest(TestCase): """ post_data = self.inline_post_data.copy() post_data.update({ - '_saveasnew': u'Save+as+new', - "article_set-1-section": u"1", - "article_set-2-section": u"1", - "article_set-3-section": u"1", - "article_set-4-section": u"1", - "article_set-5-section": u"1", + '_saveasnew': 'Save+as+new', + "article_set-1-section": "1", + "article_set-2-section": "1", + "article_set-3-section": "1", + "article_set-4-section": "1", + "article_set-5-section": "1", }) response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, post_data) self.assertEqual(response.status_code, 302) # redirect somewhere @@ -1029,14 +1029,13 @@ class AdminViewPermissionsTest(TestCase): # one error in form should produce singular error message, multiple errors plural change_dict['title'] = '' post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict) - self.assertEqual(request.status_code, 200) - self.assertTrue('Please correct the error below.' in post.content, - 'Singular error message not found in response to post with one error.') + self.assertContains(post, 'Please correct the error below.', + msg_prefix='Singular error message not found in response to post with one error') + change_dict['content'] = '' post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict) - self.assertEqual(request.status_code, 200) - self.assertTrue('Please correct the errors below.' in post.content, - 'Plural error message not found in response to post with multiple errors.') + self.assertContains(post, 'Please correct the errors below.', + msg_prefix='Plural error message not found in response to post with multiple errors') self.client.get('/test_admin/admin/logout/') # Test redirection when using row-level change permissions. Refs #11513. @@ -1167,7 +1166,7 @@ class AdminViewPermissionsTest(TestCase): self.assertEqual(mail.outbox[0].subject, 'Greetings from a deleted object') article_ct = ContentType.objects.get_for_model(Article) logged = LogEntry.objects.get(content_type=article_ct, action_flag=DELETION) - self.assertEqual(logged.object_id, u'1') + self.assertEqual(logged.object_id, '1') self.client.get('/test_admin/admin/logout/') def testDisabledPermissionsWhenLoggedIn(self): @@ -1601,29 +1600,29 @@ class AdminViewUnicodeTest(TestCase): A test to ensure that POST on edit_view handles non-ascii characters. """ post_data = { - "name": u"Test lærdommer", + "name": "Test lærdommer", # inline data - "chapter_set-TOTAL_FORMS": u"6", - "chapter_set-INITIAL_FORMS": u"3", - "chapter_set-MAX_NUM_FORMS": u"0", - "chapter_set-0-id": u"1", - "chapter_set-0-title": u"Norske bostaver æøå skaper problemer", - "chapter_set-0-content": u"<p>Svært frustrerende med UnicodeDecodeError</p>", - "chapter_set-1-id": u"2", - "chapter_set-1-title": u"Kjærlighet.", - "chapter_set-1-content": u"<p>La kjærligheten til de lidende seire.</p>", - "chapter_set-2-id": u"3", - "chapter_set-2-title": u"Need a title.", - "chapter_set-2-content": u"<p>Newest content</p>", - "chapter_set-3-id": u"", - "chapter_set-3-title": u"", - "chapter_set-3-content": u"", - "chapter_set-4-id": u"", - "chapter_set-4-title": u"", - "chapter_set-4-content": u"", - "chapter_set-5-id": u"", - "chapter_set-5-title": u"", - "chapter_set-5-content": u"", + "chapter_set-TOTAL_FORMS": "6", + "chapter_set-INITIAL_FORMS": "3", + "chapter_set-MAX_NUM_FORMS": "0", + "chapter_set-0-id": "1", + "chapter_set-0-title": "Norske bostaver æøå skaper problemer", + "chapter_set-0-content": "<p>Svært frustrerende med UnicodeDecodeError</p>", + "chapter_set-1-id": "2", + "chapter_set-1-title": "Kjærlighet.", + "chapter_set-1-content": "<p>La kjærligheten til de lidende seire.</p>", + "chapter_set-2-id": "3", + "chapter_set-2-title": "Need a title.", + "chapter_set-2-content": "<p>Newest content</p>", + "chapter_set-3-id": "", + "chapter_set-3-title": "", + "chapter_set-3-content": "", + "chapter_set-4-id": "", + "chapter_set-4-title": "", + "chapter_set-4-content": "", + "chapter_set-5-id": "", + "chapter_set-5-title": "", + "chapter_set-5-content": "", } response = self.client.post('/test_admin/admin/admin_views/book/1/', post_data) @@ -1940,8 +1939,8 @@ class AdminViewListEditable(TestCase): "form-2-id": "3", "index": "0", - "_selected_action": [u'3'], - "action": [u'', u'delete_selected'], + "_selected_action": ['3'], + "action": ['', 'delete_selected'], } self.client.post('/test_admin/admin/admin_views/person/', data) @@ -1967,8 +1966,8 @@ class AdminViewListEditable(TestCase): "form-2-id": "3", "_save": "Save", - "_selected_action": [u'1'], - "action": [u'', u'delete_selected'], + "_selected_action": ['1'], + "action": ['', 'delete_selected'], } self.client.post('/test_admin/admin/admin_views/person/', data) @@ -2077,8 +2076,8 @@ class AdminInheritedInlinesTest(TestCase): def testInline(self): "Ensure that inline models which inherit from a common parent are correctly handled by admin." - foo_user = u"foo username" - bar_user = u"bar username" + foo_user = "foo username" + bar_user = "bar username" name_re = re.compile('name="(.*?)"') @@ -2090,15 +2089,15 @@ class AdminInheritedInlinesTest(TestCase): # test the add case post_data = { - "name": u"Test Name", + "name": "Test Name", # inline data - "accounts-TOTAL_FORMS": u"1", - "accounts-INITIAL_FORMS": u"0", - "accounts-MAX_NUM_FORMS": u"0", + "accounts-TOTAL_FORMS": "1", + "accounts-INITIAL_FORMS": "0", + "accounts-MAX_NUM_FORMS": "0", "accounts-0-username": foo_user, - "accounts-2-TOTAL_FORMS": u"1", - "accounts-2-INITIAL_FORMS": u"0", - "accounts-2-MAX_NUM_FORMS": u"0", + "accounts-2-TOTAL_FORMS": "1", + "accounts-2-INITIAL_FORMS": "0", + "accounts-2-MAX_NUM_FORMS": "0", "accounts-2-0-username": bar_user, } @@ -2123,19 +2122,19 @@ class AdminInheritedInlinesTest(TestCase): self.assertEqual(len(names), len(set(names))) post_data = { - "name": u"Test Name", + "name": "Test Name", "accounts-TOTAL_FORMS": "2", - "accounts-INITIAL_FORMS": u"1", - "accounts-MAX_NUM_FORMS": u"0", + "accounts-INITIAL_FORMS": "1", + "accounts-MAX_NUM_FORMS": "0", "accounts-0-username": "%s-1" % foo_user, "accounts-0-account_ptr": str(foo_id), "accounts-0-persona": str(persona_id), - "accounts-2-TOTAL_FORMS": u"2", - "accounts-2-INITIAL_FORMS": u"1", - "accounts-2-MAX_NUM_FORMS": u"0", + "accounts-2-TOTAL_FORMS": "2", + "accounts-2-INITIAL_FORMS": "1", + "accounts-2-MAX_NUM_FORMS": "0", "accounts-2-0-username": "%s-1" % bar_user, "accounts-2-0-account_ptr": str(bar_id), @@ -2387,7 +2386,7 @@ class TestCustomChangeList(TestCase): Validate that a custom ChangeList class can be used (#9749) """ # Insert some data - post_data = {"name": u"First Gadget"} + post_data = {"name": "First Gadget"} response = self.client.post('/test_admin/%s/admin_views/gadget/add/' % self.urlbit, post_data) self.assertEqual(response.status_code, 302) # redirect somewhere # Hit the page once to get messages out of the queue message list @@ -2444,12 +2443,12 @@ class AdminCustomQuerysetTest(TestCase): def test_add_model_modeladmin_only_qs(self): # only() is used in ModelAdmin.queryset() - p = Paper.objects.create(title=u"My Paper Title") + p = Paper.objects.create(title="My Paper Title") self.assertEqual(Paper.objects.count(), 1) response = self.client.get('/test_admin/admin/admin_views/paper/%s/' % p.pk) self.assertEqual(response.status_code, 200) post_data = { - "title": u"My Modified Paper Title", + "title": "My Modified Paper Title", "_save": "Save", } response = self.client.post('/test_admin/admin/admin_views/paper/%s/' % p.pk, @@ -2459,12 +2458,12 @@ class AdminCustomQuerysetTest(TestCase): self.assertContains(response, '
      • The paper "Paper_Deferred_author object" was changed successfully.
      • ', html=True) # defer() is used in ModelAdmin.queryset() - cl = CoverLetter.objects.create(author=u"John Doe") + cl = CoverLetter.objects.create(author="John Doe") self.assertEqual(CoverLetter.objects.count(), 1) response = self.client.get('/test_admin/admin/admin_views/coverletter/%s/' % cl.pk) self.assertEqual(response.status_code, 200) post_data = { - "author": u"John Doe II", + "author": "John Doe II", "_save": "Save", } response = self.client.post('/test_admin/admin/admin_views/coverletter/%s/' % cl.pk, @@ -2503,10 +2502,10 @@ class AdminInlineFileUploadTest(TestCase): Test that inline file uploads correctly display prior data (#10002). """ post_data = { - "name": u"Test Gallery", - "pictures-TOTAL_FORMS": u"2", - "pictures-INITIAL_FORMS": u"1", - "pictures-MAX_NUM_FORMS": u"0", + "name": "Test Gallery", + "pictures-TOTAL_FORMS": "2", + "pictures-INITIAL_FORMS": "1", + "pictures-MAX_NUM_FORMS": "0", "pictures-0-id": unicode(self.picture.id), "pictures-0-gallery": unicode(self.gallery.id), "pictures-0-name": "Test Picture", @@ -2527,11 +2526,11 @@ class AdminInlineTests(TestCase): def setUp(self): self.post_data = { - "name": u"Test Name", + "name": "Test Name", "widget_set-TOTAL_FORMS": "3", - "widget_set-INITIAL_FORMS": u"0", - "widget_set-MAX_NUM_FORMS": u"0", + "widget_set-INITIAL_FORMS": "0", + "widget_set-MAX_NUM_FORMS": "0", "widget_set-0-id": "", "widget_set-0-owner": "1", "widget_set-0-name": "", @@ -2543,8 +2542,8 @@ class AdminInlineTests(TestCase): "widget_set-2-name": "", "doohickey_set-TOTAL_FORMS": "3", - "doohickey_set-INITIAL_FORMS": u"0", - "doohickey_set-MAX_NUM_FORMS": u"0", + "doohickey_set-INITIAL_FORMS": "0", + "doohickey_set-MAX_NUM_FORMS": "0", "doohickey_set-0-owner": "1", "doohickey_set-0-code": "", "doohickey_set-0-name": "", @@ -2556,8 +2555,8 @@ class AdminInlineTests(TestCase): "doohickey_set-2-name": "", "grommet_set-TOTAL_FORMS": "3", - "grommet_set-INITIAL_FORMS": u"0", - "grommet_set-MAX_NUM_FORMS": u"0", + "grommet_set-INITIAL_FORMS": "0", + "grommet_set-MAX_NUM_FORMS": "0", "grommet_set-0-code": "", "grommet_set-0-owner": "1", "grommet_set-0-name": "", @@ -2569,8 +2568,8 @@ class AdminInlineTests(TestCase): "grommet_set-2-name": "", "whatsit_set-TOTAL_FORMS": "3", - "whatsit_set-INITIAL_FORMS": u"0", - "whatsit_set-MAX_NUM_FORMS": u"0", + "whatsit_set-INITIAL_FORMS": "0", + "whatsit_set-MAX_NUM_FORMS": "0", "whatsit_set-0-owner": "1", "whatsit_set-0-index": "", "whatsit_set-0-name": "", @@ -2582,8 +2581,8 @@ class AdminInlineTests(TestCase): "whatsit_set-2-name": "", "fancydoodad_set-TOTAL_FORMS": "3", - "fancydoodad_set-INITIAL_FORMS": u"0", - "fancydoodad_set-MAX_NUM_FORMS": u"0", + "fancydoodad_set-INITIAL_FORMS": "0", + "fancydoodad_set-MAX_NUM_FORMS": "0", "fancydoodad_set-0-doodad_ptr": "", "fancydoodad_set-0-owner": "1", "fancydoodad_set-0-name": "", @@ -2969,7 +2968,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): # Main form ---------------------------------------------------------- self.selenium.find_element_by_css_selector('#id_pubdate').send_keys('2012-02-18') self.get_select_option('#id_status', 'option two').click() - self.selenium.find_element_by_css_selector('#id_name').send_keys(u' this is the mAin nÀMë and it\'s awεšome') + self.selenium.find_element_by_css_selector('#id_name').send_keys(' this is the mAin nÀMë and it\'s awεšome') slug1 = self.selenium.find_element_by_css_selector('#id_slug1').get_attribute('value') slug2 = self.selenium.find_element_by_css_selector('#id_slug2').get_attribute('value') self.assertEqual(slug1, 'main-name-and-its-awesome-2012-02-18') @@ -2979,7 +2978,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): # Initial inline self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-pubdate').send_keys('2011-12-17') self.get_select_option('#id_relatedprepopulated_set-0-status', 'option one').click() - self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-name').send_keys(u' here is a sŤāÇkeð inline ! ') + self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-name').send_keys(' here is a sŤāÇkeð inline ! ') slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-0-slug2').get_attribute('value') self.assertEqual(slug1, 'here-stacked-inline-2011-12-17') @@ -2989,7 +2988,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): self.selenium.find_elements_by_link_text('Add another Related Prepopulated')[0].click() self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-pubdate').send_keys('1999-01-25') self.get_select_option('#id_relatedprepopulated_set-1-status', 'option two').click() - self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-name').send_keys(u' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog text... ') + self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-name').send_keys(' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog text... ') slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug2').get_attribute('value') self.assertEqual(slug1, 'now-you-have-another-stacked-inline-very-loooooooo') # 50 characters maximum for slug1 field @@ -2999,7 +2998,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): # Initial inline self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-pubdate').send_keys('1234-12-07') self.get_select_option('#id_relatedprepopulated_set-2-0-status', 'option two').click() - self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-name').send_keys(u'And now, with a tÃbűlaŘ inline !!!') + self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-name').send_keys('And now, with a tÃbűlaŘ inline !!!') slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-0-slug2').get_attribute('value') self.assertEqual(slug1, 'and-now-tabular-inline-1234-12-07') @@ -3009,7 +3008,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): self.selenium.find_elements_by_link_text('Add another Related Prepopulated')[1].click() self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-pubdate').send_keys('1981-08-22') self.get_select_option('#id_relatedprepopulated_set-2-1-status', 'option one').click() - self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-name').send_keys(u'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters') + self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-name').send_keys('a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters') slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-slug1').get_attribute('value') slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-2-1-slug2').get_attribute('value') self.assertEqual(slug1, 'tabular-inline-ignored-characters-1981-08-22') @@ -3029,7 +3028,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): self.assertEqual(MainPrepopulated.objects.all().count(), 1) MainPrepopulated.objects.get( - name=u' this is the mAin nÀMë and it\'s awεšome', + name=' this is the mAin nÀMë and it\'s awεšome', pubdate='2012-02-18', status='option two', slug1='main-name-and-its-awesome-2012-02-18', @@ -3037,28 +3036,28 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): ) self.assertEqual(RelatedPrepopulated.objects.all().count(), 4) RelatedPrepopulated.objects.get( - name=u' here is a sŤāÇkeð inline ! ', + name=' here is a sŤāÇkeð inline ! ', pubdate='2011-12-17', status='option one', slug1='here-stacked-inline-2011-12-17', slug2='option-one-here-stacked-inline', ) RelatedPrepopulated.objects.get( - name=u' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooo', # 75 characters in name field + name=' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooo', # 75 characters in name field pubdate='1999-01-25', status='option two', slug1='now-you-have-another-stacked-inline-very-loooooooo', slug2='option-two-now-you-have-another-stacked-inline-very-looooooo', ) RelatedPrepopulated.objects.get( - name=u'And now, with a tÃbűlaŘ inline !!!', + name='And now, with a tÃbűlaŘ inline !!!', pubdate='1234-12-07', status='option two', slug1='and-now-tabular-inline-1234-12-07', slug2='option-two-and-now-tabular-inline', ) RelatedPrepopulated.objects.get( - name=u'a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters', + name='a tÃbűlaŘ inline with ignored ;"&*^\%$#@-/`~ characters', pubdate='1981-08-22', status='option one', slug1='tabular-inline-ignored-characters-1981-08-22', @@ -3230,7 +3229,7 @@ class UserAdminTest(TestCase): adminform = response.context['adminform'] self.assertTrue('password' not in adminform.form.errors) self.assertEqual(adminform.form.errors['password2'], - [u"The two password fields didn't match."]) + ["The two password fields didn't match."]) def test_user_fk_popup(self): """Quick user addition in a FK popup shouldn't invoke view for further user customization""" @@ -3569,7 +3568,7 @@ class AdminCustomSaveRelatedTests(TestCase): children_names = list(Child.objects.order_by('name').values_list('name', flat=True)) self.assertEqual('Josh Stone', Parent.objects.latest('id').name) - self.assertEqual([u'Catherine Stone', u'Paul Stone'], children_names) + self.assertEqual(['Catherine Stone', 'Paul Stone'], children_names) def test_should_be_able_to_edit_related_objects_on_change_view(self): parent = Parent.objects.create(name='Josh Stone') @@ -3589,7 +3588,7 @@ class AdminCustomSaveRelatedTests(TestCase): children_names = list(Child.objects.order_by('name').values_list('name', flat=True)) self.assertEqual('Josh Stone', Parent.objects.latest('id').name) - self.assertEqual([u'Catherine Stone', u'Paul Stone'], children_names) + self.assertEqual(['Catherine Stone', 'Paul Stone'], children_names) def test_should_be_able_to_edit_related_objects_on_changelist_view(self): parent = Parent.objects.create(name='Josh Rock') @@ -3608,7 +3607,7 @@ class AdminCustomSaveRelatedTests(TestCase): children_names = list(Child.objects.order_by('name').values_list('name', flat=True)) self.assertEqual('Josh Stone', Parent.objects.latest('id').name) - self.assertEqual([u'Catherine Stone', u'Paul Stone'], children_names) + self.assertEqual(['Catherine Stone', 'Paul Stone'], children_names) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index 1973d5069f..81a4ec7aba 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/regressiontests/admin_widgets/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.db import models from django.contrib.auth.models import User @@ -60,7 +62,7 @@ class Car(models.Model): model = models.CharField(max_length=30) def __unicode__(self): - return u"%s %s" % (self.make, self.model) + return "%s %s" % (self.make, self.model) class CarTire(models.Model): """ diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index be1d294051..4b115431c1 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/regressiontests/admin_widgets/tests.py @@ -1,5 +1,5 @@ # encoding: utf-8 -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from datetime import datetime @@ -169,7 +169,7 @@ class AdminForeignKeyRawIdWidget(DjangoTestCase): pk = band.pk band.delete() post_data = { - "band": u'%s' % pk, + "band": '%s' % pk, } # Try posting with a non-existent pk in a raw id field: this # should result in an error message, not a server exception. @@ -379,10 +379,10 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase): self.assertEqual(w._has_changed(None, None), False) self.assertEqual(w._has_changed([], None), False) - self.assertEqual(w._has_changed(None, [u'1']), True) - self.assertEqual(w._has_changed([1, 2], [u'1', u'2']), False) - self.assertEqual(w._has_changed([1, 2], [u'1']), True) - self.assertEqual(w._has_changed([1, 2], [u'1', u'3']), True) + self.assertEqual(w._has_changed(None, ['1']), True) + self.assertEqual(w._has_changed([1, 2], ['1', '2']), False) + self.assertEqual(w._has_changed([1, 2], ['1']), True) + self.assertEqual(w._has_changed([1, 2], ['1', '3']), True) def test_m2m_related_model_not_in_admin(self): # M2M relationship with model not registered with admin site. Raw ID diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index 36a54c0b17..e5f12e5781 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime import pickle @@ -119,7 +119,7 @@ class AggregationTests(TestCase): self.assertObjectAttrs(obj, contact_id=3, id=2, - isbn=u'067232959', + isbn='067232959', mean_auth_age=45.0, name='Sams Teach Yourself Django in 24 Hours', pages=528, @@ -136,9 +136,9 @@ class AggregationTests(TestCase): self.assertObjectAttrs(obj, contact_id=3, id=2, - isbn=u'067232959', + isbn='067232959', mean_auth_age=45.0, - name=u'Sams Teach Yourself Django in 24 Hours', + name='Sams Teach Yourself Django in 24 Hours', pages=528, price=Decimal("23.09"), pubdate=datetime.date(2008, 3, 3), @@ -156,9 +156,9 @@ class AggregationTests(TestCase): self.assertEqual(obj, { "contact_id": 3, "id": 2, - "isbn": u"067232959", + "isbn": "067232959", "mean_auth_age": 45.0, - "name": u"Sams Teach Yourself Django in 24 Hours", + "name": "Sams Teach Yourself Django in 24 Hours", "pages": 528, "price": Decimal("23.09"), "pubdate": datetime.date(2008, 3, 3), @@ -175,9 +175,9 @@ class AggregationTests(TestCase): self.assertEqual(obj, { 'contact_id': 3, 'id': 2, - 'isbn': u'067232959', + 'isbn': '067232959', 'mean_auth_age': 45.0, - 'name': u'Sams Teach Yourself Django in 24 Hours', + 'name': 'Sams Teach Yourself Django in 24 Hours', 'pages': 528, 'price': Decimal("23.09"), 'pubdate': datetime.date(2008, 3, 3), @@ -189,13 +189,13 @@ class AggregationTests(TestCase): # unless it is explicitly named obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1) self.assertEqual(obj, { - "name": u'The Definitive Guide to Django: Web Development Done Right', + "name": 'The Definitive Guide to Django: Web Development Done Right', }) obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name','mean_auth_age').get(pk=1) self.assertEqual(obj, { 'mean_auth_age': 34.5, - 'name': u'The Definitive Guide to Django: Web Development Done Right', + 'name': 'The Definitive Guide to Django: Web Development Done Right', }) # If an annotation isn't included in the values, it can still be used @@ -203,7 +203,7 @@ class AggregationTests(TestCase): qs = Book.objects.annotate(n_authors=Count('authors')).values('name').filter(n_authors__gt=2) self.assertQuerysetEqual( qs, [ - {"name": u'Python Web Development with Django'} + {"name": 'Python Web Development with Django'} ], lambda b: b, ) @@ -213,7 +213,7 @@ class AggregationTests(TestCase): obj = Book.objects.values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1) self.assertEqual(obj, { 'mean_auth_age': 34.5, - 'name': u'The Definitive Guide to Django: Web Development Done Right', + 'name': 'The Definitive Guide to Django: Web Development Done Right', }) # Check that all of the objects are getting counted (allow_nulls) and @@ -298,8 +298,8 @@ class AggregationTests(TestCase): self.assertEqual(obj, { 'contact_id': 8, 'id': 5, - 'isbn': u'013790395', - 'name': u'Artificial Intelligence: A Modern Approach', + 'isbn': '013790395', + 'name': 'Artificial Intelligence: A Modern Approach', 'num_authors': 2, 'pages': 1132, 'price': Decimal("82.8"), @@ -338,8 +338,8 @@ class AggregationTests(TestCase): qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') self.assertQuerysetEqual( qs, [ - {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, - {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7} + {'num_books': 1, 'name': 'Morgan Kaufmann', 'num_awards': 9}, + {'num_books': 2, 'name': 'Prentice Hall', 'num_awards': 7} ], lambda p: p, ) @@ -347,9 +347,9 @@ class AggregationTests(TestCase): qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') self.assertQuerysetEqual( qs, [ - {'num_books': 2, 'name': u'Apress', 'num_awards': 3}, - {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, - {'num_books': 1, 'name': u'Sams', 'num_awards': 1} + {'num_books': 2, 'name': 'Apress', 'num_awards': 3}, + {'num_books': 0, 'name': "Jonno's House of Books", 'num_awards': 0}, + {'num_books': 1, 'name': 'Sams', 'num_awards': 1} ], lambda p: p, ) @@ -358,8 +358,8 @@ class AggregationTests(TestCase): qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).order_by('name').values('name','num_books','num_awards') self.assertQuerysetEqual( qs, [ - {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, - {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7} + {'num_books': 1, 'name': 'Morgan Kaufmann', 'num_awards': 9}, + {'num_books': 2, 'name': 'Prentice Hall', 'num_awards': 7} ], lambda p: p, ) @@ -367,9 +367,9 @@ class AggregationTests(TestCase): qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') self.assertQuerysetEqual( qs, [ - {'num_books': 2, 'name': u'Apress', 'num_awards': 3}, - {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, - {'num_books': 1, 'name': u'Sams', 'num_awards': 1} + {'num_books': 2, 'name': 'Apress', 'num_awards': 3}, + {'num_books': 0, 'name': "Jonno's House of Books", 'num_awards': 0}, + {'num_books': 1, 'name': 'Sams', 'num_awards': 1} ], lambda p: p, ) @@ -399,7 +399,7 @@ class AggregationTests(TestCase): qs = Publisher.objects.filter(pk=5).annotate(num_authors=Count('book__authors'), avg_authors=Avg('book__authors'), max_authors=Max('book__authors'), max_price=Max('book__price'), max_rating=Max('book__rating')).values() self.assertQuerysetEqual( qs, [ - {'max_authors': None, 'name': u"Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None} + {'max_authors': None, 'name': "Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None} ], lambda p: p ) @@ -424,10 +424,10 @@ class AggregationTests(TestCase): qs = Book.objects.filter(rating__lt=4.5).select_related().annotate(Avg('authors__age')) self.assertQuerysetEqual( qs, [ - (u'Artificial Intelligence: A Modern Approach', 51.5, u'Prentice Hall', u'Peter Norvig'), - (u'Practical Django Projects', 29.0, u'Apress', u'James Bennett'), - (u'Python Web Development with Django', Approximate(30.333, places=2), u'Prentice Hall', u'Jeffrey Forcier'), - (u'Sams Teach Yourself Django in 24 Hours', 45.0, u'Sams', u'Brad Dayley') + ('Artificial Intelligence: A Modern Approach', 51.5, 'Prentice Hall', 'Peter Norvig'), + ('Practical Django Projects', 29.0, 'Apress', 'James Bennett'), + ('Python Web Development with Django', Approximate(30.333, places=2), 'Prentice Hall', 'Jeffrey Forcier'), + ('Sams Teach Yourself Django in 24 Hours', 45.0, 'Sams', 'Brad Dayley') ], lambda b: (b.name, b.authors__age__avg, b.publisher.name, b.contact.name) ) @@ -491,19 +491,19 @@ class AggregationTests(TestCase): # But age isn't included in the ValuesQuerySet, so it is. results = Author.objects.values('name').annotate(age=Count('book_contact_set')).order_by('name') self.assertEqual(len(results), 9) - self.assertEqual(results[0]['name'], u'Adrian Holovaty') + self.assertEqual(results[0]['name'], 'Adrian Holovaty') self.assertEqual(results[0]['age'], 1) # Same problem, but aggregating over m2m fields results = Author.objects.values('name').annotate(age=Avg('friends__age')).order_by('name') self.assertEqual(len(results), 9) - self.assertEqual(results[0]['name'], u'Adrian Holovaty') + self.assertEqual(results[0]['name'], 'Adrian Holovaty') self.assertEqual(results[0]['age'], 32.0) # Same problem, but colliding with an m2m field results = Author.objects.values('name').annotate(friends=Count('friends')).order_by('name') self.assertEqual(len(results), 9) - self.assertEqual(results[0]['name'], u'Adrian Holovaty') + self.assertEqual(results[0]['name'], 'Adrian Holovaty') self.assertEqual(results[0]['friends'], 2) def test_reverse_relation_name_conflict(self): @@ -531,12 +531,12 @@ class AggregationTests(TestCase): books.aggregate(Avg("authors__age")) self.assertQuerysetEqual( books.all(), [ - u'Artificial Intelligence: A Modern Approach', - u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', - u'Practical Django Projects', - u'Python Web Development with Django', - u'Sams Teach Yourself Django in 24 Hours', - u'The Definitive Guide to Django: Web Development Done Right' + 'Artificial Intelligence: A Modern Approach', + 'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', + 'Practical Django Projects', + 'Python Web Development with Django', + 'Sams Teach Yourself Django in 24 Hours', + 'The Definitive Guide to Django: Web Development Done Right' ], lambda b: b.name ) @@ -632,8 +632,8 @@ class AggregationTests(TestCase): qs = HardbackBook.objects.annotate(n_authors=Count('book_ptr__authors')).values('name', 'n_authors') self.assertQuerysetEqual( qs, [ - {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, - {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'} + {'n_authors': 2, 'name': 'Artificial Intelligence: A Modern Approach'}, + {'n_authors': 1, 'name': 'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'} ], lambda h: h ) @@ -641,8 +641,8 @@ class AggregationTests(TestCase): qs = HardbackBook.objects.annotate(n_authors=Count('authors')).values('name', 'n_authors') self.assertQuerysetEqual( qs, [ - {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, - {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'} + {'n_authors': 2, 'name': 'Artificial Intelligence: A Modern Approach'}, + {'n_authors': 1, 'name': 'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'} ], lambda h: h, ) diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py index 1a78a87eca..af4952dce8 100644 --- a/tests/regressiontests/backends/models.py +++ b/tests/regressiontests/backends/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType from django.db import models, connection @@ -16,7 +18,7 @@ class Person(models.Model): last_name = models.CharField(max_length=20) def __unicode__(self): - return u'%s %s' % (self.first_name, self.last_name) + return '%s %s' % (self.first_name, self.last_name) class SchoolClass(models.Model): @@ -58,7 +60,7 @@ class Reporter(models.Model): last_name = models.CharField(max_length=30) def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) + return "%s %s" % (self.first_name, self.last_name) class Article(models.Model): diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 109e1a5bcc..2569a3236f 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Unit and doctests for specific database backends. -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime import threading @@ -393,9 +393,9 @@ class BackendTestCase(TestCase): qn(f3.column))) cursor = connection.cursor() cursor.execute(query2) - self.assertEqual(cursor.fetchone(), (u'Clark', u'Kent')) - self.assertEqual(list(cursor.fetchmany(2)), [(u'Jane', u'Doe'), (u'John', u'Doe')]) - self.assertEqual(list(cursor.fetchall()), [(u'Mary', u'Agnelline'), (u'Peter', u'Parker')]) + self.assertEqual(cursor.fetchone(), ('Clark', 'Kent')) + self.assertEqual(list(cursor.fetchmany(2)), [('Jane', 'Doe'), ('John', 'Doe')]) + self.assertEqual(list(cursor.fetchall()), [('Mary', 'Agnelline'), ('Peter', 'Parker')]) def test_database_operations_helper_class(self): # Ticket #13630 diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 88fe5471cf..264ef74abd 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -2,7 +2,7 @@ # Unit tests for cache framework # Uses whatever cache backend is set in the test settings file. -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import hashlib import os @@ -139,10 +139,10 @@ class DummyCacheTests(unittest.TestCase): def test_unicode(self): "Unicode values are ignored by the dummy cache" stuff = { - u'ascii': u'ascii_value', - u'unicode_ascii': u'Iñtërnâtiônàlizætiøn1', - u'Iñtërnâtiônàlizætiøn': u'Iñtërnâtiônàlizætiøn2', - u'ascii2': {u'x' : 1 } + 'ascii': 'ascii_value', + 'unicode_ascii': 'Iñtërnâtiônàlizætiøn1', + 'Iñtërnâtiônàlizætiøn': 'Iñtërnâtiônàlizætiøn2', + 'ascii2': {'x' : 1 } } for (key, value) in stuff.items(): self.cache.set(key, value) @@ -337,10 +337,10 @@ class BaseCacheTests(object): def test_unicode(self): # Unicode values can be cached stuff = { - u'ascii': u'ascii_value', - u'unicode_ascii': u'Iñtërnâtiônàlizætiøn1', - u'Iñtërnâtiônàlizætiøn': u'Iñtërnâtiônàlizætiøn2', - u'ascii2': {u'x' : 1 } + 'ascii': 'ascii_value', + 'unicode_ascii': 'Iñtërnâtiônàlizætiøn1', + 'Iñtërnâtiônàlizætiøn': 'Iñtërnâtiônàlizætiøn2', + 'ascii2': {'x' : 1 } } # Test `set` for (key, value) in stuff.items(): @@ -1337,12 +1337,12 @@ class CacheI18nTest(TestCase): request = self._get_request() response = HttpResponse() with timezone.override(CustomTzName()): - CustomTzName.name = 'Hora estándar de Argentina' # UTF-8 string + CustomTzName.name = 'Hora estándar de Argentina'.encode('UTF-8') # UTF-8 string sanitized_name = 'Hora_estndar_de_Argentina' self.assertIn(sanitized_name, learn_cache_key(request, response), "Cache keys should include the time zone name when time zones are active") - CustomTzName.name = u'Hora estándar de Argentina' # unicode + CustomTzName.name = 'Hora estándar de Argentina' # unicode sanitized_name = 'Hora_estndar_de_Argentina' self.assertIn(sanitized_name, learn_cache_key(request, response), "Cache keys should include the time zone name when time zones are active") diff --git a/tests/regressiontests/comment_tests/tests/comment_view_tests.py b/tests/regressiontests/comment_tests/tests/comment_view_tests.py index 5edc58fe2d..429f3b2bf2 100644 --- a/tests/regressiontests/comment_tests/tests/comment_view_tests.py +++ b/tests/regressiontests/comment_tests/tests/comment_view_tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -54,7 +54,7 @@ class CommentViewTests(CommentTestCase): a = Article.objects.get(pk=1) data = self.getValidData(a) data["comment"] = "This is another comment" - data["object_pk"] = u'\ufffd' + data["object_pk"] = '\ufffd' response = self.client.post("/post/", data) self.assertEqual(response.status_code, 400) @@ -258,7 +258,7 @@ class CommentViewTests(CommentTestCase): data["comment"] = "This is another comment" response = self.client.post("/post/", data) location = response["Location"] - broken_location = location + u"\ufffd" + broken_location = location + "\ufffd" response = self.client.get(broken_location) self.assertEqual(response.status_code, 200) diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index 2d9b4f755f..52fd3ea1c7 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from django.conf import settings from django.core.context_processors import csrf @@ -11,7 +12,7 @@ from django.views.decorators.csrf import csrf_exempt, requires_csrf_token, ensur # Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests def post_form_response(): - resp = HttpResponse(content=u""" + resp = HttpResponse(content="""

        \u00a1Unicode!
        """, mimetype="text/html") return resp @@ -215,18 +216,18 @@ class CsrfViewMiddlewareTest(TestCase): """ req = self._get_GET_no_csrf_cookie_request() resp = token_view(req) - self.assertEqual(u"", resp.content) + self.assertEqual("", resp.content) def test_token_node_empty_csrf_cookie(self): """ Check that we get a new token if the csrf_cookie is the empty string """ req = self._get_GET_no_csrf_cookie_request() - req.COOKIES[settings.CSRF_COOKIE_NAME] = "" + req.COOKIES[settings.CSRF_COOKIE_NAME] = b"" CsrfViewMiddleware().process_view(req, token_view, (), {}) resp = token_view(req) - self.assertNotEqual(u"", resp.content) + self.assertNotEqual("", resp.content) def test_token_node_with_csrf_cookie(self): """ diff --git a/tests/regressiontests/custom_columns_regress/models.py b/tests/regressiontests/custom_columns_regress/models.py index fcb5a4be44..c768c12772 100644 --- a/tests/regressiontests/custom_columns_regress/models.py +++ b/tests/regressiontests/custom_columns_regress/models.py @@ -5,6 +5,7 @@ Checks some pathological column naming to make sure it doesn't break table creation or queries. """ +from __future__ import unicode_literals from django.db import models @@ -27,7 +28,7 @@ class Author(models.Model): last_name = models.CharField(max_length=30, db_column='last name') def __unicode__(self): - return u'%s %s' % (self.first_name, self.last_name) + return '%s %s' % (self.first_name, self.last_name) class Meta: db_table = 'my author table' diff --git a/tests/regressiontests/datatypes/tests.py b/tests/regressiontests/datatypes/tests.py index fb94e831ba..f8f6802041 100644 --- a/tests/regressiontests/datatypes/tests.py +++ b/tests/regressiontests/datatypes/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -62,7 +62,7 @@ class DataTypesTestCase(TestCase): d2 = Donut.objects.create(name='Apple Fritter', consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59)) - self.assertEqual([u'Apple Fritter', u'Date Test 2007'], + self.assertEqual(['Apple Fritter', 'Date Test 2007'], list(Donut.objects.filter(consumed_at__year=2007).order_by('name').values_list('name', flat=True))) self.assertEqual(0, Donut.objects.filter(consumed_at__year=2005).count()) @@ -71,7 +71,7 @@ class DataTypesTestCase(TestCase): def test_textfields_unicode(self): """Regression test for #10238: TextField values returned from the database should be unicode.""" - d = Donut.objects.create(name=u'Jelly Donut', review=u'Outstanding') + d = Donut.objects.create(name='Jelly Donut', review='Outstanding') newd = Donut.objects.get(id=d.id) self.assertTrue(isinstance(newd.review, unicode)) diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 7d0b8d61b6..ffa0a01132 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals import datetime import decimal @@ -12,39 +13,39 @@ from django.utils.safestring import SafeData class DefaultFiltersTests(TestCase): def test_floatformat(self): - self.assertEqual(floatformat(7.7), u'7.7') - self.assertEqual(floatformat(7.0), u'7') - self.assertEqual(floatformat(0.7), u'0.7') - self.assertEqual(floatformat(0.07), u'0.1') - self.assertEqual(floatformat(0.007), u'0.0') - self.assertEqual(floatformat(0.0), u'0') - self.assertEqual(floatformat(7.7, 3), u'7.700') - self.assertEqual(floatformat(6.000000, 3), u'6.000') - self.assertEqual(floatformat(6.200000, 3), u'6.200') - self.assertEqual(floatformat(6.200000, -3), u'6.200') - self.assertEqual(floatformat(13.1031, -3), u'13.103') - self.assertEqual(floatformat(11.1197, -2), u'11.12') - self.assertEqual(floatformat(11.0000, -2), u'11') - self.assertEqual(floatformat(11.000001, -2), u'11.00') - self.assertEqual(floatformat(8.2798, 3), u'8.280') - self.assertEqual(floatformat(5555.555, 2), u'5555.56') - self.assertEqual(floatformat(001.3000, 2), u'1.30') - self.assertEqual(floatformat(0.12345, 2), u'0.12') - self.assertEqual(floatformat(decimal.Decimal('555.555'), 2), u'555.56') - self.assertEqual(floatformat(decimal.Decimal('09.000')), u'9') - self.assertEqual(floatformat(u'foo'), u'') - self.assertEqual(floatformat(13.1031, u'bar'), u'13.1031') - self.assertEqual(floatformat(18.125, 2), u'18.13') - self.assertEqual(floatformat(u'foo', u'bar'), u'') - self.assertEqual(floatformat(u'¿Cómo esta usted?'), u'') - self.assertEqual(floatformat(None), u'') + self.assertEqual(floatformat(7.7), '7.7') + self.assertEqual(floatformat(7.0), '7') + self.assertEqual(floatformat(0.7), '0.7') + self.assertEqual(floatformat(0.07), '0.1') + self.assertEqual(floatformat(0.007), '0.0') + self.assertEqual(floatformat(0.0), '0') + self.assertEqual(floatformat(7.7, 3), '7.700') + self.assertEqual(floatformat(6.000000, 3), '6.000') + self.assertEqual(floatformat(6.200000, 3), '6.200') + self.assertEqual(floatformat(6.200000, -3), '6.200') + self.assertEqual(floatformat(13.1031, -3), '13.103') + self.assertEqual(floatformat(11.1197, -2), '11.12') + self.assertEqual(floatformat(11.0000, -2), '11') + self.assertEqual(floatformat(11.000001, -2), '11.00') + self.assertEqual(floatformat(8.2798, 3), '8.280') + self.assertEqual(floatformat(5555.555, 2), '5555.56') + self.assertEqual(floatformat(001.3000, 2), '1.30') + self.assertEqual(floatformat(0.12345, 2), '0.12') + self.assertEqual(floatformat(decimal.Decimal('555.555'), 2), '555.56') + self.assertEqual(floatformat(decimal.Decimal('09.000')), '9') + self.assertEqual(floatformat('foo'), '') + self.assertEqual(floatformat(13.1031, 'bar'), '13.1031') + self.assertEqual(floatformat(18.125, 2), '18.13') + self.assertEqual(floatformat('foo', 'bar'), '') + self.assertEqual(floatformat('¿Cómo esta usted?'), '') + self.assertEqual(floatformat(None), '') # Check that we're not converting to scientific notation. - self.assertEqual(floatformat(0, 6), u'0.000000') - self.assertEqual(floatformat(0, 7), u'0.0000000') - self.assertEqual(floatformat(0, 10), u'0.0000000000') + self.assertEqual(floatformat(0, 6), '0.000000') + self.assertEqual(floatformat(0, 7), '0.0000000') + self.assertEqual(floatformat(0, 10), '0.0000000000') self.assertEqual(floatformat(0.000000000000000000015, 20), - u'0.00000000000000000002') + '0.00000000000000000002') pos_inf = float(1e30000) self.assertEqual(floatformat(pos_inf), unicode(pos_inf)) @@ -61,18 +62,18 @@ class DefaultFiltersTests(TestCase): def __float__(self): return self.value - self.assertEqual(floatformat(FloatWrapper(11.000001), -2), u'11.00') + self.assertEqual(floatformat(FloatWrapper(11.000001), -2), '11.00') # Regression for #15789 decimal_ctx = decimal.getcontext() old_prec, decimal_ctx.prec = decimal_ctx.prec, 2 try: - self.assertEqual(floatformat(1.2345, 2), u'1.23') - self.assertEqual(floatformat(15.2042, -3), u'15.204') - self.assertEqual(floatformat(1.2345, '2'), u'1.23') - self.assertEqual(floatformat(15.2042, '-3'), u'15.204') - self.assertEqual(floatformat(decimal.Decimal('1.2345'), 2), u'1.23') - self.assertEqual(floatformat(decimal.Decimal('15.2042'), -3), u'15.204') + self.assertEqual(floatformat(1.2345, 2), '1.23') + self.assertEqual(floatformat(15.2042, -3), '15.204') + self.assertEqual(floatformat(1.2345, '2'), '1.23') + self.assertEqual(floatformat(15.2042, '-3'), '15.204') + self.assertEqual(floatformat(decimal.Decimal('1.2345'), 2), '1.23') + self.assertEqual(floatformat(decimal.Decimal('15.2042'), -3), '15.204') finally: decimal_ctx.prec = old_prec @@ -82,290 +83,290 @@ class DefaultFiltersTests(TestCase): # unicode or Decimal. @unittest.expectedFailure def test_floatformat_fail(self): - self.assertEqual(floatformat(1.00000000000000015, 16), u'1.0000000000000002') + self.assertEqual(floatformat(1.00000000000000015, 16), '1.0000000000000002') def test_addslashes(self): - self.assertEqual(addslashes(u'"double quotes" and \'single quotes\''), - u'\\"double quotes\\" and \\\'single quotes\\\'') + self.assertEqual(addslashes('"double quotes" and \'single quotes\''), + '\\"double quotes\\" and \\\'single quotes\\\'') - self.assertEqual(addslashes(ur'\ : backslashes, too'), - u'\\\\ : backslashes, too') + self.assertEqual(addslashes(r'\ : backslashes, too'), + '\\\\ : backslashes, too') def test_capfirst(self): - self.assertEqual(capfirst(u'hello world'), u'Hello world') + self.assertEqual(capfirst('hello world'), 'Hello world') def test_escapejs(self): - self.assertEqual(escapejs_filter(u'"double quotes" and \'single quotes\''), - u'\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027') - self.assertEqual(escapejs_filter(ur'\ : backslashes, too'), - u'\\u005C : backslashes, too') - self.assertEqual(escapejs_filter(u'and lots of whitespace: \r\n\t\v\f\b'), - u'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008') - self.assertEqual(escapejs_filter(ur''), - u'\\u003Cscript\\u003Eand this\\u003C/script\\u003E') + self.assertEqual(escapejs_filter('"double quotes" and \'single quotes\''), + '\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027') + self.assertEqual(escapejs_filter(r'\ : backslashes, too'), + '\\u005C : backslashes, too') + self.assertEqual(escapejs_filter('and lots of whitespace: \r\n\t\v\f\b'), + 'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008') + self.assertEqual(escapejs_filter(r''), + '\\u003Cscript\\u003Eand this\\u003C/script\\u003E') self.assertEqual( - escapejs_filter(u'paragraph separator:\u2029and line separator:\u2028'), - u'paragraph separator:\\u2029and line separator:\\u2028') + escapejs_filter('paragraph separator:\u2029and line separator:\u2028'), + 'paragraph separator:\\u2029and line separator:\\u2028') def test_fix_ampersands(self): - self.assertEqual(fix_ampersands_filter(u'Jack & Jill & Jeroboam'), - u'Jack & Jill & Jeroboam') + self.assertEqual(fix_ampersands_filter('Jack & Jill & Jeroboam'), + 'Jack & Jill & Jeroboam') def test_linenumbers(self): - self.assertEqual(linenumbers(u'line 1\nline 2'), - u'1. line 1\n2. line 2') - self.assertEqual(linenumbers(u'\n'.join([u'x'] * 10)), - u'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. '\ - u'x\n08. x\n09. x\n10. x') + self.assertEqual(linenumbers('line 1\nline 2'), + '1. line 1\n2. line 2') + self.assertEqual(linenumbers('\n'.join(['x'] * 10)), + '01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. '\ + 'x\n08. x\n09. x\n10. x') def test_lower(self): - self.assertEqual(lower('TEST'), u'test') + self.assertEqual(lower('TEST'), 'test') # uppercase E umlaut - self.assertEqual(lower(u'\xcb'), u'\xeb') + self.assertEqual(lower('\xcb'), '\xeb') def test_make_list(self): - self.assertEqual(make_list('abc'), [u'a', u'b', u'c']) - self.assertEqual(make_list(1234), [u'1', u'2', u'3', u'4']) + self.assertEqual(make_list('abc'), ['a', 'b', 'c']) + self.assertEqual(make_list(1234), ['1', '2', '3', '4']) def test_slugify(self): self.assertEqual(slugify(' Jack & Jill like numbers 1,2,3 and 4 and'\ ' silly characters ?%.$!/'), - u'jack-jill-like-numbers-123-and-4-and-silly-characters') + 'jack-jill-like-numbers-123-and-4-and-silly-characters') - self.assertEqual(slugify(u"Un \xe9l\xe9phant \xe0 l'or\xe9e du bois"), - u'un-elephant-a-loree-du-bois') + self.assertEqual(slugify("Un \xe9l\xe9phant \xe0 l'or\xe9e du bois"), + 'un-elephant-a-loree-du-bois') def test_stringformat(self): - self.assertEqual(stringformat(1, u'03d'), u'001') - self.assertEqual(stringformat(1, u'z'), u'') + self.assertEqual(stringformat(1, '03d'), '001') + self.assertEqual(stringformat(1, 'z'), '') def test_title(self): self.assertEqual(title('a nice title, isn\'t it?'), - u"A Nice Title, Isn't It?") - self.assertEqual(title(u'discoth\xe8que'), u'Discoth\xe8que') + "A Nice Title, Isn't It?") + self.assertEqual(title('discoth\xe8que'), 'Discoth\xe8que') def test_truncatewords(self): self.assertEqual( - truncatewords(u'A sentence with a few words in it', 1), u'A ...') + truncatewords('A sentence with a few words in it', 1), 'A ...') self.assertEqual( - truncatewords(u'A sentence with a few words in it', 5), - u'A sentence with a few ...') + truncatewords('A sentence with a few words in it', 5), + 'A sentence with a few ...') self.assertEqual( - truncatewords(u'A sentence with a few words in it', 100), - u'A sentence with a few words in it') + truncatewords('A sentence with a few words in it', 100), + 'A sentence with a few words in it') self.assertEqual( - truncatewords(u'A sentence with a few words in it', - 'not a number'), u'A sentence with a few words in it') + truncatewords('A sentence with a few words in it', + 'not a number'), 'A sentence with a few words in it') def test_truncatewords_html(self): self.assertEqual(truncatewords_html( - u'

        one two - three
        four
        five

        ', 0), u'') - self.assertEqual(truncatewords_html(u'

        one two - '\ - u'three
        four
        five

        ', 2), - u'

        one two ...

        ') + '

        one two - three
        four
        five

        ', 0), '') + self.assertEqual(truncatewords_html('

        one two - '\ + 'three
        four
        five

        ', 2), + '

        one two ...

        ') self.assertEqual(truncatewords_html( - u'

        one two - three
        four
        five

        ', 4), - u'

        one two - three
        four ...

        ') + '

        one two - three
        four
        five

        ', 4), + '

        one two - three
        four ...

        ') self.assertEqual(truncatewords_html( - u'

        one two - three
        four
        five

        ', 5), - u'

        one two - three
        four
        five

        ') + '

        one two - three
        four
        five

        ', 5), + '

        one two - three
        four
        five

        ') self.assertEqual(truncatewords_html( - u'

        one two - three
        four
        five

        ', 100), - u'

        one two - three
        four
        five

        ') + '

        one two - three
        four
        five

        ', 100), + '

        one two - three
        four
        five

        ') self.assertEqual(truncatewords_html( - u'\xc5ngstr\xf6m was here', 1), u'\xc5ngstr\xf6m ...') + '\xc5ngstr\xf6m was here', 1), '\xc5ngstr\xf6m ...') def test_upper(self): - self.assertEqual(upper(u'Mixed case input'), u'MIXED CASE INPUT') + self.assertEqual(upper('Mixed case input'), 'MIXED CASE INPUT') # lowercase e umlaut - self.assertEqual(upper(u'\xeb'), u'\xcb') + self.assertEqual(upper('\xeb'), '\xcb') def test_urlencode(self): - self.assertEqual(urlencode(u'fran\xe7ois & jill'), - u'fran%C3%A7ois%20%26%20jill') - self.assertEqual(urlencode(1), u'1') + self.assertEqual(urlencode('fran\xe7ois & jill'), + 'fran%C3%A7ois%20%26%20jill') + self.assertEqual(urlencode(1), '1') def test_iriencode(self): - self.assertEqual(iriencode(u'S\xf8r-Tr\xf8ndelag'), - u'S%C3%B8r-Tr%C3%B8ndelag') - self.assertEqual(iriencode(urlencode(u'fran\xe7ois & jill')), - u'fran%C3%A7ois%20%26%20jill') + self.assertEqual(iriencode('S\xf8r-Tr\xf8ndelag'), + 'S%C3%B8r-Tr%C3%B8ndelag') + self.assertEqual(iriencode(urlencode('fran\xe7ois & jill')), + 'fran%C3%A7ois%20%26%20jill') def test_urlizetrunc(self): - self.assertEqual(urlizetrunc(u'http://short.com/', 20), u'http://short.com/') - - self.assertEqual(urlizetrunc(u'http://www.google.co.uk/search?hl=en'\ - u'&q=some+long+url&btnG=Search&meta=', 20), u'http://www.google...') + self.assertEqual(urlizetrunc('http://short.com/', 20), 'http://short.com/') self.assertEqual(urlizetrunc('http://www.google.co.uk/search?hl=en'\ - u'&q=some+long+url&btnG=Search&meta=', 20), u'http://www.google...') + '&q=some+long+url&btnG=Search&meta=', 20), 'http://www.google...') + + self.assertEqual(urlizetrunc('http://www.google.co.uk/search?hl=en'\ + '&q=some+long+url&btnG=Search&meta=', 20), 'http://www.google...') # Check truncating of URIs which are the exact length uri = 'http://31characteruri.com/test/' self.assertEqual(len(uri), 31) self.assertEqual(urlizetrunc(uri, 31), - u''\ - u'http://31characteruri.com/test/') + ''\ + 'http://31characteruri.com/test/') self.assertEqual(urlizetrunc(uri, 30), - u''\ - u'http://31characteruri.com/t...') + ''\ + 'http://31characteruri.com/t...') self.assertEqual(urlizetrunc(uri, 2), - u'...') + '...') def test_urlize(self): # Check normal urlize self.assertEqual(urlize('http://google.com'), - u'http://google.com') + 'http://google.com') self.assertEqual(urlize('http://google.com/'), - u'http://google.com/') + 'http://google.com/') self.assertEqual(urlize('www.google.com'), - u'www.google.com') + 'www.google.com') self.assertEqual(urlize('djangoproject.org'), - u'djangoproject.org') + 'djangoproject.org') self.assertEqual(urlize('info@djangoproject.org'), - u'info@djangoproject.org') + 'info@djangoproject.org') # Check urlize with https addresses self.assertEqual(urlize('https://google.com'), - u'https://google.com') + 'https://google.com') # Check urlize doesn't overquote already quoted urls - see #9655 self.assertEqual(urlize('http://hi.baidu.com/%D6%D8%D0%C2%BF'), - u'' - u'http://hi.baidu.com/%D6%D8%D0%C2%BF') + '' + 'http://hi.baidu.com/%D6%D8%D0%C2%BF') self.assertEqual(urlize('www.mystore.com/30%OffCoupons!'), - u'' - u'www.mystore.com/30%OffCoupons!') + '' + 'www.mystore.com/30%OffCoupons!') self.assertEqual(urlize('http://en.wikipedia.org/wiki/Caf%C3%A9'), - u'' - u'http://en.wikipedia.org/wiki/Caf%C3%A9') + '' + 'http://en.wikipedia.org/wiki/Caf%C3%A9') self.assertEqual(urlize('http://en.wikipedia.org/wiki/Café'), - u'' - u'http://en.wikipedia.org/wiki/Café') + '' + 'http://en.wikipedia.org/wiki/Café') # Check urlize keeps balanced parentheses - see #11911 self.assertEqual(urlize('http://en.wikipedia.org/wiki/Django_(web_framework)'), - u'' - u'http://en.wikipedia.org/wiki/Django_(web_framework)') + '' + 'http://en.wikipedia.org/wiki/Django_(web_framework)') self.assertEqual(urlize('(see http://en.wikipedia.org/wiki/Django_(web_framework))'), - u'(see ' - u'http://en.wikipedia.org/wiki/Django_(web_framework))') + '(see ' + 'http://en.wikipedia.org/wiki/Django_(web_framework))') # Check urlize adds nofollow properly - see #12183 self.assertEqual(urlize('foo@bar.com or www.bar.com'), - u'foo@bar.com or ' - u'www.bar.com') + 'foo@bar.com or ' + 'www.bar.com') # Check urlize handles IDN correctly - see #13704 self.assertEqual(urlize('http://c✶.ws'), - u'http://c✶.ws') + 'http://c✶.ws') self.assertEqual(urlize('www.c✶.ws'), - u'www.c✶.ws') + 'www.c✶.ws') self.assertEqual(urlize('c✶.org'), - u'c✶.org') + 'c✶.org') self.assertEqual(urlize('info@c✶.org'), - u'info@c✶.org') + 'info@c✶.org') # Check urlize doesn't highlight malformed URIs - see #16395 self.assertEqual(urlize('http:///www.google.com'), - u'http:///www.google.com') + 'http:///www.google.com') self.assertEqual(urlize('http://.google.com'), - u'http://.google.com') + 'http://.google.com') self.assertEqual(urlize('http://@foo.com'), - u'http://@foo.com') + 'http://@foo.com') # Check urlize accepts more TLDs - see #16656 self.assertEqual(urlize('usa.gov'), - u'usa.gov') + 'usa.gov') # Check urlize don't crash on invalid email with dot-starting domain - see #17592 self.assertEqual(urlize('email@.stream.ru'), - u'email@.stream.ru') + 'email@.stream.ru') # Check urlize accepts uppercased URL schemes - see #18071 self.assertEqual(urlize('HTTPS://github.com/'), - u'HTTPS://github.com/') + 'HTTPS://github.com/') def test_wordcount(self): self.assertEqual(wordcount(''), 0) - self.assertEqual(wordcount(u'oneword'), 1) - self.assertEqual(wordcount(u'lots of words'), 3) + self.assertEqual(wordcount('oneword'), 1) + self.assertEqual(wordcount('lots of words'), 3) - self.assertEqual(wordwrap(u'this is a long paragraph of text that '\ - u'really needs to be wrapped I\'m afraid', 14), - u"this is a long\nparagraph of\ntext that\nreally needs\nto be "\ - u"wrapped\nI'm afraid") + self.assertEqual(wordwrap('this is a long paragraph of text that '\ + 'really needs to be wrapped I\'m afraid', 14), + "this is a long\nparagraph of\ntext that\nreally needs\nto be "\ + "wrapped\nI'm afraid") - self.assertEqual(wordwrap(u'this is a short paragraph of text.\n '\ - u'But this line should be indented', 14), - u'this is a\nshort\nparagraph of\ntext.\n But this\nline '\ - u'should be\nindented') + self.assertEqual(wordwrap('this is a short paragraph of text.\n '\ + 'But this line should be indented', 14), + 'this is a\nshort\nparagraph of\ntext.\n But this\nline '\ + 'should be\nindented') - self.assertEqual(wordwrap(u'this is a short paragraph of text.\n '\ - u'But this line should be indented',15), u'this is a short\n'\ - u'paragraph of\ntext.\n But this line\nshould be\nindented') + self.assertEqual(wordwrap('this is a short paragraph of text.\n '\ + 'But this line should be indented',15), 'this is a short\n'\ + 'paragraph of\ntext.\n But this line\nshould be\nindented') def test_rjust(self): - self.assertEqual(ljust(u'test', 10), u'test ') - self.assertEqual(ljust(u'test', 3), u'test') - self.assertEqual(rjust(u'test', 10), u' test') - self.assertEqual(rjust(u'test', 3), u'test') + self.assertEqual(ljust('test', 10), 'test ') + self.assertEqual(ljust('test', 3), 'test') + self.assertEqual(rjust('test', 10), ' test') + self.assertEqual(rjust('test', 3), 'test') def test_center(self): - self.assertEqual(center(u'test', 6), u' test ') + self.assertEqual(center('test', 6), ' test ') def test_cut(self): - self.assertEqual(cut(u'a string to be mangled', 'a'), - u' string to be mngled') - self.assertEqual(cut(u'a string to be mangled', 'ng'), - u'a stri to be maled') - self.assertEqual(cut(u'a string to be mangled', 'strings'), - u'a string to be mangled') + self.assertEqual(cut('a string to be mangled', 'a'), + ' string to be mngled') + self.assertEqual(cut('a string to be mangled', 'ng'), + 'a stri to be maled') + self.assertEqual(cut('a string to be mangled', 'strings'), + 'a string to be mangled') def test_force_escape(self): - escaped = force_escape(u' here') + escaped = force_escape(' here') self.assertEqual( - escaped, u'<some html & special characters > here') + escaped, '<some html & special characters > here') self.assertTrue(isinstance(escaped, SafeData)) self.assertEqual( - force_escape(u' here ĐÅ€£'), - u'<some html & special characters > here'\ - u' \u0110\xc5\u20ac\xa3') + force_escape(' here ĐÅ€£'), + '<some html & special characters > here'\ + ' \u0110\xc5\u20ac\xa3') def test_linebreaks(self): - self.assertEqual(linebreaks_filter(u'line 1'), u'

        line 1

        ') - self.assertEqual(linebreaks_filter(u'line 1\nline 2'), - u'

        line 1
        line 2

        ') - self.assertEqual(linebreaks_filter(u'line 1\rline 2'), - u'

        line 1
        line 2

        ') - self.assertEqual(linebreaks_filter(u'line 1\r\nline 2'), - u'

        line 1
        line 2

        ') + self.assertEqual(linebreaks_filter('line 1'), '

        line 1

        ') + self.assertEqual(linebreaks_filter('line 1\nline 2'), + '

        line 1
        line 2

        ') + self.assertEqual(linebreaks_filter('line 1\rline 2'), + '

        line 1
        line 2

        ') + self.assertEqual(linebreaks_filter('line 1\r\nline 2'), + '

        line 1
        line 2

        ') def test_linebreaksbr(self): - self.assertEqual(linebreaksbr(u'line 1\nline 2'), - u'line 1
        line 2') - self.assertEqual(linebreaksbr(u'line 1\rline 2'), - u'line 1
        line 2') - self.assertEqual(linebreaksbr(u'line 1\r\nline 2'), - u'line 1
        line 2') + self.assertEqual(linebreaksbr('line 1\nline 2'), + 'line 1
        line 2') + self.assertEqual(linebreaksbr('line 1\rline 2'), + 'line 1
        line 2') + self.assertEqual(linebreaksbr('line 1\r\nline 2'), + 'line 1
        line 2') def test_removetags(self): - self.assertEqual(removetags(u'some html with disallowed tags', 'script img'), - u'some html with alert("You smell") disallowed tags') - self.assertEqual(striptags(u'some html with disallowed tags'), - u'some html with alert("You smell") disallowed tags') + self.assertEqual(removetags('some html with disallowed tags', 'script img'), + 'some html with alert("You smell") disallowed tags') + self.assertEqual(striptags('some html with disallowed tags'), + 'some html with alert("You smell") disallowed tags') def test_dictsort(self): sorted_dicts = dictsort([{'age': 23, 'name': 'Barbara-Ann'}, @@ -404,81 +405,81 @@ class DefaultFiltersTests(TestCase): def test_first(self): self.assertEqual(first([0,1,2]), 0) - self.assertEqual(first(u''), u'') - self.assertEqual(first(u'test'), u't') + self.assertEqual(first(''), '') + self.assertEqual(first('test'), 't') def test_join(self): - self.assertEqual(join([0,1,2], u'glue'), u'0glue1glue2') + self.assertEqual(join([0,1,2], 'glue'), '0glue1glue2') def test_length(self): - self.assertEqual(length(u'1234'), 4) + self.assertEqual(length('1234'), 4) self.assertEqual(length([1,2,3,4]), 4) self.assertEqual(length_is([], 0), True) self.assertEqual(length_is([], 1), False) self.assertEqual(length_is('a', 1), True) - self.assertEqual(length_is(u'a', 10), False) + self.assertEqual(length_is('a', 10), False) def test_slice(self): - self.assertEqual(slice_filter(u'abcdefg', u'0'), u'') - self.assertEqual(slice_filter(u'abcdefg', u'1'), u'a') - self.assertEqual(slice_filter(u'abcdefg', u'-1'), u'abcdef') - self.assertEqual(slice_filter(u'abcdefg', u'1:2'), u'b') - self.assertEqual(slice_filter(u'abcdefg', u'1:3'), u'bc') - self.assertEqual(slice_filter(u'abcdefg', u'0::2'), u'aceg') + self.assertEqual(slice_filter('abcdefg', '0'), '') + self.assertEqual(slice_filter('abcdefg', '1'), 'a') + self.assertEqual(slice_filter('abcdefg', '-1'), 'abcdef') + self.assertEqual(slice_filter('abcdefg', '1:2'), 'b') + self.assertEqual(slice_filter('abcdefg', '1:3'), 'bc') + self.assertEqual(slice_filter('abcdefg', '0::2'), 'aceg') def test_unordered_list(self): - self.assertEqual(unordered_list([u'item 1', u'item 2']), - u'\t
      • item 1
      • \n\t
      • item 2
      • ') - self.assertEqual(unordered_list([u'item 1', [u'item 1.1']]), - u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ') + self.assertEqual(unordered_list(['item 1', 'item 2']), + '\t
      • item 1
      • \n\t
      • item 2
      • ') + self.assertEqual(unordered_list(['item 1', ['item 1.1']]), + '\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ') self.assertEqual( - unordered_list([u'item 1', [u'item 1.1', u'item1.2'], u'item 2']), - u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t\t
        • item1.2'\ - u'
        • \n\t
        \n\t
      • \n\t
      • item 2
      • ') + unordered_list(['item 1', ['item 1.1', 'item1.2'], 'item 2']), + '\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t\t
        • item1.2'\ + '
        • \n\t
        \n\t
      • \n\t
      • item 2
      • ') self.assertEqual( - unordered_list([u'item 1', [u'item 1.1', [u'item 1.1.1', - [u'item 1.1.1.1']]]]), - u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1\n\t\t
            \n\t\t\t
          • '\ - u'item 1.1.1\n\t\t\t
              \n\t\t\t\t
            • item 1.1.1.1
            • \n\t\t\t'\ - u'
            \n\t\t\t
          • \n\t\t
          \n\t\t
        • \n\t
        \n\t
      • ') + unordered_list(['item 1', ['item 1.1', ['item 1.1.1', + ['item 1.1.1.1']]]]), + '\t
      • item 1\n\t
          \n\t\t
        • item 1.1\n\t\t
            \n\t\t\t
          • '\ + 'item 1.1.1\n\t\t\t
              \n\t\t\t\t
            • item 1.1.1.1
            • \n\t\t\t'\ + '
            \n\t\t\t
          • \n\t\t
          \n\t\t
        • \n\t
        \n\t
      • ') self.assertEqual(unordered_list( ['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]), - u'\t
      • States\n\t
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • '\ - u'Lawrence
          • \n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • '\ - u'\n\t\t
        • Illinois
        • \n\t
        \n\t
      • ') + '\t
      • States\n\t
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • '\ + 'Lawrence
          • \n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • '\ + '\n\t\t
        • Illinois
        • \n\t
        \n\t
      • ') class ULItem(object): def __init__(self, title): self.title = title def __unicode__(self): - return u'ulitem-%s' % str(self.title) + return 'ulitem-%s' % str(self.title) a = ULItem('a') b = ULItem('b') self.assertEqual(unordered_list([a,b]), - u'\t
      • ulitem-a
      • \n\t
      • ulitem-b
      • ') + '\t
      • ulitem-a
      • \n\t
      • ulitem-b
      • ') # Old format for unordered lists should still work - self.assertEqual(unordered_list([u'item 1', []]), u'\t
      • item 1
      • ') + self.assertEqual(unordered_list(['item 1', []]), '\t
      • item 1
      • ') - self.assertEqual(unordered_list([u'item 1', [[u'item 1.1', []]]]), - u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ') + self.assertEqual(unordered_list(['item 1', [['item 1.1', []]]]), + '\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ') - self.assertEqual(unordered_list([u'item 1', [[u'item 1.1', []], - [u'item 1.2', []]]]), u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1'\ - u'
        • \n\t\t
        • item 1.2
        • \n\t
        \n\t
      • ') + self.assertEqual(unordered_list(['item 1', [['item 1.1', []], + ['item 1.2', []]]]), '\t
      • item 1\n\t
          \n\t\t
        • item 1.1'\ + '
        • \n\t\t
        • item 1.2
        • \n\t
        \n\t
      • ') self.assertEqual(unordered_list(['States', [['Kansas', [['Lawrence', - []], ['Topeka', []]]], ['Illinois', []]]]), u'\t
      • States\n\t'\ - u'
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • Lawrence
          • '\ - u'\n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • \n\t\t
        • '\ - u'Illinois
        • \n\t
        \n\t
      • ') + []], ['Topeka', []]]], ['Illinois', []]]]), '\t
      • States\n\t'\ + '
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • Lawrence
          • '\ + '\n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • \n\t\t
        • '\ + 'Illinois
        • \n\t
        \n\t
      • ') def test_add(self): - self.assertEqual(add(u'1', u'2'), 3) + self.assertEqual(add('1', '2'), 3) def test_get_digit(self): self.assertEqual(get_digit(123, 1), 3) @@ -486,148 +487,148 @@ class DefaultFiltersTests(TestCase): self.assertEqual(get_digit(123, 3), 1) self.assertEqual(get_digit(123, 4), 0) self.assertEqual(get_digit(123, 0), 123) - self.assertEqual(get_digit(u'xyz', 0), u'xyz') + self.assertEqual(get_digit('xyz', 0), 'xyz') def test_date(self): # real testing of date() is in dateformat.py - self.assertEqual(date(datetime.datetime(2005, 12, 29), u"d F Y"), - u'29 December 2005') - self.assertEqual(date(datetime.datetime(2005, 12, 29), ur'jS \o\f F'), - u'29th of December') + self.assertEqual(date(datetime.datetime(2005, 12, 29), "d F Y"), + '29 December 2005') + self.assertEqual(date(datetime.datetime(2005, 12, 29), r'jS \o\f F'), + '29th of December') def test_time(self): # real testing of time() is done in dateformat.py - self.assertEqual(time(datetime.time(13), u"h"), u'01') - self.assertEqual(time(datetime.time(0), u"h"), u'12') + self.assertEqual(time(datetime.time(13), "h"), '01') + self.assertEqual(time(datetime.time(0), "h"), '12') def test_timesince(self): # real testing is done in timesince.py, where we can provide our own 'now' self.assertEqual( timesince_filter(datetime.datetime.now() - datetime.timedelta(1)), - u'1 day') + '1 day') self.assertEqual( timesince_filter(datetime.datetime(2005, 12, 29), datetime.datetime(2005, 12, 30)), - u'1 day') + '1 day') def test_timeuntil(self): self.assertEqual( timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1, 1)), - u'1 day') + '1 day') self.assertEqual( timeuntil_filter(datetime.datetime(2005, 12, 30), datetime.datetime(2005, 12, 29)), - u'1 day') + '1 day') def test_default(self): - self.assertEqual(default(u"val", u"default"), u'val') - self.assertEqual(default(None, u"default"), u'default') - self.assertEqual(default(u'', u"default"), u'default') + self.assertEqual(default("val", "default"), 'val') + self.assertEqual(default(None, "default"), 'default') + self.assertEqual(default('', "default"), 'default') def test_if_none(self): - self.assertEqual(default_if_none(u"val", u"default"), u'val') - self.assertEqual(default_if_none(None, u"default"), u'default') - self.assertEqual(default_if_none(u'', u"default"), u'') + self.assertEqual(default_if_none("val", "default"), 'val') + self.assertEqual(default_if_none(None, "default"), 'default') + self.assertEqual(default_if_none('', "default"), '') def test_divisibleby(self): self.assertEqual(divisibleby(4, 2), True) self.assertEqual(divisibleby(4, 3), False) def test_yesno(self): - self.assertEqual(yesno(True), u'yes') - self.assertEqual(yesno(False), u'no') - self.assertEqual(yesno(None), u'maybe') - self.assertEqual(yesno(True, u'certainly,get out of town,perhaps'), - u'certainly') - self.assertEqual(yesno(False, u'certainly,get out of town,perhaps'), - u'get out of town') - self.assertEqual(yesno(None, u'certainly,get out of town,perhaps'), - u'perhaps') - self.assertEqual(yesno(None, u'certainly,get out of town'), - u'get out of town') + self.assertEqual(yesno(True), 'yes') + self.assertEqual(yesno(False), 'no') + self.assertEqual(yesno(None), 'maybe') + self.assertEqual(yesno(True, 'certainly,get out of town,perhaps'), + 'certainly') + self.assertEqual(yesno(False, 'certainly,get out of town,perhaps'), + 'get out of town') + self.assertEqual(yesno(None, 'certainly,get out of town,perhaps'), + 'perhaps') + self.assertEqual(yesno(None, 'certainly,get out of town'), + 'get out of town') def test_filesizeformat(self): - self.assertEqual(filesizeformat(1023), u'1023 bytes') - self.assertEqual(filesizeformat(1024), u'1.0 KB') - self.assertEqual(filesizeformat(10*1024), u'10.0 KB') - self.assertEqual(filesizeformat(1024*1024-1), u'1024.0 KB') - self.assertEqual(filesizeformat(1024*1024), u'1.0 MB') - self.assertEqual(filesizeformat(1024*1024*50), u'50.0 MB') - self.assertEqual(filesizeformat(1024*1024*1024-1), u'1024.0 MB') - self.assertEqual(filesizeformat(1024*1024*1024), u'1.0 GB') - self.assertEqual(filesizeformat(1024*1024*1024*1024), u'1.0 TB') - self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), u'1.0 PB') + self.assertEqual(filesizeformat(1023), '1023 bytes') + self.assertEqual(filesizeformat(1024), '1.0 KB') + self.assertEqual(filesizeformat(10*1024), '10.0 KB') + self.assertEqual(filesizeformat(1024*1024-1), '1024.0 KB') + self.assertEqual(filesizeformat(1024*1024), '1.0 MB') + self.assertEqual(filesizeformat(1024*1024*50), '50.0 MB') + self.assertEqual(filesizeformat(1024*1024*1024-1), '1024.0 MB') + self.assertEqual(filesizeformat(1024*1024*1024), '1.0 GB') + self.assertEqual(filesizeformat(1024*1024*1024*1024), '1.0 TB') + self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), '1.0 PB') self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000), - u'2000.0 PB') - self.assertEqual(filesizeformat(complex(1,-1)), u'0 bytes') - self.assertEqual(filesizeformat(""), u'0 bytes') - self.assertEqual(filesizeformat(u"\N{GREEK SMALL LETTER ALPHA}"), - u'0 bytes') + '2000.0 PB') + self.assertEqual(filesizeformat(complex(1,-1)), '0 bytes') + self.assertEqual(filesizeformat(""), '0 bytes') + self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"), + '0 bytes') def test_localized_filesizeformat(self): with self.settings(USE_L10N=True): with translation.override('de', deactivate=True): - self.assertEqual(filesizeformat(1023), u'1023 Bytes') - self.assertEqual(filesizeformat(1024), u'1,0 KB') - self.assertEqual(filesizeformat(10*1024), u'10,0 KB') - self.assertEqual(filesizeformat(1024*1024-1), u'1024,0 KB') - self.assertEqual(filesizeformat(1024*1024), u'1,0 MB') - self.assertEqual(filesizeformat(1024*1024*50), u'50,0 MB') - self.assertEqual(filesizeformat(1024*1024*1024-1), u'1024,0 MB') - self.assertEqual(filesizeformat(1024*1024*1024), u'1,0 GB') - self.assertEqual(filesizeformat(1024*1024*1024*1024), u'1,0 TB') + self.assertEqual(filesizeformat(1023), '1023 Bytes') + self.assertEqual(filesizeformat(1024), '1,0 KB') + self.assertEqual(filesizeformat(10*1024), '10,0 KB') + self.assertEqual(filesizeformat(1024*1024-1), '1024,0 KB') + self.assertEqual(filesizeformat(1024*1024), '1,0 MB') + self.assertEqual(filesizeformat(1024*1024*50), '50,0 MB') + self.assertEqual(filesizeformat(1024*1024*1024-1), '1024,0 MB') + self.assertEqual(filesizeformat(1024*1024*1024), '1,0 GB') + self.assertEqual(filesizeformat(1024*1024*1024*1024), '1,0 TB') self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), - u'1,0 PB') + '1,0 PB') self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000), - u'2000,0 PB') - self.assertEqual(filesizeformat(complex(1,-1)), u'0 Bytes') - self.assertEqual(filesizeformat(""), u'0 Bytes') - self.assertEqual(filesizeformat(u"\N{GREEK SMALL LETTER ALPHA}"), - u'0 Bytes') + '2000,0 PB') + self.assertEqual(filesizeformat(complex(1,-1)), '0 Bytes') + self.assertEqual(filesizeformat(""), '0 Bytes') + self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"), + '0 Bytes') def test_pluralize(self): - self.assertEqual(pluralize(1), u'') - self.assertEqual(pluralize(0), u's') - self.assertEqual(pluralize(2), u's') - self.assertEqual(pluralize([1]), u'') - self.assertEqual(pluralize([]), u's') - self.assertEqual(pluralize([1,2,3]), u's') - self.assertEqual(pluralize(1,u'es'), u'') - self.assertEqual(pluralize(0,u'es'), u'es') - self.assertEqual(pluralize(2,u'es'), u'es') - self.assertEqual(pluralize(1,u'y,ies'), u'y') - self.assertEqual(pluralize(0,u'y,ies'), u'ies') - self.assertEqual(pluralize(2,u'y,ies'), u'ies') - self.assertEqual(pluralize(0,u'y,ies,error'), u'') + self.assertEqual(pluralize(1), '') + self.assertEqual(pluralize(0), 's') + self.assertEqual(pluralize(2), 's') + self.assertEqual(pluralize([1]), '') + self.assertEqual(pluralize([]), 's') + self.assertEqual(pluralize([1,2,3]), 's') + self.assertEqual(pluralize(1,'es'), '') + self.assertEqual(pluralize(0,'es'), 'es') + self.assertEqual(pluralize(2,'es'), 'es') + self.assertEqual(pluralize(1,'y,ies'), 'y') + self.assertEqual(pluralize(0,'y,ies'), 'ies') + self.assertEqual(pluralize(2,'y,ies'), 'ies') + self.assertEqual(pluralize(0,'y,ies,error'), '') def test_phone2numeric(self): - self.assertEqual(phone2numeric_filter(u'0800 flowers'), u'0800 3569377') + self.assertEqual(phone2numeric_filter('0800 flowers'), '0800 3569377') def test_non_string_input(self): # Filters shouldn't break if passed non-strings - self.assertEqual(addslashes(123), u'123') - self.assertEqual(linenumbers(123), u'1. 123') - self.assertEqual(lower(123), u'123') - self.assertEqual(make_list(123), [u'1', u'2', u'3']) - self.assertEqual(slugify(123), u'123') - self.assertEqual(title(123), u'123') - self.assertEqual(truncatewords(123, 2), u'123') - self.assertEqual(upper(123), u'123') - self.assertEqual(urlencode(123), u'123') - self.assertEqual(urlize(123), u'123') - self.assertEqual(urlizetrunc(123, 1), u'123') + self.assertEqual(addslashes(123), '123') + self.assertEqual(linenumbers(123), '1. 123') + self.assertEqual(lower(123), '123') + self.assertEqual(make_list(123), ['1', '2', '3']) + self.assertEqual(slugify(123), '123') + self.assertEqual(title(123), '123') + self.assertEqual(truncatewords(123, 2), '123') + self.assertEqual(upper(123), '123') + self.assertEqual(urlencode(123), '123') + self.assertEqual(urlize(123), '123') + self.assertEqual(urlizetrunc(123, 1), '123') self.assertEqual(wordcount(123), 1) - self.assertEqual(wordwrap(123, 2), u'123') - self.assertEqual(ljust('123', 4), u'123 ') - self.assertEqual(rjust('123', 4), u' 123') - self.assertEqual(center('123', 5), u' 123 ') - self.assertEqual(center('123', 6), u' 123 ') - self.assertEqual(cut(123, '2'), u'13') - self.assertEqual(escape(123), u'123') - self.assertEqual(linebreaks_filter(123), u'

        123

        ') - self.assertEqual(linebreaksbr(123), u'123') - self.assertEqual(removetags(123, 'a'), u'123') - self.assertEqual(striptags(123), u'123') + self.assertEqual(wordwrap(123, 2), '123') + self.assertEqual(ljust('123', 4), '123 ') + self.assertEqual(rjust('123', 4), ' 123') + self.assertEqual(center('123', 5), ' 123 ') + self.assertEqual(center('123', 6), ' 123 ') + self.assertEqual(cut(123, '2'), '13') + self.assertEqual(escape(123), '123') + self.assertEqual(linebreaks_filter(123), '

        123

        ') + self.assertEqual(linebreaksbr(123), '123') + self.assertEqual(removetags(123, 'a'), '123') + self.assertEqual(striptags(123), '123') diff --git a/tests/regressiontests/expressions_regress/models.py b/tests/regressiontests/expressions_regress/models.py index 0ebccb5084..f3b6999377 100644 --- a/tests/regressiontests/expressions_regress/models.py +++ b/tests/regressiontests/expressions_regress/models.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ Model for testing arithmetic expressions. """ @@ -9,7 +10,7 @@ class Number(models.Model): float = models.FloatField(null=True, db_column='the_float') def __unicode__(self): - return u'%i, %.3f' % (self.integer, self.float) + return '%i, %.3f' % (self.integer, self.float) class Experiment(models.Model): name = models.CharField(max_length=24) diff --git a/tests/regressiontests/extra_regress/models.py b/tests/regressiontests/extra_regress/models.py index 868331e508..7d2a6fab34 100644 --- a/tests/regressiontests/extra_regress/models.py +++ b/tests/regressiontests/extra_regress/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import copy import datetime @@ -11,7 +13,7 @@ class RevisionableModel(models.Model): when = models.DateTimeField(default=datetime.datetime.now) def __unicode__(self): - return u"%s (%s, %s)" % (self.title, self.id, self.base.id) + return "%s (%s, %s)" % (self.title, self.id, self.base.id) def save(self, *args, **kwargs): super(RevisionableModel, self).save(*args, **kwargs) @@ -36,5 +38,5 @@ class TestObject(models.Model): third = models.CharField(max_length=20) def __unicode__(self): - return u'TestObject: %s,%s,%s' % (self.first,self.second,self.third) + return 'TestObject: %s,%s,%s' % (self.first,self.second,self.third) diff --git a/tests/regressiontests/extra_regress/tests.py b/tests/regressiontests/extra_regress/tests.py index 3fcafef5de..f591900afe 100644 --- a/tests/regressiontests/extra_regress/tests.py +++ b/tests/regressiontests/extra_regress/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -178,100 +178,100 @@ class ExtraRegressTests(TestCase): self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values()), - [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}] + [{'bar': 'second', 'third': 'third', 'second': 'second', 'whiz': 'third', 'foo': 'first', 'id': obj.pk, 'first': 'first'}] ) # Extra clauses after an empty values clause are still included self.assertEqual( list(TestObject.objects.values().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), - [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}] + [{'bar': 'second', 'third': 'third', 'second': 'second', 'whiz': 'third', 'foo': 'first', 'id': obj.pk, 'first': 'first'}] ) # Extra columns are ignored if not mentioned in the values() clause self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second')), - [{'second': u'second', 'first': u'first'}] + [{'second': 'second', 'first': 'first'}] ) # Extra columns after a non-empty values() clause are ignored self.assertEqual( list(TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), - [{'second': u'second', 'first': u'first'}] + [{'second': 'second', 'first': 'first'}] ) # Extra columns can be partially returned self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second', 'foo')), - [{'second': u'second', 'foo': u'first', 'first': u'first'}] + [{'second': 'second', 'foo': 'first', 'first': 'first'}] ) # Also works if only extra columns are included self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('foo', 'whiz')), - [{'foo': u'first', 'whiz': u'third'}] + [{'foo': 'first', 'whiz': 'third'}] ) # Values list works the same way # All columns are returned for an empty values_list() self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()), - [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] + [('first', 'second', 'third', obj.pk, 'first', 'second', 'third')] ) # Extra columns after an empty values_list() are still included self.assertEqual( list(TestObject.objects.values_list().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), - [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] + [('first', 'second', 'third', obj.pk, 'first', 'second', 'third')] ) # Extra columns ignored completely if not mentioned in values_list() self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')), - [(u'first', u'second')] + [('first', 'second')] ) # Extra columns after a non-empty values_list() clause are ignored completely self.assertEqual( list(TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), - [(u'first', u'second')] + [('first', 'second')] ) self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('second', flat=True)), - [u'second'] + ['second'] ) # Only the extra columns specified in the values_list() are returned self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second', 'whiz')), - [(u'first', u'second', u'third')] + [('first', 'second', 'third')] ) # ...also works if only extra columns are included self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('foo','whiz')), - [(u'first', u'third')] + [('first', 'third')] ) self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', flat=True)), - [u'third'] + ['third'] ) # ... and values are returned in the order they are specified self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz','foo')), - [(u'third', u'first')] + [('third', 'first')] ) self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first','id')), - [(u'first', obj.pk)] + [('first', obj.pk)] ) self.assertEqual( list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', 'first', 'bar', 'id')), - [(u'third', u'first', u'second', obj.pk)] + [('third', 'first', 'second', obj.pk)] ) def test_regression_10847(self): diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index 87035d96d7..51b2207867 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import errno import os @@ -251,9 +251,9 @@ class FileStorageTests(unittest.TestCase): os.mkdir(os.path.join(self.temp_dir, 'storage_dir_1')) dirs, files = self.storage.listdir('') - self.assertEqual(set(dirs), set([u'storage_dir_1'])) + self.assertEqual(set(dirs), set(['storage_dir_1'])) self.assertEqual(set(files), - set([u'storage_test_1', u'storage_test_2'])) + set(['storage_test_1', 'storage_test_2'])) self.storage.delete('storage_test_1') self.storage.delete('storage_test_2') @@ -388,7 +388,7 @@ class UnicodeFileNameTests(unittest.TestCase): out the encoding situation between doctest and this file, but the actual repr doesn't matter; it just shouldn't return a unicode object. """ - uf = UploadedFile(name=u'¿Cómo?',content_type='text') + uf = UploadedFile(name='¿Cómo?',content_type='text') self.assertEqual(type(uf.__repr__()), str) # Tests for a race condition on file saving (#4948). diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py index c8de8035f9..a7424639b4 100644 --- a/tests/regressiontests/file_uploads/tests.py +++ b/tests/regressiontests/file_uploads/tests.py @@ -1,6 +1,5 @@ #! -*- coding: utf-8 -*- - -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import base64 import errno @@ -20,7 +19,7 @@ from . import uploadhandler from .models import FileModel, temp_storage, UPLOAD_TO -UNICODE_FILENAME = u'test-0123456789_中文_Orléans.jpg' +UNICODE_FILENAME = 'test-0123456789_中文_Orléans.jpg' class FileUploadTests(TestCase): def test_simple_upload(self): @@ -71,7 +70,7 @@ class FileUploadTests(TestCase): base64.b64encode(test_string), '--' + client.BOUNDARY + '--', '', - ]) + ]).encode('utf-8') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, @@ -140,7 +139,7 @@ class FileUploadTests(TestCase): '', ]) - payload = "\r\n".join(payload) + payload = "\r\n".join(payload).encode('utf-8') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, @@ -167,7 +166,7 @@ class FileUploadTests(TestCase): 'Oops.' '--' + client.BOUNDARY + '--', '', - ]) + ]).encode('utf-8') r = { 'CONTENT_LENGTH': len(payload), 'CONTENT_TYPE': client.MULTIPART_CONTENT, @@ -192,7 +191,7 @@ class FileUploadTests(TestCase): 'file contents' '--' + client.BOUNDARY + '--', '', - ]) + ]).encode('utf-8') payload = payload[:-10] r = { 'CONTENT_LENGTH': len(payload), @@ -268,11 +267,11 @@ class FileUploadTests(TestCase): response = self.client.post('/file_uploads/getlist_count/', { 'file1': file1, - 'field1': u'test', - 'field2': u'test3', - 'field3': u'test5', - 'field4': u'test6', - 'field5': u'test7', + 'field1': 'test', + 'field2': 'test3', + 'field3': 'test5', + 'field4': 'test6', + 'field5': 'test7', 'file2': (file2, file2a) }) got = json.loads(response.content) diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py index ae6842d0a7..73b09cbcff 100644 --- a/tests/regressiontests/file_uploads/views.py +++ b/tests/regressiontests/file_uploads/views.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import hashlib import json @@ -67,7 +67,7 @@ def file_upload_unicode_name(request): # through file save. uni_named_file = request.FILES['file_unicode'] obj = FileModel.objects.create(testfile=uni_named_file) - full_name = u'%s/%s' % (UPLOAD_TO, uni_named_file.name) + full_name = '%s/%s' % (UPLOAD_TO, uni_named_file.name) if not os.path.exists(full_name): response = HttpResponseServerError() diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index 5a16f175a1..5d23a21dcd 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.auth.models import User from django.db import models @@ -29,7 +29,7 @@ class Stuff(models.Model): owner = models.ForeignKey(User, null=True) def __unicode__(self): - return unicode(self.name) + u' is owned by ' + unicode(self.owner) + return unicode(self.name) + ' is owned by ' + unicode(self.owner) class Absolute(models.Model): @@ -128,7 +128,7 @@ class Book(models.Model): ordering = ('name',) def __unicode__(self): - return u'%s by %s (available at %s)' % ( + return '%s by %s (available at %s)' % ( self.name, self.author.name, ', '.join(s.name for s in self.stores.all()) @@ -148,7 +148,7 @@ class NKChild(Parent): return self.data def __unicode__(self): - return u'NKChild %s:%s' % (self.name, self.data) + return 'NKChild %s:%s' % (self.name, self.data) class RefToNKChild(models.Model): @@ -157,7 +157,7 @@ class RefToNKChild(models.Model): nk_m2m = models.ManyToManyField(NKChild, related_name='ref_m2ms') def __unicode__(self): - return u'%s: Reference to %s [%s]' % ( + return '%s: Reference to %s [%s]' % ( self.text, self.nk_fk, ', '.join(str(o) for o in self.nk_m2m.all()) diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index c0b811bea2..405c566826 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Unittests for fixtures. -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import os import re @@ -85,7 +85,7 @@ class TestFixtures(TestCase): verbosity=0, commit=False ) - self.assertEqual(Stuff.objects.all()[0].name, u'') + self.assertEqual(Stuff.objects.all()[0].name, '') self.assertEqual(Stuff.objects.all()[0].owner, None) def test_absolute_path(self): diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py index 939055785a..18e6ddce6d 100644 --- a/tests/regressiontests/forms/models.py +++ b/tests/regressiontests/forms/models.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + import os import datetime import tempfile @@ -43,7 +45,7 @@ class ChoiceOptionModel(models.Model): ordering = ('name',) def __unicode__(self): - return u'ChoiceOption %d' % self.pk + return 'ChoiceOption %d' % self.pk class ChoiceFieldModel(models.Model): @@ -68,7 +70,7 @@ class Group(models.Model): name = models.CharField(max_length=10) def __unicode__(self): - return u'%s' % self.name + return '%s' % self.name class Cheese(models.Model): diff --git a/tests/regressiontests/forms/tests/error_messages.py b/tests/regressiontests/forms/tests/error_messages.py index 7153a3b0a6..f69b419483 100644 --- a/tests/regressiontests/forms/tests/error_messages.py +++ b/tests/regressiontests/forms/tests/error_messages.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- - -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import * @@ -24,9 +23,9 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s', } f = CharField(min_length=5, max_length=10, error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'LENGTH 4, MIN LENGTH 5'], f.clean, '1234') - self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['LENGTH 4, MIN LENGTH 5'], f.clean, '1234') + self.assertFormErrors(['LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901') def test_integerfield(self): e = { @@ -36,10 +35,10 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'max_value': 'MAX VALUE IS %(limit_value)s', } f = IntegerField(min_value=5, max_value=10, error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc') - self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4') - self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc') + self.assertFormErrors(['MIN VALUE IS 5'], f.clean, '4') + self.assertFormErrors(['MAX VALUE IS 10'], f.clean, '11') def test_floatfield(self): e = { @@ -49,10 +48,10 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'max_value': 'MAX VALUE IS %(limit_value)s', } f = FloatField(min_value=5, max_value=10, error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc') - self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4') - self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc') + self.assertFormErrors(['MIN VALUE IS 5'], f.clean, '4') + self.assertFormErrors(['MAX VALUE IS 10'], f.clean, '11') def test_decimalfield(self): e = { @@ -65,15 +64,15 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'max_whole_digits': 'MAX DIGITS BEFORE DP IS %s', } f = DecimalField(min_value=5, max_value=10, error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc') - self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4') - self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc') + self.assertFormErrors(['MIN VALUE IS 5'], f.clean, '4') + self.assertFormErrors(['MAX VALUE IS 10'], f.clean, '11') f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e) - self.assertFormErrors([u'MAX DIGITS IS 4'], f2.clean, '123.45') - self.assertFormErrors([u'MAX DP IS 2'], f2.clean, '1.234') - self.assertFormErrors([u'MAX DIGITS BEFORE DP IS 2'], f2.clean, '123.4') + self.assertFormErrors(['MAX DIGITS IS 4'], f2.clean, '123.45') + self.assertFormErrors(['MAX DP IS 2'], f2.clean, '1.234') + self.assertFormErrors(['MAX DIGITS BEFORE DP IS 2'], f2.clean, '123.4') def test_datefield(self): e = { @@ -81,8 +80,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid': 'INVALID', } f = DateField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc') def test_timefield(self): e = { @@ -90,8 +89,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid': 'INVALID', } f = TimeField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc') def test_datetimefield(self): e = { @@ -99,8 +98,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid': 'INVALID', } f = DateTimeField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc') def test_regexfield(self): e = { @@ -110,10 +109,10 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s', } f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abcde') - self.assertFormErrors([u'LENGTH 4, MIN LENGTH 5'], f.clean, '1234') - self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abcde') + self.assertFormErrors(['LENGTH 4, MIN LENGTH 5'], f.clean, '1234') + self.assertFormErrors(['LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901') def test_emailfield(self): e = { @@ -123,10 +122,10 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s', } f = EmailField(min_length=8, max_length=10, error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abcdefgh') - self.assertFormErrors([u'LENGTH 7, MIN LENGTH 8'], f.clean, 'a@b.com') - self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, 'aye@bee.com') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abcdefgh') + self.assertFormErrors(['LENGTH 7, MIN LENGTH 8'], f.clean, 'a@b.com') + self.assertFormErrors(['LENGTH 11, MAX LENGTH 10'], f.clean, 'aye@bee.com') def test_filefield(self): e = { @@ -136,10 +135,10 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'empty': 'EMPTY FILE', } f = FileField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc') - self.assertFormErrors([u'EMPTY FILE'], f.clean, SimpleUploadedFile('name', None)) - self.assertFormErrors([u'EMPTY FILE'], f.clean, SimpleUploadedFile('name', '')) + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc') + self.assertFormErrors(['EMPTY FILE'], f.clean, SimpleUploadedFile('name', None)) + self.assertFormErrors(['EMPTY FILE'], f.clean, SimpleUploadedFile('name', '')) def test_urlfield(self): e = { @@ -147,15 +146,15 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid': 'INVALID', } f = URLField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID'], f.clean, 'abc.c') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID'], f.clean, 'abc.c') def test_booleanfield(self): e = { 'required': 'REQUIRED', } f = BooleanField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') + self.assertFormErrors(['REQUIRED'], f.clean, '') def test_choicefield(self): e = { @@ -163,8 +162,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid_choice': '%(value)s IS INVALID CHOICE', } f = ChoiceField(choices=[('a', 'aye')], error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'b IS INVALID CHOICE'], f.clean, 'b') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['b IS INVALID CHOICE'], f.clean, 'b') def test_multiplechoicefield(self): e = { @@ -173,9 +172,9 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid_list': 'NOT A LIST', } f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'NOT A LIST'], f.clean, 'b') - self.assertFormErrors([u'b IS INVALID CHOICE'], f.clean, ['b']) + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['NOT A LIST'], f.clean, 'b') + self.assertFormErrors(['b IS INVALID CHOICE'], f.clean, ['b']) def test_splitdatetimefield(self): e = { @@ -184,8 +183,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid_time': 'INVALID TIME', } f = SplitDateTimeField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID DATE', u'INVALID TIME'], f.clean, ['a', 'b']) + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID DATE', 'INVALID TIME'], f.clean, ['a', 'b']) def test_ipaddressfield(self): e = { @@ -193,8 +192,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid': 'INVALID IP ADDRESS', } f = IPAddressField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID IP ADDRESS'], f.clean, '127.0.0') def test_generic_ipaddressfield(self): e = { @@ -202,8 +201,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid': 'INVALID IP ADDRESS', } f = GenericIPAddressField(error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID IP ADDRESS'], f.clean, '127.0.0') def test_subclassing_errorlist(self): class TestForm(Form): @@ -219,8 +218,8 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): return self.as_divs() def as_divs(self): - if not self: return u'' - return mark_safe(u'
        %s
        ' % ''.join([u'

        %s

        ' % e for e in self])) + if not self: return '' + return mark_safe('
        %s
        ' % ''.join(['

        %s

        ' % e for e in self])) # This form should print errors the default way. form1 = TestForm({'first_name': 'John'}) @@ -247,8 +246,8 @@ class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'invalid_choice': 'INVALID CHOICE', } f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'INVALID CHOICE'], f.clean, '4') + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['INVALID CHOICE'], f.clean, '4') # ModelMultipleChoiceField e = { @@ -257,6 +256,6 @@ class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): 'list': 'NOT A LIST OF VALUES', } f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e) - self.assertFormErrors([u'REQUIRED'], f.clean, '') - self.assertFormErrors([u'NOT A LIST OF VALUES'], f.clean, '3') - self.assertFormErrors([u'4 IS INVALID CHOICE'], f.clean, ['4']) + self.assertFormErrors(['REQUIRED'], f.clean, '') + self.assertFormErrors(['NOT A LIST OF VALUES'], f.clean, '3') + self.assertFormErrors(['4 IS INVALID CHOICE'], f.clean, ['4']) diff --git a/tests/regressiontests/forms/tests/extra.py b/tests/regressiontests/forms/tests/extra.py index c873af7a8c..25b21123c4 100644 --- a/tests/regressiontests/forms/tests/extra.py +++ b/tests/regressiontests/forms/tests/extra.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- - -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -366,7 +365,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): # Invalid dates shouldn't be allowed c = GetDate({'mydate_month':'2', 'mydate_day':'31', 'mydate_year':'2010'}) self.assertFalse(c.is_valid()) - self.assertEqual(c.errors, {'mydate': [u'Enter a valid date.']}) + self.assertEqual(c.errors, {'mydate': ['Enter a valid date.']}) # label tag is correctly associated with month dropdown d = GetDate({'mydate_month':'1', 'mydate_day':'1', 'mydate_year':'2010'}) @@ -395,7 +394,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): return [None, None, None] def format_output(self, rendered_widgets): - return u'\n'.join(rendered_widgets) + return '\n'.join(rendered_widgets) w = ComplexMultiWidget() self.assertHTMLEqual(w.render('name', 'some text,JP,2007-04-25 06:24:00'), """ @@ -422,11 +421,11 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): return None f = ComplexField(widget=w) - self.assertEqual(f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']]), u'some text,JP,2007-04-25 06:24:00') - self.assertFormErrors([u'Select a valid choice. X is not one of the available choices.'], f.clean, ['some text',['X'], ['2007-04-25','6:24:00']]) + self.assertEqual(f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']]), 'some text,JP,2007-04-25 06:24:00') + self.assertFormErrors(['Select a valid choice. X is not one of the available choices.'], f.clean, ['some text',['X'], ['2007-04-25','6:24:00']]) # If insufficient data is provided, None is substituted - self.assertFormErrors([u'This field is required.'], f.clean, ['some text',['JP']]) + self.assertFormErrors(['This field is required.'], f.clean, ['some text',['JP']]) class ComplexFieldForm(Form): field1 = ComplexField(widget=w) @@ -451,26 +450,26 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): """) - self.assertEqual(f.cleaned_data['field1'], u'some text,JP,2007-04-25 06:24:00') + self.assertEqual(f.cleaned_data['field1'], 'some text,JP,2007-04-25 06:24:00') def test_ipaddress(self): f = IPAddressField() - self.assertFormErrors([u'This field is required.'], f.clean, '') - self.assertFormErrors([u'This field is required.'], f.clean, None) - self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5') + self.assertFormErrors(['This field is required.'], f.clean, '') + self.assertFormErrors(['This field is required.'], f.clean, None) + self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, 'foo') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '127.0.0.') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '256.125.1.5') f = IPAddressField(required=False) - self.assertEqual(f.clean(''), u'') - self.assertEqual(f.clean(None), u'') - self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5') + self.assertEqual(f.clean(''), '') + self.assertEqual(f.clean(None), '') + self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, 'foo') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '127.0.0.') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '256.125.1.5') def test_generic_ipaddress_invalid_arguments(self): self.assertRaises(ValueError, GenericIPAddressField, protocol="hamster") @@ -480,93 +479,93 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): # The edge cases of the IPv6 validation code are not deeply tested # here, they are covered in the tests for django.utils.ipv6 f = GenericIPAddressField() - self.assertFormErrors([u'This field is required.'], f.clean, '') - self.assertFormErrors([u'This field is required.'], f.clean, None) - self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5') - self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2') + self.assertFormErrors(['This field is required.'], f.clean, '') + self.assertFormErrors(['This field is required.'], f.clean, None) + self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5') + self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), '2a02::223:6cff:fe8a:2e8a') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2') def test_generic_ipaddress_as_ipv4_only(self): f = GenericIPAddressField(protocol="IPv4") - self.assertFormErrors([u'This field is required.'], f.clean, '') - self.assertFormErrors([u'This field is required.'], f.clean, None) - self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'fe80::223:6cff:fe8a:2e8a') - self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '2a02::223:6cff:fe8a:2e8a') + self.assertFormErrors(['This field is required.'], f.clean, '') + self.assertFormErrors(['This field is required.'], f.clean, None) + self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, 'foo') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '127.0.0.') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '256.125.1.5') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, 'fe80::223:6cff:fe8a:2e8a') + self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '2a02::223:6cff:fe8a:2e8a') def test_generic_ipaddress_as_ipv6_only(self): f = GenericIPAddressField(protocol="IPv6") - self.assertFormErrors([u'This field is required.'], f.clean, '') - self.assertFormErrors([u'This field is required.'], f.clean, None) - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5') - self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '12345:2:3:4') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3::4') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') - self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2') + self.assertFormErrors(['This field is required.'], f.clean, '') + self.assertFormErrors(['This field is required.'], f.clean, None) + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '127.0.0.1') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, 'foo') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '127.0.0.') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '256.125.1.5') + self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), '2a02::223:6cff:fe8a:2e8a') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '12345:2:3:4') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1::2:3::4') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') + self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1:2') def test_generic_ipaddress_as_generic_not_required(self): f = GenericIPAddressField(required=False) - self.assertEqual(f.clean(''), u'') - self.assertEqual(f.clean(None), u'') - self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5') - self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') - self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2') + self.assertEqual(f.clean(''), '') + self.assertEqual(f.clean(None), '') + self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5') + self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), '2a02::223:6cff:fe8a:2e8a') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') + self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2') def test_generic_ipaddress_normalization(self): # Test the normalising code f = GenericIPAddressField() - self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10') - self.assertEqual(f.clean('::ffff:10.10.10.10'), u'::ffff:10.10.10.10') - self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef') - self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef') + self.assertEqual(f.clean('::ffff:0a0a:0a0a'), '::ffff:10.10.10.10') + self.assertEqual(f.clean('::ffff:10.10.10.10'), '::ffff:10.10.10.10') + self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') + self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') f = GenericIPAddressField(unpack_ipv4=True) - self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'10.10.10.10') + self.assertEqual(f.clean('::ffff:0a0a:0a0a'), '10.10.10.10') def test_smart_unicode(self): class Test: def __str__(self): - return 'ŠĐĆŽćžšđ' + return b'ŠĐĆŽćžšđ' class TestU: def __str__(self): return 'Foo' def __unicode__(self): - return u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' + return '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' - self.assertEqual(smart_unicode(Test()), u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') - self.assertEqual(smart_unicode(TestU()), u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') - self.assertEqual(smart_unicode(1), u'1') - self.assertEqual(smart_unicode('foo'), u'foo') + self.assertEqual(smart_unicode(Test()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') + self.assertEqual(smart_unicode(TestU()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') + self.assertEqual(smart_unicode(1), '1') + self.assertEqual(smart_unicode('foo'), 'foo') def test_accessing_clean(self): class UserForm(Form): @@ -583,7 +582,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): f = UserForm({'username': 'SirRobin', 'password': 'blue'}) self.assertTrue(f.is_valid()) - self.assertEqual(f.cleaned_data['username'], u'sirrobin') + self.assertEqual(f.cleaned_data['username'], 'sirrobin') def test_overriding_errorlist(self): class DivErrorList(ErrorList): @@ -591,8 +590,8 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): return self.as_divs() def as_divs(self): - if not self: return u'' - return u'
        %s
        ' % ''.join([u'
        %s
        ' % force_unicode(e) for e in self]) + if not self: return '' + return '
        %s
        ' % ''.join(['
        %s
        ' % force_unicode(e) for e in self]) class CommentForm(Form): name = CharField(max_length=50, required=False) @@ -774,7 +773,7 @@ class FormsExtraL10NTestCase(TestCase): a = GetDate({'mydate_month':'2', 'mydate_day':'31', 'mydate_year':'2010'}) self.assertFalse(a.is_valid()) # 'Geef een geldige datum op.' = 'Enter a valid date.' - self.assertEqual(a.errors, {'mydate': [u'Geef een geldige datum op.']}) + self.assertEqual(a.errors, {'mydate': ['Geef een geldige datum op.']}) def test_form_label_association(self): # label tag is correctly associated with first rendered dropdown diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index 4d442de382..ebeb19c8fc 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -24,6 +24,8 @@ Each Field's __init__() takes at least these parameters: Other than that, the Field subclasses have class-specific options for __init__(). For example, CharField has a max_length option. """ +from __future__ import unicode_literals + import datetime import pickle import re @@ -56,47 +58,47 @@ class FieldsTests(SimpleTestCase): def test_charfield_1(self): f = CharField() - self.assertEqual(u'1', f.clean(1)) - self.assertEqual(u'hello', f.clean('hello')) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3])) + self.assertEqual('1', f.clean(1)) + self.assertEqual('hello', f.clean('hello')) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertEqual('[1, 2, 3]', f.clean([1, 2, 3])) self.assertEqual(f.max_length, None) self.assertEqual(f.min_length, None) def test_charfield_2(self): f = CharField(required=False) - self.assertEqual(u'1', f.clean(1)) - self.assertEqual(u'hello', f.clean('hello')) - self.assertEqual(u'', f.clean(None)) - self.assertEqual(u'', f.clean('')) - self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3])) + self.assertEqual('1', f.clean(1)) + self.assertEqual('hello', f.clean('hello')) + self.assertEqual('', f.clean(None)) + self.assertEqual('', f.clean('')) + self.assertEqual('[1, 2, 3]', f.clean([1, 2, 3])) self.assertEqual(f.max_length, None) self.assertEqual(f.min_length, None) def test_charfield_3(self): f = CharField(max_length=10, required=False) - self.assertEqual(u'12345', f.clean('12345')) - self.assertEqual(u'1234567890', f.clean('1234567890')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '1234567890a') + self.assertEqual('12345', f.clean('12345')) + self.assertEqual('1234567890', f.clean('1234567890')) + self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 10 characters (it has 11).'", f.clean, '1234567890a') self.assertEqual(f.max_length, 10) self.assertEqual(f.min_length, None) def test_charfield_4(self): f = CharField(min_length=10, required=False) - self.assertEqual(u'', f.clean('')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345') - self.assertEqual(u'1234567890', f.clean('1234567890')) - self.assertEqual(u'1234567890a', f.clean('1234567890a')) + self.assertEqual('', f.clean('')) + self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 10 characters (it has 5).'", f.clean, '12345') + self.assertEqual('1234567890', f.clean('1234567890')) + self.assertEqual('1234567890a', f.clean('1234567890a')) self.assertEqual(f.max_length, None) self.assertEqual(f.min_length, 10) def test_charfield_5(self): f = CharField(min_length=10, required=True) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345') - self.assertEqual(u'1234567890', f.clean('1234567890')) - self.assertEqual(u'1234567890a', f.clean('1234567890a')) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 10 characters (it has 5).'", f.clean, '12345') + self.assertEqual('1234567890', f.clean('1234567890')) + self.assertEqual('1234567890a', f.clean('1234567890a')) self.assertEqual(f.max_length, None) self.assertEqual(f.min_length, 10) @@ -121,18 +123,18 @@ class FieldsTests(SimpleTestCase): def test_integerfield_1(self): f = IntegerField() - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(1, f.clean('1')) self.assertEqual(True, isinstance(f.clean('1'), int)) self.assertEqual(23, f.clean('23')) - self.assertRaisesMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 'a') + self.assertRaisesMessage(ValidationError, "'Enter a whole number.'", f.clean, 'a') self.assertEqual(42, f.clean(42)) - self.assertRaisesMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 3.14) + self.assertRaisesMessage(ValidationError, "'Enter a whole number.'", f.clean, 3.14) self.assertEqual(1, f.clean('1 ')) self.assertEqual(1, f.clean(' 1')) self.assertEqual(1, f.clean(' 1 ')) - self.assertRaisesMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a') + self.assertRaisesMessage(ValidationError, "'Enter a whole number.'", f.clean, '1a') self.assertEqual(f.max_value, None) self.assertEqual(f.min_value, None) @@ -145,29 +147,29 @@ class FieldsTests(SimpleTestCase): self.assertEqual(1, f.clean('1')) self.assertEqual(True, isinstance(f.clean('1'), int)) self.assertEqual(23, f.clean('23')) - self.assertRaisesMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 'a') + self.assertRaisesMessage(ValidationError, "'Enter a whole number.'", f.clean, 'a') self.assertEqual(1, f.clean('1 ')) self.assertEqual(1, f.clean(' 1')) self.assertEqual(1, f.clean(' 1 ')) - self.assertRaisesMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a') + self.assertRaisesMessage(ValidationError, "'Enter a whole number.'", f.clean, '1a') self.assertEqual(f.max_value, None) self.assertEqual(f.min_value, None) def test_integerfield_3(self): f = IntegerField(max_value=10) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(1, f.clean(1)) self.assertEqual(10, f.clean(10)) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is less than or equal to 10.']", f.clean, 11) + self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 10.'", f.clean, 11) self.assertEqual(10, f.clean('10')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is less than or equal to 10.']", f.clean, '11') + self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 10.'", f.clean, '11') self.assertEqual(f.max_value, 10) self.assertEqual(f.min_value, None) def test_integerfield_4(self): f = IntegerField(min_value=10) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1) self.assertEqual(10, f.clean(10)) self.assertEqual(11, f.clean(11)) self.assertEqual(10, f.clean('10')) @@ -177,14 +179,14 @@ class FieldsTests(SimpleTestCase): def test_integerfield_5(self): f = IntegerField(min_value=10, max_value=20) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1) self.assertEqual(10, f.clean(10)) self.assertEqual(11, f.clean(11)) self.assertEqual(10, f.clean('10')) self.assertEqual(11, f.clean('11')) self.assertEqual(20, f.clean(20)) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is less than or equal to 20.']", f.clean, 21) + self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 20.'", f.clean, 21) self.assertEqual(f.max_value, 20) self.assertEqual(f.min_value, 10) @@ -192,19 +194,19 @@ class FieldsTests(SimpleTestCase): def test_floatfield_1(self): f = FloatField() - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(1.0, f.clean('1')) self.assertEqual(True, isinstance(f.clean('1'), float)) self.assertEqual(23.0, f.clean('23')) self.assertEqual(3.1400000000000001, f.clean('3.14')) self.assertEqual(3.1400000000000001, f.clean(3.14)) self.assertEqual(42.0, f.clean(42)) - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, 'a') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, 'a') self.assertEqual(1.0, f.clean('1.0 ')) self.assertEqual(1.0, f.clean(' 1.0')) self.assertEqual(1.0, f.clean(' 1.0 ')) - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, '1.0a') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, '1.0a') self.assertEqual(f.max_value, None) self.assertEqual(f.min_value, None) @@ -218,8 +220,8 @@ class FieldsTests(SimpleTestCase): def test_floatfield_3(self): f = FloatField(max_value=1.5, min_value=0.5) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6') - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4') + self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6') + self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4') self.assertEqual(1.5, f.clean('1.5')) self.assertEqual(0.5, f.clean('0.5')) self.assertEqual(f.max_value, 1.5) @@ -229,34 +231,34 @@ class FieldsTests(SimpleTestCase): def test_decimalfield_1(self): f = DecimalField(max_digits=4, decimal_places=2) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(f.clean('1'), Decimal("1")) self.assertEqual(True, isinstance(f.clean('1'), Decimal)) self.assertEqual(f.clean('23'), Decimal("23")) self.assertEqual(f.clean('3.14'), Decimal("3.14")) self.assertEqual(f.clean(3.14), Decimal("3.14")) self.assertEqual(f.clean(Decimal('3.14')), Decimal("3.14")) - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, 'NaN') - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, 'Inf') - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, '-Inf') - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, 'a') - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, u'łąść') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, 'NaN') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, 'Inf') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, '-Inf') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, 'a') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, 'łąść') self.assertEqual(f.clean('1.0 '), Decimal("1.0")) self.assertEqual(f.clean(' 1.0'), Decimal("1.0")) self.assertEqual(f.clean(' 1.0 '), Decimal("1.0")) - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, '1.0a') - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '123.45') - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '1.234') - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 2 digits before the decimal point.']", f.clean, '123.4') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, '1.0a') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'", f.clean, '123.45') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'", f.clean, '1.234') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 digits before the decimal point.'", f.clean, '123.4') self.assertEqual(f.clean('-12.34'), Decimal("-12.34")) - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '-123.45') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'", f.clean, '-123.45') self.assertEqual(f.clean('-.12'), Decimal("-0.12")) self.assertEqual(f.clean('-00.12'), Decimal("-0.12")) self.assertEqual(f.clean('-000.12'), Decimal("-0.12")) - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '-000.123') - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '-000.12345') - self.assertRaisesMessage(ValidationError, "[u'Enter a number.']", f.clean, '--0.12') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'", f.clean, '-000.123') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'", f.clean, '-000.12345') + self.assertRaisesMessage(ValidationError, "'Enter a number.'", f.clean, '--0.12') self.assertEqual(f.max_digits, 4) self.assertEqual(f.decimal_places, 2) self.assertEqual(f.max_value, None) @@ -274,8 +276,8 @@ class FieldsTests(SimpleTestCase): def test_decimalfield_3(self): f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6') - self.assertRaisesMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4') + self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6') + self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4') self.assertEqual(f.clean('1.5'), Decimal("1.5")) self.assertEqual(f.clean('0.5'), Decimal("0.5")) self.assertEqual(f.clean('.5'), Decimal("0.5")) @@ -287,7 +289,7 @@ class FieldsTests(SimpleTestCase): def test_decimalfield_4(self): f = DecimalField(decimal_places=2) - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '0.00000001') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'", f.clean, '0.00000001') def test_decimalfield_5(self): f = DecimalField(max_digits=3) @@ -297,13 +299,13 @@ class FieldsTests(SimpleTestCase): self.assertEqual(f.clean('0000000.100'), Decimal("0.100")) # Only leading whole zeros "collapse" to one digit. self.assertEqual(f.clean('000000.02'), Decimal('0.02')) - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 3 digits in total.']", f.clean, '000000.0002') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 3 digits in total.'", f.clean, '000000.0002') self.assertEqual(f.clean('.002'), Decimal("0.002")) def test_decimalfield_6(self): f = DecimalField(max_digits=2, decimal_places=2) self.assertEqual(f.clean('.01'), Decimal(".01")) - self.assertRaisesMessage(ValidationError, "[u'Ensure that there are no more than 0 digits before the decimal point.']", f.clean, '1.1') + self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 0 digits before the decimal point.'", f.clean, '1.1') # DateField ################################################################### @@ -321,10 +323,10 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.date(2006, 10, 25), f.clean('October 25, 2006')) self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October 2006')) self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October, 2006')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '2006-4-31') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '200a-10-25') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '25/10/06') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, '2006-4-31') + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, '200a-10-25') + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, '25/10/06') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) def test_datefield_2(self): f = DateField(required=False) @@ -338,9 +340,9 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25))) self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30))) self.assertEqual(datetime.date(2006, 10, 25), f.clean('2006 10 25')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '2006-10-25') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/2006') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/06') + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, '2006-10-25') + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, '10/25/2006') + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, '10/25/06') def test_datefield_4(self): # Test whitespace stripping behavior (#5714) @@ -351,7 +353,7 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.date(2006, 10, 25), f.clean(' October 25 2006 ')) self.assertEqual(datetime.date(2006, 10, 25), f.clean(' October 25, 2006 ')) self.assertEqual(datetime.date(2006, 10, 25), f.clean(' 25 October 2006 ')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ' ') + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ' ') # TimeField ################################################################### @@ -361,8 +363,8 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59))) self.assertEqual(datetime.time(14, 25), f.clean('14:25')) self.assertEqual(datetime.time(14, 25, 59), f.clean('14:25:59')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, 'hello') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '1:24 p.m.') + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, 'hello') + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, '1:24 p.m.') def test_timefield_2(self): f = TimeField(input_formats=['%I:%M %p']) @@ -370,14 +372,14 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59))) self.assertEqual(datetime.time(4, 25), f.clean('4:25 AM')) self.assertEqual(datetime.time(16, 25), f.clean('4:25 PM')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '14:30:45') + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, '14:30:45') def test_timefield_3(self): f = TimeField() # Test whitespace stripping behavior (#5714) self.assertEqual(datetime.time(14, 25), f.clean(' 14:25 ')) self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 ')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ' ') + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ' ') # DateTimeField ############################################################### @@ -403,8 +405,8 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30:00')) self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30')) self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/06')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, 'hello') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 4:30 p.m.') + self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'", f.clean, 'hello') + self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'", f.clean, '2006-10-25 4:30 p.m.') def test_datetimefield_2(self): f = DateTimeField(input_formats=['%Y %m %d %I:%M %p']) @@ -413,7 +415,7 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))) self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59, 200), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))) self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006 10 25 2:30 PM')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 14:30:45') + self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'", f.clean, '2006-10-25 14:30:45') def test_datetimefield_3(self): f = DateTimeField(required=False) @@ -432,51 +434,51 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/2006 ')) self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 10/25/06 14:30:45 ')) self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/06 ')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, ' ') + self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'", f.clean, ' ') def test_datetimefield_5(self): - f = DateTimeField(input_formats=[u'%Y.%m.%d %H:%M:%S.%f']) + f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f']) self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006.10.25 14:30:45.0002')) # RegexField ################################################################## def test_regexfield_1(self): f = RegexField('^\d[A-F]\d$') - self.assertEqual(u'2A2', f.clean('2A2')) - self.assertEqual(u'3F3', f.clean('3F3')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, ' 2A2') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') + self.assertEqual('2A2', f.clean('2A2')) + self.assertEqual('3F3', f.clean('3F3')) + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, '3G3') + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, ' 2A2') + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, '2A2 ') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') def test_regexfield_2(self): f = RegexField('^\d[A-F]\d$', required=False) - self.assertEqual(u'2A2', f.clean('2A2')) - self.assertEqual(u'3F3', f.clean('3F3')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3') - self.assertEqual(u'', f.clean('')) + self.assertEqual('2A2', f.clean('2A2')) + self.assertEqual('3F3', f.clean('3F3')) + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, '3G3') + self.assertEqual('', f.clean('')) def test_regexfield_3(self): f = RegexField(re.compile('^\d[A-F]\d$')) - self.assertEqual(u'2A2', f.clean('2A2')) - self.assertEqual(u'3F3', f.clean('3F3')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, ' 2A2') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ') + self.assertEqual('2A2', f.clean('2A2')) + self.assertEqual('3F3', f.clean('3F3')) + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, '3G3') + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, ' 2A2') + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, '2A2 ') def test_regexfield_4(self): f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.') - self.assertEqual(u'1234', f.clean('1234')) - self.assertRaisesMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, '123') - self.assertRaisesMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, 'abcd') + self.assertEqual('1234', f.clean('1234')) + self.assertRaisesMessage(ValidationError, "'Enter a four-digit number.'", f.clean, '123') + self.assertRaisesMessage(ValidationError, "'Enter a four-digit number.'", f.clean, 'abcd') def test_regexfield_5(self): f = RegexField('^\d+$', min_length=5, max_length=10) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).']", f.clean, '123') - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).', u'Enter a valid value.']", f.clean, 'abc') - self.assertEqual(u'12345', f.clean('12345')) - self.assertEqual(u'1234567890', f.clean('1234567890')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '12345678901') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '12345a') + self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 5 characters (it has 3).'", f.clean, '123') + self.assertRaisesRegexp(ValidationError, "'Ensure this value has at least 5 characters \(it has 3\)\.', u?'Enter a valid value\.'", f.clean, 'abc') + self.assertEqual('12345', f.clean('12345')) + self.assertEqual('1234567890', f.clean('1234567890')) + self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 10 characters (it has 11).'", f.clean, '12345678901') + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, '12345a') def test_regexfield_6(self): """ @@ -489,27 +491,27 @@ class FieldsTests(SimpleTestCase): def test_change_regex_after_init(self): f = RegexField('^[a-z]+$') f.regex = '^\d+$' - self.assertEqual(u'1234', f.clean('1234')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, 'abcd') + self.assertEqual('1234', f.clean('1234')) + self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, 'abcd') # EmailField ################################################################## def test_emailfield_1(self): f = EmailField() - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertEqual(u'person@example.com', f.clean('person@example.com')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@invalid-.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@-invalid.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.alid-.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.-alid.com') - self.assertEqual(u'example@valid-----hyphens.com', f.clean('example@valid-----hyphens.com')) - self.assertEqual(u'example@valid-with-hyphens.com', f.clean('example@valid-with-hyphens.com')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@.com') - self.assertEqual(u'local@domain.with.idn.xyz\xe4\xf6\xfc\xdfabc.part.com', f.clean('local@domain.with.idn.xyzäöüßabc.part.com')) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertEqual('person@example.com', f.clean('person@example.com')) + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'foo') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'foo@') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'foo@bar') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'example@invalid-.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'example@-invalid.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'example@inv-.alid-.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'example@inv-.-alid.com') + self.assertEqual('example@valid-----hyphens.com', f.clean('example@valid-----hyphens.com')) + self.assertEqual('example@valid-with-hyphens.com', f.clean('example@valid-with-hyphens.com')) + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'example@.com') + self.assertEqual('local@domain.with.idn.xyz\xe4\xf6\xfc\xdfabc.part.com', f.clean('local@domain.with.idn.xyzäöüßabc.part.com')) def test_email_regexp_for_performance(self): f = EmailField() @@ -517,50 +519,50 @@ class FieldsTests(SimpleTestCase): # if the security fix isn't in place. self.assertRaisesMessage( ValidationError, - "[u'Enter a valid e-mail address.']", + "'Enter a valid e-mail address.'", f.clean, 'viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058' ) def test_emailfield_2(self): f = EmailField(required=False) - self.assertEqual(u'', f.clean('')) - self.assertEqual(u'', f.clean(None)) - self.assertEqual(u'person@example.com', f.clean('person@example.com')) - self.assertEqual(u'example@example.com', f.clean(' example@example.com \t \t ')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar') + self.assertEqual('', f.clean('')) + self.assertEqual('', f.clean(None)) + self.assertEqual('person@example.com', f.clean('person@example.com')) + self.assertEqual('example@example.com', f.clean(' example@example.com \t \t ')) + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'foo') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'foo@') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'foo@bar') def test_emailfield_3(self): f = EmailField(min_length=10, max_length=15) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 9).']", f.clean, 'a@foo.com') - self.assertEqual(u'alf@foo.com', f.clean('alf@foo.com')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at most 15 characters (it has 20).']", f.clean, 'alf123456788@foo.com') + self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 10 characters (it has 9).'", f.clean, 'a@foo.com') + self.assertEqual('alf@foo.com', f.clean('alf@foo.com')) + self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 15 characters (it has 20).'", f.clean, 'alf123456788@foo.com') # FileField ################################################################## def test_filefield_1(self): f = FileField() - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '', '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '', '') self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf')) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None, '') self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) - self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', b'')) - self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', b''), '') + self.assertRaisesMessage(ValidationError, "'No file was submitted. Check the encoding type on the form.'", f.clean, SimpleUploadedFile('', b'')) + self.assertRaisesMessage(ValidationError, "'No file was submitted. Check the encoding type on the form.'", f.clean, SimpleUploadedFile('', b''), '') self.assertEqual('files/test3.pdf', f.clean(None, 'files/test3.pdf')) - self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, 'some content that is not a file') - self.assertRaisesMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', None)) - self.assertRaisesMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', b'')) + self.assertRaisesMessage(ValidationError, "'No file was submitted. Check the encoding type on the form.'", f.clean, 'some content that is not a file') + self.assertRaisesMessage(ValidationError, "'The submitted file is empty.'", f.clean, SimpleUploadedFile('name', None)) + self.assertRaisesMessage(ValidationError, "'The submitted file is empty.'", f.clean, SimpleUploadedFile('name', b'')) self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content')))) - self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', u'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))))) + self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))))) self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content'), 'files/test4.pdf'))) def test_filefield_2(self): f = FileField(max_length = 5) - self.assertRaisesMessage(ValidationError, "[u'Ensure this filename has at most 5 characters (it has 18).']", f.clean, SimpleUploadedFile('test_maxlength.txt', b'hello world')) + self.assertRaisesMessage(ValidationError, "'Ensure this filename has at most 5 characters (it has 18).'", f.clean, SimpleUploadedFile('test_maxlength.txt', b'hello world')) self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf')) self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content')))) @@ -574,92 +576,92 @@ class FieldsTests(SimpleTestCase): def test_urlfield_1(self): f = URLField() - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertEqual(u'http://localhost/', f.clean('http://localhost')) - self.assertEqual(u'http://example.com/', f.clean('http://example.com')) - self.assertEqual(u'http://example.com./', f.clean('http://example.com.')) - self.assertEqual(u'http://www.example.com/', f.clean('http://www.example.com')) - self.assertEqual(u'http://www.example.com:8000/test', f.clean('http://www.example.com:8000/test')) - self.assertEqual(u'http://valid-with-hyphens.com/', f.clean('valid-with-hyphens.com')) - self.assertEqual(u'http://subdomain.domain.com/', f.clean('subdomain.domain.com')) - self.assertEqual(u'http://200.8.9.10/', f.clean('http://200.8.9.10')) - self.assertEqual(u'http://200.8.9.10:8000/test', f.clean('http://200.8.9.10:8000/test')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'foo') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example.') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'com.') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, '.') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://invalid-.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://-invalid.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.alid-.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.-alid.com') - self.assertEqual(u'http://valid-----hyphens.com/', f.clean('http://valid-----hyphens.com')) - self.assertEqual(u'http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah', f.clean('http://some.idn.xyzäöüßabc.domain.com:123/blah')) - self.assertEqual(u'http://www.example.com/s/http://code.djangoproject.com/ticket/13804', f.clean('www.example.com/s/http://code.djangoproject.com/ticket/13804')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, '[a') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://[a') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertEqual('http://localhost/', f.clean('http://localhost')) + self.assertEqual('http://example.com/', f.clean('http://example.com')) + self.assertEqual('http://example.com./', f.clean('http://example.com.')) + self.assertEqual('http://www.example.com/', f.clean('http://www.example.com')) + self.assertEqual('http://www.example.com:8000/test', f.clean('http://www.example.com:8000/test')) + self.assertEqual('http://valid-with-hyphens.com/', f.clean('valid-with-hyphens.com')) + self.assertEqual('http://subdomain.domain.com/', f.clean('subdomain.domain.com')) + self.assertEqual('http://200.8.9.10/', f.clean('http://200.8.9.10')) + self.assertEqual('http://200.8.9.10:8000/test', f.clean('http://200.8.9.10:8000/test')) + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'foo') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://example') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://example.') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'com.') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, '.') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://invalid-.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://-invalid.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://inv-.alid-.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://inv-.-alid.com') + self.assertEqual('http://valid-----hyphens.com/', f.clean('http://valid-----hyphens.com')) + self.assertEqual('http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah', f.clean('http://some.idn.xyzäöüßabc.domain.com:123/blah')) + self.assertEqual('http://www.example.com/s/http://code.djangoproject.com/ticket/13804', f.clean('www.example.com/s/http://code.djangoproject.com/ticket/13804')) + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, '[a') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://[a') def test_url_regex_ticket11198(self): f = URLField() # hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*200,)) + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://%s' % ("X"*200,)) # a second test, to make sure the problem is really addressed, even on # domains that don't fail the domain label length check in the regex - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*60,)) + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://%s' % ("X"*60,)) def test_urlfield_2(self): f = URLField(required=False) - self.assertEqual(u'', f.clean('')) - self.assertEqual(u'', f.clean(None)) - self.assertEqual(u'http://example.com/', f.clean('http://example.com')) - self.assertEqual(u'http://www.example.com/', f.clean('http://www.example.com')) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'foo') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example.') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://.com') + self.assertEqual('', f.clean('')) + self.assertEqual('', f.clean(None)) + self.assertEqual('http://example.com/', f.clean('http://example.com')) + self.assertEqual('http://www.example.com/', f.clean('http://www.example.com')) + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'foo') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://example') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://example.') + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 'http://.com') def test_urlfield_5(self): f = URLField(min_length=15, max_length=20) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at least 15 characters (it has 13).']", f.clean, 'http://f.com') - self.assertEqual(u'http://example.com/', f.clean('http://example.com')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 38).']", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com') + self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 15 characters (it has 13).'", f.clean, 'http://f.com') + self.assertEqual('http://example.com/', f.clean('http://example.com')) + self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 38).'", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com') def test_urlfield_6(self): f = URLField(required=False) - self.assertEqual(u'http://example.com/', f.clean('example.com')) - self.assertEqual(u'', f.clean('')) - self.assertEqual(u'https://example.com/', f.clean('https://example.com')) + self.assertEqual('http://example.com/', f.clean('example.com')) + self.assertEqual('', f.clean('')) + self.assertEqual('https://example.com/', f.clean('https://example.com')) def test_urlfield_7(self): f = URLField() - self.assertEqual(u'http://example.com/', f.clean('http://example.com')) - self.assertEqual(u'http://example.com/test', f.clean('http://example.com/test')) + self.assertEqual('http://example.com/', f.clean('http://example.com')) + self.assertEqual('http://example.com/test', f.clean('http://example.com/test')) def test_urlfield_8(self): # ticket #11826 f = URLField() - self.assertEqual(u'http://example.com/?some_param=some_value', f.clean('http://example.com?some_param=some_value')) + self.assertEqual('http://example.com/?some_param=some_value', f.clean('http://example.com?some_param=some_value')) def test_urlfield_9(self): f = URLField() urls = ( - u'http://עברית.idn.icann.org/', - u'http://sãopaulo.com/', - u'http://sãopaulo.com.br/', - u'http://пример.испытание/', - u'http://مثال.إختبار/', - u'http://例子.测试/', - u'http://例子.測試/', - u'http://उदाहरण.परीक्षा/', - u'http://例え.テスト/', - u'http://مثال.آزمایشی/', - u'http://실례.테스트/', - u'http://العربية.idn.icann.org/', + 'http://עברית.idn.icann.org/', + 'http://sãopaulo.com/', + 'http://sãopaulo.com.br/', + 'http://пример.испытание/', + 'http://مثال.إختبار/', + 'http://例子.测试/', + 'http://例子.測試/', + 'http://उदाहरण.परीक्षा/', + 'http://例え.テスト/', + 'http://مثال.آزمایشی/', + 'http://실례.테스트/', + 'http://العربية.idn.icann.org/', ) for url in urls: # Valid IDN @@ -667,21 +669,21 @@ class FieldsTests(SimpleTestCase): def test_urlfield_not_string(self): f = URLField(required=False) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 23) + self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 23) # BooleanField ################################################################ def test_booleanfield_1(self): f = BooleanField() - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(True, f.clean(True)) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, False) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, False) self.assertEqual(True, f.clean(1)) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, 0) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, 0) self.assertEqual(True, f.clean('Django rocks')) self.assertEqual(True, f.clean('True')) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, 'False') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, 'False') def test_booleanfield_2(self): f = BooleanField(required=False) @@ -705,34 +707,34 @@ class FieldsTests(SimpleTestCase): def test_choicefield_1(self): f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')]) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertEqual(u'1', f.clean(1)) - self.assertEqual(u'1', f.clean('1')) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertEqual('1', f.clean(1)) + self.assertEqual('1', f.clean('1')) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 3 is not one of the available choices.'", f.clean, '3') def test_choicefield_2(self): f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False) - self.assertEqual(u'', f.clean('')) - self.assertEqual(u'', f.clean(None)) - self.assertEqual(u'1', f.clean(1)) - self.assertEqual(u'1', f.clean('1')) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3') + self.assertEqual('', f.clean('')) + self.assertEqual('', f.clean(None)) + self.assertEqual('1', f.clean(1)) + self.assertEqual('1', f.clean('1')) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 3 is not one of the available choices.'", f.clean, '3') def test_choicefield_3(self): f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')]) - self.assertEqual(u'J', f.clean('J')) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. John is not one of the available choices.']", f.clean, 'John') + self.assertEqual('J', f.clean('J')) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. John is not one of the available choices.'", f.clean, 'John') def test_choicefield_4(self): f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')]) - self.assertEqual(u'1', f.clean(1)) - self.assertEqual(u'1', f.clean('1')) - self.assertEqual(u'3', f.clean(3)) - self.assertEqual(u'3', f.clean('3')) - self.assertEqual(u'5', f.clean(5)) - self.assertEqual(u'5', f.clean('5')) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, '6') + self.assertEqual('1', f.clean(1)) + self.assertEqual('1', f.clean('1')) + self.assertEqual('3', f.clean(3)) + self.assertEqual('3', f.clean('3')) + self.assertEqual('5', f.clean(5)) + self.assertEqual('5', f.clean('5')) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, '6') # TypedChoiceField ############################################################ # TypedChoiceField is just like ChoiceField, except that coerced types will @@ -741,7 +743,7 @@ class FieldsTests(SimpleTestCase): def test_typedchoicefield_1(self): f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) self.assertEqual(1, f.clean('1')) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, '2') + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 2 is not one of the available choices.'", f.clean, '2') def test_typedchoicefield_2(self): # Different coercion, same validation. @@ -755,11 +757,11 @@ class FieldsTests(SimpleTestCase): def test_typedchoicefield_4(self): # Even more weirdness: if you have a valid choice but your coercion function - # can't coerce, you'll still get a validation error. Don't do this! + # can't coerce, yo'll still get a validation error. Don't do this! f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. B is not one of the available choices.']", f.clean, 'B') + self.assertRaisesMessage(ValidationError, "'Select a valid choice. B is not one of the available choices.'", f.clean, 'B') # Required fields require values - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') def test_typedchoicefield_5(self): # Non-required fields aren't required @@ -821,42 +823,42 @@ class FieldsTests(SimpleTestCase): def test_multiplechoicefield_1(self): f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')]) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertEqual([u'1'], f.clean([1])) - self.assertEqual([u'1'], f.clean(['1'])) - self.assertEqual([u'1', u'2'], f.clean(['1', '2'])) - self.assertEqual([u'1', u'2'], f.clean([1, '2'])) - self.assertEqual([u'1', u'2'], f.clean((1, '2'))) - self.assertRaisesMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, []) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, ()) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3']) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertEqual(['1'], f.clean([1])) + self.assertEqual(['1'], f.clean(['1'])) + self.assertEqual(['1', '2'], f.clean(['1', '2'])) + self.assertEqual(['1', '2'], f.clean([1, '2'])) + self.assertEqual(['1', '2'], f.clean((1, '2'))) + self.assertRaisesMessage(ValidationError, "'Enter a list of values.'", f.clean, 'hello') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, []) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, ()) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 3 is not one of the available choices.'", f.clean, ['3']) def test_multiplechoicefield_2(self): f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False) self.assertEqual([], f.clean('')) self.assertEqual([], f.clean(None)) - self.assertEqual([u'1'], f.clean([1])) - self.assertEqual([u'1'], f.clean(['1'])) - self.assertEqual([u'1', u'2'], f.clean(['1', '2'])) - self.assertEqual([u'1', u'2'], f.clean([1, '2'])) - self.assertEqual([u'1', u'2'], f.clean((1, '2'))) - self.assertRaisesMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello') + self.assertEqual(['1'], f.clean([1])) + self.assertEqual(['1'], f.clean(['1'])) + self.assertEqual(['1', '2'], f.clean(['1', '2'])) + self.assertEqual(['1', '2'], f.clean([1, '2'])) + self.assertEqual(['1', '2'], f.clean((1, '2'))) + self.assertRaisesMessage(ValidationError, "'Enter a list of values.'", f.clean, 'hello') self.assertEqual([], f.clean([])) self.assertEqual([], f.clean(())) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3']) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 3 is not one of the available choices.'", f.clean, ['3']) def test_multiplechoicefield_3(self): f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')]) - self.assertEqual([u'1'], f.clean([1])) - self.assertEqual([u'1'], f.clean(['1'])) - self.assertEqual([u'1', u'5'], f.clean([1, 5])) - self.assertEqual([u'1', u'5'], f.clean([1, '5'])) - self.assertEqual([u'1', u'5'], f.clean(['1', 5])) - self.assertEqual([u'1', u'5'], f.clean(['1', '5'])) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['6']) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['1','6']) + self.assertEqual(['1'], f.clean([1])) + self.assertEqual(['1'], f.clean(['1'])) + self.assertEqual(['1', '5'], f.clean([1, 5])) + self.assertEqual(['1', '5'], f.clean([1, '5'])) + self.assertEqual(['1', '5'], f.clean(['1', 5])) + self.assertEqual(['1', '5'], f.clean(['1', '5'])) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['6']) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['1','6']) # TypedMultipleChoiceField ############################################################ # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types @@ -865,7 +867,7 @@ class FieldsTests(SimpleTestCase): def test_typedmultiplechoicefield_1(self): f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) self.assertEqual([1], f.clean(['1'])) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, ['2']) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 2 is not one of the available choices.'", f.clean, ['2']) def test_typedmultiplechoicefield_2(self): # Different coercion, same validation. @@ -880,15 +882,15 @@ class FieldsTests(SimpleTestCase): def test_typedmultiplechoicefield_4(self): f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) self.assertEqual([1, -1], f.clean(['1','-1'])) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, ['1','2']) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. 2 is not one of the available choices.'", f.clean, ['1','2']) def test_typedmultiplechoicefield_5(self): # Even more weirdness: if you have a valid choice but your coercion function # can't coerce, you'll still get a validation error. Don't do this! f = TypedMultipleChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. B is not one of the available choices.']", f.clean, ['B']) + self.assertRaisesMessage(ValidationError, "'Select a valid choice. B is not one of the available choices.'", f.clean, ['B']) # Required fields require values - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, []) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, []) def test_typedmultiplechoicefield_6(self): # Non-required fields aren't required @@ -904,19 +906,19 @@ class FieldsTests(SimpleTestCase): def test_combofield_1(self): f = ComboField(fields=[CharField(max_length=20), EmailField()]) - self.assertEqual(u'test@example.com', f.clean('test@example.com')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'not an e-mail') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) + self.assertEqual('test@example.com', f.clean('test@example.com')) + self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 28).'", f.clean, 'longemailaddress@example.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'not an e-mail') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) def test_combofield_2(self): f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False) - self.assertEqual(u'test@example.com', f.clean('test@example.com')) - self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'not an e-mail') - self.assertEqual(u'', f.clean('')) - self.assertEqual(u'', f.clean(None)) + self.assertEqual('test@example.com', f.clean('test@example.com')) + self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 28).'", f.clean, 'longemailaddress@example.com') + self.assertRaisesMessage(ValidationError, "'Enter a valid e-mail address.'", f.clean, 'not an e-mail') + self.assertEqual('', f.clean('')) + self.assertEqual('', f.clean(None)) # FilePathField ############################################################### @@ -943,7 +945,7 @@ class FieldsTests(SimpleTestCase): for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) self.assertTrue(got[0].endswith(exp[0])) - self.assertRaisesMessage(ValidationError, "[u'Select a valid choice. fields.py is not one of the available choices.']", f.clean, 'fields.py') + self.assertRaisesMessage(ValidationError, "'Select a valid choice. fields.py is not one of the available choices.'", f.clean, 'fields.py') assert fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py') def test_filepathfield_3(self): @@ -1018,12 +1020,12 @@ class FieldsTests(SimpleTestCase): f = SplitDateTimeField() assert isinstance(f.widget, SplitDateTimeWidget) self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) - self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, '') - self.assertRaisesMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.', u'Enter a valid time.']", f.clean, ['hello', 'there']) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', 'there']) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['hello', '07:30']) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) + self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') + self.assertRaisesMessage(ValidationError, "'Enter a list of values.'", f.clean, 'hello') + self.assertRaisesRegexp(ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", f.clean, ['hello', 'there']) + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', 'there']) + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['hello', '07:30']) def test_splitdatetimefield_2(self): f = SplitDateTimeField(required=False) @@ -1033,10 +1035,10 @@ class FieldsTests(SimpleTestCase): self.assertEqual(None, f.clean('')) self.assertEqual(None, f.clean([''])) self.assertEqual(None, f.clean(['', ''])) - self.assertRaisesMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello') - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.', u'Enter a valid time.']", f.clean, ['hello', 'there']) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', 'there']) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['hello', '07:30']) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', '']) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10']) - self.assertRaisesMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['', '07:30']) + self.assertRaisesMessage(ValidationError, "'Enter a list of values.'", f.clean, 'hello') + self.assertRaisesRegexp(ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", f.clean, ['hello', 'there']) + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', 'there']) + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['hello', '07:30']) + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', '']) + self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10']) + self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['', '07:30']) diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py index 16cf46fc29..7e1c8384a0 100644 --- a/tests/regressiontests/forms/tests/forms.py +++ b/tests/regressiontests/forms/tests/forms.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + import datetime from django.core.files.uploadedfile import SimpleUploadedFile @@ -6,6 +8,7 @@ from django.forms import * from django.http import QueryDict from django.template import Template, Context from django.test import TestCase +from django.test.utils import str_prefix from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.safestring import mark_safe @@ -29,15 +32,15 @@ class FormsTestCase(TestCase): def test_form(self): # Pass a dictionary to a Form's __init__(). - p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'}) + p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'}) self.assertTrue(p.is_bound) self.assertEqual(p.errors, {}) self.assertTrue(p.is_valid()) - self.assertHTMLEqual(p.errors.as_ul(), u'') - self.assertEqual(p.errors.as_text(), u'') - self.assertEqual(p.cleaned_data["first_name"], u'John') - self.assertEqual(p.cleaned_data["last_name"], u'Lennon') + self.assertHTMLEqual(p.errors.as_ul(), '') + self.assertEqual(p.errors.as_text(), '') + self.assertEqual(p.cleaned_data["first_name"], 'John') + self.assertEqual(p.cleaned_data["last_name"], 'Lennon') self.assertEqual(p.cleaned_data["birthday"], datetime.date(1940, 10, 9)) self.assertHTMLEqual(str(p['first_name']), '') self.assertHTMLEqual(str(p['last_name']), '') @@ -63,9 +66,9 @@ class FormsTestCase(TestCase): form_output.append([boundfield.label, boundfield.data]) self.assertEqual(form_output, [ - ['First name', u'John'], - ['Last name', u'Lennon'], - ['Birthday', u'1940-10-9'] + ['First name', 'John'], + ['Last name', 'Lennon'], + ['Birthday', '1940-10-9'] ]) self.assertHTMLEqual(str(p), """ @@ -75,9 +78,9 @@ class FormsTestCase(TestCase): # Empty dictionaries are valid, too. p = Person({}) self.assertTrue(p.is_bound) - self.assertEqual(p.errors['first_name'], [u'This field is required.']) - self.assertEqual(p.errors['last_name'], [u'This field is required.']) - self.assertEqual(p.errors['birthday'], [u'This field is required.']) + self.assertEqual(p.errors['first_name'], ['This field is required.']) + self.assertEqual(p.errors['last_name'], ['This field is required.']) + self.assertEqual(p.errors['birthday'], ['This field is required.']) self.assertFalse(p.is_valid()) try: p.cleaned_data @@ -128,16 +131,16 @@ class FormsTestCase(TestCase): def test_unicode_values(self): # Unicode values are handled properly. - p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'}) - self.assertHTMLEqual(p.as_table(), u'\n\n') - self.assertHTMLEqual(p.as_ul(), u'
      • \n
      • \n
      • ') - self.assertHTMLEqual(p.as_p(), u'

        \n

        \n

        ') + p = Person({'first_name': 'John', 'last_name': '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'}) + self.assertHTMLEqual(p.as_table(), '\n\n') + self.assertHTMLEqual(p.as_ul(), '
      • \n
      • \n
      • ') + self.assertHTMLEqual(p.as_p(), '

        \n

        \n

        ') - p = Person({'last_name': u'Lennon'}) - self.assertEqual(p.errors['first_name'], [u'This field is required.']) - self.assertEqual(p.errors['birthday'], [u'This field is required.']) + p = Person({'last_name': 'Lennon'}) + self.assertEqual(p.errors['first_name'], ['This field is required.']) + self.assertEqual(p.errors['birthday'], ['This field is required.']) self.assertFalse(p.is_valid()) - self.assertHTMLEqual(p.errors.as_ul(), u'
        • first_name
          • This field is required.
        • birthday
          • This field is required.
        ') + self.assertHTMLEqual(p.errors.as_ul(), '
        • first_name
          • This field is required.
        • birthday
          • This field is required.
        ') self.assertEqual(p.errors.as_text(), """* first_name * This field is required. * birthday @@ -147,9 +150,9 @@ class FormsTestCase(TestCase): self.fail('Attempts to access cleaned_data when validation fails should fail.') except AttributeError: pass - self.assertEqual(p['first_name'].errors, [u'This field is required.']) - self.assertHTMLEqual(p['first_name'].errors.as_ul(), u'
        • This field is required.
        ') - self.assertEqual(p['first_name'].errors.as_text(), u'* This field is required.') + self.assertEqual(p['first_name'].errors, ['This field is required.']) + self.assertHTMLEqual(p['first_name'].errors.as_ul(), '
        • This field is required.
        ') + self.assertEqual(p['first_name'].errors.as_text(), '* This field is required.') p = Person() self.assertHTMLEqual(str(p['first_name']), '') @@ -161,11 +164,11 @@ class FormsTestCase(TestCase): # Form, even if you pass extra data when you define the Form. In this # example, we pass a bunch of extra fields to the form constructor, # but cleaned_data contains only the form's fields. - data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'} + data = {'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9', 'extra1': 'hello', 'extra2': 'hello'} p = Person(data) self.assertTrue(p.is_valid()) - self.assertEqual(p.cleaned_data['first_name'], u'John') - self.assertEqual(p.cleaned_data['last_name'], u'Lennon') + self.assertEqual(p.cleaned_data['first_name'], 'John') + self.assertEqual(p.cleaned_data['last_name'], 'Lennon') self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9)) def test_optional_data(self): @@ -179,12 +182,12 @@ class FormsTestCase(TestCase): last_name = CharField() nick_name = CharField(required=False) - data = {'first_name': u'John', 'last_name': u'Lennon'} + data = {'first_name': 'John', 'last_name': 'Lennon'} f = OptionalPersonForm(data) self.assertTrue(f.is_valid()) - self.assertEqual(f.cleaned_data['nick_name'], u'') - self.assertEqual(f.cleaned_data['first_name'], u'John') - self.assertEqual(f.cleaned_data['last_name'], u'Lennon') + self.assertEqual(f.cleaned_data['nick_name'], '') + self.assertEqual(f.cleaned_data['first_name'], 'John') + self.assertEqual(f.cleaned_data['last_name'], 'Lennon') # For DateFields, it's set to None. class OptionalPersonForm(Form): @@ -192,12 +195,12 @@ class FormsTestCase(TestCase): last_name = CharField() birth_date = DateField(required=False) - data = {'first_name': u'John', 'last_name': u'Lennon'} + data = {'first_name': 'John', 'last_name': 'Lennon'} f = OptionalPersonForm(data) self.assertTrue(f.is_valid()) self.assertEqual(f.cleaned_data['birth_date'], None) - self.assertEqual(f.cleaned_data['first_name'], u'John') - self.assertEqual(f.cleaned_data['last_name'], u'Lennon') + self.assertEqual(f.cleaned_data['first_name'], 'John') + self.assertEqual(f.cleaned_data['last_name'], 'Lennon') def test_auto_id(self): # "auto_id" tells the Form to add an "id" attribute to each form element. @@ -286,9 +289,9 @@ class FormsTestCase(TestCase): # as_textarea(), as_text() and as_hidden() are shortcuts for changing the output # widget type: - self.assertHTMLEqual(f['subject'].as_textarea(), u'') - self.assertHTMLEqual(f['message'].as_text(), u'') - self.assertHTMLEqual(f['message'].as_hidden(), u'') + self.assertHTMLEqual(f['subject'].as_textarea(), '') + self.assertHTMLEqual(f['message'].as_text(), '') + self.assertHTMLEqual(f['message'].as_hidden(), '') # The 'widget' parameter to a Field can also be an instance: class ContactForm(Form): @@ -300,11 +303,11 @@ class FormsTestCase(TestCase): # Instance-level attrs are *not* carried over to as_textarea(), as_text() and # as_hidden(): - self.assertHTMLEqual(f['message'].as_text(), u'') + self.assertHTMLEqual(f['message'].as_text(), '') f = ContactForm({'subject': 'Hello', 'message': 'I love you.'}, auto_id=False) - self.assertHTMLEqual(f['subject'].as_textarea(), u'') - self.assertHTMLEqual(f['message'].as_text(), u'') - self.assertHTMLEqual(f['message'].as_hidden(), u'') + self.assertHTMLEqual(f['subject'].as_textarea(), '') + self.assertHTMLEqual(f['message'].as_text(), '') + self.assertHTMLEqual(f['message'].as_hidden(), '') def test_forms_with_choices(self): # For a form with a ') + self.assertHTMLEqual('\n'.join([str(bf) for bf in f['name']]), '') def test_forms_with_multiple_choice(self): # MultipleChoiceField is a special case, as its data is required to be a list: @@ -582,15 +585,15 @@ class FormsTestCase(TestCase): # When using CheckboxSelectMultiple, the framework expects a list of input and # returns a list of input. f = SongForm({'name': 'Yesterday'}, auto_id=False) - self.assertEqual(f.errors['composers'], [u'This field is required.']) + self.assertEqual(f.errors['composers'], ['This field is required.']) f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False) self.assertEqual(f.errors, {}) - self.assertEqual(f.cleaned_data['composers'], [u'J']) - self.assertEqual(f.cleaned_data['name'], u'Yesterday') + self.assertEqual(f.cleaned_data['composers'], ['J']) + self.assertEqual(f.cleaned_data['name'], 'Yesterday') f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False) self.assertEqual(f.errors, {}) - self.assertEqual(f.cleaned_data['composers'], [u'J', u'P']) - self.assertEqual(f.cleaned_data['name'], u'Yesterday') + self.assertEqual(f.cleaned_data['composers'], ['J', 'P']) + self.assertEqual(f.cleaned_data['name'], 'Yesterday') def test_escaping(self): # Validation errors are HTML-escaped when output as HTML. @@ -629,23 +632,23 @@ class FormsTestCase(TestCase): def clean_password2(self): if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: - raise ValidationError(u'Please make sure your passwords match.') + raise ValidationError('Please make sure your passwords match.') return self.cleaned_data['password2'] f = UserRegistration(auto_id=False) self.assertEqual(f.errors, {}) f = UserRegistration({}, auto_id=False) - self.assertEqual(f.errors['username'], [u'This field is required.']) - self.assertEqual(f.errors['password1'], [u'This field is required.']) - self.assertEqual(f.errors['password2'], [u'This field is required.']) + self.assertEqual(f.errors['username'], ['This field is required.']) + self.assertEqual(f.errors['password1'], ['This field is required.']) + self.assertEqual(f.errors['password2'], ['This field is required.']) f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) - self.assertEqual(f.errors['password2'], [u'Please make sure your passwords match.']) + self.assertEqual(f.errors['password2'], ['Please make sure your passwords match.']) f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) self.assertEqual(f.errors, {}) - self.assertEqual(f.cleaned_data['username'], u'adrian') - self.assertEqual(f.cleaned_data['password1'], u'foo') - self.assertEqual(f.cleaned_data['password2'], u'foo') + self.assertEqual(f.cleaned_data['username'], 'adrian') + self.assertEqual(f.cleaned_data['password1'], 'foo') + self.assertEqual(f.cleaned_data['password2'], 'foo') # Another way of doing multiple-field validation is by implementing the # Form's clean() method. If you do this, any ValidationError raised by that @@ -661,7 +664,7 @@ class FormsTestCase(TestCase): def clean(self): if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: - raise ValidationError(u'Please make sure your passwords match.') + raise ValidationError('Please make sure your passwords match.') return self.cleaned_data @@ -671,11 +674,11 @@ class FormsTestCase(TestCase): self.assertHTMLEqual(f.as_table(), """Username:
        • This field is required.
        Password1:
        • This field is required.
        Password2:
        • This field is required.
        """) - self.assertEqual(f.errors['username'], [u'This field is required.']) - self.assertEqual(f.errors['password1'], [u'This field is required.']) - self.assertEqual(f.errors['password2'], [u'This field is required.']) + self.assertEqual(f.errors['username'], ['This field is required.']) + self.assertEqual(f.errors['password1'], ['This field is required.']) + self.assertEqual(f.errors['password2'], ['This field is required.']) f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) - self.assertEqual(f.errors['__all__'], [u'Please make sure your passwords match.']) + self.assertEqual(f.errors['__all__'], ['Please make sure your passwords match.']) self.assertHTMLEqual(f.as_table(), """
        • Please make sure your passwords match.
        Username: Password1: @@ -686,9 +689,9 @@ class FormsTestCase(TestCase):
      • Password2:
      • """) f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) self.assertEqual(f.errors, {}) - self.assertEqual(f.cleaned_data['username'], u'adrian') - self.assertEqual(f.cleaned_data['password1'], u'foo') - self.assertEqual(f.cleaned_data['password2'], u'foo') + self.assertEqual(f.cleaned_data['username'], 'adrian') + self.assertEqual(f.cleaned_data['password1'], 'foo') + self.assertEqual(f.cleaned_data['password2'], 'foo') def test_dynamic_construction(self): # It's possible to construct a Form dynamically by adding to the self.fields @@ -985,10 +988,10 @@ class FormsTestCase(TestCase): # A label can be a Unicode object or a bytestring with special characters. class UserRegistration(Form): username = CharField(max_length=10, label='ŠĐĆŽćžšđ') - password = CharField(widget=PasswordInput, label=u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') + password = CharField(widget=PasswordInput, label='\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') p = UserRegistration(auto_id=False) - self.assertHTMLEqual(p.as_ul(), u'
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • \n
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • ') + self.assertHTMLEqual(p.as_ul(), '
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • \n
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • ') # If a label is set to the empty string for a field, that field won't get a label. class UserRegistration(Form): @@ -1034,8 +1037,8 @@ class FormsTestCase(TestCase): f = FavoriteForm(auto_id=False, label_suffix='') self.assertHTMLEqual(f.as_ul(), """
      • Favorite color?
      • Favorite animal
      • """) - f = FavoriteForm(auto_id=False, label_suffix=u'\u2192') - self.assertHTMLEqual(f.as_ul(), u'
      • Favorite color?
      • \n
      • Favorite animal\u2192
      • ') + f = FavoriteForm(auto_id=False, label_suffix='\u2192') + self.assertHTMLEqual(f.as_ul(), '
      • Favorite color?
      • \n
      • Favorite animal\u2192
      • ') def test_initial_data(self): # You can specify initial data for a field by using the 'initial' argument to a @@ -1056,10 +1059,10 @@ class FormsTestCase(TestCase): p = UserRegistration({}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
        • This field is required.
        Username:
        • This field is required.
        Password:
      • """) - p = UserRegistration({'username': u''}, auto_id=False) + p = UserRegistration({'username': ''}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
        • This field is required.
        Username:
        • This field is required.
        Password:
      • """) - p = UserRegistration({'username': u'foo'}, auto_id=False) + p = UserRegistration({'username': 'foo'}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
      • Username:
        • This field is required.
        Password:
      • """) @@ -1067,7 +1070,7 @@ class FormsTestCase(TestCase): # example, we don't provide a value for 'username', and the form raises a # validation error rather than using the initial value for 'username'. p = UserRegistration({'password': 'secret'}) - self.assertEqual(p.errors['username'], [u'This field is required.']) + self.assertEqual(p.errors['username'], ['This field is required.']) self.assertFalse(p.is_valid()) def test_dynamic_initial_data(self): @@ -1092,10 +1095,10 @@ class FormsTestCase(TestCase): p = UserRegistration({}, initial={'username': 'django'}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
        • This field is required.
        Username:
        • This field is required.
        Password:
      • """) - p = UserRegistration({'username': u''}, initial={'username': 'django'}, auto_id=False) + p = UserRegistration({'username': ''}, initial={'username': 'django'}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
        • This field is required.
        Username:
        • This field is required.
        Password:
      • """) - p = UserRegistration({'username': u'foo'}, initial={'username': 'django'}, auto_id=False) + p = UserRegistration({'username': 'foo'}, initial={'username': 'django'}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
      • Username:
        • This field is required.
        Password:
      • """) @@ -1103,7 +1106,7 @@ class FormsTestCase(TestCase): # In this example, we don't provide a value for 'username', and the form raises a # validation error rather than using the initial value for 'username'. p = UserRegistration({'password': 'secret'}, initial={'username': 'django'}) - self.assertEqual(p.errors['username'], [u'This field is required.']) + self.assertEqual(p.errors['username'], ['This field is required.']) self.assertFalse(p.is_valid()) # If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), @@ -1156,7 +1159,7 @@ class FormsTestCase(TestCase): """) - p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False) + p = UserRegistration({'username': ''}, initial={'username': initial_django}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
        • This field is required.
        Username:
        • This field is required.
        Password:
        • This field is required.
        Options:
      • """) - p = UserRegistration({'username': u'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False) + p = UserRegistration({'username': 'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
      • Username:
        • This field is required.
        Password:
      • Options:
        Choose wisely.""") # The help text is displayed whether or not data is provided for the form. - p = UserRegistration({'username': u'foo'}, auto_id=False) + p = UserRegistration({'username': 'foo'}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """
      • Username: e.g., user@example.com
        • This field is required.
        Password: Choose wisely.
      • """) @@ -1254,7 +1257,7 @@ class FormsTestCase(TestCase): username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ') p = UserRegistration(auto_id=False) - self.assertHTMLEqual(p.as_ul(), u'
      • Username: \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111
      • ') + self.assertHTMLEqual(p.as_ul(), '
      • Username: \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111
      • ') def test_subclassing_forms(self): # You can subclass a Form to add fields. The resulting form subclass will have @@ -1312,9 +1315,9 @@ class FormsTestCase(TestCase): birthday = DateField() data = { - 'person1-first_name': u'John', - 'person1-last_name': u'Lennon', - 'person1-birthday': u'1940-10-9' + 'person1-first_name': 'John', + 'person1-last_name': 'Lennon', + 'person1-birthday': '1940-10-9' } p = Person(data, prefix='person1') self.assertHTMLEqual(p.as_ul(), """
      • @@ -1325,22 +1328,22 @@ class FormsTestCase(TestCase): self.assertHTMLEqual(str(p['birthday']), '') self.assertEqual(p.errors, {}) self.assertTrue(p.is_valid()) - self.assertEqual(p.cleaned_data['first_name'], u'John') - self.assertEqual(p.cleaned_data['last_name'], u'Lennon') + self.assertEqual(p.cleaned_data['first_name'], 'John') + self.assertEqual(p.cleaned_data['last_name'], 'Lennon') self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9)) # Let's try submitting some bad data to make sure form.errors and field.errors # work as expected. data = { - 'person1-first_name': u'', - 'person1-last_name': u'', - 'person1-birthday': u'' + 'person1-first_name': '', + 'person1-last_name': '', + 'person1-birthday': '' } p = Person(data, prefix='person1') - self.assertEqual(p.errors['first_name'], [u'This field is required.']) - self.assertEqual(p.errors['last_name'], [u'This field is required.']) - self.assertEqual(p.errors['birthday'], [u'This field is required.']) - self.assertEqual(p['first_name'].errors, [u'This field is required.']) + self.assertEqual(p.errors['first_name'], ['This field is required.']) + self.assertEqual(p.errors['last_name'], ['This field is required.']) + self.assertEqual(p.errors['birthday'], ['This field is required.']) + self.assertEqual(p['first_name'].errors, ['This field is required.']) try: p['person1-first_name'].errors self.fail('Attempts to access non-existent fields should fail.') @@ -1350,34 +1353,34 @@ class FormsTestCase(TestCase): # In this example, the data doesn't have a prefix, but the form requires it, so # the form doesn't "see" the fields. data = { - 'first_name': u'John', - 'last_name': u'Lennon', - 'birthday': u'1940-10-9' + 'first_name': 'John', + 'last_name': 'Lennon', + 'birthday': '1940-10-9' } p = Person(data, prefix='person1') - self.assertEqual(p.errors['first_name'], [u'This field is required.']) - self.assertEqual(p.errors['last_name'], [u'This field is required.']) - self.assertEqual(p.errors['birthday'], [u'This field is required.']) + self.assertEqual(p.errors['first_name'], ['This field is required.']) + self.assertEqual(p.errors['last_name'], ['This field is required.']) + self.assertEqual(p.errors['birthday'], ['This field is required.']) # With prefixes, a single data dictionary can hold data for multiple instances # of the same form. data = { - 'person1-first_name': u'John', - 'person1-last_name': u'Lennon', - 'person1-birthday': u'1940-10-9', - 'person2-first_name': u'Jim', - 'person2-last_name': u'Morrison', - 'person2-birthday': u'1943-12-8' + 'person1-first_name': 'John', + 'person1-last_name': 'Lennon', + 'person1-birthday': '1940-10-9', + 'person2-first_name': 'Jim', + 'person2-last_name': 'Morrison', + 'person2-birthday': '1943-12-8' } p1 = Person(data, prefix='person1') self.assertTrue(p1.is_valid()) - self.assertEqual(p1.cleaned_data['first_name'], u'John') - self.assertEqual(p1.cleaned_data['last_name'], u'Lennon') + self.assertEqual(p1.cleaned_data['first_name'], 'John') + self.assertEqual(p1.cleaned_data['last_name'], 'Lennon') self.assertEqual(p1.cleaned_data['birthday'], datetime.date(1940, 10, 9)) p2 = Person(data, prefix='person2') self.assertTrue(p2.is_valid()) - self.assertEqual(p2.cleaned_data['first_name'], u'Jim') - self.assertEqual(p2.cleaned_data['last_name'], u'Morrison') + self.assertEqual(p2.cleaned_data['first_name'], 'Jim') + self.assertEqual(p2.cleaned_data['last_name'], 'Morrison') self.assertEqual(p2.cleaned_data['birthday'], datetime.date(1943, 12, 8)) # By default, forms append a hyphen between the prefix and the field name, but a @@ -1397,14 +1400,14 @@ class FormsTestCase(TestCase):
      • """) data = { - 'foo-prefix-first_name': u'John', - 'foo-prefix-last_name': u'Lennon', - 'foo-prefix-birthday': u'1940-10-9' + 'foo-prefix-first_name': 'John', + 'foo-prefix-last_name': 'Lennon', + 'foo-prefix-birthday': '1940-10-9' } p = Person(data, prefix='foo') self.assertTrue(p.is_valid()) - self.assertEqual(p.cleaned_data['first_name'], u'John') - self.assertEqual(p.cleaned_data['last_name'], u'Lennon') + self.assertEqual(p.cleaned_data['first_name'], 'John') + self.assertEqual(p.cleaned_data['last_name'], 'Lennon') self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9)) def test_forms_with_null_boolean(self): @@ -1414,37 +1417,37 @@ class FormsTestCase(TestCase): name = CharField() is_cool = NullBooleanField() - p = Person({'name': u'Joe'}, auto_id=False) + p = Person({'name': 'Joe'}, auto_id=False) self.assertHTMLEqual(str(p['is_cool']), """""") - p = Person({'name': u'Joe', 'is_cool': u'1'}, auto_id=False) + p = Person({'name': 'Joe', 'is_cool': '1'}, auto_id=False) self.assertHTMLEqual(str(p['is_cool']), """""") - p = Person({'name': u'Joe', 'is_cool': u'2'}, auto_id=False) + p = Person({'name': 'Joe', 'is_cool': '2'}, auto_id=False) self.assertHTMLEqual(str(p['is_cool']), """""") - p = Person({'name': u'Joe', 'is_cool': u'3'}, auto_id=False) + p = Person({'name': 'Joe', 'is_cool': '3'}, auto_id=False) self.assertHTMLEqual(str(p['is_cool']), """""") - p = Person({'name': u'Joe', 'is_cool': True}, auto_id=False) + p = Person({'name': 'Joe', 'is_cool': True}, auto_id=False) self.assertHTMLEqual(str(p['is_cool']), """""") - p = Person({'name': u'Joe', 'is_cool': False}, auto_id=False) + p = Person({'name': 'Joe', 'is_cool': False}, auto_id=False) self.assertHTMLEqual(str(p['is_cool']), """') self.assertTrue(f.is_valid()) - f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', u'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False) + f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False) self.assertHTMLEqual(f.as_table(), 'File1:') def test_basic_processing_in_view(self): @@ -1484,7 +1487,7 @@ class FormsTestCase(TestCase): def clean(self): if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: - raise ValidationError(u'Please make sure your passwords match.') + raise ValidationError('Please make sure your passwords match.') return self.cleaned_data @@ -1520,7 +1523,7 @@ class FormsTestCase(TestCase): """) # Case 3: POST with valid data (the success message).) - self.assertHTMLEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}), "VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'}") + self.assertHTMLEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}), str_prefix("VALID: {'username': %(_)s'adrian', 'password1': %(_)s'secret', 'password2': %(_)s'secret'}")) def test_templates_with_forms(self): class UserRegistration(Form): @@ -1530,7 +1533,7 @@ class FormsTestCase(TestCase): def clean(self): if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: - raise ValidationError(u'Please make sure your passwords match.') + raise ValidationError('Please make sure your passwords match.') return self.cleaned_data @@ -1612,7 +1615,7 @@ class FormsTestCase(TestCase):

        Password2:

        """) - self.assertEqual(Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})), u'') + self.assertEqual(Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})), '') # The label_tag() method takes an optional attrs argument: a dictionary of HTML # attributes to add to the

      ', + flatatt(self.attrs), + format_html_join('\n', '
    • {0}
    • ', + ((force_unicode(w),) for w in self))) class AdminRadioSelect(forms.RadioSelect): renderer = AdminRadioFieldRenderer diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 780b0c015e..bce8747661 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,6 +1,7 @@ from django import forms from django.forms.util import flatatt from django.template import loader +from django.utils.html import format_html, format_html_join from django.utils.http import int_to_base36 from django.utils.safestring import mark_safe from django.utils.translation import ugettext, ugettext_lazy as _ @@ -28,13 +29,15 @@ class ReadOnlyPasswordHashWidget(forms.Widget): try: hasher = identify_hasher(encoded) except ValueError: - summary = "Invalid password format or unknown hashing algorithm." + summary = mark_safe("Invalid password format or unknown hashing algorithm.") else: - summary = "" - for key, value in hasher.safe_summary(encoded).iteritems(): - summary += "%(key)s: %(value)s " % {"key": ugettext(key), "value": value} + summary = format_html_join('', + "{0}: {1} ", + ((ugettext(key), value) + for key, value in hasher.safe_summary(encoded).items()) + ) - return mark_safe("%(summary)s" % {"attrs": flatatt(final_attrs), "summary": summary}) + return format_html("{1}", flatatt(final_attrs), summary) class ReadOnlyPasswordHashField(forms.Field): diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 587c752a94..c842498934 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -5,6 +5,7 @@ from django.db import models from django.contrib.databrowse.datastructures import EasyModel from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response +from django.utils.html import format_html, format_html_join from django.utils.text import capfirst from django.utils.encoding import force_unicode from django.utils.safestring import mark_safe @@ -64,8 +65,9 @@ class CalendarPlugin(DatabrowsePlugin): fields = self.field_dict(model) if not fields: return '' - return mark_safe('

      View calendar by: %s

      ' % \ - ', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) + return format_html('

      View calendar by: {0}

      ', + format_html_join(', ', '{1}', + ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if isinstance(easy_instance_field.field, models.DateField): diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index 5a13252ab3..f3bd829e61 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -5,6 +5,7 @@ from django.db import models from django.contrib.databrowse.datastructures import EasyModel from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response +from django.utils.html import format_html, format_html_join from django.utils.text import capfirst from django.utils.encoding import smart_str, force_unicode from django.utils.safestring import mark_safe @@ -32,8 +33,9 @@ class FieldChoicePlugin(DatabrowsePlugin): fields = self.field_dict(model) if not fields: return '' - return mark_safe('

      View by: %s

      ' % \ - ', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) + return format_html('

      View by: {0}

      ', + format_html_join(', ', '{1}', + ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): diff --git a/django/contrib/gis/maps/google/gmap.py b/django/contrib/gis/maps/google/gmap.py index f0e8d73399..49515c0dce 100644 --- a/django/contrib/gis/maps/google/gmap.py +++ b/django/contrib/gis/maps/google/gmap.py @@ -1,5 +1,6 @@ from django.conf import settings from django.template.loader import render_to_string +from django.utils.html import format_html from django.utils.safestring import mark_safe from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline, GMarker @@ -111,17 +112,18 @@ class GoogleMap(object): @property def body(self): "Returns HTML body tag for loading and unloading Google Maps javascript." - return mark_safe('' % (self.onload, self.onunload)) + return format_html('', self.onload, self.onunload) @property def onload(self): "Returns the `onload` HTML attribute." - return mark_safe('onload="%s.%s_load()"' % (self.js_module, self.dom_id)) + return format_html('onload="{0}.{1}_load()"', self.js_module, self.dom_id) @property def api_script(self): "Returns the ' % (self.api_url, self.key)) + return format_html('', + self.api_url, self.key) @property def js(self): @@ -131,17 +133,17 @@ class GoogleMap(object): @property def scripts(self): "Returns all tags required with Google Maps JavaScript." - return mark_safe('%s\n ' % (self.api_script, self.js)) + return format_html('%s\n ', self.api_script, mark_safe(self.js)) @property def style(self): "Returns additional CSS styling needed for Google Maps on IE." - return mark_safe('' % self.vml_css) + return format_html('', self.vml_css) @property def xhtml(self): "Returns XHTML information needed for IE VML overlays." - return mark_safe('' % self.xmlns) + return format_html('', self.xmlns) @property def icons(self): diff --git a/django/forms/forms.py b/django/forms/forms.py index 22c3057c62..880873a273 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -11,7 +11,7 @@ from django.forms.fields import Field, FileField 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 +from django.utils.html import conditional_escape, format_html from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode from django.utils.safestring import mark_safe @@ -167,7 +167,7 @@ class BaseForm(StrAndUnicode): # punctuation. if self.label_suffix: if label[-1] not in ':?.!': - label += self.label_suffix + label = format_html('{}{}', label, self.label_suffix) label = bf.label_tag(label) or '' else: label = '' @@ -498,8 +498,8 @@ class BoundField(StrAndUnicode): def label_tag(self, contents=None, attrs=None): """ Wraps the given contents in a ', label_for, self.tag(), choice_label) def is_checked(self): return self.value == self.choice_value @@ -677,7 +679,7 @@ class RadioInput(SubWidget): final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' - return mark_safe('' % flatatt(final_attrs)) + return format_html('', flatatt(final_attrs)) class RadioFieldRenderer(StrAndUnicode): """ @@ -701,8 +703,10 @@ class RadioFieldRenderer(StrAndUnicode): def render(self): """Outputs a
        for this set of radio fields.""" - return mark_safe('
          \n%s\n
        ' % '\n'.join(['
      • %s
      • ' - % force_unicode(w) for w in self])) + return format_html('
          \n{0}\n
        ', + format_html_join('\n', '
      • {0}
      • ', + [(force_unicode(w),) for w in self] + )) class RadioSelect(Select): renderer = RadioFieldRenderer @@ -751,15 +755,16 @@ class CheckboxSelectMultiple(SelectMultiple): # so that the checkboxes don't all have the same ID attribute. if has_id: final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i)) - label_for = ' for="%s"' % final_attrs['id'] + label_for = format_html(' for="{0}"', final_attrs['id']) else: label_for = '' cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values) option_value = force_unicode(option_value) rendered_cb = cb.render(name, option_value) - option_label = conditional_escape(force_unicode(option_label)) - output.append('
      • %s %s
      • ' % (label_for, rendered_cb, option_label)) + option_label = force_unicode(option_label) + output.append(format_html('
      • {1} {2}
      • ', + label_for, rendered_cb, option_label)) output.append('
      ') return mark_safe('\n'.join(output)) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 83b72e120b..d27439b3b8 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -16,6 +16,7 @@ from django.template.smartif import IfParser, Literal from django.template.defaultfilters import date from django.utils.encoding import smart_unicode from django.utils.safestring import mark_safe +from django.utils.html import format_html from django.utils import timezone register = Library() @@ -44,9 +45,9 @@ class CsrfTokenNode(Node): csrf_token = context.get('csrf_token', None) if csrf_token: if csrf_token == 'NOTPROVIDED': - return mark_safe("") + return format_html("") else: - return mark_safe("
      " % csrf_token) + return format_html("
      ", csrf_token) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning From b0eee0ba4bcc15c767a64794caff9d233f12a5d5 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 3 Jul 2012 00:33:44 +0100 Subject: [PATCH 141/519] Removed various unnecessary instances of mark_safe applied to URLs Also fixed some test breakages introduced in last commit --- django/contrib/admin/models.py | 3 +-- django/contrib/admin/options.py | 2 +- .../templates/admin_doc/bookmarklets.html | 2 +- django/contrib/admindocs/views.py | 2 +- django/contrib/auth/admin.py | 2 +- django/contrib/databrowse/datastructures.py | 15 +++++++-------- django/contrib/databrowse/plugins/calendars.py | 5 ++--- django/contrib/databrowse/plugins/fieldchoices.py | 5 ++--- django/contrib/gis/maps/google/gmap.py | 4 ++-- tests/regressiontests/admin_views/tests.py | 8 ++++---- 10 files changed, 22 insertions(+), 26 deletions(-) diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 93d8f307c0..58bbbabfdf 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -6,7 +6,6 @@ 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_unicode -from django.utils.safestring import mark_safe ADDITION = 1 CHANGE = 2 @@ -66,5 +65,5 @@ class LogEntry(models.Model): This is relative to the Django admin index page. """ if self.content_type and self.object_id: - return mark_safe("%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))) + return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id)) return None diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 665e3f76b8..4d23f8f384 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -745,7 +745,7 @@ class ModelAdmin(BaseModelAdmin): 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, 'has_absolute_url': hasattr(self.model, 'get_absolute_url'), 'ordered_objects': ordered_objects, - 'form_url': mark_safe(form_url), + 'form_url': form_url, 'opts': opts, 'content_type_id': ContentType.objects.get_for_model(self.model).id, 'save_as': self.save_as, diff --git a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html index cde285481d..819beea326 100644 --- a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html +++ b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html @@ -22,7 +22,7 @@ your computer is "internal").

      {% endblocktrans %}
      -

      {% trans "Documentation for this page" %}

      +

      {% trans "Documentation for this page" %}

      {% trans "Jumps you from any page to the documentation for the view that generates that page." %}

      {% trans "Show object ID" %}

      diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index aad698836e..5649398cc8 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -37,7 +37,7 @@ def bookmarklets(request): admin_root = urlresolvers.reverse('admin:index') return render_to_response('admin_doc/bookmarklets.html', { 'root_path': admin_root, - 'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)), + 'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root), }, context_instance=RequestContext(request)) @staff_member_required diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index f14b3d219b..ad61904041 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -134,7 +134,7 @@ class UserAdmin(admin.ModelAdmin): context = { 'title': _('Change password: %s') % escape(user.username), 'adminForm': adminForm, - 'form_url': mark_safe(form_url), + 'form_url': form_url, 'form': form, 'is_popup': '_popup' in request.REQUEST, 'add': True, diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 6a78b3688b..687aa87f03 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -8,7 +8,6 @@ from django.db import models from django.utils import formats from django.utils.text import capfirst from django.utils.encoding import smart_unicode, smart_str, iri_to_uri -from django.utils.safestring import mark_safe from django.db.models.query import QuerySet EMPTY_VALUE = '(None)' @@ -30,7 +29,7 @@ class EasyModel(object): return self.site.registry[self.model] def url(self): - return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)) + return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name) def objects(self, **kwargs): return self.get_query_set().filter(**kwargs) @@ -70,9 +69,9 @@ class EasyField(object): def url(self): if self.field.choices: - return mark_safe('%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name)) + return '%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name) elif self.field.rel: - return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)) + return '%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name) class EasyChoice(object): def __init__(self, easy_model, field, value, label): @@ -83,7 +82,7 @@ class EasyChoice(object): return smart_str('' % (self.model.model._meta.object_name, self.field.name)) def url(self): - return mark_safe('%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))) + 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)) class EasyInstance(object): def __init__(self, easy_model, instance): @@ -105,7 +104,7 @@ class EasyInstance(object): return self.instance._get_pk_val() def url(self): - return mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk()))) + return '%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk())) def fields(self): """ @@ -187,14 +186,14 @@ class EasyInstanceField(object): for value in self.values(): if value is None: continue - url = mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))) + url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())) lst.append((smart_unicode(value), url)) else: lst = [(value, None) for value in self.values()] elif self.field.choices: lst = [] for value in self.values(): - url = mark_safe('%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))) + url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)) lst.append((value, url)) elif isinstance(self.field, models.URLField): val = self.values()[0] diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index c842498934..7bdd1e0032 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -8,7 +8,6 @@ from django.shortcuts import render_to_response from django.utils.html import format_html, format_html_join from django.utils.text import capfirst from django.utils.encoding import force_unicode -from django.utils.safestring import mark_safe from django.views.generic import dates from django.utils import datetime_safe @@ -72,12 +71,12 @@ class CalendarPlugin(DatabrowsePlugin): def urls(self, plugin_name, easy_instance_field): if isinstance(easy_instance_field.field, models.DateField): d = easy_instance_field.raw_value - return [mark_safe('%s%s/%s/%s/%s/%s/' % ( + return ['%s%s/%s/%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, str(d.year), datetime_safe.new_date(d).strftime('%b').lower(), - d.day))] + d.day)] def model_view(self, request, model_databrowse, url): self.model, self.site = model_databrowse.model, model_databrowse.site diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index f3bd829e61..4b1f0e6614 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -8,7 +8,6 @@ from django.shortcuts import render_to_response from django.utils.html import format_html, format_html_join from django.utils.text import capfirst from django.utils.encoding import smart_str, force_unicode -from django.utils.safestring import mark_safe import urllib class FieldChoicePlugin(DatabrowsePlugin): @@ -40,10 +39,10 @@ class FieldChoicePlugin(DatabrowsePlugin): def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): field_value = smart_str(easy_instance_field.raw_value) - return [mark_safe('%s%s/%s/%s/' % ( + return ['%s%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, - urllib.quote(field_value, safe='')))] + urllib.quote(field_value, safe=''))] def model_view(self, request, model_databrowse, url): self.model, self.site = model_databrowse.model, model_databrowse.site diff --git a/django/contrib/gis/maps/google/gmap.py b/django/contrib/gis/maps/google/gmap.py index 49515c0dce..72c50eab0f 100644 --- a/django/contrib/gis/maps/google/gmap.py +++ b/django/contrib/gis/maps/google/gmap.py @@ -11,7 +11,7 @@ class GoogleMapException(Exception): # The default Google Maps URL (for the API javascript) # TODO: Internationalize for Japan, UK, etc. -GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&v=%s&key=' +GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&v=%s&key=' class GoogleMap(object): @@ -49,7 +49,7 @@ class GoogleMap(object): # Can specify the API URL in the `api_url` keyword. if not api_url: - self.api_url = mark_safe(getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version) + self.api_url = getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version else: self.api_url = api_url diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 937b086d40..49ec3c1945 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -1369,19 +1369,19 @@ class AdminViewStringPrimaryKeyTest(TestCase): def test_changelist_to_changeform_link(self): "The link from the changelist referring to the changeform of the object should be quoted" response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/') - should_contain = """%s""" % (quote(self.pk), escape(self.pk)) + should_contain = """%s""" % (escape(quote(self.pk)), escape(self.pk)) self.assertContains(response, should_contain) def test_recentactions_link(self): "The link from the recent actions list referring to the changeform of the object should be quoted" response = self.client.get('/test_admin/admin/') - should_contain = """%s""" % (quote(self.pk), escape(self.pk)) + should_contain = """%s""" % (escape(quote(self.pk)), escape(self.pk)) self.assertContains(response, should_contain) def test_recentactions_without_content_type(self): "If a LogEntry is missing content_type it will not display it in span tag under the hyperlink." response = self.client.get('/test_admin/admin/') - should_contain = """%s""" % (quote(self.pk), escape(self.pk)) + should_contain = """%s""" % (escape(quote(self.pk)), escape(self.pk)) self.assertContains(response, should_contain) should_contain = "Model with string primary key" # capitalized in Recent Actions self.assertContains(response, should_contain) @@ -1402,7 +1402,7 @@ class AdminViewStringPrimaryKeyTest(TestCase): "The link from the delete confirmation page referring back to the changeform of the object should be quoted" response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk)) # this URL now comes through reverse(), thus iri_to_uri encoding - should_contain = """/%s/">%s""" % (iri_to_uri(quote(self.pk)), escape(self.pk)) + should_contain = """/%s/">%s""" % (escape(iri_to_uri(quote(self.pk))), escape(self.pk)) self.assertContains(response, should_contain) def test_url_conflicts_with_add(self): From a222d6e80029de772a291e18460d96dcfbd23570 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 3 Jul 2012 00:42:16 +0100 Subject: [PATCH 142/519] Fixed incorrect URL to object on delete confirmation and history page --- .../admin/templates/admin/delete_confirmation.html | 2 +- django/contrib/admin/templates/admin/object_history.html | 2 +- django/contrib/admin/templatetags/admin_urls.py | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index 71d3eb9f0f..c1a711534d 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -7,7 +7,7 @@ {% trans 'Home' %}{{ app_label|capfirst }}{{ opts.verbose_name_plural|capfirst|escape }} -› {{ object|truncatewords:"18" }} +› {{ object|truncatewords:"18" }} › {% trans 'Delete' %}
      {% endblock %} diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html index c8169a6c3b..55dd4a3b4c 100644 --- a/django/contrib/admin/templates/admin/object_history.html +++ b/django/contrib/admin/templates/admin/object_history.html @@ -7,7 +7,7 @@ {% trans 'Home' %}{{ app_label|capfirst|escape }}{{ module_name }} -› {{ object|truncatewords:"18" }} +› {{ object|truncatewords:"18" }} › {% trans 'History' %} {% endblock %} diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py index 53dc65b567..90e81b0ef3 100644 --- a/django/contrib/admin/templatetags/admin_urls.py +++ b/django/contrib/admin/templatetags/admin_urls.py @@ -1,8 +1,14 @@ -from django.core.urlresolvers import reverse, NoReverseMatch +from django.core.urlresolvers import reverse from django import template +from django.contrib.admin.util import quote register = template.Library() @register.filter def admin_urlname(value, arg): return 'admin:%s_%s_%s' % (value.app_label, value.module_name, arg) + + +@register.filter +def admin_urlquote(value): + return quote(value) From 3caf53524c5285f1a057e00fc6549cde0ca67dda Mon Sep 17 00:00:00 2001 From: Renato Pedigoni Date: Wed, 4 Jul 2012 18:34:55 -0300 Subject: [PATCH 143/519] fixed typo --- docs/releases/1.5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 4544be0eac..944f19f03b 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -70,7 +70,7 @@ To make it easier to deal with javascript templates which collide with Django's syntax, you can now use the :ttag:`verbatim` block tag to avoid parsing the tag's content. -Retreival of ``ContentType`` instances associated with proxy models +Retrieval of ``ContentType`` instances associated with proxy models ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The methods :meth:`ContentTypeManager.get_for_model() ` From e4a1407a9cf35e6449136c22647a58cb14451e7a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 5 Jul 2012 08:39:05 -0400 Subject: [PATCH 144/519] Fixed #17997 - Documented that the debug server is now multithreaded by default. Thanks trey.smith@ for the report and vanessagomes for the patch. --- docs/ref/django-admin.txt | 1 + docs/releases/1.4.txt | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 8fea699c1b..7ca1ee5ddd 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -668,6 +668,7 @@ Example usage:: .. versionadded:: 1.4 +Since version 1.4, the development server is multithreaded by default. Use the ``--nothreading`` option to disable the use of threading in the development server. diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 51766dc2f5..a091869645 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -1145,6 +1145,15 @@ field. This was something that should not have worked, and in 1.4 loading such incomplete fixtures will fail. Because fixtures are a raw import, they should explicitly specify all field values, regardless of field options on the model. +Development Server Multithreading +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The development server is now is multithreaded by default. Use the +:djadminopt:`--nothreading` option to disable the use of threading in the +development server:: + + django-admin.py runserver --nothreading + Attributes disabled in markdown when safe mode set ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1338,4 +1347,3 @@ Versions of Python-Markdown earlier than 2.1 do not support the option to disable attributes. As a security issue, earlier versions of this library will not be supported by the markup contrib app in 1.5 under an accelerated deprecation timeline. - From 0f49b2bce2d5b3c5891c8a329bab7dffe16fc79b Mon Sep 17 00:00:00 2001 From: Andrei Antoukh Date: Thu, 5 Jul 2012 16:39:19 +0300 Subject: [PATCH 145/519] Fixed #18362 - Made model.save() update_fields accept attnames --- django/db/models/base.py | 13 ++++++++++--- tests/modeltests/update_only_fields/tests.py | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index b2d92a2aee..82283d591c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -468,8 +468,15 @@ class Model(object): return update_fields = frozenset(update_fields) - field_names = set([field.name for field in self._meta.fields - if not field.primary_key]) + field_names = set() + + for field in self._meta.fields: + if not field.primary_key: + field_names.add(field.name) + + if field.name != field.attname: + field_names.add(field.attname) + non_model_fields = update_fields.difference(field_names) if non_model_fields: @@ -534,7 +541,7 @@ class Model(object): non_pks = [f for f in meta.local_fields if not f.primary_key] if update_fields: - non_pks = [f for f in non_pks if f.name in update_fields] + non_pks = [f for f in non_pks if f.name in update_fields or f.attname in update_fields] # First, try an UPDATE. If that doesn't update anything, do an INSERT. pk_val = self._get_pk_val(meta) diff --git a/tests/modeltests/update_only_fields/tests.py b/tests/modeltests/update_only_fields/tests.py index e843bd7ab9..bce53ca621 100644 --- a/tests/modeltests/update_only_fields/tests.py +++ b/tests/modeltests/update_only_fields/tests.py @@ -55,6 +55,14 @@ class UpdateOnlyFieldsTests(TestCase): self.assertEqual(e3.name, 'Ian') self.assertEqual(e3.profile, profile_receptionist) + with self.assertNumQueries(1): + e3.profile = profile_boss + e3.save(update_fields=['profile_id']) + + e4 = Employee.objects.get(pk=e3.pk) + self.assertEqual(e4.profile, profile_boss) + self.assertEqual(e4.profile_id, profile_boss.pk) + def test_update_fields_inheritance_with_proxy_model(self): profile_boss = Profile.objects.create(name='Boss', salary=3000) profile_receptionist = Profile.objects.create(name='Receptionist', salary=1000) From d3c2eb103f6682c029a850e60dc4cf85896b6aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Thu, 5 Jul 2012 17:20:48 +0300 Subject: [PATCH 146/519] Fixed #18330 - Made cache culling 3rd party db backend friendly This is Ian Kelly's patch from #15580 with minor modifications. --- django/core/cache/backends/db.py | 14 +++----------- django/db/backends/__init__.py | 10 ++++++++++ django/db/backends/oracle/base.py | 7 +++++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 62ea5c420b..1ac6ef5d9b 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -167,17 +167,9 @@ class DatabaseCache(BaseDatabaseCache): num = cursor.fetchone()[0] if num > self._max_entries: cull_num = num / self._cull_frequency - if connections[db].vendor == 'oracle': - # Oracle doesn't support LIMIT + OFFSET - cursor.execute("""SELECT cache_key FROM -(SELECT ROW_NUMBER() OVER (ORDER BY cache_key) AS counter, cache_key FROM %s) -WHERE counter > %%s AND COUNTER <= %%s""" % table, [cull_num, cull_num + 1]) - else: - # This isn't standard SQL, it's likely to break - # with some non officially supported databases - cursor.execute("SELECT cache_key FROM %s " - "ORDER BY cache_key " - "LIMIT 1 OFFSET %%s" % table, [cull_num]) + cursor.execute( + connections[db].ops.cache_key_culling_sql() % table, + [cull_num]) cursor.execute("DELETE FROM %s " "WHERE cache_key < %%s" % table, [cursor.fetchone()[0]]) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 05ef7bf62a..2da1f2be0f 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -475,6 +475,16 @@ class BaseDatabaseOperations(object): """ return None + def cache_key_culling_sql(self): + """ + Returns a SQL query that retrieves the first cache key greater than the + n smallest. + + This is used by the 'db' cache backend to determine where to start + culling. + """ + return "SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" + def date_extract_sql(self, lookup_type, field_name): """ Given a lookup_type of 'year', 'month' or 'day', returns the SQL that diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index b90f6ea155..49b628075b 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -118,6 +118,13 @@ WHEN (new.%(col_name)s IS NULL) /""" % locals() return sequence_sql, trigger_sql + def cache_key_culling_sql(self): + return """ + SELECT cache_key + FROM (SELECT cache_key, rank() OVER (ORDER BY cache_key) AS rank FROM %s) + WHERE rank = %%s + 1 + """ + def date_extract_sql(self, lookup_type, field_name): # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163 if lookup_type == 'week_day': From 8fdc56d2a6f7537cdd52272501af9e94cab96ed4 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 5 Jul 2012 23:23:03 +0100 Subject: [PATCH 147/519] Fixed #18572 - Python26 string format incompatibility Thanks to anonymous/AeroNotix for the report --- django/contrib/admin/templatetags/admin_list.py | 4 ++-- django/forms/forms.py | 2 +- django/forms/util.py | 8 ++++---- django/forms/widgets.py | 12 ++++++------ django/template/defaulttags.py | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 29b5d71a16..0f15781fa9 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -31,7 +31,7 @@ def paginator_number(cl,i): if i == DOT: return '... ' elif i == cl.page_num: - return format_html('{} ', i+1) + return format_html('{0} ', i+1) else: return format_html('{2} ', cl.get_query_string({PAGE_VAR: i}), @@ -162,7 +162,7 @@ def result_headers(cl): "url_primary": cl.get_query_string({ORDER_VAR: '.'.join(o_list_primary)}), "url_remove": cl.get_query_string({ORDER_VAR: '.'.join(o_list_remove)}), "url_toggle": cl.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}), - "class_attrib": format_html(' class="{}"', ' '.join(th_classes)) + "class_attrib": format_html(' class="{0}"', ' '.join(th_classes)) if th_classes else '', } diff --git a/django/forms/forms.py b/django/forms/forms.py index 880873a273..7942275609 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -167,7 +167,7 @@ class BaseForm(StrAndUnicode): # punctuation. if self.label_suffix: if label[-1] not in ':?.!': - label = format_html('{}{}', label, self.label_suffix) + label = format_html('{0}{1}', label, self.label_suffix) label = bf.label_tag(label) or '' else: label = '' diff --git a/django/forms/util.py b/django/forms/util.py index 91a5686886..8cf03d38af 100644 --- a/django/forms/util.py +++ b/django/forms/util.py @@ -20,7 +20,7 @@ def flatatt(attrs): The result is passed through 'mark_safe'. """ - return format_html_join('', ' {}="{}"', attrs.items()) + return format_html_join('', ' {0}="{1}"', attrs.items()) class ErrorDict(dict, StrAndUnicode): """ @@ -33,7 +33,7 @@ class ErrorDict(dict, StrAndUnicode): def as_ul(self): if not self: return '' - return format_html('
        {}
      ', + return format_html('
        {0}
      ', format_html_join('', '
    • {0}{1}
    • ', ((k, force_unicode(v)) for k, v in self.items()) @@ -51,8 +51,8 @@ class ErrorList(list, StrAndUnicode): def as_ul(self): if not self: return '' - return format_html('
        {}
      ', - format_html_join('', '
    • {}
    • ', + return format_html('
        {0}
      ', + format_html_join('', '
    • {0}
    • ', ((force_unicode(e),) for e in self) ) ) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 65caa68db2..04a838093c 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -254,7 +254,7 @@ class Input(Widget): if value != '': # Only add the 'value' attribute if a value is non-empty. final_attrs['value'] = force_unicode(self._format_value(value)) - return format_html('', flatatt(final_attrs)) + return format_html('', flatatt(final_attrs)) class TextInput(Input): input_type = 'text' @@ -295,7 +295,7 @@ class MultipleHiddenInput(HiddenInput): # An ID attribute was given. Add a numeric index as a suffix # so that the inputs don't all have the same ID attribute. input_attrs['id'] = '%s_%s' % (id_, i) - inputs.append(format_html('', flatatt(input_attrs))) + inputs.append(format_html('', flatatt(input_attrs))) return mark_safe('\n'.join(inputs)) def value_from_datadict(self, data, files, name): @@ -512,7 +512,7 @@ class CheckboxInput(Widget): if not (value is True or value is False or value is None or value == ''): # Only add the 'value' attribute if a value is non-empty. final_attrs['value'] = force_unicode(value) - return format_html('', flatatt(final_attrs)) + return format_html('', flatatt(final_attrs)) def value_from_datadict(self, data, files, name): if name not in data: @@ -544,7 +544,7 @@ class Select(Widget): def render(self, name, value, attrs=None, choices=()): if value is None: value = '' final_attrs = self.build_attrs(attrs, name=name) - output = [format_html('', flatatt(final_attrs))] + output = [format_html('', flatatt(final_attrs))] options = self.render_options(choices, [value]) if options: output.append(options) @@ -620,7 +620,7 @@ class SelectMultiple(Select): def render(self, name, value, attrs=None, choices=()): if value is None: value = [] final_attrs = self.build_attrs(attrs, name=name) - output = [format_html('', flatatt(final_attrs))] options = self.render_options(choices, value) if options: output.append(options) @@ -679,7 +679,7 @@ class RadioInput(SubWidget): final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' - return format_html('', flatatt(final_attrs)) + return format_html('', flatatt(final_attrs)) class RadioFieldRenderer(StrAndUnicode): """ diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index d27439b3b8..52d886a5a1 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -47,7 +47,7 @@ class CsrfTokenNode(Node): if csrf_token == 'NOTPROVIDED': return format_html("") else: - return format_html("
      ", csrf_token) + return format_html("
      ", csrf_token) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning From 83da36ebfbdad2ac6e714b5308c076a1fb64b0be Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Fri, 6 Jul 2012 10:10:27 +0100 Subject: [PATCH 148/519] restored a missing \ in uwsgi docs --- docs/howto/deployment/wsgi/uwsgi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt index 3ac2203544..b5d438450e 100644 --- a/docs/howto/deployment/wsgi/uwsgi.txt +++ b/docs/howto/deployment/wsgi/uwsgi.txt @@ -46,7 +46,7 @@ uWSGI supports multiple ways to configure the process. See uWSGI's Here's an example command to start a uWSGI server:: - uwsgi --chdir=/path/to/your/project + uwsgi --chdir=/path/to/your/project \ --module=mysite.wsgi:application \ --env DJANGO_SETTINGS_MODULE=mysite.settings \ --master --pidfile=/tmp/project-master.pid \ From 86eb606b88b3979de8074c3a049535150a0cc8a5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 6 Jul 2012 11:15:20 +0200 Subject: [PATCH 149/519] Used skipIf decorator to skip image tests when PIL is not available --- .../model_fields/imagefield.py | 738 +++++++++--------- tests/regressiontests/model_fields/tests.py | 10 +- 2 files changed, 379 insertions(+), 369 deletions(-) diff --git a/tests/regressiontests/model_fields/imagefield.py b/tests/regressiontests/model_fields/imagefield.py index 09c1bd76d3..7446f222ff 100644 --- a/tests/regressiontests/model_fields/imagefield.py +++ b/tests/regressiontests/model_fields/imagefield.py @@ -6,416 +6,428 @@ import shutil from django.core.files import File from django.core.files.images import ImageFile from django.test import TestCase +from django.utils.unittest import skipIf -from .models import (Image, Person, PersonWithHeight, PersonWithHeightAndWidth, - PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile) +from .models import Image - -# If PIL available, do these tests. if Image: - + from .models import (Person, PersonWithHeight, PersonWithHeightAndWidth, + PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile) from .models import temp_storage_dir +else: + # PIL not available, create dummy classes (tests will be skipped anyway) + class Person(): + pass + PersonWithHeight = PersonWithHeightAndWidth = PersonDimensionsFirst = Person + PersonTwoImages = Person - class ImageFieldTestMixin(object): +class ImageFieldTestMixin(object): + """ + Mixin class to provide common functionality to ImageField test classes. + """ + + # Person model to use for tests. + PersonModel = PersonWithHeightAndWidth + # File class to use for file instances. + File = ImageFile + + def setUp(self): """ - Mixin class to provide common functionality to ImageField test classes. + Creates a pristine temp directory (or deletes and recreates if it + already exists) that the model uses as its storage directory. + + Sets up two ImageFile instances for use in tests. """ - - # Person model to use for tests. - PersonModel = PersonWithHeightAndWidth - # File class to use for file instances. - File = ImageFile - - def setUp(self): - """ - Creates a pristine temp directory (or deletes and recreates if it - already exists) that the model uses as its storage directory. - - Sets up two ImageFile instances for use in tests. - """ - if os.path.exists(temp_storage_dir): - shutil.rmtree(temp_storage_dir) - os.mkdir(temp_storage_dir) - - file_path1 = os.path.join(os.path.dirname(__file__), "4x8.png") - self.file1 = self.File(open(file_path1, 'rb')) - - file_path2 = os.path.join(os.path.dirname(__file__), "8x4.png") - self.file2 = self.File(open(file_path2, 'rb')) - - def tearDown(self): - """ - Removes temp directory and all its contents. - """ + if os.path.exists(temp_storage_dir): shutil.rmtree(temp_storage_dir) + os.mkdir(temp_storage_dir) - def check_dimensions(self, instance, width, height, - field_name='mugshot'): - """ - Asserts that the given width and height values match both the - field's height and width attributes and the height and width fields - (if defined) the image field is caching to. + file_path1 = os.path.join(os.path.dirname(__file__), "4x8.png") + self.file1 = self.File(open(file_path1, 'rb')) - Note, this method will check for dimension fields named by adding - "_width" or "_height" to the name of the ImageField. So, the - models used in these tests must have their fields named - accordingly. + file_path2 = os.path.join(os.path.dirname(__file__), "8x4.png") + self.file2 = self.File(open(file_path2, 'rb')) - By default, we check the field named "mugshot", but this can be - specified by passing the field_name parameter. - """ - field = getattr(instance, field_name) - # Check height/width attributes of field. - if width is None and height is None: - self.assertRaises(ValueError, getattr, field, 'width') - self.assertRaises(ValueError, getattr, field, 'height') - else: - self.assertEqual(field.width, width) - self.assertEqual(field.height, height) - - # Check height/width fields of model, if defined. - width_field_name = field_name + '_width' - if hasattr(instance, width_field_name): - self.assertEqual(getattr(instance, width_field_name), width) - height_field_name = field_name + '_height' - if hasattr(instance, height_field_name): - self.assertEqual(getattr(instance, height_field_name), height) - - - class ImageFieldTests(ImageFieldTestMixin, TestCase): + def tearDown(self): """ - Tests for ImageField that don't need to be run with each of the - different test model classes. + Removes temp directory and all its contents. """ + shutil.rmtree(temp_storage_dir) - def test_equal_notequal_hash(self): - """ - Bug #9786: Ensure '==' and '!=' work correctly. - Bug #9508: make sure hash() works as expected (equal items must - hash to the same value). - """ - # Create two Persons with different mugshots. - p1 = self.PersonModel(name="Joe") - p1.mugshot.save("mug", self.file1) - p2 = self.PersonModel(name="Bob") - p2.mugshot.save("mug", self.file2) - self.assertEqual(p1.mugshot == p2.mugshot, False) - self.assertEqual(p1.mugshot != p2.mugshot, True) - - # Test again with an instance fetched from the db. - p1_db = self.PersonModel.objects.get(name="Joe") - self.assertEqual(p1_db.mugshot == p2.mugshot, False) - self.assertEqual(p1_db.mugshot != p2.mugshot, True) - - # Instance from db should match the local instance. - self.assertEqual(p1_db.mugshot == p1.mugshot, True) - self.assertEqual(hash(p1_db.mugshot), hash(p1.mugshot)) - self.assertEqual(p1_db.mugshot != p1.mugshot, False) - - def test_instantiate_missing(self): - """ - If the underlying file is unavailable, still create instantiate the - object without error. - """ - p = self.PersonModel(name="Joan") - p.mugshot.save("shot", self.file1) - p = self.PersonModel.objects.get(name="Joan") - path = p.mugshot.path - shutil.move(path, path + '.moved') - p2 = self.PersonModel.objects.get(name="Joan") - - def test_delete_when_missing(self): - """ - Bug #8175: correctly delete an object where the file no longer - exists on the file system. - """ - p = self.PersonModel(name="Fred") - p.mugshot.save("shot", self.file1) - os.remove(p.mugshot.path) - p.delete() - - def test_size_method(self): - """ - Bug #8534: FileField.size should not leave the file open. - """ - p = self.PersonModel(name="Joan") - p.mugshot.save("shot", self.file1) - - # Get a "clean" model instance - p = self.PersonModel.objects.get(name="Joan") - # It won't have an opened file. - self.assertEqual(p.mugshot.closed, True) - - # After asking for the size, the file should still be closed. - _ = p.mugshot.size - self.assertEqual(p.mugshot.closed, True) - - def test_pickle(self): - """ - Tests that ImageField can be pickled, unpickled, and that the - image of the unpickled version is the same as the original. - """ - import pickle - - p = Person(name="Joe") - p.mugshot.save("mug", self.file1) - dump = pickle.dumps(p) - - p2 = Person(name="Bob") - p2.mugshot = self.file1 - - loaded_p = pickle.loads(dump) - self.assertEqual(p.mugshot, loaded_p.mugshot) - - - class ImageFieldTwoDimensionsTests(ImageFieldTestMixin, TestCase): + def check_dimensions(self, instance, width, height, + field_name='mugshot'): """ - Tests behavior of an ImageField and its dimensions fields. + Asserts that the given width and height values match both the + field's height and width attributes and the height and width fields + (if defined) the image field is caching to. + + Note, this method will check for dimension fields named by adding + "_width" or "_height" to the name of the ImageField. So, the + models used in these tests must have their fields named + accordingly. + + By default, we check the field named "mugshot", but this can be + specified by passing the field_name parameter. """ + field = getattr(instance, field_name) + # Check height/width attributes of field. + if width is None and height is None: + self.assertRaises(ValueError, getattr, field, 'width') + self.assertRaises(ValueError, getattr, field, 'height') + else: + self.assertEqual(field.width, width) + self.assertEqual(field.height, height) - def test_constructor(self): - """ - Tests assigning an image field through the model's constructor. - """ - p = self.PersonModel(name='Joe', mugshot=self.file1) - self.check_dimensions(p, 4, 8) - p.save() - self.check_dimensions(p, 4, 8) - - def test_image_after_constructor(self): - """ - Tests behavior when image is not passed in constructor. - """ - p = self.PersonModel(name='Joe') - # TestImageField value will default to being an instance of its - # attr_class, a TestImageFieldFile, with name == None, which will - # cause it to evaluate as False. - self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True) - self.assertEqual(bool(p.mugshot), False) - - # Test setting a fresh created model instance. - p = self.PersonModel(name='Joe') - p.mugshot = self.file1 - self.check_dimensions(p, 4, 8) - - def test_create(self): - """ - Tests assigning an image in Manager.create(). - """ - p = self.PersonModel.objects.create(name='Joe', mugshot=self.file1) - self.check_dimensions(p, 4, 8) - - def test_default_value(self): - """ - Tests that the default value for an ImageField is an instance of - the field's attr_class (TestImageFieldFile in this case) with no - name (name set to None). - """ - p = self.PersonModel() - self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True) - self.assertEqual(bool(p.mugshot), False) - - def test_assignment_to_None(self): - """ - Tests that assigning ImageField to None clears dimensions. - """ - p = self.PersonModel(name='Joe', mugshot=self.file1) - self.check_dimensions(p, 4, 8) - - # If image assigned to None, dimension fields should be cleared. - p.mugshot = None - self.check_dimensions(p, None, None) - - p.mugshot = self.file2 - self.check_dimensions(p, 8, 4) - - def test_field_save_and_delete_methods(self): - """ - Tests assignment using the field's save method and deletion using - the field's delete method. - """ - p = self.PersonModel(name='Joe') - p.mugshot.save("mug", self.file1) - self.check_dimensions(p, 4, 8) - - # A new file should update dimensions. - p.mugshot.save("mug", self.file2) - self.check_dimensions(p, 8, 4) - - # Field and dimensions should be cleared after a delete. - p.mugshot.delete(save=False) - self.assertEqual(p.mugshot, None) - self.check_dimensions(p, None, None) - - def test_dimensions(self): - """ - Checks that dimensions are updated correctly in various situations. - """ - p = self.PersonModel(name='Joe') - - # Dimensions should get set if file is saved. - p.mugshot.save("mug", self.file1) - self.check_dimensions(p, 4, 8) - - # Test dimensions after fetching from database. - p = self.PersonModel.objects.get(name='Joe') - # Bug 11084: Dimensions should not get recalculated if file is - # coming from the database. We test this by checking if the file - # was opened. - self.assertEqual(p.mugshot.was_opened, False) - self.check_dimensions(p, 4, 8) - # After checking dimensions on the image field, the file will have - # opened. - self.assertEqual(p.mugshot.was_opened, True) - # Dimensions should now be cached, and if we reset was_opened and - # check dimensions again, the file should not have opened. - p.mugshot.was_opened = False - self.check_dimensions(p, 4, 8) - self.assertEqual(p.mugshot.was_opened, False) - - # If we assign a new image to the instance, the dimensions should - # update. - p.mugshot = self.file2 - self.check_dimensions(p, 8, 4) - # Dimensions were recalculated, and hence file should have opened. - self.assertEqual(p.mugshot.was_opened, True) + # Check height/width fields of model, if defined. + width_field_name = field_name + '_width' + if hasattr(instance, width_field_name): + self.assertEqual(getattr(instance, width_field_name), width) + height_field_name = field_name + '_height' + if hasattr(instance, height_field_name): + self.assertEqual(getattr(instance, height_field_name), height) - class ImageFieldNoDimensionsTests(ImageFieldTwoDimensionsTests): +@skipIf(Image is None, "PIL is required to test ImageField") +class ImageFieldTests(ImageFieldTestMixin, TestCase): + """ + Tests for ImageField that don't need to be run with each of the + different test model classes. + """ + + def test_equal_notequal_hash(self): """ - Tests behavior of an ImageField with no dimension fields. + Bug #9786: Ensure '==' and '!=' work correctly. + Bug #9508: make sure hash() works as expected (equal items must + hash to the same value). """ + # Create two Persons with different mugshots. + p1 = self.PersonModel(name="Joe") + p1.mugshot.save("mug", self.file1) + p2 = self.PersonModel(name="Bob") + p2.mugshot.save("mug", self.file2) + self.assertEqual(p1.mugshot == p2.mugshot, False) + self.assertEqual(p1.mugshot != p2.mugshot, True) - PersonModel = Person + # Test again with an instance fetched from the db. + p1_db = self.PersonModel.objects.get(name="Joe") + self.assertEqual(p1_db.mugshot == p2.mugshot, False) + self.assertEqual(p1_db.mugshot != p2.mugshot, True) + # Instance from db should match the local instance. + self.assertEqual(p1_db.mugshot == p1.mugshot, True) + self.assertEqual(hash(p1_db.mugshot), hash(p1.mugshot)) + self.assertEqual(p1_db.mugshot != p1.mugshot, False) - class ImageFieldOneDimensionTests(ImageFieldTwoDimensionsTests): + def test_instantiate_missing(self): """ - Tests behavior of an ImageField with one dimensions field. + If the underlying file is unavailable, still create instantiate the + object without error. """ + p = self.PersonModel(name="Joan") + p.mugshot.save("shot", self.file1) + p = self.PersonModel.objects.get(name="Joan") + path = p.mugshot.path + shutil.move(path, path + '.moved') + p2 = self.PersonModel.objects.get(name="Joan") - PersonModel = PersonWithHeight - - - class ImageFieldDimensionsFirstTests(ImageFieldTwoDimensionsTests): + def test_delete_when_missing(self): """ - Tests behavior of an ImageField where the dimensions fields are - defined before the ImageField. + Bug #8175: correctly delete an object where the file no longer + exists on the file system. """ + p = self.PersonModel(name="Fred") + p.mugshot.save("shot", self.file1) + os.remove(p.mugshot.path) + p.delete() - PersonModel = PersonDimensionsFirst - - - class ImageFieldUsingFileTests(ImageFieldTwoDimensionsTests): + def test_size_method(self): """ - Tests behavior of an ImageField when assigning it a File instance - rather than an ImageFile instance. + Bug #8534: FileField.size should not leave the file open. """ + p = self.PersonModel(name="Joan") + p.mugshot.save("shot", self.file1) - PersonModel = PersonDimensionsFirst - File = File + # Get a "clean" model instance + p = self.PersonModel.objects.get(name="Joan") + # It won't have an opened file. + self.assertEqual(p.mugshot.closed, True) + # After asking for the size, the file should still be closed. + _ = p.mugshot.size + self.assertEqual(p.mugshot.closed, True) - class TwoImageFieldTests(ImageFieldTestMixin, TestCase): + def test_pickle(self): """ - Tests a model with two ImageFields. + Tests that ImageField can be pickled, unpickled, and that the + image of the unpickled version is the same as the original. """ + import pickle - PersonModel = PersonTwoImages + p = Person(name="Joe") + p.mugshot.save("mug", self.file1) + dump = pickle.dumps(p) - def test_constructor(self): - p = self.PersonModel(mugshot=self.file1, headshot=self.file2) - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') - p.save() - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') + p2 = Person(name="Bob") + p2.mugshot = self.file1 - def test_create(self): - p = self.PersonModel.objects.create(mugshot=self.file1, - headshot=self.file2) - self.check_dimensions(p, 4, 8) - self.check_dimensions(p, 8, 4, 'headshot') + loaded_p = pickle.loads(dump) + self.assertEqual(p.mugshot, loaded_p.mugshot) - def test_assignment(self): - p = self.PersonModel() - self.check_dimensions(p, None, None, 'mugshot') - self.check_dimensions(p, None, None, 'headshot') - p.mugshot = self.file1 - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, None, None, 'headshot') - p.headshot = self.file2 - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') +@skipIf(Image is None, "PIL is required to test ImageField") +class ImageFieldTwoDimensionsTests(ImageFieldTestMixin, TestCase): + """ + Tests behavior of an ImageField and its dimensions fields. + """ - # Clear the ImageFields one at a time. - p.mugshot = None - self.check_dimensions(p, None, None, 'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') - p.headshot = None - self.check_dimensions(p, None, None, 'mugshot') - self.check_dimensions(p, None, None, 'headshot') + def test_constructor(self): + """ + Tests assigning an image field through the model's constructor. + """ + p = self.PersonModel(name='Joe', mugshot=self.file1) + self.check_dimensions(p, 4, 8) + p.save() + self.check_dimensions(p, 4, 8) - def test_field_save_and_delete_methods(self): - p = self.PersonModel(name='Joe') - p.mugshot.save("mug", self.file1) - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, None, None, 'headshot') - p.headshot.save("head", self.file2) - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') + def test_image_after_constructor(self): + """ + Tests behavior when image is not passed in constructor. + """ + p = self.PersonModel(name='Joe') + # TestImageField value will default to being an instance of its + # attr_class, a TestImageFieldFile, with name == None, which will + # cause it to evaluate as False. + self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True) + self.assertEqual(bool(p.mugshot), False) - # We can use save=True when deleting the image field with null=True - # dimension fields and the other field has an image. - p.headshot.delete(save=True) - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, None, None, 'headshot') - p.mugshot.delete(save=False) - self.check_dimensions(p, None, None, 'mugshot') - self.check_dimensions(p, None, None, 'headshot') + # Test setting a fresh created model instance. + p = self.PersonModel(name='Joe') + p.mugshot = self.file1 + self.check_dimensions(p, 4, 8) - def test_dimensions(self): - """ - Checks that dimensions are updated correctly in various situations. - """ - p = self.PersonModel(name='Joe') + def test_create(self): + """ + Tests assigning an image in Manager.create(). + """ + p = self.PersonModel.objects.create(name='Joe', mugshot=self.file1) + self.check_dimensions(p, 4, 8) - # Dimensions should get set for the saved file. - p.mugshot.save("mug", self.file1) - p.headshot.save("head", self.file2) - self.check_dimensions(p, 4, 8, 'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') + def test_default_value(self): + """ + Tests that the default value for an ImageField is an instance of + the field's attr_class (TestImageFieldFile in this case) with no + name (name set to None). + """ + p = self.PersonModel() + self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True) + self.assertEqual(bool(p.mugshot), False) - # Test dimensions after fetching from database. - p = self.PersonModel.objects.get(name='Joe') - # Bug 11084: Dimensions should not get recalculated if file is - # coming from the database. We test this by checking if the file - # was opened. - self.assertEqual(p.mugshot.was_opened, False) - self.assertEqual(p.headshot.was_opened, False) - self.check_dimensions(p, 4, 8,'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') - # After checking dimensions on the image fields, the files will - # have been opened. - self.assertEqual(p.mugshot.was_opened, True) - self.assertEqual(p.headshot.was_opened, True) - # Dimensions should now be cached, and if we reset was_opened and - # check dimensions again, the file should not have opened. - p.mugshot.was_opened = False - p.headshot.was_opened = False - self.check_dimensions(p, 4, 8,'mugshot') - self.check_dimensions(p, 8, 4, 'headshot') - self.assertEqual(p.mugshot.was_opened, False) - self.assertEqual(p.headshot.was_opened, False) + def test_assignment_to_None(self): + """ + Tests that assigning ImageField to None clears dimensions. + """ + p = self.PersonModel(name='Joe', mugshot=self.file1) + self.check_dimensions(p, 4, 8) - # If we assign a new image to the instance, the dimensions should - # update. - p.mugshot = self.file2 - p.headshot = self.file1 - self.check_dimensions(p, 8, 4, 'mugshot') - self.check_dimensions(p, 4, 8, 'headshot') - # Dimensions were recalculated, and hence file should have opened. - self.assertEqual(p.mugshot.was_opened, True) - self.assertEqual(p.headshot.was_opened, True) + # If image assigned to None, dimension fields should be cleared. + p.mugshot = None + self.check_dimensions(p, None, None) + + p.mugshot = self.file2 + self.check_dimensions(p, 8, 4) + + def test_field_save_and_delete_methods(self): + """ + Tests assignment using the field's save method and deletion using + the field's delete method. + """ + p = self.PersonModel(name='Joe') + p.mugshot.save("mug", self.file1) + self.check_dimensions(p, 4, 8) + + # A new file should update dimensions. + p.mugshot.save("mug", self.file2) + self.check_dimensions(p, 8, 4) + + # Field and dimensions should be cleared after a delete. + p.mugshot.delete(save=False) + self.assertEqual(p.mugshot, None) + self.check_dimensions(p, None, None) + + def test_dimensions(self): + """ + Checks that dimensions are updated correctly in various situations. + """ + p = self.PersonModel(name='Joe') + + # Dimensions should get set if file is saved. + p.mugshot.save("mug", self.file1) + self.check_dimensions(p, 4, 8) + + # Test dimensions after fetching from database. + p = self.PersonModel.objects.get(name='Joe') + # Bug 11084: Dimensions should not get recalculated if file is + # coming from the database. We test this by checking if the file + # was opened. + self.assertEqual(p.mugshot.was_opened, False) + self.check_dimensions(p, 4, 8) + # After checking dimensions on the image field, the file will have + # opened. + self.assertEqual(p.mugshot.was_opened, True) + # Dimensions should now be cached, and if we reset was_opened and + # check dimensions again, the file should not have opened. + p.mugshot.was_opened = False + self.check_dimensions(p, 4, 8) + self.assertEqual(p.mugshot.was_opened, False) + + # If we assign a new image to the instance, the dimensions should + # update. + p.mugshot = self.file2 + self.check_dimensions(p, 8, 4) + # Dimensions were recalculated, and hence file should have opened. + self.assertEqual(p.mugshot.was_opened, True) + + +@skipIf(Image is None, "PIL is required to test ImageField") +class ImageFieldNoDimensionsTests(ImageFieldTwoDimensionsTests): + """ + Tests behavior of an ImageField with no dimension fields. + """ + + PersonModel = Person + + +@skipIf(Image is None, "PIL is required to test ImageField") +class ImageFieldOneDimensionTests(ImageFieldTwoDimensionsTests): + """ + Tests behavior of an ImageField with one dimensions field. + """ + + PersonModel = PersonWithHeight + + +@skipIf(Image is None, "PIL is required to test ImageField") +class ImageFieldDimensionsFirstTests(ImageFieldTwoDimensionsTests): + """ + Tests behavior of an ImageField where the dimensions fields are + defined before the ImageField. + """ + + PersonModel = PersonDimensionsFirst + + +@skipIf(Image is None, "PIL is required to test ImageField") +class ImageFieldUsingFileTests(ImageFieldTwoDimensionsTests): + """ + Tests behavior of an ImageField when assigning it a File instance + rather than an ImageFile instance. + """ + + PersonModel = PersonDimensionsFirst + File = File + + +@skipIf(Image is None, "PIL is required to test ImageField") +class TwoImageFieldTests(ImageFieldTestMixin, TestCase): + """ + Tests a model with two ImageFields. + """ + + PersonModel = PersonTwoImages + + def test_constructor(self): + p = self.PersonModel(mugshot=self.file1, headshot=self.file2) + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + p.save() + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + + def test_create(self): + p = self.PersonModel.objects.create(mugshot=self.file1, + headshot=self.file2) + self.check_dimensions(p, 4, 8) + self.check_dimensions(p, 8, 4, 'headshot') + + def test_assignment(self): + p = self.PersonModel() + self.check_dimensions(p, None, None, 'mugshot') + self.check_dimensions(p, None, None, 'headshot') + + p.mugshot = self.file1 + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, None, None, 'headshot') + p.headshot = self.file2 + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + + # Clear the ImageFields one at a time. + p.mugshot = None + self.check_dimensions(p, None, None, 'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + p.headshot = None + self.check_dimensions(p, None, None, 'mugshot') + self.check_dimensions(p, None, None, 'headshot') + + def test_field_save_and_delete_methods(self): + p = self.PersonModel(name='Joe') + p.mugshot.save("mug", self.file1) + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, None, None, 'headshot') + p.headshot.save("head", self.file2) + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + + # We can use save=True when deleting the image field with null=True + # dimension fields and the other field has an image. + p.headshot.delete(save=True) + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, None, None, 'headshot') + p.mugshot.delete(save=False) + self.check_dimensions(p, None, None, 'mugshot') + self.check_dimensions(p, None, None, 'headshot') + + def test_dimensions(self): + """ + Checks that dimensions are updated correctly in various situations. + """ + p = self.PersonModel(name='Joe') + + # Dimensions should get set for the saved file. + p.mugshot.save("mug", self.file1) + p.headshot.save("head", self.file2) + self.check_dimensions(p, 4, 8, 'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + + # Test dimensions after fetching from database. + p = self.PersonModel.objects.get(name='Joe') + # Bug 11084: Dimensions should not get recalculated if file is + # coming from the database. We test this by checking if the file + # was opened. + self.assertEqual(p.mugshot.was_opened, False) + self.assertEqual(p.headshot.was_opened, False) + self.check_dimensions(p, 4, 8,'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + # After checking dimensions on the image fields, the files will + # have been opened. + self.assertEqual(p.mugshot.was_opened, True) + self.assertEqual(p.headshot.was_opened, True) + # Dimensions should now be cached, and if we reset was_opened and + # check dimensions again, the file should not have opened. + p.mugshot.was_opened = False + p.headshot.was_opened = False + self.check_dimensions(p, 4, 8,'mugshot') + self.check_dimensions(p, 8, 4, 'headshot') + self.assertEqual(p.mugshot.was_opened, False) + self.assertEqual(p.headshot.was_opened, False) + + # If we assign a new image to the instance, the dimensions should + # update. + p.mugshot = self.file2 + p.headshot = self.file1 + self.check_dimensions(p, 8, 4, 'mugshot') + self.check_dimensions(p, 4, 8, 'headshot') + # Dimensions were recalculated, and hence file should have opened. + self.assertEqual(p.mugshot.was_opened, True) + self.assertEqual(p.headshot.was_opened, True) diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index a89ffcae32..b94c4696f9 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -13,12 +13,10 @@ from django.utils import unittest from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel, Document, RenamedField) -# If PIL available, do these tests. -if Image: - from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests, - TwoImageFieldTests, ImageFieldNoDimensionsTests, - ImageFieldOneDimensionTests, ImageFieldDimensionsFirstTests, - ImageFieldUsingFileTests) +from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests, + TwoImageFieldTests, ImageFieldNoDimensionsTests, + ImageFieldOneDimensionTests, ImageFieldDimensionsFirstTests, + ImageFieldUsingFileTests) class BasicFieldTests(test.TestCase): From 4c417cc9ebd65595128b81cdddc9ea8293cbcbbe Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 6 Jul 2012 13:57:14 +0200 Subject: [PATCH 150/519] Fixed #18576 -- Added missing import in tutorial02 Thanks jaaruiz at yahoo.com for the report. --- docs/intro/tutorial02.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 16682c67c3..84da36be86 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -284,7 +284,7 @@ Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Poll`` registration code to read:: from django.contrib import admin - from polls.models import Poll + from polls.models import Choice, Poll class ChoiceInline(admin.StackedInline): model = Choice From 2ba4278cb38f1346d70cf427bbeac71a4d1dc5ad Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 6 Jul 2012 15:29:23 +0100 Subject: [PATCH 151/519] Fixed #18484 - 'display:none' on CSRF token div is redundant and causes problems with some browsers Thanks to hedleyroos for the report --- django/template/defaulttags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 52d886a5a1..fb45fe722e 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -47,7 +47,7 @@ class CsrfTokenNode(Node): if csrf_token == 'NOTPROVIDED': return format_html("") else: - return format_html("
      ", csrf_token) + return format_html("
      ", csrf_token) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning From 91e4f7effb6c0911d2db68c9f17b40f484838538 Mon Sep 17 00:00:00 2001 From: Guilherme Gondim Date: Fri, 6 Jul 2012 15:24:07 -0300 Subject: [PATCH 152/519] Fix copyright holder --- django/utils/baseconv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/utils/baseconv.py b/django/utils/baseconv.py index 8a6181bb2d..053ce3e97e 100644 --- a/django/utils/baseconv.py +++ b/django/utils/baseconv.py @@ -1,4 +1,4 @@ -# Copyright (c) 2010 Taurinus Collective. All rights reserved. +# Copyright (c) 2010 Guilherme Gondim. All rights reserved. # Copyright (c) 2009 Simon Willison. All rights reserved. # Copyright (c) 2002 Drew Perttula. All rights reserved. # From 8dafd04c45a66ff60f0503da1ebea14628322b45 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 7 Jul 2012 11:34:04 +0200 Subject: [PATCH 153/519] Fixed #17257 - Removed outdated comment in syndication view Thanks krzysiumed for the patch. --- django/contrib/syndication/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index af767e1867..773a1cd7bd 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -10,6 +10,7 @@ from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode from django.utils.html import escape from django.utils.timezone import is_naive + def add_domain(domain, url, secure=False): protocol = 'https' if secure else 'http' if url.startswith('//'): @@ -18,8 +19,6 @@ def add_domain(domain, url, secure=False): elif not (url.startswith('http://') or url.startswith('https://') or url.startswith('mailto:')): - # 'url' must already be ASCII and URL-quoted, so no need for encoding - # conversions here. url = iri_to_uri('%s://%s%s' % (protocol, domain, url)) return url From 52a9e15794ac050afb17837a34407722e7249854 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sat, 7 Jul 2012 15:29:20 +0200 Subject: [PATCH 154/519] Fixed a regression in the user admin page introduced in a92e7f37c4ae84b6b8d8016cc6783211e9047219. a92e7f37c4ae84b6b8d8016cc6783211e9047219 switched most of the internal stuff to format_html. Using format_html in the `render` method of `ReadOnlyPasswordHashWidget` caused it to generate `SafeString` instances. Later these safe strings where returned from `BoundField.__unicode__` which caused force_unicode to loose the "safe" information. This commit fixes that by ensuring that the render method returns `SafeUnicode` instead of `SafeString`. --- django/contrib/auth/forms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index bce8747661..d2cb039e68 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import forms from django.forms.util import flatatt from django.template import loader From 0a68a2994be17ed2669accead331881cb0be41dd Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sat, 7 Jul 2012 15:30:25 +0200 Subject: [PATCH 155/519] Fixed #18254 -- Added ability to the static template tags to store the result in a contextt variable. Many thanks to Andrei Antoukh for the initial patch. --- .../staticfiles/templatetags/staticfiles.py | 30 ++++++++- django/templatetags/static.py | 63 +++++++++++++++++-- docs/ref/contrib/staticfiles.txt | 11 ++++ docs/ref/templates/builtins.txt | 11 ++++ .../staticfiles_tests/tests.py | 12 ++-- tests/regressiontests/templates/tests.py | 2 + 6 files changed, 118 insertions(+), 11 deletions(-) diff --git a/django/contrib/staticfiles/templatetags/staticfiles.py b/django/contrib/staticfiles/templatetags/staticfiles.py index 788f06ec16..71339ea8cd 100644 --- a/django/contrib/staticfiles/templatetags/staticfiles.py +++ b/django/contrib/staticfiles/templatetags/staticfiles.py @@ -1,13 +1,37 @@ from django import template +from django.templatetags.static import StaticNode from django.contrib.staticfiles.storage import staticfiles_storage register = template.Library() -@register.simple_tag -def static(path): +class StaticFilesNode(StaticNode): + + def url(self, context): + path = self.path.resolve(context) + return staticfiles_storage.url(path) + + +@register.tag('static') +def do_static(parser, token): """ A template tag that returns the URL to a file using staticfiles' storage backend + + Usage:: + + {% static path [as varname] %} + + Examples:: + + {% static "myapp/css/base.css" %} + {% static variable_with_path %} + {% static "myapp/css/base.css" as admin_base_css %} + {% static variable_with_path as varname %} + """ - return staticfiles_storage.url(path) + return StaticFilesNode.handle_token(parser, token) + + +def static(path): + return StaticNode.handle_simple(path) diff --git a/django/templatetags/static.py b/django/templatetags/static.py index cba44378c3..4b2d66d521 100644 --- a/django/templatetags/static.py +++ b/django/templatetags/static.py @@ -1,9 +1,11 @@ from urlparse import urljoin from django import template +from django.template.base import Node from django.utils.encoding import iri_to_uri register = template.Library() + class PrefixNode(template.Node): def __repr__(self): @@ -48,6 +50,7 @@ class PrefixNode(template.Node): context[self.varname] = prefix return '' + @register.tag def get_static_prefix(parser, token): """ @@ -66,6 +69,7 @@ def get_static_prefix(parser, token): """ return PrefixNode.handle_token(parser, token, "STATIC_URL") + @register.tag def get_media_prefix(parser, token): """ @@ -84,19 +88,70 @@ def get_media_prefix(parser, token): """ return PrefixNode.handle_token(parser, token, "MEDIA_URL") -@register.simple_tag -def static(path): + +class StaticNode(Node): + def __init__(self, varname=None, path=None): + if path is None: + raise template.TemplateSyntaxError( + "Static template nodes must be given a path to return.") + self.path = path + self.varname = varname + + def url(self, context): + path = self.path.resolve(context) + return self.handle_simple(path) + + def render(self, context): + url = self.url(context) + if self.varname is None: + return url + context[self.varname] = url + return '' + + @classmethod + def handle_simple(cls, path): + return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) + + @classmethod + def handle_token(cls, parser, token): + """ + Class method to parse prefix node and return a Node. + """ + bits = token.split_contents() + + if len(bits) < 2: + raise template.TemplateSyntaxError( + "'%s' takes at least one argument (path to file)" % bits[0]) + + path = parser.compile_filter(bits[1]) + + if len(bits) >= 2 and bits[-2] == 'as': + varname = bits[3] + else: + varname = None + + return cls(varname, path) + + +@register.tag('static') +def do_static(parser, token): """ Joins the given path with the STATIC_URL setting. Usage:: - {% static path %} + {% static path [as varname] %} Examples:: {% static "myapp/css/base.css" %} {% static variable_with_path %} + {% static "myapp/css/base.css" as admin_base_css %} + {% static variable_with_path as varname %} """ - return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) + return StaticNode.handle_token(parser, token) + + +def static(path): + return StaticNode.handle_simple(path) diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 126bcdd4e6..f5557dff91 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -387,6 +387,17 @@ The previous example is equal to calling the ``url`` method of an instance of useful when using a non-local storage backend to deploy files as documented in :ref:`staticfiles-from-cdn`. +.. versionadded:: 1.5 + +If you'd like to retrieve a static URL without displaying it, you can use a +slightly different call:: + +.. code-block:: html+django + + {% load static from staticfiles %} + {% static "images/hi.jpg" as myphoto %} + Hi! + Other Helpers ============= diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index cf228d72f6..71f57acdbf 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2354,6 +2354,17 @@ It is also able to consume standard context variables, e.g. assuming a {% load static %} +If you'd like to retrieve a static URL without displaying it, you can use a +slightly different call:: + +.. versionadded:: 1.5 + +.. code-block:: html+django + + {% load static %} + {% static "images/hi.jpg" as myphoto %} + + .. note:: The :mod:`staticfiles` contrib app also ships diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 8321fc2365..7678d7f100 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -87,14 +87,16 @@ class BaseStaticFilesTestCase(object): template = loader.get_template_from_string(template) return template.render(Context(kwargs)).strip() - def static_template_snippet(self, path): + def static_template_snippet(self, path, asvar=False): + if asvar: + return "{%% load static from staticfiles %%}{%% static '%s' as var %%}{{ var }}" % path return "{%% load static from staticfiles %%}{%% static '%s' %%}" % path - def assertStaticRenders(self, path, result, **kwargs): - template = self.static_template_snippet(path) + def assertStaticRenders(self, path, result, asvar=False, **kwargs): + template = self.static_template_snippet(path, asvar) self.assertEqual(self.render_template(template, **kwargs), result) - def assertStaticRaises(self, exc, path, result, **kwargs): + def assertStaticRaises(self, exc, path, result, asvar=False, **kwargs): self.assertRaises(exc, self.assertStaticRenders, path, result, **kwargs) @@ -368,6 +370,8 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, "/static/does/not/exist.png") self.assertStaticRenders("test/file.txt", "/static/test/file.dad0999e4f8f.txt") + self.assertStaticRenders("test/file.txt", + "/static/test/file.dad0999e4f8f.txt", asvar=True) self.assertStaticRenders("cached/styles.css", "/static/cached/styles.93b1147e8552.css") self.assertStaticRenders("path/", diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 35d01221ab..4aa71f9709 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1616,6 +1616,8 @@ class Templates(unittest.TestCase): 'static-prefixtag04': ('{% load static %}{% get_media_prefix as media_prefix %}{{ media_prefix }}', {}, settings.MEDIA_URL), 'static-statictag01': ('{% load static %}{% static "admin/base.css" %}', {}, urljoin(settings.STATIC_URL, 'admin/base.css')), 'static-statictag02': ('{% load static %}{% static base_css %}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')), + 'static-statictag03': ('{% load static %}{% static "admin/base.css" as foo %}{{ foo }}', {}, urljoin(settings.STATIC_URL, 'admin/base.css')), + 'static-statictag04': ('{% load static %}{% static base_css as foo %}{{ foo }}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')), # Verbatim template tag outputs contents without rendering. 'verbatim-tag01': ('{% verbatim %}{{bare }}{% endverbatim %}', {}, '{{bare }}'), From 29ca3d3c4b3d330337cce8713196ef7eea956dc9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jul 2012 16:00:03 +0200 Subject: [PATCH 156/519] Fixed #18587 -- Typo in management command example Thanks Frank Wiles. --- docs/howto/custom-management-commands.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 4a27bdf7a9..12e8ec2494 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -129,7 +129,7 @@ default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`. class Command(BaseCommand): ... - self.can_import_settings = True + can_import_settings = True def handle(self, *args, **options): From bbc590697a8cde2faf067b124d08fe9488db4905 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jul 2012 16:44:55 +0200 Subject: [PATCH 157/519] Removed Django 1.0-specific sections. --- docs/ref/contrib/gis/install.txt | 11 ----------- docs/ref/models/querysets.txt | 16 ---------------- 2 files changed, 27 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 00f8f8a370..805772fa88 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -838,17 +838,6 @@ your ``.profile`` to be able to run the package programs from the command-line:: __ http://www.kyngchaos.com/software/frameworks __ http://www.kyngchaos.com/software/postgres -.. note:: - - Use of these binaries requires Django 1.0.3 and above. If you are - using a previous version of Django (like 1.0.2), then you will have - to add the following in your settings: - - .. code-block:: python - - GEOS_LIBRARY_PATH='/Library/Frameworks/GEOS.framework/GEOS' - GDAL_LIBRARY_PATH='/Library/Frameworks/GDAL.framework/GDAL' - .. _psycopg2_kyngchaos: psycopg2 diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 2876f1474d..0a9005ad26 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1768,22 +1768,6 @@ This queryset will be evaluated as subselect statement:: SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%') -The above code fragment could also be written as follows:: - - inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query - entries = Entry.objects.filter(blog__in=inner_q) - -.. warning:: - - This ``query`` attribute should be considered an opaque internal attribute. - It's fine to use it like above, but its API may change between Django - versions. - -This second form is a bit less readable and unnatural to write, since it -accesses the internal ``query`` attribute and requires a ``ValuesQuerySet``. -If your code doesn't require compatibility with Django 1.0, use the first -form, passing in a queryset directly. - If you pass in a ``ValuesQuerySet`` or ``ValuesListQuerySet`` (the result of calling ``values()`` or ``values_list()`` on a queryset) as the value to an ``__in`` lookup, you need to ensure you are only extracting one field in the From d94cfdcfae92fd4409cd1b5e14eebc75033266fd Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jul 2012 17:42:04 +0200 Subject: [PATCH 158/519] Fixed #18589 -- Typo in generic CBV docs. Thanks cpthomas for the report. --- docs/topics/class-based-views/generic-display.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt index 4c2f95ce17..0d4cb6244d 100644 --- a/docs/topics/class-based-views/generic-display.txt +++ b/docs/topics/class-based-views/generic-display.txt @@ -157,7 +157,7 @@ might look like the following:: That's really all there is to it. All the cool features of generic views come from changing the attributes set on the generic view. The :doc:`generic views reference` documents all the -generic views and their options in detail; the rest of this document will +generic views and their options in detail; the rest of this document will consider some of the common ways you might customize and extend generic views. @@ -220,7 +220,7 @@ more:: def get_context_data(self, **kwargs): # Call the base implementation first to get a context - context = super(PublisherDetailView, self).get_context_data(**kwargs) + context = super(PublisherDetail, self).get_context_data(**kwargs) # Add in a QuerySet of all the books context['book_list'] = Book.objects.all() return context @@ -284,7 +284,7 @@ technique:: from django.views.generic import ListView from books.models import Book - class AcmeBookListView(ListView): + class AcmeBookList(ListView): context_object_name = 'book_list' queryset = Book.objects.filter(publisher__name='Acme Publishing') @@ -361,7 +361,7 @@ use it in the template:: def get_context_data(self, **kwargs): # Call the base implementation first to get a context - context = super(PublisherBookListView, self).get_context_data(**kwargs) + context = super(PublisherBookList, self).get_context_data(**kwargs) # Add in the publisher context['publisher'] = self.publisher return context From 249c445446f0811d6396cfd3053aed33edf2e7b3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 7 Jul 2012 23:08:43 +0200 Subject: [PATCH 159/519] Fixed #18164 -- Precised startapp template context content --- docs/ref/django-admin.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 7ca1ee5ddd..31c46c7fa0 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -888,7 +888,8 @@ through the template engine: the files whose extensions match the with the ``--name`` option. The :class:`template context ` used is: -- Any option passed to the startapp command +- Any option passed to the startapp command (among the command's supported + options) - ``app_name`` -- the app name as passed to the command - ``app_directory`` -- the full path of the newly created app From 8015593bc1f2f226e7979bfdee8b7e88658d0e74 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 7 Jul 2012 15:41:04 -0700 Subject: [PATCH 160/519] Fixed #17978 -- Fixed a minor layout issue when an inline contains a filter horizontal widget. Thanks to Aymeric Augustin for the report. --- django/contrib/admin/static/admin/css/widgets.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/static/admin/css/widgets.css b/django/contrib/admin/static/admin/css/widgets.css index 2989f2f4fd..0a7012c7b2 100644 --- a/django/contrib/admin/static/admin/css/widgets.css +++ b/django/contrib/admin/static/admin/css/widgets.css @@ -41,7 +41,8 @@ text-align: left; } -.selector .selector-filter label { +.selector .selector-filter label, +.inline-group .aligned .selector .selector-filter label { width: 16px; padding: 2px; } From 5e94ef293cb1cfe4dc43cbd5509653c0e0bf66cf Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 8 Jul 2012 11:53:45 +0200 Subject: [PATCH 161/519] Fixed #18374 -- Explained "corrupt image" error Thanks fabian and charettes. --- docs/ref/forms/fields.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 486d49d796..082ec17a35 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -591,7 +591,11 @@ For each field, we describe the default widget used if you don't specify * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``, ``invalid_image`` - Using an ImageField requires that the `Python Imaging Library`_ is installed. + Using an ``ImageField`` requires that the `Python Imaging Library`_ (PIL) + is installed and supports the image formats you use. If you encounter a + ``corrupt image`` error when you upload an image, it usually means PIL + doesn't understand its format. To fix this, install the appropriate + library and reinstall PIL. When you use an ``ImageField`` on a form, you must also remember to :ref:`bind the file data to the form `. From 3727f6d09681e4cb23d67e14ecc677a364c991bd Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 8 Jul 2012 12:56:49 +0200 Subject: [PATCH 162/519] Fixed #18430 -- Use the FILE_CHARSET setting when reading from a file during post processing with the cached staticfiles storage. Thanks to Brant Young for initial debugging. --- django/contrib/staticfiles/storage.py | 2 +- .../apps/test/static/test/nonascii.css | 5 +++++ .../apps/test/static/test/window.png | Bin 0 -> 207 bytes tests/regressiontests/staticfiles_tests/tests.py | 5 +++-- 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 tests/regressiontests/staticfiles_tests/apps/test/static/test/nonascii.css create mode 100644 tests/regressiontests/staticfiles_tests/apps/test/static/test/window.png diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index e02fec8ec0..16d33fff4b 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -228,7 +228,7 @@ class CachedFilesMixin(object): # ..to apply each replacement pattern to the content if name in adjustable_paths: - content = original_file.read() + content = original_file.read().decode(settings.FILE_CHARSET) converter = self.url_converter(name) for patterns in self._patterns.values(): for pattern in patterns: diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/nonascii.css b/tests/regressiontests/staticfiles_tests/apps/test/static/test/nonascii.css new file mode 100644 index 0000000000..a5358f6ede --- /dev/null +++ b/tests/regressiontests/staticfiles_tests/apps/test/static/test/nonascii.css @@ -0,0 +1,5 @@ +body { + background: url('window.png'); +} + +.snowman:before { content: "☃"; } diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/window.png b/tests/regressiontests/staticfiles_tests/apps/test/static/test/window.png new file mode 100644 index 0000000000000000000000000000000000000000..ba48325c0a580d4480cb9f76713d5ec9b2c3756a GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{1|(OCFP#RYrg^$JhFF|FdNr5tfB+A}g?R_~ z{oaP`=$u`mn5ncc$|Q1f>cKq*B1@)RuxwXksp*wS-nu}?4ww>;!tZUL%?Z@*`pWolf$81WoeO<{E@bd@^>bP0 Hl+XkKUAs`5 literal 0 HcmV?d00001 diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 7678d7f100..91435a2c86 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -498,8 +498,9 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, collectstatic_cmd = CollectstaticCommand() collectstatic_cmd.set_options(**collectstatic_args) stats = collectstatic_cmd.collect() - self.assertTrue(os.path.join('cached', 'css', 'window.css') in stats['post_processed']) - self.assertTrue(os.path.join('cached', 'css', 'img', 'window.png') in stats['unmodified']) + self.assertIn(os.path.join('cached', 'css', 'window.css'), stats['post_processed']) + self.assertIn(os.path.join('cached', 'css', 'img', 'window.png'), stats['unmodified']) + self.assertIn(os.path.join('test', 'nonascii.css'), stats['post_processed']) def test_cache_key_memcache_validation(self): """ From 3047981517ffa0c75c97f05446bd0d41865e323b Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 8 Jul 2012 18:17:53 +0200 Subject: [PATCH 163/519] Fixed #18050 -- Fixed a rather glaring bug in the handling of @import statements when using the cached staticfiles storage. --- django/contrib/staticfiles/storage.py | 21 +++++++++++++------ .../project/documents/cached/import.css | 1 + .../staticfiles_tests/tests.py | 7 +++++++ 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 tests/regressiontests/staticfiles_tests/project/documents/cached/import.css diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index 16d33fff4b..4a6650b193 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -45,10 +45,11 @@ class StaticFilesStorage(FileSystemStorage): class CachedFilesMixin(object): + default_template = """url("%s")""" patterns = ( ("*.css", ( br"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""", - br"""(@import\s*["']\s*(.*?)["'])""", + (br"""(@import\s*["']\s*(.*?)["'])""", """@import url("%s")"""), )), ) @@ -62,8 +63,12 @@ class CachedFilesMixin(object): self._patterns = SortedDict() for extension, patterns in self.patterns: for pattern in patterns: + if isinstance(pattern, (tuple, list)): + pattern, template = pattern + else: + template = self.default_template compiled = re.compile(pattern) - self._patterns.setdefault(extension, []).append(compiled) + self._patterns.setdefault(extension, []).append((compiled, template)) def file_hash(self, name, content=None): """ @@ -140,10 +145,13 @@ class CachedFilesMixin(object): return unquote(final_url) - def url_converter(self, name): + def url_converter(self, name, template=None): """ Returns the custom URL converter for the given file name. """ + if template is None: + template = self.default_template + def converter(matchobj): """ Converts the matched URL depending on the parent level (`..`) @@ -178,7 +186,8 @@ class CachedFilesMixin(object): relative_url = '/'.join(url.split('/')[:-1] + file_name) # Return the hashed version to the file - return 'url("%s")' % unquote(relative_url) + return template % unquote(relative_url) + return converter def post_process(self, paths, dry_run=False, **options): @@ -229,9 +238,9 @@ class CachedFilesMixin(object): # ..to apply each replacement pattern to the content if name in adjustable_paths: content = original_file.read().decode(settings.FILE_CHARSET) - converter = self.url_converter(name) for patterns in self._patterns.values(): - for pattern in patterns: + for pattern, template in patterns: + converter = self.url_converter(name, template) content = pattern.sub(converter, content) if hashed_file_exists: self.delete(hashed_name) diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/import.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/import.css new file mode 100644 index 0000000000..6bc7ce04c4 --- /dev/null +++ b/tests/regressiontests/staticfiles_tests/project/documents/cached/import.css @@ -0,0 +1 @@ +@import 'styles.css'; diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 91435a2c86..9b14c78dc5 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -446,6 +446,13 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, self.assertIn(b'url("img/relative.acae32e4532b.png")', content) self.assertIn(b"../cached/styles.93b1147e8552.css", content) + def test_import_replacement(self): + "See #18050" + relpath = self.cached_file_path("cached/import.css") + self.assertEqual(relpath, "cached/import.2b1d40b0bbd4.css") + with storage.staticfiles_storage.open(relpath) as relfile: + self.assertIn(b"""import url("styles.93b1147e8552.css")""", relfile.read()) + def test_template_tag_deep_relative(self): relpath = self.cached_file_path("cached/css/window.css") self.assertEqual(relpath, "cached/css/window.9db38d5169f3.css") From 1aa0d8ac4d3149fad6ebe80834d9e6f83021cf55 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 8 Jul 2012 18:25:12 +0200 Subject: [PATCH 164/519] Fixed #18487 -- Made sure that protocol-relative URLs aren't processed by the cached staticfiles storage. Thanks to LukaszBalcerzak for the patch. --- django/contrib/staticfiles/storage.py | 2 +- .../project/documents/cached/css/ignored.css | 8 ++++++++ tests/regressiontests/staticfiles_tests/tests.py | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/regressiontests/staticfiles_tests/project/documents/cached/css/ignored.css diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index 4a6650b193..47132831eb 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -161,7 +161,7 @@ class CachedFilesMixin(object): matched, url = matchobj.groups() # Completely ignore http(s) prefixed URLs, # fragments and data-uri URLs - if url.startswith(('#', 'http:', 'https:', 'data:')): + if url.startswith(('#', 'http:', 'https:', 'data:', '//')): return matched name_parts = name.split(os.sep) # Using posix normpath here to remove duplicates diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/ignored.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/ignored.css new file mode 100644 index 0000000000..fe7b022215 --- /dev/null +++ b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/ignored.css @@ -0,0 +1,8 @@ +body { + background: url("#foobar"); + background: url("http:foobar"); + background: url("https:foobar"); + background: url("data:foobar"); + background: url("//foobar"); +} + diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 9b14c78dc5..812a80a583 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -387,6 +387,17 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, self.assertNotIn(b"cached/other.css", content) self.assertIn(b"other.d41d8cd98f00.css", content) + def test_path_ignored_completely(self): + relpath = self.cached_file_path("cached/css/ignored.css") + self.assertEqual(relpath, "cached/css/ignored.6c77f2643390.css") + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + self.assertIn(b'#foobar', content) + self.assertIn(b'http:foobar', content) + self.assertIn(b'https:foobar', content) + self.assertIn(b'data:foobar', content) + self.assertIn(b'//foobar', content) + def test_path_with_querystring(self): relpath = self.cached_file_path("cached/styles.css?spam=eggs") self.assertEqual(relpath, From 146aff3bac974e56ec8cb597c2720d1cd4f77b26 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 7 Jul 2012 18:39:09 +0200 Subject: [PATCH 165/519] Fixed #18590 - Reverted Python 2.4 workaround for Model pickling Revert of 08d521efa0. Refs #10547, #12121. Thanks Michal Petrucha for the report. --- django/db/models/base.py | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 82283d591c..79af1cb34c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -16,7 +16,7 @@ from django.db.models.fields.related import (ManyToOneRel, from django.db import (router, transaction, DatabaseError, DEFAULT_DB_ALIAS) from django.db.models.query import Q -from django.db.models.query_utils import DeferredAttribute +from django.db.models.query_utils import DeferredAttribute, deferred_class_factory from django.db.models.deletion import Collector from django.db.models.options import Options from django.db.models import signals @@ -400,25 +400,16 @@ class Model(object): need to do things manually, as they're dynamically created classes and only module-level classes can be pickled by the default path. """ + if not self._deferred: + return super(Model, self).__reduce__() data = self.__dict__ - model = self.__class__ - # The obvious thing to do here is to invoke super().__reduce__() - # for the non-deferred case. Don't do that. - # On Python 2.4, there is something weird with __reduce__, - # and as a result, the super call will cause an infinite recursion. - # See #10547 and #12121. defers = [] - if self._deferred: - from django.db.models.query_utils import deferred_class_factory - factory = deferred_class_factory - for field in self._meta.fields: - if isinstance(self.__class__.__dict__.get(field.attname), - DeferredAttribute): - defers.append(field.attname) - model = self._meta.proxy_for_model - else: - factory = simple_class_factory - return (model_unpickle, (model, defers, factory), data) + for field in self._meta.fields: + if isinstance(self.__class__.__dict__.get(field.attname), + DeferredAttribute): + defers.append(field.attname) + model = self._meta.proxy_for_model + return (model_unpickle, (model, defers), data) def _get_pk_val(self, meta=None): if not meta: @@ -926,20 +917,11 @@ def get_absolute_url(opts, func, self, *args, **kwargs): class Empty(object): pass -def simple_class_factory(model, attrs): - """Used to unpickle Models without deferred fields. - - We need to do this the hard way, rather than just using - the default __reduce__ implementation, because of a - __deepcopy__ problem in Python 2.4 - """ - return model - -def model_unpickle(model, attrs, factory): +def model_unpickle(model, attrs): """ Used to unpickle Model subclasses with deferred fields. """ - cls = factory(model, attrs) + cls = deferred_class_factory(model, attrs) return cls.__new__(cls) model_unpickle.__safe_for_unpickle__ = True From d44aa98184ced66d95ba95d0e4a255d09c15df0c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 8 Jul 2012 18:35:17 -0400 Subject: [PATCH 166/519] Fixed #18173 - Corrected ModelAdmin documentation for get_changelist. Thanks Keryn Knight for the report and vanessagomes for the patch. --- docs/ref/contrib/admin/index.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 7aca0981c3..ad4f6ba3cd 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -371,12 +371,6 @@ subclass:: because ``raw_id_fields`` and ``radio_fields`` imply custom widgets of their own. -.. attribute:: ModelAdmin.get_changelist - - Returns the Changelist class to be used for listing. By default, - ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this - class you can change the behavior of the listing. - .. attribute:: ModelAdmin.inlines See :class:`InlineModelAdmin` objects below. @@ -1168,6 +1162,12 @@ templates used by the :class:`ModelAdmin` views: kwargs['choices'] += (('ready', 'Ready for deployment'),) return super(MyModelAdmin, self).formfield_for_choice_field(db_field, request, **kwargs) +.. method:: ModelAdmin.get_changelist(self, request, **kwargs) + + Returns the Changelist class to be used for listing. By default, + ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this + class you can change the behavior of the listing. + .. method:: ModelAdmin.has_add_permission(self, request) Should return ``True`` if adding an object is permitted, ``False`` From 590de18add78945344de049c2d3e7021fd46ce53 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 8 Jul 2012 19:26:53 -0400 Subject: [PATCH 167/519] Fixed #18577 - Clarified middleware initialization. Thanks Lukasz Balcerzak for the patch. --- docs/topics/http/middleware.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index a768a3bbd8..fe92bc59a9 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -199,7 +199,8 @@ of caveats: define ``__init__`` as requiring any arguments. * Unlike the ``process_*`` methods which get called once per request, - ``__init__`` gets called only *once*, when the Web server starts up. + ``__init__`` gets called only *once*, when the Web server responds to the + first request. Marking middleware as unused ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 1d2982362df1dbd9b08ffcc1d2506b2e3789250e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 9 Jul 2012 13:58:07 +0200 Subject: [PATCH 168/519] Fixed #18537 -- Fixed CUIT calculation in ar localflavor Thanks mmoya at 8ksoft.com.ar for the report and Kevin Shaul for the initial patch. --- django/contrib/localflavor/ar/forms.py | 9 ++++++++- tests/regressiontests/localflavor/ar/tests.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/django/contrib/localflavor/ar/forms.py b/django/contrib/localflavor/ar/forms.py index 8e252beec9..ffc2d177c0 100644 --- a/django/contrib/localflavor/ar/forms.py +++ b/django/contrib/localflavor/ar/forms.py @@ -105,9 +105,16 @@ class ARCUITField(RegexField): return cuit[:-1], cuit[-1] def _calc_cd(self, cuit): + # Calculation code based on: + # http://es.wikipedia.org/wiki/C%C3%B3digo_%C3%9Anico_de_Identificaci%C3%B3n_Tributaria mults = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2) tmp = sum([m * int(cuit[idx]) for idx, m in enumerate(mults)]) - return str(11 - tmp % 11) + result = 11 - (tmp % 11) + if result == 11: + result = 0 + elif result == 10: + result = 9 + return str(result) def _format(self, cuit, check_digit=None): if check_digit == None: diff --git a/tests/regressiontests/localflavor/ar/tests.py b/tests/regressiontests/localflavor/ar/tests.py index 0731c3ce9b..efc2025fd2 100644 --- a/tests/regressiontests/localflavor/ar/tests.py +++ b/tests/regressiontests/localflavor/ar/tests.py @@ -87,6 +87,7 @@ class ARLocalFlavorTests(SimpleTestCase): '27-10345678-4': '27-10345678-4', '20101234569': '20-10123456-9', '27103456784': '27-10345678-4', + '30011111110': '30-01111111-0', } invalid = { '2-10123456-9': error_format, From d9db1d3373e9bee37258d7f6b50bea80c767a28c Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 9 Jul 2012 14:07:01 +0200 Subject: [PATCH 169/519] Added supplementary check for CUIT number of ar localflavor Thanks Kevin Schaul for the initial patch. --- django/contrib/localflavor/ar/forms.py | 3 +++ tests/regressiontests/localflavor/ar/tests.py | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/django/contrib/localflavor/ar/forms.py b/django/contrib/localflavor/ar/forms.py index ffc2d177c0..dc4235f9dd 100644 --- a/django/contrib/localflavor/ar/forms.py +++ b/django/contrib/localflavor/ar/forms.py @@ -81,6 +81,7 @@ class ARCUITField(RegexField): default_error_messages = { 'invalid': _('Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'), 'checksum': _("Invalid CUIT."), + 'legal_type': _('Invalid legal type. Type must be 27, 20, 23 or 30.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -96,6 +97,8 @@ class ARCUITField(RegexField): if value in EMPTY_VALUES: return '' value, cd = self._canon(value) + if not value[:2] in ['27', '20', '23', '30']: + raise ValidationError(self.error_messages['legal_type']) if self._calc_cd(value) != cd: raise ValidationError(self.error_messages['checksum']) return self._format(value, cd) diff --git a/tests/regressiontests/localflavor/ar/tests.py b/tests/regressiontests/localflavor/ar/tests.py index efc2025fd2..82c2bd491a 100644 --- a/tests/regressiontests/localflavor/ar/tests.py +++ b/tests/regressiontests/localflavor/ar/tests.py @@ -81,6 +81,7 @@ class ARLocalFlavorTests(SimpleTestCase): def test_ARCUITField(self): error_format = ['Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] error_invalid = ['Invalid CUIT.'] + error_legal_type = [u'Invalid legal type. Type must be 27, 20, 23 or 30.'] valid = { '20-10123456-9': '20-10123456-9', '20-10123456-9': '20-10123456-9', @@ -94,8 +95,9 @@ class ARLocalFlavorTests(SimpleTestCase): '210123456-9': error_format, '20-10123456': error_format, '20-10123456-': error_format, - '20-10123456-5': error_invalid, - '27-10345678-1': error_invalid, - '27-10345678-1': error_invalid, + '20-10123456-5': error_invalid, + '27-10345678-1': error_invalid, + '27-10345678-1': error_invalid, + '11211111110': error_legal_type, } self.assertFieldOutput(ARCUITField, valid, invalid) From 828f7b62e8d29f796403606a797d7aec6da98647 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 10 Jul 2012 13:22:55 +0200 Subject: [PATCH 170/519] Fixed #18602 -- Improved error message when database NAME is missing Thanks Kristian Glass for the report. --- django/db/backends/postgresql_psycopg2/base.py | 6 ++++-- django/db/backends/sqlite3/base.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 61be680d83..f76b91ddd6 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -157,9 +157,11 @@ class DatabaseWrapper(BaseDatabaseWrapper): def _cursor(self): settings_dict = self.settings_dict if self.connection is None: - if settings_dict['NAME'] == '': + if not settings_dict['NAME']: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("You need to specify NAME in your Django settings file.") + raise ImproperlyConfigured( + "settings.DATABASES is improperly configured. " + "Please supply the NAME value.") conn_params = { 'database': settings_dict['NAME'], } diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index c59905b29a..d7f51605d5 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -250,7 +250,9 @@ class DatabaseWrapper(BaseDatabaseWrapper): settings_dict = self.settings_dict if not settings_dict['NAME']: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("Please fill out the database NAME in the settings module before using the database.") + raise ImproperlyConfigured( + "settings.DATABASES is improperly configured. " + "Please supply the NAME value.") kwargs = { 'database': settings_dict['NAME'], 'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES, From 5664338e22c1312e68293181a41b290434b2018a Mon Sep 17 00:00:00 2001 From: Stefan Kjartansson Date: Tue, 10 Jul 2012 15:27:50 +0000 Subject: [PATCH 171/519] typo in "django/docs/topics/python3.txt" --- docs/topics/python3.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 974ddb0e88..1aea252e3f 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -181,7 +181,7 @@ xrange range xrange =============================== ====================================== ====================== -Ouptut encoding now Unicode +Output encoding now Unicode =========================== If you want to catch stdout/stderr output, the output content is UTF-8 encoded From 76d5daa60f90d3692b3ff3b7f5054e4bc7c1f374 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 10 Jul 2012 14:26:42 -0700 Subject: [PATCH 172/519] Changed `manage.py shell`'s help text to reflect that it can invoke bpython. --- django/core/management/commands/shell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 26cbd7f005..075efa0bcd 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -5,9 +5,9 @@ from optparse import make_option class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( make_option('--plain', action='store_true', dest='plain', - help='Tells Django to use plain Python, not IPython.'), + help='Tells Django to use plain Python, not IPython or bpython.'), ) - help = "Runs a Python interactive interpreter. Tries to use IPython, if it's available." + help = "Runs a Python interactive interpreter. Tries to use IPython or bpython, if one of them is available." shells = ['ipython', 'bpython'] requires_model_validation = False From fe443b11def46828a140bdd5521807e6a6c27bf8 Mon Sep 17 00:00:00 2001 From: mitnk Date: Wed, 11 Jul 2012 10:57:26 +0800 Subject: [PATCH 173/519] fixed a typo in timezones docs. --- docs/topics/i18n/timezones.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index 1d9dd4b3c6..f3bb13ab03 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -509,7 +509,7 @@ Setup Finally, our calendar system contains interesting traps for computers:: >>> import datetime - >>> def substract_one_year(value): # DON'T DO THAT! + >>> def one_year_before(value): # DON'T DO THAT! ... return value.replace(year=value.year - 1) >>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0)) datetime.datetime(2011, 3, 1, 10, 0) From fb8ec2855bac1fd335a00ac5b550ae54aea3100f Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 12 Jul 2012 09:22:09 -0700 Subject: [PATCH 174/519] Remove some code that has been dead since newforms-admin was merged, many moons ago. --- django/db/models/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 3582720e55..2ad89b3ed5 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -15,8 +15,6 @@ from django.db.models.deletion import CASCADE, PROTECT, SET, SET_NULL, SET_DEFAU from django.db.models import signals from django.utils.decorators import wraps -# Admin stages. -ADD, CHANGE, BOTH = 1, 2, 3 def permalink(func): """ From e806f047f3e54a0c3830cf669935a8f12854990b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 12 Jul 2012 18:49:32 +0200 Subject: [PATCH 175/519] Removed old gis install instructions for obsolete distros --- docs/ref/contrib/gis/install.txt | 50 -------------------------------- 1 file changed, 50 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 805772fa88..b6122b8289 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -1034,61 +1034,11 @@ Optional packages to consider: do not plan on doing any database transformation of geometries to the Google projection (900913). -.. _heron: - -8.04 and lower -~~~~~~~~~~~~~~ - -The 8.04 (and lower) versions of Ubuntu use GEOS v2.2.3 in their binary packages, -which is incompatible with GeoDjango. Thus, do *not* use the binary packages -for GEOS or PostGIS and build some prerequisites from source, per the instructions -in this document; however, it is okay to use the PostgreSQL binary packages. - -For more details, please see the Debian instructions for :ref:`etch` below. - .. _debian: Debian ------ -.. _etch: - -4.0 (Etch) -^^^^^^^^^^ - -The situation here is the same as that of Ubuntu :ref:`heron` -- in other words, -some packages must be built from source to work properly with GeoDjango. - -Binary packages -~~~~~~~~~~~~~~~ -The following command will install acceptable binary packages, as well as -the development tools necessary to build the rest of the requirements: - -.. code-block:: bash - - $ sudo apt-get install binutils bzip2 gcc g++ flex make postgresql-8.1 \ - postgresql-server-dev-8.1 python-ctypes python-psycopg2 python-setuptools - -Required package information: - -* ``binutils``: for ctypes to find libraries -* ``bzip2``: for decompressing the source packages -* ``gcc``, ``g++``, ``make``: GNU developer tools used to compile the libraries -* ``flex``: required to build PostGIS -* ``postgresql-8.1`` -* ``postgresql-server-dev-8.1``: for ``pg_config`` -* ``python-psycopg2`` - -Optional packages: - -* ``libgeoip``: for :ref:`GeoIP ` support - -Source packages -~~~~~~~~~~~~~~~ -You will still have to install :ref:`geosbuild`, :ref:`proj4`, -:ref:`postgis`, and :ref:`gdalbuild` from source. Please follow the -directions carefully. - .. _lenny: 5.0 (Lenny) From 8c670ee34714acffbc64e5cafd1e664fb8341a37 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 12 Jul 2012 23:02:58 +0200 Subject: [PATCH 176/519] Fixed #18617 -- Highlighted that the app_directories template loader depends on the order of INSTALLED_APPS. Thanks evildmp for the patch. --- docs/ref/templates/api.txt | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index ec01fe2faa..bd2b4c6e9d 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -649,14 +649,24 @@ class. Here are the template loaders that come with Django: INSTALLED_APPS = ('myproject.polls', 'myproject.music') - ...then ``get_template('foo.html')`` will look for templates in these + ...then ``get_template('foo.html')`` will look for ``foo.html`` in these directories, in this order: - * ``/path/to/myproject/polls/templates/foo.html`` - * ``/path/to/myproject/music/templates/foo.html`` + * ``/path/to/myproject/polls/templates/`` + * ``/path/to/myproject/music/templates/`` - Note that the loader performs an optimization when it is first imported: It - caches a list of which :setting:`INSTALLED_APPS` packages have a + ... and will use the one it finds first. + + The order of :setting:`INSTALLED_APPS` is significant! For example, if you + want to customize the Django admin, you might choose to override the + standard ``admin/base_site.html`` template, from ``django.contrib.admin``, + with your own ``admin/base_site.html`` in ``myproject.polls``. You must + then make sure that your ``myproject.polls`` comes *before* + ``django.contrib.admin`` in :setting:`INSTALLED_APPS`, otherwise + ``django.contrib.admin``'s will be loaded first and yours will be ignored. + + Note that the loader performs an optimization when it is first imported: + it caches a list of which :setting:`INSTALLED_APPS` packages have a ``templates`` subdirectory. This loader is enabled by default. From 18b9dc41543616ba5b15d0400564e665b76701d1 Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Fri, 13 Jul 2012 17:30:45 +0200 Subject: [PATCH 177/519] Fixed #18601 -- Specified that Python minimum version is 2.6.5 This is due to a bug in previous Python 2.6 versions related to unicode keyword arguments. --- docs/faq/install.txt | 6 +++--- docs/intro/install.txt | 2 +- docs/releases/1.5.txt | 2 +- docs/topics/install.txt | 2 +- docs/topics/logging.txt | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/faq/install.txt b/docs/faq/install.txt index e2ecfb4717..a14615e47c 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -16,8 +16,8 @@ How do I get started? What are Django's prerequisites? -------------------------------- -Django requires Python_, specifically Python 2.6 or 2.7. -No other Python libraries are required for basic Django usage. +Django requires Python_, specifically Python 2.6.5 - 2.7.x. No other Python +libraries are required for basic Django usage. For a development environment -- if you just want to experiment with Django -- you don't need to have a separate Web server installed; Django comes with its @@ -42,7 +42,7 @@ Do I lose anything by using Python 2.6 versus newer Python versions, such as Pyt ---------------------------------------------------------------------------------------- Not in the core framework. Currently, Django itself officially supports -Python 2.6 and 2.7. However, newer versions of +Python 2.6 (2.6.5 or higher) and 2.7. However, newer versions of Python are often faster, have more features, and are better supported. If you use a newer version of Python you will also have access to some APIs that aren't available under older versions of Python. diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 41339b5f11..7e8c7db7b3 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -10,7 +10,7 @@ Install Python -------------- Being a Python Web framework, Django requires Python. It works with any Python -version from 2.6 to 2.7 (due to backwards incompatibilities in Python 3.0, +version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with Python 3.0; see :doc:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition), these versions of Python include a lightweight database called diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 944f19f03b..33ea0cb4a2 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -16,7 +16,7 @@ features`_. Python compatibility ==================== -Django 1.5 has dropped support for Python 2.5. Python 2.6 is now the minimum +Django 1.5 has dropped support for Python 2.5. Python 2.6.5 is now the minimum required Python version. Django is tested and supported on Python 2.6 and 2.7. diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 4b0aca3548..291b22cb3e 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -9,7 +9,7 @@ Install Python Being a Python Web framework, Django requires Python. -It works with any Python version from 2.6 to 2.7 (due to backwards +It works with any Python version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with Python 3.0; see :doc:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition). diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index aa2afba760..a878d42266 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -209,8 +209,8 @@ By default, Django uses the `dictConfig format`_. ``logging.dictConfig`` is a builtin library in Python 2.7. In order to make this library available for users of earlier Python versions, Django includes a copy as part of ``django.utils.log``. - If you have Python 2.7, the system native library will be used; if - you have Python 2.6 or earlier, Django's copy will be used. + If you have Python 2.7 or later, the system native library will be used; if + you have Python 2.6, Django's copy will be used. In order to configure logging, you use :setting:`LOGGING` to define a dictionary of logging settings. These settings describes the loggers, From b6215f68100884600fb23bc2a16b9a8c27dd1d6b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 13 Jul 2012 17:49:50 +0200 Subject: [PATCH 178/519] Updated links for MacOSX python installers --- docs/ref/contrib/gis/install.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index b6122b8289..d7deb17d49 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -762,13 +762,13 @@ Python ^^^^^^ Although OS X comes with Python installed, users can use framework -installers (`2.5`__ and `2.6`__ are available) provided by +installers (`2.6`__ and `2.7`__ are available) provided by the Python Software Foundation. An advantage to using the installer is that OS X's Python will remain "pristine" for internal operating system use. -__ http://python.org/ftp/python/2.5.4/python-2.5.4-macosx.dmg -__ http://python.org/ftp/python/2.6.2/python-2.6.2-macosx2009-04-16.dmg +__ http://python.org/ftp/python/2.6.6/python-2.6.6-macosx10.3.dmg +__ http://python.org/ftp/python/2.7.3/ .. note:: From c13a98968e25c84b418c779004b3c8b0cdc81355 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Jul 2012 12:31:34 +0200 Subject: [PATCH 179/519] Fixed a misplaced Sphinx reference. --- docs/topics/db/transactions.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 1f15949000..9928354664 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -54,8 +54,6 @@ The various cache middlewares are an exception: Even when using database caching, Django's cache backend uses its own database cursor (which is mapped to its own database connection internally). -.. _transaction-management-functions: - .. note:: The ``TransactionMiddleware`` only affects the database aliased @@ -63,6 +61,8 @@ database cursor (which is mapped to its own database connection internally). multiple databases and want transaction control over databases other than "default", you will need to write your own transaction middleware. +.. _transaction-management-functions: + Controlling transaction management in views =========================================== From 9a25d8618a6a3fa91605b17b55f9f89fddee9b8f Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Jul 2012 12:32:05 +0200 Subject: [PATCH 180/519] Update links to diveintomark.org. These pages are gone. --- django/utils/feedgenerator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index 52f40fb5f1..3dc66a68c6 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -19,7 +19,7 @@ Sample usage: ... feed.write(fp, 'utf-8') For definitions of the different versions of RSS, see: -http://diveintomark.org/archives/2004/02/04/incompatible-rss +http://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/02/04/incompatible-rss """ from __future__ import unicode_literals @@ -65,7 +65,7 @@ def get_tag_uri(url, date): """ Creates a TagURI. - See http://diveintomark.org/archives/2004/05/28/howto-atom-id + See http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id """ bits = urlparse.urlparse(url) d = '' From 8f002867b2aa414c4f98bbd7992e3b44825b228a Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Jul 2012 14:07:11 -0700 Subject: [PATCH 181/519] Cleaned up the QueryDict implementation. - Use super(). - Don't poke at internals. - Don't override methods for no reason. --- django/http/__init__.py | 45 ++++++++------------- django/utils/datastructures.py | 1 - tests/regressiontests/httpwrappers/tests.py | 2 +- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 51e6ca2304..7ded554665 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, unicode_literals +import copy import datetime import os import re @@ -360,6 +361,7 @@ class HttpRequest(object): def readlines(self): return list(iter(self)) + class QueryDict(MultiValueDict): """ A specialized MultiValueDict that takes a query string when initialized. @@ -374,7 +376,7 @@ class QueryDict(MultiValueDict): _encoding = None def __init__(self, query_string, mutable=False, encoding=None): - MultiValueDict.__init__(self) + super(QueryDict, self).__init__() if not encoding: encoding = settings.DEFAULT_CHARSET self.encoding = encoding @@ -401,7 +403,7 @@ class QueryDict(MultiValueDict): self._assert_mutable() key = str_to_unicode(key, self.encoding) value = str_to_unicode(value, self.encoding) - MultiValueDict.__setitem__(self, key, value) + super(QueryDict, self).__setitem__(key, value) def __delitem__(self, key): self._assert_mutable() @@ -409,64 +411,50 @@ class QueryDict(MultiValueDict): def __copy__(self): result = self.__class__('', mutable=True, encoding=self.encoding) - for key, value in dict.items(self): - dict.__setitem__(result, key, value) + for key, value in self.iterlists(): + result.setlist(key, value) return result def __deepcopy__(self, memo): - import copy result = self.__class__('', mutable=True, encoding=self.encoding) memo[id(self)] = result - for key, value in dict.items(self): - dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)) + for key, value in self.iterlists(): + 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_] - MultiValueDict.setlist(self, key, list_) + super(QueryDict, self).setlist(key, list_) - def setlistdefault(self, key, default_list=()): + def setlistdefault(self, key, default_list=None): self._assert_mutable() - if key not in self: - self.setlist(key, default_list) - return MultiValueDict.getlist(self, key) + return super(QueryDict, self).setlistdefault(key, default_list) def appendlist(self, key, value): self._assert_mutable() key = str_to_unicode(key, self.encoding) value = str_to_unicode(value, self.encoding) - MultiValueDict.appendlist(self, key, value) - - def update(self, other_dict): - self._assert_mutable() - f = lambda s: str_to_unicode(s, self.encoding) - if hasattr(other_dict, 'lists'): - for key, valuelist in other_dict.lists(): - for value in valuelist: - MultiValueDict.update(self, {f(key): f(value)}) - else: - d = dict([(f(k), f(v)) for k, v in other_dict.items()]) - MultiValueDict.update(self, d) + super(QueryDict, self).appendlist(key, value) def pop(self, key, *args): self._assert_mutable() - return MultiValueDict.pop(self, key, *args) + return super(QueryDict, self).pop(key, *args) def popitem(self): self._assert_mutable() - return MultiValueDict.popitem(self) + return super(QueryDict, self).popitem() def clear(self): self._assert_mutable() - MultiValueDict.clear(self) + super(QueryDict, self).clear() def setdefault(self, key, default=None): self._assert_mutable() key = str_to_unicode(key, self.encoding) default = str_to_unicode(default, self.encoding) - return MultiValueDict.setdefault(self, key, default) + return super(QueryDict, self).setdefault(key, default) def copy(self): """Returns a mutable copy of this object.""" @@ -499,6 +487,7 @@ class QueryDict(MultiValueDict): for v in list_]) return '&'.join(output) + def parse_cookie(cookie): if cookie == '': return {} diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index f7042f7061..9a41b4311c 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -334,7 +334,6 @@ class MultiValueDict(dict): if default_list is None: default_list = [] self.setlist(key, default_list) - return default_list return self.getlist(key) def appendlist(self, key, value): diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 870324bffb..cc55057d31 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -189,7 +189,7 @@ class QueryDictTests(unittest.TestCase): self.assertEqual(q == q1, True) q = QueryDict('a=b&c=d&a=1') q1 = pickle.loads(pickle.dumps(q, 2)) - self.assertEqual(q == q1 , True) + self.assertEqual(q == q1, True) def test_update_from_querydict(self): """Regression test for #8278: QueryDict.update(QueryDict)""" From 3e8d8bb094b9f765d94ac033b9135bcb81b816d2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Jul 2012 14:33:13 -0700 Subject: [PATCH 182/519] Fixed auth to not use an internal implementation detail of SortedDict --- django/contrib/auth/forms.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index d2cb039e68..d17c41132e 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django import forms from django.forms.util import flatatt from django.template import loader +from django.utils.datastructures import SortedDict from django.utils.html import format_html, format_html_join from django.utils.http import int_to_base36 from django.utils.safestring import mark_safe @@ -14,6 +15,7 @@ from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, i from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import get_current_site + UNMASKED_DIGITS_TO_SHOW = 6 mask_password = lambda p: "%s%s" % (p[:UNMASKED_DIGITS_TO_SHOW], "*" * max(len(p) - UNMASKED_DIGITS_TO_SHOW, 0)) @@ -293,8 +295,11 @@ class PasswordChangeForm(SetPasswordForm): raise forms.ValidationError( self.error_messages['password_incorrect']) return old_password -PasswordChangeForm.base_fields.keyOrder = ['old_password', 'new_password1', - 'new_password2'] + +PasswordChangeForm.base_fields = SortedDict([ + (k, PasswordChangeForm.base_fields[k]) + for k in ['old_password', 'new_password1', 'new_password2'] +]) class AdminPasswordChangeForm(forms.Form): From 9877d25dc67b9c852e1a69ece6153894d4c9d1d1 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Jul 2012 16:02:30 -0700 Subject: [PATCH 183/519] Switched from usign a method that was about to be deprecated to normal code. --- django/db/models/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/models/options.py b/django/db/models/options.py index 44f8891942..767b625c1d 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -126,7 +126,7 @@ class Options(object): if self.parents: # Promote the first parent link in lieu of adding yet another # field. - field = self.parents.value_for_index(0) + field = next(self.parents.itervalues()) # Look for a local field with the same name as the # first parent link. If a local field has already been # created, use it instead of promoting the parent From 8b3c2f2c51183bde47bd1e98057b77866f35dd6e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Jul 2012 16:08:42 -0700 Subject: [PATCH 184/519] Deprecate two methods (which I seriously doubt anyone ever used, but they were documented so...) because they cannot be implemented efficiently on top of collections.SortedDict in Python 2.7 and up. --- django/utils/datastructures.py | 9 +++++++++ docs/ref/utils.txt | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 9a41b4311c..832b16c3d4 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,6 +1,8 @@ import copy +import warning from types import GeneratorType + class MergeDict(object): """ A simple class for creating new "virtual" dictionaries that actually look @@ -191,10 +193,17 @@ class SortedDict(dict): def value_for_index(self, index): """Returns the value of the item at the given zero-based index.""" + # This, and insert() are deprecated because they cannot be implemented + # using collections.OrderedDict (Python 2.7 and up), which we'll + # eventually switch to + warning.warn(PendingDeprecationWarning, + "SortedDict.value_for_index is deprecated", stacklevel=2) return self[self.keyOrder[index]] def insert(self, index, key, value): """Inserts the key, value pair before the item with the given index.""" + warning.warn(PendingDeprecationWarning, + "SortedDict.insert is deprecated", stacklevel=2) if key in self.keyOrder: n = self.keyOrder.index(key) del self.keyOrder[n] diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index c74392df36..c2f2025bc3 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -112,10 +112,14 @@ to distinguish caches by the ``Accept-language`` header. .. method:: insert(index, key, value) + .. deprecated:: 1.5 + Inserts the key, value pair before the item with the given index. .. method:: value_for_index(index) + .. deprecated:: 1.5 + Returns the value of the item at the given zero-based index. Creating a new SortedDict From 0f57935bcd8cd525d8d661c5af4fb70b79e126ae Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Jul 2012 17:08:52 -0700 Subject: [PATCH 185/519] Fix an incredibly emberassing typo. --- django/utils/datastructures.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 832b16c3d4..69be07ba7e 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,5 +1,5 @@ import copy -import warning +import warnings from types import GeneratorType @@ -196,13 +196,13 @@ class SortedDict(dict): # This, and insert() are deprecated because they cannot be implemented # using collections.OrderedDict (Python 2.7 and up), which we'll # eventually switch to - warning.warn(PendingDeprecationWarning, + warnings.warn(PendingDeprecationWarning, "SortedDict.value_for_index is deprecated", stacklevel=2) return self[self.keyOrder[index]] def insert(self, index, key, value): """Inserts the key, value pair before the item with the given index.""" - warning.warn(PendingDeprecationWarning, + warnings.warn(PendingDeprecationWarning, "SortedDict.insert is deprecated", stacklevel=2) if key in self.keyOrder: n = self.keyOrder.index(key) From c57abd3c29cedcca00821d2a0d5708f10977f3c6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Jul 2012 19:04:37 -0700 Subject: [PATCH 186/519] Remove DotExpandedDict, which was undocumented and unused. --- django/utils/datastructures.py | 32 ------------------- tests/regressiontests/utils/datastructures.py | 18 ++--------- tests/regressiontests/utils/tests.py | 2 +- 3 files changed, 3 insertions(+), 49 deletions(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 69be07ba7e..16741ba36b 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -412,38 +412,6 @@ class MultiValueDict(dict): """ return dict((key, self[key]) for key in self) -class DotExpandedDict(dict): - """ - A special dictionary constructor that takes a dictionary in which the keys - may contain dots to specify inner dictionaries. It's confusing, but this - example should make sense. - - >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \ - 'person.1.lastname': ['Willison'], \ - 'person.2.firstname': ['Adrian'], \ - 'person.2.lastname': ['Holovaty']}) - >>> d - {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}} - >>> d['person'] - {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}} - >>> d['person']['1'] - {'lastname': ['Willison'], 'firstname': ['Simon']} - - # Gotcha: Results are unpredictable if the dots are "uneven": - >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1}) - {'c': 1} - """ - def __init__(self, key_to_list_mapping): - for k, v in key_to_list_mapping.items(): - current = self - bits = k.split('.') - for bit in bits[:-1]: - current = current.setdefault(bit, {}) - # Now assign value to current position - try: - current[bits[-1]] = v - except TypeError: # Special-case if current isn't a dict. - current = {bits[-1]: v} class ImmutableList(tuple): """ diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index 000f7f76a1..1af5018f3b 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -6,8 +6,8 @@ import copy import pickle from django.test import SimpleTestCase -from django.utils.datastructures import (DictWrapper, DotExpandedDict, - ImmutableList, MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) +from django.utils.datastructures import (DictWrapper, ImmutableList, + MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) class SortedDictTests(SimpleTestCase): @@ -251,20 +251,6 @@ class MultiValueDictTests(SimpleTestCase): self.assertEqual({}, MultiValueDict().dict()) -class DotExpandedDictTests(SimpleTestCase): - - def test_dotexpandeddict(self): - - d = DotExpandedDict({'person.1.firstname': ['Simon'], - 'person.1.lastname': ['Willison'], - 'person.2.firstname': ['Adrian'], - 'person.2.lastname': ['Holovaty']}) - - self.assertEqual(d['person']['1']['lastname'], ['Willison']) - self.assertEqual(d['person']['2']['lastname'], ['Holovaty']) - self.assertEqual(d['person']['2']['firstname'], ['Adrian']) - - class ImmutableListTests(SimpleTestCase): def test_sort(self): diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index f5ca06ef1e..a7deeeefae 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -16,7 +16,7 @@ from .decorators import DecoratorFromMiddlewareTests from .functional import FunctionalTestCase from .timesince import TimesinceTests from .datastructures import (MultiValueDictTests, SortedDictTests, - DictWrapperTests, ImmutableListTests, DotExpandedDictTests, MergeDictTests) + DictWrapperTests, ImmutableListTests, MergeDictTests) from .tzinfo import TzinfoTests from .datetime_safe import DatetimeTests from .baseconv import TestBaseConv From fb46f243b4c48ab42ed2f33a2636f7c33f5861a9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 15 Jul 2012 11:19:00 +0200 Subject: [PATCH 187/519] Fixed #18625 -- Removed old-style use of url tag from the documentation. --- docs/ref/contrib/comments/index.txt | 2 +- docs/topics/http/urls.txt | 4 ++-- docs/topics/i18n/translation.txt | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 40b1b662b7..af937e036e 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -250,7 +250,7 @@ Redirecting after the comment post To specify the URL you want to redirect to after the comment has been posted, you can include a hidden form input called ``next`` in your comment form. For example:: - + .. _notes-on-the-comment-form: diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 2310fac413..4e75dfe55f 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -704,8 +704,8 @@ target each pattern individually by using its name: .. code-block:: html+django - {% url arch-summary 1945 %} - {% url full-archive 2007 %} + {% url 'arch-summary' 1945 %} + {% url 'full-archive' 2007 %} Even though both URL patterns refer to the ``archive`` view here, using the ``name`` parameter to ``url()`` allows you to tell them apart in templates. diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index ec6159d538..c912bf9575 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -596,7 +596,7 @@ apply. Reverse URL lookups cannot be carried out within the ``blocktrans`` and should be retrieved (and stored) beforehand:: - {% url path.to.view arg arg2 as the_url %} + {% url 'path.to.view' arg arg2 as the_url %} {% blocktrans %} This is a URL: {{ the_url }} {% endblocktrans %} @@ -790,7 +790,7 @@ To use the catalog, just pull in the dynamically generated script like this: .. code-block:: html+django - + This uses reverse URL lookup to find the URL of the JavaScript catalog view. When the catalog is loaded, your JavaScript code can use the standard @@ -992,7 +992,7 @@ template tag. It enables the given language in the enclosed template section: {% trans "View this category in:" %} {% for lang_code, lang_name in languages %} {% language lang_code %} - {{ lang_name }} + {{ lang_name }} {% endlanguage %} {% endfor %} From bf9d5eff4cd74ffc8fcd1f610587e5ad00dc7f3f Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 15 Jul 2012 11:25:13 +0200 Subject: [PATCH 188/519] Fixed #18626 -- rst syntax collision. --- docs/howto/initial-data.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/howto/initial-data.txt b/docs/howto/initial-data.txt index e5a4957cb2..eca2e2c4f9 100644 --- a/docs/howto/initial-data.txt +++ b/docs/howto/initial-data.txt @@ -67,12 +67,12 @@ And here's that same fixture as YAML: You'll store this data in a ``fixtures`` directory inside your app. -Loading data is easy: just call :djadmin:`manage.py loaddata -`, where ```` is the name of the fixture file you've -created. Each time you run :djadmin:`loaddata`, the data will be read from the -fixture and re-loaded into the database. Note this means that if you change one -of the rows created by a fixture and then run :djadmin:`loaddata` again, you'll -wipe out any changes you've made. +Loading data is easy: just call :djadmin:`manage.py loaddata ` +````, where ```` is the name of the fixture file +you've created. Each time you run :djadmin:`loaddata`, the data will be read +from the fixture and re-loaded into the database. Note this means that if you +change one of the rows created by a fixture and then run :djadmin:`loaddata` +again, you'll wipe out any changes you've made. Automatically loading initial data fixtures ------------------------------------------- From 34340517fc2ac324114791d784502de2a4780757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Sun, 15 Jul 2012 12:24:21 +0300 Subject: [PATCH 189/519] Avoid using a column named "date" in tests Oracle can have problems with such columns. Fixed #17932 again. Thanks to Vinay Sajip for the report. --- tests/regressiontests/admin_changelist/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/regressiontests/admin_changelist/models.py index dcc343bb6a..487db50689 100644 --- a/tests/regressiontests/admin_changelist/models.py +++ b/tests/regressiontests/admin_changelist/models.py @@ -1,7 +1,8 @@ from django.db import models class Event(models.Model): - date = models.DateField() + # Oracle can have problems with a column named "date" + date = models.DateField(db_column="event_date") class Parent(models.Model): name = models.CharField(max_length=128) From cdcdd131da950741fa74debc21bef8632fd3c684 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 15 Jul 2012 21:07:02 +0200 Subject: [PATCH 190/519] Dropped support for GDAL < 1.5 GDAL 1.5 has been released in December 2007. --- django/contrib/gis/gdal/__init__.py | 4 ++-- django/contrib/gis/gdal/geometries.py | 20 +++++--------------- django/contrib/gis/gdal/libgdal.py | 9 +-------- django/contrib/gis/gdal/prototypes/geom.py | 15 +++++---------- django/contrib/gis/gdal/tests/test_geom.py | 5 ----- django/contrib/gis/geos/base.py | 1 - django/contrib/gis/geos/geometry.py | 9 ++++----- django/contrib/gis/geos/tests/test_geos.py | 2 +- django/contrib/gis/tests/test_geoforms.py | 1 + docs/ref/contrib/gis/gdal.txt | 2 +- docs/ref/contrib/gis/install.txt | 8 ++++---- docs/releases/1.5.txt | 5 +++++ 12 files changed, 29 insertions(+), 52 deletions(-) diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index 5c336f3210..adff96b47a 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -37,12 +37,12 @@ try: from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.datasource import DataSource - from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GEOJSON, GDAL_VERSION + from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GDAL_VERSION from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform from django.contrib.gis.gdal.geometries import OGRGeometry HAS_GDAL = True except: - HAS_GDAL, GEOJSON = False, False + HAS_GDAL = False try: from django.contrib.gis.gdal.envelope import Envelope diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index b4d4ad1646..3feb18a923 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -48,7 +48,7 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.geomtype import OGRGeomType -from django.contrib.gis.gdal.libgdal import GEOJSON, GDAL_VERSION +from django.contrib.gis.gdal.libgdal import GDAL_VERSION from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform # Getting the ctypes prototype functions that interface w/the GDAL C library. @@ -97,10 +97,7 @@ class OGRGeometry(GDALBase): else: g = capi.from_wkt(byref(c_char_p(wkt_m.group('wkt'))), None, byref(c_void_p())) elif json_m: - if GEOJSON: - g = capi.from_json(geom_input) - else: - raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') + g = capi.from_json(geom_input) else: # Seeing if the input is a valid short-hand string # (e.g., 'Point', 'POLYGON'). @@ -328,22 +325,15 @@ class OGRGeometry(GDALBase): @property def json(self): """ - Returns the GeoJSON representation of this Geometry (requires - GDAL 1.5+). + Returns the GeoJSON representation of this Geometry. """ - if GEOJSON: - return capi.to_json(self.ptr) - else: - raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.') + return capi.to_json(self.ptr) geojson = json @property def kml(self): "Returns the KML representation of the Geometry." - if GEOJSON: - return capi.to_kml(self.ptr, None) - else: - raise NotImplementedError('KML output only supported on GDAL 1.5+.') + return capi.to_kml(self.ptr, None) @property def wkb_size(self): diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 69643371d2..27a5b8172e 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -19,7 +19,7 @@ elif os.name == 'nt': elif os.name == 'posix': # *NIX library names. lib_names = ['gdal', 'GDAL', 'gdal1.9.0', 'gdal1.8.0', 'gdal1.7.0', - 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] + 'gdal1.6.0', 'gdal1.5.0'] else: raise OGRException('Unsupported OS "%s"' % os.name) @@ -97,10 +97,3 @@ GDAL_MINOR_VERSION = int(_verinfo['minor']) GDAL_SUBMINOR_VERSION = _verinfo['subminor'] and int(_verinfo['subminor']) GDAL_VERSION = (GDAL_MAJOR_VERSION, GDAL_MINOR_VERSION, GDAL_SUBMINOR_VERSION) del _verinfo - -# GeoJSON support is available only in GDAL 1.5+. -if GDAL_VERSION >= (1, 5): - GEOJSON = True -else: - GEOJSON = False - diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index 7fa83910c7..f2c833d576 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -1,6 +1,6 @@ from ctypes import c_char_p, c_double, c_int, c_void_p, POINTER from django.contrib.gis.gdal.envelope import OGREnvelope -from django.contrib.gis.gdal.libgdal import lgdal, GEOJSON +from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.prototypes.errcheck import check_bool, check_envelope from django.contrib.gis.gdal.prototypes.generation import (const_string_output, double_output, geom_output, int_output, srs_output, string_output, void_output) @@ -25,15 +25,10 @@ def topology_func(f): ### OGR_G ctypes function prototypes ### -# GeoJSON routines, if supported. -if GEOJSON: - from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) - to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) - to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True) -else: - from_json = False - to_json = False - to_kml = False +# GeoJSON routines. +from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) +to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) +to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True) # GetX, GetY, GetZ all return doubles. getx = pnt_func(lgdal.OGR_G_GetX) diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index b68aa41b0a..20e25946b0 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -6,7 +6,6 @@ except ImportError: from django.contrib.gis.gdal import (OGRGeometry, OGRGeomType, OGRException, OGRIndexError, SpatialReference, CoordTransform, GDAL_VERSION) -from django.contrib.gis.gdal.prototypes.geom import GEOJSON from django.contrib.gis.geometry.test_data import TestDataMixin from django.utils import unittest @@ -108,7 +107,6 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): def test01e_json(self): "Testing GeoJSON input/output." - if not GEOJSON: return for g in self.geometries.json_geoms: geom = OGRGeometry(g.wkt) if not hasattr(g, 'not_equal'): @@ -244,9 +242,6 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): self.fail('Should have raised an OGRException!') print("\nEND - expecting IllegalArgumentException; safe to ignore.\n") - # Closing the rings -- doesn't work on GDAL versions 1.4.1 and below: - # http://trac.osgeo.org/gdal/ticket/1673 - if GDAL_VERSION <= (1, 4, 1): return poly.close_rings() self.assertEqual(10, poly.point_count) # Two closing points should've been added self.assertEqual(OGRGeometry('POINT(2.5 2.5)'), poly.centroid) diff --git a/django/contrib/gis/geos/base.py b/django/contrib/gis/geos/base.py index 754bcce7ad..fd2693ed59 100644 --- a/django/contrib/gis/geos/base.py +++ b/django/contrib/gis/geos/base.py @@ -10,7 +10,6 @@ except ImportError: # A 'dummy' gdal module. class GDALInfo(object): HAS_GDAL = False - GEOJSON = False gdal = GDALInfo() # NumPy supported? diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index f411d5a353..2d8ac53dbd 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -65,7 +65,7 @@ class GEOSGeometry(GEOSBase, ListMixin): elif hex_regex.match(geo_input): # Handling HEXEWKB input. g = wkb_r().read(geo_input) - elif gdal.GEOJSON and json_regex.match(geo_input): + elif gdal.HAS_GDAL and json_regex.match(geo_input): # Handling GeoJSON input. g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb) else: @@ -409,13 +409,12 @@ class GEOSGeometry(GEOSBase, ListMixin): @property def json(self): """ - Returns GeoJSON representation of this Geometry if GDAL 1.5+ - is installed. + Returns GeoJSON representation of this Geometry if GDAL is installed. """ - if gdal.GEOJSON: + if gdal.HAS_GDAL: return self.ogr.json else: - raise GEOSException('GeoJSON output only supported on GDAL 1.5+.') + raise GEOSException('GeoJSON output only supported when GDAL is installed.') geojson = json @property diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index ed38283dfc..18f1fb65be 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -196,7 +196,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export - @unittest.skipUnless(gdal.HAS_GDAL and gdal.GEOJSON, "gdal >= 1.5 is required") + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_json(self): "Testing GeoJSON input/output (via GDAL)." for g in self.geometries.json_geoms: diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py index ed851df0d2..79a3ae85a2 100644 --- a/django/contrib/gis/tests/test_geoforms.py +++ b/django/contrib/gis/tests/test_geoforms.py @@ -71,6 +71,7 @@ class GeometryFieldTest(unittest.TestCase): for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'): self.assertEqual(GEOSGeometry(wkt), fld.to_python(wkt)) # but raises a ValidationError for any other string + import pdb; pdb.set_trace() for wkt in ('POINT(5)', 'MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'BLAH(0 0, 1 1)'): self.assertRaises(forms.ValidationError, fld.to_python, wkt) diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index 619f23fba2..c4b29bead7 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -447,7 +447,7 @@ systems and coordinate transformation:: This object is a wrapper for the `OGR Geometry`__ class. These objects are instantiated directly from the given ``geom_input`` - parameter, which may be a string containing WKT or HEX, a ``buffer`` + parameter, which may be a string containing WKT, HEX, GeoJSON, a ``buffer`` containing WKB data, or an :class:`OGRGeomType` object. These objects are also returned from the :class:`Feature.geom` attribute, when reading vector data from :class:`Layer` (which is in turn a part of diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index d7deb17d49..5ee6d5153d 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -81,7 +81,7 @@ Program Description Required ======================== ==================================== ================================ ========================== :ref:`GEOS ` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0 `PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.7, 4.6, 4.5, 4.4 -:ref:`GDAL ` Geospatial Data Abstraction Library No (but, required for SQLite) 1.8, 1.7, 1.6, 1.5, 1.4 +:ref:`GDAL ` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6, 1.5 :ref:`GeoIP ` IP-based geolocation library No 1.4 `PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 1.5, 1.4, 1.3 `SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 3.0, 2.4, 2.3 @@ -270,9 +270,9 @@ supports :ref:`GDAL's vector data ` capabilities [#]_. First download the latest GDAL release version and untar the archive:: - $ wget http://download.osgeo.org/gdal/gdal-1.8.1.tar.gz - $ tar xzf gdal-1.8.1.tar.gz - $ cd gdal-1.8.1 + $ wget http://download.osgeo.org/gdal/gdal-1.9.1.tar.gz + $ tar xzf gdal-1.9.1.tar.gz + $ cd gdal-1.9.1 Configure, make and install:: diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 33ea0cb4a2..4275fbae52 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -177,6 +177,11 @@ autocommit behavior was never restored. This bug is now fixed in 1.5. While this is only a bug fix, it is worth checking your applications behavior if you are using PostgreSQL together with the autocommit option. +Miscellaneous +~~~~~~~~~~~~~ + +* GeoDjango dropped support for GDAL < 1.5 + Features deprecated in 1.5 ========================== From 35ddeee45573de57ae3c791bf36496b4a7028ddf Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 15 Jul 2012 21:19:23 +0200 Subject: [PATCH 191/519] Removed debugging line left in previous commit --- django/contrib/gis/tests/test_geoforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py index 79a3ae85a2..ed851df0d2 100644 --- a/django/contrib/gis/tests/test_geoforms.py +++ b/django/contrib/gis/tests/test_geoforms.py @@ -71,7 +71,6 @@ class GeometryFieldTest(unittest.TestCase): for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'): self.assertEqual(GEOSGeometry(wkt), fld.to_python(wkt)) # but raises a ValidationError for any other string - import pdb; pdb.set_trace() for wkt in ('POINT(5)', 'MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'BLAH(0 0, 1 1)'): self.assertRaises(forms.ValidationError, fld.to_python, wkt) From dea554bd9df193ee11b7d924ffa0631fabdfdaee Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Sun, 15 Jul 2012 17:30:39 -0700 Subject: [PATCH 192/519] Added mention in MRO section about method/attribute inheritence. Added simple examples to Generic editing views, added index to Generic editing views and Editing mixins, added missing template_name_suffix attribute to Generic editing views. --- .../ref/class-based-views/generic-editing.txt | 106 +++++++++++++++++- docs/ref/class-based-views/mixins-editing.txt | 10 ++ 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index d5df369fb3..eb286ec3a7 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -2,7 +2,26 @@ Generic editing views ===================== -The views described here provide a foundation for editing content. +The following views are described on this page and provide a foundation for +editing content: + +* :class:`django.views.generic.edit.FormView` +* :class:`django.views.generic.edit.CreateView` +* :class:`django.views.generic.edit.UpdateView` +* :class:`django.views.generic.edit.DeleteView` + +.. note:: Some of the examples on this page assume that a model titled 'authors' + has been defined. For these cases we assume the following has been defined + in `myapps.models.py`:: + + from django import models + from django.core.urlresolvers import reverse + + class Author(models.Model): + name = models.CharField(max_length=200) + + def get_absolute_url(self): + return reverse('author-detail', kwargs={'pk': self.pk}) .. class:: django.views.generic.edit.FormView @@ -11,6 +30,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.FormView` * :class:`django.views.generic.base.TemplateResponseMixin` * :class:`django.views.generic.edit.BaseFormView` @@ -18,6 +39,35 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.edit.ProcessFormView` * :class:`django.views.generic.base.View` + **Example forms.py**:: + + from django import forms + + class ContactForm(forms.Form): + name = forms.CharField() + message = forms.CharField(widget=forms.Textarea) + + def send_email(self): + # send email using the self.cleaned_data dictionary + pass + + **Example views.py**:: + + from myapp.forms import ContactForm + from django.views.generic.edit import FormView + + class ContactView(FormView): + template_name = 'contact.html' + form_class = ContactForm + success_url = '/thanks/' + + def form_valid(self, form): + # This method is called when valid form data has been POSTed. + # It should return an HttpResponse. + form.send_email() + return super(ContactView, self).form_valid(form) + + .. class:: django.views.generic.edit.CreateView A view that displays a form for creating an object, redisplaying the form @@ -25,6 +75,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.CreateView` * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` * :class:`django.views.generic.base.TemplateResponseMixin` @@ -35,6 +87,21 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.edit.ProcessFormView` * :class:`django.views.generic.base.View` + **Attributes** + + .. attribute:: template_name_suffix + + The CreateView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_form'``. + + **Example views.py**:: + + from django.views.generic.edit import CreateView + from myapp.models import Author + + class AuthorCreate(CreateView): + model = Author + .. class:: django.views.generic.edit.UpdateView A view that displays a form for editing an existing object, redisplaying @@ -44,6 +111,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.UpdateView` * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` * :class:`django.views.generic.base.TemplateResponseMixin` @@ -54,6 +123,21 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.edit.ProcessFormView` * :class:`django.views.generic.base.View` + **Attributes** + + .. attribute:: template_name_suffix + + The CreateView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_form'``. + + **Example views.py**:: + + from django.views.generic.edit import UpdateView + from myapp.models import Author + + class AuthorUpdate(UpdateView): + model = Author + .. class:: django.views.generic.edit.DeleteView A view that displays a confirmation page and deletes an existing object. @@ -63,6 +147,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.DeleteView` * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` * :class:`django.views.generic.base.TemplateResponseMixin` @@ -72,7 +158,19 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.detail.SingleObjectMixin` * :class:`django.views.generic.base.View` - **Notes** + **Attributes** - * The delete confirmation page displayed to a GET request uses a - ``template_name_suffix`` of ``'_confirm_delete'``. + .. attribute:: template_name_suffix + + The CreateView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_confirm_delete'``. + + **Example views.py**:: + + from django.views.generic.edit import DeleteView + from django.core.urlresolvers import reverse_lazy + from myapp.models import Author + + class AuthorDelete(DeleteView): + model = Author + success_url = reverse_lazy('author-list') diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index 7258893d63..f68c279ff3 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -2,6 +2,16 @@ Editing mixins ============== +The following mixins are used to construct Django's editing views: + +* :class:`django.views.generic.edit.FormMixin` +* :class:`django.views.generic.edit.ModelFormMixin` +* :class:`django.views.generic.edit.ProcessFormView` +* :class:`django.views.generic.edit.DeletionMixin` + +.. note:: Examples of how these are combined into editing views can be found at + the documentation on ``Generic editing views``. + .. class:: django.views.generic.edit.FormMixin A mixin class that provides facilities for creating and displaying forms. From b90caf014c8cb447d34217e81bbc3fb57b2df89b Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Sun, 15 Jul 2012 19:29:19 -0700 Subject: [PATCH 193/519] Changed myapps.models.py to myapps/models.py --- docs/ref/class-based-views/generic-editing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index eb286ec3a7..78f46851f7 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -12,7 +12,7 @@ editing content: .. note:: Some of the examples on this page assume that a model titled 'authors' has been defined. For these cases we assume the following has been defined - in `myapps.models.py`:: + in `myapp/models.py`:: from django import models from django.core.urlresolvers import reverse From bebbbb7af096ecffc0d7585617fdfacb196bc7c2 Mon Sep 17 00:00:00 2001 From: Nuno Maltez Date: Mon, 16 Jul 2012 19:52:31 +0300 Subject: [PATCH 194/519] Fixed #18056 - Cleared aggregations on DateQuery.add_date_select Cleared aggregations on add_date_select method so only distinct dates are returned when dealing with a QuerySet that contained aggregations. That would cause the query set to return repeated dates because it would look for distinct (date kind, aggregation) pairs. --- django/db/models/sql/subqueries.py | 2 ++ tests/modeltests/aggregation/tests.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 5cfc984770..7b92394e90 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -8,6 +8,7 @@ from django.db.models.sql.constants import * from django.db.models.sql.datastructures import Date from django.db.models.sql.query import Query from django.db.models.sql.where import AND, Constraint +from django.utils.datastructures import SortedDict from django.utils.functional import Promise from django.utils.encoding import force_unicode @@ -205,6 +206,7 @@ class DateQuery(Query): self.select = [select] self.select_fields = [None] self.select_related = False # See #7097. + self.aggregates = SortedDict() # See 18056. self.set_extra_mask([]) self.distinct = True self.order_by = order == 'ASC' and [1] or [-1] diff --git a/tests/modeltests/aggregation/tests.py b/tests/modeltests/aggregation/tests.py index 433ea1641a..c23b32fc85 100644 --- a/tests/modeltests/aggregation/tests.py +++ b/tests/modeltests/aggregation/tests.py @@ -565,3 +565,23 @@ class BaseAggregateTestCase(TestCase): (Decimal('82.8'), 1), ] ) + + def test_dates_with_aggregation(self): + """ + Test that .dates() returns a distinct set of dates when applied to a + QuerySet with aggregation. + + Refs #18056. Previously, .dates() would return distinct (date_kind, + aggregation) sets, in this case (year, num_authors), so 2008 would be + returned twice because there are books from 2008 with a different + number of authors. + """ + dates = Book.objects.annotate(num_authors=Count("authors")).dates('pubdate', 'year') + self.assertQuerysetEqual( + dates, [ + "datetime.datetime(1991, 1, 1, 0, 0)", + "datetime.datetime(1995, 1, 1, 0, 0)", + "datetime.datetime(2007, 1, 1, 0, 0)", + "datetime.datetime(2008, 1, 1, 0, 0)" + ] + ) From aeda55e6bffc3cfbf53698af398a19c1a0f02d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Thu, 5 Jul 2012 18:09:48 +0300 Subject: [PATCH 195/519] Fixed #3881 -- skip saving session when response status is 500 Saving session data is somewhat likely to lead into error when the status code is 500. It is guaranteed to lead into error if the reason for the 500 code is query error on PostgreSQL. --- django/contrib/sessions/middleware.py | 16 +++++++++------- django/contrib/sessions/tests.py | 16 ++++++++++++++++ docs/releases/1.5.txt | 6 ++++++ docs/topics/http/sessions.txt | 3 +++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 68cb77f7e1..9f65255f47 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -33,11 +33,13 @@ class SessionMiddleware(object): expires_time = time.time() + max_age expires = cookie_date(expires_time) # Save the session data and refresh the client cookie. - request.session.save() - response.set_cookie(settings.SESSION_COOKIE_NAME, - request.session.session_key, max_age=max_age, - expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, - path=settings.SESSION_COOKIE_PATH, - secure=settings.SESSION_COOKIE_SECURE or None, - httponly=settings.SESSION_COOKIE_HTTPONLY or None) + # Skip session save for 500 responses, refs #3881. + if response.status_code != 500: + request.session.save() + response.set_cookie(settings.SESSION_COOKIE_NAME, + request.session.session_key, max_age=max_age, + expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, + path=settings.SESSION_COOKIE_PATH, + secure=settings.SESSION_COOKIE_SECURE or None, + httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 92ea6bbd91..328b085f1e 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -409,6 +409,22 @@ class SessionMiddlewareTests(unittest.TestCase): self.assertNotIn('httponly', str(response.cookies[settings.SESSION_COOKIE_NAME])) + def test_session_save_on_500(self): + request = RequestFactory().get('/') + response = HttpResponse('Horrible error') + response.status_code = 500 + middleware = SessionMiddleware() + + # Simulate a request the modifies the session + middleware.process_request(request) + request.session['hello'] = 'world' + + # Handle the response through the middleware + response = middleware.process_response(request, response) + + # Check that the value wasn't saved above. + self.assertNotIn('hello', request.session.load()) + class CookieSessionTests(SessionTestsMixin, TestCase): diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 4275fbae52..b863d102ec 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -177,6 +177,12 @@ autocommit behavior was never restored. This bug is now fixed in 1.5. While this is only a bug fix, it is worth checking your applications behavior if you are using PostgreSQL together with the autocommit option. +Session not saved on 500 responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Django's session middleware will skip saving the session data if the +response's status code is 500. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index a32d9b54dd..1f55293413 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -423,6 +423,9 @@ cookie will be sent on every request. Similarly, the ``expires`` part of a session cookie is updated each time the session cookie is sent. +.. versionchanged:: 1.5 + The session is not saved if the response's status code is 500. + Browser-length sessions vs. persistent sessions =============================================== From fcad6c48f07bdc6346c065849a87f0f02afb6f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Tue, 17 Jul 2012 12:24:56 +0300 Subject: [PATCH 196/519] Fixed #17497 -- Corrected FieldError message in add_fields() The erroneous message was user visible in values_list() calls. Thanks to ojii for report and review, and to antoviaque for the patch. --- django/db/models/sql/query.py | 13 +++++++++---- tests/modeltests/many_to_one/tests.py | 14 +++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 8fbba3dbc9..118bc1e14f 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1655,10 +1655,15 @@ class Query(object): except MultiJoin: raise FieldError("Invalid field name: '%s'" % name) except FieldError: - names = opts.get_all_field_names() + self.extra.keys() + self.aggregate_select.keys() - names.sort() - raise FieldError("Cannot resolve keyword %r into field. " - "Choices are: %s" % (name, ", ".join(names))) + if name.find(LOOKUP_SEP) != -1: + # For lookups spanning over relationships, show the error + # from the model on which the lookup failed. + raise + else: + names = sorted(opts.get_all_field_names() + self.extra.keys() + + self.aggregate_select.keys()) + raise FieldError("Cannot resolve keyword %r into field. " + "Choices are: %s" % (name, ", ".join(names))) self.remove_inherited_models() def add_ordering(self, *ordering): diff --git a/tests/modeltests/many_to_one/tests.py b/tests/modeltests/many_to_one/tests.py index 257025583b..fd849df051 100644 --- a/tests/modeltests/many_to_one/tests.py +++ b/tests/modeltests/many_to_one/tests.py @@ -3,7 +3,7 @@ from __future__ import absolute_import from copy import deepcopy from datetime import datetime -from django.core.exceptions import MultipleObjectsReturned +from django.core.exceptions import MultipleObjectsReturned, FieldError from django.test import TestCase from django.utils.translation import ugettext_lazy @@ -424,3 +424,15 @@ class ManyToOneTests(TestCase): notlazy = unicode(lazy) article = reporter.article_set.get() self.assertEqual(article.headline, notlazy) + + def test_values_list_exception(self): + expected_message = "Cannot resolve keyword 'notafield' into field. Choices are: %s" + + self.assertRaisesMessage(FieldError, + expected_message % ', '.join(Reporter._meta.get_all_field_names()), + Article.objects.values_list, + 'reporter__notafield') + self.assertRaisesMessage(FieldError, + expected_message % ', '.join(['EXTRA',] + Article._meta.get_all_field_names()), + Article.objects.extra(select={'EXTRA': 'EXTRA_SELECT'}).values_list, + 'notafield') From 29132ebdef0e0b9c09e456b05f0e6a22f1106a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Sun, 29 Apr 2012 04:22:05 +0300 Subject: [PATCH 197/519] Fixed #17788 -- Added batch_size argument to qs.bulk_create() The qs.bulk_create() method did not work with large batches together with SQLite3. This commit adds a way to split the bulk into smaller batches. The default batch size is unlimited except for SQLite3 where the batch size is limited to 999 SQL parameters per batch. Thanks to everybody who participated in the discussions at Trac. --- django/db/backends/__init__.py | 30 +++++++++----- django/db/backends/sqlite3/base.py | 9 +++- django/db/models/query.py | 29 ++++++++++--- docs/ref/models/querysets.txt | 20 +++------ docs/releases/1.5.txt | 5 +++ tests/regressiontests/bulk_create/models.py | 6 ++- tests/regressiontests/bulk_create/tests.py | 46 +++++++++++++++++++-- tests/regressiontests/queries/tests.py | 3 +- 8 files changed, 110 insertions(+), 38 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 2da1f2be0f..3075bdb3e4 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -475,6 +475,14 @@ class BaseDatabaseOperations(object): """ return None + def bulk_batch_size(self, fields, objs): + """ + Returns the maximum allowed batch size for the backend. The fields + are the fields going to be inserted in the batch, the objs contains + all the objects to be inserted. + """ + return len(objs) + def cache_key_culling_sql(self): """ Returns a SQL query that retrieves the first cache key greater than the @@ -522,6 +530,17 @@ class BaseDatabaseOperations(object): """ return '' + def distinct_sql(self, fields): + """ + Returns an SQL DISTINCT clause which removes duplicate rows from the + result set. If any fields are given, only the given fields are being + checked for duplicates. + """ + if fields: + raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') + else: + return 'DISTINCT' + def drop_foreignkey_sql(self): """ Returns the SQL command that drops a foreign key. @@ -577,17 +596,6 @@ class BaseDatabaseOperations(object): """ raise NotImplementedError('Full-text search is not implemented for this database backend') - def distinct_sql(self, fields): - """ - Returns an SQL DISTINCT clause which removes duplicate rows from the - result set. If any fields are given, only the given fields are being - checked for duplicates. - """ - if fields: - raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') - else: - return 'DISTINCT' - def last_executed_query(self, cursor, sql, params): """ Returns a string of the query last executed by the given cursor, with diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index d7f51605d5..60723124c1 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -85,7 +85,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_1000_query_parameters = False supports_mixed_date_datetime_comparisons = False has_bulk_insert = True - can_combine_inserts_with_and_without_auto_increment_pk = True + can_combine_inserts_with_and_without_auto_increment_pk = False @cached_property def supports_stddev(self): @@ -107,6 +107,13 @@ class DatabaseFeatures(BaseDatabaseFeatures): return has_support class DatabaseOperations(BaseDatabaseOperations): + def bulk_batch_size(self, fields, objs): + """ + SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of + 999 variables per query. + """ + return (999 // len(fields)) if len(fields) > 0 else len(objs) + def date_extract_sql(self, lookup_type, field_name): # sqlite doesn't support extract, so we fake it with the user-defined # function django_extract that's registered in connect(). Note that diff --git a/django/db/models/query.py b/django/db/models/query.py index 0f1d87c642..ebe61a1226 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -388,7 +388,7 @@ class QuerySet(object): obj.save(force_insert=True, using=self.db) return obj - def bulk_create(self, objs): + def bulk_create(self, objs, batch_size=None): """ Inserts each of the instances into the database. This does *not* call save() on each of the instances, does not send any pre/post save @@ -401,8 +401,10 @@ class QuerySet(object): # this could be implemented if you didn't have an autoincrement pk, # and 2) you could do it by doing O(n) normal inserts into the parent # tables to get the primary keys back, and then doing a single bulk - # insert into the childmost table. We're punting on these for now - # because they are relatively rare cases. + # insert into the childmost table. Some databases might allow doing + # this by using RETURNING clause for the insert query. We're punting + # on these for now because they are relatively rare cases. + assert batch_size is None or batch_size > 0 if self.model._meta.parents: raise ValueError("Can't bulk create an inherited model") if not objs: @@ -418,13 +420,14 @@ class QuerySet(object): try: if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk and self.model._meta.has_auto_field): - self.model._base_manager._insert(objs, fields=fields, using=self.db) + self._batched_insert(objs, fields, batch_size) else: objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs) if objs_with_pk: - self.model._base_manager._insert(objs_with_pk, fields=fields, using=self.db) + self._batched_insert(objs_with_pk, fields, batch_size) if objs_without_pk: - self.model._base_manager._insert(objs_without_pk, fields=[f for f in fields if not isinstance(f, AutoField)], using=self.db) + fields= [f for f in fields if not isinstance(f, AutoField)] + self._batched_insert(objs_without_pk, fields, batch_size) if forced_managed: transaction.commit(using=self.db) else: @@ -860,6 +863,20 @@ class QuerySet(object): ################### # PRIVATE METHODS # ################### + def _batched_insert(self, objs, fields, batch_size): + """ + A little helper method for bulk_insert to insert the bulk one batch + at a time. Inserts recursively a batch from the front of the bulk and + then _batched_insert() the remaining objects again. + """ + if not objs: + return + ops = connections[self.db].ops + batch_size = (batch_size or max(ops.bulk_batch_size(fields, objs), 1)) + for batch in [objs[i:i+batch_size] + for i in range(0, len(objs), batch_size)]: + self.model._base_manager._insert(batch, fields=fields, + using=self.db) def _clone(self, klass=None, setup=False, **kwargs): if klass is None: diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 0a9005ad26..8c188c67c3 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1350,7 +1350,7 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec. bulk_create ~~~~~~~~~~~ -.. method:: bulk_create(objs) +.. method:: bulk_create(objs, batch_size=None) .. versionadded:: 1.4 @@ -1372,20 +1372,12 @@ This has a number of caveats though: * If the model's primary key is an :class:`~django.db.models.AutoField` it does not retrieve and set the primary key attribute, as ``save()`` does. -.. admonition:: Limits of SQLite +The ``batch_size`` parameter controls how many objects are created in single +query. The default is to create all objects in one batch, except for SQLite +where the default is such that at maximum 999 variables per query is used. - SQLite sets a limit on the number of parameters per SQL statement. The - maximum is defined by the SQLITE_MAX_VARIABLE_NUMBER_ compilation option, - which defaults to 999. For instance, if your model has 8 fields (including - the primary key), you cannot create more than 999 // 8 = 124 instances at - a time. If you exceed this limit, you'll get an exception:: - - django.db.utils.DatabaseError: too many SQL variables - - If your application's performance requirements exceed SQLite's limits, you - should switch to another database engine, such as PostgreSQL. - -.. _SQLITE_MAX_VARIABLE_NUMBER: http://sqlite.org/limits.html#max_variable_number +.. versionadded:: 1.5 + The ``batch_size`` parameter was added in version 1.5. count ~~~~~ diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index b863d102ec..fd9ae4f038 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -106,6 +106,11 @@ Django 1.5 also includes several smaller improvements worth noting: * The :ref:`receiver ` decorator is now able to connect to more than one signal by supplying a list of signals. +* :meth:`QuerySet.bulk_create() + ` has now a batch_size + argument. By default the batch_size is unlimited except for SQLite where + single batch is limited so that 999 parameters per query isn't exceeded. + Backwards incompatible changes in 1.5 ===================================== diff --git a/tests/regressiontests/bulk_create/models.py b/tests/regressiontests/bulk_create/models.py index a4c611d537..bc685bbbe4 100644 --- a/tests/regressiontests/bulk_create/models.py +++ b/tests/regressiontests/bulk_create/models.py @@ -18,4 +18,8 @@ class Pizzeria(Restaurant): pass class State(models.Model): - two_letter_code = models.CharField(max_length=2, primary_key=True) \ No newline at end of file + two_letter_code = models.CharField(max_length=2, primary_key=True) + +class TwoFields(models.Model): + f1 = models.IntegerField(unique=True) + f2 = models.IntegerField(unique=True) diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/regressiontests/bulk_create/tests.py index 0b55f637a4..33108ea9b0 100644 --- a/tests/regressiontests/bulk_create/tests.py +++ b/tests/regressiontests/bulk_create/tests.py @@ -2,9 +2,11 @@ from __future__ import absolute_import from operator import attrgetter -from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature +from django.db import connection +from django.test import TestCase, skipIfDBFeature +from django.test.utils import override_settings -from .models import Country, Restaurant, Pizzeria, State +from .models import Country, Restaurant, Pizzeria, State, TwoFields class BulkCreateTests(TestCase): @@ -27,7 +29,6 @@ class BulkCreateTests(TestCase): self.assertEqual(created, []) self.assertEqual(Country.objects.count(), 4) - @skipUnlessDBFeature("has_bulk_insert") def test_efficiency(self): with self.assertNumQueries(1): Country.objects.bulk_create(self.data) @@ -69,3 +70,42 @@ class BulkCreateTests(TestCase): invalid_country = Country(id=0, name='Poland', iso_two_letter='PL') with self.assertRaises(ValueError): Country.objects.bulk_create([valid_country, invalid_country]) + + def test_large_batch(self): + with override_settings(DEBUG=True): + connection.queries = [] + TwoFields.objects.bulk_create([ + TwoFields(f1=i, f2=i+1) for i in range(0, 1001) + ]) + self.assertTrue(len(connection.queries) < 10) + self.assertEqual(TwoFields.objects.count(), 1001) + self.assertEqual( + TwoFields.objects.filter(f1__gte=450, f1__lte=550).count(), + 101) + self.assertEqual(TwoFields.objects.filter(f2__gte=901).count(), 101) + + def test_large_batch_mixed(self): + """ + Test inserting a large batch with objects having primary key set + mixed together with objects without PK set. + """ + with override_settings(DEBUG=True): + connection.queries = [] + TwoFields.objects.bulk_create([ + TwoFields(id=i if i % 2 == 0 else None, f1=i, f2=i+1) + for i in range(100000, 101000)]) + self.assertTrue(len(connection.queries) < 10) + self.assertEqual(TwoFields.objects.count(), 1000) + # We can't assume much about the ID's created, except that the above + # created IDs must exist. + id_range = range(100000, 101000, 2) + self.assertEqual(TwoFields.objects.filter(id__in=id_range).count(), 500) + self.assertEqual(TwoFields.objects.exclude(id__in=id_range).count(), 500) + + def test_explicit_batch_size(self): + objs = [TwoFields(f1=i, f2=i) for i in range(0, 100)] + with self.assertNumQueries(2): + TwoFields.objects.bulk_create(objs, 50) + TwoFields.objects.all().delete() + with self.assertNumQueries(1): + TwoFields.objects.bulk_create(objs, len(objs)) diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 9bb3a29430..1582993dfc 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -1879,8 +1879,7 @@ class ConditionalTests(BaseQuerysetTest): # Test that the "in" lookup works with lists of 1000 items or more. Number.objects.all().delete() numbers = range(2500) - for num in numbers: - _ = Number.objects.create(num=num) + Number.objects.bulk_create(Number(num=num) for num in numbers) self.assertEqual( Number.objects.filter(num__in=numbers[:1000]).count(), 1000 From 52df0d50b0d903c8d845404b8da89b174f8824cc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 17 Jul 2012 07:01:01 -0700 Subject: [PATCH 198/519] Switched to use a more idiomatic construct. --- django/db/models/sql/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 118bc1e14f..53dad608bf 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1655,7 +1655,7 @@ class Query(object): except MultiJoin: raise FieldError("Invalid field name: '%s'" % name) except FieldError: - if name.find(LOOKUP_SEP) != -1: + if LOOKUP_SEP in name: # For lookups spanning over relationships, show the error # from the model on which the lookup failed. raise From d5012d6371e804fc0427112cc1ceacbe8a059c4f Mon Sep 17 00:00:00 2001 From: Vebjorn Ljosa Date: Tue, 17 Jul 2012 10:38:04 -0400 Subject: [PATCH 199/519] Fixed #18644 -- Made urlize trim trailing period followed by parenthesis --- django/utils/html.py | 2 +- tests/regressiontests/defaultfilters/tests.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django/utils/html.py b/django/utils/html.py index 390c45dcec..6a47bfc869 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -13,7 +13,7 @@ from django.utils.functional import allow_lazy from django.utils.text import normalize_newlines # Configuration for urlize() function. -TRAILING_PUNCTUATION = ['.', ',', ':', ';'] +TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)'] WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('<', '>')] # List of possible strings used for bullets in bulleted lists. diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index ffa0a01132..d08bcf0fd9 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -297,6 +297,10 @@ class DefaultFiltersTests(TestCase): self.assertEqual(urlize('HTTPS://github.com/'), 'HTTPS://github.com/') + # Check urlize trims trailing period when followed by parenthesis - see #18644 + self.assertEqual(urlize('(Go to http://www.example.com/foo.)'), + '(Go to http://www.example.com/foo.)') + def test_wordcount(self): self.assertEqual(wordcount(''), 0) self.assertEqual(wordcount('oneword'), 1) From f2abfe1e4853df1848d178c3de369c80932292e8 Mon Sep 17 00:00:00 2001 From: Mike Grouchy Date: Mon, 16 Jul 2012 20:37:52 -0400 Subject: [PATCH 200/519] Adds interpreter option to shell command as per ticket #18639 Specify python interpreter interface ipython or bpython with the -i, --interface options argument. ex// python manage.py shell -i bpython ex// python manage.py shell --interface bpython Like all other options, defaults to default python interpreter when your selected choice isn't available. updated documentation where appropriate --- django/core/management/commands/shell.py | 20 +++++++++++++++----- docs/ref/django-admin.txt | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 075efa0bcd..4e7d1dbbf4 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -2,13 +2,19 @@ import os from django.core.management.base import NoArgsCommand from optparse import make_option + class Command(NoArgsCommand): + shells = ['ipython', 'bpython'] + option_list = NoArgsCommand.option_list + ( make_option('--plain', action='store_true', dest='plain', help='Tells Django to use plain Python, not IPython or bpython.'), + make_option('-i', '--interface', action='store', type='choice', choices=shells, + dest='interface', + help='Specify an interactive interpreter interface. Available options: "ipython" and "bpython"'), + ) help = "Runs a Python interactive interpreter. Tries to use IPython or bpython, if one of them is available." - shells = ['ipython', 'bpython'] requires_model_validation = False def ipython(self): @@ -31,8 +37,10 @@ class Command(NoArgsCommand): import bpython bpython.embed() - def run_shell(self): - for shell in self.shells: + def run_shell(self, shell=None): + available_shells = [shell] if shell else self.shells + + for shell in available_shells: try: return getattr(self, shell)() except ImportError: @@ -46,19 +54,21 @@ class Command(NoArgsCommand): get_models() use_plain = options.get('plain', False) + interface = options.get('interface', None) try: if use_plain: # Don't bother loading IPython, because the user wants plain Python. raise ImportError - self.run_shell() + + self.run_shell(shell=interface) except ImportError: import code # Set up a dictionary to serve as the environment for the shell, so # that tab completion works on objects that are imported at runtime. # See ticket 5082. imported_objects = {} - try: # Try activating rlcompleter, because it's handy. + try: # Try activating rlcompleter, because it's handy. import readline except ImportError: pass diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 31c46c7fa0..c4295c68d5 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -743,6 +743,24 @@ use the ``--plain`` option, like so:: django-admin.py shell --plain +.. versionchanged:: 1.5 + +If you would like to specify either IPython or bpython as your interpreter if +you have both installed you can specify an alternative interpreter interface +with the ``-i`` or ``--interface`` options like so:: + +IPython:: + + django-admin.py shell -i ipython + django-admin.py shell --interface ipython + + +bpython:: + + django-admin.py shell -i bpython + django-admin.py shell --interface bpython + + .. _IPython: http://ipython.scipy.org/ .. _bpython: http://bpython-interpreter.org/ From 23f94f0741100233862922a8e77a3ac9dc1833e9 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 17 Jul 2012 21:58:18 +0200 Subject: [PATCH 201/519] Fixed #18561 -- Made HttpResponse.tell() support non-ascii chars --- django/http/__init__.py | 2 +- tests/regressiontests/httpwrappers/tests.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 7ded554665..4b8f70fb4b 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -683,7 +683,7 @@ class HttpResponse(object): def tell(self): if self._base_content_is_iter: raise Exception("This %s instance cannot tell its position" % self.__class__) - return sum([len(str(chunk)) for chunk in self._container]) + return sum([len(chunk) for chunk in self]) class HttpResponseRedirect(HttpResponse): status_code = 302 diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index cc55057d31..9b599db6d0 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -1,3 +1,4 @@ +# -*- encoding: utf-8 -*- from __future__ import unicode_literals import copy @@ -298,6 +299,17 @@ class HttpResponseTests(unittest.TestCase): self.assertRaises(UnicodeEncodeError, getattr, r, 'content') + def test_file_interface(self): + r = HttpResponse() + r.write(b"hello") + self.assertEqual(r.tell(), 5) + r.write("привет") + self.assertEqual(r.tell(), 17) + + r = HttpResponse(['abc']) + self.assertRaises(Exception, r.write, 'def') + + class CookieTests(unittest.TestCase): def test_encode(self): """ From 8184aff2b0a3fbe6759163c0289f640a393a3e99 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 17 Jul 2012 22:04:47 +0200 Subject: [PATCH 202/519] Fixed #18547 -- Improved error message when gettext is missing --- django/core/management/commands/makemessages.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index 046ffb48f2..cc6d3a7e48 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -291,7 +291,10 @@ def make_messages(locale=None, domain='django', verbosity=1, all=False, raise CommandError(message) # We require gettext version 0.15 or newer. - output = _popen('xgettext --version')[0] + output, errors = _popen('xgettext --version') + if errors: + raise CommandError("Error running xgettext. Note that Django " + "internationalization requires GNU gettext 0.15 or newer.") match = re.search(r'(?P\d+)\.(?P\d+)', output) if match: xversion = (int(match.group('major')), int(match.group('minor'))) From adad6c3afeea91422a22312939330ce8165084e3 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 18 Jul 2012 13:45:42 +0200 Subject: [PATCH 203/519] Added myself to internals/committers.txt. --- docs/internals/committers.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index b0eed95571..712b29ee25 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -365,6 +365,16 @@ Anssi Kääriäinen all related features. He's also a fan of benckmarking and he tries keep Django as fast as possible. +Florian Apolloner + Florian is currently studying Physics at the `Graz University of Technology`_. + Soon after he started using Django he joined the `Ubuntuusers webteam`_ to + work on *Inyoka*, the software powering the whole Ubuntusers site. + + For the time beeing he lives in Graz, Austria (not Australia ;)). + +.. _Graz University of Technology: http://tugraz.at/ +.. _Ubuntuusers webteam: http://wiki.ubuntuusers.de/ubuntuusers/Webteam + Specialists ----------- From 810fd236fad03e687e714c6a8e0ccd21229f89c1 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 18 Jul 2012 14:10:40 +0200 Subject: [PATCH 204/519] Updated my bio. --- docs/internals/committers.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 712b29ee25..79cbcb47d0 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -191,14 +191,19 @@ Karen Tracey `Jannis Leidel`_ Jannis graduated in media design from `Bauhaus-University Weimar`_, is the author of a number of pluggable Django apps and likes to - contribute to Open Source projects like Pinax_. He currently works as - a freelance Web developer and designer. + contribute to Open Source projects like virtualenv_ and pip_. + + He has worked on Django's auth, admin and staticfiles apps as well as + the form, core, internationalization and test systems. He currently works + as the lead engineer at Gidsy_. Jannis lives in Berlin, Germany. .. _Jannis Leidel: http://jezdez.com/ .. _Bauhaus-University Weimar: http://www.uni-weimar.de/ -.. _pinax: http://pinaxproject.com/ +.. _virtualenv: http://www.virtualenv.org/ +.. _pip: http://www.pip-installer.org/ +.. _Gidsy: http://gidsy.com/ `James Tauber`_ James is the lead developer of Pinax_ and the CEO and founder of From 1e89a208d0d741558c31a161222e8aa96764e08c Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 18 Jul 2012 14:59:12 +0200 Subject: [PATCH 205/519] Fixed #18645 -- Clarified filesizeformat implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Jérôme Renard for the patch. --- django/template/defaultfilters.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index d2c5b03f30..b9cdd94296 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -827,17 +827,23 @@ def filesizeformat(bytes): filesize_number_format = lambda value: formats.number_format(round(value, 1), 1) - if bytes < 1024: + KB = 1<<10 + MB = 1<<20 + GB = 1<<30 + TB = 1<<40 + PB = 1<<50 + + if bytes < KB: return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes} - if bytes < 1024 * 1024: - return ugettext("%s KB") % filesize_number_format(bytes / 1024) - if bytes < 1024 * 1024 * 1024: - return ugettext("%s MB") % filesize_number_format(bytes / (1024 * 1024)) - if bytes < 1024 * 1024 * 1024 * 1024: - return ugettext("%s GB") % filesize_number_format(bytes / (1024 * 1024 * 1024)) - if bytes < 1024 * 1024 * 1024 * 1024 * 1024: - return ugettext("%s TB") % filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024)) - return ugettext("%s PB") % filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024 * 1024)) + if bytes < MB: + return ugettext("%s KB") % filesize_number_format(bytes / KB) + if bytes < GB: + return ugettext("%s MB") % filesize_number_format(bytes / MB) + if bytes < TB: + return ugettext("%s GB") % filesize_number_format(bytes / GB) + if bytes < PB: + return ugettext("%s TB") % filesize_number_format(bytes / TB) + return ugettext("%s PB") % filesize_number_format(bytes / PB) @register.filter(is_safe=False) def pluralize(value, arg='s'): From 6d0dbd88f649f5038732de8643e9c9ba50ebb567 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 18 Jul 2012 08:04:29 -0700 Subject: [PATCH 206/519] Update my bio. --- docs/internals/committers.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 79cbcb47d0..fb601a24c0 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -219,15 +219,15 @@ Karen Tracey .. _James Tauber: http://jtauber.com/ `Alex Gaynor`_ - Alex is a student at Rensselaer Polytechnic Institute, and is also an - independent contractor. He found Django in 2007 and has been addicted ever - since he found out you don't need to write out your forms by hand. He has - a small obsession with compilers. He's contributed to the ORM, forms, - admin, and other components of Django. + Alex is a software engineer working at Rdio_. He found Django in 2007 and + has been addicted ever since he found out you don't need to write out your + forms by hand. He has a small obsession with compilers. He's contributed + to the ORM, forms, admin, and other components of Django. - Alex lives in Chicago, IL, but spends most of his time in Troy, NY. + Alex lives in San Francisco, CA, USA. .. _Alex Gaynor: http://alexgaynor.net +.. _Rdio: http://rdio.com `Andrew Godwin`_ Andrew is a freelance Python developer and tinkerer, and has been From d8e221db90294e0f26ee41ef81a1466907ca201d Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 18 Jul 2012 17:22:27 +0200 Subject: [PATCH 207/519] Moved myself to primary authors in AUTHORS. --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index ea0b01926c..6ca8b297f4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -30,6 +30,7 @@ The PRIMARY AUTHORS are (and/or have been): * Aymeric Augustin * Claude Paroz * Anssi Kääriäinen + * Florian Apolloner More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -61,7 +62,6 @@ answer newbie questions, and generally made Django that much better: andy@jadedplanet.net Fabrice Aneche ant9000@netwise.it - Florian Apolloner arien David Ascher atlithorn From a7928dedc83bfe00736fc4ef3a905652684b55d4 Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Wed, 18 Jul 2012 14:34:08 -0400 Subject: [PATCH 208/519] Fix typo in staticfiles app documentation In the documentation for the `static` template tag, a `::` was used prior to a `code-block`. Doing so caused the `code-block` line to render as code. Changing the `::` to `:` corrects the display. --- docs/ref/contrib/staticfiles.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index f5557dff91..cbe8ad54b8 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -390,7 +390,7 @@ in :ref:`staticfiles-from-cdn`. .. versionadded:: 1.5 If you'd like to retrieve a static URL without displaying it, you can use a -slightly different call:: +slightly different call: .. code-block:: html+django From c54905b3597545dbe1a6d8cd0a60186bccf8b3e7 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 18 Jul 2012 18:34:13 +0200 Subject: [PATCH 209/519] Fixed #18479 -- Stopped makemessages raising error on gettext warnings Thanks Niels Busch for the initial patch. --- .../core/management/commands/makemessages.py | 66 ++++++++++++------- .../regressiontests/i18n/commands/code.sample | 4 ++ .../i18n/commands/extraction.py | 8 +++ 3 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 tests/regressiontests/i18n/commands/code.sample diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index cc6d3a7e48..d8e8f6cff7 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -13,6 +13,7 @@ from django.utils.text import get_text_list from django.utils.jslex import prepare_js_for_gettext plural_forms_re = re.compile(r'^(?P"Plural-Forms.+?\\n")\s*$', re.MULTILINE | re.DOTALL) +STATUS_OK = 0 def handle_extensions(extensions=('html',), ignored=('py',)): """ @@ -43,7 +44,8 @@ def _popen(cmd): Friendly wrapper around Popen for Windows """ p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True) - return p.communicate() + 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): @@ -198,15 +200,19 @@ def process_file(file, dirpath, potfile, domain, verbosity, (domain, wrap, location, work_file)) else: return - msgs, errors = _popen(cmd) + msgs, errors, status = _popen(cmd) if errors: - if is_templatized: - os.unlink(work_file) - if os.path.exists(potfile): - os.unlink(potfile) - raise CommandError( - "errors happened while running xgettext on %s\n%s" % - (file, errors)) + if status != STATUS_OK: + if is_templatized: + os.unlink(work_file) + if os.path.exists(potfile): + os.unlink(potfile) + raise CommandError( + "errors happened while running xgettext on %s\n%s" % + (file, errors)) + elif verbosity > 0: + # Print warnings + stdout.write(errors) if msgs: write_pot_file(potfile, msgs, orig_file, work_file, is_templatized) if is_templatized: @@ -220,20 +226,28 @@ def write_po_file(pofile, potfile, domain, locale, verbosity, stdout, Uses mguniq, msgmerge, and msgattrib GNU gettext utilities. """ - msgs, errors = _popen('msguniq %s %s --to-code=utf-8 "%s"' % - (wrap, location, potfile)) + msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' % + (wrap, location, potfile)) if errors: - os.unlink(potfile) - raise CommandError("errors happened while running msguniq\n%s" % errors) + if status != STATUS_OK: + os.unlink(potfile) + raise CommandError( + "errors happened while running msguniq\n%s" % errors) + elif verbosity > 0: + stdout.write(errors) + if os.path.exists(pofile): with open(potfile, 'w') as fp: fp.write(msgs) - msgs, errors = _popen('msgmerge %s %s -q "%s" "%s"' % - (wrap, location, pofile, potfile)) + msgs, errors, status = _popen('msgmerge %s %s -q "%s" "%s"' % + (wrap, location, pofile, potfile)) if errors: - os.unlink(potfile) - raise CommandError( - "errors happened while running msgmerge\n%s" % errors) + if status != STATUS_OK: + os.unlink(potfile) + raise CommandError( + "errors happened while running msgmerge\n%s" % errors) + elif verbosity > 0: + stdout.write(errors) elif copy_pforms: msgs = copy_plural_forms(msgs, locale, domain, verbosity, stdout) msgs = msgs.replace( @@ -242,11 +256,15 @@ def write_po_file(pofile, potfile, domain, locale, verbosity, stdout, fp.write(msgs) os.unlink(potfile) if no_obsolete: - msgs, errors = _popen('msgattrib %s %s -o "%s" --no-obsolete "%s"' % - (wrap, location, pofile, pofile)) + msgs, errors, status = _popen( + 'msgattrib %s %s -o "%s" --no-obsolete "%s"' % + (wrap, location, pofile, pofile)) if errors: - raise CommandError( - "errors happened while running msgattrib\n%s" % errors) + if status != STATUS_OK: + raise CommandError( + "errors happened while running msgattrib\n%s" % errors) + elif verbosity > 0: + stdout.write(errors) def make_messages(locale=None, domain='django', verbosity=1, all=False, extensions=None, symlinks=False, ignore_patterns=None, no_wrap=False, @@ -291,8 +309,8 @@ def make_messages(locale=None, domain='django', verbosity=1, all=False, raise CommandError(message) # We require gettext version 0.15 or newer. - output, errors = _popen('xgettext --version') - if errors: + output, errors, status = _popen('xgettext --version') + if status != STATUS_OK: raise CommandError("Error running xgettext. Note that Django " "internationalization requires GNU gettext 0.15 or newer.") match = re.search(r'(?P\d+)\.(?P\d+)', output) diff --git a/tests/regressiontests/i18n/commands/code.sample b/tests/regressiontests/i18n/commands/code.sample new file mode 100644 index 0000000000..bbcb83164b --- /dev/null +++ b/tests/regressiontests/i18n/commands/code.sample @@ -0,0 +1,4 @@ +from django.utils.translation import ugettext + +# This will generate an xgettext warning +my_string = ugettext("This string contain two placeholders: %s and %s" % ('a', 'b')) diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py index fb9ca4ed08..cd6d50893a 100644 --- a/tests/regressiontests/i18n/commands/extraction.py +++ b/tests/regressiontests/i18n/commands/extraction.py @@ -117,6 +117,14 @@ class BasicExtractorTests(ExtractorTests): # Check that the temporary file was cleaned up self.assertFalse(os.path.exists('./templates/template_with_error.html.py')) + def test_extraction_warning(self): + os.chdir(self.test_dir) + shutil.copyfile('./code.sample', './code_sample.py') + stdout = StringIO() + management.call_command('makemessages', locale=LOCALE, stdout=stdout) + os.remove('./code_sample.py') + self.assertIn("code_sample.py:4", stdout.getvalue()) + def test_template_message_context_extractor(self): """ Ensure that message contexts are correctly extracted for the From 123362dd37a076987f265179fe16dcd2b6a16d12 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 19 Jul 2012 20:02:20 +0200 Subject: [PATCH 210/519] Fixed #18608 -- Reduced monkey-patching in tests. Thanks Claude Paroz for the patch. --- django/contrib/humanize/templatetags/humanize.py | 4 ++-- django/contrib/humanize/tests.py | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 7a2e5147d8..1398b43676 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -184,7 +184,7 @@ def naturaltime(value): if delta.days != 0: return pgettext( 'naturaltime', '%(delta)s ago' - ) % {'delta': defaultfilters.timesince(value)} + ) % {'delta': defaultfilters.timesince(value, now)} elif delta.seconds == 0: return _('now') elif delta.seconds < 60: @@ -206,7 +206,7 @@ def naturaltime(value): if delta.days != 0: return pgettext( 'naturaltime', '%(delta)s from now' - ) % {'delta': defaultfilters.timeuntil(value)} + ) % {'delta': defaultfilters.timeuntil(value, now)} elif delta.seconds == 0: return _('now') elif delta.seconds < 60: diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py index ecf4b83d6d..62df2be143 100644 --- a/django/contrib/humanize/tests.py +++ b/django/contrib/humanize/tests.py @@ -130,7 +130,7 @@ class HumanizeTests(TestCase): def utcoffset(self, dt): return None # we're going to mock datetime.datetime, so use a fixed datetime - now = datetime.datetime(2011, 8, 15) + now = datetime.datetime(2011, 8, 15, 1, 23) test_list = [ now, now - datetime.timedelta(seconds=1), @@ -185,17 +185,11 @@ class HumanizeTests(TestCase): # equals now.replace(tzinfo=utc) return now.replace(tzinfo=tz) + tz.utcoffset(now) - # naturaltime also calls timesince/timeuntil from django.contrib.humanize.templatetags import humanize - from django.utils import timesince orig_humanize_datetime = humanize.datetime - orig_timesince_datetime = timesince.datetime humanize.datetime = MockDateTime - timesince.datetime = new.module(b"mock_datetime") - timesince.datetime.datetime = MockDateTime try: self.humanize_tester(test_list, result_list, 'naturaltime') finally: humanize.datetime = orig_humanize_datetime - timesince.datetime = orig_timesince_datetime From 5d560dcb981400451d69a834f7c6a9090f5e5221 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 19 Jul 2012 23:02:22 +0200 Subject: [PATCH 211/519] Fixed #18504 -- Computed |naturalday in local time. --- .../contrib/humanize/templatetags/humanize.py | 8 ++- django/contrib/humanize/tests.py | 70 ++++++++++++------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 1398b43676..8f6c2602c9 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals import re -from datetime import date, datetime, timedelta +from datetime import date, datetime from django import template from django.conf import settings @@ -143,7 +143,9 @@ def apnumber(value): return value return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1] -@register.filter +# Perform the comparison in the default time zone when USE_TZ = True +# (unless a specific time zone has been applied with the |timezone filter). +@register.filter(expects_localtime=True) def naturalday(value, arg=None): """ For date values that are tomorrow, today or yesterday compared to @@ -169,6 +171,8 @@ def naturalday(value, arg=None): return _('yesterday') return defaultfilters.date(value, arg) +# This filter doesn't require expects_localtime=True because it deals properly +# with both naive and aware datetimes. Therefore avoid the cost of conversion. @register.filter def naturaltime(value): """ diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py index 62df2be143..a0f13d3ee9 100644 --- a/django/contrib/humanize/tests.py +++ b/django/contrib/humanize/tests.py @@ -1,13 +1,31 @@ from __future__ import unicode_literals import datetime -import new +from django.contrib.humanize.templatetags import humanize from django.template import Template, Context, defaultfilters from django.test import TestCase -from django.utils import translation, tzinfo -from django.utils.translation import ugettext as _ +from django.test.utils import override_settings from django.utils.html import escape from django.utils.timezone import utc +from django.utils import translation +from django.utils.translation import ugettext as _ +from django.utils import tzinfo + + +# Mock out datetime in some tests so they don't fail occasionally when they +# run too slow. Use a fixed datetime for datetime.now(). DST change in +# America/Chicago (the default time zone) happened on March 11th in 2012. + +now = datetime.datetime(2012, 3, 9, 22, 30) + +class MockDateTime(datetime.datetime): + @classmethod + def now(self, tz=None): + if tz is None or tz.utcoffset(now) is None: + return now + else: + # equals now.replace(tzinfo=utc) + return now.replace(tzinfo=tz) + tz.utcoffset(now) class HumanizeTests(TestCase): @@ -109,28 +127,36 @@ class HumanizeTests(TestCase): self.humanize_tester(test_list, result_list, 'naturalday') def test_naturalday_tz(self): - from django.contrib.humanize.templatetags.humanize import naturalday - today = datetime.date.today() tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12)) tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12)) # Can be today or yesterday date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one) - naturalday_one = naturalday(date_one) + naturalday_one = humanize.naturalday(date_one) # Can be today or tomorrow date_two = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_two) - naturalday_two = naturalday(date_two) + naturalday_two = humanize.naturalday(date_two) # As 24h of difference they will never be the same self.assertNotEqual(naturalday_one, naturalday_two) + def test_naturalday_uses_localtime(self): + # Regression for #18504 + # This is 2012-03-08HT19:30:00-06:00 in Ameria/Chicago + dt = datetime.datetime(2012, 3, 9, 1, 30, tzinfo=utc) + + orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime + try: + with override_settings(USE_TZ=True): + self.humanize_tester([dt], ['yesterday'], 'naturalday') + finally: + humanize.datetime = orig_humanize_datetime + def test_naturaltime(self): class naive(datetime.tzinfo): def utcoffset(self, dt): return None - # we're going to mock datetime.datetime, so use a fixed datetime - now = datetime.datetime(2011, 8, 15, 1, 23) test_list = [ now, now - datetime.timedelta(seconds=1), @@ -148,6 +174,7 @@ class HumanizeTests(TestCase): now + datetime.timedelta(hours=1, minutes=30, seconds=30), now + datetime.timedelta(hours=23, minutes=50, seconds=50), now + datetime.timedelta(days=1), + now + datetime.timedelta(days=2, hours=6), now + datetime.timedelta(days=500), now.replace(tzinfo=naive()), now.replace(tzinfo=utc), @@ -169,27 +196,22 @@ class HumanizeTests(TestCase): 'an hour from now', '23 hours from now', '1 day from now', + '2 days, 6 hours from now', '1 year, 4 months from now', 'now', 'now', ] + # Because of the DST change, 2 days and 6 hours after the chosen + # date in naive arithmetic is only 2 days and 5 hours after in + # aware arithmetic. + result_list_with_tz_support = result_list[:] + assert result_list_with_tz_support[-4] == '2 days, 6 hours from now' + result_list_with_tz_support[-4] == '2 days, 5 hours from now' - # mock out datetime so these tests don't fail occasionally when the - # test runs too slow - class MockDateTime(datetime.datetime): - @classmethod - def now(self, tz=None): - if tz is None or tz.utcoffset(now) is None: - return now - else: - # equals now.replace(tzinfo=utc) - return now.replace(tzinfo=tz) + tz.utcoffset(now) - - from django.contrib.humanize.templatetags import humanize - orig_humanize_datetime = humanize.datetime - humanize.datetime = MockDateTime - + orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime try: self.humanize_tester(test_list, result_list, 'naturaltime') + with override_settings(USE_TZ=True): + self.humanize_tester(test_list, result_list_with_tz_support, 'naturaltime') finally: humanize.datetime = orig_humanize_datetime From 85cd458944c16769a707921619e94ccc3dfbf7bd Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 12:29:22 +0200 Subject: [PATCH 212/519] Removed u prefixes on unicode strings. They break Python 3. --- tests/regressiontests/localflavor/ar/tests.py | 2 +- tests/regressiontests/utils/html.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/regressiontests/localflavor/ar/tests.py b/tests/regressiontests/localflavor/ar/tests.py index 82c2bd491a..0bc228eae9 100644 --- a/tests/regressiontests/localflavor/ar/tests.py +++ b/tests/regressiontests/localflavor/ar/tests.py @@ -81,7 +81,7 @@ class ARLocalFlavorTests(SimpleTestCase): def test_ARCUITField(self): error_format = ['Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] error_invalid = ['Invalid CUIT.'] - error_legal_type = [u'Invalid legal type. Type must be 27, 20, 23 or 30.'] + error_legal_type = ['Invalid legal type. Type must be 27, 20, 23 or 30.'] valid = { '20-10123456-9': '20-10123456-9', '20-10123456-9': '20-10123456-9', diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py index 389ae8ec75..fe40d4eaae 100644 --- a/tests/regressiontests/utils/html.py +++ b/tests/regressiontests/utils/html.py @@ -36,13 +36,13 @@ class TestUtilsHtml(unittest.TestCase): def test_format_html(self): self.assertEqual( - html.format_html(u"{0} {1} {third} {fourth}", - u"< Dangerous >", - html.mark_safe(u"safe"), + html.format_html("{0} {1} {third} {fourth}", + "< Dangerous >", + html.mark_safe("safe"), third="< dangerous again", - fourth=html.mark_safe(u"safe again") + fourth=html.mark_safe("safe again") ), - u"< Dangerous > safe < dangerous again safe again" + "< Dangerous > safe < dangerous again safe again" ) def test_linebreaks(self): From 324d48d0a7de895aeb7397cd3365ab03ac7f6724 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 13:28:36 +0200 Subject: [PATCH 213/519] Switched to Python 3-compatible octal notation. --- django/utils/daemonize.py | 2 +- tests/regressiontests/conditional_processing/models.py | 6 +++--- tests/regressiontests/file_storage/tests.py | 6 +++--- tests/regressiontests/file_uploads/tests.py | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/django/utils/daemonize.py b/django/utils/daemonize.py index a9d5128b33..ecd72aad52 100644 --- a/django/utils/daemonize.py +++ b/django/utils/daemonize.py @@ -3,7 +3,7 @@ import sys if os.name == 'posix': def become_daemon(our_home_dir='.', out_log='/dev/null', - err_log='/dev/null', umask=022): + err_log='/dev/null', umask=0o022): "Robustly turn into a UNIX daemon, running in our_home_dir." # First fork try: diff --git a/tests/regressiontests/conditional_processing/models.py b/tests/regressiontests/conditional_processing/models.py index 97aeff5eaa..d1a8ac605b 100644 --- a/tests/regressiontests/conditional_processing/models.py +++ b/tests/regressiontests/conditional_processing/models.py @@ -143,14 +143,14 @@ class HttpDateProcessing(unittest.TestCase): def testParsingRfc1123(self): parsed = parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT') self.assertEqual(datetime.utcfromtimestamp(parsed), - datetime(1994, 11, 06, 8, 49, 37)) + datetime(1994, 11, 6, 8, 49, 37)) def testParsingRfc850(self): parsed = parse_http_date('Sunday, 06-Nov-94 08:49:37 GMT') self.assertEqual(datetime.utcfromtimestamp(parsed), - datetime(1994, 11, 06, 8, 49, 37)) + datetime(1994, 11, 6, 8, 49, 37)) def testParsingAsctime(self): parsed = parse_http_date('Sun Nov 6 08:49:37 1994') self.assertEqual(datetime.utcfromtimestamp(parsed), - datetime(1994, 11, 06, 8, 49, 37)) + datetime(1994, 11, 6, 8, 49, 37)) diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index 51b2207867..31e33d915c 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -424,7 +424,7 @@ class FileSaveRaceConditionTest(unittest.TestCase): class FileStoragePermissions(unittest.TestCase): def setUp(self): self.old_perms = settings.FILE_UPLOAD_PERMISSIONS - settings.FILE_UPLOAD_PERMISSIONS = 0666 + settings.FILE_UPLOAD_PERMISSIONS = 0o666 self.storage_dir = tempfile.mkdtemp() self.storage = FileSystemStorage(self.storage_dir) @@ -434,8 +434,8 @@ class FileStoragePermissions(unittest.TestCase): def test_file_upload_permissions(self): name = self.storage.save("the_file", ContentFile(b"data")) - actual_mode = os.stat(self.storage.path(name))[0] & 0777 - self.assertEqual(actual_mode, 0666) + actual_mode = os.stat(self.storage.path(name))[0] & 0o777 + self.assertEqual(actual_mode, 0o666) class FileStoragePathParsing(unittest.TestCase): diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py index a7424639b4..9fe3ca15a7 100644 --- a/tests/regressiontests/file_uploads/tests.py +++ b/tests/regressiontests/file_uploads/tests.py @@ -362,16 +362,16 @@ class DirectoryCreationTests(unittest.TestCase): if not os.path.isdir(temp_storage.location): os.makedirs(temp_storage.location) if os.path.isdir(UPLOAD_TO): - os.chmod(UPLOAD_TO, 0700) + os.chmod(UPLOAD_TO, 0o700) shutil.rmtree(UPLOAD_TO) def tearDown(self): - os.chmod(temp_storage.location, 0700) + os.chmod(temp_storage.location, 0o700) shutil.rmtree(temp_storage.location) def test_readonly_root(self): """Permission errors are not swallowed""" - os.chmod(temp_storage.location, 0500) + os.chmod(temp_storage.location, 0o500) try: self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', b'x')) except OSError as err: From 38c18f1747ef272f397e1080f04e52f27bd1bc33 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 13:42:44 +0200 Subject: [PATCH 214/519] Switched to octal notation (bis). --- django/utils/daemonize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/utils/daemonize.py b/django/utils/daemonize.py index ecd72aad52..763a9db752 100644 --- a/django/utils/daemonize.py +++ b/django/utils/daemonize.py @@ -33,7 +33,7 @@ if os.name == 'posix': # Set custom file descriptors so that they get proper buffering. sys.stdout, sys.stderr = so, se else: - def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=022): + def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=0o022): """ If we're not running under a POSIX system, just simulate the daemon mode by doing redirections and directory changing. From 32a4df6c55f2efd020b7fbc44c858f61b07da17d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 21 Jul 2012 13:49:07 +0200 Subject: [PATCH 215/519] Fixed #18395 -- Reset language-related global variables with setting_changed --- django/test/signals.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/django/test/signals.py b/django/test/signals.py index 81808dfa3c..052b7dfa5c 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -11,6 +11,9 @@ template_rendered = Signal(providing_args=["template", "context"]) setting_changed = Signal(providing_args=["setting", "value"]) +# Most setting_changed receivers are supposed to be added below, +# except for cases where the receiver is related to a contrib app. + @receiver(setting_changed) def update_connections_time_zone(**kwargs): @@ -46,3 +49,12 @@ def update_connections_time_zone(**kwargs): def clear_context_processors_cache(**kwargs): if kwargs['setting'] == 'TEMPLATE_CONTEXT_PROCESSORS': context._standard_context_processors = None + + +@receiver(setting_changed) +def language_changed(**kwargs): + if kwargs['setting'] in ('LOCALE_PATHS', 'LANGUAGE_CODE'): + from django.utils.translation import trans_real + trans_real._default = None + if kwargs['setting'] == 'LOCALE_PATHS': + trans_real._translations = {} From 423244bc6b670abc2b7d6896add5c1baf0b4ef2a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 21 Jul 2012 14:24:29 +0200 Subject: [PATCH 216/519] Fixed #4680 -- Improved initial_sql parsing In particular, allow the '--' sequence to be present in string values without being interpreted as comment marker. Thanks Tim Chase for the report and shaleh for the initial patch. --- django/core/management/sql.py | 27 ++++++++++++------- .../initial_sql_regress/sql/simple.sql | 6 +++-- .../initial_sql_regress/tests.py | 22 ++++++++++++--- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 993d75aa6b..46d3cf28ed 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -136,6 +136,20 @@ def sql_all(app, style, connection): "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module." return sql_create(app, style, connection) + sql_custom(app, style, connection) + sql_indexes(app, style, connection) +def _split_statements(content): + comment_re = re.compile(r"^((?:'[^']*'|[^'])*?)--.*$") + statements = [] + statement = "" + for line in content.split("\n"): + cleaned_line = comment_re.sub(r"\1", line).strip() + if not cleaned_line: + continue + statement += cleaned_line + if statement.endswith(";"): + statements.append(statement) + statement = "" + return statements + def custom_sql_for_model(model, style, connection): opts = model._meta app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql')) @@ -149,10 +163,6 @@ def custom_sql_for_model(model, style, connection): for f in post_sql_fields: output.extend(f.post_create_sql(style, model._meta.db_table)) - # Some backends can't execute more than one SQL statement at a time, - # so split into separate statements. - statements = re.compile(r";[ \t]*$", re.M) - # Find custom SQL, if it's available. backend_name = connection.settings_dict['ENGINE'].split('.')[-1] sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), backend_name)), @@ -160,12 +170,9 @@ def custom_sql_for_model(model, style, connection): for sql_file in sql_files: if os.path.exists(sql_file): with open(sql_file, 'U') as fp: - for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): - # Remove any comments from the file - statement = re.sub(r"--.*([\n\Z]|$)", "", statement) - if statement.strip(): - output.append(statement + ";") - + # 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))) return output diff --git a/tests/regressiontests/initial_sql_regress/sql/simple.sql b/tests/regressiontests/initial_sql_regress/sql/simple.sql index ca9bd40dab..18fd8a401e 100644 --- a/tests/regressiontests/initial_sql_regress/sql/simple.sql +++ b/tests/regressiontests/initial_sql_regress/sql/simple.sql @@ -1,8 +1,10 @@ -INSERT INTO initial_sql_regress_simple (name) VALUES ('John'); +-- a comment +INSERT INTO initial_sql_regress_simple (name) VALUES ('John'); -- another comment +INSERT INTO initial_sql_regress_simple (name) VALUES ('-- Comment Man'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Paul'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Ringo'); INSERT INTO initial_sql_regress_simple (name) VALUES ('George'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man'); -INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending'); +INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending'); diff --git a/tests/regressiontests/initial_sql_regress/tests.py b/tests/regressiontests/initial_sql_regress/tests.py index 815b75a9bb..03a91cb807 100644 --- a/tests/regressiontests/initial_sql_regress/tests.py +++ b/tests/regressiontests/initial_sql_regress/tests.py @@ -4,12 +4,26 @@ from .models import Simple class InitialSQLTests(TestCase): - def test_initial_sql(self): - # The format of the included SQL file for this test suite is important. - # It must end with a trailing newline in order to test the fix for #2161. + # The format of the included SQL file for this test suite is important. + # It must end with a trailing newline in order to test the fix for #2161. - # However, as pointed out by #14661, test data loaded by custom SQL + def test_initial_sql(self): + # As pointed out by #14661, test data loaded by custom SQL # can't be relied upon; as a result, the test framework flushes the # data contents before every test. This test validates that this has # occurred. self.assertEqual(Simple.objects.count(), 0) + + def test_custom_sql(self): + from django.core.management.sql import custom_sql_for_model + from django.core.management.color import no_style + from django.db import connections, DEFAULT_DB_ALIAS + + # Simulate the custom SQL loading by syncdb + connection = connections[DEFAULT_DB_ALIAS] + custom_sql = custom_sql_for_model(Simple, no_style(), connection) + self.assertEqual(len(custom_sql), 8) + cursor = connection.cursor() + for sql in custom_sql: + cursor.execute(sql) + self.assertEqual(Simple.objects.count(), 8) From 9ecd978e269f28f711080fa66e47c84e6ff08d90 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 21 Jul 2012 15:38:24 +0200 Subject: [PATCH 217/519] Re-added Windows line ending stripped in previous commit Thanks Aymeric Augustin for noticing the issue. --- tests/regressiontests/initial_sql_regress/sql/simple.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressiontests/initial_sql_regress/sql/simple.sql b/tests/regressiontests/initial_sql_regress/sql/simple.sql index 18fd8a401e..39363baa9a 100644 --- a/tests/regressiontests/initial_sql_regress/sql/simple.sql +++ b/tests/regressiontests/initial_sql_regress/sql/simple.sql @@ -6,5 +6,5 @@ INSERT INTO initial_sql_regress_simple (name) VALUES ('Ringo'); INSERT INTO initial_sql_regress_simple (name) VALUES ('George'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien'); INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man'); -INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending'); +INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending'); From 1af0271d7c6f5ecda2247a2b363d25f493c7b05a Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 21 Jul 2012 13:30:34 -0700 Subject: [PATCH 218/519] Fixed #6170 -- Ensured that a useful exception is raised when a regex is invalid in the URLConf. Thanks to abrahamson.j for the report, to guettli for initial work on the patch, and to David Gouldin for the new patch and test. --- django/core/urlresolvers.py | 8 +++++++- .../regressiontests/urlpatterns_reverse/erroneous_urls.py | 2 ++ tests/regressiontests/urlpatterns_reverse/tests.py | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 625ec6348b..58213a3a73 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -160,10 +160,16 @@ class LocaleRegexProvider(object): language_code = get_language() if language_code not in self._regex_dict: if isinstance(self._regex, basestring): - compiled_regex = re.compile(self._regex, re.UNICODE) + regex = self._regex else: regex = force_unicode(self._regex) + try: compiled_regex = re.compile(regex, re.UNICODE) + except re.error, e: + raise ImproperlyConfigured( + u'"%s" is not a valid regular expression: %s' % + (regex, unicode(e))) + self._regex_dict[language_code] = compiled_regex return self._regex_dict[language_code] diff --git a/tests/regressiontests/urlpatterns_reverse/erroneous_urls.py b/tests/regressiontests/urlpatterns_reverse/erroneous_urls.py index 8e6433e16e..d1e4f3db5d 100644 --- a/tests/regressiontests/urlpatterns_reverse/erroneous_urls.py +++ b/tests/regressiontests/urlpatterns_reverse/erroneous_urls.py @@ -11,4 +11,6 @@ urlpatterns = patterns('', url(r'uncallable/$', 'regressiontests.urlpatterns_reverse.views.uncallable'), # Module does not exist url(r'missing_outer/$', 'regressiontests.urlpatterns_reverse.missing_module.missing_view'), + # Regex contains an error (refs #6170) + url(r'(regex_error/$', 'regressiontestes.urlpatterns_reverse.views.empty_view'), ) diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index bb25806830..500a0e0327 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -511,3 +511,11 @@ class ErroneousViewTests(TestCase): self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/') self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/') + def test_erroneous_reverse(self): + """ + Ensure that a useful exception is raised when a regex is invalid in the + URLConf. + Refs #6170. + """ + # The regex error will be hit before NoReverseMatch can be raised + self.assertRaises(ImproperlyConfigured, reverse, 'whatever blah blah') From 4ceb9db9d849905720a1c958a5f8629753ef6604 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 21 Jul 2012 22:38:25 +0200 Subject: [PATCH 219/519] Removed u prefix on a unicode string. --- django/core/urlresolvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 58213a3a73..9745eb65b7 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -167,7 +167,7 @@ class LocaleRegexProvider(object): compiled_regex = re.compile(regex, re.UNICODE) except re.error, e: raise ImproperlyConfigured( - u'"%s" is not a valid regular expression: %s' % + '"%s" is not a valid regular expression: %s' % (regex, unicode(e))) self._regex_dict[language_code] = compiled_regex From 2b6644388f1a33f0b2eeb1c7a576fc04cfa72e0a Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 21 Jul 2012 15:13:55 -0700 Subject: [PATCH 220/519] Fixed an `except` statement to be Python3-compatible. Thanks to charettes for the tip. --- django/core/urlresolvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 9745eb65b7..a5248f2a5d 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -165,7 +165,7 @@ class LocaleRegexProvider(object): regex = force_unicode(self._regex) try: compiled_regex = re.compile(regex, re.UNICODE) - except re.error, e: + except re.error as e: raise ImproperlyConfigured( '"%s" is not a valid regular expression: %s' % (regex, unicode(e))) From ea667ee3aeed33bce1dd681d9c0ea42f9926db5a Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 21 Jul 2012 20:16:47 -0300 Subject: [PATCH 221/519] Made LiveServerTestCase to restore state on exit. The piece of state is DB connections' allow_thread_sharing attribute which gets munged test are run when in-memory SQLite databases. Thanks Anssi for suggesting the possible root cause and Julien for implementing the fix. --- django/test/testcases.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/django/test/testcases.py b/django/test/testcases.py index 95e751d384..0a0b029796 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1138,4 +1138,11 @@ class LiveServerTestCase(TransactionTestCase): if hasattr(cls, 'server_thread'): # Terminate the live server's thread cls.server_thread.join() + + # Restore sqlite connections' non-sharability + for conn in connections.all(): + if (conn.settings_dict['ENGINE'] == 'django.db.backends.sqlite3' + and conn.settings_dict['NAME'] == ':memory:'): + conn.allow_thread_sharing = False + super(LiveServerTestCase, cls).tearDownClass() From 01c392623d988d7486bdaa870886df0ea3da5fa7 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 21 Jul 2012 18:10:24 -0700 Subject: [PATCH 222/519] Fixed #10057 -- Ensured that the 'show_delete' context variable in the admin's change view actually controls the display of the delete button. Thanks to rajeesh for the report, to patcoll for the patch, and to David Gouldin for the test. --- django/contrib/admin/options.py | 1 - django/contrib/admin/templatetags/admin_modify.py | 2 +- tests/regressiontests/admin_views/admin.py | 8 +++++++- tests/regressiontests/admin_views/customadmin.py | 1 + tests/regressiontests/admin_views/models.py | 7 +++++++ tests/regressiontests/admin_views/tests.py | 13 ++++++++++++- 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 4d23f8f384..40f6e2842c 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -998,7 +998,6 @@ class ModelAdmin(BaseModelAdmin): 'title': _('Add %s') % force_unicode(opts.verbose_name), 'adminform': adminForm, 'is_popup': "_popup" in request.REQUEST, - 'show_delete': False, 'media': media, 'inline_admin_formsets': inline_admin_formsets, 'errors': helpers.AdminErrorList(form, formsets), diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index e55b3bf2bd..c190533f95 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -32,7 +32,7 @@ def submit_row(context): 'onclick_attrib': (opts.get_ordered_objects() and change and 'onclick="submitOrderForm();"' or ''), 'show_delete_link': (not is_popup and context['has_delete_permission'] - and (change or context['show_delete'])), + and change and context.get('show_delete', True)), 'show_save_as_new': not is_popup and change and save_as, 'show_save_and_add_another': context['has_add_permission'] and not is_popup and (not save_as or context['add']), diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py index 01a19e6373..6ec933f89b 100644 --- a/tests/regressiontests/admin_views/admin.py +++ b/tests/regressiontests/admin_views/admin.py @@ -27,7 +27,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture, Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated, - RelatedPrepopulated) + RelatedPrepopulated, UndeletableObject) def callable_year(dt_value): @@ -569,6 +569,11 @@ class UnorderedObjectAdmin(admin.ModelAdmin): list_per_page = 2 +class UndeletableObjectAdmin(admin.ModelAdmin): + def change_view(self, *args, **kwargs): + kwargs['extra_context'] = {'show_delete': False} + return super(UndeletableObjectAdmin, self).change_view(*args, **kwargs) + site = admin.AdminSite(name="admin") site.register(Article, ArticleAdmin) @@ -616,6 +621,7 @@ site.register(OtherStory, OtherStoryAdmin) site.register(Report, ReportAdmin) site.register(MainPrepopulated, MainPrepopulatedAdmin) site.register(UnorderedObject, UnorderedObjectAdmin) +site.register(UndeletableObject, UndeletableObjectAdmin) # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. # That way we cover all four cases: diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py index d205e0e290..142527b022 100644 --- a/tests/regressiontests/admin_views/customadmin.py +++ b/tests/regressiontests/admin_views/customadmin.py @@ -48,3 +48,4 @@ site.register(models.Thing, base_admin.ThingAdmin) site.register(models.Fabric, base_admin.FabricAdmin) site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin) site.register(User, UserLimitedAdmin) +site.register(models.UndeletableObject, base_admin.UndeletableObjectAdmin) diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index ab2bc202f9..aee193b572 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -611,3 +611,10 @@ class UnorderedObject(models.Model): """ name = models.CharField(max_length=255) bool = models.BooleanField(default=True) + +class UndeletableObject(models.Model): + """ + Model whose show_delete in admin change_view has been disabled + Refs #10057. + """ + name = models.CharField(max_length=255) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 49ec3c1945..630758a91d 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -41,7 +41,8 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount, FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, - Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject) + Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject, + UndeletableObject) ERROR_MESSAGE = "Please enter the correct username and password \ @@ -588,6 +589,16 @@ class AdminViewBasicTest(TestCase): 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.') + def test_change_view_with_show_delete_extra_context(self): + """ + Ensured that the 'show_delete' context variable in the admin's change + view actually controls the display of the delete button. + Refs #10057. + """ + instance = UndeletableObject.objects.create(name='foo') + response = self.client.get('/test_admin/%s/admin_views/undeletableobject/%d/' % + (self.urlbit, instance.pk)) + self.assertNotContains(response, 'deletelink') @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewFormUrlTest(TestCase): From 8b0190984116873158ee627c7a033a7bd4c3a199 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 11:32:38 +0200 Subject: [PATCH 223/519] [py3] Bundled six for Python 3 compatibility. Refs #18363. --- django/utils/py3.py | 109 ------------- django/utils/six.py | 353 ++++++++++++++++++++++++++++++++++++++++ docs/topics/python3.txt | 257 +++-------------------------- 3 files changed, 380 insertions(+), 339 deletions(-) delete mode 100644 django/utils/py3.py create mode 100644 django/utils/six.py diff --git a/django/utils/py3.py b/django/utils/py3.py deleted file mode 100644 index 8a5c37e976..0000000000 --- a/django/utils/py3.py +++ /dev/null @@ -1,109 +0,0 @@ -# Compatibility layer for running Django both in 2.x and 3.x - -import sys - -if sys.version_info[0] < 3: - PY3 = False - # Changed module locations - from urlparse import (urlparse, urlunparse, urljoin, urlsplit, urlunsplit, - urldefrag, parse_qsl) - from urllib import (quote, unquote, quote_plus, urlopen, urlencode, - url2pathname, urlretrieve, unquote_plus) - from urllib2 import (Request, OpenerDirector, UnknownHandler, HTTPHandler, - HTTPSHandler, HTTPDefaultErrorHandler, FTPHandler, - HTTPError, HTTPErrorProcessor) - import urllib2 - import Cookie as cookies - try: - import cPickle as pickle - except ImportError: - import pickle - try: - import thread - except ImportError: - import dummy_thread as thread - from htmlentitydefs import name2codepoint - import HTMLParser - from os import getcwdu - from itertools import izip as zip - unichr = unichr - xrange = xrange - maxsize = sys.maxint - - # Type aliases - string_types = basestring, - text_type = unicode - integer_types = int, long - long_type = long - - from io import BytesIO as OutputIO - - # Glue code for syntax differences - def reraise(tp, value, tb=None): - exec("raise tp, value, tb") - - def with_metaclass(meta, base=object): - class _DjangoBase(base): - __metaclass__ = meta - return _DjangoBase - - iteritems = lambda o: o.iteritems() - itervalues = lambda o: o.itervalues() - iterkeys = lambda o: o.iterkeys() - - # n() is useful when python3 needs a str (unicode), and python2 str (bytes) - n = lambda s: s.encode('utf-8') - -else: - PY3 = True - import builtins - - # Changed module locations - from urllib.parse import (urlparse, urlunparse, urlencode, urljoin, - urlsplit, urlunsplit, quote, unquote, - quote_plus, unquote_plus, parse_qsl, - urldefrag) - from urllib.request import (urlopen, url2pathname, Request, OpenerDirector, - UnknownHandler, HTTPHandler, HTTPSHandler, - HTTPDefaultErrorHandler, FTPHandler, - HTTPError, HTTPErrorProcessor, urlretrieve) - import urllib.request as urllib2 - import http.cookies as cookies - import pickle - try: - import _thread as thread - except ImportError: - import _dummy_thread as thread - from html.entities import name2codepoint - import html.parser as HTMLParser - from os import getcwd as getcwdu - zip = zip - unichr = chr - xrange = range - maxsize = sys.maxsize - - # Type aliases - string_types = str, - text_type = str - integer_types = int, - long_type = int - - from io import StringIO as OutputIO - - # Glue code for syntax differences - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - def with_metaclass(meta, base=object): - ns = dict(base=base, meta=meta) - exec("""class _DjangoBase(base, metaclass=meta): - pass""", ns) - return ns["_DjangoBase"] - - iteritems = lambda o: o.items() - itervalues = lambda o: o.values() - iterkeys = lambda o: o.keys() - - n = lambda s: s diff --git a/django/utils/six.py b/django/utils/six.py new file mode 100644 index 0000000000..6526d76cb1 --- /dev/null +++ b/django/utils/six.py @@ -0,0 +1,353 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.1.0" + + +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + 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 + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) + # This is a bit ugly, but it avoids running this again. + delattr(tp, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + + +class _MovedItems(types.ModuleType): + """Lazy loading of moved objects""" + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) +del attr + +moves = sys.modules["six.moves"] = _MovedItems("moves") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_code = "__code__" + _func_defaults = "__defaults__" + + _iterkeys = "keys" + _itervalues = "values" + _iteritems = "items" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_code = "func_code" + _func_defaults = "func_defaults" + + _iterkeys = "iterkeys" + _itervalues = "itervalues" + _iteritems = "iteritems" + + +if PY3: + def get_unbound_function(unbound): + return unbound + + + advance_iterator = next + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) +else: + def get_unbound_function(unbound): + return unbound.im_func + + + def advance_iterator(it): + return it.next() + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) + + +def iterkeys(d): + """Return an iterator over the keys of a dictionary.""" + return getattr(d, _iterkeys)() + +def itervalues(d): + """Return an iterator over the values of a dictionary.""" + return getattr(d, _itervalues)() + +def iteritems(d): + """Return an iterator over the (key, value) pairs of a dictionary.""" + return getattr(d, _iteritems)() + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + return unicode(s, "unicode_escape") + int2byte = chr + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + import builtins + exec_ = getattr(builtins, "exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = getattr(builtins, "print") + del builtins + +else: + def exec_(code, globs=None, locs=None): + """Execute code in a namespace.""" + if globs is None: + frame = sys._getframe(1) + globs = frame.f_globals + if locs is None: + locs = frame.f_locals + del frame + elif locs is None: + locs = globs + exec("""exec code in globs, locs""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +_add_doc(reraise, """Reraise an exception.""") + + +def with_metaclass(meta, base=object): + """Create a base class with a metaclass.""" + return meta("NewBase", (base,), {}) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 1aea252e3f..e4bfc1bd9c 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -2,251 +2,48 @@ Python 3 compatibility ====================== -Django 1.5 introduces a compatibility layer that allows the code to be run both -in Python 2 (2.6/2.7) and Python 3 (>= 3.2) (*work in progress*). +Django 1.5 is the first version of Django to support Python 3. -This document is not meant as a complete Python 2 to Python 3 migration guide. -There are many existing resources you can read. But we describe some utilities -and guidelines that we recommend you should use when you want to ensure your -code can be run with both Python 2 and 3. +The same code runs both on Python 2 (≥2.6.5) and Python 3 (≥3.2). To +achieve this: -* http://docs.python.org/py3k/howto/pyporting.html -* http://python3porting.com/ +- wherever possible, Django uses the six_ compatibility layer, +- all modules declare ``from __future__ import unicode_literals``. -django.utils.py3 +.. _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`_. But +it describes guidelines that apply to Django's code and are recommended for +pluggable apps that run with both Python 2 and 3. + +.. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html + +.. module: django.utils.six + +django.utils.six ================ -Whenever a symbol or module has different semantics or different locations on -Python 2 and Python 3, you can import it from ``django.utils.py3`` where it -will be automatically converted depending on your current Python version. +Read the documentation of six_. It's the canonical compatibility library for +supporting Python 2 and 3 in a single codebase. -PY3 ---- +``six`` is bundled with Django: you can import it as :mod:`django.utils.six`. -If you need to know anywhere in your code if you are running Python 3 or a -previous Python 2 version, you can check the ``PY3`` boolean variable:: - - from django.utils.py3 import PY3 - - if PY3: - # Do stuff Python 3-wise - else: - # Do stuff Python 2-wise - -This should be considered as a last resort solution when it is not possible -to import a compatible name from django.utils.py3, as described in the sections -below. +.. _string-handling: String handling =============== -In Python 3, all strings are considered Unicode strings by default. Byte strings -have to be prefixed with the letter 'b'. To mimic the same behaviour in Python 2, -we recommend you import ``unicode_literals`` from the ``__future__`` library:: +In Python 3, all strings are considered Unicode strings by default. Byte +strings must be prefixed with the letter ``b``. In order to enable the same +behavior in Python 2, every module must import ``unicode_literals`` from +``__future__``:: from __future__ import unicode_literals my_string = "This is an unicode literal" my_bytestring = b"This is a bytestring" -Be cautious if you have to slice bytestrings. -See http://docs.python.org/py3k/howto/pyporting.html#bytes-literals - -Different expected strings --------------------------- - -Some method parameters have changed the expected string type of a parameter. -For example, ``strftime`` format parameter expects a bytestring on Python 2 but -a normal (Unicode) string on Python 3. For these cases, ``django.utils.py3`` -provides a ``n()`` function which encodes the string parameter only with -Python 2. - - >>> from __future__ import unicode_literals - >>> from datetime import datetime - - >>> print(datetime.date(2012, 5, 21).strftime(n("%m → %Y"))) - 05 → 2012 - -Renamed types -============= - -Several types are named differently in Python 2 and Python 3. In order to keep -compatibility while using those types, import their corresponding aliases from -``django.utils.py3``. - -=========== ========= ===================== -Python 2 Python 3 django.utils.py3 -=========== ========= ===================== -basestring, str, string_types (tuple) -unicode str text_type -int, long int, integer_types (tuple) -long int long_type -=========== ========= ===================== - -String aliases --------------- - -Code sample:: - - if isinstance(foo, basestring): - print("foo is a string") - - # I want to convert a number to a Unicode string - bar = 45 - bar_string = unicode(bar) - -Should be replaced by:: - - from django.utils.py3 import string_types, text_type - - if isinstance(foo, string_types): - print("foo is a string") - - # I want to convert a number to a Unicode string - bar = 45 - bar_string = text_type(bar) - -No more long type ------------------ - -``long`` and ``int`` types have been unified in Python 3, meaning that ``long`` -is no longer available. ``django.utils.py3`` provides both ``long_type`` and -``integer_types`` aliases. For example: - -.. code-block:: python - - # Old Python 2 code - my_var = long(333463247234623) - if isinstance(my_var, (int, long)): - # ... - -Should be replaced by: - -.. code-block:: python - - from django.utils.py3 import long_type, integer_types - - my_var = long_type(333463247234623) - if isinstance(my_var, integer_types): - # ... - - -Changed module locations -======================== - -The following modules have changed their location in Python 3. Therefore, it is -recommended to import them from the ``django.utils.py3`` compatibility layer: - -=============================== ====================================== ====================== -Python 2 Python3 django.utils.py3 -=============================== ====================================== ====================== -Cookie http.cookies cookies - -urlparse.urlparse urllib.parse.urlparse urlparse -urlparse.urlunparse urllib.parse.urlunparse urlunparse -urlparse.urljoin urllib.parse.urljoin urljoin -urlparse.urlsplit urllib.parse.urlsplit urlsplit -urlparse.urlunsplit urllib.parse.urlunsplit urlunsplit -urlparse.urldefrag urllib.parse.urldefrag urldefrag -urlparse.parse_qsl urllib.parse.parse_qsl parse_qsl -urllib.quote urllib.parse.quote quote -urllib.unquote urllib.parse.unquote unquote -urllib.quote_plus urllib.parse.quote_plus quote_plus -urllib.unquote_plus urllib.parse.unquote_plus unquote_plus -urllib.urlencode urllib.parse.urlencode urlencode -urllib.urlopen urllib.request.urlopen urlopen -urllib.url2pathname urllib.request.url2pathname url2pathname -urllib.urlretrieve urllib.request.urlretrieve urlretrieve -urllib2 urllib.request urllib2 -urllib2.Request urllib.request.Request Request -urllib2.OpenerDirector urllib.request.OpenerDirector OpenerDirector -urllib2.UnknownHandler urllib.request.UnknownHandler UnknownHandler -urllib2.HTTPHandler urllib.request.HTTPHandler HTTPHandler -urllib2.HTTPSHandler urllib.request.HTTPSHandler HTTPSHandler -urllib2.HTTPDefaultErrorHandler urllib.request.HTTPDefaultErrorHandler HTTPDefaultErrorHandler -urllib2.FTPHandler urllib.request.FTPHandler FTPHandler -urllib2.HTTPError urllib.request.HTTPError HTTPError -urllib2.HTTPErrorProcessor urllib.request.HTTPErrorProcessor HTTPErrorProcessor - -htmlentitydefs.name2codepoint html.entities.name2codepoint name2codepoint -HTMLParser html.parser HTMLParser -cPickle/pickle pickle pickle -thread/dummy_thread _thread/_dummy_thread thread - -os.getcwdu os.getcwd getcwdu -itertools.izip zip zip -sys.maxint sys.maxsize maxsize -unichr chr unichr -xrange range xrange -=============================== ====================================== ====================== - - -Output encoding now Unicode -=========================== - -If you want to catch stdout/stderr output, the output content is UTF-8 encoded -in Python 2, while it is Unicode strings in Python 3. You can use the OutputIO -stream to capture this output:: - - from django.utils.py3 import OutputIO - - try: - old_stdout = sys.stdout - out = OutputIO() - sys.stdout = out - # Do stuff which produces standard output - result = out.getvalue() - finally: - sys.stdout = old_stdout - -Dict iteritems/itervalues/iterkeys -================================== - -The iteritems(), itervalues() and iterkeys() methods of dictionaries do not -exist any more in Python 3, simply because they represent the default items() -values() and keys() behavior in Python 3. Therefore, to keep compatibility, -use similar functions from ``django.utils.py3``:: - - from django.utils.py3 import iteritems, itervalues, iterkeys - - my_dict = {'a': 21, 'b': 42} - for key, value in iteritems(my_dict): - # ... - for value in itervalues(my_dict): - # ... - for key in iterkeys(my_dict): - # ... - -Note that in Python 3, dict.keys(), dict.items() and dict.values() return -"views" instead of lists. Wrap them into list() if you really need their return -values to be in a list. - -http://docs.python.org/release/3.0.1/whatsnew/3.0.html#views-and-iterators-instead-of-lists - -Metaclass -========= - -The syntax for declaring metaclasses has changed in Python 3. -``django.utils.py3`` offers a compatible way to declare metaclasses:: - - from django.utils.py3 import with_metaclass - - class MyClass(with_metaclass(SubClass1, SubClass2,...)): - # ... - -Re-raising exceptions -===================== - -One of the syntaxes to raise exceptions (raise E, V, T) is gone in Python 3. -This is especially used in very specific cases where you want to re-raise a -different exception that the initial one, while keeping the original traceback. -So, instead of:: - - raise Exception, Exception(msg), traceback - -Use:: - - from django.utils.py3 import reraise - - reraise(Exception, Exception(msg), traceback) +Be cautious if you have to `slice bytestrings`_. +.. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals From 473d5f4ba1ce4ea93e7cb371b78d318423dfebe6 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 16:05:39 +0200 Subject: [PATCH 224/519] [py3] Fixed django.utils.six.moves. It didn't work because six was inside django.utils. --- django/utils/six.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/utils/six.py b/django/utils/six.py index 6526d76cb1..b1d58e5668 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -159,7 +159,7 @@ for attr in _moved_attributes: setattr(_MovedItems, attr.name, attr) del attr -moves = sys.modules["six.moves"] = _MovedItems("moves") +moves = sys.modules["django.utils.six.moves"] = _MovedItems("moves") def add_move(move): From d796c94b03ffbe90895ec68c5041806907cb9577 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 12:18:38 +0200 Subject: [PATCH 225/519] [py3] Used six.reraise wherever necessary. --- django/core/handlers/base.py | 3 ++- django/db/backends/mysql/base.py | 17 +++++++++-------- django/db/backends/oracle/base.py | 19 ++++++++++--------- .../db/backends/postgresql_psycopg2/base.py | 11 ++++++----- django/db/backends/sqlite3/base.py | 9 +++++---- django/db/models/query.py | 3 ++- django/http/__init__.py | 3 ++- django/test/client.py | 3 ++- 8 files changed, 38 insertions(+), 30 deletions(-) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 4c07524ecc..7fd7d19c4a 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -7,6 +7,7 @@ from django.core import signals from django.utils.encoding import force_unicode from django.utils.importlib import import_module from django.utils.log import getLogger +from django.utils import six logger = getLogger('django.request') @@ -224,7 +225,7 @@ class BaseHandler(object): # If Http500 handler is not installed, re-raise last exception if resolver.urlconf_module is None: - raise exc_info[1], None, exc_info[2] + six.reraise(exc_info[1], None, exc_info[2]) # Return an HttpResponse that displays a friendly error message. callback, param_dict = resolver.resolve500() return callback(request, **param_dict) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index f381d48307..201fed66a6 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -39,6 +39,7 @@ from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.validation import DatabaseValidation from django.utils.functional import cached_property from django.utils.safestring import SafeString, SafeUnicode +from django.utils import six from django.utils import timezone # Raise exceptions for database warnings if DEBUG is on @@ -117,29 +118,29 @@ class CursorWrapper(object): try: return self.cursor.execute(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.OperationalError as e: # Map some error codes to IntegrityError, since they seem to be # misclassified and Django would prefer the more logical place. if e[0] in self.codes_for_integrityerror: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def executemany(self, query, args): try: return self.cursor.executemany(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.OperationalError as e: # Map some error codes to IntegrityError, since they seem to be # misclassified and Django would prefer the more logical place. if e[0] in self.codes_for_integrityerror: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def __getattr__(self, attr): if attr in self.__dict__: diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 49b628075b..705ae30ccc 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -53,6 +53,7 @@ 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_str, force_unicode +from django.utils import six from django.utils import timezone DatabaseError = Database.DatabaseError @@ -549,7 +550,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): except Database.IntegrityError as e: # In case cx_Oracle implements (now or in a future version) # raising this specific exception - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle 5.0.4 raises a cx_Oracle.DatabaseError exception # with the following attributes and values: @@ -561,8 +562,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): x = e.args[0] if hasattr(x, 'code') and hasattr(x, 'message') \ and x.code == 2091 and 'ORA-02291' in x.message: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) class OracleParam(object): @@ -688,12 +689,12 @@ class FormatStylePlaceholderCursor(object): try: return self.cursor.execute(query, self._param_generator(params)) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def executemany(self, query, params=None): # cx_Oracle doesn't support iterators, convert them to lists @@ -717,12 +718,12 @@ class FormatStylePlaceholderCursor(object): return self.cursor.executemany(query, [self._param_generator(p) for p in formatted]) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def fetchone(self): row = self.cursor.fetchone() diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index f76b91ddd6..8b21b8ceb8 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -15,6 +15,7 @@ from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection from django.utils.log import getLogger from django.utils.safestring import SafeUnicode, SafeString +from django.utils import six from django.utils.timezone import utc try: @@ -51,17 +52,17 @@ class CursorWrapper(object): try: return self.cursor.execute(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def executemany(self, query, args): try: return self.cursor.executemany(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def __getattr__(self, attr): if attr in self.__dict__: @@ -236,4 +237,4 @@ class DatabaseWrapper(BaseDatabaseWrapper): try: return self.connection.commit() except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 60723124c1..4c7b881e11 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -21,6 +21,7 @@ from django.db.backends.sqlite3.introspection import DatabaseIntrospection 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 import six from django.utils import timezone try: @@ -348,18 +349,18 @@ class SQLiteCursorWrapper(Database.Cursor): try: return Database.Cursor.execute(self, query, params) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def executemany(self, query, param_list): query = self.convert_query(query) try: return Database.Cursor.executemany(self, query, param_list) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) def convert_query(self, query): return FORMAT_QMARK_REGEX.sub('?', query).replace('%%','%') diff --git a/django/db/models/query.py b/django/db/models/query.py index ebe61a1226..f9e757d9f1 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -14,6 +14,7 @@ from django.db.models.query_utils import (Q, select_related_descend, from django.db.models.deletion import Collector from django.db.models import sql from django.utils.functional import partition +from django.utils import six # Used to control how many objects are worked with at once in some cases (e.g. # when deleting objects). @@ -470,7 +471,7 @@ class QuerySet(object): return self.get(**lookup), False except self.model.DoesNotExist: # Re-raise the IntegrityError with its original traceback. - raise exc_info[1], None, exc_info[2] + six.reraise(exc_info[1], None, exc_info[2]) def latest(self, field_name=None): """ diff --git a/django/http/__init__.py b/django/http/__init__.py index 4b8f70fb4b..da1f9edc63 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -84,6 +84,7 @@ from django.http.utils import * from django.utils.datastructures import MultiValueDict, ImmutableList from django.utils.encoding import smart_str, iri_to_uri, force_unicode from django.utils.http import cookie_date +from django.utils import six from django.utils import timezone RESERVED_CHARS="!*'();:@&=+$,/?%#[]" @@ -290,7 +291,7 @@ class HttpRequest(object): try: self._body = self.read() except IOError as e: - raise UnreadablePostError, e, sys.exc_traceback + six.reraise(UnreadablePostError, e, sys.exc_traceback) self._stream = BytesIO(self._body) return self._body diff --git a/django/test/client.py b/django/test/client.py index 79844426f1..74e11a0efd 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -20,6 +20,7 @@ from django.utils.encoding import smart_str from django.utils.http import urlencode from django.utils.importlib import import_module from django.utils.itercompat import is_iterable +from django.utils import six from django.db import close_connection from django.test.utils import ContextList @@ -381,7 +382,7 @@ class Client(RequestFactory): if self.exc_info: exc_info = self.exc_info self.exc_info = None - raise exc_info[1], None, exc_info[2] + six.reraise(exc_info[1], None, exc_info[2]) # Save the client and request that stimulated the response. response.client = self From 7fa51a24a8ca47f41f5d711e81b8a71b08542a36 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 21 Jul 2012 21:06:13 +0200 Subject: [PATCH 226/519] [py3] Exception aren't iterable in Python 3. --- django/db/backends/mysql/base.py | 16 ++++++++-------- django/db/backends/oracle/base.py | 18 +++++++++--------- django/db/backends/postgresql_psycopg2/base.py | 10 +++++----- django/db/backends/sqlite3/base.py | 8 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 201fed66a6..9f5b9f5b77 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -118,29 +118,29 @@ class CursorWrapper(object): try: return self.cursor.execute(query, args) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.OperationalError as e: # Map some error codes to IntegrityError, since they seem to be # misclassified and Django would prefer the more logical place. if e[0] in self.codes_for_integrityerror: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, args): try: return self.cursor.executemany(query, args) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.OperationalError as e: # Map some error codes to IntegrityError, since they seem to be # misclassified and Django would prefer the more logical place. if e[0] in self.codes_for_integrityerror: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def __getattr__(self, attr): if attr in self.__dict__: diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 705ae30ccc..88a243a46d 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -550,7 +550,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): except Database.IntegrityError as e: # In case cx_Oracle implements (now or in a future version) # raising this specific exception - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle 5.0.4 raises a cx_Oracle.DatabaseError exception # with the following attributes and values: @@ -562,8 +562,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): x = e.args[0] if hasattr(x, 'code') and hasattr(x, 'message') \ and x.code == 2091 and 'ORA-02291' in x.message: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) class OracleParam(object): @@ -689,12 +689,12 @@ class FormatStylePlaceholderCursor(object): try: return self.cursor.execute(query, self._param_generator(params)) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, params=None): # cx_Oracle doesn't support iterators, convert them to lists @@ -718,12 +718,12 @@ class FormatStylePlaceholderCursor(object): return self.cursor.executemany(query, [self._param_generator(p) for p in formatted]) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def fetchone(self): row = self.cursor.fetchone() diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 8b21b8ceb8..d553594ec1 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -52,17 +52,17 @@ class CursorWrapper(object): try: return self.cursor.execute(query, args) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, args): try: return self.cursor.executemany(query, args) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def __getattr__(self, attr): if attr in self.__dict__: @@ -237,4 +237,4 @@ class DatabaseWrapper(BaseDatabaseWrapper): try: return self.connection.commit() except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 4c7b881e11..e2149ca8d8 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -349,18 +349,18 @@ class SQLiteCursorWrapper(Database.Cursor): try: return Database.Cursor.execute(self, query, params) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, param_list): query = self.convert_query(query) try: return Database.Cursor.executemany(self, query, param_list) except Database.IntegrityError as e: - six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def convert_query(self, query): return FORMAT_QMARK_REGEX.sub('?', query).replace('%%','%') From d11d45aad969be313b9e046d0d42b179a3fb6906 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 21:20:42 +0200 Subject: [PATCH 227/519] [py3] Used six.with_metaclass wherever necessary. --- django/contrib/admin/options.py | 4 ++-- django/db/models/base.py | 18 ++++++++++++++---- django/forms/forms.py | 4 ++-- django/forms/models.py | 6 ++++-- django/forms/widgets.py | 4 ++-- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 40f6e2842c..c13a6bc5cc 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -24,6 +24,7 @@ from django.utils.decorators import method_decorator from django.utils.datastructures import SortedDict from django.utils.html import escape, escapejs from django.utils.safestring import mark_safe +from django.utils import six from django.utils.text import capfirst, get_text_list from django.utils.translation import ugettext as _ from django.utils.translation import ungettext @@ -57,9 +58,8 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = { csrf_protect_m = method_decorator(csrf_protect) -class BaseModelAdmin(object): +class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): """Functionality common to both ModelAdmin and InlineAdmin.""" - __metaclass__ = forms.MediaDefiningClass raw_id_fields = () fields = None diff --git a/django/db/models/base.py b/django/db/models/base.py index 79af1cb34c..04ae4bc96d 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -24,6 +24,7 @@ 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_str, force_unicode +from django.utils import six from django.utils.text import get_text_list, capfirst @@ -275,8 +276,8 @@ class ModelState(object): # This impacts validation only; it has no effect on the actual save. self.adding = True -class Model(object): - __metaclass__ = ModelBase + +class ModelWithoutMeta(object): _deferred = False def __init__(self, *args, **kwargs): @@ -369,7 +370,7 @@ class Model(object): pass if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]) - super(Model, self).__init__() + super(ModelWithoutMeta, self).__init__() signals.post_init.send(sender=self.__class__, instance=self) def __repr__(self): @@ -401,7 +402,7 @@ class Model(object): only module-level classes can be pickled by the default path. """ if not self._deferred: - return super(Model, self).__reduce__() + return super(ModelWithoutMeta, self).__reduce__() data = self.__dict__ defers = [] for field in self._meta.fields: @@ -876,6 +877,15 @@ class Model(object): raise ValidationError(errors) +# For unknown reasons, six.with_metaclass doesn't work correctly for Model. +# Fallback to exec'ing the appropriate syntax for each Python version. + +if six.PY3: + six.exec_("class Model(ModelWithoutMeta, metaclass=ModelBase): pass") +else: + six.exec_("class Model(ModelWithoutMeta): __metaclass__ = ModelBase") + + ############################################ # HELPER FUNCTIONS (CURRIED MODEL METHODS) # ############################################ diff --git a/django/forms/forms.py b/django/forms/forms.py index 7942275609..0af71918d8 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -14,6 +14,7 @@ from django.utils.datastructures import SortedDict from django.utils.html import conditional_escape, format_html from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode from django.utils.safestring import mark_safe +from django.utils import six __all__ = ('BaseForm', 'Form') @@ -380,14 +381,13 @@ class BaseForm(StrAndUnicode): """ return [field for field in self if not field.is_hidden] -class Form(BaseForm): +class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)): "A collection of Fields, plus their associated data." # This is a separate class from BaseForm in order to abstract the way # self.fields is specified. This class (Form) is the one that does the # fancy metaclass stuff purely for the semantic sugar -- it allows one # to define a form using declarative syntax. # BaseForm itself has no way of designating self.fields. - __metaclass__ = DeclarativeFieldsMetaclass class BoundField(StrAndUnicode): "A Field plus data" diff --git a/django/forms/models.py b/django/forms/models.py index 26f869155b..1ef308d93a 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -15,6 +15,7 @@ from django.forms.widgets import (SelectMultiple, HiddenInput, MultipleHiddenInput, media_property) from django.utils.encoding import smart_unicode, force_unicode 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 @@ -365,8 +366,8 @@ class BaseModelForm(BaseForm): save.alters_data = True -class ModelForm(BaseModelForm): - __metaclass__ = ModelFormMetaclass +class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)): + pass def modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None): @@ -401,6 +402,7 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, form_metaclass = ModelFormMetaclass + # TODO: this doesn't work under Python 3. if issubclass(form, BaseModelForm) and hasattr(form, '__metaclass__'): form_metaclass = form.__metaclass__ diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 04a838093c..20fa9e973a 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -17,6 +17,7 @@ from django.utils.translation import ugettext, ugettext_lazy from django.utils.encoding import StrAndUnicode, force_unicode from django.utils.safestring import mark_safe from django.utils import datetime_safe, formats +from django.utils import six __all__ = ( 'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput', @@ -153,8 +154,7 @@ class SubWidget(StrAndUnicode): args.append(self.choices) return self.parent_widget.render(*args) -class Widget(object): - __metaclass__ = MediaDefiningClass +class Widget(six.with_metaclass(MediaDefiningClass)): is_hidden = False # Determines whether this corresponds to an . needs_multipart_form = False # Determines does this widget need multipart form is_localized = False From f1d5dc81ac37fe9a7c7ca860900ee6a16150bb09 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 21 Jul 2012 22:02:28 +0200 Subject: [PATCH 228/519] [py3] Switched to Python 3-compatible introspection. --- django/contrib/syndication/views.py | 8 ++++---- django/test/_doctest.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 773a1cd7bd..3c84f1f60c 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -60,14 +60,14 @@ class Feed(object): except AttributeError: return default if callable(attr): - # Check func_code.co_argcount rather than try/excepting the + # Check __code__.co_argcount rather than try/excepting the # function and catching the TypeError, because something inside # the function may raise the TypeError. This technique is more # accurate. - if hasattr(attr, 'func_code'): - argcount = attr.func_code.co_argcount + if hasattr(attr, '__code__'): + argcount = attr.__code__.co_argcount else: - argcount = attr.__call__.func_code.co_argcount + argcount = attr.__call__.__code__.co_argcount if argcount == 2: # one argument is 'self' return attr(obj) else: diff --git a/django/test/_doctest.py b/django/test/_doctest.py index ab8f034570..b5d5da970b 100644 --- a/django/test/_doctest.py +++ b/django/test/_doctest.py @@ -861,7 +861,7 @@ class DocTestFinder: if module is None: return True elif inspect.isfunction(object): - return module.__dict__ is object.func_globals + return module.__dict__ is object.__globals__ elif inspect.isclass(object): return module.__name__ == object.__module__ elif inspect.getmodule(object) is not None: @@ -926,7 +926,7 @@ class DocTestFinder: if isinstance(val, staticmethod): val = getattr(obj, valname) if isinstance(val, classmethod): - val = getattr(obj, valname).im_func + val = getattr(obj, valname).__func__ # Recurse to methods, properties, and nested classes. if ((inspect.isfunction(val) or inspect.isclass(val) or @@ -998,8 +998,8 @@ class DocTestFinder: break # Find the line number for functions & methods. - if inspect.ismethod(obj): obj = obj.im_func - if inspect.isfunction(obj): obj = obj.func_code + if inspect.ismethod(obj): obj = obj.__func__ + if inspect.isfunction(obj): obj = obj.__code__ if inspect.istraceback(obj): obj = obj.tb_frame if inspect.isframe(obj): obj = obj.f_code if inspect.iscode(obj): From 56dbe924a6e700cefbfd34f1a5aa6c1ee01478dc Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 12:45:19 +0200 Subject: [PATCH 229/519] [py3] Removed longs. --- django/contrib/admin/util.py | 3 ++- .../gis/db/backends/oracle/operations.py | 3 ++- .../gis/db/backends/postgis/operations.py | 3 ++- .../gis/db/backends/spatialite/operations.py | 3 ++- django/contrib/gis/db/models/query.py | 11 ++++---- django/contrib/gis/gdal/base.py | 3 ++- django/contrib/gis/gdal/geometries.py | 8 +++--- django/contrib/gis/gdal/layer.py | 18 +++++++------ .../contrib/gis/gdal/prototypes/errcheck.py | 19 +++++++------- django/contrib/gis/gdal/srs.py | 26 ++++++++++--------- django/contrib/gis/geos/mutable_list.py | 7 ++--- django/contrib/gis/geos/point.py | 5 ++-- django/contrib/gis/geos/polygon.py | 3 ++- .../gis/geos/tests/test_mutable_list.py | 3 ++- django/contrib/gis/measure.py | 3 ++- django/db/backends/creation.py | 2 +- django/db/backends/mysql/base.py | 2 +- django/db/backends/oracle/base.py | 2 +- django/db/models/query.py | 2 +- django/utils/crypto.py | 2 +- django/utils/encoding.py | 9 +++---- django/utils/formats.py | 5 ++-- tests/modeltests/basic/tests.py | 22 +++++++++------- tests/modeltests/field_defaults/tests.py | 3 ++- tests/regressiontests/i18n/tests.py | 3 ++- tests/regressiontests/model_fields/tests.py | 7 ++--- 26 files changed, 99 insertions(+), 78 deletions(-) diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 92e1c0efd5..1d43585fcd 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -13,6 +13,7 @@ from django.utils.html import format_html from django.utils.text import capfirst from django.utils import timezone from django.utils.encoding import force_unicode, smart_unicode, smart_str +from django.utils import six from django.utils.translation import ungettext from django.core.urlresolvers import reverse @@ -349,7 +350,7 @@ def display_for_value(value, boolean=False): return formats.localize(timezone.template_localtime(value)) elif isinstance(value, (datetime.date, datetime.time)): return formats.localize(value) - elif isinstance(value, (decimal.Decimal, float, int, long)): + elif isinstance(value, six.integer_types + (decimal.Decimal, float)): return formats.number_format(value) else: return smart_unicode(value) diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index a2374bb808..3a189ea1fe 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -16,6 +16,7 @@ from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter from django.contrib.gis.db.backends.util import SpatialFunction from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Distance +from django.utils import six class SDOOperation(SpatialFunction): "Base class for SDO* Oracle operations." @@ -65,7 +66,7 @@ class SDORelate(SpatialFunction): super(SDORelate, self).__init__(self.relate_func, mask=mask) # Valid distance types and substitutions -dtypes = (Decimal, Distance, float, int, long) +dtypes = (Decimal, Distance, float) + six.integer_types class OracleOperations(DatabaseOperations, BaseSpatialOperations): compiler_module = "django.contrib.gis.db.backends.oracle.compiler" diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 964be8de0e..8f190882e1 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -10,6 +10,7 @@ from django.contrib.gis.measure import Distance from django.core.exceptions import ImproperlyConfigured from django.db.backends.postgresql_psycopg2.base import DatabaseOperations from django.db.utils import DatabaseError +from django.utils import six #### Classes used in constructing PostGIS spatial SQL #### class PostGISOperator(SpatialOperation): @@ -165,7 +166,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): } # Valid distance types and substitutions - dtypes = (Decimal, Distance, float, int, long) + dtypes = (Decimal, Distance, float) + six.integer_types def get_dist_ops(operator): "Returns operations for both regular and spherical distances." return {'cartesian' : PostGISDistance(prefix, operator), diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 6adcdc5275..ffd7d33dad 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -9,6 +9,7 @@ from django.contrib.gis.measure import Distance from django.core.exceptions import ImproperlyConfigured from django.db.backends.sqlite3.base import DatabaseOperations from django.db.utils import DatabaseError +from django.utils import six class SpatiaLiteOperator(SpatialOperation): "For SpatiaLite operators (e.g. `&&`, `~`)." @@ -42,7 +43,7 @@ class SpatiaLiteRelate(SpatiaLiteFunctionParam): super(SpatiaLiteRelate, self).__init__('Relate') # Valid distance types and substitutions -dtypes = (Decimal, Distance, float, int, long) +dtypes = (Decimal, Distance, float) + six.integer_types def get_dist_ops(operator): "Returns operations for regular distances; spherical distances are not currently supported." return (SpatiaLiteDistance(operator),) diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index c1e360de27..a11b1213f4 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -6,6 +6,7 @@ from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineS 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 class GeoQuerySet(QuerySet): "The Geographic QuerySet." @@ -144,7 +145,7 @@ class GeoQuerySet(QuerySet): if not backend.geojson: raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.') - if not isinstance(precision, (int, long)): + if not isinstance(precision, six.integer_types): raise TypeError('Precision keyword must be set with an integer.') # Setting the options flag -- which depends on which version of @@ -173,7 +174,7 @@ class GeoQuerySet(QuerySet): The `precision` keyword may be used to custom the number of _characters_ used in the output GeoHash, the default is 20. """ - s = {'desc' : 'GeoHash', + s = {'desc' : 'GeoHash', 'procedure_args': {'precision': precision}, 'procedure_fmt': '%(geo_col)s,%(precision)s', } @@ -309,7 +310,7 @@ class GeoQuerySet(QuerySet): - 2 arguments: X and Y sizes to snap the grid to. - 4 arguments: X, Y sizes and the X, Y origins. """ - if False in [isinstance(arg, (float, int, long)) for arg in args]: + if False in [isinstance(arg, (float,) + six.integer_types) for arg in args]: raise TypeError('Size argument(s) for the grid must be a float or integer values.') nargs = len(args) @@ -349,7 +350,7 @@ class GeoQuerySet(QuerySet): digits used in output (defaults to 8). """ relative = int(bool(relative)) - if not isinstance(precision, (int, long)): + if not isinstance(precision, six.integer_types): raise TypeError('SVG precision keyword argument must be an integer.') s = {'desc' : 'SVG', 'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s', @@ -390,7 +391,7 @@ class GeoQuerySet(QuerySet): Transforms the given geometry field to the given SRID. If no SRID is provided, the transformation will default to using 4326 (WGS84). """ - if not isinstance(srid, (int, long)): + if not isinstance(srid, six.integer_types): raise TypeError('An integer SRID must be provided.') field_name = kwargs.get('field_name', None) tmp, geo_field = self._spatial_setup('transform', field_name=field_name) diff --git a/django/contrib/gis/gdal/base.py b/django/contrib/gis/gdal/base.py index 36c03eb51e..e86277e95e 100644 --- a/django/contrib/gis/gdal/base.py +++ b/django/contrib/gis/gdal/base.py @@ -1,6 +1,7 @@ from ctypes import c_void_p from django.contrib.gis.gdal.error import GDALException +from django.utils import six class GDALBase(object): """ @@ -24,7 +25,7 @@ class GDALBase(object): def _set_ptr(self, ptr): # Only allow the pointer to be set with pointers of the # compatible type or None (NULL). - if isinstance(ptr, (int, long)): + if isinstance(ptr, six.integer_types): self._ptr = self.ptr_type(ptr) elif ptr is None or isinstance(ptr, self.ptr_type): self._ptr = ptr diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 3feb18a923..617c4acef4 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -57,6 +57,8 @@ from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api # For recognizing geometry input. from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex +from django.utils import six + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -281,7 +283,7 @@ class OGRGeometry(GDALBase): # (decremented) when this geometry's destructor is called. if isinstance(srs, SpatialReference): srs_ptr = srs.ptr - elif isinstance(srs, (int, long, basestring)): + elif isinstance(srs, six.integer_types + (basestring,)): sr = SpatialReference(srs) srs_ptr = sr.ptr else: @@ -297,7 +299,7 @@ class OGRGeometry(GDALBase): return None def _set_srid(self, srid): - if isinstance(srid, (int, long)): + if isinstance(srid, six.integer_types): self.srs = srid else: raise TypeError('SRID must be set with an integer.') @@ -410,7 +412,7 @@ class OGRGeometry(GDALBase): capi.geom_transform(self.ptr, coord_trans.ptr) elif isinstance(coord_trans, SpatialReference): capi.geom_transform_to(self.ptr, coord_trans.ptr) - elif isinstance(coord_trans, (int, long, basestring)): + elif isinstance(coord_trans, six.integer_types + (basestring,)): sr = SpatialReference(coord_trans) capi.geom_transform_to(self.ptr, sr.ptr) else: diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index a2163bc3c8..e3aebeeda5 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -14,6 +14,8 @@ from django.contrib.gis.gdal.srs import SpatialReference # GDAL ctypes function prototypes. from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api +from django.utils import six + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -25,8 +27,8 @@ class Layer(GDALBase): def __init__(self, layer_ptr, ds): """ Initializes on an OGR C pointer to the Layer and the `DataSource` object - that owns this layer. The `DataSource` object is required so that a - reference to it is kept with this Layer. This prevents garbage + that owns this layer. The `DataSource` object is required so that a + reference to it is kept with this Layer. This prevents garbage collection of the `DataSource` while this Layer is still active. """ if not layer_ptr: @@ -39,7 +41,7 @@ class Layer(GDALBase): def __getitem__(self, index): "Gets the Feature at the specified index." - if isinstance(index, (int, long)): + if isinstance(index, six.integer_types): # An integer index was given -- we cannot do a check based on the # number of features because the beginning and ending feature IDs # are not guaranteed to be 0 and len(layer)-1, respectively. @@ -85,7 +87,7 @@ class Layer(GDALBase): # each feature until the given feature ID is encountered. for feat in self: if feat.fid == feat_id: return feat - # Should have returned a Feature, raise an OGRIndexError. + # Should have returned a Feature, raise an OGRIndexError. raise OGRIndexError('Invalid feature id: %s.' % feat_id) #### Layer properties #### @@ -131,9 +133,9 @@ class Layer(GDALBase): Returns a list of string names corresponding to each of the Fields available in this Layer. """ - return [capi.get_field_name(capi.get_field_defn(self._ldefn, i)) + return [capi.get_field_name(capi.get_field_defn(self._ldefn, i)) for i in xrange(self.num_fields) ] - + @property def field_types(self): """ @@ -145,13 +147,13 @@ class Layer(GDALBase): return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))] for i in xrange(self.num_fields)] - @property + @property def field_widths(self): "Returns a list of the maximum field widths for the features." return [capi.get_field_width(capi.get_field_defn(self._ldefn, i)) for i in xrange(self.num_fields)] - @property + @property def field_precisions(self): "Returns the field precisions for the features." return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i)) diff --git a/django/contrib/gis/gdal/prototypes/errcheck.py b/django/contrib/gis/gdal/prototypes/errcheck.py index 91858ea572..d8ff1c7dcf 100644 --- a/django/contrib/gis/gdal/prototypes/errcheck.py +++ b/django/contrib/gis/gdal/prototypes/errcheck.py @@ -5,9 +5,10 @@ from ctypes import c_void_p, string_at from django.contrib.gis.gdal.error import check_err, OGRException, SRSException from django.contrib.gis.gdal.libgdal import lgdal +from django.utils import six -# Helper routines for retrieving pointers and/or values from -# arguments passed in by reference. +# Helper routines for retrieving pointers and/or values from +# arguments passed in by reference. def arg_byref(args, offset=-1): "Returns the pointer argument's by-refernece value." return args[offset]._obj.value @@ -53,7 +54,7 @@ def check_string(result, func, cargs, offset=-1, str_result=False): ptr = ptr_byref(cargs, offset) # Getting the string value s = ptr.value - # Correctly freeing the allocated memory beind GDAL pointer + # Correctly freeing the allocated memory beind GDAL pointer # w/the VSIFree routine. if ptr: lgdal.VSIFree(ptr) return s @@ -71,9 +72,9 @@ def check_geom(result, func, cargs): "Checks a function that returns a geometry." # OGR_G_Clone may return an integer, even though the # restype is set to c_void_p - if isinstance(result, (int, long)): + if isinstance(result, six.integer_types): result = c_void_p(result) - if not result: + if not result: raise OGRException('Invalid geometry pointer returned from "%s".' % func.__name__) return result @@ -85,7 +86,7 @@ def check_geom_offset(result, func, cargs, offset=-1): ### Spatial Reference error-checking routines ### def check_srs(result, func, cargs): - if isinstance(result, (int, long)): + if isinstance(result, six.integer_types): result = c_void_p(result) if not result: raise SRSException('Invalid spatial reference pointer returned from "%s".' % func.__name__) @@ -109,11 +110,11 @@ def check_errcode(result, func, cargs): def check_pointer(result, func, cargs): "Makes sure the result pointer is valid." - if isinstance(result, (int, long)): + if isinstance(result, six.integer_types): result = c_void_p(result) - if bool(result): + if bool(result): return result - else: + else: raise OGRException('Invalid pointer returned from "%s"' % func.__name__) def check_str_arg(result, func, cargs): diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index 67049731de..ad07ed27b8 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -33,11 +33,13 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import SRSException from django.contrib.gis.gdal.prototypes import srs as capi +from django.utils import six + #### Spatial Reference class. #### class SpatialReference(GDALBase): """ A wrapper for the OGRSpatialReference object. According to the GDAL Web site, - the SpatialReference object "provide[s] services to represent coordinate + the SpatialReference object "provide[s] services to represent coordinate systems (projections and datums) and to transform between them." """ @@ -45,8 +47,8 @@ class SpatialReference(GDALBase): def __init__(self, srs_input=''): """ Creates a GDAL OSR Spatial Reference object from the given input. - The input may be string of OGC Well Known Text (WKT), an integer - EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand + The input may be string of OGC Well Known Text (WKT), an integer + EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand string (one of 'WGS84', 'WGS72', 'NAD27', 'NAD83'). """ buf = c_char_p('') @@ -63,7 +65,7 @@ class SpatialReference(GDALBase): srs_input = 'EPSG:%d' % srid except ValueError: pass - elif isinstance(srs_input, (int, long)): + elif isinstance(srs_input, six.integer_types): # EPSG integer code was input. srs_type = 'epsg' elif isinstance(srs_input, self.ptr_type): @@ -97,8 +99,8 @@ class SpatialReference(GDALBase): def __getitem__(self, target): """ - Returns the value of the given string attribute node, None if the node - doesn't exist. Can also take a tuple as a parameter, (target, child), + Returns the value of the given string attribute node, None if the node + doesn't exist. Can also take a tuple as a parameter, (target, child), where child is the index of the attribute in the WKT. For example: >>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]') @@ -140,7 +142,7 @@ class SpatialReference(GDALBase): def auth_name(self, target): "Returns the authority name for the given string target node." return capi.get_auth_name(self.ptr, target) - + def auth_code(self, target): "Returns the authority code for the given string target node." return capi.get_auth_code(self.ptr, target) @@ -167,7 +169,7 @@ class SpatialReference(GDALBase): def validate(self): "Checks to see if the given spatial reference is valid." capi.srs_validate(self.ptr) - + #### Name & SRID properties #### @property def name(self): @@ -184,7 +186,7 @@ class SpatialReference(GDALBase): return int(self.attr_value('AUTHORITY', 1)) except (TypeError, ValueError): return None - + #### Unit Properties #### @property def linear_name(self): @@ -213,7 +215,7 @@ class SpatialReference(GDALBase): @property def units(self): """ - Returns a 2-tuple of the units value and the units name, + Returns a 2-tuple of the units value and the units name, and will automatically determines whether to return the linear or angular units. """ @@ -252,7 +254,7 @@ class SpatialReference(GDALBase): @property def geographic(self): """ - Returns True if this SpatialReference is geographic + Returns True if this SpatialReference is geographic (root node is GEOGCS). """ return bool(capi.isgeographic(self.ptr)) @@ -265,7 +267,7 @@ class SpatialReference(GDALBase): @property def projected(self): """ - Returns True if this SpatialReference is a projected coordinate system + Returns True if this SpatialReference is a projected coordinate system (root node is PROJCS). """ return bool(capi.isprojected(self.ptr)) diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py index 1a9dcf0b5b..ea5571ad4c 100644 --- a/django/contrib/gis/geos/mutable_list.py +++ b/django/contrib/gis/geos/mutable_list.py @@ -9,6 +9,7 @@ See also http://www.aryehleib.com/MutableLists.html Author: Aryeh Leib Taurog. """ from django.utils.functional import total_ordering +from django.utils import six @total_ordering class ListMixin(object): @@ -82,12 +83,12 @@ class ListMixin(object): def __delitem__(self, index): "Delete the item(s) at the specified index/slice." - if not isinstance(index, (int, long, slice)): + if not isinstance(index, six.integer_types + (slice,)): raise TypeError("%s is not a legal index" % index) # calculate new length and dimensions origLen = len(self) - if isinstance(index, (int, long)): + if isinstance(index, six.integer_types): index = self._checkindex(index) indexRange = [index] else: @@ -195,7 +196,7 @@ class ListMixin(object): def insert(self, index, val): "Standard list insert method" - if not isinstance(index, (int, long)): + if not isinstance(index, six.integer_types): raise TypeError("%s is not a legal index" % index) self[index:index] = [val] diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py index b126856ba3..6ba5800d4a 100644 --- a/django/contrib/gis/geos/point.py +++ b/django/contrib/gis/geos/point.py @@ -2,6 +2,7 @@ from ctypes import c_uint from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos import prototypes as capi +from django.utils import six class Point(GEOSGeometry): _minlength = 2 @@ -20,9 +21,9 @@ class Point(GEOSGeometry): # Here a tuple or list was passed in under the `x` parameter. ndim = len(x) coords = x - elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)): + elif isinstance(x, six.integer_types + (float,)) and isinstance(y, six.integer_types + (float,)): # Here X, Y, and (optionally) Z were passed in individually, as parameters. - if isinstance(z, (int, float, long)): + if isinstance(z, six.integer_types + (float,)): ndim = 3 coords = [x, y, z] else: diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py index 2c0f90be3c..bb02689c81 100644 --- a/django/contrib/gis/geos/polygon.py +++ b/django/contrib/gis/geos/polygon.py @@ -3,6 +3,7 @@ from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR from django.contrib.gis.geos.linestring import LinearRing from django.contrib.gis.geos import prototypes as capi +from django.utils import six class Polygon(GEOSGeometry): _minlength = 1 @@ -56,7 +57,7 @@ class Polygon(GEOSGeometry): "Constructs a Polygon from a bounding box (4-tuple)." x0, y0, x1, y1 = bbox for z in bbox: - if not isinstance(z, (int, long, float)): + if not isinstance(z, six.integer_types + (float,)): return GEOSGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)) return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0))) diff --git a/django/contrib/gis/geos/tests/test_mutable_list.py b/django/contrib/gis/geos/tests/test_mutable_list.py index 3e63a25e95..cd174d7cfa 100644 --- a/django/contrib/gis/geos/tests/test_mutable_list.py +++ b/django/contrib/gis/geos/tests/test_mutable_list.py @@ -4,6 +4,7 @@ # Modified from original contribution by Aryeh Leib Taurog, which was # released under the New BSD license. from django.contrib.gis.geos.mutable_list import ListMixin +from django.utils import six from django.utils import unittest @@ -267,7 +268,7 @@ class ListMixinTest(unittest.TestCase): def test07_allowed_types(self): 'Type-restricted list' pl, ul = self.lists_of_len() - ul._allowed = (int, long) + ul._allowed = six.integer_types ul[1] = 50 ul[:2] = [60, 70, 80] def setfcn(x, i, v): x[i] = v diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index 9efea504c7..24e8075cab 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -39,8 +39,9 @@ __all__ = ['A', 'Area', 'D', 'Distance'] from decimal import Decimal from django.utils.functional import total_ordering +from django.utils import six -NUMERIC_TYPES = (int, float, long, Decimal) +NUMERIC_TYPES = six.integer_types + (float, Decimal) AREA_PREFIX = "sq_" def pretty_name(obj): diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 0f06131bc4..fcc6ab7584 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -26,7 +26,7 @@ class BaseDatabaseCreation(object): Generates a 32-bit digest of a set of arguments that can be used to shorten identifying names. """ - return '%x' % (abs(hash(args)) % 4294967296L) # 2**32 + return '%x' % (abs(hash(args)) % 4294967296) # 2**32 def sql_create_model(self, model, style, known_models=set()): """ diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 9f5b9f5b77..a7668dec49 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -243,7 +243,7 @@ class DatabaseOperations(BaseDatabaseOperations): def no_limit_value(self): # 2**64 - 1, as recommended by the MySQL documentation - return 18446744073709551615L + return 18446744073709551615 def quote_name(self, name): if name.startswith("`") and name.endswith("`"): diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 88a243a46d..e093f7e84e 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -209,7 +209,7 @@ WHEN (new.%(col_name)s IS NULL) return "DROP SEQUENCE %s;" % self.quote_name(self._get_sequence_name(table)) def fetch_returned_insert_id(self, cursor): - return long(cursor._insert_id_var.getvalue()) + return int(cursor._insert_id_var.getvalue()) def field_cast_sql(self, db_type): if db_type and db_type.endswith('LOB'): diff --git a/django/db/models/query.py b/django/db/models/query.py index f9e757d9f1..8b6b42b7b1 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -169,7 +169,7 @@ class QuerySet(object): """ Retrieves an item or slice from the set of results. """ - if not isinstance(k, (slice, int, long)): + if not isinstance(k, (slice,) + six.integer_types): raise TypeError assert ((not isinstance(k, slice) and (k >= 0)) or (isinstance(k, slice) and (k.start is None or k.start >= 0) diff --git a/django/utils/crypto.py b/django/utils/crypto.py index cc59e2e39f..8c9649eef1 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -98,7 +98,7 @@ def _bin_to_long(x): This is a clever optimization for fast xor vector math """ - return long(x.encode('hex'), 16) + return int(x.encode('hex'), 16) def _long_to_bin(x, hex_format_string): diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 80e456ba2a..30665480f6 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -7,6 +7,7 @@ import codecs from decimal import Decimal from django.utils.functional import Promise +from django.utils import six class DjangoUnicodeDecodeError(UnicodeDecodeError): def __init__(self, obj, *args): @@ -45,12 +46,8 @@ def is_protected_type(obj): Objects of protected types are preserved as-is when passed to force_unicode(strings_only=True). """ - return isinstance(obj, ( - type(None), - int, long, - datetime.datetime, datetime.date, datetime.time, - float, Decimal) - ) + return isinstance(obj, six.integer_types + (type(None), float, Decimal, + datetime.datetime, datetime.date, datetime.time)) def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): """ diff --git a/django/utils/formats.py b/django/utils/formats.py index e283490b17..d3afc72729 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -7,6 +7,7 @@ from django.utils.importlib import import_module from django.utils.encoding import smart_str from django.utils.functional import lazy from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import get_language, to_locale, check_for_language # format_cache is a mapping from (format_type, lang) to the format string. @@ -139,7 +140,7 @@ def localize(value, use_l10n=None): """ if isinstance(value, bool): return mark_safe(unicode(value)) - elif isinstance(value, (decimal.Decimal, float, int, long)): + elif isinstance(value, (decimal.Decimal, float) + six.integer_types): return number_format(value, use_l10n=use_l10n) elif isinstance(value, datetime.datetime): return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n) @@ -155,7 +156,7 @@ def localize_input(value, default=None): Checks if an input value is a localizable type and returns it formatted with the appropriate formatting string of the current locale. """ - if isinstance(value, (decimal.Decimal, float, int, long)): + if isinstance(value, (decimal.Decimal, float) + six.integer_types): return number_format(value) elif isinstance(value, datetime.datetime): value = datetime_safe.new_datetime(value) diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py index 3f00fb25fe..d96c60bbe8 100644 --- a/tests/modeltests/basic/tests.py +++ b/tests/modeltests/basic/tests.py @@ -5,6 +5,7 @@ from datetime import datetime from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields import Field, FieldDoesNotExist from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature +from django.utils.six import PY3 from django.utils.translation import ugettext_lazy from .models import Article @@ -321,17 +322,18 @@ class ModelTest(TestCase): ["", ""]) - # Slicing works with longs. - self.assertEqual(Article.objects.all()[0L], a) - self.assertQuerysetEqual(Article.objects.all()[1L:3L], - ["", ""]) - self.assertQuerysetEqual((s1 | s2 | s3)[::2L], - ["", - ""]) + # Slicing works with longs (Python 2 only -- Python 3 doesn't have longs). + if not PY3: + self.assertEqual(Article.objects.all()[long(0)], a) + self.assertQuerysetEqual(Article.objects.all()[long(1):long(3)], + ["", ""]) + self.assertQuerysetEqual((s1 | s2 | s3)[::long(2)], + ["", + ""]) - # And can be mixed with ints. - self.assertQuerysetEqual(Article.objects.all()[1:3L], - ["", ""]) + # And can be mixed with ints. + self.assertQuerysetEqual(Article.objects.all()[1:long(3)], + ["", ""]) # Slices (without step) are lazy: self.assertQuerysetEqual(Article.objects.all()[0:5].filter(), diff --git a/tests/modeltests/field_defaults/tests.py b/tests/modeltests/field_defaults/tests.py index 206d380e1e..5d9b45610e 100644 --- a/tests/modeltests/field_defaults/tests.py +++ b/tests/modeltests/field_defaults/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from datetime import datetime from django.test import TestCase +from django.utils import six from .models import Article @@ -13,6 +14,6 @@ class DefaultTests(TestCase): now = datetime.now() a.save() - self.assertTrue(isinstance(a.id, (int, long))) + self.assertTrue(isinstance(a.id, six.integer_types)) self.assertEqual(a.headline, "Default headline") self.assertTrue((now - a.pub_date).seconds < 5) diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index f91d7c042b..69260edb0a 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -19,6 +19,7 @@ from django.utils.formats import (get_format, date_format, time_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.six import PY3 from django.utils.translation import (ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, pgettext, npgettext, to_locale, get_language_info, get_language, get_language_from_request) @@ -309,7 +310,7 @@ class FormattingTests(TestCase): self.d = datetime.date(2009, 12, 31) self.dt = datetime.datetime(2009, 12, 31, 20, 50) self.t = datetime.time(10, 15, 48) - self.l = 10000L + self.l = 10000 if PY3 else long(10000) self.ctxt = Context({ 'n': self.n, 't': self.t, diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index b94c4696f9..e86159463d 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -8,6 +8,7 @@ from django import forms from django.core.exceptions import ValidationError from django.db import models from django.db.models.fields.files import FieldFile +from django.utils import six from django.utils import unittest from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, @@ -303,11 +304,11 @@ class BigIntegerFieldTests(test.TestCase): def test_types(self): b = BigInt(value = 0) - self.assertTrue(isinstance(b.value, (int, long))) + self.assertTrue(isinstance(b.value, six.integer_types)) b.save() - self.assertTrue(isinstance(b.value, (int, long))) + self.assertTrue(isinstance(b.value, six.integer_types)) b = BigInt.objects.all()[0] - self.assertTrue(isinstance(b.value, (int, long))) + self.assertTrue(isinstance(b.value, six.integer_types)) def test_coercing(self): BigInt.objects.create(value ='10') From cacd845996d1245f6aed257bff5f748f46206d3f Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 13:52:16 +0200 Subject: [PATCH 230/519] [py3] Fixed remaining Python 3 syntax errors. django.utils.unittest.* weren't touched -- they're only imported on Python 2.6. --- django/core/management/__init__.py | 2 +- django/test/_doctest.py | 6 ++++-- tests/modeltests/select_for_update/tests.py | 2 +- tests/regressiontests/forms/tests/extra.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 0464eb27bb..68048e5672 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -324,7 +324,7 @@ class ManagementUtility(object): subcommand_cls.option_list] # filter out previously specified options from available options prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]] - options = filter(lambda (x, v): x not in prev_opts, options) + options = [opt for opt in options if opt[0] not in prev_opts] # filter options by current input options = sorted([(k, v) for k, v in options if k.startswith(curr)]) diff --git a/django/test/_doctest.py b/django/test/_doctest.py index b5d5da970b..4456511532 100644 --- a/django/test/_doctest.py +++ b/django/test/_doctest.py @@ -105,6 +105,8 @@ import unittest, difflib, pdb, tempfile import warnings from StringIO import StringIO +from django.utils import six + if sys.platform.startswith('java'): # On Jython, isclass() reports some modules as classes. Patch it. def patch_isclass(isclass): @@ -1232,8 +1234,8 @@ class DocTestRunner: # keyboard interrupts.) try: # Don't blink! This is where the user's code gets run. - exec compile(example.source, filename, "single", - compileflags, 1) in test.globs + six.exec_(compile(example.source, filename, "single", + compileflags, 1), test.globs) self.debugger.set_continue() # ==== Example Finished ==== exception = None except KeyboardInterrupt: diff --git a/tests/modeltests/select_for_update/tests.py b/tests/modeltests/select_for_update/tests.py index cb8f19f1e8..e3e4d9e7e2 100644 --- a/tests/modeltests/select_for_update/tests.py +++ b/tests/modeltests/select_for_update/tests.py @@ -206,7 +206,7 @@ class SelectForUpdateTests(TransactionTestCase): sanity_count += 1 time.sleep(1) if sanity_count >= 10: - raise ValueError, 'Thread did not run and block' + raise ValueError('Thread did not run and block') # Check the person hasn't been updated. Since this isn't # using FOR UPDATE, it won't block. diff --git a/tests/regressiontests/forms/tests/extra.py b/tests/regressiontests/forms/tests/extra.py index 25b21123c4..28b6c12453 100644 --- a/tests/regressiontests/forms/tests/extra.py +++ b/tests/regressiontests/forms/tests/extra.py @@ -554,7 +554,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): def test_smart_unicode(self): class Test: def __str__(self): - return b'ŠĐĆŽćžšđ' + return 'ŠĐĆŽćžšđ'.encode('utf-8') class TestU: def __str__(self): From 3cb2457f46b3e40ff6b6acffcb3fd44cbea091e5 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 14:22:00 +0200 Subject: [PATCH 231/519] [py3] Replaced basestring by six.string_types. --- django/conf/__init__.py | 5 +++-- django/conf/urls/__init__.py | 5 +++-- django/contrib/admin/helpers.py | 3 ++- django/contrib/admin/util.py | 2 +- django/contrib/formtools/utils.py | 3 ++- django/contrib/gis/admin/widgets.py | 5 +++-- .../gis/db/backends/oracle/operations.py | 2 +- .../gis/db/backends/postgis/operations.py | 2 +- .../db/backends/spatialite/introspection.py | 3 ++- .../gis/db/backends/spatialite/operations.py | 2 +- django/contrib/gis/db/backends/util.py | 4 +++- django/contrib/gis/db/models/fields.py | 3 ++- django/contrib/gis/db/models/proxy.py | 3 ++- django/contrib/gis/db/models/query.py | 4 +++- django/contrib/gis/gdal/datasource.py | 10 +++++---- django/contrib/gis/gdal/driver.py | 10 +++++---- django/contrib/gis/gdal/feature.py | 16 +++++++------- django/contrib/gis/gdal/geometries.py | 8 +++---- django/contrib/gis/gdal/geomtype.py | 8 ++++--- django/contrib/gis/gdal/srs.py | 4 ++-- django/contrib/gis/geoip/base.py | 6 ++++-- django/contrib/gis/geoip/tests.py | 4 +++- django/contrib/gis/geos/factory.py | 4 +++- django/contrib/gis/geos/geometry.py | 8 ++++--- django/contrib/gis/geos/prototypes/io.py | 8 ++++--- django/contrib/gis/geos/tests/test_geos.py | 5 +++-- django/contrib/gis/geos/tests/test_io.py | 9 ++++---- django/contrib/gis/maps/google/overlays.py | 11 +++++----- django/contrib/gis/measure.py | 2 +- django/contrib/gis/utils/layermapping.py | 9 ++++---- django/contrib/gis/utils/ogrinspect.py | 3 ++- django/contrib/gis/utils/wkt.py | 8 ++++--- django/core/cache/backends/memcached.py | 4 +++- django/core/mail/backends/filebased.py | 3 ++- django/core/mail/message.py | 9 ++++---- django/core/management/validation.py | 7 ++++--- django/core/serializers/base.py | 3 ++- django/core/serializers/json.py | 3 ++- django/core/serializers/pyyaml.py | 3 ++- django/core/urlresolvers.py | 7 ++++--- django/core/validators.py | 3 ++- django/db/backends/oracle/base.py | 7 ++++--- django/db/models/fields/__init__.py | 7 ++++--- django/db/models/fields/files.py | 3 ++- django/db/models/fields/related.py | 21 ++++++++++--------- django/db/models/options.py | 5 +++-- django/db/utils.py | 3 ++- django/forms/extras/widgets.py | 3 ++- django/forms/fields.py | 5 +++-- django/forms/widgets.py | 3 ++- django/template/base.py | 3 ++- django/template/loader.py | 3 ++- django/template/response.py | 3 ++- django/templatetags/i18n.py | 3 ++- django/templatetags/tz.py | 3 ++- django/test/_doctest.py | 10 ++++----- django/test/client.py | 2 +- django/test/html.py | 19 +++++++++-------- django/test/utils.py | 3 ++- django/utils/archive.py | 4 +++- django/utils/checksums.py | 4 +++- django/utils/dictconfig.py | 4 +++- django/utils/encoding.py | 4 ++-- django/utils/formats.py | 2 +- django/utils/regex_helper.py | 4 +++- django/utils/timezone.py | 5 +++-- django/views/debug.py | 3 ++- django/views/i18n.py | 7 ++++--- tests/modeltests/field_subclassing/fields.py | 3 ++- tests/modeltests/serializers/tests.py | 3 ++- tests/regressiontests/forms/tests/fields.py | 3 ++- tests/regressiontests/i18n/commands/tests.py | 4 +++- .../staticfiles_tests/tests.py | 3 ++- 73 files changed, 230 insertions(+), 150 deletions(-) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 5e4b412f32..77454b3fb9 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -15,6 +15,7 @@ from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured from django.utils.functional import LazyObject, empty from django.utils import importlib +from django.utils import six ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" @@ -73,7 +74,7 @@ class BaseSettings(object): elif name == "ADMIN_MEDIA_PREFIX": warnings.warn("The ADMIN_MEDIA_PREFIX setting has been removed; " "use STATIC_URL instead.", DeprecationWarning) - elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, basestring): + elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types): raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set " "to a tuple, not a string.") object.__setattr__(self, name, value) @@ -102,7 +103,7 @@ class Settings(BaseSettings): if setting == setting.upper(): setting_value = getattr(mod, setting) if setting in tuple_settings and \ - isinstance(setting_value, basestring): + isinstance(setting_value, six.string_types): warnings.warn("The %s setting must be a tuple. Please fix your " "settings, as auto-correction is now deprecated." % setting, PendingDeprecationWarning) diff --git a/django/conf/urls/__init__.py b/django/conf/urls/__init__.py index 0b6ab6496e..04fb1dff59 100644 --- a/django/conf/urls/__init__.py +++ b/django/conf/urls/__init__.py @@ -2,6 +2,7 @@ from django.core.urlresolvers import (RegexURLPattern, RegexURLResolver, LocaleRegexURLResolver) from django.core.exceptions import ImproperlyConfigured from django.utils.importlib import import_module +from django.utils import six __all__ = ['handler403', 'handler404', 'handler500', 'include', 'patterns', 'url'] @@ -20,7 +21,7 @@ def include(arg, namespace=None, app_name=None): # No namespace hint - use manually provided namespace urlconf_module = arg - if isinstance(urlconf_module, basestring): + if isinstance(urlconf_module, six.string_types): urlconf_module = import_module(urlconf_module) patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module) @@ -52,7 +53,7 @@ def url(regex, view, kwargs=None, name=None, prefix=''): urlconf_module, app_name, namespace = view return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace) else: - if isinstance(view, basestring): + if isinstance(view, six.string_types): if not view: raise ImproperlyConfigured('Empty URL pattern view name not permitted (for pattern %r)' % regex) if prefix: diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index ac29d19469..11b14dcb51 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -12,6 +12,7 @@ from django.template.defaultfilters import capfirst from django.utils.encoding import force_unicode, smart_unicode from django.utils.html import conditional_escape, format_html from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -49,7 +50,7 @@ class AdminForm(object): try: fieldset_name, fieldset_options = self.fieldsets[0] field_name = fieldset_options['fields'][0] - if not isinstance(field_name, basestring): + if not isinstance(field_name, six.string_types): field_name = field_name[0] return self.form[field_name] except (KeyError, IndexError): diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 1d43585fcd..a529bacd18 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -52,7 +52,7 @@ def quote(s): quoting is slightly different so that it doesn't get automatically unquoted by the Web browser. """ - if not isinstance(s, basestring): + if not isinstance(s, six.string_types): return s res = list(s) for i in range(len(res)): diff --git a/django/contrib/formtools/utils.py b/django/contrib/formtools/utils.py index 96a0092a35..8763cded07 100644 --- a/django/contrib/formtools/utils.py +++ b/django/contrib/formtools/utils.py @@ -2,6 +2,7 @@ import pickle from django.utils.crypto import salted_hmac +from django.utils import six def form_hmac(form): @@ -16,7 +17,7 @@ def form_hmac(form): value = bf.data or '' else: value = bf.field.clean(bf.data) or '' - if isinstance(value, basestring): + if isinstance(value, six.string_types): value = value.strip() data.append((bf.name, value)) diff --git a/django/contrib/gis/admin/widgets.py b/django/contrib/gis/admin/widgets.py index c7b48e4263..47570d3f9d 100644 --- a/django/contrib/gis/admin/widgets.py +++ b/django/contrib/gis/admin/widgets.py @@ -1,6 +1,7 @@ from django.forms.widgets import Textarea from django.template import loader, Context from django.templatetags.static import static +from django.utils import six from django.utils import translation from django.contrib.gis.gdal import OGRException @@ -25,7 +26,7 @@ class OpenLayersWidget(Textarea): # If a string reaches here (via a validation error on another # field) then just reconstruct the Geometry. - if isinstance(value, basestring): + if isinstance(value, six.string_types): try: value = GEOSGeometry(value) except (GEOSException, ValueError): @@ -109,7 +110,7 @@ class OpenLayersWidget(Textarea): """ Compare geographic value of data with its initial value. """ # Ensure we are dealing with a geographic object - if isinstance(initial, basestring): + if isinstance(initial, six.string_types): try: initial = GEOSGeometry(initial) except (GEOSException, ValueError): diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 3a189ea1fe..4e33942f7a 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -121,7 +121,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations): 'exact' : SDOOperation('SDO_EQUAL'), 'overlaps' : SDOOperation('SDO_OVERLAPS'), 'same_as' : SDOOperation('SDO_EQUAL'), - 'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch' + 'relate' : (SDORelate, six.string_types), # Oracle uses a different syntax, e.g., 'mask=inside+touch' 'touches' : SDOOperation('SDO_TOUCH'), 'within' : SDOOperation('SDO_INSIDE'), } diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 8f190882e1..a6340ce22b 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -162,7 +162,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 'overlaps' : PostGISFunction(prefix, 'Overlaps'), 'contains' : PostGISFunction(prefix, 'Contains'), 'intersects' : PostGISFunction(prefix, 'Intersects'), - 'relate' : (PostGISRelate, basestring), + 'relate' : (PostGISRelate, six.string_types), } # Valid distance types and substitutions diff --git a/django/contrib/gis/db/backends/spatialite/introspection.py b/django/contrib/gis/db/backends/spatialite/introspection.py index 1b5952ceac..4f12ade114 100644 --- a/django/contrib/gis/db/backends/spatialite/introspection.py +++ b/django/contrib/gis/db/backends/spatialite/introspection.py @@ -1,5 +1,6 @@ from django.contrib.gis.gdal import OGRGeomType from django.db.backends.sqlite3.introspection import DatabaseIntrospection, FlexibleFieldLookupDict +from django.utils import six class GeoFlexibleFieldLookupDict(FlexibleFieldLookupDict): """ @@ -43,7 +44,7 @@ class SpatiaLiteIntrospection(DatabaseIntrospection): field_params = {} if srid != 4326: field_params['srid'] = srid - if isinstance(dim, basestring) and 'Z' in dim: + if isinstance(dim, six.string_types) and 'Z' in dim: field_params['dim'] = 3 finally: cursor.close() diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index ffd7d33dad..1d7c4fab52 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -90,7 +90,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): 'overlaps' : SpatiaLiteFunction('Overlaps'), 'contains' : SpatiaLiteFunction('Contains'), 'intersects' : SpatiaLiteFunction('Intersects'), - 'relate' : (SpatiaLiteRelate, basestring), + 'relate' : (SpatiaLiteRelate, six.string_types), # Returns true if B's bounding box completely contains A's bounding box. 'contained' : SpatiaLiteFunction('MbrWithin'), # Returns true if A's bounding box completely contains B's bounding box. diff --git a/django/contrib/gis/db/backends/util.py b/django/contrib/gis/db/backends/util.py index b50c8e222e..b899934a01 100644 --- a/django/contrib/gis/db/backends/util.py +++ b/django/contrib/gis/db/backends/util.py @@ -3,13 +3,15 @@ A collection of utility routines and classes used by the spatial backends. """ +from django.utils import six + def gqn(val): """ The geographic quote name function; used for quoting tables and geometries (they use single rather than the double quotes of the backend quotename function). """ - if isinstance(val, basestring): + if isinstance(val, six.string_types): if isinstance(val, unicode): val = val.encode('ascii') return "'%s'" % val else: diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 2b1660763a..17630d0899 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -4,6 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.gis import forms from django.contrib.gis.db.models.proxy import GeometryProxy from django.contrib.gis.geometry.backend import Geometry, GeometryException +from django.utils import six # Local cache of the spatial_ref_sys table, which holds SRID data for each # spatial database alias. This cache exists so that the database isn't queried @@ -159,7 +160,7 @@ class GeometryField(Field): # from the given string input. if isinstance(geom, Geometry): pass - elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'): + elif isinstance(geom, six.string_types) or hasattr(geom, '__geo_interface__'): try: geom = Geometry(geom) except GeometryException: diff --git a/django/contrib/gis/db/models/proxy.py b/django/contrib/gis/db/models/proxy.py index e569dd5c4f..413610fc5c 100644 --- a/django/contrib/gis/db/models/proxy.py +++ b/django/contrib/gis/db/models/proxy.py @@ -5,6 +5,7 @@ corresponding to geographic model fields. Thanks to Robert Coup for providing this functionality (see #4322). """ +from django.utils import six class GeometryProxy(object): def __init__(self, klass, field): @@ -53,7 +54,7 @@ class GeometryProxy(object): if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'): # Assigning the SRID to the geometry. if value.srid is None: value.srid = self._field.srid - elif value is None or isinstance(value, (basestring, buffer)): + elif value is None or isinstance(value, six.string_types + (buffer,)): # Set with None, WKT, HEX, or WKB pass else: diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index a11b1213f4..dd2983aecc 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -8,6 +8,8 @@ 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." @@ -534,7 +536,7 @@ class GeoQuerySet(QuerySet): geo_field = settings['geo_field'] # The attribute to attach to the model. - if not isinstance(model_att, basestring): model_att = att + if not isinstance(model_att, six.string_types): model_att = att # Special handling for any argument that is a geometry. for name in settings['geom_args']: diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index e5f3602ddb..1797ed3320 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -45,6 +45,8 @@ from django.contrib.gis.gdal.layer import Layer # Getting the ctypes prototypes for the DataSource. from django.contrib.gis.gdal.prototypes import ds as capi +from django.utils import six + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -65,7 +67,7 @@ class DataSource(GDALBase): if not capi.get_driver_count(): capi.register_all() - if isinstance(ds_input, basestring): + if isinstance(ds_input, six.string_types): # The data source driver is a void pointer. ds_driver = Driver.ptr_type() try: @@ -84,7 +86,7 @@ class DataSource(GDALBase): self.ptr = ds self.driver = Driver(ds_driver) else: - # Raise an exception if the returned pointer is NULL + # Raise an exception if the returned pointer is NULL raise OGRException('Invalid data source file "%s"' % ds_input) def __del__(self): @@ -98,7 +100,7 @@ class DataSource(GDALBase): def __getitem__(self, index): "Allows use of the index [] operator to get a layer at the index." - if isinstance(index, basestring): + if isinstance(index, six.string_types): l = capi.get_layer_by_name(self.ptr, index) if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index) elif isinstance(index, int): @@ -108,7 +110,7 @@ class DataSource(GDALBase): else: raise TypeError('Invalid index type: %s' % type(index)) return Layer(l, self) - + def __len__(self): "Returns the number of layers within the data source." return self.layer_count diff --git a/django/contrib/gis/gdal/driver.py b/django/contrib/gis/gdal/driver.py index 1753db2b2b..de4dc61c63 100644 --- a/django/contrib/gis/gdal/driver.py +++ b/django/contrib/gis/gdal/driver.py @@ -1,9 +1,11 @@ -# prerequisites imports +# prerequisites imports from ctypes import c_void_p from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import OGRException from django.contrib.gis.gdal.prototypes import ds as capi +from django.utils import six + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -18,11 +20,11 @@ class Driver(GDALBase): 'tiger' : 'TIGER', 'tiger/line' : 'TIGER', } - + def __init__(self, dr_input): "Initializes an OGR driver on either a string or integer input." - if isinstance(dr_input, basestring): + if isinstance(dr_input, six.string_types): # If a string name of the driver was passed in self._register() @@ -57,7 +59,7 @@ class Driver(GDALBase): # Only register all if the driver count is 0 (or else all drivers # will be registered over and over again) if not self.driver_count: capi.register_all() - + # Driver properties @property def driver_count(self): diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index 47fd9e522e..52eadfa06f 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -7,6 +7,8 @@ from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType # ctypes function prototypes from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api +from django.utils import six + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -30,17 +32,17 @@ class Feature(GDALBase): """ Gets the Field object at the specified index, which may be either an integer or the Field's string label. Note that the Field object - is not the field's _value_ -- use the `get` method instead to + is not the field's _value_ -- use the `get` method instead to retrieve the value (e.g. an integer) instead of a Field instance. """ - if isinstance(index, basestring): + if isinstance(index, six.string_types): i = self.index(index) else: if index < 0 or index > self.num_fields: raise OGRIndexError('index out of range') i = index return Field(self.ptr, i) - + def __iter__(self): "Iterates over each field in the Feature." for i in xrange(self.num_fields): @@ -49,7 +51,7 @@ class Feature(GDALBase): def __len__(self): "Returns the count of fields in this feature." return self.num_fields - + def __str__(self): "The string name of the feature." return 'Feature FID %d in Layer<%s>' % (self.fid, self.layer_name) @@ -63,7 +65,7 @@ class Feature(GDALBase): def fid(self): "Returns the feature identifier." return capi.get_fid(self.ptr) - + @property def layer_name(self): "Returns the name of the layer for the feature." @@ -77,7 +79,7 @@ class Feature(GDALBase): @property def fields(self): "Returns a list of fields in the Feature." - return [capi.get_field_name(capi.get_field_defn(self._fdefn, i)) + return [capi.get_field_name(capi.get_field_defn(self._fdefn, i)) for i in xrange(self.num_fields)] @property @@ -91,7 +93,7 @@ class Feature(GDALBase): def geom_type(self): "Returns the OGR Geometry Type for this Feture." return OGRGeomType(capi.get_fd_geom_type(self._fdefn)) - + #### Feature Methods #### def get(self, field): """ diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 617c4acef4..f38aeba838 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -71,7 +71,7 @@ class OGRGeometry(GDALBase): def __init__(self, geom_input, srs=None): "Initializes Geometry on either WKT or an OGR pointer as input." - str_instance = isinstance(geom_input, basestring) + str_instance = isinstance(geom_input, six.string_types) # If HEX, unpack input to to a binary buffer. if str_instance and hex_regex.match(geom_input): @@ -283,7 +283,7 @@ class OGRGeometry(GDALBase): # (decremented) when this geometry's destructor is called. if isinstance(srs, SpatialReference): srs_ptr = srs.ptr - elif isinstance(srs, six.integer_types + (basestring,)): + elif isinstance(srs, six.integer_types + six.string_types): sr = SpatialReference(srs) srs_ptr = sr.ptr else: @@ -412,7 +412,7 @@ class OGRGeometry(GDALBase): capi.geom_transform(self.ptr, coord_trans.ptr) elif isinstance(coord_trans, SpatialReference): capi.geom_transform_to(self.ptr, coord_trans.ptr) - elif isinstance(coord_trans, six.integer_types + (basestring,)): + elif isinstance(coord_trans, six.integer_types + six.string_types): sr = SpatialReference(coord_trans) capi.geom_transform_to(self.ptr, sr.ptr) else: @@ -687,7 +687,7 @@ class GeometryCollection(OGRGeometry): for g in geom: capi.add_geom(self.ptr, g.ptr) else: capi.add_geom(self.ptr, geom.ptr) - elif isinstance(geom, basestring): + elif isinstance(geom, six.string_types): tmp = OGRGeometry(geom) capi.add_geom(self.ptr, tmp.ptr) else: diff --git a/django/contrib/gis/gdal/geomtype.py b/django/contrib/gis/gdal/geomtype.py index 3bf94d4815..fe4b89adeb 100644 --- a/django/contrib/gis/gdal/geomtype.py +++ b/django/contrib/gis/gdal/geomtype.py @@ -1,5 +1,7 @@ from django.contrib.gis.gdal.error import OGRException +from django.utils import six + #### OGRGeomType #### class OGRGeomType(object): "Encapulates OGR Geometry Types." @@ -32,7 +34,7 @@ class OGRGeomType(object): "Figures out the correct OGR Type based upon the input." if isinstance(type_input, OGRGeomType): num = type_input.num - elif isinstance(type_input, basestring): + elif isinstance(type_input, six.string_types): type_input = type_input.lower() if type_input == 'geometry': type_input='unknown' num = self._str_types.get(type_input, None) @@ -44,7 +46,7 @@ class OGRGeomType(object): num = type_input else: raise TypeError('Invalid OGR input type given.') - + # Setting the OGR geometry type number. self.num = num @@ -59,7 +61,7 @@ class OGRGeomType(object): """ if isinstance(other, OGRGeomType): return self.num == other.num - elif isinstance(other, basestring): + elif isinstance(other, six.string_types): return self.name.lower() == other.lower() elif isinstance(other, int): return self.num == other diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index ad07ed27b8..6003ce75ad 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -54,7 +54,7 @@ class SpatialReference(GDALBase): buf = c_char_p('') srs_type = 'user' - if isinstance(srs_input, basestring): + if isinstance(srs_input, six.string_types): # Encoding to ASCII if unicode passed in. if isinstance(srs_input, unicode): srs_input = srs_input.encode('ascii') @@ -135,7 +135,7 @@ class SpatialReference(GDALBase): The attribute value for the given target node (e.g. 'PROJCS'). The index keyword specifies an index of the child node to return. """ - if not isinstance(target, basestring) or not isinstance(index, int): + if not isinstance(target, six.string_types) or not isinstance(index, int): raise TypeError return capi.get_attr_value(self.ptr, target, index) diff --git a/django/contrib/gis/geoip/base.py b/django/contrib/gis/geoip/base.py index e00e0a4d93..944240c811 100644 --- a/django/contrib/gis/geoip/base.py +++ b/django/contrib/gis/geoip/base.py @@ -10,6 +10,8 @@ from django.contrib.gis.geoip.prototypes import ( GeoIP_country_code_by_addr, GeoIP_country_code_by_name, GeoIP_country_name_by_addr, GeoIP_country_name_by_name) +from django.utils import six + # Regular expressions for recognizing the GeoIP free database editions. free_regex = re.compile(r'^GEO-\d{3}FREE') lite_regex = re.compile(r'^GEO-\d{3}LITE') @@ -86,7 +88,7 @@ class GeoIP(object): if not path: path = GEOIP_SETTINGS.get('GEOIP_PATH', None) if not path: raise GeoIPException('GeoIP path must be provided via parameter or the GEOIP_PATH setting.') - if not isinstance(path, basestring): + if not isinstance(path, six.string_types): raise TypeError('Invalid path type: %s' % type(path).__name__) if os.path.isdir(path): @@ -129,7 +131,7 @@ class GeoIP(object): def _check_query(self, query, country=False, city=False, city_or_country=False): "Helper routine for checking the query and database availability." # Making sure a string was passed in for the query. - if not isinstance(query, basestring): + if not isinstance(query, six.string_types): raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__) # GeoIP only takes ASCII-encoded strings. diff --git a/django/contrib/gis/geoip/tests.py b/django/contrib/gis/geoip/tests.py index 1d9132ba6f..e53230d9ad 100644 --- a/django/contrib/gis/geoip/tests.py +++ b/django/contrib/gis/geoip/tests.py @@ -6,6 +6,8 @@ from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.geoip import GeoIP, GeoIPException from django.utils import unittest +from django.utils import six + # Note: Requires use of both the GeoIP country and city datasets. # The GEOIP_DATA path should be the only setting set (the directory # should contain links or the actual database files 'GeoIP.dat' and @@ -35,7 +37,7 @@ class GeoIPTest(unittest.TestCase): bad_params = (23, 'foo', 15.23) for bad in bad_params: self.assertRaises(GeoIPException, GeoIP, cache=bad) - if isinstance(bad, basestring): + if isinstance(bad, six.string_types): e = GeoIPException else: e = TypeError diff --git a/django/contrib/gis/geos/factory.py b/django/contrib/gis/geos/factory.py index ee33f14d19..fbd7d5a3e9 100644 --- a/django/contrib/gis/geos/factory.py +++ b/django/contrib/gis/geos/factory.py @@ -1,12 +1,14 @@ from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex +from django.utils import six + def fromfile(file_h): """ Given a string file name, returns a GEOSGeometry. The file may contain WKB, WKT, or HEX. """ # If given a file name, get a real handle. - if isinstance(file_h, basestring): + if isinstance(file_h, six.string_types): with open(file_h, 'rb') as file_h: buf = file_h.read() else: diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 2d8ac53dbd..703b11bc02 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -27,6 +27,8 @@ from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ew # For recognizing geometry input. from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex +from django.utils import six + class GEOSGeometry(GEOSBase, ListMixin): "A class that, generally, encapsulates a GEOS geometry." @@ -52,7 +54,7 @@ class GEOSGeometry(GEOSBase, ListMixin): The `srid` keyword is used to specify the Source Reference Identifier (SRID) number for this Geometry. If not set, the SRID will be None. """ - if isinstance(geo_input, basestring): + if isinstance(geo_input, six.string_types): if isinstance(geo_input, unicode): # Encoding to ASCII, WKT or HEXEWKB doesn't need any more. geo_input = geo_input.encode('ascii') @@ -153,7 +155,7 @@ class GEOSGeometry(GEOSBase, ListMixin): Equivalence testing, a Geometry may be compared with another Geometry or a WKT representation. """ - if isinstance(other, basestring): + if isinstance(other, six.string_types): return self.wkt == other elif isinstance(other, GEOSGeometry): return self.equals_exact(other) @@ -333,7 +335,7 @@ class GEOSGeometry(GEOSBase, ListMixin): Returns true if the elements in the DE-9IM intersection matrix for the two Geometries match the elements in pattern. """ - if not isinstance(pattern, basestring) or len(pattern) > 9: + if not isinstance(pattern, six.string_types) or len(pattern) > 9: raise GEOSException('invalid intersection matrix pattern') return capi.geos_relatepattern(self.ptr, other.ptr, pattern) diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index 56f702243d..053b9948a2 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -6,6 +6,8 @@ from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc +from django.utils import six + ### The WKB/WKT Reader/Writer structures and pointers ### class WKTReader_st(Structure): pass class WKTWriter_st(Structure): pass @@ -118,7 +120,7 @@ class _WKTReader(IOBase): ptr_type = WKT_READ_PTR def read(self, wkt): - if not isinstance(wkt, basestring): raise TypeError + if not isinstance(wkt, six.string_types): raise TypeError return wkt_reader_read(self.ptr, wkt) class _WKBReader(IOBase): @@ -131,7 +133,7 @@ class _WKBReader(IOBase): if isinstance(wkb, buffer): wkb_s = str(wkb) return wkb_reader_read(self.ptr, wkb_s, len(wkb_s)) - elif isinstance(wkb, basestring): + elif isinstance(wkb, six.string_types): return wkb_reader_read_hex(self.ptr, wkb, len(wkb)) else: raise TypeError @@ -195,7 +197,7 @@ class WKBWriter(IOBase): # `ThreadLocalIO` object holds instances of the WKT and WKB reader/writer # objects that are local to the thread. The `GEOSGeometry` internals # access these instances by calling the module-level functions, defined -# below. +# below. class ThreadLocalIO(threading.local): wkt_r = None wkt_w = None diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 18f1fb65be..b1d00d5241 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -8,6 +8,7 @@ from django.contrib.gis.geos.base import gdal, numpy, GEOSBase from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geometry.test_data import TestDataMixin +from django.utils import six from django.utils import unittest @@ -1004,7 +1005,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): g = GEOSGeometry("POINT(0 0)") self.assertTrue(g.valid) - self.assertTrue(isinstance(g.valid_reason, basestring)) + self.assertTrue(isinstance(g.valid_reason, six.string_types)) self.assertEqual(g.valid_reason, "Valid Geometry") print("\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n") @@ -1012,7 +1013,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): g = GEOSGeometry("LINESTRING(0 0, 0 0)") self.assertTrue(not g.valid) - self.assertTrue(isinstance(g.valid_reason, basestring)) + self.assertTrue(isinstance(g.valid_reason, six.string_types)) self.assertTrue(g.valid_reason.startswith("Too few points in geometry component")) print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n") diff --git a/django/contrib/gis/geos/tests/test_io.py b/django/contrib/gis/geos/tests/test_io.py index 50c36684a0..b39d705f03 100644 --- a/django/contrib/gis/geos/tests/test_io.py +++ b/django/contrib/gis/geos/tests/test_io.py @@ -1,6 +1,7 @@ import binascii import unittest from django.contrib.gis.geos import GEOSGeometry, WKTReader, WKTWriter, WKBReader, WKBWriter, geos_version_info +from django.utils import six class GEOSIOTest(unittest.TestCase): @@ -17,7 +18,7 @@ class GEOSIOTest(unittest.TestCase): for geom in (g1, g2): self.assertEqual(ref, geom) - # Should only accept basestring objects. + # Should only accept six.string_types objects. self.assertRaises(TypeError, wkt_r.read, 1) self.assertRaises(TypeError, wkt_r.read, buffer('foo')) @@ -48,7 +49,7 @@ class GEOSIOTest(unittest.TestCase): bad_input = (1, 5.23, None, False) for bad_wkb in bad_input: self.assertRaises(TypeError, wkb_r.read, bad_wkb) - + def test04_wkbwriter(self): wkb_w = WKBWriter() @@ -67,7 +68,7 @@ class GEOSIOTest(unittest.TestCase): for bad_byteorder in (-1, 2, 523, 'foo', None): # Equivalent of `wkb_w.byteorder = bad_byteorder` self.assertRaises(ValueError, wkb_w._set_byteorder, bad_byteorder) - + # Setting the byteorder to 0 (for Big Endian) wkb_w.byteorder = 0 self.assertEqual(hex2, wkb_w.write_hex(g)) @@ -79,7 +80,7 @@ class GEOSIOTest(unittest.TestCase): # Now, trying out the 3D and SRID flags. g = GEOSGeometry('POINT (5 23 17)') g.srid = 4326 - + hex3d = '0101000080000000000000144000000000000037400000000000003140' wkb3d = buffer(binascii.a2b_hex(hex3d)) hex3d_srid = '01010000A0E6100000000000000000144000000000000037400000000000003140' diff --git a/django/contrib/gis/maps/google/overlays.py b/django/contrib/gis/maps/google/overlays.py index eaf12dad6d..28603ac422 100644 --- a/django/contrib/gis/maps/google/overlays.py +++ b/django/contrib/gis/maps/google/overlays.py @@ -1,6 +1,7 @@ from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon from django.utils.functional import total_ordering from django.utils.safestring import mark_safe +from django.utils import six class GEvent(object): @@ -98,7 +99,7 @@ class GPolygon(GOverlayBase): fill_opacity: The opacity of the polygon fill. Defaults to 0.4. """ - if isinstance(poly, basestring): poly = fromstr(poly) + if isinstance(poly, six.string_types): poly = fromstr(poly) if isinstance(poly, (tuple, list)): poly = Polygon(poly) if not isinstance(poly, Polygon): raise TypeError('GPolygon may only initialize on GEOS Polygons.') @@ -148,7 +149,7 @@ class GPolyline(GOverlayBase): The opacity of the polyline, between 0 and 1. Defaults to 1. """ # If a GEOS geometry isn't passed in, try to contsruct one. - if isinstance(geom, basestring): geom = fromstr(geom) + if isinstance(geom, six.string_types): geom = fromstr(geom) if isinstance(geom, (tuple, list)): geom = Polygon(geom) # Generating the lat/lng coordinate pairs. if isinstance(geom, (LineString, LinearRing)): @@ -239,9 +240,9 @@ class GIcon(object): def __lt__(self, other): return self.varname < other.varname - + def __hash__(self): - # XOR with hash of GIcon type so that hash('varname') won't + # XOR with hash of GIcon type so that hash('varname') won't # equal hash(GIcon('varname')). return hash(self.__class__) ^ hash(self.varname) @@ -278,7 +279,7 @@ class GMarker(GOverlayBase): Draggable option for GMarker, disabled by default. """ # If a GEOS geometry isn't passed in, try to construct one. - if isinstance(geom, basestring): geom = fromstr(geom) + if isinstance(geom, six.string_types): geom = fromstr(geom) if isinstance(geom, (tuple, list)): geom = Point(geom) if isinstance(geom, Point): self.latlng = self.latlng_from_coords(geom.coords) diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index 24e8075cab..ba7817e51c 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -58,7 +58,7 @@ class MeasureBase(object): def __init__(self, default_unit=None, **kwargs): value, self._default_unit = self.default_units(kwargs) setattr(self, self.STANDARD_UNIT, value) - if default_unit and isinstance(default_unit, basestring): + if default_unit and isinstance(default_unit, six.string_types): self._default_unit = default_unit def _get_standard(self): diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 48d6c1b70e..770bbe63db 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -17,6 +17,7 @@ from django.contrib.gis.gdal.field import ( OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime) from django.db import models, transaction from django.contrib.localflavor.us.models import USStateField +from django.utils import six # LayerMapping exceptions. class LayerMapError(Exception): pass @@ -74,7 +75,7 @@ class LayerMapping(object): argument usage. """ # Getting the DataSource and the associated Layer. - if isinstance(data, basestring): + if isinstance(data, six.string_types): self.ds = DataSource(data) else: self.ds = data @@ -249,7 +250,7 @@ class LayerMapping(object): sr = source_srs elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()): sr = source_srs.srs - elif isinstance(source_srs, (int, basestring)): + elif isinstance(source_srs, (int, six.string_types)): sr = SpatialReference(source_srs) else: # Otherwise just pulling the SpatialReference from the layer @@ -266,7 +267,7 @@ class LayerMapping(object): # List of fields to determine uniqueness with for attr in unique: if not attr in self.mapping: raise ValueError - elif isinstance(unique, basestring): + elif isinstance(unique, six.string_types): # Only a single field passed in. if unique not in self.mapping: raise ValueError else: @@ -312,7 +313,7 @@ class LayerMapping(object): will construct and return the uniqueness keyword arguments -- a subset of the feature kwargs. """ - if isinstance(self.unique, basestring): + if isinstance(self.unique, six.string_types): return {self.unique : kwargs[self.unique]} else: return dict((fld, kwargs[fld]) for fld in self.unique) diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py index a9a0362eef..f87bb24c7f 100644 --- a/django/contrib/gis/utils/ogrinspect.py +++ b/django/contrib/gis/utils/ogrinspect.py @@ -9,6 +9,7 @@ from future_builtins import zip # Requires GDAL to use. from django.contrib.gis.gdal import DataSource from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime +from django.utils import six def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): """ @@ -24,7 +25,7 @@ def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): `multi_geom` => Boolean (default: False) - specify as multigeometry. """ - if isinstance(data_source, basestring): + if isinstance(data_source, six.string_types): # Instantiating the DataSource from the string. data_source = DataSource(data_source) elif isinstance(data_source, DataSource): diff --git a/django/contrib/gis/utils/wkt.py b/django/contrib/gis/utils/wkt.py index 4aecc6247d..d60eed31bd 100644 --- a/django/contrib/gis/utils/wkt.py +++ b/django/contrib/gis/utils/wkt.py @@ -2,9 +2,11 @@ Utilities for manipulating Geometry WKT. """ +from django.utils import six + def precision_wkt(geom, prec): """ - Returns WKT text of the geometry according to the given precision (an + Returns WKT text of the geometry according to the given precision (an integer or a string). If the precision is an integer, then the decimal places of coordinates WKT will be truncated to that number: @@ -14,12 +16,12 @@ def precision_wkt(geom, prec): >>> precision(geom, 1) 'POINT (5.0 23.0)' - If the precision is a string, it must be valid Python format string + If the precision is a string, it must be valid Python format string (e.g., '%20.7f') -- thus, you should know what you're doing. """ if isinstance(prec, int): num_fmt = '%%.%df' % prec - elif isinstance(prec, basestring): + elif isinstance(prec, six.string_types): num_fmt = prec else: raise TypeError diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py index 951c1eda26..e7724f1b91 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -5,10 +5,12 @@ from threading import local from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError +from django.utils import six + class BaseMemcachedCache(BaseCache): def __init__(self, server, params, library, value_not_found_exception): super(BaseMemcachedCache, self).__init__(params) - if isinstance(server, basestring): + if isinstance(server, six.string_types): self._servers = server.split(';') else: self._servers = server diff --git a/django/core/mail/backends/filebased.py b/django/core/mail/backends/filebased.py index 674ca32f3f..4a74c34f1f 100644 --- a/django/core/mail/backends/filebased.py +++ b/django/core/mail/backends/filebased.py @@ -6,6 +6,7 @@ import os from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.mail.backends.console import EmailBackend as ConsoleEmailBackend +from django.utils import six class EmailBackend(ConsoleEmailBackend): def __init__(self, *args, **kwargs): @@ -15,7 +16,7 @@ class EmailBackend(ConsoleEmailBackend): else: self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None) # Make sure self.file_path is a string. - if not isinstance(self.file_path, basestring): + if not isinstance(self.file_path, six.string_types): raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path) self.file_path = os.path.abspath(self.file_path) # Make sure that self.file_path is an directory if it exists. diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 82f6b6900f..629ad464f9 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -16,6 +16,7 @@ from io import BytesIO from django.conf import settings from django.core.mail.utils import DNS_NAME from django.utils.encoding import smart_str, force_unicode +from django.utils import six # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from @@ -96,7 +97,7 @@ def forbid_multi_line_headers(name, val, encoding): def sanitize_address(addr, encoding): - if isinstance(addr, basestring): + if isinstance(addr, six.string_types): addr = parseaddr(force_unicode(addr)) nm, addr = addr nm = str(Header(nm, encoding)) @@ -180,17 +181,17 @@ class EmailMessage(object): necessary encoding conversions. """ if to: - assert not isinstance(to, basestring), '"to" argument must be a list or tuple' + assert not isinstance(to, six.string_types), '"to" argument must be a list or tuple' self.to = list(to) else: self.to = [] if cc: - assert not isinstance(cc, basestring), '"cc" argument must be a list or tuple' + assert not isinstance(cc, six.string_types), '"cc" argument must be a list or tuple' self.cc = list(cc) else: self.cc = [] if bcc: - assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple' + assert not isinstance(bcc, six.string_types), '"bcc" argument must be a list or tuple' self.bcc = list(bcc) else: self.bcc = [] diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 7cd6958abf..51eeae4e91 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -3,6 +3,7 @@ import sys from django.core.management.color import color_style from django.utils.encoding import smart_str from django.utils.itercompat import is_iterable +from django.utils import six class ModelErrorCollection: def __init__(self, outfile=sys.stdout): @@ -93,7 +94,7 @@ def get_validation_errors(outfile, app=None): if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders): e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name) if f.choices: - if isinstance(f.choices, basestring) or not is_iterable(f.choices): + if isinstance(f.choices, six.string_types) or not is_iterable(f.choices): e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name) else: for c in f.choices: @@ -168,7 +169,7 @@ def get_validation_errors(outfile, app=None): if f.unique: e.add(opts, "ManyToManyFields cannot be unique. Remove the unique argument on '%s'." % f.name) - if f.rel.through is not None and not isinstance(f.rel.through, basestring): + if f.rel.through is not None and not isinstance(f.rel.through, six.string_types): from_model, to_model = cls, f.rel.to if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created: e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.") @@ -239,7 +240,7 @@ def get_validation_errors(outfile, app=None): "to %s and %s" % (f.name, f.rel.through._meta.object_name, f.rel.to._meta.object_name, cls._meta.object_name) ) - elif isinstance(f.rel.through, basestring): + elif isinstance(f.rel.through, six.string_types): e.add(opts, "'%s' specifies an m2m relation through model %s, " "which has not been installed" % (f.name, f.rel.through) ) diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 04053c1f8f..19886f7d53 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -6,6 +6,7 @@ from io import BytesIO from django.db import models from django.utils.encoding import smart_unicode +from django.utils import six class SerializerDoesNotExist(KeyError): """The requested serializer was not found.""" @@ -123,7 +124,7 @@ class Deserializer(object): Init this serializer given a stream or a string """ self.options = options - if isinstance(stream_or_string, basestring): + if isinstance(stream_or_string, six.string_types): self.stream = BytesIO(stream_or_string) else: self.stream = stream_or_string diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index 941b9e0ba8..8b56d0e7b8 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -13,6 +13,7 @@ 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_str +from django.utils import six from django.utils.timezone import is_aware class Serializer(PythonSerializer): @@ -63,7 +64,7 @@ def Deserializer(stream_or_string, **options): if isinstance(stream_or_string, bytes): stream_or_string = stream_or_string.decode('utf-8') try: - if isinstance(stream_or_string, basestring): + if isinstance(stream_or_string, six.string_types): objects = json.loads(stream_or_string) else: objects = json.load(stream_or_string) diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index 73e92d557f..ac0e6cf82d 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -13,6 +13,7 @@ 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_str +from django.utils import six class DjangoSafeDumper(yaml.SafeDumper): @@ -53,7 +54,7 @@ def Deserializer(stream_or_string, **options): """ if isinstance(stream_or_string, bytes): stream_or_string = stream_or_string.decode('utf-8') - if isinstance(stream_or_string, basestring): + if isinstance(stream_or_string, six.string_types): stream = StringIO(stream_or_string) else: stream = stream_or_string diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index a5248f2a5d..e88975f849 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -19,6 +19,7 @@ from django.utils.functional import memoize, lazy from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule from django.utils.regex_helper import normalize +from django.utils import six from django.utils.translation import get_language @@ -159,7 +160,7 @@ class LocaleRegexProvider(object): """ language_code = get_language() if language_code not in self._regex_dict: - if isinstance(self._regex, basestring): + if isinstance(self._regex, six.string_types): regex = self._regex else: regex = force_unicode(self._regex) @@ -228,7 +229,7 @@ class RegexURLResolver(LocaleRegexProvider): LocaleRegexProvider.__init__(self, regex) # urlconf_name is a string representing the module containing URLconfs. self.urlconf_name = urlconf_name - if not isinstance(urlconf_name, basestring): + if not isinstance(urlconf_name, six.string_types): self._urlconf_module = self.urlconf_name self.callback = None self.default_kwargs = default_kwargs or {} @@ -434,7 +435,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current if prefix is None: prefix = get_script_prefix() - if not isinstance(viewname, basestring): + if not isinstance(viewname, six.string_types): view = viewname else: parts = viewname.split(':') diff --git a/django/core/validators.py b/django/core/validators.py index c7c89786da..47c1cbf1cd 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode from django.utils.ipv6 import is_valid_ipv6_address +from django.utils import six # These values, if given to validate(), will trigger the self.required check. EMPTY_VALUES = (None, '', [], (), {}) @@ -25,7 +26,7 @@ class RegexValidator(object): self.code = code # Compile the regex if it was not passed pre-compiled. - if isinstance(self.regex, basestring): + if isinstance(self.regex, six.string_types): self.regex = re.compile(self.regex) def __call__(self, value): diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index e093f7e84e..9ac41a5741 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -10,6 +10,7 @@ import decimal import sys import warnings +from django.utils import six def _setup_environment(environ): import platform @@ -361,7 +362,7 @@ WHEN (new.%(col_name)s IS NULL) if value is None: return None - if isinstance(value, basestring): + if isinstance(value, six.string_types): return datetime.datetime.strptime(value, '%H:%M:%S') # Oracle doesn't support tz-aware times @@ -596,7 +597,7 @@ class OracleParam(object): if hasattr(param, 'input_size'): # If parameter has `input_size` attribute, use that. self.input_size = param.input_size - elif isinstance(param, basestring) and len(param) > 4000: + elif isinstance(param, six.string_types) and len(param) > 4000: # Mark any string param greater than 4000 characters as a CLOB. self.input_size = Database.CLOB else: @@ -824,7 +825,7 @@ def to_unicode(s): Convert strings to Unicode objects (and return all other data types unchanged). """ - if isinstance(s, basestring): + if isinstance(s, six.string_types): return force_unicode(s) return s diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 9db76a617b..9606b1b843 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -21,6 +21,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode, force_unicode from django.utils.ipv6 import clean_ipv6_address +from django.utils import six class NOT_PROVIDED: pass @@ -625,7 +626,7 @@ class CharField(Field): return "CharField" def to_python(self, value): - if isinstance(value, basestring) or value is None: + if isinstance(value, six.string_types) or value is None: return value return smart_unicode(value) @@ -864,7 +865,7 @@ class DecimalField(Field): raise exceptions.ValidationError(msg) def _format(self, value): - if isinstance(value, basestring) or value is None: + if isinstance(value, six.string_types) or value is None: return value else: return self.format_number(value) @@ -1185,7 +1186,7 @@ class TextField(Field): return "TextField" def get_prep_value(self, value): - if isinstance(value, basestring) or value is None: + if isinstance(value, six.string_types) or value is None: return value return smart_unicode(value) diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index b0dce381a6..d3f1327315 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -9,6 +9,7 @@ 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_unicode, smart_str +from django.utils import six from django.utils.translation import ugettext_lazy as _ class FieldFile(File): @@ -176,7 +177,7 @@ class FileDescriptor(object): # subclasses might also want to subclass the attribute class]. This # object understands how to convert a path to a file, and also how to # handle None. - if isinstance(file, basestring) or file is None: + if isinstance(file, six.string_types) or file is None: attr = self.field.attr_class(instance, self.field, file) instance.__dict__[self.field.name] = attr diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 96d1438282..2a2502b54f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -10,6 +10,7 @@ from django.db.models.query import QuerySet from django.db.models.query_utils import QueryWrapper from django.db.models.deletion import CASCADE from django.utils.encoding import smart_unicode +from django.utils import six from django.utils.translation import ugettext_lazy as _, string_concat from django.utils.functional import curry, cached_property from django.core import exceptions @@ -104,7 +105,7 @@ class RelatedField(object): } other = self.rel.to - if isinstance(other, basestring) or other._meta.pk is None: + if isinstance(other, six.string_types) or other._meta.pk is None: def resolve_related_class(field, model, cls): field.rel.to = model field.do_related_class(model, cls) @@ -865,7 +866,7 @@ class ManyToOneRel(object): try: to._meta except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT - assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT + assert isinstance(to, six.string_types), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT self.to, self.field_name = to, field_name self.related_name = related_name if limit_choices_to is None: @@ -933,7 +934,7 @@ class ForeignKey(RelatedField, Field): try: to_name = to._meta.object_name.lower() except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT - assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) + assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) else: assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) # For backwards compatibility purposes, we need to *try* and set @@ -1004,7 +1005,7 @@ class ForeignKey(RelatedField, Field): def contribute_to_class(self, cls, name): super(ForeignKey, self).contribute_to_class(cls, name) setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self)) - if isinstance(self.rel.to, basestring): + if isinstance(self.rel.to, six.string_types): target = self.rel.to else: target = self.rel.to._meta.db_table @@ -1022,7 +1023,7 @@ class ForeignKey(RelatedField, Field): def formfield(self, **kwargs): db = kwargs.pop('using', None) - if isinstance(self.rel.to, basestring): + if isinstance(self.rel.to, six.string_types): raise ValueError("Cannot create form field for %r yet, because " "its related model %r has not been loaded yet" % (self.name, self.rel.to)) @@ -1079,13 +1080,13 @@ class OneToOneField(ForeignKey): def create_many_to_many_intermediary_model(field, klass): from django.db import models managed = True - if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: + if isinstance(field.rel.to, six.string_types) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: to_model = field.rel.to to = to_model.split('.')[-1] def set_managed(field, model, cls): field.rel.through._meta.managed = model._meta.managed or cls._meta.managed add_lazy_relation(klass, field, to_model, set_managed) - elif isinstance(field.rel.to, basestring): + elif isinstance(field.rel.to, six.string_types): to = klass._meta.object_name to_model = klass managed = klass._meta.managed @@ -1124,7 +1125,7 @@ class ManyToManyField(RelatedField, Field): try: assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT - assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) + assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) # Python 2.6 and earlier require dictionary keys to be of str type, # not unicode and class names must be ASCII (in Python 2.x), so we # forcibly coerce it here (breaks early if there's a problem). @@ -1232,12 +1233,12 @@ class ManyToManyField(RelatedField, Field): # Populate some necessary rel arguments so that cross-app relations # work correctly. - if isinstance(self.rel.through, basestring): + if isinstance(self.rel.through, six.string_types): def resolve_through_model(field, model, cls): field.rel.through = model add_lazy_relation(cls, self, self.rel.through, resolve_through_model) - if isinstance(self.rel.to, basestring): + if isinstance(self.rel.to, six.string_types): target = self.rel.to else: target = self.rel.to._meta.db_table diff --git a/django/db/models/options.py b/django/db/models/options.py index 767b625c1d..7308a15c6b 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -10,6 +10,7 @@ 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_unicode, smart_str from django.utils.datastructures import SortedDict +from django.utils import six # 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() @@ -400,7 +401,7 @@ class Options(object): proxy_cache = cache.copy() for klass in get_models(include_auto_created=True, only_installed=False): for f in klass._meta.local_fields: - if f.rel and not isinstance(f.rel.to, basestring): + if f.rel and not isinstance(f.rel.to, six.string_types): if self == f.rel.to._meta: cache[RelatedObject(f.rel.to, klass, f)] = None proxy_cache[RelatedObject(f.rel.to, klass, f)] = None @@ -442,7 +443,7 @@ class Options(object): cache[obj] = model for klass in get_models(only_installed=False): for f in klass._meta.local_many_to_many: - if f.rel and not isinstance(f.rel.to, basestring) and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, six.string_types) and self == f.rel.to._meta: cache[RelatedObject(f.rel.to, klass, f)] = None if app_cache_ready(): self._related_many_to_many_cache = cache diff --git a/django/db/utils.py b/django/db/utils.py index 2b6ae2cf2e..0ce09bab70 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -4,6 +4,7 @@ from threading import local from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.importlib import import_module +from django.utils import six DEFAULT_DB_ALIAS = 'default' @@ -108,7 +109,7 @@ class ConnectionRouter(object): def __init__(self, routers): self.routers = [] for r in routers: - if isinstance(r, basestring): + if isinstance(r, six.string_types): try: module_name, klass_name = r.rsplit('.', 1) module = import_module(module_name) diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py index 6c39b25a74..4e11a4ee06 100644 --- a/django/forms/extras/widgets.py +++ b/django/forms/extras/widgets.py @@ -11,6 +11,7 @@ from django.utils import datetime_safe from django.utils.dates import MONTHS from django.utils.safestring import mark_safe from django.utils.formats import get_format +from django.utils import six from django.conf import settings __all__ = ('SelectDateWidget',) @@ -64,7 +65,7 @@ class SelectDateWidget(Widget): year_val, month_val, day_val = value.year, value.month, value.day except AttributeError: year_val = month_val = day_val = None - if isinstance(value, basestring): + if isinstance(value, six.string_types): if settings.USE_L10N: try: input_format = get_format('DATE_INPUT_FORMATS')[0] diff --git a/django/forms/fields.py b/django/forms/fields.py index 4668eade97..4c4209dddd 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -22,6 +22,7 @@ from django.forms.widgets import (TextInput, PasswordInput, HiddenInput, from django.utils import formats from django.utils.encoding import smart_unicode, force_unicode from django.utils.ipv6 import clean_ipv6_address +from django.utils import six from django.utils.translation import ugettext_lazy as _ # Provide this import for backwards compatibility. @@ -445,7 +446,7 @@ class RegexField(CharField): return self._regex def _set_regex(self, regex): - if isinstance(regex, basestring): + if isinstance(regex, six.string_types): regex = re.compile(regex, re.UNICODE) self._regex = regex if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: @@ -633,7 +634,7 @@ class BooleanField(Field): # will submit for False. Also check for '0', since this is what # RadioSelect will provide. Because bool("True") == bool('1') == True, # we don't need to handle that explicitly. - if isinstance(value, basestring) and value.lower() in ('false', '0'): + if isinstance(value, six.string_types) and value.lower() in ('false', '0'): value = False else: value = bool(value) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 20fa9e973a..f2446efcdf 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -16,6 +16,7 @@ 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_unicode from django.utils.safestring import mark_safe +from django.utils import six from django.utils import datetime_safe, formats from django.utils import six @@ -522,7 +523,7 @@ class CheckboxInput(Widget): value = data.get(name) # Translate true and false strings to boolean values. values = {'true': True, 'false': False} - if isinstance(value, basestring): + if isinstance(value, six.string_types): value = values.get(value.lower(), value) return value diff --git a/django/template/base.py b/django/template/base.py index 89bc90971f..cbf6de2d40 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -18,6 +18,7 @@ from django.utils.safestring import (SafeData, EscapeData, mark_safe, from django.utils.formats import localize 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 @@ -1188,7 +1189,7 @@ class Library(object): from django.template.loader import get_template, select_template if isinstance(file_name, Template): t = file_name - elif not isinstance(file_name, basestring) and is_iterable(file_name): + elif not isinstance(file_name, six.string_types) and is_iterable(file_name): t = select_template(file_name) else: t = get_template(file_name) diff --git a/django/template/loader.py b/django/template/loader.py index b6d62cc2b0..cfffb4014e 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -29,6 +29,7 @@ from django.core.exceptions import ImproperlyConfigured from django.template.base import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins from django.utils.importlib import import_module from django.conf import settings +from django.utils import six template_source_loaders = None @@ -89,7 +90,7 @@ def find_template_loader(loader): loader, args = loader[0], loader[1:] else: args = [] - if isinstance(loader, basestring): + if isinstance(loader, six.string_types): module, attr = loader.rsplit('.', 1) try: mod = import_module(module) diff --git a/django/template/response.py b/django/template/response.py index 286417ccdf..800e060c74 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -1,5 +1,6 @@ from django.http import HttpResponse from django.template import loader, Context, RequestContext +from django.utils import six class ContentNotRenderedError(Exception): @@ -53,7 +54,7 @@ class SimpleTemplateResponse(HttpResponse): "Accepts a template object, path-to-template or list of paths" if isinstance(template, (list, tuple)): return loader.select_template(template) - elif isinstance(template, basestring): + elif isinstance(template, six.string_types): return loader.get_template(template) else: return template diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 231b723d3a..509ab6707d 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -5,6 +5,7 @@ from django.template import (Node, Variable, TemplateSyntaxError, TokenParser, Library, TOKEN_TEXT, TOKEN_VAR) from django.template.base import _render_value_in_context from django.template.defaulttags import token_kwargs +from django.utils import six from django.utils import translation @@ -76,7 +77,7 @@ class TranslateNode(Node): self.asvar = asvar self.message_context = message_context self.filter_expression = filter_expression - if isinstance(self.filter_expression.var, basestring): + if isinstance(self.filter_expression.var, six.string_types): self.filter_expression.var = Variable("'%s'" % self.filter_expression.var) diff --git a/django/templatetags/tz.py b/django/templatetags/tz.py index ca72ca5ec8..96210f189d 100644 --- a/django/templatetags/tz.py +++ b/django/templatetags/tz.py @@ -7,6 +7,7 @@ except ImportError: from django.template import Node from django.template import TemplateSyntaxError, Library +from django.utils import six from django.utils import timezone register = Library() @@ -64,7 +65,7 @@ def do_timezone(value, arg): # Obtain a tzinfo instance if isinstance(arg, tzinfo): tz = arg - elif isinstance(arg, basestring) and pytz is not None: + elif isinstance(arg, six.string_types) and pytz is not None: try: tz = pytz.timezone(arg) except pytz.UnknownTimeZoneError: diff --git a/django/test/_doctest.py b/django/test/_doctest.py index 4456511532..0388714094 100644 --- a/django/test/_doctest.py +++ b/django/test/_doctest.py @@ -480,7 +480,7 @@ class DocTest: Create a new DocTest containing the given examples. The DocTest's globals are initialized with a copy of `globs`. """ - assert not isinstance(examples, basestring), \ + assert not isinstance(examples, six.string_types), \ "DocTest no longer accepts str; use DocTestParser instead" self.examples = examples self.docstring = docstring @@ -906,13 +906,13 @@ class DocTestFinder: # Look for tests in a module's __test__ dictionary. if inspect.ismodule(obj) and self._recurse: for valname, val in getattr(obj, '__test__', {}).items(): - if not isinstance(valname, basestring): + if not isinstance(valname, six.string_types): raise ValueError("DocTestFinder.find: __test__ keys " "must be strings: %r" % (type(valname),)) if not (inspect.isfunction(val) or inspect.isclass(val) or inspect.ismethod(val) or inspect.ismodule(val) or - isinstance(val, basestring)): + isinstance(val, six.string_types)): raise ValueError("DocTestFinder.find: __test__ values " "must be strings, functions, methods, " "classes, or modules: %r" % @@ -945,7 +945,7 @@ class DocTestFinder: """ # Extract the object's docstring. If it doesn't have one, # then return None (no test for this object). - if isinstance(obj, basestring): + if isinstance(obj, six.string_types): docstring = obj else: try: @@ -953,7 +953,7 @@ class DocTestFinder: docstring = '' else: docstring = obj.__doc__ - if not isinstance(docstring, basestring): + if not isinstance(docstring, six.string_types): docstring = str(docstring) except (TypeError, AttributeError): docstring = '' diff --git a/django/test/client.py b/django/test/client.py index 74e11a0efd..7b6cdaa2a4 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -116,7 +116,7 @@ def encode_multipart(boundary, data): for (key, value) in data.items(): if is_file(value): lines.extend(encode_file(boundary, key, value)) - elif not isinstance(value, basestring) and is_iterable(value): + elif not isinstance(value, six.string_types) and is_iterable(value): for item in value: if is_file(item): lines.extend(encode_file(boundary, key, item)) diff --git a/django/test/html.py b/django/test/html.py index 79b198f1be..a44eb72322 100644 --- a/django/test/html.py +++ b/django/test/html.py @@ -8,6 +8,7 @@ import re from HTMLParser import HTMLParseError from django.utils.encoding import force_unicode from django.utils.html_parser import HTMLParser +from django.utils import six WHITESPACE = re.compile('\s+') @@ -24,11 +25,11 @@ class Element(object): self.children = [] def append(self, element): - if isinstance(element, basestring): + if isinstance(element, six.string_types): element = force_unicode(element) element = normalize_whitespace(element) if self.children: - if isinstance(self.children[-1], basestring): + if isinstance(self.children[-1], six.string_types): self.children[-1] += element self.children[-1] = normalize_whitespace(self.children[-1]) return @@ -36,7 +37,7 @@ class Element(object): # removing last children if it is only whitespace # this can result in incorrect dom representations since # whitespace between inline tags like is significant - if isinstance(self.children[-1], basestring): + if isinstance(self.children[-1], six.string_types): if self.children[-1].isspace(): self.children.pop() if element: @@ -45,7 +46,7 @@ class Element(object): def finalize(self): def rstrip_last_element(children): if children: - if isinstance(children[-1], basestring): + if isinstance(children[-1], six.string_types): children[-1] = children[-1].rstrip() if not children[-1]: children.pop() @@ -54,7 +55,7 @@ class Element(object): rstrip_last_element(self.children) for i, child in enumerate(self.children): - if isinstance(child, basestring): + if isinstance(child, six.string_types): self.children[i] = child.strip() elif hasattr(child, 'finalize'): child.finalize() @@ -87,15 +88,15 @@ class Element(object): return not self.__eq__(element) def _count(self, element, count=True): - if not isinstance(element, basestring): + if not isinstance(element, six.string_types): if self == element: return 1 i = 0 for child in self.children: # child is text content and element is also text content, then # make a simple "text" in "text" - if isinstance(child, basestring): - if isinstance(element, basestring): + if isinstance(child, six.string_types): + if isinstance(element, six.string_types): if count: i += child.count(element) elif element in child: @@ -219,6 +220,6 @@ def parse_html(html): document.finalize() # Removing ROOT element if it's not necessary if len(document.children) == 1: - if not isinstance(document.children[0], basestring): + if not isinstance(document.children[0], six.string_types): document = document.children[0] return document diff --git a/django/test/utils.py b/django/test/utils.py index ca4a5e7474..4b121bdfb0 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -6,6 +6,7 @@ from django.template import Template, loader, TemplateDoesNotExist from django.template.loaders import cached from django.utils.translation import deactivate from django.utils.functional import wraps +from django.utils import six __all__ = ( @@ -35,7 +36,7 @@ class ContextList(list): in a list of context objects. """ def __getitem__(self, key): - if isinstance(key, basestring): + if isinstance(key, six.string_types): for subcontext in self: if key in subcontext: return subcontext[key] diff --git a/django/utils/archive.py b/django/utils/archive.py index 70e1f9ba59..6b5d73290f 100644 --- a/django/utils/archive.py +++ b/django/utils/archive.py @@ -27,6 +27,8 @@ import sys import tarfile import zipfile +from django.utils import six + class ArchiveException(Exception): """ @@ -58,7 +60,7 @@ class Archive(object): @staticmethod def _archive_cls(file): cls = None - if isinstance(file, basestring): + if isinstance(file, six.string_types): filename = file else: try: diff --git a/django/utils/checksums.py b/django/utils/checksums.py index 970f563f38..6bbdccc58c 100644 --- a/django/utils/checksums.py +++ b/django/utils/checksums.py @@ -4,6 +4,8 @@ Common checksum routines (used in multiple localflavor/ cases, for example). __all__ = ['luhn',] +from django.utils import six + LUHN_ODD_LOOKUP = (0, 2, 4, 6, 8, 1, 3, 5, 7, 9) # sum_of_digits(index * 2) def luhn(candidate): @@ -12,7 +14,7 @@ def luhn(candidate): algorithm (used in validation of, for example, credit cards). Both numeric and string candidates are accepted. """ - if not isinstance(candidate, basestring): + if not isinstance(candidate, six.string_types): candidate = str(candidate) try: evens = sum([int(c) for c in candidate[-1::-2]]) diff --git a/django/utils/dictconfig.py b/django/utils/dictconfig.py index ae797afcc5..b4d6d66b3c 100644 --- a/django/utils/dictconfig.py +++ b/django/utils/dictconfig.py @@ -23,6 +23,8 @@ import re import sys import types +from django.utils import six + IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) def valid_ident(s): @@ -231,7 +233,7 @@ class BaseConfigurator(object): isinstance(value, tuple): value = ConvertingTuple(value) value.configurator = self - elif isinstance(value, basestring): # str for py3k + elif isinstance(value, six.string_types): # str for py3k m = self.CONVERT_PATTERN.match(value) if m: d = m.groupdict() diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 30665480f6..716d46ceff 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -64,7 +64,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): if strings_only and is_protected_type(s): return s try: - if not isinstance(s, basestring,): + if not isinstance(s, six.string_types,): if hasattr(s, '__unicode__'): s = unicode(s) else: @@ -109,7 +109,7 @@ def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): return s if isinstance(s, Promise): return unicode(s).encode(encoding, errors) - elif not isinstance(s, basestring): + elif not isinstance(s, six.string_types): try: return str(s) except UnicodeEncodeError: diff --git a/django/utils/formats.py b/django/utils/formats.py index d3afc72729..eae40970da 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -178,7 +178,7 @@ def sanitize_separators(value): """ if settings.USE_L10N: decimal_separator = get_format('DECIMAL_SEPARATOR') - if isinstance(value, basestring): + if isinstance(value, six.string_types): parts = [] if decimal_separator in value: value, decimals = value.split(decimal_separator, 1) diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py index 4b8ecea721..8953a21e95 100644 --- a/django/utils/regex_helper.py +++ b/django/utils/regex_helper.py @@ -7,6 +7,8 @@ should be good enough for a large class of URLS, however. """ from __future__ import unicode_literals +from django.utils import six + # Mapping of an escape character to a representative of that class. So, e.g., # "\w" is replaced by "x" in a reverse URL. A value of None means to ignore # this sequence. Any missing key is mapped to itself. @@ -302,7 +304,7 @@ def flatten_result(source): result_args = [[]] pos = last = 0 for pos, elt in enumerate(source): - if isinstance(elt, basestring): + if isinstance(elt, six.string_types): continue piece = ''.join(source[last:pos]) if isinstance(elt, Group): diff --git a/django/utils/timezone.py b/django/utils/timezone.py index d9d9636023..68c214d26f 100644 --- a/django/utils/timezone.py +++ b/django/utils/timezone.py @@ -13,6 +13,7 @@ except ImportError: pytz = None from django.conf import settings +from django.utils import six __all__ = [ 'utc', 'get_default_timezone', 'get_current_timezone', @@ -107,7 +108,7 @@ def get_default_timezone(): """ global _localtime if _localtime is None: - if isinstance(settings.TIME_ZONE, basestring) and pytz is not None: + if isinstance(settings.TIME_ZONE, six.string_types) and pytz is not None: _localtime = pytz.timezone(settings.TIME_ZONE) else: _localtime = LocalTimezone() @@ -160,7 +161,7 @@ def activate(timezone): """ if isinstance(timezone, tzinfo): _active.value = timezone - elif isinstance(timezone, basestring) and pytz is not None: + elif isinstance(timezone, six.string_types) and pytz is not None: _active.value = pytz.timezone(timezone) else: raise ValueError("Invalid timezone: %r" % timezone) diff --git a/django/views/debug.py b/django/views/debug.py index 3414cb193c..65226b5ca7 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -15,6 +15,7 @@ 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_unicode, smart_str +from django.utils import six HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE') @@ -214,7 +215,7 @@ class ExceptionReporter(object): self.loader_debug_info = None # Handle deprecated string exceptions - if isinstance(self.exc_type, basestring): + if isinstance(self.exc_type, six.string_types): self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type) self.exc_type = type(self.exc_value) diff --git a/django/views/i18n.py b/django/views/i18n.py index 5d1ecb99ea..b0f64f4902 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -8,6 +8,7 @@ from django.utils.translation import check_for_language, activate, to_locale, ge from django.utils.text import javascript_quote from django.utils.encoding import smart_unicode from django.utils.formats import get_format_modules, get_format +from django.utils import six def set_language(request): """ @@ -52,7 +53,7 @@ def get_formats(): result[attr] = get_format(attr) src = [] for k, v in result.items(): - if isinstance(v, (basestring, int)): + if isinstance(v, (six.string_types, int)): src.append("formats['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(smart_unicode(v)))) elif isinstance(v, (tuple, list)): v = [javascript_quote(smart_unicode(value)) for value in v] @@ -184,7 +185,7 @@ def javascript_catalog(request, domain='djangojs', packages=None): activate(request.GET['language']) if packages is None: packages = ['django.conf'] - if isinstance(packages, basestring): + if isinstance(packages, six.string_types): packages = packages.split('+') packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS] default_locale = to_locale(settings.LANGUAGE_CODE) @@ -258,7 +259,7 @@ def javascript_catalog(request, domain='djangojs', packages=None): for k, v in t.items(): if k == '': continue - if isinstance(k, basestring): + if isinstance(k, six.string_types): csrc.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(v))) elif isinstance(k, tuple): if k[0] not in pdict: diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/modeltests/field_subclassing/fields.py index b9987c0fab..553b031de3 100644 --- a/tests/modeltests/field_subclassing/fields.py +++ b/tests/modeltests/field_subclassing/fields.py @@ -4,6 +4,7 @@ import json from django.db import models from django.utils.encoding import force_unicode +from django.utils import six class Small(object): @@ -66,7 +67,7 @@ class JSONField(models.TextField): if not value: return None - if isinstance(value, basestring): + if isinstance(value, six.string_types): value = json.loads(value) return value diff --git a/tests/modeltests/serializers/tests.py b/tests/modeltests/serializers/tests.py index 13cf1e7e17..73a3aa3e7a 100644 --- a/tests/modeltests/serializers/tests.py +++ b/tests/modeltests/serializers/tests.py @@ -10,6 +10,7 @@ from django.conf import settings from django.core import serializers from django.db import transaction, connection from django.test import TestCase, TransactionTestCase, Approximate +from django.utils import six from django.utils import unittest from .models import (Category, Author, Article, AuthorProfile, Actor, Movie, @@ -461,7 +462,7 @@ else: # yaml.safe_load will return non-string objects for some # of the fields we are interested in, this ensures that # everything comes back as a string - if isinstance(field_value, basestring): + if isinstance(field_value, six.string_types): ret_list.append(field_value) else: ret_list.append(str(field_value)) diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index 12eb016c6e..feb2ade458 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -35,10 +35,11 @@ from decimal import Decimal from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import * from django.test import SimpleTestCase +from django.utils import six def fix_os_paths(x): - if isinstance(x, basestring): + if isinstance(x, six.string_types): return x.replace('\\', '/') elif isinstance(x, tuple): return tuple(fix_os_paths(list(x))) diff --git a/tests/regressiontests/i18n/commands/tests.py b/tests/regressiontests/i18n/commands/tests.py index 38e8af1d0d..e00ef72d59 100644 --- a/tests/regressiontests/i18n/commands/tests.py +++ b/tests/regressiontests/i18n/commands/tests.py @@ -2,13 +2,15 @@ import os import re from subprocess import Popen, PIPE +from django.utils import six + can_run_extraction_tests = False can_run_compilation_tests = False def find_command(cmd, path=None, pathext=None): if path is None: path = os.environ.get('PATH', []).split(os.pathsep) - if isinstance(path, basestring): + if isinstance(path, six.string_types): path = [path] # check if there are funny path extensions for executables, e.g. Windows if pathext is None: diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 812a80a583..e05729cf7f 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -20,6 +20,7 @@ from django.test.utils import override_settings from django.utils.encoding import smart_unicode from django.utils.functional import empty from django.utils._os import rmtree_errorhandler +from django.utils import six from django.contrib.staticfiles import finders, storage @@ -83,7 +84,7 @@ class BaseStaticFilesTestCase(object): self.assertRaises(IOError, self._get_file, filepath) def render_template(self, template, **kwargs): - if isinstance(template, basestring): + if isinstance(template, six.string_types): template = loader.get_template_from_string(template) return template.render(Context(kwargs)).strip() From bdca5ea345c548a82a80d198906818c9ccbef896 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 14:48:51 +0200 Subject: [PATCH 232/519] [py3] Replaced unicode/str by six.text_type/bytes. --- django/contrib/admin/helpers.py | 2 +- django/contrib/admin/util.py | 4 +- django/contrib/admin/widgets.py | 3 +- django/contrib/auth/models.py | 9 +++-- django/contrib/auth/tokens.py | 5 ++- django/contrib/contenttypes/tests.py | 3 +- django/contrib/formtools/wizard/views.py | 5 ++- django/contrib/gis/db/backends/base.py | 7 ++-- django/contrib/gis/db/backends/util.py | 2 +- django/contrib/gis/gdal/geometries.py | 2 +- django/contrib/gis/gdal/srs.py | 2 +- django/contrib/gis/geos/geometry.py | 2 +- django/contrib/gis/geos/tests/test_io.py | 2 +- django/contrib/gis/tests/geoapp/tests.py | 3 +- django/contrib/gis/utils/layermapping.py | 2 +- django/contrib/localflavor/mx/forms.py | 5 ++- django/contrib/localflavor/se/utils.py | 5 ++- django/core/management/commands/inspectdb.py | 3 +- django/core/management/validation.py | 4 +- django/core/urlresolvers.py | 2 +- django/db/backends/__init__.py | 7 ++-- django/db/backends/mysql/base.py | 8 ++-- django/db/backends/oracle/base.py | 2 +- django/db/backends/sqlite3/base.py | 4 +- django/db/models/base.py | 14 +++---- django/db/models/fields/files.py | 2 +- django/forms/fields.py | 4 +- django/forms/forms.py | 4 +- django/forms/formsets.py | 7 ++-- django/forms/models.py | 4 +- django/http/__init__.py | 16 ++++---- django/http/multipartparser.py | 3 +- django/template/base.py | 2 +- django/template/defaultfilters.py | 9 +++-- django/test/_doctest.py | 2 +- django/test/html.py | 6 +-- django/test/testcases.py | 5 ++- django/utils/dateformat.py | 5 ++- django/utils/encoding.py | 37 ++++++++++++------ django/utils/formats.py | 4 +- django/utils/functional.py | 11 +++--- django/utils/html.py | 17 ++++---- django/utils/http.py | 9 +++-- django/utils/numberformat.py | 5 ++- django/utils/safestring.py | 33 ++++++++-------- django/utils/text.py | 25 ++++++------ django/utils/translation/__init__.py | 15 +++---- django/views/debug.py | 2 +- tests/modeltests/custom_columns/tests.py | 9 +++-- tests/modeltests/custom_managers/tests.py | 3 +- tests/modeltests/custom_pk/fields.py | 5 ++- tests/modeltests/custom_pk/tests.py | 15 +++---- tests/modeltests/expressions/tests.py | 3 +- tests/modeltests/field_subclassing/fields.py | 4 +- tests/modeltests/lookup/models.py | 3 +- tests/modeltests/m2m_and_m2o/models.py | 3 +- tests/modeltests/m2m_intermediary/tests.py | 5 ++- tests/modeltests/many_to_one/tests.py | 3 +- tests/modeltests/model_forms/models.py | 3 +- tests/modeltests/model_forms/tests.py | 33 ++++++++-------- tests/modeltests/model_formsets/models.py | 3 +- tests/modeltests/model_formsets/tests.py | 21 +++++----- tests/modeltests/model_inheritance/tests.py | 5 ++- .../order_with_respect_to/models.py | 3 +- tests/modeltests/pagination/tests.py | 7 ++-- tests/modeltests/prefetch_related/tests.py | 39 ++++++++++--------- tests/modeltests/save_delete_hooks/tests.py | 3 +- tests/modeltests/serializers/models.py | 3 +- tests/modeltests/serializers/tests.py | 2 +- tests/modeltests/signals/tests.py | 3 +- tests/modeltests/update/models.py | 5 ++- .../regressiontests/admin_changelist/tests.py | 9 +++-- tests/regressiontests/admin_util/models.py | 3 +- tests/regressiontests/admin_util/tests.py | 7 ++-- tests/regressiontests/admin_views/tests.py | 7 ++-- tests/regressiontests/backends/tests.py | 5 ++- tests/regressiontests/datatypes/tests.py | 3 +- tests/regressiontests/defaultfilters/tests.py | 7 ++-- tests/regressiontests/file_uploads/views.py | 3 +- .../fixtures_regress/models.py | 3 +- tests/regressiontests/forms/tests/models.py | 3 +- tests/regressiontests/forms/tests/util.py | 3 +- tests/regressiontests/forms/tests/widgets.py | 7 ++-- .../i18n/contenttypes/tests.py | 7 ++-- tests/regressiontests/i18n/tests.py | 9 +++-- .../regressiontests/inline_formsets/tests.py | 5 ++- .../model_forms_regress/tests.py | 9 +++-- .../model_formsets_regress/tests.py | 11 +++--- tests/regressiontests/model_regress/tests.py | 5 ++- tests/regressiontests/queries/models.py | 3 +- .../select_related_regress/tests.py | 5 ++- .../templates/templatetags/custom.py | 23 +++++------ tests/regressiontests/templates/unicode.py | 3 +- .../regressiontests/utils/simplelazyobject.py | 7 ++-- tests/regressiontests/wsgi/tests.py | 3 +- tests/runtests.py | 3 +- 96 files changed, 376 insertions(+), 294 deletions(-) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 11b14dcb51..1bc843cdf9 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -189,7 +189,7 @@ class AdminReadonlyField(object): if value is None: result_repr = EMPTY_CHANGELIST_VALUE elif isinstance(f.rel, ManyToManyRel): - result_repr = ", ".join(map(unicode, value.all())) + result_repr = ", ".join(map(six.text_type, value.all())) else: result_repr = display_for_field(value, f) return conditional_escape(result_repr) diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index a529bacd18..16bdfe0566 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -275,10 +275,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False): except models.FieldDoesNotExist: if name == "__unicode__": label = force_unicode(model._meta.verbose_name) - attr = unicode + attr = six.text_type elif name == "__str__": label = smart_str(model._meta.verbose_name) - attr = str + attr = bytes else: if callable(name): attr = name diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 37f286d0d4..550ed0f7e2 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -15,6 +15,7 @@ from django.utils.text import Truncator from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe from django.utils.encoding import force_unicode +from django.utils import six class FilteredSelectMultiple(forms.SelectMultiple): @@ -121,7 +122,7 @@ def url_params_from_lookup_dict(lookups): # See django.db.fields.BooleanField.get_prep_lookup v = ('0', '1')[v] else: - v = unicode(v) + v = six.text_type(v) items.append((k, v)) params.update(dict(items)) return params diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 244721065d..95a7494d38 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -8,6 +8,7 @@ from django.db import models from django.db.models.manager import EmptyManager from django.utils.crypto import get_random_string from django.utils.encoding import smart_str +from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -79,9 +80,9 @@ class Permission(models.Model): def __unicode__(self): return "%s | %s | %s" % ( - unicode(self.content_type.app_label), - unicode(self.content_type), - unicode(self.name)) + six.text_type(self.content_type.app_label), + six.text_type(self.content_type), + six.text_type(self.name)) def natural_key(self): return (self.codename,) + self.content_type.natural_key() @@ -421,7 +422,7 @@ class AnonymousUser(object): return 'AnonymousUser' def __str__(self): - return unicode(self).encode('utf-8') + return six.text_type(self).encode('utf-8') def __eq__(self, other): return isinstance(other, self.__class__) diff --git a/django/contrib/auth/tokens.py b/django/contrib/auth/tokens.py index fcc8c94011..9b2eda83d4 100644 --- a/django/contrib/auth/tokens.py +++ b/django/contrib/auth/tokens.py @@ -2,6 +2,7 @@ from datetime import date from django.conf import settings from django.utils.http import int_to_base36, base36_to_int from django.utils.crypto import constant_time_compare, salted_hmac +from django.utils import six class PasswordResetTokenGenerator(object): """ @@ -56,8 +57,8 @@ class PasswordResetTokenGenerator(object): # Ensure results are consistent across DB backends login_timestamp = user.last_login.replace(microsecond=0, tzinfo=None) - value = (unicode(user.id) + user.password + - unicode(login_timestamp) + unicode(timestamp)) + value = (six.text_type(user.id) + user.password + + six.text_type(login_timestamp) + six.text_type(timestamp)) hash = salted_hmac(key_salt, value).hexdigest()[::2] return "%s-%s" % (ts_b36, hash) diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py index e9caa20cf2..1ecabc16e9 100644 --- a/django/contrib/contenttypes/tests.py +++ b/django/contrib/contenttypes/tests.py @@ -9,6 +9,7 @@ from django.contrib.sites.models import Site from django.http import HttpRequest, Http404 from django.test import TestCase from django.utils.encoding import smart_str +from django.utils import six class ConcreteModel(models.Model): @@ -271,4 +272,4 @@ class ContentTypesTests(TestCase): app_label = 'contenttypes', model = 'OldModel', ) - self.assertEqual(unicode(ct), 'Old model') + self.assertEqual(six.text_type(ct), 'Old model') diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index 6222d1ddab..466af1cac9 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -7,6 +7,7 @@ from django.forms import formsets, ValidationError from django.views.generic import TemplateView from django.utils.datastructures import SortedDict from django.utils.decorators import classonlymethod +from django.utils import six from django.contrib.formtools.wizard.storage import get_storage from django.contrib.formtools.wizard.storage.exceptions import NoFileStorageConfigured @@ -157,10 +158,10 @@ class WizardView(TemplateView): if isinstance(form, (list, tuple)): # if the element is a tuple, add the tuple to the new created # sorted dictionary. - init_form_list[unicode(form[0])] = form[1] + init_form_list[six.text_type(form[0])] = form[1] else: # if not, add the form with a zero based counter as unicode - init_form_list[unicode(i)] = form + init_form_list[six.text_type(i)] = form # walk through the new created list of forms for form in init_form_list.itervalues(): diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py index 26e97622a8..d9f3546cff 100644 --- a/django/contrib/gis/db/backends/base.py +++ b/django/contrib/gis/db/backends/base.py @@ -4,6 +4,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 class BaseSpatialOperations(object): """ @@ -88,7 +89,7 @@ class BaseSpatialOperations(object): # For quoting column values, rather than columns. def geo_quote_name(self, name): - if isinstance(name, unicode): + if isinstance(name, six.text_type): name = name.encode('ascii') return "'%s'" % name @@ -330,6 +331,6 @@ class SpatialRefSysMixin(object): it will be 'pretty' OGC WKT. """ try: - return unicode(self.srs) + return six.text_type(self.srs) except: - return unicode(self.wkt) + return six.text_type(self.wkt) diff --git a/django/contrib/gis/db/backends/util.py b/django/contrib/gis/db/backends/util.py index b899934a01..648fcfe963 100644 --- a/django/contrib/gis/db/backends/util.py +++ b/django/contrib/gis/db/backends/util.py @@ -12,7 +12,7 @@ def gqn(val): backend quotename function). """ if isinstance(val, six.string_types): - if isinstance(val, unicode): val = val.encode('ascii') + if isinstance(val, six.text_type): val = val.encode('ascii') return "'%s'" % val else: return str(val) diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index f38aeba838..4ad8f91d06 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -81,7 +81,7 @@ class OGRGeometry(GDALBase): # Constructing the geometry, if str_instance: # Checking if unicode - if isinstance(geom_input, unicode): + if isinstance(geom_input, six.text_type): # Encoding to ASCII, WKT or HEX doesn't need any more. geom_input = geom_input.encode('ascii') diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index 6003ce75ad..cdeaaca690 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -56,7 +56,7 @@ class SpatialReference(GDALBase): if isinstance(srs_input, six.string_types): # Encoding to ASCII if unicode passed in. - if isinstance(srs_input, unicode): + if isinstance(srs_input, six.text_type): srs_input = srs_input.encode('ascii') try: # If SRID is a string, e.g., '4326', then make acceptable diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 703b11bc02..4e5409de1d 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -55,7 +55,7 @@ class GEOSGeometry(GEOSBase, ListMixin): (SRID) number for this Geometry. If not set, the SRID will be None. """ if isinstance(geo_input, six.string_types): - if isinstance(geo_input, unicode): + if isinstance(geo_input, six.text_type): # Encoding to ASCII, WKT or HEXEWKB doesn't need any more. geo_input = geo_input.encode('ascii') diff --git a/django/contrib/gis/geos/tests/test_io.py b/django/contrib/gis/geos/tests/test_io.py index b39d705f03..ebf178a807 100644 --- a/django/contrib/gis/geos/tests/test_io.py +++ b/django/contrib/gis/geos/tests/test_io.py @@ -13,7 +13,7 @@ class GEOSIOTest(unittest.TestCase): # read() should return a GEOSGeometry ref = GEOSGeometry(wkt) g1 = wkt_r.read(wkt) - g2 = wkt_r.read(unicode(wkt)) + g2 = wkt_r.read(six.text_type(wkt)) for geom in (g1, g2): self.assertEqual(ref, geom) diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 4136a65d5c..bcdbe734ff 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -11,6 +11,7 @@ from django.contrib.gis.tests.utils import ( no_mysql, no_oracle, no_spatialite, mysql, oracle, postgis, spatialite) from django.test import TestCase +from django.utils import six from .models import Country, City, PennsylvaniaCity, State, Track @@ -663,7 +664,7 @@ class GeoModelTest(TestCase): # Let's try and break snap_to_grid() with bad combinations of arguments. for bad_args in ((), range(3), range(5)): self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args) - for bad_args in (('1.0',), (1.0, None), tuple(map(unicode, range(4)))): + for bad_args in (('1.0',), (1.0, None), tuple(map(six.text_type, range(4)))): self.assertRaises(TypeError, Country.objects.snap_to_grid, *bad_args) # Boundary for San Marino, courtesy of Bjorn Sandvik of thematicmapping.org diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 770bbe63db..e898f6de2e 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -330,7 +330,7 @@ class LayerMapping(object): if self.encoding: # The encoding for OGR data sources may be specified here # (e.g., 'cp437' for Census Bureau boundary files). - val = unicode(ogr_field.value, self.encoding) + val = six.text_type(ogr_field.value, self.encoding) else: val = ogr_field.value if model_field.max_length and len(val) > model_field.max_length: diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py index 2dcf17d26c..4a7c005ad5 100644 --- a/django/contrib/localflavor/mx/forms.py +++ b/django/contrib/localflavor/mx/forms.py @@ -7,6 +7,7 @@ import re from django.forms import ValidationError from django.forms.fields import Select, RegexField +from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.core.validators import EMPTY_VALUES from django.contrib.localflavor.mx.mx_states import STATE_CHOICES @@ -155,7 +156,7 @@ class MXRFCField(RegexField): elif checksum == 11: return '0' - return unicode(checksum) + return six.text_type(checksum) def _has_inconvenient_word(self, rfc): first_four = rfc[:4] @@ -219,7 +220,7 @@ class MXCURPField(RegexField): if checksum == 10: return '0' - return unicode(checksum) + return six.text_type(checksum) def _has_inconvenient_word(self, curp): first_four = curp[:4] diff --git a/django/contrib/localflavor/se/utils.py b/django/contrib/localflavor/se/utils.py index 5e7c2b7dae..783062ebb4 100644 --- a/django/contrib/localflavor/se/utils.py +++ b/django/contrib/localflavor/se/utils.py @@ -1,4 +1,5 @@ import datetime +from django.utils import six def id_number_checksum(gd): """ @@ -65,7 +66,7 @@ def validate_id_birthday(gd, fix_coordination_number_day=True): def format_personal_id_number(birth_day, gd): # birth_day.strftime cannot be used, since it does not support dates < 1900 - return unicode(str(birth_day.year) + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) + return six.text_type(str(birth_day.year) + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) def format_organisation_number(gd): if gd['century'] is None: @@ -73,7 +74,7 @@ def format_organisation_number(gd): else: century = gd['century'] - return unicode(century + gd['year'] + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) + return six.text_type(century + gd['year'] + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) def valid_organisation(gd): return gd['century'] in (None, 16) and \ diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index a524e64f65..7c868e4b60 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -3,6 +3,7 @@ from optparse import make_option from django.core.management.base import NoArgsCommand, CommandError from django.db import connections, DEFAULT_DB_ALIAS +from django.utils import six class Command(NoArgsCommand): help = "Introspects the database tables in the given database and outputs a Django model module." @@ -115,7 +116,7 @@ class Command(NoArgsCommand): if att_name[0].isdigit(): att_name = 'number_%s' % att_name - extra_params['db_column'] = unicode(column_name) + extra_params['db_column'] = six.text_type(column_name) comment_notes.append("Field renamed because it wasn't a " "valid Python identifier.") diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 51eeae4e91..274f98ee79 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -120,7 +120,7 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to)) # it is a string and we could not find the model it refers to # so skip the next section - if isinstance(f.rel.to, (str, unicode)): + if isinstance(f.rel.to, six.string_types): continue # Make sure the related field specified by a ForeignKey is unique @@ -162,7 +162,7 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' has an m2m relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to)) # it is a string and we could not find the model it refers to # so skip the next section - if isinstance(f.rel.to, (str, unicode)): + if isinstance(f.rel.to, six.string_types): continue # Check that the field is not set to unique. ManyToManyFields do not support unique. diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index e88975f849..7397cf3b3d 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -169,7 +169,7 @@ class LocaleRegexProvider(object): except re.error as e: raise ImproperlyConfigured( '"%s" is not a valid regular expression: %s' % - (regex, unicode(e))) + (regex, six.text_type(e))) self._regex_dict[language_code] = compiled_regex return self._regex_dict[language_code] diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 3075bdb3e4..b416343f88 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -12,6 +12,7 @@ from django.db.backends import util from django.db.transaction import TransactionManagementError from django.utils.functional import cached_property from django.utils.importlib import import_module +from django.utils import six from django.utils.timezone import is_aware @@ -808,7 +809,7 @@ class BaseDatabaseOperations(object): """ if value is None: return None - return unicode(value) + return six.text_type(value) def value_to_db_datetime(self, value): """ @@ -817,7 +818,7 @@ class BaseDatabaseOperations(object): """ if value is None: return None - return unicode(value) + return six.text_type(value) def value_to_db_time(self, value): """ @@ -828,7 +829,7 @@ class BaseDatabaseOperations(object): return None if is_aware(value): raise ValueError("Django does not support timezone-aware times.") - return unicode(value) + return six.text_type(value) def value_to_db_decimal(self, value, max_digits, decimal_places): """ diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index a7668dec49..ec65207ed8 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -297,7 +297,7 @@ class DatabaseOperations(BaseDatabaseOperations): raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.") # MySQL doesn't support microseconds - return unicode(value.replace(microsecond=0)) + return six.text_type(value.replace(microsecond=0)) def value_to_db_time(self, value): if value is None: @@ -308,7 +308,7 @@ class DatabaseOperations(BaseDatabaseOperations): raise ValueError("MySQL backend does not support timezone-aware times.") # MySQL doesn't support microseconds - return unicode(value.replace(microsecond=0)) + return six.text_type(value.replace(microsecond=0)) def year_lookup_bounds(self, value): # Again, no microseconds @@ -399,8 +399,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[unicode] - self.connection.encoders[SafeString] = self.connection.encoders[str] + self.connection.encoders[SafeUnicode] = self.connection.encoders[six.text_type] + self.connection.encoders[SafeString] = 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/oracle/base.py b/django/db/backends/oracle/base.py index 9ac41a5741..32ae420ce0 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -356,7 +356,7 @@ WHEN (new.%(col_name)s IS NULL) else: raise ValueError("Oracle backend does not support timezone-aware datetimes when USE_TZ is False.") - return unicode(value) + return six.text_type(value) def value_to_db_time(self, value): if value is None: diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index e2149ca8d8..0a97449789 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -177,7 +177,7 @@ class DatabaseOperations(BaseDatabaseOperations): else: raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.") - return unicode(value) + return six.text_type(value) def value_to_db_time(self, value): if value is None: @@ -187,7 +187,7 @@ class DatabaseOperations(BaseDatabaseOperations): if timezone.is_aware(value): raise ValueError("SQLite backend does not support timezone-aware times.") - return unicode(value) + return six.text_type(value) def year_lookup_bounds(self, value): first = '%s-01-01' diff --git a/django/db/models/base.py b/django/db/models/base.py index 04ae4bc96d..8c448b2f39 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -375,7 +375,7 @@ class ModelWithoutMeta(object): def __repr__(self): try: - u = unicode(self) + u = six.text_type(self) except (UnicodeEncodeError, UnicodeDecodeError): u = '[Bad Unicode data]' return smart_str('<%s: %s>' % (self.__class__.__name__, u)) @@ -790,8 +790,8 @@ class ModelWithoutMeta(object): def date_error_message(self, lookup_type, field, unique_for): opts = self._meta return _("%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { - 'field_name': unicode(capfirst(opts.get_field(field).verbose_name)), - 'date_field': unicode(capfirst(opts.get_field(unique_for).verbose_name)), + 'field_name': six.text_type(capfirst(opts.get_field(field).verbose_name)), + 'date_field': six.text_type(capfirst(opts.get_field(unique_for).verbose_name)), 'lookup': lookup_type, } @@ -806,16 +806,16 @@ class ModelWithoutMeta(object): field_label = capfirst(field.verbose_name) # Insert the error into the error dict, very sneaky return field.error_messages['unique'] % { - 'model_name': unicode(model_name), - 'field_label': unicode(field_label) + 'model_name': six.text_type(model_name), + 'field_label': six.text_type(field_label) } # unique_together else: field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check) field_labels = get_text_list(field_labels, _('and')) return _("%(model_name)s with this %(field_label)s already exists.") % { - 'model_name': unicode(model_name), - 'field_label': unicode(field_labels) + 'model_name': six.text_type(model_name), + 'field_label': six.text_type(field_labels) } def full_clean(self, exclude=None): diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index d3f1327315..b51ef1d5d6 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -265,7 +265,7 @@ class FileField(Field): # Need to convert File objects provided via a form to unicode for database insertion if value is None: return None - return unicode(value) + return six.text_type(value) def pre_save(self, model_instance, add): "Returns field's value just before saving." diff --git a/django/forms/fields.py b/django/forms/fields.py index 4c4209dddd..9c944ad0ac 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -331,10 +331,10 @@ class BaseTemporalField(Field): def to_python(self, value): # Try to coerce the value to unicode. unicode_value = force_unicode(value, strings_only=True) - if isinstance(unicode_value, unicode): + if isinstance(unicode_value, six.text_type): value = unicode_value.strip() # If unicode, try to strptime against each input format. - if isinstance(value, unicode): + if isinstance(value, six.text_type): for format in self.input_formats: try: return self.strptime(value, format) diff --git a/django/forms/forms.py b/django/forms/forms.py index 0af71918d8..4bc3ee9d26 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -151,7 +151,7 @@ class BaseForm(StrAndUnicode): if bf.is_hidden: if bf_errors: top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) - hidden_fields.append(unicode(bf)) + hidden_fields.append(six.text_type(bf)) else: # Create a 'class="..."' atribute if the row should have any # CSS classes applied. @@ -181,7 +181,7 @@ class BaseForm(StrAndUnicode): output.append(normal_row % { 'errors': force_unicode(bf_errors), 'label': force_unicode(label), - 'field': unicode(bf), + 'field': six.text_type(bf), 'help_text': help_text, 'html_class_attr': html_class_attr }) diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 31ca088bdb..8a61f6cd62 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -7,6 +7,7 @@ from django.forms.util import ErrorList from django.forms.widgets import Media, HiddenInput from django.utils.encoding import StrAndUnicode from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import ugettext as _ @@ -345,17 +346,17 @@ class BaseFormSet(StrAndUnicode): # probably should be. It might make sense to render each form as a # table row with each field as a td. forms = ' '.join([form.as_table() for form in self]) - return mark_safe('\n'.join([unicode(self.management_form), forms])) + return mark_safe('\n'.join([six.text_type(self.management_form), forms])) def as_p(self): "Returns this formset rendered as HTML

      s." forms = ' '.join([form.as_p() for form in self]) - return mark_safe('\n'.join([unicode(self.management_form), forms])) + return mark_safe('\n'.join([six.text_type(self.management_form), forms])) def as_ul(self): "Returns this formset rendered as HTML

    • s." forms = ' '.join([form.as_ul() for form in self]) - return mark_safe('\n'.join([unicode(self.management_form), forms])) + return mark_safe('\n'.join([six.text_type(self.management_form), forms])) def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None): diff --git a/django/forms/models.py b/django/forms/models.py index 1ef308d93a..0831d5f4b2 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -576,7 +576,7 @@ class BaseModelFormSet(BaseFormSet): else: return ugettext("Please correct the duplicate data for %(field)s, " "which must be unique.") % { - "field": get_text_list(unique_check, unicode(_("and"))), + "field": get_text_list(unique_check, six.text_type(_("and"))), } def get_date_error_message(self, date_check): @@ -584,7 +584,7 @@ class BaseModelFormSet(BaseFormSet): "which must be unique for the %(lookup)s in %(date_field)s.") % { 'field_name': date_check[2], 'date_field': date_check[3], - 'lookup': unicode(date_check[1]), + 'lookup': six.text_type(date_check[1]), } def get_form_error(self): diff --git a/django/http/__init__.py b/django/http/__init__.py index da1f9edc63..7c5184a329 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -137,10 +137,10 @@ def build_request_repr(request, path_override=None, GET_override=None, return smart_str('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % (request.__class__.__name__, path, - unicode(get), - unicode(post), - unicode(cookies), - unicode(meta))) + six.text_type(get), + six.text_type(post), + six.text_type(cookies), + six.text_type(meta))) class UnreadablePostError(IOError): pass @@ -544,7 +544,7 @@ class HttpResponse(object): def _convert_to_ascii(self, *values): """Converts all values to ascii strings.""" for value in values: - if isinstance(value, unicode): + if isinstance(value, six.text_type): try: value = value.encode('us-ascii') except UnicodeError as e: @@ -663,7 +663,7 @@ class HttpResponse(object): def next(self): chunk = next(self._iterator) - if isinstance(chunk, unicode): + if isinstance(chunk, six.text_type): chunk = chunk.encode(self._charset) return str(chunk) @@ -740,8 +740,8 @@ def str_to_unicode(s, encoding): Returns any non-basestring objects without change. """ - if isinstance(s, str): - return unicode(s, encoding, 'replace') + if isinstance(s, bytes): + return six.text_type(s, encoding, 'replace') else: return s diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index bbe4b052b7..0e28a55c3a 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -11,6 +11,7 @@ from django.conf import settings from django.core.exceptions import SuspiciousOperation from django.utils.datastructures import MultiValueDict from django.utils.encoding import force_unicode +from django.utils import six from django.utils.text import unescape_entities from django.core.files.uploadhandler import StopUpload, SkipFile, StopFutureHandlers @@ -77,7 +78,7 @@ class MultiPartParser(object): # This means we shouldn't continue...raise an error. raise MultiPartParserError("Invalid content length: %r" % content_length) - if isinstance(boundary, unicode): + if isinstance(boundary, six.text_type): boundary = boundary.encode('ascii') self._boundary = boundary self._input_data = input_data diff --git a/django/template/base.py b/django/template/base.py index cbf6de2d40..489b1681e0 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -86,7 +86,7 @@ class VariableDoesNotExist(Exception): self.params = params def __str__(self): - return unicode(self).encode('utf-8') + return six.text_type(self).encode('utf-8') def __unicode__(self): return self.msg % tuple([force_unicode(p, errors='replace') diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index b9cdd94296..fa799cd46f 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -18,6 +18,7 @@ from django.utils.html import (conditional_escape, escapejs, fix_ampersands, from django.utils.http import urlquote from django.utils.text import Truncator, wrap, phone2numeric from django.utils.safestring import mark_safe, SafeData, mark_for_escaping +from django.utils import six from django.utils.timesince import timesince, timeuntil from django.utils.translation import ugettext, ungettext from django.utils.text import normalize_newlines @@ -176,7 +177,7 @@ def floatformat(text, arg=-1): # and `exponent` from `Decimal.as_tuple()` directly. sign, digits, exponent = d.quantize(exp, ROUND_HALF_UP, Context(prec=prec)).as_tuple() - digits = [unicode(digit) for digit in reversed(digits)] + digits = [six.text_type(digit) for digit in reversed(digits)] while len(digits) <= abs(exponent): digits.append('0') digits.insert(-exponent, '.') @@ -200,7 +201,7 @@ def linenumbers(value, autoescape=None): lines = value.split('\n') # Find the maximum width of the line count, for use with zero padding # string format command - width = unicode(len(unicode(len(lines)))) + width = six.text_type(len(six.text_type(len(lines)))) if not autoescape or isinstance(value, SafeData): for i, line in enumerate(lines): lines[i] = ("%0" + width + "d. %s") % (i + 1, line) @@ -234,7 +235,7 @@ def slugify(value): and converts spaces to hyphens. """ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') - value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) + value = six.text_type(re.sub('[^\w\s-]', '', value).strip().lower()) return mark_safe(re.sub('[-\s]+', '-', value)) @register.filter(is_safe=True) @@ -249,7 +250,7 @@ def stringformat(value, arg): of Python string formatting """ try: - return ("%" + unicode(arg)) % value + return ("%" + six.text_type(arg)) % value except (ValueError, TypeError): return "" diff --git a/django/test/_doctest.py b/django/test/_doctest.py index 0388714094..75f16e202a 100644 --- a/django/test/_doctest.py +++ b/django/test/_doctest.py @@ -211,7 +211,7 @@ def _normalize_module(module, depth=2): """ if inspect.ismodule(module): return module - elif isinstance(module, (str, unicode)): + elif isinstance(module, six.string_types): return __import__(module, globals(), locals(), ["*"]) elif module is None: return sys.modules[sys._getframe(depth).f_globals['__name__']] diff --git a/django/test/html.py b/django/test/html.py index a44eb72322..8d577d91fd 100644 --- a/django/test/html.py +++ b/django/test/html.py @@ -125,14 +125,14 @@ class Element(object): output += ' %s' % key if self.children: output += '>\n' - output += ''.join(unicode(c) for c in self.children) + output += ''.join(six.text_type(c) for c in self.children) output += '\n' % self.name else: output += ' />' return output def __repr__(self): - return unicode(self) + return six.text_type(self) class RootElement(Element): @@ -140,7 +140,7 @@ class RootElement(Element): super(RootElement, self).__init__(None, ()) def __unicode__(self): - return ''.join(unicode(c) for c in self.children) + return ''.join(six.text_type(c) for c in self.children) class Parser(HTMLParser): diff --git a/django/test/testcases.py b/django/test/testcases.py index 0a0b029796..eb7bd70d12 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -38,6 +38,7 @@ from django.test.utils import (get_warnings_state, restore_warnings_state, from django.test.utils import ContextList from django.utils import unittest as ut2 from django.utils.encoding import smart_str, force_unicode +from django.utils import six from django.utils.unittest.util import safe_repr from django.views.static import serve @@ -421,8 +422,8 @@ class SimpleTestCase(ut2.TestCase): standardMsg = '%s != %s' % ( safe_repr(dom1, True), safe_repr(dom2, True)) diff = ('\n' + '\n'.join(difflib.ndiff( - unicode(dom1).splitlines(), - unicode(dom2).splitlines()))) + six.text_type(dom1).splitlines(), + six.text_type(dom2).splitlines()))) standardMsg = self._truncateMessage(standardMsg, diff) self.fail(self._formatMessage(msg, standardMsg)) diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index d410bce63e..c9a6138aed 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -21,6 +21,7 @@ from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS from django.utils.tzinfo import LocalTimezone from django.utils.translation import ugettext as _ from django.utils.encoding import force_unicode +from django.utils import six from django.utils.timezone import is_aware, is_naive re_formatchars = re.compile(r'(?', '>').replace('"', '"').replace("'", ''')) -escape = allow_lazy(escape, unicode) +escape = allow_lazy(escape, six.text_type) _base_js_escapes = ( ('\\', '\\u005C'), @@ -61,7 +62,7 @@ def escapejs(value): for bad, good in _js_escapes: value = mark_safe(force_unicode(value).replace(bad, good)) return value -escapejs = allow_lazy(escapejs, unicode) +escapejs = allow_lazy(escapejs, six.text_type) def conditional_escape(text): """ @@ -112,7 +113,7 @@ def linebreaks(value, autoescape=False): else: paras = ['

      %s

      ' % p.replace('\n', '
      ') for p in paras] return '\n\n'.join(paras) -linebreaks = allow_lazy(linebreaks, unicode) +linebreaks = allow_lazy(linebreaks, six.text_type) def strip_tags(value): """Returns the given HTML with all tags stripped.""" @@ -122,17 +123,17 @@ strip_tags = allow_lazy(strip_tags) def strip_spaces_between_tags(value): """Returns the given HTML with spaces between tags removed.""" return re.sub(r'>\s+<', '><', force_unicode(value)) -strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode) +strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, six.text_type) def strip_entities(value): """Returns the given HTML with all entities (&something;) stripped.""" return re.sub(r'&(?:\w+|#\d+);', '', force_unicode(value)) -strip_entities = allow_lazy(strip_entities, unicode) +strip_entities = allow_lazy(strip_entities, six.text_type) def fix_ampersands(value): """Returns the given HTML with all unencoded ampersands encoded correctly.""" return unencoded_ampersands_re.sub('&', force_unicode(value)) -fix_ampersands = allow_lazy(fix_ampersands, unicode) +fix_ampersands = allow_lazy(fix_ampersands, six.text_type) def smart_urlquote(url): "Quotes a URL if it isn't already quoted." @@ -226,7 +227,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): elif autoescape: words[i] = escape(word) return ''.join(words) -urlize = allow_lazy(urlize, unicode) +urlize = allow_lazy(urlize, six.text_type) def clean_html(text): """ @@ -260,4 +261,4 @@ def clean_html(text): # of the text. text = trailing_empty_content_re.sub('', text) return text -clean_html = allow_lazy(clean_html, unicode) +clean_html = allow_lazy(clean_html, six.text_type) diff --git a/django/utils/http.py b/django/utils/http.py index 87db284416..ec94d62903 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -9,6 +9,7 @@ from email.utils import formatdate from django.utils.datastructures import MultiValueDict from django.utils.encoding import smart_str, force_unicode from django.utils.functional import allow_lazy +from django.utils import six ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"') @@ -31,7 +32,7 @@ def urlquote(url, safe='/'): without double-quoting occurring. """ return force_unicode(urllib.quote(smart_str(url), smart_str(safe))) -urlquote = allow_lazy(urlquote, unicode) +urlquote = allow_lazy(urlquote, six.text_type) def urlquote_plus(url, safe=''): """ @@ -41,7 +42,7 @@ def urlquote_plus(url, safe=''): iri_to_uri() call without double-quoting occurring. """ return force_unicode(urllib.quote_plus(smart_str(url), smart_str(safe))) -urlquote_plus = allow_lazy(urlquote_plus, unicode) +urlquote_plus = allow_lazy(urlquote_plus, six.text_type) def urlunquote(quoted_url): """ @@ -49,7 +50,7 @@ def urlunquote(quoted_url): the result of django.utils.http.urlquote(). """ return force_unicode(urllib.unquote(smart_str(quoted_url))) -urlunquote = allow_lazy(urlunquote, unicode) +urlunquote = allow_lazy(urlunquote, six.text_type) def urlunquote_plus(quoted_url): """ @@ -57,7 +58,7 @@ def urlunquote_plus(quoted_url): the result of django.utils.http.urlquote_plus(). """ return force_unicode(urllib.unquote_plus(smart_str(quoted_url))) -urlunquote_plus = allow_lazy(urlunquote_plus, unicode) +urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type) def urlencode(query, doseq=0): """ diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index 924d06511b..d51b230823 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -1,5 +1,6 @@ from django.conf import settings from django.utils.safestring import mark_safe +from django.utils import six def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', @@ -18,13 +19,13 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', use_grouping = use_grouping and grouping > 0 # Make the common case fast if isinstance(number, int) and not use_grouping and not decimal_pos: - return mark_safe(unicode(number)) + return mark_safe(six.text_type(number)) # sign if float(number) < 0: sign = '-' else: sign = '' - str_number = unicode(number) + str_number = six.text_type(number) if str_number[0] == '-': str_number = str_number[1:] # decimal part diff --git a/django/utils/safestring.py b/django/utils/safestring.py index 2e31c23676..1599fc2a66 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -5,17 +5,18 @@ 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. """ from django.utils.functional import curry, Promise +from django.utils import six class EscapeData(object): pass -class EscapeString(str, EscapeData): +class EscapeString(bytes, EscapeData): """ A string that should be HTML-escaped when output. """ pass -class EscapeUnicode(unicode, EscapeData): +class EscapeUnicode(six.text_type, EscapeData): """ A unicode object that should be HTML-escaped when output. """ @@ -24,7 +25,7 @@ class EscapeUnicode(unicode, EscapeData): class SafeData(object): pass -class SafeString(str, SafeData): +class SafeString(bytes, SafeData): """ A string subclass that has been specifically marked as "safe" (requires no further escaping) for HTML output purposes. @@ -40,7 +41,7 @@ class SafeString(str, SafeData): elif isinstance(rhs, SafeString): return SafeString(t) return t - + def _proxy_method(self, *args, **kwargs): """ Wrap a call to a normal unicode method up so that we return safe @@ -49,14 +50,14 @@ class SafeString(str, SafeData): """ method = kwargs.pop('method') data = method(self, *args, **kwargs) - if isinstance(data, str): + if isinstance(data, bytes): return SafeString(data) else: return SafeUnicode(data) - decode = curry(_proxy_method, method = str.decode) + decode = curry(_proxy_method, method=bytes.decode) -class SafeUnicode(unicode, SafeData): +class SafeUnicode(six.text_type, SafeData): """ A unicode subclass that has been specifically marked as "safe" for HTML output purposes. @@ -70,7 +71,7 @@ class SafeUnicode(unicode, SafeData): if isinstance(rhs, SafeData): return SafeUnicode(t) return t - + def _proxy_method(self, *args, **kwargs): """ Wrap a call to a normal unicode method up so that we return safe @@ -79,12 +80,12 @@ class SafeUnicode(unicode, SafeData): """ method = kwargs.pop('method') data = method(self, *args, **kwargs) - if isinstance(data, str): + if isinstance(data, bytes): return SafeString(data) else: return SafeUnicode(data) - encode = curry(_proxy_method, method = unicode.encode) + encode = curry(_proxy_method, method=six.text_type.encode) def mark_safe(s): """ @@ -95,11 +96,11 @@ def mark_safe(s): """ if isinstance(s, SafeData): return s - if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str): + if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str): return SafeString(s) - if isinstance(s, (unicode, Promise)): + if isinstance(s, (six.text_type, Promise)): return SafeUnicode(s) - return SafeString(str(s)) + return SafeString(bytes(s)) def mark_for_escaping(s): """ @@ -111,9 +112,9 @@ def mark_for_escaping(s): """ if isinstance(s, (SafeData, EscapeData)): return s - if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str): + if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str): return EscapeString(s) - if isinstance(s, (unicode, Promise)): + if isinstance(s, (six.text_type, Promise)): return EscapeUnicode(s) - return EscapeString(str(s)) + return EscapeString(bytes(s)) diff --git a/django/utils/text.py b/django/utils/text.py index 2546f770b5..9f773ad41b 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -9,11 +9,12 @@ from io import BytesIO from django.utils.encoding import force_unicode from django.utils.functional import allow_lazy, SimpleLazyObject +from django.utils import six from django.utils.translation import ugettext_lazy, ugettext as _, pgettext # Capitalizes the first letter of a string. capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:] -capfirst = allow_lazy(capfirst, unicode) +capfirst = allow_lazy(capfirst, six.text_type) # Set up regular expressions re_words = re.compile(r'&.*?;|<.*?>|(\w[\w-]*)', re.U|re.S) @@ -46,7 +47,7 @@ def wrap(text, width): pos = len(lines[-1]) yield word return ''.join(_generator()) -wrap = allow_lazy(wrap, unicode) +wrap = allow_lazy(wrap, six.text_type) class Truncator(SimpleLazyObject): @@ -207,14 +208,14 @@ def truncate_words(s, num, end_text='...'): 'in django.utils.text instead.', category=DeprecationWarning) truncate = end_text and ' %s' % end_text or '' return Truncator(s).words(num, truncate=truncate) -truncate_words = allow_lazy(truncate_words, unicode) +truncate_words = allow_lazy(truncate_words, six.text_type) def truncate_html_words(s, num, end_text='...'): warnings.warn('This function has been deprecated. Use the Truncator class ' 'in django.utils.text instead.', category=DeprecationWarning) truncate = end_text and ' %s' % end_text or '' return Truncator(s).words(num, truncate=truncate, html=True) -truncate_html_words = allow_lazy(truncate_html_words, unicode) +truncate_html_words = allow_lazy(truncate_html_words, six.text_type) def get_valid_filename(s): """ @@ -227,7 +228,7 @@ def get_valid_filename(s): """ s = force_unicode(s).strip().replace(' ', '_') return re.sub(r'(?u)[^-\w.]', '', s) -get_valid_filename = allow_lazy(get_valid_filename, unicode) +get_valid_filename = allow_lazy(get_valid_filename, six.text_type) def get_text_list(list_, last_word=ugettext_lazy('or')): """ @@ -248,11 +249,11 @@ def get_text_list(list_, last_word=ugettext_lazy('or')): # Translators: This string is used as a separator between list elements _(', ').join([force_unicode(i) for i in list_][:-1]), force_unicode(last_word), force_unicode(list_[-1])) -get_text_list = allow_lazy(get_text_list, unicode) +get_text_list = allow_lazy(get_text_list, six.text_type) def normalize_newlines(text): return force_unicode(re.sub(r'\r\n|\r|\n', '\n', text)) -normalize_newlines = allow_lazy(normalize_newlines, unicode) +normalize_newlines = allow_lazy(normalize_newlines, six.text_type) def recapitalize(text): "Recapitalizes text, placing caps after end-of-sentence punctuation." @@ -288,9 +289,9 @@ def javascript_quote(s, quote_double_quotes=False): def fix(match): return b"\u%04x" % ord(match.group(1)) - if type(s) == str: + if type(s) == bytes: s = s.decode('utf-8') - elif type(s) != unicode: + elif type(s) != six.text_type: raise TypeError(s) s = s.replace('\\', '\\\\') s = s.replace('\r', '\\r') @@ -300,7 +301,7 @@ def javascript_quote(s, quote_double_quotes=False): if quote_double_quotes: s = s.replace('"', '"') return str(ustring_re.sub(fix, s)) -javascript_quote = allow_lazy(javascript_quote, unicode) +javascript_quote = allow_lazy(javascript_quote, six.text_type) # Expression to match some_token and some_token="with spaces" (and similarly # for single-quoted strings). @@ -332,7 +333,7 @@ def smart_split(text): text = force_unicode(text) for bit in smart_split_re.finditer(text): yield bit.group(0) -smart_split = allow_lazy(smart_split, unicode) +smart_split = allow_lazy(smart_split, six.text_type) def _replace_entity(match): text = match.group(1) @@ -356,7 +357,7 @@ _entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));") def unescape_entities(text): return _entity_re.sub(_replace_entity, text) -unescape_entities = allow_lazy(unescape_entities, unicode) +unescape_entities = allow_lazy(unescape_entities, six.text_type) def unescape_string_literal(s): r""" diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index 0f1f28e5c4..d31a7aebf1 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals from django.utils.encoding import force_unicode from django.utils.functional import lazy +from django.utils import six __all__ = [ @@ -78,12 +79,12 @@ def pgettext(context, message): def npgettext(context, singular, plural, number): return _trans.npgettext(context, singular, plural, number) -ngettext_lazy = lazy(ngettext, str) -gettext_lazy = lazy(gettext, str) -ungettext_lazy = lazy(ungettext, unicode) -ugettext_lazy = lazy(ugettext, unicode) -pgettext_lazy = lazy(pgettext, unicode) -npgettext_lazy = lazy(npgettext, unicode) +ngettext_lazy = lazy(ngettext, bytes) +gettext_lazy = lazy(gettext, bytes) +ungettext_lazy = lazy(ungettext, six.text_type) +ugettext_lazy = lazy(ugettext, six.text_type) +pgettext_lazy = lazy(pgettext, six.text_type) +npgettext_lazy = lazy(npgettext, six.text_type) def activate(language): return _trans.activate(language) @@ -139,7 +140,7 @@ def _string_concat(*strings): constructed from multiple parts. """ return ''.join([force_unicode(s) for s in strings]) -string_concat = lazy(_string_concat, unicode) +string_concat = lazy(_string_concat, six.text_type) def get_language_info(lang_code): from django.conf.locale import LANG_INFO diff --git a/django/views/debug.py b/django/views/debug.py index 65226b5ca7..8e81b8239b 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -362,7 +362,7 @@ class ExceptionReporter(object): if match: encoding = match.group(1) break - source = [unicode(sline, encoding, 'replace') for sline in source] + source = [six.text_type(sline, encoding, 'replace') for sline in source] lower_bound = max(0, lineno - context_lines) upper_bound = lineno + context_lines diff --git a/tests/modeltests/custom_columns/tests.py b/tests/modeltests/custom_columns/tests.py index c1bb6f0a01..a2e5323a75 100644 --- a/tests/modeltests/custom_columns/tests.py +++ b/tests/modeltests/custom_columns/tests.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from django.core.exceptions import FieldError from django.test import TestCase +from django.utils import six from .models import Author, Article @@ -22,13 +23,13 @@ class CustomColumnsTests(TestCase): Author.objects.all(), [ "Peter Jones", "John Smith", ], - unicode + six.text_type ) self.assertQuerysetEqual( Author.objects.filter(first_name__exact="John"), [ "John Smith", ], - unicode + six.text_type ) self.assertEqual( Author.objects.get(first_name__exact="John"), @@ -55,7 +56,7 @@ class CustomColumnsTests(TestCase): "Peter Jones", "John Smith", ], - unicode + six.text_type ) # Get the articles for an author self.assertQuerysetEqual( @@ -69,5 +70,5 @@ class CustomColumnsTests(TestCase): art.authors.filter(last_name='Jones'), [ "Peter Jones" ], - unicode + six.text_type ) diff --git a/tests/modeltests/custom_managers/tests.py b/tests/modeltests/custom_managers/tests.py index bdba3d0733..294920de2b 100644 --- a/tests/modeltests/custom_managers/tests.py +++ b/tests/modeltests/custom_managers/tests.py @@ -1,6 +1,7 @@ from __future__ import absolute_import from django.test import TestCase +from django.utils import six from .models import Person, Book, Car, PersonManager, PublishedBookManager @@ -14,7 +15,7 @@ class CustomManagerTests(TestCase): Person.objects.get_fun_people(), [ "Bugs Bunny" ], - unicode + six.text_type ) # The RelatedManager used on the 'books' descriptor extends the default # manager diff --git a/tests/modeltests/custom_pk/fields.py b/tests/modeltests/custom_pk/fields.py index 40551a363c..68fb9dcd16 100644 --- a/tests/modeltests/custom_pk/fields.py +++ b/tests/modeltests/custom_pk/fields.py @@ -2,6 +2,7 @@ import random import string from django.db import models +from django.utils import six class MyWrapper(object): @@ -44,12 +45,12 @@ class MyAutoField(models.CharField): if not value: return if isinstance(value, MyWrapper): - return unicode(value) + return six.text_type(value) return value def get_db_prep_value(self, value, connection, prepared=False): if not value: return if isinstance(value, MyWrapper): - return unicode(value) + return six.text_type(value) return value diff --git a/tests/modeltests/custom_pk/tests.py b/tests/modeltests/custom_pk/tests.py index b473dcab59..3f562f0bed 100644 --- a/tests/modeltests/custom_pk/tests.py +++ b/tests/modeltests/custom_pk/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals from django.db import transaction, IntegrityError from django.test import TestCase, skipIfDBFeature +from django.utils import six from .models import Employee, Business, Bar, Foo @@ -16,7 +17,7 @@ class CustomPKTests(TestCase): Employee.objects.all(), [ "Dan Jones", ], - unicode + six.text_type ) fran = Employee.objects.create( @@ -27,7 +28,7 @@ class CustomPKTests(TestCase): "Fran Bones", "Dan Jones", ], - unicode + six.text_type ) self.assertEqual(Employee.objects.get(pk=123), dan) @@ -45,7 +46,7 @@ class CustomPKTests(TestCase): "Fran Bones", "Dan Jones", ], - unicode + six.text_type ) # The primary key can be accessed via the pk property on the model. e = Employee.objects.get(pk=123) @@ -63,7 +64,7 @@ class CustomPKTests(TestCase): "Dan Jones", "Fran Jones", ], - unicode + six.text_type ) emps = Employee.objects.in_bulk([123, 456]) @@ -76,7 +77,7 @@ class CustomPKTests(TestCase): "Dan Jones", "Fran Jones", ], - unicode + six.text_type ) self.assertQuerysetEqual( fran.business_set.all(), [ @@ -108,14 +109,14 @@ class CustomPKTests(TestCase): "Dan Jones", "Fran Jones", ], - unicode, + six.text_type, ) self.assertQuerysetEqual( Employee.objects.filter(business__pk="Sears"), [ "Dan Jones", "Fran Jones", ], - unicode, + six.text_type, ) self.assertQuerysetEqual( diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py index c4e2707109..99eb07e370 100644 --- a/tests/modeltests/expressions/tests.py +++ b/tests/modeltests/expressions/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals from django.core.exceptions import FieldError from django.db.models import F from django.test import TestCase +from django.utils import six from .models import Company, Employee @@ -156,7 +157,7 @@ class ExpressionsTests(TestCase): "Frank Meyer", "Max Mustermann", ], - lambda c: unicode(c.point_of_contact), + lambda c: six.text_type(c.point_of_contact), ) c = Company.objects.all()[0] diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/modeltests/field_subclassing/fields.py index 553b031de3..a21085de9d 100644 --- a/tests/modeltests/field_subclassing/fields.py +++ b/tests/modeltests/field_subclassing/fields.py @@ -19,7 +19,7 @@ class Small(object): return '%s%s' % (force_unicode(self.first), force_unicode(self.second)) def __str__(self): - return unicode(self).encode('utf-8') + return six.text_type(self).encode('utf-8') class SmallField(models.Field): """ @@ -42,7 +42,7 @@ class SmallField(models.Field): return Small(value[0], value[1]) def get_db_prep_save(self, value, connection): - return unicode(value) + return six.text_type(value) def get_prep_lookup(self, lookup_type, value): if lookup_type == 'exact': diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 3e5d61538a..b685750347 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -7,6 +7,7 @@ This demonstrates features of the database API. from __future__ import unicode_literals from django.db import models +from django.utils import six class Author(models.Model): @@ -35,7 +36,7 @@ class Season(models.Model): gt = models.IntegerField(null=True, blank=True) def __unicode__(self): - return unicode(self.year) + return six.text_type(self.year) class Game(models.Model): season = models.ForeignKey(Season, related_name='games') diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py index 6c1f277811..92ed3fbcd9 100644 --- a/tests/modeltests/m2m_and_m2o/models.py +++ b/tests/modeltests/m2m_and_m2o/models.py @@ -6,6 +6,7 @@ Make sure to set ``related_name`` if you use relationships to the same table. from __future__ import unicode_literals from django.db import models +from django.utils import six class User(models.Model): @@ -17,7 +18,7 @@ class Issue(models.Model): client = models.ForeignKey(User, related_name='test_issue_client') def __unicode__(self): - return unicode(self.num) + return six.text_type(self.num) class Meta: ordering = ('num',) diff --git a/tests/modeltests/m2m_intermediary/tests.py b/tests/modeltests/m2m_intermediary/tests.py index cdc762246a..f261f23546 100644 --- a/tests/modeltests/m2m_intermediary/tests.py +++ b/tests/modeltests/m2m_intermediary/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from datetime import datetime from django.test import TestCase +from django.utils import six from .models import Reporter, Article, Writer @@ -24,7 +25,7 @@ class M2MIntermediaryTests(TestCase): ("John Smith", "Main writer"), ("Jane Doe", "Contributor"), ], - lambda w: (unicode(w.reporter), w.position) + lambda w: (six.text_type(w.reporter), w.position) ) self.assertEqual(w1.reporter, r1) self.assertEqual(w2.reporter, r2) @@ -36,5 +37,5 @@ class M2MIntermediaryTests(TestCase): r1.writer_set.all(), [ ("John Smith", "Main writer") ], - lambda w: (unicode(w.reporter), w.position) + lambda w: (six.text_type(w.reporter), w.position) ) diff --git a/tests/modeltests/many_to_one/tests.py b/tests/modeltests/many_to_one/tests.py index fd849df051..20bd1be0df 100644 --- a/tests/modeltests/many_to_one/tests.py +++ b/tests/modeltests/many_to_one/tests.py @@ -5,6 +5,7 @@ from datetime import datetime from django.core.exceptions import MultipleObjectsReturned, FieldError from django.test import TestCase +from django.utils import six from django.utils.translation import ugettext_lazy from .models import Article, Reporter @@ -421,7 +422,7 @@ class ManyToOneTests(TestCase): lazy = ugettext_lazy('test') reporter.article_set.create(headline=lazy, pub_date=datetime(2011, 6, 10)) - notlazy = unicode(lazy) + notlazy = six.text_type(lazy) article = reporter.article_set.get() self.assertEqual(article.headline, notlazy) diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index a4ce86f184..8942b21f73 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -13,6 +13,7 @@ import tempfile from django.core.files.storage import FileSystemStorage from django.db import models +from django.utils import six temp_storage_dir = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR']) @@ -226,7 +227,7 @@ class BigInt(models.Model): biggie = models.BigIntegerField() def __unicode__(self): - return unicode(self.biggie) + return six.text_type(self.biggie) class MarkupField(models.CharField): def __init__(self, *args, **kwargs): diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py index 281316a28e..fc37a25872 100644 --- a/tests/modeltests/model_forms/tests.py +++ b/tests/modeltests/model_forms/tests.py @@ -11,6 +11,7 @@ from django.db import connection from django.forms.models import model_to_dict from django.utils.unittest import skipUnless from django.test import TestCase +from django.utils import six from .models import (Article, ArticleStatus, BetterWriter, BigInt, Book, Category, CommaSeparatedInteger, CustomFieldForExclusionModel, DerivedBook, @@ -653,7 +654,7 @@ class OldFormForXTests(TestCase): # ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any # fields with the 'choices' attribute are represented by a ChoiceField. f = ArticleForm(auto_id=False) - self.assertHTMLEqual(unicode(f), '''Headline: + self.assertHTMLEqual(six.text_type(f), '''Headline: Slug: Pub date: Writer: + self.assertHTMLEqual(six.text_type(f), '''Headline: Pub date:''') # When the ModelForm is passed an instance, that instance's current values are # inserted as 'initial' data in each Field. w = Writer.objects.get(name='Mike Royko') f = RoykoForm(auto_id=False, instance=w) - self.assertHTMLEqual(unicode(f), '''Name:
      Use both first and last names.''') + self.assertHTMLEqual(six.text_type(f), '''Name:
      Use both first and last names.''') art = Article( headline='Test article', @@ -725,7 +726,7 @@ class OldFormForXTests(TestCase): 'headline': 'Test headline', 'slug': 'test-headline', 'pub_date': '1984-02-06', - 'writer': unicode(w_royko.pk), + 'writer': six.text_type(w_royko.pk), 'article': 'Hello.' }, instance=art) self.assertEqual(f.errors, {}) @@ -808,9 +809,9 @@ class OldFormForXTests(TestCase): 'headline': 'New headline', 'slug': 'new-headline', 'pub_date': '1988-01-04', - 'writer': unicode(w_royko.pk), + 'writer': six.text_type(w_royko.pk), 'article': 'Hello.', - 'categories': [unicode(c1.id), unicode(c2.id)] + 'categories': [six.text_type(c1.id), six.text_type(c2.id)] }, instance=new_art) new_art = f.save() self.assertEqual(new_art.id == art_id_1, True) @@ -820,7 +821,7 @@ class OldFormForXTests(TestCase): # Now, submit form data with no categories. This deletes the existing categories. f = TestArticleForm({'headline': 'New headline', 'slug': 'new-headline', 'pub_date': '1988-01-04', - 'writer': unicode(w_royko.pk), 'article': 'Hello.'}, instance=new_art) + 'writer': six.text_type(w_royko.pk), 'article': 'Hello.'}, instance=new_art) new_art = f.save() self.assertEqual(new_art.id == art_id_1, True) new_art = Article.objects.get(id=art_id_1) @@ -828,7 +829,7 @@ class OldFormForXTests(TestCase): # 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', - 'writer': unicode(w_royko.pk), 'article': 'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) + 'writer': six.text_type(w_royko.pk), 'article': 'Test.', 'categories': [six.text_type(c1.id), six.text_type(c2.id)]}) new_art = f.save() art_id_2 = new_art.id self.assertEqual(art_id_2 not in (None, art_id_1), True) @@ -837,7 +838,7 @@ class OldFormForXTests(TestCase): # 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', - 'writer': unicode(w_royko.pk), 'article': 'Test.'}) + 'writer': six.text_type(w_royko.pk), 'article': 'Test.'}) new_art = f.save() art_id_3 = new_art.id self.assertEqual(art_id_3 not in (None, art_id_1, art_id_2), True) @@ -847,7 +848,7 @@ class OldFormForXTests(TestCase): # 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. f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01', - 'writer': unicode(w_royko.pk), 'article': 'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) + 'writer': six.text_type(w_royko.pk), 'article': 'Test.', 'categories': [six.text_type(c1.id), six.text_type(c2.id)]}) new_art = f.save(commit=False) # Manually save the instance @@ -1091,12 +1092,12 @@ class OldFormForXTests(TestCase):

      ''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) data = { - 'writer': unicode(w_woodward.pk), + 'writer': six.text_type(w_woodward.pk), 'age': '65', } form = WriterProfileForm(data) instance = form.save() - self.assertEqual(unicode(instance), 'Bob Woodward is 65') + self.assertEqual(six.text_type(instance), 'Bob Woodward is 65') form = WriterProfileForm(instance=instance) self.assertHTMLEqual(form.as_p(), '''

      + self.assertHTMLEqual(six.text_type(form['parent']), ''' + self.assertHTMLEqual(six.text_type(CategoryForm()), ''' ''') # to_field_name should also work on ModelMultipleChoiceField ################## @@ -1481,5 +1482,5 @@ class OldFormForXTests(TestCase): def test_model_field_that_returns_none_to_exclude_itself_with_explicit_fields(self): self.assertEqual(CustomFieldForExclusionForm.base_fields.keys(), ['name']) - self.assertHTMLEqual(unicode(CustomFieldForExclusionForm()), + self.assertHTMLEqual(six.text_type(CustomFieldForExclusionForm()), '''''') diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index 055aa8dff1..a01a2da0fa 100644 --- a/tests/modeltests/model_formsets/models.py +++ b/tests/modeltests/model_formsets/models.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import datetime from django.db import models +from django.utils import six class Author(models.Model): @@ -149,7 +150,7 @@ class Revision(models.Model): unique_together = (("repository", "revision"),) def __unicode__(self): - return "%s (%s)" % (self.revision, unicode(self.repository)) + return "%s (%s)" % (self.revision, six.text_type(self.repository)) # models for testing callable defaults (see bug #7975). If you define a model # with a callable default value, you cannot rely on the initial value in a diff --git a/tests/modeltests/model_formsets/tests.py b/tests/modeltests/model_formsets/tests.py index 309dd3aaa2..e28560b237 100644 --- a/tests/modeltests/model_formsets/tests.py +++ b/tests/modeltests/model_formsets/tests.py @@ -10,6 +10,7 @@ from django.db import models from django.forms.models import (_get_foreign_key, inlineformset_factory, modelformset_factory) from django.test import TestCase, skipUnlessDBFeature +from django.utils import six from .models import (Author, BetterAuthor, Book, BookWithCustomPK, BookWithOptionalAltEditor, AlternateBook, AuthorMeeting, CustomPrimaryKey, @@ -72,7 +73,7 @@ class DeletionTests(TestCase): 'form-TOTAL_FORMS': '1', 'form-INITIAL_FORMS': '1', 'form-MAX_NUM_FORMS': '0', - 'form-0-id': unicode(poet.id), + 'form-0-id': six.text_type(poet.id), 'form-0-name': 'x' * 1000, } formset = PoetFormSet(data, queryset=Poet.objects.all()) @@ -772,7 +773,7 @@ class ModelFormsetTest(TestCase): 'owner_set-TOTAL_FORMS': '3', 'owner_set-INITIAL_FORMS': '1', 'owner_set-MAX_NUM_FORMS': '', - 'owner_set-0-auto_id': unicode(owner1.auto_id), + 'owner_set-0-auto_id': six.text_type(owner1.auto_id), 'owner_set-0-name': 'Joe Perry', 'owner_set-1-auto_id': '', 'owner_set-1-name': 'Jack Berry', @@ -835,7 +836,7 @@ class ModelFormsetTest(TestCase): 'ownerprofile-TOTAL_FORMS': '1', 'ownerprofile-INITIAL_FORMS': '1', 'ownerprofile-MAX_NUM_FORMS': '1', - 'ownerprofile-0-owner': unicode(owner1.auto_id), + 'ownerprofile-0-owner': six.text_type(owner1.auto_id), 'ownerprofile-0-age': '55', } formset = FormSet(data, instance=owner1) @@ -993,8 +994,8 @@ class ModelFormsetTest(TestCase): 'membership_set-TOTAL_FORMS': '1', 'membership_set-INITIAL_FORMS': '0', 'membership_set-MAX_NUM_FORMS': '', - 'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), - 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), + 'membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')), + 'initial-membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')), 'membership_set-0-karma': '', } formset = FormSet(data, instance=person) @@ -1007,8 +1008,8 @@ class ModelFormsetTest(TestCase): 'membership_set-TOTAL_FORMS': '1', 'membership_set-INITIAL_FORMS': '0', 'membership_set-MAX_NUM_FORMS': '', - 'membership_set-0-date_joined': unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')), - 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), + 'membership_set-0-date_joined': six.text_type(one_day_later.strftime('%Y-%m-%d %H:%M:%S')), + 'initial-membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')), 'membership_set-0-karma': '', } formset = FormSet(filled_data, instance=person) @@ -1029,9 +1030,9 @@ class ModelFormsetTest(TestCase): 'membership_set-TOTAL_FORMS': '1', 'membership_set-INITIAL_FORMS': '0', 'membership_set-MAX_NUM_FORMS': '', - 'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')), - 'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')), - 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), + 'membership_set-0-date_joined_0': six.text_type(now.strftime('%Y-%m-%d')), + 'membership_set-0-date_joined_1': six.text_type(now.strftime('%H:%M:%S')), + 'initial-membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')), 'membership_set-0-karma': '', } formset = FormSet(data, instance=person) diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py index 695d3f836c..16d2242fbe 100644 --- a/tests/modeltests/model_inheritance/tests.py +++ b/tests/modeltests/model_inheritance/tests.py @@ -4,6 +4,7 @@ from operator import attrgetter from django.core.exceptions import FieldError from django.test import TestCase +from django.utils import six from .models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel) @@ -21,8 +22,8 @@ class ModelInheritanceTests(TestCase): s = Student.objects.create(name="Pebbles", age=5, school_class="1B") - self.assertEqual(unicode(w1), "Worker Fred") - self.assertEqual(unicode(s), "Student Pebbles") + self.assertEqual(six.text_type(w1), "Worker Fred") + self.assertEqual(six.text_type(s), "Student Pebbles") # The children inherit the Meta class of their parents (if they don't # specify their own). diff --git a/tests/modeltests/order_with_respect_to/models.py b/tests/modeltests/order_with_respect_to/models.py index 59f01d4cd1..a4e20c2fe0 100644 --- a/tests/modeltests/order_with_respect_to/models.py +++ b/tests/modeltests/order_with_respect_to/models.py @@ -3,6 +3,7 @@ Tests for the order_with_respect_to Meta attribute. """ from django.db import models +from django.utils import six class Question(models.Model): @@ -16,7 +17,7 @@ class Answer(models.Model): order_with_respect_to = 'question' def __unicode__(self): - return unicode(self.text) + return six.text_type(self.text) class Post(models.Model): title = models.CharField(max_length=200) diff --git a/tests/modeltests/pagination/tests.py b/tests/modeltests/pagination/tests.py index 4d5d8680e4..cba8825ec4 100644 --- a/tests/modeltests/pagination/tests.py +++ b/tests/modeltests/pagination/tests.py @@ -4,6 +4,7 @@ from datetime import datetime from django.core.paginator import Paginator, InvalidPage, EmptyPage from django.test import TestCase +from django.utils import six from .models import Article @@ -32,7 +33,7 @@ class PaginationTests(TestCase): def test_first_page(self): paginator = Paginator(Article.objects.all(), 5) p = paginator.page(1) - self.assertEqual("", unicode(p)) + self.assertEqual("", six.text_type(p)) self.assertQuerysetEqual(p.object_list, [ "", "", @@ -52,7 +53,7 @@ class PaginationTests(TestCase): def test_last_page(self): paginator = Paginator(Article.objects.all(), 5) p = paginator.page(2) - self.assertEqual("", unicode(p)) + self.assertEqual("", six.text_type(p)) self.assertQuerysetEqual(p.object_list, [ "", "", @@ -109,7 +110,7 @@ class PaginationTests(TestCase): self.assertEqual(3, paginator.num_pages) self.assertEqual([1, 2, 3], paginator.page_range) p = paginator.page(2) - self.assertEqual("", unicode(p)) + self.assertEqual("", six.text_type(p)) self.assertEqual([4, 5, 6], p.object_list) self.assertTrue(p.has_next()) self.assertTrue(p.has_previous()) diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py index 43f195d357..8ae0a1e298 100644 --- a/tests/modeltests/prefetch_related/tests.py +++ b/tests/modeltests/prefetch_related/tests.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import connection from django.test import TestCase from django.test.utils import override_settings +from django.utils import six from .models import (Author, Book, Reader, Qualification, Teacher, Department, TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge, @@ -120,7 +121,7 @@ class PrefetchRelatedTests(TestCase): """ with self.assertNumQueries(3): qs = Author.objects.prefetch_related('books__read_by') - lists = [[[unicode(r) for r in b.read_by.all()] + lists = [[[six.text_type(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs] self.assertEqual(lists, @@ -134,7 +135,7 @@ class PrefetchRelatedTests(TestCase): def test_overriding_prefetch(self): with self.assertNumQueries(3): qs = Author.objects.prefetch_related('books', 'books__read_by') - lists = [[[unicode(r) for r in b.read_by.all()] + lists = [[[six.text_type(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs] self.assertEqual(lists, @@ -146,7 +147,7 @@ class PrefetchRelatedTests(TestCase): ]) with self.assertNumQueries(3): qs = Author.objects.prefetch_related('books__read_by', 'books') - lists = [[[unicode(r) for r in b.read_by.all()] + lists = [[[six.text_type(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs] self.assertEqual(lists, @@ -164,7 +165,7 @@ class PrefetchRelatedTests(TestCase): # Need a double with self.assertNumQueries(3): author = Author.objects.prefetch_related('books__read_by').get(name="Charlotte") - lists = [[unicode(r) for r in b.read_by.all()] + lists = [[six.text_type(r) for r in b.read_by.all()] for b in author.books.all()] self.assertEqual(lists, [["Amy"], ["Belinda"]]) # Poems, Jane Eyre @@ -175,7 +176,7 @@ class PrefetchRelatedTests(TestCase): """ with self.assertNumQueries(2): qs = Author.objects.select_related('first_book').prefetch_related('first_book__read_by') - lists = [[unicode(r) for r in a.first_book.read_by.all()] + lists = [[six.text_type(r) for r in a.first_book.read_by.all()] for a in qs] self.assertEqual(lists, [["Amy"], ["Amy"], @@ -227,7 +228,7 @@ class DefaultManagerTests(TestCase): # qualifications, since this will do one query per teacher. qs = Department.objects.prefetch_related('teachers') depts = "".join(["%s department: %s\n" % - (dept.name, ", ".join(unicode(t) for t in dept.teachers.all())) + (dept.name, ", ".join(six.text_type(t) for t in dept.teachers.all())) for dept in qs]) self.assertEqual(depts, @@ -343,9 +344,9 @@ class MultiTableInheritanceTest(TestCase): def test_foreignkey(self): with self.assertNumQueries(2): qs = AuthorWithAge.objects.prefetch_related('addresses') - addresses = [[unicode(address) for address in obj.addresses.all()] + addresses = [[six.text_type(address) for address in obj.addresses.all()] for obj in qs] - self.assertEqual(addresses, [[unicode(self.authorAddress)], [], []]) + self.assertEqual(addresses, [[six.text_type(self.authorAddress)], [], []]) def test_foreignkey_to_inherited(self): with self.assertNumQueries(2): @@ -356,19 +357,19 @@ class MultiTableInheritanceTest(TestCase): def test_m2m_to_inheriting_model(self): qs = AuthorWithAge.objects.prefetch_related('books_with_year') with self.assertNumQueries(2): - lst = [[unicode(book) for book in author.books_with_year.all()] + lst = [[six.text_type(book) for book in author.books_with_year.all()] for author in qs] qs = AuthorWithAge.objects.all() - lst2 = [[unicode(book) for book in author.books_with_year.all()] + lst2 = [[six.text_type(book) for book in author.books_with_year.all()] for author in qs] self.assertEqual(lst, lst2) qs = BookWithYear.objects.prefetch_related('aged_authors') with self.assertNumQueries(2): - lst = [[unicode(author) for author in book.aged_authors.all()] + lst = [[six.text_type(author) for author in book.aged_authors.all()] for book in qs] qs = BookWithYear.objects.all() - lst2 = [[unicode(author) for author in book.aged_authors.all()] + lst2 = [[six.text_type(author) for author in book.aged_authors.all()] for book in qs] self.assertEqual(lst, lst2) @@ -410,23 +411,23 @@ class ForeignKeyToFieldTest(TestCase): def test_foreignkey(self): with self.assertNumQueries(2): qs = Author.objects.prefetch_related('addresses') - addresses = [[unicode(address) for address in obj.addresses.all()] + addresses = [[six.text_type(address) for address in obj.addresses.all()] for obj in qs] - self.assertEqual(addresses, [[unicode(self.authorAddress)], [], []]) + self.assertEqual(addresses, [[six.text_type(self.authorAddress)], [], []]) def test_m2m(self): with self.assertNumQueries(3): qs = Author.objects.all().prefetch_related('favorite_authors', 'favors_me') favorites = [( - [unicode(i_like) for i_like in author.favorite_authors.all()], - [unicode(likes_me) for likes_me in author.favors_me.all()] + [six.text_type(i_like) for i_like in author.favorite_authors.all()], + [six.text_type(likes_me) for likes_me in author.favors_me.all()] ) for author in qs] self.assertEqual( favorites, [ - ([unicode(self.author2)],[unicode(self.author3)]), - ([unicode(self.author3)],[unicode(self.author1)]), - ([unicode(self.author1)],[unicode(self.author2)]) + ([six.text_type(self.author2)],[six.text_type(self.author3)]), + ([six.text_type(self.author3)],[six.text_type(self.author1)]), + ([six.text_type(self.author1)],[six.text_type(self.author2)]) ] ) diff --git a/tests/modeltests/save_delete_hooks/tests.py b/tests/modeltests/save_delete_hooks/tests.py index 377d9eec03..42e0d4a80e 100644 --- a/tests/modeltests/save_delete_hooks/tests.py +++ b/tests/modeltests/save_delete_hooks/tests.py @@ -1,6 +1,7 @@ from __future__ import absolute_import from django.test import TestCase +from django.utils import six from .models import Person @@ -19,7 +20,7 @@ class SaveDeleteHookTests(TestCase): Person.objects.all(), [ "John Smith", ], - unicode + six.text_type ) p.delete() diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index 3549be1b4f..9da099c027 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -10,6 +10,7 @@ from __future__ import unicode_literals from decimal import Decimal from django.db import models +from django.utils import six class Category(models.Model): @@ -100,7 +101,7 @@ class TeamField(models.CharField): super(TeamField, self).__init__(max_length=100) def get_db_prep_save(self, value, connection): - return unicode(value.title) + return six.text_type(value.title) def to_python(self, value): if isinstance(value, Team): diff --git a/tests/modeltests/serializers/tests.py b/tests/modeltests/serializers/tests.py index 73a3aa3e7a..9177227539 100644 --- a/tests/modeltests/serializers/tests.py +++ b/tests/modeltests/serializers/tests.py @@ -296,7 +296,7 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase): def _comparison_value(value): # The XML serializer handles everything as strings, so comparisons # need to be performed on the stringified value - return unicode(value) + return six.text_type(value) @staticmethod def _validate_output(serial_str): diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py index 29563dc363..58f25c2868 100644 --- a/tests/modeltests/signals/tests.py +++ b/tests/modeltests/signals/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from django.db.models import signals from django.dispatch import receiver from django.test import TestCase +from django.utils import six from .models import Person, Car @@ -144,7 +145,7 @@ class SignalTests(TestCase): Person.objects.all(), [ "James Jones", ], - unicode + six.text_type ) signals.post_delete.disconnect(post_delete_test) diff --git a/tests/modeltests/update/models.py b/tests/modeltests/update/models.py index 92156a5553..b93e4a7aae 100644 --- a/tests/modeltests/update/models.py +++ b/tests/modeltests/update/models.py @@ -4,6 +4,7 @@ updates. """ from django.db import models +from django.utils import six class DataPoint(models.Model): @@ -12,14 +13,14 @@ class DataPoint(models.Model): another_value = models.CharField(max_length=20, blank=True) def __unicode__(self): - return unicode(self.name) + return six.text_type(self.name) class RelatedPoint(models.Model): name = models.CharField(max_length=20) data = models.ForeignKey(DataPoint) def __unicode__(self): - return unicode(self.name) + return six.text_type(self.name) class A(models.Model): diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py index 62166ce174..1ed963aaf2 100644 --- a/tests/regressiontests/admin_changelist/tests.py +++ b/tests/regressiontests/admin_changelist/tests.py @@ -10,6 +10,7 @@ from django.template import Context, Template from django.test import TestCase from django.test.client import RequestFactory from django.utils import formats +from django.utils import six from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin, GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin, @@ -339,7 +340,7 @@ class ChangeListTests(TestCase): event = Event.objects.create(date=datetime.date.today()) response = self.client.get('/admin/admin_changelist/event/') self.assertContains(response, formats.localize(event.date)) - self.assertNotContains(response, unicode(event.date)) + self.assertNotContains(response, six.text_type(event.date)) def test_dynamic_list_display(self): """ @@ -443,9 +444,9 @@ class ChangeListTests(TestCase): request = self._mocked_authenticated_request('/swallow/', superuser) response = model_admin.changelist_view(request) # just want to ensure it doesn't blow up during rendering - self.assertContains(response, unicode(swallow.origin)) - self.assertContains(response, unicode(swallow.load)) - self.assertContains(response, unicode(swallow.speed)) + self.assertContains(response, six.text_type(swallow.origin)) + self.assertContains(response, six.text_type(swallow.load)) + self.assertContains(response, six.text_type(swallow.speed)) def test_deterministic_order_for_unordered_model(self): """ diff --git a/tests/regressiontests/admin_util/models.py b/tests/regressiontests/admin_util/models.py index 0e81df3817..5541097022 100644 --- a/tests/regressiontests/admin_util/models.py +++ b/tests/regressiontests/admin_util/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils import six class Article(models.Model): @@ -22,7 +23,7 @@ class Count(models.Model): parent = models.ForeignKey('self', null=True) def __unicode__(self): - return unicode(self.num) + return six.text_type(self.num) class Event(models.Model): date = models.DateTimeField(auto_now_add=True) diff --git a/tests/regressiontests/admin_util/tests.py b/tests/regressiontests/admin_util/tests.py index ba2be363ca..6b6dad4336 100644 --- a/tests/regressiontests/admin_util/tests.py +++ b/tests/regressiontests/admin_util/tests.py @@ -15,6 +15,7 @@ from django.test import TestCase from django.utils import unittest from django.utils.formats import localize from django.utils.safestring import mark_safe +from django.utils import six from .models import Article, Count, Event, Location @@ -249,17 +250,17 @@ class UtilTests(unittest.TestCase): log_entry.action_flag = admin.models.ADDITION self.assertTrue( - unicode(log_entry).startswith('Added ') + six.text_type(log_entry).startswith('Added ') ) log_entry.action_flag = admin.models.CHANGE self.assertTrue( - unicode(log_entry).startswith('Changed ') + six.text_type(log_entry).startswith('Changed ') ) log_entry.action_flag = admin.models.DELETION self.assertTrue( - unicode(log_entry).startswith('Deleted ') + six.text_type(log_entry).startswith('Deleted ') ) def test_safestring_in_field_label(self): diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 630758a91d..a139016f27 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -30,6 +30,7 @@ from django.utils.cache import get_max_age from django.utils.encoding import iri_to_uri from django.utils.html import escape from django.utils.http import urlencode +from django.utils import six from django.test.utils import override_settings # local test models @@ -2478,7 +2479,7 @@ class AdminCustomQuerysetTest(TestCase): response = self.client.post('/test_admin/admin/admin_views/paper/%s/' % p.pk, post_data, follow=True) self.assertEqual(response.status_code, 200) - # Message should contain non-ugly model name. Instance representation is set by unicode() (ugly) + # Message should contain non-ugly model name. Instance representation is set by six.text_type() (ugly) self.assertContains(response, '

    • The paper "Paper_Deferred_author object" was changed successfully.
    • ', html=True) # defer() is used in ModelAdmin.queryset() @@ -2530,8 +2531,8 @@ class AdminInlineFileUploadTest(TestCase): "pictures-TOTAL_FORMS": "2", "pictures-INITIAL_FORMS": "1", "pictures-MAX_NUM_FORMS": "0", - "pictures-0-id": unicode(self.picture.id), - "pictures-0-gallery": unicode(self.gallery.id), + "pictures-0-id": six.text_type(self.picture.id), + "pictures-0-gallery": six.text_type(self.gallery.id), "pictures-0-name": "Test Picture", "pictures-0-image": "", "pictures-1-id": "", diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index cb25ac0a32..fa41aa7401 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -16,6 +16,7 @@ from django.db.utils import ConnectionHandler, DatabaseError, load_backend from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature, TransactionTestCase) from django.test.utils import override_settings +from django.utils import six from django.utils import unittest from . import models @@ -50,7 +51,7 @@ class OracleChecks(unittest.TestCase): # than 4000 chars and read it properly c = connection.cursor() c.execute('CREATE TABLE ltext ("TEXT" NCLOB)') - long_str = ''.join([unicode(x) for x in xrange(4000)]) + long_str = ''.join([six.text_type(x) for x in xrange(4000)]) c.execute('INSERT INTO ltext VALUES (%s)',[long_str]) c.execute('SELECT text FROM ltext') row = c.fetchone() @@ -154,7 +155,7 @@ class LastExecutedQueryTest(TestCase): sql, params = tags.query.sql_with_params() cursor = tags.query.get_compiler('default').execute_sql(None) last_sql = cursor.db.ops.last_executed_query(cursor, sql, params) - self.assertTrue(isinstance(last_sql, unicode)) + self.assertTrue(isinstance(last_sql, six.text_type)) class ParameterHandlingTest(TestCase): diff --git a/tests/regressiontests/datatypes/tests.py b/tests/regressiontests/datatypes/tests.py index f8f6802041..f0ec5f3c0a 100644 --- a/tests/regressiontests/datatypes/tests.py +++ b/tests/regressiontests/datatypes/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals import datetime from django.test import TestCase, skipIfDBFeature +from django.utils import six from django.utils.timezone import utc from .models import Donut, RumBaba @@ -73,7 +74,7 @@ class DataTypesTestCase(TestCase): database should be unicode.""" d = Donut.objects.create(name='Jelly Donut', review='Outstanding') newd = Donut.objects.get(id=d.id) - self.assertTrue(isinstance(newd.review, unicode)) + self.assertTrue(isinstance(newd.review, six.text_type)) @skipIfDBFeature('supports_timezones') def test_error_on_timezone(self): diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index ffa0a01132..e2907632ef 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -6,6 +6,7 @@ import decimal from django.template.defaultfilters import * from django.test import TestCase +from django.utils import six from django.utils import unittest, translation from django.utils.safestring import SafeData @@ -48,13 +49,13 @@ class DefaultFiltersTests(TestCase): '0.00000000000000000002') pos_inf = float(1e30000) - self.assertEqual(floatformat(pos_inf), unicode(pos_inf)) + self.assertEqual(floatformat(pos_inf), six.text_type(pos_inf)) neg_inf = float(-1e30000) - self.assertEqual(floatformat(neg_inf), unicode(neg_inf)) + self.assertEqual(floatformat(neg_inf), six.text_type(neg_inf)) nan = pos_inf / pos_inf - self.assertEqual(floatformat(nan), unicode(nan)) + self.assertEqual(floatformat(nan), six.text_type(nan)) class FloatWrapper(object): def __init__(self, value): diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py index 73b09cbcff..c5d2720e1a 100644 --- a/tests/regressiontests/file_uploads/views.py +++ b/tests/regressiontests/file_uploads/views.py @@ -6,6 +6,7 @@ import os from django.core.files.uploadedfile import UploadedFile from django.http import HttpResponse, HttpResponseServerError +from django.utils import six from .models import FileModel, UPLOAD_TO from .tests import UNICODE_FILENAME @@ -19,7 +20,7 @@ def file_upload_view(request): """ form_data = request.POST.copy() form_data.update(request.FILES) - if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode): + if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], six.text_type): # If a file is posted, the dummy client should only post the file name, # not the full path. if os.path.dirname(form_data['file_field'].name) != '': diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index 14cf880003..7151cb0ed9 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals from django.contrib.auth.models import User from django.db import models +from django.utils import six class Animal(models.Model): @@ -29,7 +30,7 @@ class Stuff(models.Model): owner = models.ForeignKey(User, null=True) def __unicode__(self): - return unicode(self.name) + ' is owned by ' + unicode(self.owner) + return six.text_type(self.name) + ' is owned by ' + six.text_type(self.owner) class Absolute(models.Model): diff --git a/tests/regressiontests/forms/tests/models.py b/tests/regressiontests/forms/tests/models.py index 5bea49b840..7687335b48 100644 --- a/tests/regressiontests/forms/tests/models.py +++ b/tests/regressiontests/forms/tests/models.py @@ -8,6 +8,7 @@ from django.db import models from django.forms import Form, ModelForm, FileField, ModelChoiceField from django.forms.models import ModelFormMetaclass from django.test import TestCase +from django.utils import six from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group, BoundaryModel, Defaults) @@ -40,7 +41,7 @@ class ModelFormCallableModelDefault(TestCase): choices = list(ChoiceFieldForm().fields['choice'].choices) self.assertEqual(len(choices), 1) - self.assertEqual(choices[0], (option.pk, unicode(option))) + self.assertEqual(choices[0], (option.pk, six.text_type(option))) def test_callable_initial_value(self): "The initial value for a callable default returning a queryset is the pk (refs #13769)" diff --git a/tests/regressiontests/forms/tests/util.py b/tests/regressiontests/forms/tests/util.py index 280049c97b..b7cc4ec809 100644 --- a/tests/regressiontests/forms/tests/util.py +++ b/tests/regressiontests/forms/tests/util.py @@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError from django.forms.util import flatatt, ErrorDict, ErrorList from django.test import TestCase from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import ugettext_lazy @@ -30,7 +31,7 @@ class FormsUtilTestCase(TestCase): '
      • There was an error.
      ') # Can take a unicode string. - self.assertHTMLEqual(unicode(ErrorList(ValidationError("Not \u03C0.").messages)), + self.assertHTMLEqual(six.text_type(ErrorList(ValidationError("Not \u03C0.").messages)), '
      • Not π.
      ') # Can take a lazy string. diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/regressiontests/forms/tests/widgets.py index d5f6334fe9..3ea42cf549 100644 --- a/tests/regressiontests/forms/tests/widgets.py +++ b/tests/regressiontests/forms/tests/widgets.py @@ -10,6 +10,7 @@ from django.forms import * from django.forms.widgets import RadioFieldRenderer from django.utils import formats from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import activate, deactivate from django.test import TestCase @@ -676,7 +677,7 @@ beatle J R Ringo False""") # You can create your own custom renderers for RadioSelect to use. class MyRenderer(RadioFieldRenderer): def render(self): - return '
      \n'.join([unicode(choice) for choice in self]) + return '
      \n'.join([six.text_type(choice) for choice in self]) w = RadioSelect(renderer=MyRenderer) self.assertHTMLEqual(w.render('beatle', 'G', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """

      @@ -716,7 +717,7 @@ beatle J R Ringo False""") # Unicode choices are correctly rendered as HTML w = RadioSelect() - self.assertHTMLEqual(unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])), '
        \n
      • \n
      • \n
      ') + self.assertHTMLEqual(six.text_type(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])), '
        \n
      • \n
      • \n
      ') # Attributes provided at instantiation are passed to the constituent inputs w = RadioSelect(attrs={'id':'foo'}) @@ -1135,7 +1136,7 @@ class ClearableFileInputTests(TestCase): output = widget.render('my
      file', field) self.assertFalse(field.url in output) self.assertTrue('href="something?chapter=1&sect=2&copy=3&lang=en"' in output) - self.assertFalse(unicode(field) in output) + self.assertFalse(six.text_type(field) in output) self.assertTrue('something<div onclick="alert('oops')">.jpg' in output) self.assertTrue('my<div>file' in output) self.assertFalse('my
      file' in output) diff --git a/tests/regressiontests/i18n/contenttypes/tests.py b/tests/regressiontests/i18n/contenttypes/tests.py index bed94da8b8..178232f543 100644 --- a/tests/regressiontests/i18n/contenttypes/tests.py +++ b/tests/regressiontests/i18n/contenttypes/tests.py @@ -6,6 +6,7 @@ import os from django.contrib.contenttypes.models import ContentType from django.test import TestCase from django.test.utils import override_settings +from django.utils import six from django.utils import translation @@ -24,11 +25,11 @@ class ContentTypeTests(TestCase): def test_verbose_name(self): company_type = ContentType.objects.get(app_label='i18n', model='company') with translation.override('en'): - self.assertEqual(unicode(company_type), 'Company') + self.assertEqual(six.text_type(company_type), 'Company') with translation.override('fr'): - self.assertEqual(unicode(company_type), 'Société') + self.assertEqual(six.text_type(company_type), 'Société') def test_field_override(self): company_type = ContentType.objects.get(app_label='i18n', model='company') company_type.name = 'Other' - self.assertEqual(unicode(company_type), 'Other') + self.assertEqual(six.text_type(company_type), 'Other') diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 69260edb0a..9ca66bdb9b 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -19,6 +19,7 @@ from django.utils.formats import (get_format, date_format, time_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 import six from django.utils.six import PY3 from django.utils.translation import (ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, pgettext, npgettext, to_locale, @@ -81,9 +82,9 @@ class TranslationTests(TestCase): def test_lazy_pickle(self): s1 = ugettext_lazy("test") - self.assertEqual(unicode(s1), "test") + self.assertEqual(six.text_type(s1), "test") s2 = pickle.loads(pickle.dumps(s1)) - self.assertEqual(unicode(s2), "test") + self.assertEqual(six.text_type(s2), "test") def test_pgettext(self): # Reset translation catalog to include other/locale/de @@ -222,10 +223,10 @@ class TranslationTests(TestCase): def test_string_concat(self): """ - unicode(string_concat(...)) should not raise a TypeError - #4796 + six.text_type(string_concat(...)) should not raise a TypeError - #4796 """ import django.utils.translation - self.assertEqual('django', unicode(django.utils.translation.string_concat("dja", "ngo"))) + self.assertEqual('django', six.text_type(django.utils.translation.string_concat("dja", "ngo"))) def test_safe_status(self): """ diff --git a/tests/regressiontests/inline_formsets/tests.py b/tests/regressiontests/inline_formsets/tests.py index 8ad84f221f..6e63f34ed0 100644 --- a/tests/regressiontests/inline_formsets/tests.py +++ b/tests/regressiontests/inline_formsets/tests.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals from django.forms.models import inlineformset_factory from django.test import TestCase +from django.utils import six from .models import Poet, Poem, School, Parent, Child @@ -66,8 +67,8 @@ class DeletionTests(TestCase): 'poem_set-TOTAL_FORMS': '1', 'poem_set-INITIAL_FORMS': '1', 'poem_set-MAX_NUM_FORMS': '0', - 'poem_set-0-id': unicode(poem.id), - 'poem_set-0-poem': unicode(poem.id), + 'poem_set-0-id': six.text_type(poem.id), + 'poem_set-0-poem': six.text_type(poem.id), 'poem_set-0-name': 'x' * 1000, } formset = PoemFormSet(data, instance=poet) diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py index a0f9bba170..3cb129f84e 100644 --- a/tests/regressiontests/model_forms_regress/tests.py +++ b/tests/regressiontests/model_forms_regress/tests.py @@ -7,6 +7,7 @@ from django.core.exceptions import FieldError, ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.forms.models import (modelform_factory, ModelChoiceField, fields_for_model, construct_instance, ModelFormMetaclass) +from django.utils import six from django.utils import unittest from django.test import TestCase @@ -392,14 +393,14 @@ class FileFieldTests(unittest.TestCase): """ form = DocumentForm() - self.assertTrue('name="myfile"' in unicode(form)) - self.assertTrue('myfile-clear' not in unicode(form)) + self.assertTrue('name="myfile"' in six.text_type(form)) + self.assertTrue('myfile-clear' not in six.text_type(form)) form = DocumentForm(files={'myfile': SimpleUploadedFile('something.txt', b'content')}) self.assertTrue(form.is_valid()) doc = form.save(commit=False) self.assertEqual(doc.myfile.name, 'something.txt') form = DocumentForm(instance=doc) - self.assertTrue('myfile-clear' in unicode(form)) + self.assertTrue('myfile-clear' in six.text_type(form)) form = DocumentForm(instance=doc, data={'myfile-clear': 'true'}) doc = form.save(commit=False) self.assertEqual(bool(doc.myfile), False) @@ -420,7 +421,7 @@ class FileFieldTests(unittest.TestCase): self.assertTrue(not form.is_valid()) self.assertEqual(form.errors['myfile'], ['Please either submit a file or check the clear checkbox, not both.']) - rendered = unicode(form) + rendered = six.text_type(form) self.assertTrue('something.txt' in rendered) self.assertTrue('myfile-clear' in rendered) diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py index 68ebe48bde..1fbdb9744f 100644 --- a/tests/regressiontests/model_formsets_regress/tests.py +++ b/tests/regressiontests/model_formsets_regress/tests.py @@ -5,6 +5,7 @@ from django.forms.formsets import BaseFormSet, DELETION_FIELD_NAME from django.forms.util import ErrorDict, ErrorList from django.forms.models import modelform_factory, inlineformset_factory, modelformset_factory, BaseModelFormSet from django.test import TestCase +from django.utils import six from .models import User, UserSite, Restaurant, Manager, Network, Host @@ -51,7 +52,7 @@ class InlineFormsetTests(TestCase): 'usersite_set-TOTAL_FORMS': '1', 'usersite_set-INITIAL_FORMS': '1', 'usersite_set-MAX_NUM_FORMS': '0', - 'usersite_set-0-id': unicode(usersite[0]['id']), + 'usersite_set-0-id': six.text_type(usersite[0]['id']), 'usersite_set-0-data': '11', 'usersite_set-0-user': 'apollo13' } @@ -69,7 +70,7 @@ class InlineFormsetTests(TestCase): 'usersite_set-TOTAL_FORMS': '2', 'usersite_set-INITIAL_FORMS': '1', 'usersite_set-MAX_NUM_FORMS': '0', - 'usersite_set-0-id': unicode(usersite[0]['id']), + 'usersite_set-0-id': six.text_type(usersite[0]['id']), 'usersite_set-0-data': '11', 'usersite_set-0-user': 'apollo13', 'usersite_set-1-data': '42', @@ -124,7 +125,7 @@ class InlineFormsetTests(TestCase): 'manager_set-TOTAL_FORMS': '1', 'manager_set-INITIAL_FORMS': '1', 'manager_set-MAX_NUM_FORMS': '0', - 'manager_set-0-id': unicode(manager[0]['id']), + 'manager_set-0-id': six.text_type(manager[0]['id']), 'manager_set-0-name': 'Terry Gilliam' } form_set = FormSet(data, instance=restaurant) @@ -140,7 +141,7 @@ class InlineFormsetTests(TestCase): 'manager_set-TOTAL_FORMS': '2', 'manager_set-INITIAL_FORMS': '1', 'manager_set-MAX_NUM_FORMS': '0', - 'manager_set-0-id': unicode(manager[0]['id']), + 'manager_set-0-id': six.text_type(manager[0]['id']), 'manager_set-0-name': 'Terry Gilliam', 'manager_set-1-name': 'John Cleese' } @@ -188,7 +189,7 @@ class InlineFormsetTests(TestCase): 'host_set-TOTAL_FORMS': '2', 'host_set-INITIAL_FORMS': '1', 'host_set-MAX_NUM_FORMS': '0', - 'host_set-0-id': unicode(host1.id), + 'host_set-0-id': six.text_type(host1.id), 'host_set-0-hostname': 'tranquility.hub.dal.net', 'host_set-1-hostname': 'matrix.de.eu.dal.net' } diff --git a/tests/regressiontests/model_regress/tests.py b/tests/regressiontests/model_regress/tests.py index 7f9f514c7a..6a45a83052 100644 --- a/tests/regressiontests/model_regress/tests.py +++ b/tests/regressiontests/model_regress/tests.py @@ -5,6 +5,7 @@ from operator import attrgetter from django.core.exceptions import ValidationError from django.test import TestCase, skipUnlessDBFeature +from django.utils import six from django.utils import tzinfo from .models import (Worker, Article, Party, Event, Department, @@ -38,7 +39,7 @@ class ModelTests(TestCase): # Empty strings should be returned as Unicode a = Article.objects.get(pk=a.pk) self.assertEqual(a.misc_data, '') - self.assertIs(type(a.misc_data), unicode) + self.assertIs(type(a.misc_data), six.text_type) def test_long_textfield(self): # TextFields can hold more than 4000 characters (this was broken in @@ -138,7 +139,7 @@ class ModelTests(TestCase): # Check Department and Worker (non-default PK type) d = Department.objects.create(id=10, name="IT") w = Worker.objects.create(department=d, name="Full-time") - self.assertEqual(unicode(w), "Full-time") + self.assertEqual(six.text_type(w), "Full-time") def test_broken_unicode(self): # Models with broken unicode methods should still have a printable repr diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index 8c34b50e93..6328776e91 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import threading from django.db import models +from django.utils import six class DumbCategory(models.Model): @@ -122,7 +123,7 @@ class Number(models.Model): num = models.IntegerField() def __unicode__(self): - return unicode(self.num) + return six.text_type(self.num) # Symmetrical m2m field with a normal field using the reverse accesor name # ("valid"). diff --git a/tests/regressiontests/select_related_regress/tests.py b/tests/regressiontests/select_related_regress/tests.py index 73b0a8a875..7f93a1c33c 100644 --- a/tests/regressiontests/select_related_regress/tests.py +++ b/tests/regressiontests/select_related_regress/tests.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, unicode_literals from django.test import TestCase +from django.utils import six from .models import (Building, Child, Device, Port, Item, Country, Connection, ClientStatus, State, Client, SpecialClient, TUser, Person, Student, @@ -33,11 +34,11 @@ class SelectRelatedRegressTests(TestCase): c2=Connection.objects.create(start=port2, end=port3) connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id') - self.assertEqual([(c.id, unicode(c.start), unicode(c.end)) for c in connections], + self.assertEqual([(c.id, six.text_type(c.start), six.text_type(c.end)) for c in connections], [(c1.id, 'router/4', 'switch/7'), (c2.id, 'switch/7', 'server/1')]) connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id') - self.assertEqual([(c.id, unicode(c.start), unicode(c.end)) for c in connections], + self.assertEqual([(c.id, six.text_type(c.start), six.text_type(c.end)) for c in connections], [(c1.id, 'router/4', 'switch/7'), (c2.id, 'switch/7', 'server/1')]) # This final query should only have seven tables (port, device and building diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/regressiontests/templates/templatetags/custom.py index 7f788311c6..95fcd551de 100644 --- a/tests/regressiontests/templates/templatetags/custom.py +++ b/tests/regressiontests/templates/templatetags/custom.py @@ -3,6 +3,7 @@ import operator from django import template from django.template.defaultfilters import stringfilter from django.template.loader import get_template +from django.utils import six register = template.Library() @@ -56,13 +57,13 @@ simple_one_default.anything = "Expected simple_one_default __dict__" @register.simple_tag def simple_unlimited_args(one, two='hi', *args): """Expected simple_unlimited_args __doc__""" - return "simple_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)])) + return "simple_unlimited_args - Expected result: %s" % (', '.join([six.text_type(arg) for arg in [one, two] + list(args)])) simple_unlimited_args.anything = "Expected simple_unlimited_args __dict__" @register.simple_tag def simple_only_unlimited_args(*args): """Expected simple_only_unlimited_args __doc__""" - return "simple_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args]) + return "simple_only_unlimited_args - Expected result: %s" % ', '.join([six.text_type(arg) for arg in args]) simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__" @register.simple_tag @@ -71,7 +72,7 @@ def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs): # Sort the dictionary by key to guarantee the order for testing. sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) return "simple_unlimited_args_kwargs - Expected result: %s / %s" % ( - ', '.join([unicode(arg) for arg in [one, two] + list(args)]), + ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) ) simple_unlimited_args_kwargs.anything = "Expected simple_unlimited_args_kwargs __dict__" @@ -183,25 +184,25 @@ inclusion_one_default_from_template.anything = "Expected inclusion_one_default_f @register.inclusion_tag('inclusion.html') def inclusion_unlimited_args(one, two='hi', *args): """Expected inclusion_unlimited_args __doc__""" - return {"result": "inclusion_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))} + return {"result": "inclusion_unlimited_args - Expected result: %s" % (', '.join([six.text_type(arg) for arg in [one, two] + list(args)]))} inclusion_unlimited_args.anything = "Expected inclusion_unlimited_args __dict__" @register.inclusion_tag(get_template('inclusion.html')) def inclusion_unlimited_args_from_template(one, two='hi', *args): """Expected inclusion_unlimited_args_from_template __doc__""" - return {"result": "inclusion_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))} + return {"result": "inclusion_unlimited_args_from_template - Expected result: %s" % (', '.join([six.text_type(arg) for arg in [one, two] + list(args)]))} inclusion_unlimited_args_from_template.anything = "Expected inclusion_unlimited_args_from_template __dict__" @register.inclusion_tag('inclusion.html') def inclusion_only_unlimited_args(*args): """Expected inclusion_only_unlimited_args __doc__""" - return {"result": "inclusion_only_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))} + return {"result": "inclusion_only_unlimited_args - Expected result: %s" % (', '.join([six.text_type(arg) for arg in args]))} inclusion_only_unlimited_args.anything = "Expected inclusion_only_unlimited_args __dict__" @register.inclusion_tag(get_template('inclusion.html')) def inclusion_only_unlimited_args_from_template(*args): """Expected inclusion_only_unlimited_args_from_template __doc__""" - return {"result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))} + return {"result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % (', '.join([six.text_type(arg) for arg in args]))} inclusion_only_unlimited_args_from_template.anything = "Expected inclusion_only_unlimited_args_from_template __dict__" @register.inclusion_tag('test_incl_tag_current_app.html', takes_context=True) @@ -222,7 +223,7 @@ def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs): # Sort the dictionary by key to guarantee the order for testing. sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % ( - ', '.join([unicode(arg) for arg in [one, two] + list(args)]), + ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) )} inclusion_unlimited_args_kwargs.anything = "Expected inclusion_unlimited_args_kwargs __dict__" @@ -278,13 +279,13 @@ assignment_one_default.anything = "Expected assignment_one_default __dict__" @register.assignment_tag def assignment_unlimited_args(one, two='hi', *args): """Expected assignment_unlimited_args __doc__""" - return "assignment_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)])) + return "assignment_unlimited_args - Expected result: %s" % (', '.join([six.text_type(arg) for arg in [one, two] + list(args)])) assignment_unlimited_args.anything = "Expected assignment_unlimited_args __dict__" @register.assignment_tag def assignment_only_unlimited_args(*args): """Expected assignment_only_unlimited_args __doc__""" - return "assignment_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args]) + return "assignment_only_unlimited_args - Expected result: %s" % ', '.join([six.text_type(arg) for arg in args]) assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_args __dict__" @register.assignment_tag @@ -293,7 +294,7 @@ def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs): # Sort the dictionary by key to guarantee the order for testing. sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % ( - ', '.join([unicode(arg) for arg in [one, two] + list(args)]), + ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) ) assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_kwargs __dict__" diff --git a/tests/regressiontests/templates/unicode.py b/tests/regressiontests/templates/unicode.py index 2c41176b01..7cb2a28d15 100644 --- a/tests/regressiontests/templates/unicode.py +++ b/tests/regressiontests/templates/unicode.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django.template import Template, TemplateEncodingError, Context from django.utils.safestring import SafeData +from django.utils import six from django.utils.unittest import TestCase @@ -27,5 +28,5 @@ class UnicodeTests(TestCase): # they all render the same (and are returned as unicode objects and # "safe" objects as well, for auto-escaping purposes). self.assertEqual(t1.render(c3), t2.render(c3)) - self.assertIsInstance(t1.render(c3), unicode) + self.assertIsInstance(t1.render(c3), six.text_type) self.assertIsInstance(t1.render(c3), SafeData) diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py index 982d2226e6..960a5e3201 100644 --- a/tests/regressiontests/utils/simplelazyobject.py +++ b/tests/regressiontests/utils/simplelazyobject.py @@ -4,6 +4,7 @@ import copy import pickle from django.test.utils import str_prefix +from django.utils import six from django.utils.unittest import TestCase from django.utils.functional import SimpleLazyObject, empty @@ -22,7 +23,7 @@ class _ComplexObject(object): return "I am _ComplexObject(%r)" % self.name def __unicode__(self): - return unicode(self.name) + return six.text_type(self.name) def __repr__(self): return "_ComplexObject(%r)" % self.name @@ -58,7 +59,7 @@ class TestUtilsSimpleLazyObject(TestCase): str(SimpleLazyObject(complex_object))) def test_unicode(self): - self.assertEqual("joe", unicode(SimpleLazyObject(complex_object))) + self.assertEqual("joe", six.text_type(SimpleLazyObject(complex_object))) def test_class(self): # This is important for classes that use __class__ in things like @@ -108,5 +109,5 @@ class TestUtilsSimpleLazyObject(TestCase): pickled = pickle.dumps(x) unpickled = pickle.loads(pickled) self.assertEqual(unpickled, x) - self.assertEqual(unicode(unpickled), unicode(x)) + self.assertEqual(six.text_type(unpickled), six.text_type(x)) self.assertEqual(unpickled.name, x.name) diff --git a/tests/regressiontests/wsgi/tests.py b/tests/regressiontests/wsgi/tests.py index 9614a81c67..a482a5c1eb 100644 --- a/tests/regressiontests/wsgi/tests.py +++ b/tests/regressiontests/wsgi/tests.py @@ -6,6 +6,7 @@ from django.core.wsgi import get_wsgi_application from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings +from django.utils import six from django.utils import unittest @@ -39,7 +40,7 @@ class WSGITest(TestCase): response_data["headers"], [('Content-Type', 'text/html; charset=utf-8')]) self.assertEqual( - unicode(response), + six.text_type(response), "Content-Type: text/html; charset=utf-8\n\nHello World!") diff --git a/tests/runtests.py b/tests/runtests.py index b71cf98b13..c548d2745b 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -7,6 +7,7 @@ import tempfile import warnings from django import contrib +from django.utils import six # databrowse is deprecated, but we still want to run its tests warnings.filterwarnings('ignore', "The Databrowse contrib app is deprecated", @@ -142,7 +143,7 @@ def teardown(state): # so that it will successfully remove temp trees containing # non-ASCII filenames on Windows. (We're assuming the temp dir # name itself does not contain non-ASCII characters.) - shutil.rmtree(unicode(TEMP_DIR)) + shutil.rmtree(six.text_type(TEMP_DIR)) # Restore the old settings. for key, value in state.items(): setattr(settings, key, value) From 0d914d08a0d7b5a1521f498a8047971fe6cd61e7 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 15:36:52 +0200 Subject: [PATCH 233/519] [py3] Updated urllib/urllib2/urlparse imports. Lots of functions were moved. Use explicit imports in all cases to keey it easy to identify where the functions come from. --- django/contrib/auth/decorators.py | 10 +++++---- django/contrib/auth/models.py | 6 ++--- django/contrib/auth/tests/views.py | 10 ++++----- django/contrib/auth/views.py | 13 ++++++----- django/contrib/comments/views/utils.py | 8 +++++-- django/contrib/contenttypes/tests.py | 6 ++--- .../databrowse/plugins/fieldchoices.py | 8 +++---- django/contrib/databrowse/plugins/objects.py | 8 +++++-- django/contrib/sitemaps/__init__.py | 10 ++++++--- django/contrib/staticfiles/handlers.py | 10 ++++++--- django/contrib/staticfiles/storage.py | 7 ++++-- django/contrib/staticfiles/views.py | 7 ++++-- django/core/cache/__init__.py | 5 ++++- django/core/files/storage.py | 7 ++++-- django/core/management/templates.py | 8 ++++--- django/core/servers/basehttp.py | 11 ++++++---- django/core/validators.py | 9 +++++--- django/forms/fields.py | 11 ++++++---- django/forms/widgets.py | 5 ++++- django/http/__init__.py | 7 ++++-- django/templatetags/static.py | 6 ++++- django/test/client.py | 11 ++++++---- django/test/testcases.py | 5 ++++- django/utils/encoding.py | 13 ++++++----- django/utils/feedgenerator.py | 7 ++++-- django/utils/html.py | 13 ++++++----- django/utils/http.py | 22 ++++++++++++------- django/views/static.py | 7 ++++-- tests/modeltests/test_client/views.py | 6 +++-- tests/regressiontests/admin_views/tests.py | 7 ++++-- tests/regressiontests/servers/tests.py | 9 +++++--- tests/regressiontests/templates/tests.py | 5 ++++- 32 files changed, 181 insertions(+), 96 deletions(-) diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 5805a3122c..7a608d0777 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -1,4 +1,7 @@ -import urlparse +try: + from urllib.parse import urlparse +except ImportError: # Python 2 + from urlparse import urlparse from functools import wraps from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME @@ -21,9 +24,8 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE path = request.build_absolute_uri() # 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.urlparse(login_url or - settings.LOGIN_URL)[:2] - current_scheme, current_netloc = urlparse.urlparse(path)[:2] + login_scheme, login_netloc = urlparse(login_url or settings.LOGIN_URL)[: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)): path = request.get_full_path() diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 95a7494d38..39d9e8408d 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,13 +1,11 @@ from __future__ import unicode_literals -import urllib - from django.core.exceptions import ImproperlyConfigured from django.core.mail import send_mail from django.db import models from django.db.models.manager import EmptyManager from django.utils.crypto import get_random_string -from django.utils.encoding import smart_str +from django.utils.http import urlquote from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -268,7 +266,7 @@ class User(models.Model): return (self.username,) def get_absolute_url(self): - return "/users/%s/" % urllib.quote(smart_str(self.username)) + return "/users/%s/" % urlquote(self.username) def is_anonymous(self): """ diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index a9754c5bad..e76e7dd10f 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -1,6 +1,5 @@ import os import re -import urllib from django.conf import settings from django.contrib.sites.models import Site, RequestSite @@ -10,6 +9,7 @@ from django.core.urlresolvers import reverse, NoReverseMatch from django.http import QueryDict from django.utils.encoding import force_unicode from django.utils.html import escape +from django.utils.http import urlquote from django.test import TestCase from django.test.utils import override_settings @@ -256,7 +256,7 @@ class LoginTest(AuthViewsTestCase): nasty_url = '%(url)s?%(next)s=%(bad_url)s' % { 'url': login_url, 'next': REDIRECT_FIELD_NAME, - 'bad_url': urllib.quote(bad_url), + 'bad_url': urlquote(bad_url), } response = self.client.post(nasty_url, { 'username': 'testclient', @@ -277,7 +277,7 @@ class LoginTest(AuthViewsTestCase): safe_url = '%(url)s?%(next)s=%(good_url)s' % { 'url': login_url, 'next': REDIRECT_FIELD_NAME, - 'good_url': urllib.quote(good_url), + 'good_url': urlquote(good_url), } response = self.client.post(safe_url, { 'username': 'testclient', @@ -412,7 +412,7 @@ class LogoutTest(AuthViewsTestCase): nasty_url = '%(url)s?%(next)s=%(bad_url)s' % { 'url': logout_url, 'next': REDIRECT_FIELD_NAME, - 'bad_url': urllib.quote(bad_url), + 'bad_url': urlquote(bad_url), } self.login() response = self.client.get(nasty_url) @@ -432,7 +432,7 @@ class LogoutTest(AuthViewsTestCase): safe_url = '%(url)s?%(next)s=%(good_url)s' % { 'url': logout_url, 'next': REDIRECT_FIELD_NAME, - 'good_url': urllib.quote(good_url), + 'good_url': urlquote(good_url), } self.login() response = self.client.get(safe_url) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index c86ef53595..ccfc7a1003 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -1,4 +1,7 @@ -import urlparse +try: + from urllib.parse import urlparse, urlunparse +except ImportError: # Python 2 + from urlparse import urlparse, urlunparse from django.conf import settings from django.core.urlresolvers import reverse @@ -34,7 +37,7 @@ def login(request, template_name='registration/login.html', if request.method == "POST": form = authentication_form(data=request.POST) if form.is_valid(): - netloc = urlparse.urlparse(redirect_to)[1] + netloc = urlparse(redirect_to)[1] # Use default setting if redirect_to is empty if not redirect_to: @@ -80,7 +83,7 @@ def logout(request, next_page=None, auth_logout(request) redirect_to = request.REQUEST.get(redirect_field_name, '') if redirect_to: - netloc = urlparse.urlparse(redirect_to)[1] + netloc = urlparse(redirect_to)[1] # Security check -- don't allow redirection to a different host. if not (netloc and netloc != request.get_host()): return HttpResponseRedirect(redirect_to) @@ -116,13 +119,13 @@ def redirect_to_login(next, login_url=None, if not login_url: login_url = settings.LOGIN_URL - login_url_parts = list(urlparse.urlparse(login_url)) + login_url_parts = list(urlparse(login_url)) if redirect_field_name: querystring = QueryDict(login_url_parts[4], mutable=True) querystring[redirect_field_name] = next login_url_parts[4] = querystring.urlencode(safe='/') - return HttpResponseRedirect(urlparse.urlunparse(login_url_parts)) + return HttpResponseRedirect(urlunparse(login_url_parts)) # 4 views for password reset: # - password_reset sends the mail diff --git a/django/contrib/comments/views/utils.py b/django/contrib/comments/views/utils.py index 8879e9fb8f..abaed68560 100644 --- a/django/contrib/comments/views/utils.py +++ b/django/contrib/comments/views/utils.py @@ -2,8 +2,12 @@ A few bits of helper functions for comment views. """ -import urllib import textwrap +try: + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode + from django.http import HttpResponseRedirect from django.core import urlresolvers from django.shortcuts import render_to_response @@ -33,7 +37,7 @@ def next_redirect(data, default, default_view, **get_kwargs): anchor = '' joiner = ('?' in next) and '&' or '?' - next += joiner + urllib.urlencode(get_kwargs) + anchor + next += joiner + urlencode(get_kwargs) + anchor return HttpResponseRedirect(next) def confirmation_view(template, doc="Display a confirmation view."): diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py index 1ecabc16e9..cfd7e6ff32 100644 --- a/django/contrib/contenttypes/tests.py +++ b/django/contrib/contenttypes/tests.py @@ -1,14 +1,12 @@ from __future__ import unicode_literals -import urllib - from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.views import shortcut from django.contrib.sites.models import Site from django.http import HttpRequest, Http404 from django.test import TestCase -from django.utils.encoding import smart_str +from django.utils.http import urlquote from django.utils import six @@ -36,7 +34,7 @@ class FooWithUrl(FooWithoutUrl): """ def get_absolute_url(self): - return "/users/%s/" % urllib.quote(smart_str(self.name)) + return "/users/%s/" % urlquote(self.name) class FooWithBrokenAbsoluteUrl(FooWithoutUrl): """ diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index 4b1f0e6614..c016385ffb 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -6,9 +6,10 @@ from django.contrib.databrowse.datastructures import EasyModel from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response from django.utils.html import format_html, format_html_join +from django.utils.http import urlquote from django.utils.text import capfirst -from django.utils.encoding import smart_str, force_unicode -import urllib +from django.utils.encoding import force_unicode + class FieldChoicePlugin(DatabrowsePlugin): def __init__(self, field_filter=None): @@ -38,11 +39,10 @@ class FieldChoicePlugin(DatabrowsePlugin): def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): - field_value = smart_str(easy_instance_field.raw_value) return ['%s%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, - urllib.quote(field_value, safe=''))] + urlquote(easy_instance_field.raw_value, safe=''))] def model_view(self, request, model_databrowse, url): self.model, self.site = model_databrowse.model, model_databrowse.site diff --git a/django/contrib/databrowse/plugins/objects.py b/django/contrib/databrowse/plugins/objects.py index 7326566655..e956f4ea67 100644 --- a/django/contrib/databrowse/plugins/objects.py +++ b/django/contrib/databrowse/plugins/objects.py @@ -1,14 +1,18 @@ +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin + from django import http from django.contrib.databrowse.datastructures import EasyModel from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response -import urlparse class ObjectDetailPlugin(DatabrowsePlugin): def model_view(self, request, model_databrowse, url): # If the object ID wasn't provided, redirect to the model page, which is one level up. if url is None: - return http.HttpResponseRedirect(urlparse.urljoin(request.path, '../')) + return http.HttpResponseRedirect(urljoin(request.path, '../')) easy_model = EasyModel(model_databrowse.site, model_databrowse.model) obj = easy_model.object_by_pk(url) return render_to_response('databrowse/object_detail.html', {'object': obj, 'root_url': model_databrowse.site.root_url}) diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index 53b375a48b..7d03ef19f5 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -1,7 +1,11 @@ from django.contrib.sites.models import Site from django.core import urlresolvers, paginator from django.core.exceptions import ImproperlyConfigured -import urllib +try: + from urllib.parse import urlencode + from urllib.request import urlopen +except ImportError: # Python 2 + from urllib import urlencode, urlopen PING_URL = "http://www.google.com/webmasters/tools/ping" @@ -32,8 +36,8 @@ def ping_google(sitemap_url=None, ping_url=PING_URL): from django.contrib.sites.models import Site current_site = Site.objects.get_current() url = "http://%s%s" % (current_site.domain, sitemap_url) - params = urllib.urlencode({'sitemap':url}) - urllib.urlopen("%s?%s" % (ping_url, params)) + params = urlencode({'sitemap':url}) + urlopen("%s?%s" % (ping_url, params)) class Sitemap(object): # This limit is defined by Google. See the index documentation at diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py index f475b22d9c..9067a0e75e 100644 --- a/django/contrib/staticfiles/handlers.py +++ b/django/contrib/staticfiles/handlers.py @@ -1,5 +1,9 @@ -import urllib -from urlparse import urlparse +try: + from urllib.parse import urlparse + from urllib.request import url2pathname +except ImportError: # Python 2 + from urllib import url2pathname + from urlparse import urlparse from django.conf import settings from django.core.handlers.wsgi import WSGIHandler @@ -42,7 +46,7 @@ class StaticFilesHandler(WSGIHandler): Returns the relative path to the media file on disk for the given URL. """ relative_url = url[len(self.base_url[2]):] - return urllib.url2pathname(relative_url) + return url2pathname(relative_url) def serve(self, request): """ diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index 47132831eb..a0133e1c6a 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -3,8 +3,11 @@ import hashlib import os import posixpath import re -from urllib import unquote -from urlparse import urlsplit, urlunsplit, urldefrag +try: + from urllib.parse import unquote, urlsplit, urlunsplit, urldefrag +except ImportError: # Python 2 + from urllib import unquote + from urlparse import urlsplit, urlunsplit, urldefrag from django.conf import settings from django.core.cache import (get_cache, InvalidCacheBackendError, diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py index 1a9c166ad7..85459812ad 100644 --- a/django/contrib/staticfiles/views.py +++ b/django/contrib/staticfiles/views.py @@ -5,7 +5,10 @@ development, and SHOULD NOT be used in a production setting. """ import os import posixpath -import urllib +try: + from urllib.parse import unquote +except ImportError: # Python 2 + from urllib import unquote from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -31,7 +34,7 @@ def serve(request, path, document_root=None, insecure=False, **kwargs): raise ImproperlyConfigured("The staticfiles view can only be used in " "debug mode or if the the --insecure " "option of 'runserver' is used") - normalized_path = posixpath.normpath(urllib.unquote(path)).lstrip('/') + normalized_path = posixpath.normpath(unquote(path)).lstrip('/') absolute_path = finders.find(normalized_path) if not absolute_path: if path.endswith('/') or path == '': diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 2a9e1a700b..f496c35e2b 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -14,7 +14,10 @@ cache class. See docs/topics/cache.txt for information on the public API. """ -from urlparse import parse_qsl +try: + from urllib.parse import parse_qsl +except ImportError: # Python 2 + from urlparse import parse_qsl from django.conf import settings from django.core import signals diff --git a/django/core/files/storage.py b/django/core/files/storage.py index ba88674dbd..5179980513 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -1,6 +1,9 @@ import os import errno -import urlparse +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin import itertools from datetime import datetime @@ -252,7 +255,7 @@ class FileSystemStorage(Storage): def url(self, name): if self.base_url is None: raise ValueError("This file is not accessible via a URL.") - return urlparse.urljoin(self.base_url, filepath_to_uri(name)) + return urljoin(self.base_url, filepath_to_uri(name)) def accessed_time(self, name): return datetime.fromtimestamp(os.path.getatime(self.path(name))) diff --git a/django/core/management/templates.py b/django/core/management/templates.py index 623aa69deb..2bf2f661fd 100644 --- a/django/core/management/templates.py +++ b/django/core/management/templates.py @@ -8,7 +8,10 @@ import shutil import stat import sys import tempfile -import urllib +try: + from urllib.request import urlretrieve +except ImportError: # Python 2 + from urllib import urlretrieve from optparse import make_option from os import path @@ -227,8 +230,7 @@ class TemplateCommand(BaseCommand): if self.verbosity >= 2: self.stdout.write("Downloading %s\n" % display_url) try: - the_path, info = urllib.urlretrieve(url, - path.join(tempdir, filename)) + the_path, info = urlretrieve(url, path.join(tempdir, filename)) except IOError as e: raise CommandError("couldn't download URL %s to %s: %s" % (url, filename, e)) diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index be2962d660..f67b105397 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -11,8 +11,11 @@ import os import socket import sys import traceback -import urllib -import urlparse +try: + from urllib.parse import unquote, urljoin +except ImportError: # Python 2 + from urllib import unquote + from urlparse import urljoin from SocketServer import ThreadingMixIn from wsgiref import simple_server from wsgiref.util import FileWrapper # for backwards compatibility @@ -127,7 +130,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object): def __init__(self, *args, **kwargs): from django.conf import settings - self.admin_static_prefix = urlparse.urljoin(settings.STATIC_URL, 'admin/') + self.admin_static_prefix = urljoin(settings.STATIC_URL, 'admin/') # We set self.path to avoid crashes in log_message() on unsupported # requests (like "OPTIONS"). self.path = '' @@ -143,7 +146,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object): else: path,query = self.path,'' - env['PATH_INFO'] = urllib.unquote(path) + env['PATH_INFO'] = unquote(path) env['QUERY_STRING'] = query env['REMOTE_ADDR'] = self.client_address[0] env['CONTENT_TYPE'] = self.headers.get('content-type', 'text/plain') diff --git a/django/core/validators.py b/django/core/validators.py index 47c1cbf1cd..03ff8be3bc 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -1,7 +1,10 @@ from __future__ import unicode_literals import re -import urlparse +try: + from urllib.parse import urlsplit, urlunsplit +except ImportError: # Python 2 + from urlparse import urlsplit, urlunsplit from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ @@ -52,12 +55,12 @@ class URLValidator(RegexValidator): # Trivial case failed. Try for possible IDN domain if value: value = smart_unicode(value) - scheme, netloc, path, query, fragment = urlparse.urlsplit(value) + scheme, netloc, path, query, fragment = urlsplit(value) try: netloc = netloc.encode('idna') # IDN -> ACE except UnicodeError: # invalid domain part raise e - url = urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + url = urlunsplit((scheme, netloc, path, query, fragment)) super(URLValidator, self).__call__(url) else: raise diff --git a/django/forms/fields.py b/django/forms/fields.py index 9c944ad0ac..c4a675da74 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -8,7 +8,10 @@ import copy import datetime import os import re -import urlparse +try: + from urllib.parse import urlsplit, urlunsplit +except ImportError: # Python 2 + from urlparse import urlsplit, urlunsplit from decimal import Decimal, DecimalException from io import BytesIO @@ -599,7 +602,7 @@ class URLField(CharField): ``ValidationError`` exception for certain). """ try: - return list(urlparse.urlsplit(url)) + return list(urlsplit(url)) except ValueError: # urlparse.urlsplit can raise a ValueError with some # misformatted URLs. @@ -618,11 +621,11 @@ class URLField(CharField): url_fields[2] = '' # Rebuild the url_fields list, since the domain segment may now # contain the path too. - url_fields = split_url(urlparse.urlunsplit(url_fields)) + url_fields = split_url(urlunsplit(url_fields)) if not url_fields[2]: # the path portion may need to be added before query params url_fields[2] = '/' - value = urlparse.urlunsplit(url_fields) + value = urlunsplit(url_fields) return value class BooleanField(Field): diff --git a/django/forms/widgets.py b/django/forms/widgets.py index f2446efcdf..6b1be37ec2 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -7,7 +7,10 @@ from __future__ import absolute_import, unicode_literals import copy import datetime from itertools import chain -from urlparse import urljoin +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin from django.conf import settings from django.forms.util import flatatt, to_current_timezone diff --git a/django/http/__init__.py b/django/http/__init__.py index 7c5184a329..da97506c8c 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -10,8 +10,11 @@ import warnings from io import BytesIO from pprint import pformat -from urllib import urlencode, quote -from urlparse import urljoin, parse_qsl +try: + from urllib.parse import quote, parse_qsl, urlencode, urljoin +except ImportError: # Python 2 + from urllib import quote, urlencode + from urlparse import parse_qsl, urljoin import Cookie # Some versions of Python 2.7 and later won't need this encoding bug fix: diff --git a/django/templatetags/static.py b/django/templatetags/static.py index 4b2d66d521..5b0e40eba6 100644 --- a/django/templatetags/static.py +++ b/django/templatetags/static.py @@ -1,4 +1,8 @@ -from urlparse import urljoin +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin + from django import template from django.template.base import Node from django.utils.encoding import iri_to_uri diff --git a/django/test/client.py b/django/test/client.py index 7b6cdaa2a4..a18b7f8853 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -1,11 +1,14 @@ -import urllib import sys import os import re import mimetypes from copy import copy from io import BytesIO -from urlparse import urlparse, urlsplit +try: + from urllib.parse import unquote, urlparse, urlsplit +except ImportError: # Python 2 + from urllib import unquote + from urlparse import urlparse, urlsplit from django.conf import settings from django.contrib.auth import authenticate, login @@ -222,9 +225,9 @@ class RequestFactory(object): def _get_path(self, parsed): # If there are parameters, add them if parsed[3]: - return urllib.unquote(parsed[2] + ";" + parsed[3]) + return unquote(parsed[2] + ";" + parsed[3]) else: - return urllib.unquote(parsed[2]) + return unquote(parsed[2]) def get(self, path, data={}, **extra): "Construct a GET request." diff --git a/django/test/testcases.py b/django/test/testcases.py index eb7bd70d12..b9aae21e8e 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -7,7 +7,10 @@ import re import sys from copy import copy from functools import wraps -from urlparse import urlsplit, urlunsplit +try: + from urllib.parse import urlsplit, urlunsplit +except ImportError: # Python 2 + from urlparse import urlsplit, urlunsplit from xml.dom.minidom import parseString, Node import select import socket diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 789e709da5..f2295444bf 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -1,10 +1,13 @@ from __future__ import unicode_literals -import urllib -import locale -import datetime import codecs +import datetime from decimal import Decimal +import locale +try: + from urllib.parse import quote +except ImportError: # Python 2 + from urllib import quote from django.utils.functional import Promise from django.utils import six @@ -165,7 +168,7 @@ def iri_to_uri(iri): # converted. if iri is None: return iri - return urllib.quote(smart_str(iri), safe=b"/#%[]=:;$&()+,!?*@'~") + return quote(smart_str(iri), safe=b"/#%[]=:;$&()+,!?*@'~") def filepath_to_uri(path): """Convert an file system path to a URI portion that is suitable for @@ -184,7 +187,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 urllib.quote(smart_str(path).replace("\\", "/"), safe=b"/~!*()'") + return quote(smart_str(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/feedgenerator.py b/django/utils/feedgenerator.py index 3dc66a68c6..6498aaf57c 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -24,7 +24,10 @@ http://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/ from __future__ import unicode_literals import datetime -import urlparse +try: + from urllib.parse import urlparse +except ImportError: # Python 2 + from urlparse import urlparse from django.utils.xmlutils import SimplerXMLGenerator from django.utils.encoding import force_unicode, iri_to_uri from django.utils import datetime_safe @@ -67,7 +70,7 @@ def get_tag_uri(url, date): See http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id """ - bits = urlparse.urlparse(url) + bits = urlparse(url) d = '' if date is not None: d = ',%s' % datetime_safe.new_datetime(date).strftime('%Y-%m-%d') diff --git a/django/utils/html.py b/django/utils/html.py index d5881996d9..7e35fdecb8 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -4,8 +4,11 @@ from __future__ import unicode_literals import re import string -import urllib -import urlparse +try: + from urllib.parse import quote, urlsplit, urlunsplit +except ImportError: # Python 2 + from urllib import quote + from urlparse import urlsplit, urlunsplit from django.utils.safestring import SafeData, mark_safe from django.utils.encoding import smart_str, force_unicode @@ -138,19 +141,19 @@ fix_ampersands = allow_lazy(fix_ampersands, six.text_type) def smart_urlquote(url): "Quotes a URL if it isn't already quoted." # Handle IDN before quoting. - scheme, netloc, path, query, fragment = urlparse.urlsplit(url) + scheme, netloc, path, query, fragment = urlsplit(url) try: netloc = netloc.encode('idna') # IDN -> ACE except UnicodeError: # invalid domain part pass else: - url = urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + url = urlunsplit((scheme, netloc, path, query, fragment)) # An URL is considered unquoted if it contains no % characters or # 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 = urllib.quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~') + url = quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~') return force_unicode(url) diff --git a/django/utils/http.py b/django/utils/http.py index ec94d62903..f3a3dce58c 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -2,8 +2,14 @@ import calendar import datetime import re import sys -import urllib -import urlparse +try: + from urllib import parse as urllib_parse +except ImportError: # Python 2 + import urllib as urllib_parse + import urlparse + urllib_parse.urlparse = urlparse.urlparse + + from email.utils import formatdate from django.utils.datastructures import MultiValueDict @@ -31,7 +37,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_unicode(urllib.quote(smart_str(url), smart_str(safe))) + return force_unicode(urllib_parse.quote(smart_str(url), smart_str(safe))) urlquote = allow_lazy(urlquote, six.text_type) def urlquote_plus(url, safe=''): @@ -41,7 +47,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_unicode(urllib.quote_plus(smart_str(url), smart_str(safe))) + return force_unicode(urllib_parse.quote_plus(smart_str(url), smart_str(safe))) urlquote_plus = allow_lazy(urlquote_plus, six.text_type) def urlunquote(quoted_url): @@ -49,7 +55,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_unicode(urllib.unquote(smart_str(quoted_url))) + return force_unicode(urllib_parse.unquote(smart_str(quoted_url))) urlunquote = allow_lazy(urlunquote, six.text_type) def urlunquote_plus(quoted_url): @@ -57,7 +63,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_unicode(urllib.unquote_plus(smart_str(quoted_url))) + return force_unicode(urllib_parse.unquote_plus(smart_str(quoted_url))) urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type) def urlencode(query, doseq=0): @@ -70,7 +76,7 @@ def urlencode(query, doseq=0): query = query.lists() elif hasattr(query, 'items'): query = query.items() - return urllib.urlencode( + return urllib_parse.urlencode( [(smart_str(k), [smart_str(i) for i in v] if isinstance(v, (list,tuple)) else smart_str(v)) for k, v in query], @@ -212,5 +218,5 @@ def same_origin(url1, url2): """ Checks if two URLs are 'same-origin' """ - p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2) + p1, p2 = urllib_parse.urlparse(url1), urllib_parse.urlparse(url2) return (p1.scheme, p1.hostname, p1.port) == (p2.scheme, p2.hostname, p2.port) diff --git a/django/views/static.py b/django/views/static.py index 64f0b1c262..bcac9475e2 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -9,7 +9,10 @@ import os import stat import posixpath import re -import urllib +try: + from urllib.parse import unquote +except ImportError: # Python 2 + from urllib import unquote from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified from django.template import loader, Template, Context, TemplateDoesNotExist @@ -30,7 +33,7 @@ def serve(request, path, document_root=None, show_indexes=False): but if you'd like to override it, you can create a template called ``static/directory_index.html``. """ - path = posixpath.normpath(urllib.unquote(path)) + path = posixpath.normpath(unquote(path)) path = path.lstrip('/') newpath = '' for part in path.split('/'): diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index 6ea7213a02..477a27b178 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -1,3 +1,7 @@ +try: + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode from xml.dom.minidom import parseString from django.contrib.auth.decorators import login_required, permission_required @@ -9,7 +13,6 @@ from django.shortcuts import render_to_response from django.template import Context, Template from django.utils.decorators import method_decorator - def get_view(request): "A simple view that expects a GET request, and returns a rendered template" t = Template('This is a test. {{ var }} is the value.', name='GET Template') @@ -58,7 +61,6 @@ def raw_post_view(request): def redirect_view(request): "A view that redirects all requests to the GET view" if request.GET: - from urllib import urlencode query = '?' + urlencode(request.GET, True) else: query = '' diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index a139016f27..09be55baba 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -4,7 +4,10 @@ from __future__ import absolute_import, unicode_literals import os import re import datetime -import urlparse +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin from django.conf import settings, global_settings from django.core import mail @@ -3199,7 +3202,7 @@ class RawIdFieldsTest(TestCase): popup_url = m.groups()[0].replace("&", "&") # Handle relative links - popup_url = urlparse.urljoin(response.request['PATH_INFO'], popup_url) + popup_url = urljoin(response.request['PATH_INFO'], popup_url) # Get the popup response2 = self.client.get(popup_url) self.assertContains(response2, "Spain") diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py index 9537e1feb3..b98b4b73c2 100644 --- a/tests/regressiontests/servers/tests.py +++ b/tests/regressiontests/servers/tests.py @@ -2,7 +2,10 @@ Tests for django.core.servers. """ import os -import urllib2 +try: + from urllib.request import urlopen, HTTPError +except ImportError: # Python 2 + from urllib2 import urlopen, HTTPError from django.core.exceptions import ImproperlyConfigured from django.test import LiveServerTestCase @@ -39,7 +42,7 @@ class LiveServerBase(LiveServerTestCase): super(LiveServerBase, cls).tearDownClass() def urlopen(self, url): - return urllib2.urlopen(self.live_server_url + url) + return urlopen(self.live_server_url + url) class LiveServerAddress(LiveServerBase): @@ -102,7 +105,7 @@ class LiveServerViews(LiveServerBase): """ try: self.urlopen('/') - except urllib2.HTTPError as err: + except HTTPError as err: self.assertEqual(err.code, 404, 'Expected 404 response') else: self.fail('Expected 404 response') diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 4aa71f9709..402cbb19d2 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -13,7 +13,10 @@ import time import os import sys import traceback -from urlparse import urljoin +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin from django import template from django.template import base as template_base, RequestContext, Template, Context From ca07fda2efea24cb43423b884fa4648d44e52963 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 16:16:57 +0200 Subject: [PATCH 234/519] [py3] Switched to Python 3-compatible imports. xrange/range will be dealt with in a separate commit due to the huge number of changes. --- django/contrib/auth/tests/basic.py | 6 ++--- django/contrib/auth/tests/management.py | 3 +-- django/contrib/gis/db/models/sql/compiler.py | 2 +- django/contrib/gis/gdal/tests/test_geom.py | 2 +- django/contrib/gis/geos/tests/test_geos.py | 3 ++- django/contrib/gis/utils/ogrinspect.py | 2 +- django/contrib/sessions/backends/base.py | 2 +- .../sessions/backends/signed_cookies.py | 2 +- django/core/cache/backends/db.py | 2 +- django/core/cache/backends/filebased.py | 2 +- django/core/cache/backends/locmem.py | 2 +- django/core/servers/basehttp.py | 4 ++-- django/db/backends/__init__.py | 2 +- django/db/models/base.py | 2 +- django/db/models/sql/compiler.py | 2 +- django/http/__init__.py | 22 +++++++++---------- django/test/_doctest.py | 2 +- django/test/html.py | 3 +-- django/utils/autoreload.py | 2 +- django/utils/html_parser.py | 18 ++++++++------- django/utils/itercompat.py | 6 ++--- django/utils/six.py | 5 +++++ django/utils/text.py | 4 ++-- django/utils/translation/trans_real.py | 2 +- 24 files changed, 54 insertions(+), 48 deletions(-) diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py index 4c2e8cd8aa..21acb2004f 100644 --- a/django/contrib/auth/tests/basic.py +++ b/django/contrib/auth/tests/basic.py @@ -1,8 +1,8 @@ -from django.test import TestCase -from django.utils.unittest import skipUnless from django.contrib.auth.models import User, AnonymousUser from django.core.management import call_command -from StringIO import StringIO +from django.test import TestCase +from django.utils.six import StringIO +from django.utils.unittest import skipUnless try: import crypt as crypt_module diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py index 8d1f7c7965..c98b7491c8 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -1,11 +1,10 @@ from __future__ import unicode_literals -from StringIO import StringIO - 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.six import StringIO class GetDefaultUsernameTestCase(TestCase): diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index ebaee60bd0..d016357f1b 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -1,4 +1,4 @@ -from future_builtins import zip +from django.utils.six.moves import zip from django.db.backends.util import truncate_name, typecast_timestamp from django.db.models.sql import compiler diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index 20e25946b0..e5c550b0d0 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -1,6 +1,6 @@ from binascii import b2a_hex try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index b1d00d5241..102c6ba55a 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -952,7 +952,8 @@ class GEOSTest(unittest.TestCase, TestDataMixin): def test_pickle(self): "Testing pickling and unpickling support." # Using both pickle and cPickle -- just 'cause. - import pickle, cPickle + from django.utils.six.moves import cPickle + import pickle # Creating a list of test geometries for pickling, # and setting the SRID on some of them. diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py index f87bb24c7f..f8977059d9 100644 --- a/django/contrib/gis/utils/ogrinspect.py +++ b/django/contrib/gis/utils/ogrinspect.py @@ -5,7 +5,7 @@ models for GeoDjango and/or mapping dictionaries for use with the Author: Travis Pinney, Dane Springmeyer, & Justin Bronn """ -from future_builtins import zip +from django.utils.six.moves import zip # Requires GDAL to use. from django.contrib.gis.gdal import DataSource from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 5a637e24d2..153cde9830 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -2,7 +2,7 @@ import base64 import time from datetime import datetime, timedelta try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/contrib/sessions/backends/signed_cookies.py b/django/contrib/sessions/backends/signed_cookies.py index 2a0f261441..41ba7af634 100644 --- a/django/contrib/sessions/backends/signed_cookies.py +++ b/django/contrib/sessions/backends/signed_cookies.py @@ -1,5 +1,5 @@ try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 1ac6ef5d9b..f60b4e0cd1 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -4,7 +4,7 @@ import time from datetime import datetime try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 7f9f7175be..1170996a76 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -5,7 +5,7 @@ import os import shutil import time try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py index 9196b3b42b..76667e9609 100644 --- a/django/core/cache/backends/locmem.py +++ b/django/core/cache/backends/locmem.py @@ -2,7 +2,7 @@ import time try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index f67b105397..2db4e5ef6a 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -16,7 +16,7 @@ try: except ImportError: # Python 2 from urllib import unquote from urlparse import urljoin -from SocketServer import ThreadingMixIn +from django.utils.six.moves import socketserver from wsgiref import simple_server from wsgiref.util import FileWrapper # for backwards compatibility @@ -200,7 +200,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', (ThreadingMixIn, WSGIServer), {}) + httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, WSGIServer), {}) else: httpd_cls = WSGIServer httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index b416343f88..a896f5fd08 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -3,7 +3,7 @@ from django.db.utils import DatabaseError try: import thread except ImportError: - import dummy_thread as thread + from django.utils.six.moves import _dummy_thread as thread from contextlib import contextmanager from django.conf import settings diff --git a/django/db/models/base.py b/django/db/models/base.py index 8c448b2f39..567fb53217 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import copy import sys from functools import update_wrapper -from future_builtins import zip +from django.utils.six.moves import zip import django.db.models.manager # Imported to register signal handler. from django.conf import settings diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index d44cdfe4a4..7a0afa349d 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1,4 +1,4 @@ -from future_builtins import zip +from django.utils.six.moves import zip from django.core.exceptions import FieldError from django.db import transaction diff --git a/django/http/__init__.py b/django/http/__init__.py index da97506c8c..6f9d70eebd 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -16,23 +16,23 @@ except ImportError: # Python 2 from urllib import quote, urlencode from urlparse import parse_qsl, urljoin -import Cookie +from django.utils.six.moves import http_cookies # Some versions of Python 2.7 and later won't need this encoding bug fix: -_cookie_encodes_correctly = Cookie.SimpleCookie().value_encode(';') == (';', '"\\073"') +_cookie_encodes_correctly = http_cookies.SimpleCookie().value_encode(';') == (';', '"\\073"') # See ticket #13007, http://bugs.python.org/issue2193 and http://trac.edgewall.org/ticket/2256 -_tc = Cookie.SimpleCookie() +_tc = http_cookies.SimpleCookie() try: _tc.load(b'foo:bar=1') _cookie_allows_colon_in_names = True -except Cookie.CookieError: +except http_cookies.CookieError: _cookie_allows_colon_in_names = False if _cookie_encodes_correctly and _cookie_allows_colon_in_names: - SimpleCookie = Cookie.SimpleCookie + SimpleCookie = http_cookies.SimpleCookie else: - Morsel = Cookie.Morsel + Morsel = http_cookies.Morsel - class SimpleCookie(Cookie.SimpleCookie): + class SimpleCookie(http_cookies.SimpleCookie): if not _cookie_encodes_correctly: def value_encode(self, val): # Some browsers do not support quoted-string from RFC 2109, @@ -73,9 +73,9 @@ else: M = self.get(key, Morsel()) M.set(key, real_value, coded_value) dict.__setitem__(self, key, M) - except Cookie.CookieError: + except http_cookies.CookieError: self.bad_cookies.add(key) - dict.__setitem__(self, key, Cookie.Morsel()) + dict.__setitem__(self, key, http_cookies.Morsel()) from django.conf import settings @@ -495,11 +495,11 @@ class QueryDict(MultiValueDict): def parse_cookie(cookie): if cookie == '': return {} - if not isinstance(cookie, Cookie.BaseCookie): + if not isinstance(cookie, http_cookies.BaseCookie): try: c = SimpleCookie() c.load(cookie) - except Cookie.CookieError: + except http_cookies.CookieError: # Invalid cookie return {} else: diff --git a/django/test/_doctest.py b/django/test/_doctest.py index 75f16e202a..316c785f33 100644 --- a/django/test/_doctest.py +++ b/django/test/_doctest.py @@ -103,9 +103,9 @@ import __future__ import sys, traceback, inspect, linecache, os, re import unittest, difflib, pdb, tempfile import warnings -from StringIO import StringIO from django.utils import six +from django.utils.six import StringIO if sys.platform.startswith('java'): # On Jython, isclass() reports some modules as classes. Patch it. diff --git a/django/test/html.py b/django/test/html.py index 8d577d91fd..2a1421bf17 100644 --- a/django/test/html.py +++ b/django/test/html.py @@ -5,9 +5,8 @@ Comparing two html documents. from __future__ import unicode_literals import re -from HTMLParser import HTMLParseError from django.utils.encoding import force_unicode -from django.utils.html_parser import HTMLParser +from django.utils.html_parser import HTMLParser, HTMLParseError from django.utils import six diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 85d9907856..b6c055383c 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -33,7 +33,7 @@ import os, sys, time, signal try: import thread except ImportError: - import dummy_thread as thread + from django.utils.six.moves import _dummy_thread as thread # This import does nothing, but it's necessary to avoid some race conditions # in the threading module. See http://code.djangoproject.com/ticket/2330 . diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py index 98f6545c41..ee56c01aec 100644 --- a/django/utils/html_parser.py +++ b/django/utils/html_parser.py @@ -1,26 +1,28 @@ -import HTMLParser as _HTMLParser +from django.utils.six.moves import html_parser as _html_parser import re tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*') -class HTMLParser(_HTMLParser.HTMLParser): +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): - _HTMLParser.HTMLParser.__init__(self) + _html_parser.HTMLParser.__init__(self) self.cdata_tag = None def set_cdata_mode(self, tag): try: - self.interesting = _HTMLParser.interesting_cdata + self.interesting = _html_parser.interesting_cdata except AttributeError: self.interesting = re.compile(r'' % tag.lower(), re.I) self.cdata_tag = tag.lower() def clear_cdata_mode(self): - self.interesting = _HTMLParser.interesting_normal + self.interesting = _html_parser.interesting_normal self.cdata_tag = None # Internal -- handle starttag, return end or -1 if not terminated @@ -40,7 +42,7 @@ class HTMLParser(_HTMLParser.HTMLParser): self.lasttag = tag = match.group(1).lower() while k < endpos: - m = _HTMLParser.attrfind.match(rawdata, k) + m = _html_parser.attrfind.match(rawdata, k) if not m: break attrname, rest, attrvalue = m.group(1, 2, 3) @@ -78,11 +80,11 @@ class HTMLParser(_HTMLParser.HTMLParser): def parse_endtag(self, i): rawdata = self.rawdata assert rawdata[i:i + 2] == " + match = _html_parser.endendtag.search(rawdata, i + 1) # > if not match: return -1 j = match.end() - match = _HTMLParser.endtagfind.match(rawdata, i) # + match = _html_parser.endtagfind.match(rawdata, i) # if not match: if self.cdata_tag is not None: # *** add *** self.handle_data(rawdata[i:j]) # *** add *** diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py index 2f016b1c3f..aa329c152e 100644 --- a/django/utils/itercompat.py +++ b/django/utils/itercompat.py @@ -4,7 +4,7 @@ Where possible, we try to use the system-native version and only fall back to these implementations if necessary. """ -import __builtin__ +from django.utils.six.moves import builtins import itertools import warnings @@ -25,9 +25,9 @@ def product(*args, **kwds): def all(iterable): warnings.warn("django.utils.itercompat.all is deprecated; use the native version instead", DeprecationWarning) - return __builtin__.all(iterable) + return builtins.all(iterable) def any(iterable): warnings.warn("django.utils.itercompat.any is deprecated; use the native version instead", DeprecationWarning) - return __builtin__.any(iterable) + return builtins.any(iterable) diff --git a/django/utils/six.py b/django/utils/six.py index b1d58e5668..c74f9fa7df 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -351,3 +351,8 @@ _add_doc(reraise, """Reraise an exception.""") def with_metaclass(meta, base=object): """Create a base class with a metaclass.""" return meta("NewBase", (base,), {}) + + +### Additional customizations for Django ### + +add_move(MovedModule("_dummy_thread", "dummy_thread")) diff --git a/django/utils/text.py b/django/utils/text.py index 9f773ad41b..43056aa634 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -4,7 +4,7 @@ import re import unicodedata import warnings from gzip import GzipFile -from htmlentitydefs import name2codepoint +from django.utils.six.moves import html_entities from io import BytesIO from django.utils.encoding import force_unicode @@ -349,7 +349,7 @@ def _replace_entity(match): return match.group(0) else: try: - return unichr(name2codepoint[text]) + return unichr(html_entities.name2codepoint[text]) except (ValueError, KeyError): return match.group(0) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 0cd13fd6f5..9ebcbf5441 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -6,11 +6,11 @@ import os import re import sys import gettext as gettext_module -from io import StringIO from threading import local from django.utils.importlib import import_module from django.utils.safestring import mark_safe, SafeData +from django.utils.six import StringIO # Translations are cached in a dictionary for every language+app tuple. From a84d79f572fbe7512b999c6b3cd7667cbe3138ff Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 18:53:11 +0200 Subject: [PATCH 235/519] [py3] Added Python 3 compatibility for xrange. --- django/contrib/gis/gdal/datasource.py | 1 + django/contrib/gis/gdal/feature.py | 1 + django/contrib/gis/gdal/geometries.py | 1 + django/contrib/gis/gdal/layer.py | 1 + django/contrib/gis/gdal/tests/test_geom.py | 1 + django/contrib/gis/geos/collections.py | 9 +++-- django/contrib/gis/geos/coordseq.py | 1 + django/contrib/gis/geos/linestring.py | 3 +- django/contrib/gis/geos/mutable_list.py | 1 + django/contrib/gis/geos/point.py | 1 + django/contrib/gis/geos/polygon.py | 1 + django/contrib/gis/geos/prototypes/misc.py | 1 + django/contrib/gis/geos/tests/test_geos.py | 1 + django/contrib/gis/maps/google/gmap.py | 1 + django/contrib/gis/maps/google/zoom.py | 21 +++++----- django/contrib/localflavor/mx/forms.py | 4 +- django/contrib/localflavor/tr/forms.py | 6 +-- django/contrib/messages/tests/base.py | 10 ++--- django/contrib/messages/tests/cookie.py | 2 +- django/contrib/sessions/backends/cache.py | 1 + django/db/models/sql/where.py | 1 + django/dispatch/dispatcher.py | 39 ++++++++++--------- django/forms/formsets.py | 1 + django/utils/crypto.py | 1 + django/utils/importlib.py | 2 +- django/utils/ipv6.py | 1 + tests/modeltests/delete/tests.py | 1 + tests/modeltests/many_to_one_null/tests.py | 4 +- tests/regressiontests/backends/tests.py | 5 ++- .../dispatch/tests/test_saferef.py | 4 +- tests/regressiontests/middleware/tests.py | 1 + tests/regressiontests/utils/datastructures.py | 2 +- 32 files changed, 77 insertions(+), 53 deletions(-) diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index 1797ed3320..4ceddc6c72 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -46,6 +46,7 @@ from django.contrib.gis.gdal.layer import Layer from django.contrib.gis.gdal.prototypes import ds as capi from django.utils import six +from django.utils.six.moves import xrange # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index 52eadfa06f..292004873d 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -8,6 +8,7 @@ from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api from django.utils import six +from django.utils.six.moves import xrange # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 4ad8f91d06..d752104e0a 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -58,6 +58,7 @@ from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex from django.utils import six +from django.utils.six.moves import xrange # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index e3aebeeda5..2357fbb88a 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -15,6 +15,7 @@ from django.contrib.gis.gdal.srs import SpatialReference from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api from django.utils import six +from django.utils.six.moves import xrange # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index e5c550b0d0..a0b2593605 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -7,6 +7,7 @@ except ImportError: from django.contrib.gis.gdal import (OGRGeometry, OGRGeomType, OGRException, OGRIndexError, SpatialReference, CoordTransform, GDAL_VERSION) from django.contrib.gis.geometry.test_data import TestDataMixin +from django.utils.six.moves import xrange from django.utils import unittest class OGRGeomTest(unittest.TestCase, TestDataMixin): diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py index 8b0edf5985..2b62bce22c 100644 --- a/django/contrib/gis/geos/collections.py +++ b/django/contrib/gis/geos/collections.py @@ -10,6 +10,7 @@ from django.contrib.gis.geos.linestring import LineString, LinearRing from django.contrib.gis.geos.point import Point from django.contrib.gis.geos.polygon import Polygon from django.contrib.gis.geos import prototypes as capi +from django.utils.six.moves import xrange class GeometryCollection(GEOSGeometry): _typeid = 7 @@ -100,11 +101,11 @@ class MultiLineString(GeometryCollection): @property def merged(self): - """ - Returns a LineString representing the line merge of this + """ + Returns a LineString representing the line merge of this MultiLineString. - """ - return self._topology(capi.geos_linemerge(self.ptr)) + """ + return self._topology(capi.geos_linemerge(self.ptr)) class MultiPolygon(GeometryCollection): _allowed = Polygon diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py index 027d34e740..acf34f7262 100644 --- a/django/contrib/gis/geos/coordseq.py +++ b/django/contrib/gis/geos/coordseq.py @@ -8,6 +8,7 @@ from django.contrib.gis.geos.base import GEOSBase, numpy from django.contrib.gis.geos.error import GEOSException, GEOSIndexError from django.contrib.gis.geos.libgeos import CS_PTR from django.contrib.gis.geos import prototypes as capi +from django.utils.six.moves import xrange class GEOSCoordSeq(GEOSBase): "The internal representation of a list of coordinates inside a Geometry." diff --git a/django/contrib/gis/geos/linestring.py b/django/contrib/gis/geos/linestring.py index ecf774145e..4784ea7c2d 100644 --- a/django/contrib/gis/geos/linestring.py +++ b/django/contrib/gis/geos/linestring.py @@ -4,6 +4,7 @@ from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos.point import Point from django.contrib.gis.geos import prototypes as capi +from django.utils.six.moves import xrange class LineString(GEOSGeometry): _init_func = capi.create_linestring @@ -128,7 +129,7 @@ class LineString(GEOSGeometry): @property def merged(self): "Returns the line merge of this LineString." - return self._topology(capi.geos_linemerge(self.ptr)) + return self._topology(capi.geos_linemerge(self.ptr)) @property def x(self): diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py index ea5571ad4c..69e50e6b3f 100644 --- a/django/contrib/gis/geos/mutable_list.py +++ b/django/contrib/gis/geos/mutable_list.py @@ -10,6 +10,7 @@ Author: Aryeh Leib Taurog. """ from django.utils.functional import total_ordering from django.utils import six +from django.utils.six.moves import xrange @total_ordering class ListMixin(object): diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py index 6ba5800d4a..907347dbf8 100644 --- a/django/contrib/gis/geos/point.py +++ b/django/contrib/gis/geos/point.py @@ -3,6 +3,7 @@ from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos import prototypes as capi from django.utils import six +from django.utils.six.moves import xrange class Point(GEOSGeometry): _minlength = 2 diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py index bb02689c81..c50f549e7a 100644 --- a/django/contrib/gis/geos/polygon.py +++ b/django/contrib/gis/geos/polygon.py @@ -4,6 +4,7 @@ from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR from django.contrib.gis.geos.linestring import LinearRing from django.contrib.gis.geos import prototypes as capi from django.utils import six +from django.utils.six.moves import xrange class Polygon(GEOSGeometry): _minlength = 1 diff --git a/django/contrib/gis/geos/prototypes/misc.py b/django/contrib/gis/geos/prototypes/misc.py index fd4f78a011..5a9b5555ad 100644 --- a/django/contrib/gis/geos/prototypes/misc.py +++ b/django/contrib/gis/geos/prototypes/misc.py @@ -7,6 +7,7 @@ from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string from django.contrib.gis.geos.prototypes.geom import geos_char_p from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc +from django.utils.six.moves import xrange __all__ = ['geos_area', 'geos_distance', 'geos_length'] diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 102c6ba55a..d621c6b4d4 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -9,6 +9,7 @@ from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geometry.test_data import TestDataMixin from django.utils import six +from django.utils.six.moves import xrange from django.utils import unittest diff --git a/django/contrib/gis/maps/google/gmap.py b/django/contrib/gis/maps/google/gmap.py index 72c50eab0f..75b285ca76 100644 --- a/django/contrib/gis/maps/google/gmap.py +++ b/django/contrib/gis/maps/google/gmap.py @@ -2,6 +2,7 @@ from django.conf import settings from django.template.loader import render_to_string from django.utils.html import format_html from django.utils.safestring import mark_safe +from django.utils.six.moves import xrange from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline, GMarker diff --git a/django/contrib/gis/maps/google/zoom.py b/django/contrib/gis/maps/google/zoom.py index fc9ff1db96..c93cf4ef48 100644 --- a/django/contrib/gis/maps/google/zoom.py +++ b/django/contrib/gis/maps/google/zoom.py @@ -1,5 +1,6 @@ from django.contrib.gis.geos import GEOSGeometry, LinearRing, Polygon, Point from django.contrib.gis.maps.google.gmap import GoogleMapException +from django.utils.six.moves import xrange from math import pi, sin, log, exp, atan # Constants used for degree to radian conversion, and vice-versa. @@ -20,21 +21,21 @@ class GoogleZoom(object): "Google Maps Hacks" may be found at http://safari.oreilly.com/0596101619 """ - + def __init__(self, num_zoom=19, tilesize=256): "Initializes the Google Zoom object." # Google's tilesize is 256x256, square tiles are assumed. self._tilesize = tilesize - + # The number of zoom levels self._nzoom = num_zoom - # Initializing arrays to hold the parameters for each one of the + # Initializing arrays to hold the parameters for each one of the # zoom levels. self._degpp = [] # Degrees per pixel self._radpp = [] # Radians per pixel self._npix = [] # 1/2 the number of pixels for a tile at the given zoom level - + # Incrementing through the zoom levels and populating the parameter arrays. z = tilesize # The number of pixels per zoom level. for i in xrange(num_zoom): @@ -70,9 +71,9 @@ class GoogleZoom(object): # with with the number of degrees/pixel at the given zoom level. px_x = round(npix + (lon * self._degpp[zoom])) - # Creating the factor, and ensuring that 1 or -1 is not passed in as the + # Creating the factor, and ensuring that 1 or -1 is not passed in as the # base to the logarithm. Here's why: - # if fac = -1, we'll get log(0) which is undefined; + # if fac = -1, we'll get log(0) which is undefined; # if fac = 1, our logarithm base will be divided by 0, also undefined. fac = min(max(sin(DTOR * lat), -0.9999), 0.9999) @@ -98,7 +99,7 @@ class GoogleZoom(object): # Returning the longitude, latitude coordinate pair. return (lon, lat) - + def tile(self, lonlat, zoom): """ Returns a Polygon corresponding to the region represented by a fictional @@ -119,7 +120,7 @@ class GoogleZoom(object): # Constructing the Polygon, representing the tile and returning. return Polygon(LinearRing(ll, (ll[0], ur[1]), ur, (ur[0], ll[1]), ll), srid=4326) - + def get_zoom(self, geom): "Returns the optimal Zoom level for the given geometry." # Checking the input type. @@ -139,10 +140,10 @@ class GoogleZoom(object): # When we span more than one tile, this is an approximately good # zoom level. if (env_w > tile_w) or (env_h > tile_h): - if z == 0: + if z == 0: raise GoogleMapException('Geometry width and height should not exceed that of the Earth.') return z-1 - + # Otherwise, we've zoomed in to the max. return self._nzoom-1 diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py index 4a7c005ad5..b42bf22b89 100644 --- a/django/contrib/localflavor/mx/forms.py +++ b/django/contrib/localflavor/mx/forms.py @@ -148,7 +148,7 @@ class MXRFCField(RegexField): if len(rfc) == 11: rfc = '-' + rfc - sum_ = sum(i * chars.index(c) for i, c in zip(reversed(xrange(14)), rfc)) + sum_ = sum(i * chars.index(c) for i, c in zip(reversed(range(14)), rfc)) checksum = 11 - sum_ % 11 if checksum == 10: @@ -215,7 +215,7 @@ class MXCURPField(RegexField): def _checksum(self, value): chars = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ' - s = sum(i * chars.index(c) for i, c in zip(reversed(xrange(19)), value)) + s = sum(i * chars.index(c) for i, c in zip(reversed(range(19)), value)) checksum = 10 - s % 10 if checksum == 10: diff --git a/django/contrib/localflavor/tr/forms.py b/django/contrib/localflavor/tr/forms.py index 1ffbc17361..15bc1f99bb 100644 --- a/django/contrib/localflavor/tr/forms.py +++ b/django/contrib/localflavor/tr/forms.py @@ -80,10 +80,10 @@ class TRIdentificationNumberField(Field): raise ValidationError(self.error_messages['invalid']) if int(value[0]) == 0: raise ValidationError(self.error_messages['invalid']) - chksum = (sum([int(value[i]) for i in xrange(0,9,2)])*7- - sum([int(value[i]) for i in xrange(1,9,2)])) % 10 + chksum = (sum([int(value[i]) for i in range(0, 9, 2)]) * 7 - + sum([int(value[i]) for i in range(1, 9, 2)])) % 10 if chksum != int(value[9]) or \ - (sum([int(value[i]) for i in xrange(10)]) % 10) != int(value[10]): + (sum([int(value[i]) for i in range(10)]) % 10) != int(value[10]): raise ValidationError(self.error_messages['invalid']) return value diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py index 1f64c61ecb..e9a67b0500 100644 --- a/django/contrib/messages/tests/base.py +++ b/django/contrib/messages/tests/base.py @@ -152,7 +152,7 @@ class BaseTest(TestCase): cycle. """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show') for level in ('debug', 'info', 'success', 'warning', 'error'): @@ -170,7 +170,7 @@ class BaseTest(TestCase): @override_settings(MESSAGE_LEVEL=constants.DEBUG) def test_with_template_response(self): data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show_template_response') for level in self.levels.keys(): @@ -194,7 +194,7 @@ class BaseTest(TestCase): before a GET. """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show') messages = [] @@ -226,7 +226,7 @@ class BaseTest(TestCase): when one attempts to store a message. """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show') for level in ('debug', 'info', 'success', 'warning', 'error'): @@ -251,7 +251,7 @@ class BaseTest(TestCase): raised if 'fail_silently' = True """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], 'fail_silently': True, } show_url = reverse('django.contrib.messages.tests.urls.show') diff --git a/django/contrib/messages/tests/cookie.py b/django/contrib/messages/tests/cookie.py index 477eb72e56..e0668ab604 100644 --- a/django/contrib/messages/tests/cookie.py +++ b/django/contrib/messages/tests/cookie.py @@ -123,7 +123,7 @@ class CookieTest(BaseTest): { 'message': Message(constants.INFO, 'Test message'), 'message_list': [Message(constants.INFO, 'message %s') \ - for x in xrange(5)] + [{'another-message': \ + for x in range(5)] + [{'another-message': \ Message(constants.ERROR, 'error')}], }, Message(constants.INFO, 'message %s'), diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index 467d5f1265..b66123b915 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -1,5 +1,6 @@ from django.contrib.sessions.backends.base import SessionBase, CreateError from django.core.cache import cache +from django.utils.six.moves import xrange KEY_PREFIX = "django.contrib.sessions.cache" diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 2602c3066c..47f4ffaba9 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -12,6 +12,7 @@ from django.utils import tree from django.db.models.fields import Field from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.aggregates import Aggregate +from django.utils.six.moves import xrange # Connection types AND = 'AND' diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index e7f440a7c2..ad7302176e 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -2,6 +2,7 @@ import weakref import threading from django.dispatch import saferef +from django.utils.six.moves import xrange WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) @@ -13,17 +14,17 @@ def _make_id(target): class Signal(object): """ Base class for all signals - + Internal attributes: - + receivers { receriverkey (id) : weakref(receiver) } """ - + def __init__(self, providing_args=None): """ Create a new signal. - + providing_args A list of the arguments this signal can pass along in a send() call. """ @@ -36,9 +37,9 @@ class Signal(object): def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): """ Connect receiver to sender for signal. - + Arguments: - + receiver A function or an instance method which is to receive signals. Receivers must be hashable objects. @@ -46,7 +47,7 @@ class Signal(object): If weak is True, then receiver must be weak-referencable (more precisely saferef.safeRef() must be able to create a reference to the receiver). - + Receivers must be able to accept keyword arguments. If receivers have a dispatch_uid attribute, the receiver will @@ -62,19 +63,19 @@ class Signal(object): module will attempt to use weak references to the receiver objects. If this parameter is false, then strong references will be used. - + dispatch_uid An identifier used to uniquely identify a particular instance of a receiver. This will usually be a string, though it may be anything hashable. """ from django.conf import settings - + # If DEBUG is on, check that we got a good receiver if settings.DEBUG: import inspect assert callable(receiver), "Signal receivers must be callable." - + # Check for **kwargs # Not all callables are inspectable with getargspec, so we'll # try a couple different ways but in the end fall back on assuming @@ -90,7 +91,7 @@ class Signal(object): if argspec: assert argspec[2] is not None, \ "Signal receivers must accept keyword arguments (**kwargs)." - + if dispatch_uid: lookup_key = (dispatch_uid, _make_id(sender)) else: @@ -112,19 +113,19 @@ class Signal(object): If weak references are used, disconnect need not be called. The receiver will be remove from dispatch automatically. - + Arguments: - + receiver The registered receiver to disconnect. May be none if dispatch_uid is specified. - + sender The registered sender to disconnect - + weak The weakref state to disconnect - + dispatch_uid the unique identifier of the receiver to disconnect """ @@ -149,10 +150,10 @@ class Signal(object): receivers called if a raises an error. Arguments: - + sender The sender of the signal Either a specific object or None. - + named Named arguments which will be passed to receivers. @@ -172,7 +173,7 @@ class Signal(object): Send signal from sender to all connected receivers catching errors. Arguments: - + sender The sender of the signal. Can be any python object (normally one registered with a connect if you actually want something to diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 8a61f6cd62..240cf71f43 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -8,6 +8,7 @@ from django.forms.widgets import Media, HiddenInput from django.utils.encoding import StrAndUnicode from django.utils.safestring import mark_safe from django.utils import six +from django.utils.six.moves import xrange from django.utils.translation import ugettext as _ diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 8c9649eef1..67b628625e 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -24,6 +24,7 @@ except NotImplementedError: from django.conf import settings from django.utils.encoding import smart_str +from django.utils.six.moves import xrange _trans_5c = b"".join([chr(x ^ 0x5C) for x in xrange(256)]) diff --git a/django/utils/importlib.py b/django/utils/importlib.py index ef4d0e4a27..703ba7f6d8 100644 --- a/django/utils/importlib.py +++ b/django/utils/importlib.py @@ -6,7 +6,7 @@ def _resolve_name(name, package, level): if not hasattr(package, 'rindex'): raise ValueError("'package' not set to a string") dot = len(package) - for x in xrange(level, 1, -1): + for x in range(level, 1, -1): try: dot = package.rindex('.', 0, dot) except ValueError: diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py index e80a0e205f..2ccae8cdd0 100644 --- a/django/utils/ipv6.py +++ b/django/utils/ipv6.py @@ -2,6 +2,7 @@ # Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/ # Licensed under the Apache License, Version 2.0 (the "License"). from django.core.exceptions import ValidationError +from django.utils.six.moves import xrange def clean_ipv6_address(ip_str, unpack_ipv4=False, error_message="This is not a valid IPv6 address"): diff --git a/tests/modeltests/delete/tests.py b/tests/modeltests/delete/tests.py index d681a76fd2..26f2fd52c1 100644 --- a/tests/modeltests/delete/tests.py +++ b/tests/modeltests/delete/tests.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from django.db import models, IntegrityError from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature +from django.utils.six.moves import xrange from .models import (R, RChild, S, T, U, A, M, MR, MRNull, create_a, get_default_r, User, Avatar, HiddenUser, HiddenUserProfile) diff --git a/tests/modeltests/many_to_one_null/tests.py b/tests/modeltests/many_to_one_null/tests.py index 1a404bde02..4de44b5e64 100644 --- a/tests/modeltests/many_to_one_null/tests.py +++ b/tests/modeltests/many_to_one_null/tests.py @@ -88,8 +88,8 @@ class ManyToOneNullTests(TestCase): def test_clear_efficiency(self): r = Reporter.objects.create() - for _ in xrange(3): + for _ in range(3): r.article_set.create() with self.assertNumQueries(1): r.article_set.clear() - self.assertEqual(r.article_set.count(), 0) \ No newline at end of file + self.assertEqual(r.article_set.count(), 0) diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index fa41aa7401..a6425c5591 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -17,6 +17,7 @@ from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature, TransactionTestCase) from django.test.utils import override_settings from django.utils import six +from django.utils.six.moves import xrange from django.utils import unittest from . import models @@ -531,7 +532,7 @@ class ThreadTests(TestCase): from django.db import connection connection.cursor() connections_set.add(connection.connection) - for x in xrange(2): + for x in range(2): t = threading.Thread(target=runner) t.start() t.join() @@ -558,7 +559,7 @@ class ThreadTests(TestCase): # main thread. conn.allow_thread_sharing = True connections_set.add(conn) - for x in xrange(2): + for x in range(2): t = threading.Thread(target=runner) t.start() t.join() diff --git a/tests/regressiontests/dispatch/tests/test_saferef.py b/tests/regressiontests/dispatch/tests/test_saferef.py index 99251c5127..cfe6c5df85 100644 --- a/tests/regressiontests/dispatch/tests/test_saferef.py +++ b/tests/regressiontests/dispatch/tests/test_saferef.py @@ -1,7 +1,7 @@ from django.dispatch.saferef import safeRef +from django.utils.six.moves import xrange from django.utils import unittest - class Test1(object): def x(self): pass @@ -70,4 +70,4 @@ class SaferefTests(unittest.TestCase): def _closure(self, ref): """Dumb utility mechanism to increment deletion counter""" - self.closureCount +=1 \ No newline at end of file + self.closureCount +=1 diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py index 47fca03ba3..ead34f46db 100644 --- a/tests/regressiontests/middleware/tests.py +++ b/tests/regressiontests/middleware/tests.py @@ -15,6 +15,7 @@ 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.six.moves import xrange class CommonMiddlewareTest(TestCase): def setUp(self): diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index 1af5018f3b..08bcd7157a 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -98,7 +98,7 @@ class SortedDictTests(SimpleTestCase): self.assertEqual(l - len(self.d1), 1) def test_dict_equality(self): - d = SortedDict((i, i) for i in xrange(3)) + d = SortedDict((i, i) for i in range(3)) self.assertEqual(d, {0: 0, 1: 1, 2: 2}) def test_tuple_init(self): From 00ace01411b4cb558e71bfaf34cf42870e73092b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 22 Jul 2012 10:29:07 +0200 Subject: [PATCH 236/519] [py3] Documented coding guidelines for Python 3. --- docs/conf.py | 1 + docs/topics/python3.txt | 123 ++++++++++++++++++++++++++++++++-------- 2 files changed, 99 insertions(+), 25 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 659115dfbd..39a280e464 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -94,6 +94,7 @@ pygments_style = 'trac' intersphinx_mapping = { 'python': ('http://docs.python.org/2.7', None), 'sphinx': ('http://sphinx.pocoo.org/', None), + 'six': ('http://packages.python.org/six/', None), } # Python's docs don't change every week. diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index e4bfc1bd9c..cfa38d9bec 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -2,42 +2,34 @@ Python 3 compatibility ====================== -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). To -achieve this: - -- wherever possible, Django uses the six_ compatibility layer, -- all modules declare ``from __future__ import unicode_literals``. +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``. .. _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`_. But -it describes guidelines that apply to Django's code and are recommended for -pluggable apps that run with both Python 2 and 3. +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. .. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html -.. module: django.utils.six +Syntax requirements +=================== -django.utils.six -================ +Unicode +------- -Read the documentation of six_. It's the canonical compatibility library for -supporting Python 2 and 3 in a single codebase. +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 +``bytes``. -``six`` is bundled with Django: you can import it as :mod:`django.utils.six`. +You mustn't use the ``u`` prefix before a unicode string literal because it's +a syntax error in Python 3.2. You must prefix byte strings with ``b``. -.. _string-handling: - -String handling -=============== - -In Python 3, all strings are considered Unicode strings by default. Byte -strings must be prefixed with the letter ``b``. In order to enable the same -behavior in Python 2, every module must import ``unicode_literals`` from -``__future__``:: +In order to enable the same behavior in Python 2, every module must import +``unicode_literals`` from ``__future__``:: from __future__ import unicode_literals @@ -47,3 +39,84 @@ behavior in Python 2, every module must import ``unicode_literals`` from Be cautious if you have to `slice bytestrings`_. .. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals + +Exceptions +---------- + +When you capture exceptions, use the ``as`` keyword:: + + try: + ... + except MyException as exc: + ... + +This older syntax was removed in Python 3:: + + try: + ... + except MyException, exc: + ... + +The syntax to reraise an exception with a different traceback also changed. +Use :func:`six.reraise`. + + +.. 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 `_! + +:mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`. + +Here are the most common changes required to write compatible code. + +String types +------------ + +The ``basestring`` and ``unicode`` types were removed in Python 3, and the +meaning of ``str`` changed. To test these types, use the following idioms:: + + isinstance(myvalue, six.string_types) # replacement for basestring + isinstance(myvalue, six.text_type) # replacement for unicode + isinstance(myvalue, bytes) # replacement for str + +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:: + + 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 +` module provides a compatible location to import them. + +In addition to six' defaults, Django's version provides ``dummy_thread`` as +``_dummy_thread``. + +PY3 +--- + +If you need different code in Python 2 and Python 3, check :data:`six.PY3`:: + + if six.PY3: + # do stuff Python 3-wise + else: + # do stuff Python 2-wise + +This is a last resort solution when :mod:`six` doesn't provide an appropriate +function. From cc65f4ec8db21ee24f954bf52c39749b4b7caca4 Mon Sep 17 00:00:00 2001 From: Roman Haritonov Date: Sun, 22 Jul 2012 18:54:47 +0400 Subject: [PATCH 237/519] Documentation: Fix link to uWSGI deployment --- docs/topics/install.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 291b22cb3e..a11a44baa1 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -62,7 +62,7 @@ for information on how to configure mod_wsgi once you have it installed. If you can't use mod_wsgi for some reason, fear not: Django supports many other -deployment options. One is :doc:`uWSGI `; it works +deployment options. One is :doc:`uWSGI `; it works very well with `nginx`_. Another is :doc:`FastCGI `, perfect for using Django with servers other than Apache. Additionally, Django follows the WSGI spec (:pep:`3333`), which allows it to run on a variety of From 690cabe2033b43999f576cbe581b8f465903eda0 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 22 Jul 2012 18:05:53 +0200 Subject: [PATCH 238/519] Used a Python 3-compatible syntax for building a translation table --- django/utils/crypto.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 67b628625e..9d46bdd793 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -27,8 +27,8 @@ from django.utils.encoding import smart_str from django.utils.six.moves import xrange -_trans_5c = b"".join([chr(x ^ 0x5C) for x in xrange(256)]) -_trans_36 = b"".join([chr(x ^ 0x36) for x in xrange(256)]) +_trans_5c = bytearray([(x ^ 0x5C) for x in xrange(256)]) +_trans_36 = bytearray([(x ^ 0x36) for x in xrange(256)]) def salted_hmac(key_salt, value, secret=None): From ebc89a800ac743bccb56810526c352fedd2aaa3e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 22 Jul 2012 19:48:10 +0200 Subject: [PATCH 239/519] Fixed a broken link in the Python 3 docs. Thanks ptone for the report. --- docs/topics/python3.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index cfa38d9bec..7c1cb53150 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -66,8 +66,8 @@ Use :func:`six.reraise`. Writing compatible code with six ================================ -six is the canonical compatibility library for supporting Python 2 and 3 in -a single codebase. Read its `documentation `_! +six_ is the canonical compatibility library for supporting Python 2 and 3 in +a single codebase. Read its documentation! :mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`. From ae4125ffce6c0a82e9016f67d60d31bc7595887c Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 23 Jul 2012 13:48:04 +0200 Subject: [PATCH 240/519] Removed a Python 3-compatibility hack. Thanks Preston Holmes for the patch. --- django/db/models/base.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 567fb53217..002e2aff65 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -34,7 +34,10 @@ class ModelBase(type): """ def __new__(cls, name, bases, attrs): super_new = super(ModelBase, cls).__new__ - parents = [b for b in bases if isinstance(b, ModelBase)] + # six.with_metaclass() inserts an extra class called 'NewBase' in the + # inheritance tree: Model -> NewBase -> object. Ignore this class. + parents = [b for b in bases if isinstance(b, ModelBase) and + not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))] if not parents: # If this isn't a subclass of Model, don't do anything special. return super_new(cls, name, bases, attrs) @@ -276,8 +279,7 @@ class ModelState(object): # This impacts validation only; it has no effect on the actual save. self.adding = True - -class ModelWithoutMeta(object): +class Model(six.with_metaclass(ModelBase, object)): _deferred = False def __init__(self, *args, **kwargs): @@ -370,7 +372,7 @@ class ModelWithoutMeta(object): pass if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]) - super(ModelWithoutMeta, self).__init__() + super(Model, self).__init__() signals.post_init.send(sender=self.__class__, instance=self) def __repr__(self): @@ -402,7 +404,7 @@ class ModelWithoutMeta(object): only module-level classes can be pickled by the default path. """ if not self._deferred: - return super(ModelWithoutMeta, self).__reduce__() + return super(Model, self).__reduce__() data = self.__dict__ defers = [] for field in self._meta.fields: @@ -877,14 +879,6 @@ class ModelWithoutMeta(object): raise ValidationError(errors) -# For unknown reasons, six.with_metaclass doesn't work correctly for Model. -# Fallback to exec'ing the appropriate syntax for each Python version. - -if six.PY3: - six.exec_("class Model(ModelWithoutMeta, metaclass=ModelBase): pass") -else: - six.exec_("class Model(ModelWithoutMeta): __metaclass__ = ModelBase") - ############################################ # HELPER FUNCTIONS (CURRIED MODEL METHODS) # From 226a3e7e00357a5c055faa5138d8338243c4c2c9 Mon Sep 17 00:00:00 2001 From: Jeroen Dekkers Date: Tue, 24 Jul 2012 00:28:29 +0200 Subject: [PATCH 241/519] Remove double isinstance check in force_unicode --- django/utils/encoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/utils/encoding.py b/django/utils/encoding.py index f2295444bf..b5f4b76507 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -94,7 +94,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): # output should be. s = ' '.join([force_unicode(arg, encoding, strings_only, errors) for arg in s]) - elif not isinstance(s, six.text_type): + 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. From 38ce709fe44ebf37e6c8531451eab98fe0a552a0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 24 Jul 2012 07:01:57 -0700 Subject: [PATCH 242/519] Added tests for deprecation warnings and fixed the argument order for the warnings. --- django/utils/datastructures.py | 12 ++++++++---- tests/regressiontests/utils/datastructures.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 16741ba36b..41b43b286c 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -196,14 +196,18 @@ class SortedDict(dict): # This, and insert() are deprecated because they cannot be implemented # using collections.OrderedDict (Python 2.7 and up), which we'll # eventually switch to - warnings.warn(PendingDeprecationWarning, - "SortedDict.value_for_index is deprecated", stacklevel=2) + warnings.warn( + "SortedDict.value_for_index is deprecated", PendingDeprecationWarning, + stacklevel=2 + ) return self[self.keyOrder[index]] def insert(self, index, key, value): """Inserts the key, value pair before the item with the given index.""" - warnings.warn(PendingDeprecationWarning, - "SortedDict.insert is deprecated", stacklevel=2) + warnings.warn( + "SortedDict.insert is deprecated", PendingDeprecationWarning, + stacklevel=2 + ) if key in self.keyOrder: n = self.keyOrder.index(key) del self.keyOrder[n] diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index 08bcd7157a..aea5393aab 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -4,6 +4,7 @@ Tests for stuff in django.utils.datastructures. import copy import pickle +import warnings from django.test import SimpleTestCase from django.utils.datastructures import (DictWrapper, ImmutableList, @@ -122,6 +123,21 @@ class SortedDictTests(SimpleTestCase): self.assertEqual(self.d1, {}) self.assertEqual(self.d1.keyOrder, []) + def test_insert(self): + d = SortedDict() + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + d.insert(0, "hello", "world") + assert w[0].category is PendingDeprecationWarning + + def test_value_for_index(self): + d = SortedDict({"a": 3}) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + self.assertEqual(d.value_for_index(0), 3) + assert w[0].category is PendingDeprecationWarning + + class MergeDictTests(SimpleTestCase): def test_simple_mergedict(self): From 6006c1f076ae7661a601ab6efa4087caf935fbba Mon Sep 17 00:00:00 2001 From: nklas Date: Wed, 25 Jul 2012 01:45:56 +0700 Subject: [PATCH 243/519] Fixed a small typo. --- docs/releases/1.4.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index a091869645..01532cc04c 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -37,7 +37,7 @@ Other notable new features in Django 1.4 include: the ability to `bulk insert <#model-objects-bulk-create-in-the-orm>`_ large datasets for improved performance, and `QuerySet.prefetch_related`_, a method to batch-load related objects - in areas where :meth:`~django.db.models.QuerySet.select_related` does't + in areas where :meth:`~django.db.models.QuerySet.select_related` doesn't work. * Some nice security additions, including `improved password hashing`_ From f758bdab5eec3e615598948dd5bcf9bb7b910c9d Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 24 Jul 2012 17:24:16 -0300 Subject: [PATCH 244/519] Fixed #18271 -- Changed stage at which TransactionTestCase flushes DB tables. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the flush was done before the test case execution and now it is performed after it. Other changes to the testing infrastructure include: * TransactionTestCase now doesn't reset autoincrement sequences either (previous behavior can achieved by using `reset_sequences`.) With this, no implicit such reset is performed by any of the provided TestCase classes. * New ordering of test cases: All unittest tes cases are run first and doctests are run at the end. THse changes could be backward-incompatible with test cases that relied on some kind of state being preserved between tests. Please read the relevant sections of the release notes and testing documentation for further details. Thanks Andreas Pelme for the initial patch. Karen Tracey and Anssi Kääriäinen for the feedback and Anssi for reviewing. This also fixes #12408. --- django/core/management/commands/flush.py | 4 +- django/core/management/sql.py | 7 +- django/db/backends/__init__.py | 13 ++ django/db/backends/mysql/base.py | 27 ++-- django/db/backends/oracle/base.py | 21 ++- .../postgresql_psycopg2/operations.py | 34 +++-- django/test/simple.py | 4 +- django/test/testcases.py | 62 +++++--- docs/releases/1.5.txt | 51 +++++++ docs/topics/testing.txt | 137 +++++++++++++----- tests/regressiontests/test_runner/tests.py | 3 + 11 files changed, 267 insertions(+), 96 deletions(-) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 2fc2e7ed26..ac7b7a3599 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -29,6 +29,8 @@ class Command(NoArgsCommand): connection = connections[db] verbosity = int(options.get('verbosity')) interactive = options.get('interactive') + # 'reset_sequences' is a stealth option + reset_sequences = options.get('reset_sequences', True) self.style = no_style() @@ -40,7 +42,7 @@ class Command(NoArgsCommand): except ImportError: pass - sql_list = sql_flush(self.style, connection, only_django=True) + sql_list = sql_flush(self.style, connection, only_django=True, reset_sequences=reset_sequences) if interactive: confirm = raw_input("""You have requested a flush of the database. diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 46d3cf28ed..7579cbe8ab 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -98,7 +98,7 @@ def sql_delete(app, style, connection): return output[::-1] # Reverse it, to deal with table dependencies. -def sql_flush(style, connection, only_django=False): +def sql_flush(style, connection, only_django=False, reset_sequences=True): """ Returns a list of the SQL statements used to flush the database. @@ -109,9 +109,8 @@ def sql_flush(style, connection, only_django=False): tables = connection.introspection.django_table_names(only_existing=True) else: tables = connection.introspection.table_names() - statements = connection.ops.sql_flush( - style, tables, connection.introspection.sequence_list() - ) + seqs = connection.introspection.sequence_list() if reset_sequences else () + statements = connection.ops.sql_flush(style, tables, seqs) return statements def sql_custom(app, style, connection): diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index a896f5fd08..6e23ad5bb5 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -748,11 +748,24 @@ class BaseDatabaseOperations(object): the given database tables (without actually removing the tables themselves). + The returned value also includes SQL statements required to reset DB + sequences passed in :param sequences:. + The `style` argument is a Style object as returned by either color_style() or no_style() in django.core.management.color. """ raise NotImplementedError() + def sequence_reset_by_name_sql(self, style, sequences): + """ + Returns a list of the SQL statements required to reset sequences + passed in :param sequences:. + + The `style` argument is a Style object as returned by either + color_style() or no_style() in django.core.management.color. + """ + return [] + def sequence_reset_sql(self, style, model_list): """ Returns a list of the SQL statements required to reset sequences for diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index ec65207ed8..2222f89cf0 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -262,22 +262,25 @@ class DatabaseOperations(BaseDatabaseOperations): for table in tables: sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table)))) sql.append('SET FOREIGN_KEY_CHECKS = 1;') - - # Truncate already resets the AUTO_INCREMENT field from - # MySQL version 5.0.13 onwards. Refs #16961. - if self.connection.mysql_version < (5,0,13): - sql.extend( - ["%s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('TABLE'), - style.SQL_TABLE(self.quote_name(sequence['table'])), - style.SQL_KEYWORD('AUTO_INCREMENT'), - style.SQL_FIELD('= 1'), - ) for sequence in sequences]) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + # Truncate already resets the AUTO_INCREMENT field from + # MySQL version 5.0.13 onwards. Refs #16961. + if self.connection.mysql_version < (5, 0, 13): + return ["%s %s %s %s %s;" % \ + (style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('TABLE'), + style.SQL_TABLE(self.quote_name(sequence['table'])), + style.SQL_KEYWORD('AUTO_INCREMENT'), + style.SQL_FIELD('= 1'), + ) for sequence in sequences] + else: + return [] + def validate_autopk_value(self, value): # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. if value == 0: diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 32ae420ce0..b08113fed7 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -298,18 +298,23 @@ WHEN (new.%(col_name)s IS NULL) for table in tables] # Since we've just deleted all the rows, running our sequence # ALTER code will reset the sequence to 0. - for sequence_info in sequences: - sequence_name = self._get_sequence_name(sequence_info['table']) - table_name = self.quote_name(sequence_info['table']) - column_name = self.quote_name(sequence_info['column'] or 'id') - query = _get_sequence_reset_sql() % {'sequence': sequence_name, - 'table': table_name, - 'column': column_name} - sql.append(query) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + sql = [] + for sequence_info in sequences: + sequence_name = self._get_sequence_name(sequence_info['table']) + table_name = self.quote_name(sequence_info['table']) + column_name = self.quote_name(sequence_info['column'] or 'id') + query = _get_sequence_reset_sql() % {'sequence': sequence_name, + 'table': table_name, + 'column': column_name} + sql.append(query) + return sql + def sequence_reset_sql(self, style, model_list): from django.db import models output = [] diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index e93a15512b..40fe629110 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -85,25 +85,29 @@ class DatabaseOperations(BaseDatabaseOperations): (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables])) )] - - # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements - # to reset sequence indices - for sequence_info in sequences: - table_name = sequence_info['table'] - column_name = sequence_info['column'] - if not (column_name and len(column_name) > 0): - # This will be the case if it's an m2m using an autogenerated - # intermediate table (see BaseDatabaseIntrospection.sequence_list) - column_name = 'id' - sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_TABLE(self.quote_name(table_name)), - style.SQL_FIELD(column_name)) - ) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements + # to reset sequence indices + sql = [] + for sequence_info in sequences: + table_name = sequence_info['table'] + column_name = sequence_info['column'] + if not (column_name and len(column_name) > 0): + # This will be the case if it's an m2m using an autogenerated + # intermediate table (see BaseDatabaseIntrospection.sequence_list) + column_name = 'id' + sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_TABLE(self.quote_name(table_name)), + style.SQL_FIELD(column_name)) + ) + return sql + def tablespace_sql(self, tablespace, inline=False): if inline: return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace) diff --git a/django/test/simple.py b/django/test/simple.py index 4f05284543..bf0219d53f 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -5,7 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.db.models import get_app, get_apps from django.test import _doctest as doctest from django.test.utils import setup_test_environment, teardown_test_environment -from django.test.testcases import OutputChecker, DocTestRunner, TestCase +from django.test.testcases import OutputChecker, DocTestRunner from django.utils import unittest from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule @@ -263,7 +263,7 @@ class DjangoTestSuiteRunner(object): for test in extra_tests: suite.addTest(test) - return reorder_suite(suite, (TestCase,)) + return reorder_suite(suite, (unittest.TestCase,)) def setup_databases(self, **kwargs): from django.db import connections, DEFAULT_DB_ALIAS diff --git a/django/test/testcases.py b/django/test/testcases.py index b9aae21e8e..b60188bf30 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -23,6 +23,7 @@ from django.core import mail from django.core.exceptions import ValidationError, ImproperlyConfigured from django.core.handlers.wsgi import WSGIHandler from django.core.management import call_command +from django.core.management.color import no_style from django.core.signals import request_started from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer, WSGIServerException) @@ -444,10 +445,15 @@ class SimpleTestCase(ut2.TestCase): class TransactionTestCase(SimpleTestCase): + # The class we'll use for the test client self.client. # Can be overridden in derived classes. client_class = Client + # Subclasses can ask for resetting of auto increment sequence before each + # test case + reset_sequences = False + def _pre_setup(self): """Performs any pre-test setup. This includes: @@ -462,22 +468,36 @@ class TransactionTestCase(SimpleTestCase): self._urlconf_setup() mail.outbox = [] + def _reset_sequences(self, db_name): + conn = connections[db_name] + if conn.features.supports_sequence_reset: + sql_list = \ + conn.ops.sequence_reset_by_name_sql(no_style(), + conn.introspection.sequence_list()) + if sql_list: + try: + cursor = conn.cursor() + for sql in sql_list: + cursor.execute(sql) + except Exception: + transaction.rollback_unless_managed(using=db_name) + raise + transaction.commit_unless_managed(using=db_name) + def _fixture_setup(self): - # If the test case has a multi_db=True flag, flush all databases. - # Otherwise, just flush default. - if getattr(self, 'multi_db', False): - databases = connections - else: - databases = [DEFAULT_DB_ALIAS] - for db in databases: - call_command('flush', verbosity=0, interactive=False, database=db, - skip_validation=True) + # If the test case has a multi_db=True flag, act on all databases. + # Otherwise, just on the default DB. + db_names = connections if getattr(self, 'multi_db', False) else [DEFAULT_DB_ALIAS] + for db_name in db_names: + # Reset sequences + if self.reset_sequences: + self._reset_sequences(db_name) if hasattr(self, 'fixtures'): # We have to use this slightly awkward syntax due to the fact # that we're using *args and **kwargs together. call_command('loaddata', *self.fixtures, - **{'verbosity': 0, 'database': db, 'skip_validation': True}) + **{'verbosity': 0, 'database': db_name, 'skip_validation': True}) def _urlconf_setup(self): if hasattr(self, 'urls'): @@ -534,7 +554,12 @@ class TransactionTestCase(SimpleTestCase): conn.close() def _fixture_teardown(self): - pass + # If the test case has a multi_db=True flag, flush all databases. + # Otherwise, just flush default. + databases = connections if getattr(self, 'multi_db', False) else [DEFAULT_DB_ALIAS] + for db in databases: + call_command('flush', verbosity=0, interactive=False, database=db, + skip_validation=True, reset_sequences=False) def _urlconf_teardown(self): if hasattr(self, '_old_root_urlconf'): @@ -808,22 +833,21 @@ class TestCase(TransactionTestCase): if not connections_support_transactions(): return super(TestCase, self)._fixture_setup() + assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances' + # If the test case has a multi_db=True flag, setup all databases. # Otherwise, just use default. - if getattr(self, 'multi_db', False): - databases = connections - else: - databases = [DEFAULT_DB_ALIAS] + db_names = connections if getattr(self, 'multi_db', False) else [DEFAULT_DB_ALIAS] - for db in databases: - transaction.enter_transaction_management(using=db) - transaction.managed(True, using=db) + for db_name in db_names: + transaction.enter_transaction_management(using=db_name) + transaction.managed(True, using=db_name) disable_transaction_methods() from django.contrib.sites.models import Site Site.objects.clear_cache() - for db in databases: + for db in db_names: if hasattr(self, 'fixtures'): call_command('loaddata', *self.fixtures, **{ diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index fd9ae4f038..aae8b25e07 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -188,6 +188,57 @@ Session not saved on 500 responses Django's session middleware will skip saving the session data if the response's status code is 500. +Changes in tests execution +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some changes have been introduced in the execution of tests that might be +backward-incompatible for some testing setups: + +Database flushing in ``django.test.TransactionTestCase`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, the test database was truncated *before* each test run in a +:class:`~django.test.TransactionTestCase`. + +In order to be able to run unit tests in any order and to make sure they are +always isolated from each other, :class:`~django.test.TransactionTestCase` will +now reset the database *after* each test run instead. + +No more implict DB sequences reset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:class:`~django.test.TransactionTestCase` tests used to reset primary key +sequences automatically together with the database flushing actions described +above. + +This has been changed so no sequences are implicitly reset. This can cause +:class:`~django.test.TransactionTestCase` tests that depend on hard-coded +primary key values to break. + +The new :attr:`~django.test.TransactionTestCase.reset_sequences` attribute can +be used to force the old behavior for :class:`~django.test.TransactionTestCase` +that might need it. + +Ordering of tests +^^^^^^^^^^^^^^^^^ + +In order to make sure all ``TestCase`` code starts with a clean database, +tests are now executed in the following order: + +* First, all unittests (including :class:`unittest.TestCase`, + :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and + :class:`~django.test.TransactionTestCase`) are run with no particular ordering + guaranteed nor enforced among them. + +* Then any other tests (e.g. doctests) that may alter the database without + restoring it to its original state are run. + +This should not cause any problems unless you have existing doctests which +assume a :class:`~django.test.TransactionTestCase` executed earlier left some +database state behind or unit tests that rely on some form of state being +preserved after the execution of other tests. Such tests are already very +fragile, and must now be changed to be able to run independently. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index aa274d83c9..1f4c970d3e 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -478,6 +478,32 @@ If there are any circular dependencies in the :setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured`` exception will be raised. +Order in which tests are executed +--------------------------------- + +In order to guarantee that all ``TestCase`` code starts with a clean database, +the Django test runner reorders tests in the following way: + +* First, all unittests (including :class:`unittest.TestCase`, + :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and + :class:`~django.test.TransactionTestCase`) are run with no particular ordering + guaranteed nor enforced among them. + +* Then any other tests (e.g. doctests) that may alter the database without + restoring it to its original state are run. + +.. versionchanged:: 1.5 + Before Django 1.5, the only guarantee was that + :class:`~django.test.TestCase` tests were always ran first, before any other + tests. + +.. note:: + + The new ordering of tests may reveal unexpected dependencies on test case + ordering. This is the case with doctests that relied on state left in the + database by a given :class:`~django.test.TransactionTestCase` test, they + must be updated to be able to run independently. + Other test conditions --------------------- @@ -1109,8 +1135,11 @@ The following is a simple unit test using the request factory:: response = my_view(request) self.assertEqual(response.status_code, 200) -TestCase --------- +Test cases +---------- + +Provided test case classes +~~~~~~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: django.test @@ -1124,16 +1153,19 @@ Normal Python unit test classes extend a base class of Hierarchy of Django unit testing classes +TestCase +^^^^^^^^ + .. class:: TestCase() This class provides some additional capabilities that can be useful for testing Web sites. Converting a normal :class:`unittest.TestCase` to a Django :class:`TestCase` is -easy: just change the base class of your test from :class:`unittest.TestCase` to -:class:`django.test.TestCase`. All of the standard Python unit test -functionality will continue to be available, but it will be augmented with some -useful additions, including: +easy: Just change the base class of your test from `'unittest.TestCase'` to +`'django.test.TestCase'`. All of the standard Python unit test functionality +will continue to be available, but it will be augmented with some useful +additions, including: * Automatic loading of fixtures. @@ -1141,11 +1173,18 @@ useful additions, including: * Creates a TestClient instance. -* Django-specific assertions for testing for things - like redirection and form errors. +* Django-specific assertions for testing for things like redirection and form + errors. + +.. versionchanged:: 1.5 + The order in which tests are run has changed. See `Order in which tests are + executed`_. ``TestCase`` inherits from :class:`~django.test.TransactionTestCase`. +TransactionTestCase +^^^^^^^^^^^^^^^^^^^ + .. class:: TransactionTestCase() Django ``TestCase`` classes make use of database transaction facilities, if @@ -1157,38 +1196,66 @@ behavior, you should use a Django ``TransactionTestCase``. ``TransactionTestCase`` and ``TestCase`` are identical except for the manner in which the database is reset to a known state and the ability for test code -to test the effects of commit and rollback. A ``TransactionTestCase`` resets -the database before the test runs by truncating all tables and reloading -initial data. A ``TransactionTestCase`` may call commit and rollback and -observe the effects of these calls on the database. +to test the effects of commit and rollback: -A ``TestCase``, on the other hand, does not truncate tables and reload initial -data at the beginning of a test. Instead, it encloses the test code in a -database transaction that is rolled back at the end of the test. It also -prevents the code under test from issuing any commit or rollback operations -on the database, to ensure that the rollback at the end of the test restores -the database to its initial state. In order to guarantee that all ``TestCase`` -code starts with a clean database, the Django test runner runs all ``TestCase`` -tests first, before any other tests (e.g. doctests) that may alter the -database without restoring it to its original state. +* A ``TransactionTestCase`` resets the database after the test runs by + truncating all tables. A ``TransactionTestCase`` may call commit and rollback + and observe the effects of these calls on the database. -When running on a database that does not support rollback (e.g. MySQL with the -MyISAM storage engine), ``TestCase`` falls back to initializing the database -by truncating tables and reloading initial data. +* A ``TestCase``, on the other hand, does not truncate tables after a test. + Instead, it encloses the test code in a database transaction that is rolled + back at the end of the test. It also prevents the code under test from + issuing any commit or rollback operations on the database, to ensure that the + rollback at the end of the test restores the database to its initial state. + + When running on a database that does not support rollback (e.g. MySQL with the + MyISAM storage engine), ``TestCase`` falls back to initializing the database + by truncating tables and reloading initial data. + +.. note:: + + .. versionchanged:: 1.5 + + Prior to 1.5, ``TransactionTestCase`` flushed the database tables *before* + each test. In Django 1.5, this is instead done *after* the test has been run. + + When the flush took place before the test, it was guaranteed that primary + key values started at one in :class:`~django.test.TransactionTestCase` + tests. + + Tests should not depend on this behaviour, but for legacy tests that do, the + :attr:`~TransactionTestCase.reset_sequences` attribute can be used until + the test has been properly updated. + +.. versionchanged:: 1.5 + The order in which tests are run has changed. See `Order in which tests are + executed`_. ``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`. -.. note:: - The ``TestCase`` use of rollback to un-do the effects of the test code - may reveal previously-undetected errors in test code. For example, - test code that assumes primary keys values will be assigned starting at - one may find that assumption no longer holds true when rollbacks instead - of table truncation are being used to reset the database. Similarly, - the reordering of tests so that all ``TestCase`` classes run first may - reveal unexpected dependencies on test case ordering. In such cases a - quick fix is to switch the ``TestCase`` to a ``TransactionTestCase``. - A better long-term fix, that allows the test to take advantage of the - speed benefit of ``TestCase``, is to fix the underlying test problem. +.. attribute:: TransactionTestCase.reset_sequences + + .. versionadded:: 1.5 + + Setting ``reset_sequences = True`` on a ``TransactionTestCase`` will make + sure sequences are always reset before the test run:: + + class TestsThatDependsOnPrimaryKeySequences(TransactionTestCase): + reset_sequences = True + + def test_animal_pk(self): + lion = Animal.objects.create(name="lion", sound="roar") + # lion.pk is guaranteed to always be 1 + self.assertEqual(lion.pk, 1) + + Unless you are explicitly testing primary keys sequence numbers, it is + recommended that you do not hard code primary key values in tests. + + Using ``reset_sequences = True`` will slow down the test, since the primary + key reset is an relatively expensive database operation. + +SimpleTestCase +^^^^^^^^^^^^^^ .. class:: SimpleTestCase() diff --git a/tests/regressiontests/test_runner/tests.py b/tests/regressiontests/test_runner/tests.py index 8c6dabf771..c723f162a4 100644 --- a/tests/regressiontests/test_runner/tests.py +++ b/tests/regressiontests/test_runner/tests.py @@ -267,6 +267,9 @@ class AutoIncrementResetTest(TransactionTestCase): and check that both times they get "1" as their PK value. That is, we test that AutoField values start from 1 for each transactional test case. """ + + reset_sequences = True + @skipUnlessDBFeature('supports_sequence_reset') def test_autoincrement_reset1(self): p = Person.objects.create(first_name='Jack', last_name='Smith') From 487b92a13ca88cff7297fec57848a166094d5711 Mon Sep 17 00:00:00 2001 From: Piet Delport Date: Mon, 23 Jul 2012 05:12:36 +0200 Subject: [PATCH 245/519] it's -> its --- django/forms/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/forms/models.py b/django/forms/models.py index 0831d5f4b2..4d56f1d38a 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -714,7 +714,7 @@ class BaseInlineFormSet(BaseModelFormSet): # Remove the foreign key from the form's data form.data[form.add_prefix(self.fk.name)] = None - # Set the fk value here so that the form can do it's validation. + # Set the fk value here so that the form can do its validation. setattr(form.instance, self.fk.get_attname(), self.instance.pk) return form From f1128e54746ca211254f4f6b0a37809812957b3e Mon Sep 17 00:00:00 2001 From: Piet Delport Date: Wed, 25 Jul 2012 01:17:27 +0200 Subject: [PATCH 246/519] Fix typo. --- docs/ref/databases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 1f4d09f6cb..74e6b48f07 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -111,7 +111,7 @@ outputs a single ``CREATE INDEX`` statement. However, if the database type for the field is either ``varchar`` or ``text`` (e.g., used by ``CharField``, ``FileField``, and ``TextField``), then Django will create an additional index that uses an appropriate `PostgreSQL operator class`_ -for the column. The extra index is necessary to correctly perfrom +for the column. The extra index is necessary to correctly perform lookups that use the ``LIKE`` operator in their SQL, as is done with the ``contains`` and ``startswith`` lookup types. From 50837434dbafe7d542aa4bd499f50314b1bac36f Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 24 Jul 2012 22:44:28 -0300 Subject: [PATCH 247/519] Clarified default name of M2M relationship DB table. --- docs/ref/models/fields.txt | 22 +++++++++++----------- docs/topics/db/models.txt | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 23dcf4bd9f..5039ba4373 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1074,15 +1074,14 @@ the model is related. This works exactly the same as it does for Database Representation ~~~~~~~~~~~~~~~~~~~~~~~ -Behind the scenes, Django creates an intermediary join table to -represent the many-to-many relationship. By default, this table name -is generated using the name of the many-to-many field and the model -that contains it. Since some databases don't support table names above -a certain length, these table names will be automatically truncated to -64 characters and a uniqueness hash will be used. This means you might -see table names like ``author_books_9cdf4``; this is perfectly normal. -You can manually provide the name of the join table using the -:attr:`~ManyToManyField.db_table` option. +Behind the scenes, Django creates an intermediary join table to represent the +many-to-many relationship. By default, this table name is generated using the +name of the many-to-many field and the name of the table for the model that +contains it. Since some databases don't support table names above a certain +length, these table names will be automatically truncated to 64 characters and a +uniqueness hash will be used. This means you might see table names like +``author_books_9cdf4``; this is perfectly normal. You can manually provide the +name of the join table using the :attr:`~ManyToManyField.db_table` option. .. _manytomany-arguments: @@ -1138,8 +1137,9 @@ that control how the relationship functions. .. attribute:: ManyToManyField.db_table The name of the table to create for storing the many-to-many data. If this - is not provided, Django will assume a default name based upon the names of - the two tables being joined. + is not provided, Django will assume a default name based upon the names of: + the table for the model defining the relationship and the name of the field + itself. .. _ref-onetoone: diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 4010ff6f9c..ce15dc9535 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -384,7 +384,8 @@ Extra fields on many-to-many relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When you're only dealing with simple many-to-many relationships such as -mixing and matching pizzas and toppings, a standard :class:`~django.db.models.ManyToManyField` is all you need. However, sometimes +mixing and matching pizzas and toppings, a standard +:class:`~django.db.models.ManyToManyField` is all you need. However, sometimes you may need to associate data with the relationship between two models. For example, consider the case of an application tracking the musical groups From ace9ccfe9f6b987861404fdf08d22a5663953713 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 24 Jul 2012 19:03:26 -0700 Subject: [PATCH 248/519] Fixed #18666 -- when upgrading a user's password to a new algorithm only save the password field to the databaes. --- django/contrib/auth/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 39d9e8408d..1099aa195b 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -299,7 +299,7 @@ class User(models.Model): """ def setter(raw_password): self.set_password(raw_password) - self.save() + self.save(update_fields=["password"]) return check_password(raw_password, self.password, setter) def set_unusable_password(self): From c3a05d8794ea79aa7321ba8d1a36423a7d202d60 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Tue, 24 Jul 2012 16:55:08 -1000 Subject: [PATCH 249/519] Changed the word "brackets" to "parentheses" I want to change the word "brackets" to "parentheses" because when I think of brackets, I think of [], and when I think of parentheses, I think of (), and when I originally read this, I found the word confusing. --- docs/ref/templates/builtins.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 71f57acdbf..500a47c6f1 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -419,7 +419,7 @@ will be interpreted like: if (athlete_list and coach_list) or cheerleader_list -Use of actual brackets in the :ttag:`if` tag is invalid syntax. If you need +Use of actual parentheses in the :ttag:`if` tag is invalid syntax. If you need them to indicate precedence, you should use nested :ttag:`if` tags. :ttag:`if` tags may also use the operators ``==``, ``!=``, ``<``, ``>``, From c7ac44e64ba0862e77753bf4859102bb1dbfe883 Mon Sep 17 00:00:00 2001 From: nklas Date: Wed, 25 Jul 2012 13:20:26 +0700 Subject: [PATCH 250/519] Update docs/topics/signals.txt Fixed a typo. --- docs/topics/signals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index fa668cc8c7..db1bcb03df 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -235,7 +235,7 @@ Remember that you're allowed to change this list of arguments at any time, so ge Sending signals --------------- -There are two ways to send send signals in Django. +There are two ways to send signals in Django. .. method:: Signal.send(sender, **kwargs) .. method:: Signal.send_robust(sender, **kwargs) From 82292141a07b9860add18c42b50ad98610a8250d Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 25 Jul 2012 09:57:00 +0200 Subject: [PATCH 251/519] Made staticfiles tests independent of test execution order. --- tests/regressiontests/staticfiles_tests/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index e05729cf7f..2c038e1713 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -54,6 +54,9 @@ class BaseStaticFilesTestCase(object): # since we're planning on changing that we need to clear out the cache. default_storage._wrapped = empty storage.staticfiles_storage._wrapped = empty + # Clear the cached staticfile finders, so they are reinitialized every + # run and pick up changes in settings.STATICFILES_DIRS. + finders._finders.clear() testfiles_path = os.path.join(TEST_ROOT, 'apps', 'test', 'static', 'test') # To make sure SVN doesn't hangs itself with the non-ASCII characters From 942818e1b35aefd382f45f1b4552ea250f5e1090 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 25 Jul 2012 10:16:35 +0200 Subject: [PATCH 252/519] Rolled back a unnecessary change in 8f002867b2. This keeps the implementation of setdefault and setlistdefault consistent. Also it's marginally faster than looking up the value again. --- django/utils/datastructures.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 41b43b286c..ce5218deb3 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -347,6 +347,7 @@ class MultiValueDict(dict): if default_list is None: default_list = [] self.setlist(key, default_list) + return default_list return self.getlist(key) def appendlist(self, key, value): From 206c248f1e8be71890d248cc705702a5eda6d0c8 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 25 Jul 2012 10:44:43 +0200 Subject: [PATCH 253/519] Ticket 18667: fix typo in CBV doc --- docs/ref/class-based-views/generic-display.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index b90cbf95b2..bbf0d4f05a 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -54,7 +54,7 @@ many projects they are typically the most commonly used views. from article.views import ArticleDetailView urlpatterns = patterns('', - url(r'^(?P[-_\w]+)/$', ArticleDetailView.as_view(), 'article-detail'), + url(r'^(?P[-_\w]+)/$', ArticleDetailView.as_view(), name='article-detail'), ) .. class:: django.views.generic.list.ListView From 69f4856f23ff61b8816901c1f3369c7d8237d97c Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 25 Jul 2012 10:57:30 +0200 Subject: [PATCH 254/519] Fixed a typo in the admin reference docs. Thanks Yohan Boniface for the report. --- docs/ref/contrib/admin/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index ad4f6ba3cd..f28aa4687b 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -115,7 +115,7 @@ subclass:: .. attribute:: ModelAdmin.actions_selection_counter - Controls whether a selection counter is display next to the action dropdown. + Controls whether a selection counter is displayed next to the action dropdown. By default, the admin changelist will display it (``actions_selection_counter = True``). From f3c9a16a423c90baaf3804cb050d320741f799a2 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 25 Jul 2012 19:10:40 +0200 Subject: [PATCH 255/519] Fixed QueryDict.setlistdefault. It was broken by a seemingly innocuous change in MultiValueDict. Document the pitfall for now. This is fragile and should be considered for refactoring. --- django/utils/datastructures.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index ce5218deb3..4d265ca719 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -339,7 +339,8 @@ class MultiValueDict(dict): def setdefault(self, key, default=None): if key not in self: self[key] = default - return default + # Do not return default here because __setitem__() may store + # another value -- QueryDict.__setitem__() does. Look it up. return self[key] def setlistdefault(self, key, default_list=None): @@ -347,7 +348,8 @@ class MultiValueDict(dict): if default_list is None: default_list = [] self.setlist(key, default_list) - return default_list + # Do not return default_list here because setlist() may store + # another value -- QueryDict.setlist() does. Look it up. return self.getlist(key) def appendlist(self, key, value): From a875f612e0ae84c2084d0b6230ffafe32a9777c8 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Mon, 16 Jul 2012 23:26:31 +0100 Subject: [PATCH 256/519] Fixed #18634 -- Don't escape variables in the context for startproject/startapp. The & symbols which can come up in the secret key were being escaped to &. --- django/core/management/templates.py | 2 +- .../project_template/additional_dir/extra.py | 1 + .../management/commands/custom_startproject.py | 11 +++++++++++ tests/regressiontests/admin_scripts/tests.py | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/extra.py create mode 100644 tests/regressiontests/admin_scripts/management/commands/custom_startproject.py diff --git a/django/core/management/templates.py b/django/core/management/templates.py index 2bf2f661fd..52d0e5c89d 100644 --- a/django/core/management/templates.py +++ b/django/core/management/templates.py @@ -115,7 +115,7 @@ class TemplateCommand(BaseCommand): context = Context(dict(options, **{ base_name: name, base_directory: top_dir, - })) + }), autoescape=False) # Setup a stub settings environment for template rendering from django.conf import settings diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/extra.py b/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/extra.py new file mode 100644 index 0000000000..6b553f190f --- /dev/null +++ b/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/extra.py @@ -0,0 +1 @@ +# this file uses the {{ extra }} variable diff --git a/tests/regressiontests/admin_scripts/management/commands/custom_startproject.py b/tests/regressiontests/admin_scripts/management/commands/custom_startproject.py new file mode 100644 index 0000000000..80c6d6b805 --- /dev/null +++ b/tests/regressiontests/admin_scripts/management/commands/custom_startproject.py @@ -0,0 +1,11 @@ +from optparse import make_option + +from django.core.management.commands.startproject import Command as BaseCommand + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--extra', + action='store', dest='extra', + help='An arbitrary extra value passed to the context'), + ) diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py index ecb16df53a..546fa7d79c 100644 --- a/tests/regressiontests/admin_scripts/tests.py +++ b/tests/regressiontests/admin_scripts/tests.py @@ -1541,6 +1541,24 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): self.assertIn("project_name = 'another_project'", content) self.assertIn("project_directory = '%s'" % testproject_dir, content) + def test_no_escaping_of_project_variables(self): + "Make sure template context variables are not html escaped" + # We're using a custom command so we need the alternate settings + self.write_settings('alternate_settings.py') + template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + args = ['custom_startproject', '--template', template_path, 'another_project', 'project_dir', '--extra', '<&>', '--settings=alternate_settings'] + testproject_dir = os.path.join(test_dir, 'project_dir') + os.mkdir(testproject_dir) + out, err = self.run_manage(args) + self.addCleanup(shutil.rmtree, testproject_dir) + self.assertNoOutput(err) + test_manage_py = os.path.join(testproject_dir, 'additional_dir', 'extra.py') + with open(test_manage_py, 'r') as fp: + content = fp.read() + self.assertIn("<&>", content) + # tidy up alternate settings + self.remove_settings('alternate_settings.py') + def test_custom_project_destination_missing(self): """ Make sure an exception is raised when the provided From 7d06f975fe445f0393455b3eb9ec17dbe04f2ec3 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 25 Jul 2012 22:32:31 +0200 Subject: [PATCH 257/519] Fixed #18614 -- Added missing imports in code samples. --- docs/topics/forms/index.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index b843107871..4693de6c7e 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -91,6 +91,9 @@ The standard pattern for processing a form in a view looks like this: .. code-block:: python + from django.shortcuts import render + from django.http import HttpResponseRedirect + def contact(request): if request.method == 'POST': # If the form has been submitted... form = ContactForm(request.POST) # A form bound to the POST data From 04e8ef5a8361042577c2ebdb0512561e8249e0c5 Mon Sep 17 00:00:00 2001 From: Rafik Draoui Date: Wed, 25 Jul 2012 21:33:38 +0100 Subject: [PATCH 258/519] Updated example of customized ModelAdmin in documentation for 1.4 The change_view method of django.contrib.admin.ModelAdmin takes an extra `form_url` argument in Django 1.4. --- docs/ref/contrib/admin/index.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f28aa4687b..4d39981a4d 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1266,11 +1266,11 @@ provided some extra mapping data that would not otherwise be available:: # ... pass - def change_view(self, request, object_id, extra_context=None): + def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} extra_context['osm_data'] = self.get_osm_info() return super(MyModelAdmin, self).change_view(request, object_id, - extra_context=extra_context) + form_url, extra_context=extra_context) .. versionadded:: 1.4 From 4b5cb116e30020c459ad8c9314ae9311b461beb5 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 25 Jul 2012 22:45:46 +0200 Subject: [PATCH 259/519] Fixed error message in detail generic view. Thanks go to mitar for the report and the patch. --- django/views/generic/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py index cc0b44512e..c27b92b85e 100644 --- a/django/views/generic/detail.py +++ b/django/views/generic/detail.py @@ -66,7 +66,7 @@ class SingleObjectMixin(ContextMixin): else: raise ImproperlyConfigured("%(cls)s is missing a queryset. Define " "%(cls)s.model, %(cls)s.queryset, or override " - "%(cls)s.get_object()." % { + "%(cls)s.get_queryset()." % { 'cls': self.__class__.__name__ }) return self.queryset._clone() From ab6cd1c839b136cbc94178da433b2e97ab7f6061 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 25 Jul 2012 09:12:59 +0200 Subject: [PATCH 260/519] [py3] Updated dict-like data structures for Python 3. The keys/items/values methods return iterators in Python 3, and the iterkeys/items/values methods don't exist in Python 3. The behavior under Python 2 is unchanged. --- django/utils/datastructures.py | 135 +++++++++++------- django/utils/six.py | 9 ++ docs/topics/python3.txt | 15 ++ tests/regressiontests/utils/datastructures.py | 68 ++++----- 4 files changed, 140 insertions(+), 87 deletions(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 4d265ca719..bbd31ad36c 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,6 +1,7 @@ import copy import warnings from types import GeneratorType +from django.utils import six class MergeDict(object): @@ -31,38 +32,48 @@ class MergeDict(object): except KeyError: return default + # This is used by MergeDicts of MultiValueDicts. def getlist(self, key): for dict_ in self.dicts: - if key in dict_.keys(): + if key in dict_: return dict_.getlist(key) return [] - def iteritems(self): + def _iteritems(self): seen = set() for dict_ in self.dicts: - for item in dict_.iteritems(): - k, v = item + for item in six.iteritems(dict_): + k = item[0] if k in seen: continue seen.add(k) yield item - def iterkeys(self): - for k, v in self.iteritems(): + def _iterkeys(self): + for k, v in self._iteritems(): yield k - def itervalues(self): - for k, v in self.iteritems(): + def _itervalues(self): + for k, v in self._iteritems(): yield v - def items(self): - return list(self.iteritems()) + if six.PY3: + items = _iteritems + keys = _iterkeys + values = _itervalues + else: + iteritems = _iteritems + iterkeys = _iterkeys + itervalues = _itervalues - def keys(self): - return list(self.iterkeys()) + def items(self): + return list(self.iteritems()) - def values(self): - return list(self.itervalues()) + def keys(self): + return list(self.iterkeys()) + + def values(self): + return list(self.itervalues()) def has_key(self, key): for dict_ in self.dicts: @@ -71,7 +82,8 @@ class MergeDict(object): return False __contains__ = has_key - __iter__ = iterkeys + + __iter__ = _iterkeys def copy(self): """Returns a copy of this object.""" @@ -117,7 +129,7 @@ class SortedDict(dict): data = list(data) super(SortedDict, self).__init__(data) if isinstance(data, dict): - self.keyOrder = data.keys() + self.keyOrder = list(six.iterkeys(data)) else: self.keyOrder = [] seen = set() @@ -128,7 +140,7 @@ class SortedDict(dict): def __deepcopy__(self, memo): return self.__class__([(key, copy.deepcopy(value, memo)) - for key, value in self.iteritems()]) + for key, value in six.iteritems(self)]) def __copy__(self): # The Python's default copy implementation will alter the state @@ -162,28 +174,38 @@ class SortedDict(dict): self.keyOrder.remove(result[0]) return result - def items(self): - return zip(self.keyOrder, self.values()) - - def iteritems(self): + def _iteritems(self): for key in self.keyOrder: yield key, self[key] - def keys(self): - return self.keyOrder[:] + def _iterkeys(self): + for key in self.keyOrder: + yield key - def iterkeys(self): - return iter(self.keyOrder) - - def values(self): - return map(self.__getitem__, self.keyOrder) - - def itervalues(self): + def _itervalues(self): for key in self.keyOrder: yield self[key] + if six.PY3: + items = _iteritems + keys = _iterkeys + values = _itervalues + else: + iteritems = _iteritems + iterkeys = _iterkeys + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) + + def keys(self): + return list(self.iterkeys()) + + def values(self): + return list(self.itervalues()) + def update(self, dict_): - for k, v in dict_.iteritems(): + for k, v in six.iteritems(dict_): self[k] = v def setdefault(self, key, default): @@ -226,7 +248,7 @@ class SortedDict(dict): Replaces the normal dict.__repr__ with a version that returns the keys in their sorted order. """ - return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in six.iteritems(self)]) def clear(self): super(SortedDict, self).clear() @@ -356,38 +378,41 @@ class MultiValueDict(dict): """Appends an item to the internal list associated with key.""" self.setlistdefault(key).append(value) - def items(self): - """ - Returns a list of (key, value) pairs, where value is the last item in - the list associated with the key. - """ - return [(key, self[key]) for key in self.keys()] - - def iteritems(self): + def _iteritems(self): """ Yields (key, value) pairs, where value is the last item in the list associated with the key. """ - for key in self.keys(): - yield (key, self[key]) + for key in self: + yield key, self[key] - def lists(self): - """Returns a list of (key, list) pairs.""" - return super(MultiValueDict, self).items() - - def iterlists(self): + def _iterlists(self): """Yields (key, list) pairs.""" - return super(MultiValueDict, self).iteritems() + return six.iteritems(super(MultiValueDict, self)) - def values(self): - """Returns a list of the last value on every key list.""" - return [self[key] for key in self.keys()] - - def itervalues(self): + def _itervalues(self): """Yield the last value on every key list.""" - for key in self.iterkeys(): + for key in self: yield self[key] + if six.PY3: + items = _iteritems + lists = _iterlists + values = _itervalues + else: + iteritems = _iteritems + iterlists = _iterlists + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) + + def lists(self): + return list(self.iterlists()) + + def values(self): + return list(self.itervalues()) + def copy(self): """Returns a shallow copy of this object.""" return copy.copy(self) @@ -410,7 +435,7 @@ class MultiValueDict(dict): self.setlistdefault(key).append(value) except TypeError: raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): self.setlistdefault(key).append(value) def dict(self): diff --git a/django/utils/six.py b/django/utils/six.py index c74f9fa7df..e226bba09e 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -355,4 +355,13 @@ def with_metaclass(meta, base=object): ### Additional customizations for Django ### +if PY3: + _iterlists = "lists" +else: + _iterlists = "iterlists" + +def iterlists(d): + """Return an iterator over the values of a MultiValueDict.""" + return getattr(d, _iterlists)() + add_move(MovedModule("_dummy_thread", "dummy_thread")) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 7c1cb53150..3f799edac7 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -120,3 +120,18 @@ If you need different code in Python 2 and Python 3, check :data:`six.PY3`:: This is a last resort solution when :mod:`six` doesn't provide an appropriate function. + +.. module:: django.utils.six + +Customizations of six +===================== + +The version of six bundled with Django includes a few additional tools: + +.. function:: iterlists(MultiValueDict) + + Returns an iterator over the lists of values of a + :class:`~django.utils.datastructures.MultiValueDict`. This replaces + :meth:`~django.utils.datastructures.MultiValueDict.iterlists()` on Python + 2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on + Python 3. diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index aea5393aab..dbc65d37a8 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -9,6 +9,7 @@ import warnings from django.test import SimpleTestCase from django.utils.datastructures import (DictWrapper, ImmutableList, MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) +from django.utils import six class SortedDictTests(SimpleTestCase): @@ -25,19 +26,19 @@ class SortedDictTests(SimpleTestCase): self.d2[7] = 'seven' def test_basic_methods(self): - self.assertEqual(self.d1.keys(), [7, 1, 9]) - self.assertEqual(self.d1.values(), ['seven', 'one', 'nine']) - self.assertEqual(self.d1.items(), [(7, 'seven'), (1, 'one'), (9, 'nine')]) + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9]) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'one', 'nine']) + self.assertEqual(list(six.iteritems(self.d1)), [(7, 'seven'), (1, 'one'), (9, 'nine')]) def test_overwrite_ordering(self): - """ Overwriting an item keeps it's place. """ + """ Overwriting an item keeps its place. """ self.d1[1] = 'ONE' - self.assertEqual(self.d1.values(), ['seven', 'ONE', 'nine']) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'ONE', 'nine']) def test_append_items(self): """ New items go to the end. """ self.d1[0] = 'nil' - self.assertEqual(self.d1.keys(), [7, 1, 9, 0]) + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9, 0]) def test_delete_and_insert(self): """ @@ -45,18 +46,22 @@ class SortedDictTests(SimpleTestCase): at the end. """ del self.d2[7] - self.assertEqual(self.d2.keys(), [1, 9, 0]) + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0]) self.d2[7] = 'lucky number 7' - self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0, 7]) - def test_change_keys(self): - """ - Changing the keys won't do anything, it's only a copy of the - keys dict. - """ - k = self.d2.keys() - k.remove(9) - self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + if not six.PY3: + def test_change_keys(self): + """ + Changing the keys won't do anything, it's only a copy of the + keys dict. + + This test doesn't make sense under Python 3 because keys is + an iterator. + """ + k = self.d2.keys() + k.remove(9) + self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) def test_init_keys(self): """ @@ -68,18 +73,18 @@ class SortedDictTests(SimpleTestCase): tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) d = SortedDict(tuples) - self.assertEqual(d.keys(), [2, 1]) + self.assertEqual(list(six.iterkeys(d)), [2, 1]) real_dict = dict(tuples) - self.assertEqual(sorted(real_dict.values()), ['one', 'second-two']) + self.assertEqual(sorted(six.itervalues(real_dict)), ['one', 'second-two']) # Here the order of SortedDict values *is* what we are testing - self.assertEqual(d.values(), ['second-two', 'one']) + self.assertEqual(list(six.itervalues(d)), ['second-two', 'one']) def test_overwrite(self): self.d1[1] = 'not one' self.assertEqual(self.d1[1], 'not one') - self.assertEqual(self.d1.keys(), self.d1.copy().keys()) + self.assertEqual(list(six.iterkeys(self.d1)), list(six.iterkeys(self.d1.copy()))) def test_append(self): self.d1[13] = 'thirteen' @@ -115,8 +120,8 @@ class SortedDictTests(SimpleTestCase): def test_copy(self): orig = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) copied = copy.copy(orig) - self.assertEqual(orig.keys(), [1, 0, 2]) - self.assertEqual(copied.keys(), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(orig)), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(copied)), [1, 0, 2]) def test_clear(self): self.d1.clear() @@ -178,12 +183,12 @@ class MergeDictTests(SimpleTestCase): self.assertEqual(mm.getlist('key4'), ['value5', 'value6']) self.assertEqual(mm.getlist('undefined'), []) - self.assertEqual(sorted(mm.keys()), ['key1', 'key2', 'key4']) - self.assertEqual(len(mm.values()), 3) + self.assertEqual(sorted(six.iterkeys(mm)), ['key1', 'key2', 'key4']) + self.assertEqual(len(list(six.itervalues(mm))), 3) - self.assertTrue('value1' in mm.values()) + self.assertTrue('value1' in six.itervalues(mm)) - self.assertEqual(sorted(mm.items(), key=lambda k: k[0]), + self.assertEqual(sorted(six.iteritems(mm), key=lambda k: k[0]), [('key1', 'value1'), ('key2', 'value3'), ('key4', 'value6')]) @@ -201,10 +206,10 @@ class MultiValueDictTests(SimpleTestCase): self.assertEqual(d['name'], 'Simon') self.assertEqual(d.get('name'), 'Simon') self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) - self.assertEqual(list(d.iteritems()), + self.assertEqual(list(six.iteritems(d)), [('position', 'Developer'), ('name', 'Simon')]) - self.assertEqual(list(d.iterlists()), + self.assertEqual(list(six.iterlists(d)), [('position', ['Developer']), ('name', ['Adrian', 'Simon'])]) @@ -224,8 +229,7 @@ class MultiValueDictTests(SimpleTestCase): d.setlist('lastname', ['Holovaty', 'Willison']) self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison']) - self.assertEqual(d.values(), ['Developer', 'Simon', 'Willison']) - self.assertEqual(list(d.itervalues()), + self.assertEqual(list(six.itervalues(d)), ['Developer', 'Simon', 'Willison']) def test_appendlist(self): @@ -260,8 +264,8 @@ class MultiValueDictTests(SimpleTestCase): 'pm': ['Rory'], }) d = mvd.dict() - self.assertEqual(d.keys(), mvd.keys()) - for key in mvd.keys(): + self.assertEqual(list(six.iterkeys(d)), list(six.iterkeys(mvd))) + for key in six.iterkeys(mvd): self.assertEqual(d[key], mvd[key]) self.assertEqual({}, MultiValueDict().dict()) From 00d5e632fabc03a0633b9910605b23bcec439cdc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 28 Jul 2012 13:17:33 -0400 Subject: [PATCH 261/519] Fixed #18630 -- Updated version in docs/intro/install.txt; thanks Kevin London. --- docs/intro/install.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 7e8c7db7b3..70c8034c5d 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -84,8 +84,9 @@ Then at the Python prompt, try to import Django:: >>> import django >>> print(django.get_version()) - 1.4 + 1.5 +You may have another version of Django installed. That's it! ---------- From 07d70e9b267797f5f375a1dfcc0513a68d0feb5f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 28 Jul 2012 13:31:41 -0400 Subject: [PATCH 262/519] Fixed #18656 -- Fixed LocaleMiddleware link; thanks mitar for the report. --- docs/ref/middleware.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index c280202ebf..a6ea9a6c41 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -146,7 +146,7 @@ Locale middleware Enables language selection based on data from the request. It customizes content for each user. See the :doc:`internationalization documentation -`. +`. Message middleware ------------------ From 690ed5794672495e4cffca9a4a701008d254a4e7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 29 Jul 2012 18:14:26 -0400 Subject: [PATCH 263/519] Fixed #18476 - Added use of {% url %} tag to tutorial. Thanks Claude Paroz for the patch. --- docs/intro/tutorial03.txt | 17 +++++++++++++++++ docs/intro/tutorial04.txt | 23 ++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index fd3a04ba93..d15b2f43ae 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -533,5 +533,22 @@ under "/content/polls/", or any other path root, and the app will still work. All the poll app cares about is its relative path, not its absolute path. +Removing hardcoded URLs in templates +------------------------------------ + +Remember, when we wrote the link to a poll in our template, the link was +partially hardcoded like this: + +.. code-block:: html+django + +
    • {{ poll.question }}
    • + +To use the decoupled URLs we've just introduced, replace the hardcoded link +with the :ttag:`url` template tag: + +.. code-block:: html+django + +
    • {{ poll.question }}
    • + When you're comfortable with writing views, read :doc:`part 4 of this tutorial ` to learn about simple form processing and generic views. diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index 44b9c16c2a..31680ea5e5 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -18,7 +18,7 @@ tutorial, so that the template contains an HTML ``
      `` element: {% if error_message %}

      {{ error_message }}

      {% endif %} - + {% csrf_token %} {% for choice in poll.choice_set.all %} @@ -35,7 +35,7 @@ A quick rundown: selects one of the radio buttons and submits the form, it'll send the POST data ``choice=3``. This is HTML Forms 101. -* We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, and we +* We set the form's ``action`` to ``{% url 'polls.views.vote' poll.id %}``, and we set ``method="post"``. Using ``method="post"`` (as opposed to ``method="get"``) is very important, because the act of submitting this form will alter data server-side. Whenever you create a form that alters @@ -172,7 +172,7 @@ Now, create a ``results.html`` template: {% endfor %} - Vote again? + Vote again? Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a results page that gets updated each time you vote. If you submit the form @@ -238,11 +238,13 @@ Change it like so:: ListView.as_view( queryset=Poll.objects.order_by('-pub_date')[:5], context_object_name='latest_poll_list', - template_name='polls/index.html')), + template_name='polls/index.html'), + name='poll_index'), url(r'^(?P\d+)/$', DetailView.as_view( model=Poll, - template_name='polls/detail.html')), + template_name='polls/detail.html'), + name='poll_detail'), url(r'^(?P\d+)/results/$', DetailView.as_view( model=Poll, @@ -265,8 +267,8 @@ two views abstract the concepts of "display a list of objects" and ``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic views. -* We've added a name, ``poll_results``, to the results view so - that we have a way to refer to its URL later on (see the +* We've added the ``name`` argument to the views (e.g. ``name='poll_results'``) + so that we have a way to refer to their URL later on (see the documentation about :ref:`naming URL patterns ` for information). We're also using the :func:`~django.conf.urls.url` function from @@ -317,6 +319,13 @@ function anymore -- generic views can be (and are) used multiple times return HttpResponseRedirect(reverse('poll_results', args=(p.id,))) +The same rule apply for the :ttag:`url` template tag. For example in the +``results.html`` template: + +.. code-block:: html+django + + Vote again? + Run the server, and use your new polling app based on generic views. For full details on generic views, see the :doc:`generic views documentation From c0748a621ceaae0f90133bb7980c857bf13811a1 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sun, 29 Jul 2012 16:53:56 -0700 Subject: [PATCH 264/519] Updated my bio. --- docs/internals/committers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index fb601a24c0..9e9847d88d 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -174,7 +174,7 @@ Justin Bronn implementing GeoDjango, Justin obtained a deep knowledge of Django's internals including the ORM, the admin, and Oracle support. - Justin lives in Houston, Texas. + Justin lives in San Francisco, CA. .. _GeoDjango: http://geodjango.org/ From dd16b17099b7d86f27773df048c5014cf439b282 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 30 Jul 2012 21:54:29 +0200 Subject: [PATCH 265/519] Fixed a security issue in image uploading. Disclosure and release forthcoming. --- django/core/files/images.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/django/core/files/images.py b/django/core/files/images.py index 228a7118c5..7d7eac65db 100644 --- a/django/core/files/images.py +++ b/django/core/files/images.py @@ -47,13 +47,18 @@ def get_image_dimensions(file_or_path, close=False): file = open(file_or_path, 'rb') close = True try: + # Most of the time PIL only needs a small chunk to parse the image and + # get the dimensions, but with some TIFF files PIL needs to parse the + # whole file. + chunk_size = 1024 while 1: - data = file.read(1024) + data = file.read(chunk_size) if not data: break p.feed(data) if p.image: return p.image.size + chunk_size = chunk_size*2 return None finally: if close: From b1d463468694f2e91fde67221b7996e9c52a9720 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 30 Jul 2012 21:57:22 +0200 Subject: [PATCH 266/519] Fixed second security issue in image uploading. Disclosure and release forthcoming. --- django/forms/fields.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/django/forms/fields.py b/django/forms/fields.py index c4a675da74..cdb1d7be67 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -560,20 +560,10 @@ class ImageField(FileField): file = BytesIO(data['content']) try: - # load() is the only method that can spot a truncated JPEG, - # but it cannot be called sanely after verify() - trial_image = Image.open(file) - trial_image.load() - - # Since we're about to use the file again we have to reset the - # file object if possible. - if hasattr(file, 'seek') and callable(file.seek): - file.seek(0) - - # verify() is the only method that can spot a corrupt PNG, - # but it must be called immediately after the constructor - trial_image = Image.open(file) - trial_image.verify() + # load() could spot a truncated JPEG, but it loads the entire + # image in memory, which is a DoS vector. See #3848 and #18520. + # verify() must be called immediately after the constructor. + Image.open(file).verify() except ImportError: # Under PyPy, it is possible to import PIL. However, the underlying # _imaging C module isn't available, so an ImportError will be From 4129201c3e0fa057c198bdefcb34686a23b4a93c Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 30 Jul 2012 22:01:50 +0200 Subject: [PATCH 267/519] Fixed a security issue in http redirects. Disclosure and new release forthcoming. --- django/http/__init__.py | 28 +++++++++++---------- tests/regressiontests/httpwrappers/tests.py | 19 ++++++++++++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 6f9d70eebd..19b581f5cb 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -11,10 +11,10 @@ import warnings from io import BytesIO from pprint import pformat try: - from urllib.parse import quote, parse_qsl, urlencode, urljoin + from urllib.parse import quote, parse_qsl, urlencode, urljoin, urlparse except ImportError: # Python 2 from urllib import quote, urlencode - from urlparse import parse_qsl, urljoin + from urlparse import parse_qsl, urljoin, urlparse from django.utils.six.moves import http_cookies # Some versions of Python 2.7 and later won't need this encoding bug fix: @@ -80,7 +80,7 @@ else: from django.conf import settings from django.core import signing -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.core.files import uploadhandler from django.http.multipartparser import MultiPartParser from django.http.utils import * @@ -689,20 +689,22 @@ class HttpResponse(object): raise Exception("This %s instance cannot tell its position" % self.__class__) return sum([len(chunk) for chunk in self]) -class HttpResponseRedirect(HttpResponse): +class HttpResponseRedirectBase(HttpResponse): + allowed_schemes = ['http', 'https', 'ftp'] + + def __init__(self, redirect_to): + 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__() + self['Location'] = iri_to_uri(redirect_to) + +class HttpResponseRedirect(HttpResponseRedirectBase): status_code = 302 - def __init__(self, redirect_to): - super(HttpResponseRedirect, self).__init__() - self['Location'] = iri_to_uri(redirect_to) - -class HttpResponsePermanentRedirect(HttpResponse): +class HttpResponsePermanentRedirect(HttpResponseRedirectBase): status_code = 301 - def __init__(self, redirect_to): - super(HttpResponsePermanentRedirect, self).__init__() - self['Location'] = iri_to_uri(redirect_to) - class HttpResponseNotModified(HttpResponse): status_code = 304 diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 9b599db6d0..0f61c2d840 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -4,8 +4,11 @@ from __future__ import unicode_literals import copy import pickle -from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError, - parse_cookie) +from django.core.exceptions import SuspiciousOperation +from django.http import (QueryDict, HttpResponse, HttpResponseRedirect, + HttpResponsePermanentRedirect, + SimpleCookie, BadHeaderError, + parse_cookie) from django.utils import unittest @@ -309,6 +312,18 @@ class HttpResponseTests(unittest.TestCase): r = HttpResponse(['abc']) self.assertRaises(Exception, r.write, 'def') + def test_unsafe_redirect(self): + bad_urls = [ + 'data:text/html,', + 'mailto:test@example.com', + 'file:///etc/passwd', + ] + for url in bad_urls: + self.assertRaises(SuspiciousOperation, + HttpResponseRedirect, url) + self.assertRaises(SuspiciousOperation, + HttpResponsePermanentRedirect, url) + class CookieTests(unittest.TestCase): def test_encode(self): From 8d3e501502c308cbdd3cc95b62ace0fe11d373ab Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 30 Jul 2012 16:16:11 -0400 Subject: [PATCH 268/519] Fixed #17131 - Added per object permission notes to docs. Thanks dchandek for the suggestion and mateusgondim for the patch. --- docs/topics/auth.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index c0e56dbba0..942d46073a 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1450,7 +1450,7 @@ The permission_required decorator Limiting access to generic views -------------------------------- -To limit access to a :doc:`class-based generic view +To limit access to a :doc:`class-based generic view `, decorate the :meth:`View.dispatch ` method on the class. See :ref:`decorating-class-based-views` for details. @@ -1476,12 +1476,13 @@ The Django admin site uses permissions as follows: * Access to delete an object is limited to users with the "delete" permission for that type of object. -Permissions are set globally per type of object, not per specific object -instance. For example, it's possible to say "Mary may change news stories," but -it's not currently possible to say "Mary may change news stories, but only the -ones she created herself" or "Mary may only change news stories that have a -certain status, publication date or ID." The latter functionality is something -Django developers are currently discussing. +Permissions can be set not only per type of object, but also per specific +object instance. By using the +:meth:`~django.contrib.admin.ModelAdmin.has_add_permission`, +:meth:`~django.contrib.admin.ModelAdmin.has_change_permission` and +:meth:`~django.contrib.admin.ModelAdmin.has_delete_permission` methods provided +by the :class:`~django.contrib.admin.ModelAdmin` class, it is possible to +customize permissions for different object instances of the same type. Default permissions ------------------- From 964979e8ecec3ceccb2119ccb7270111b953611e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 31 Jul 2012 16:13:52 -0400 Subject: [PATCH 269/519] Fixed #18122 - Clarified section title regarding applying permissions to generic views. --- docs/topics/auth.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 942d46073a..7e1353210a 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1447,10 +1447,10 @@ The permission_required decorator .. currentmodule:: django.contrib.auth -Limiting access to generic views --------------------------------- +Applying permissions to generic views +------------------------------------- -To limit access to a :doc:`class-based generic view +To apply a permission to a :doc:`class-based generic view `, decorate the :meth:`View.dispatch ` method on the class. See :ref:`decorating-class-based-views` for details. From ebbc414d1729f24e395ee050a62276229c59d75d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 31 Jul 2012 16:20:41 -0400 Subject: [PATCH 270/519] Fixed #16168 - Added note regarding type requirements when overridng ModelForm fields. Thanks Pieter Swinkels for the patch. --- docs/topics/forms/modelforms.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 4cfde400a7..0ca37745c7 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -437,6 +437,10 @@ parameter when declaring the form field:: class Meta: model = Article + You must ensure that the type of the form field can be used to set the + contents of the corresponding model field. When they are not compatible, + you will get a ``ValueError`` as no implicit conversion takes place. + See the :doc:`form field documentation ` for more information on fields and their arguments. From c59fff707ac5cfa1f6d12e889a157b569cf2429e Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Thu, 2 Aug 2012 20:27:53 +1000 Subject: [PATCH 271/519] Reinstated Pinax link that was still in use by others. --- docs/internals/committers.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 9e9847d88d..5567c26fa1 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -149,6 +149,7 @@ Joseph Kocherhans .. _brian rosner: http://brosner.com/ .. _eldarion: http://eldarion.com/ .. _django dose: http://djangodose.com/ +.. _pinax: http://pinaxproject.com/ `Gary Wilson`_ Gary starting contributing patches to Django in 2006 while developing Web From d7816c563b58ed53d49956321d549a16e2b2ebc0 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Thu, 2 Aug 2012 20:45:55 +1000 Subject: [PATCH 272/519] Fixed #18472 - Added warning regarding set_language / i18n_patterns. --- docs/topics/i18n/translation.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index c912bf9575..988948e259 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1247,6 +1247,12 @@ Activate this view by adding the following line to your URLconf:: (Note that this example makes the view available at ``/i18n/setlang/``.) +.. warning:: + + Make sure that you don't include the above URL within + :func:`~django.conf.urls.i18n.i18n_patterns` - it needs to be + language-independent itself to work correctly. + The view expects to be called via the ``POST`` method, with a ``language`` parameter set in request. If session support is enabled, the view saves the language choice in the user's session. Otherwise, it saves the From 6f229d2d7a4621fd73e0a5d22b8b7d42dec9493e Mon Sep 17 00:00:00 2001 From: Martin Brochhaus Date: Thu, 2 Aug 2012 19:29:19 +0800 Subject: [PATCH 273/519] Update docs/topics/class-based-views/index.txt Fixed a small typo: "We can use create" should be "We can create" --- docs/topics/class-based-views/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index 6c2848944c..1ac70e6938 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -87,7 +87,7 @@ 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 use create a mixin class to use in all of our views, handling the +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:: From 39541be3bc76f937cd8a2182aee4c92c9212c6b7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 2 Aug 2012 06:47:59 -0400 Subject: [PATCH 274/519] Fixed #18581 - Updated admin actions screenshots. Thanks Kevin London. --- .../contrib/admin/_images/article_actions.png | Bin 38545 -> 52563 bytes .../admin/_images/article_actions_message.png | Bin 22098 -> 31936 bytes .../contrib/admin/_images/user_actions.png | Bin 27047 -> 35765 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/ref/contrib/admin/_images/article_actions.png b/docs/ref/contrib/admin/_images/article_actions.png index 78a78ae494b636a391f9c16869bd726a9aa8584d..1d35e60e5d620d2fe115073f9f624c418fd75c93 100644 GIT binary patch literal 52563 zcmZU41yo#3vn>w6-CcsaySuwP1PJc#?vUW_t^tC(ySp>E4-P>d`M&%9`@i*Ot(kSY zyIQJFpVPZ`)kG>ONFu=E!h(Q+AV^DzsepihDT082Lc&0O*1Vj9KZAh4{IL=hRgxAJ zC025>|7K-l4gw;DaG_(Zr7DT*H!Tg*?4YiO&K?x7fcgVjN<~CXXaFuEq6upm18ffx z4W3w~X={nrq{$9Ax1>vNy{yyJw4}4npx#VV5ba?K)LGUUY?9HAw##+v9+hU=yId1^ zYncAwnKSBh#Oti^ZOij$g7b)9NDP?@_!dZALMl6y2eIlr-A(MwkA&sXpURSJPDh8G zkRuRNL1OE%CZr*dahVM|%;%aIIWK|9=XDt=X~5kYDiQEgd9rv2OcrJJ>PIyLHI31O z`7d6bGG#MPR^I9Fw{{a#m^Mq{aVZlqVyBToHdlPTm(%Aqy$Ti&Y^H>pKZ&3tUVoxu zs4a)aiNB{uo~X6aLwV(pb;BNuy4D$5;%Nt5&G58M1G{)W>qc{r*;d232_V;uH{ zd&v+-B@vSB*U1RHb<>AS`5UHSE!|kQf^AU>4w)Kku}n&mdXhQYQ6~K_0WvrlXD9m8 zz-P2*C-d)~F?s~$F-ESdTE9>&b-odh)u{G~ZWs9wyHV^WzRqwOK%W=B|AF^7hEtt$ zxz=_A4_^@QIBor`$JdknS6-%wnmgOy zIth5)J3o3aksf^-9|69(=O5>{g6h7GJ3IM;{+>@Gv!|x)!-}uP4`!R?^A=M%l^@pq z&v00b`dv4Ri{1KvUS2pk?Kn8LUjAIzUu|@*-|0Kw3SRnt_>RB*L3)>NQU1^;|M9W) zwpIO>+63i%1MZ_3EaVf&h(&42H@eW&diI*3f8=LdN z{{=@mIL+}%>b@R8`CT!G$N6{H?(5*VW{p9+Um(a>QHSvL8f_;FZHk+O4=2mH zPk=Jyez{GdWu{h4i@8l-QY&=jw-;Z%5y?n$h6j4)*USluuiD0m<^|M>n41qGM47>h zN7M~J%!dbF>3F|-XZN9|%hbeVD3PjKqL0S0Lu7WWX~pW?KH>Bhs!^LHeK)UC!6ZFV&QGSTQnIT8?a(sPM!^xd8Knm9pT8Rz!ams`(Q zw?(2OTy&DYVi_9y$8@0$cKE$=&k&Z_if>vl7bZ+D%~0EsXAwLIpGN!6l0IQZFS^vC zl!J9KjQm|LLZBTLb421TsZz@#foKr}3;|LS15hka3#=h@?Ku&vX&%GsmTGF^R{VmG zGsci=XZO{W^ja3#Vc*}JbC2IK=fWh)s^pP0M>aSR+BEPvbu|tmO$p9y>IgXvVvicy ziX&dyG=P1WdqbOCWPy&@0xDiE5iZ3BY-=1X|Fi^xR}m~*cJTUQFOG#8`pO9_gDfZE zs{_n7MemTSK@UFSqNZM=(0V{Ck<&`Xd0Y~W|G?Wo{sS%2bu6fdV9s#Nrb>}H^xOw9 zVtpqzs#3V@t>6OfFg0(dYTW?kbBz*M#3j%`&N%+{Ku)juhBx{=0fZKb_Vo-OM2jQv z_sBbVgUR4pD4G-Q>PX~5wxIr^^d8$zh#&I!Lz-ZdSQ|5WfW&zax~OE*JwBxPDg19l z_req7q&lWtUQ_OQ_YyH}PKf5iJiD#&gHjcTd2%OF5zK6oMSMKV?!!5Vs8wOLpUjTD!4p}`xeRVB=Q8S^ z*Q~{X35I>#%V<1mOX_qzdaoS$)q@6qQ9Yk)6E$w8wz}5R4-$1scAX5Y+8e4#zkdk_PBnm44Li%LH|0@=CuD=H#f9FN}|`udfdeFE3BWFZlj=1m{WY*``P! z_nUiaeyBqA1UWu={%VbIMRnPvvE9 zLc8H;BD33QcCXm{U|h4K?X)S1-P+LKxqoOAj1>dEekHBbv^QENhsJIPBpBU0Z1dka~9k~Q_d z!M`Z)|I%`b6>sH+@(mNlq1B{ah~lcwN^%m<5+N*%Qa&zZfqxN(D%K&aRH5Ee(`nVH zEL5q9RG%9ahVl&(CZmkZer~ML64z_gs3|xGbqw{rSg&$^ zc6AoYH^~(I9a4kN*2XkxFX0mneQ}V|b_M}~L;L#$1AbcJ?pO?1gF2=+jwl;RoJRba{|48tBUjMCTBqjbw#KoGQR7+lo zSk&IhoS2<~nSq5=0G61Tn9s@V8;^>Z#Q${vEb)_Cy0|#-FfzKkyEC}6GT1v=Ffwy< zb2BorFtV`Fe@f6hd)m1ed(hiCll?o${~brn+}YI0%E86T-j4Y1xW*>-t}gtfq<<&+ z&*$HDntNFN?@V^i|D)EYf{cG#7?~NE82>Z&XIH+zwLD5z9_BV$Vpg{1cFv!32yn7- z@%DeY66V2!J8HwcV- zzt~VMJ^raef-BQfLPw#CfJ&wz1`AAp00RvoPe`a6{n|eIezeiv;N48oPTmW`TG#4q zF_qd{ZG?mTeumd6^E1OwbFez7wXWxMm zFYsd7se=O#)o3{jmd)cT^6~yQTOyAa5)!hIser3^FGBw1KZ7~kpwz9}Xf=F8a64ad zya~-EL9N!Hjf##=-hR11V|LgPiXq^|^zz+2RNkzYhbPos2wlyr|C5$Xjx*cf&)CSe zU=L)k*C|E!ZhzzYwQN7kj2ssBaGaY6CJLf)Tb?vJ7R+1k_{c1N5R&tjh{2Funf#V7WffmXal>=Hi(z44V%k7qppI z@R`#<%TZ|bq%Je3zY1P;2Row~aNoQo0$IudFS7j6j!3V_I^jm=O#sJ1#VxjN-M)9& zTVJY;Xzuo}h2TIh?KNu+=n*Kf$YU?tk8-@xbDWQ+?)N&~uMFr6yYR8}T@tsuxQqai ztw;3Uh|a0|EfCai+SJ-s& ziEn5p%p!QQVkHxT9C^U8MGlnH?qY+CiFu$4O+>8_yetq=me50s@?ppB{@G7?iO4$- zk0Xg>W4NVZbum&nakNACL5K%W=DQ*`jE|GmM=i9>q!j390_pMe6qjtSgRQDB-Dg~u z@LrM^{XdA@3e2Y+TU^9o5o`Y7%QoXbq?egbGmHI<0xQtI3?H)SlR(^Pv_gYIzzz9r zyZ*wK2llZx^$X$35}|ZOEe4k#*SDRVQzm?K}w z#&qwe5fiR48}@7vW^Hy|rNC~I0NPSOYUG-Io}SCnW~LF= zjK6y?h-v7>mG0${#lqJ|kBQgUPw?wBA(!Lb?RDEjb5F?p4<0)l8^7$`u;}bi{8aDt zBPE#<;nvF+z#J3<=9;)~;EmeNQp~%@Q9TYdrF!D$!>vFA*3{HArs;}+iaSfN`GYh< z9N}%Agrk~rt!fS2wTcoqcqmp>0(KH@QAeHj$V|`Y z!jO6EXJ9Lc;e*PK3OqSkEKm8uz4#OjxQ&E)>3>cN)xzOK#!PSkL`LvkjAW)cEho6?iV+$X)FA` zOUgx*Ns@ybbCSkxpr2Y=HaLAz?w&P$aivLN^q!SkUo7RjR9{}`Fuci{K$XKv$zlQ6 z{P4!J#-Hv-UWWvHeGtDKlhbKb%%F@N%3h8Q0w-a4!P7xPmp>r-JSleIg#!%1XlcZB zJxtOSPM3ox5c4c=WzOxqF~i6y@(mv?ecBuFr9~xa!&K!5h0nQ=qYU&>vqVm#oKX`? z0jvG!{pC~^tgr|Kvy|+hIB#t`@%@djVF(EN^kd`ky(-pXK5$;U)#W|;CaibHU(Le? z(d~pX-?5A#a1_|h*bdN7XDH}F$r$*E#yQ%^i{Uj9N}XOTEfvkJ@1)0jS{kB(%s4^4 zJoQ7Ptl1VVZU}0go~_%UB(}{vqw^PA3-zq@CkBNz)%D+uFKv-bH{>}@P<=NK4kp%_ zj%rd9uTYd7jld1M1c^BLAF(avT?nRP#N#*PF>HJ-^#?)eJfHp0H#wji@S&0@>~RRw z+Qk-SWv#1w0;@XB6p_R)?VrtFElI1zxrBbpZJ_-6&Cg?jfbsD91o?a419J~{-$pAW zoa$}-F<#?hxPoOLS#+?2yAB!2*3q&2_abp>Ok3MN7STw)t7(O!S{2EJ?gNpIY081g z;k7oky*9U`P)ev~7HgjHbbw0@94ku>_MW@49AU*(^#Sy{)E1$-M!aQru|{20S^=!tQ%eYlI-k z8b~Ow1h@D%7EXBaHUGI%IoB*rDSl4(xGVPP@JR)5XL15|+YQ`fdr*s;*q~)G_qpct z$WzC%`aQnSusYNy3H*a4kh#FP^3;4@m5-KfsQsB+Q2`}vh{&s z1$48^MtS{y6|@Sp{?io{TI$@`7rMX2hanCn8RNb#%lgk=Z5sjCf+H>kpjf9ALJxLo zpNwTet36K-%zmZ~6iQbOql(90w5#L^8bpm1B`RdZUFalqL#2EtASDPEtS`?-mLHlQ z#r1Wf0qlNR@enx&iMhNfFDaz$bHeS_CR~SPwM&atw(7(EWjttI%lmn9+*!4Jq$IA~ zkLqapw9ilFxyZL<(Onx&C{j@LNJj{4ZFYqYL^+B!_0xd)`@`!2Phj!a~L#5B6WO+)A~`2D^gxqJBT5Qib7!ch@8gE zfT1r@YKQ37F9~f5Ny^*Ye^-t~ zY}Fg>cYxQSf|j}`*HOlt1daKpy}asc*?|@S>H)S_i#nr5oIVQM(f= zUV)O)y=Rdo>AXsE1l-^f1o}#n5VVeL^qD8?`C%rn%#NYoGu~phz*Ev#FAH32-VHEJ z_#e<8Sz;g!SD5GLPYYo%e*DFg7KC6ZC@4(U0_3nzfe~??BpFjlF%2YMu=7*5vj%+*ZO{4=9#Wh?dr%spXS(_BNDRaRilY$Y1o1y5*;l(QY^F{6o?%Z@lsW6PW z!Wp z-jCWHO<%VM>N6NEnU8x~O;>&0!-( zjicBlkHy-hzw7l=G1r5-ExinlfDiFaU?SvZ(CY-#hI3kIntz*A_HJju_G_$0tOHZ5 ztQ=^j1s?=1YG%*_JvPLmhs^mF8a4I1FiQFv$BCPZs+|qfd`%6?Xy@#pl*X^VFbTpI-p>-G*O+Cq>$U8u|LuT}(fK zMSQ3rl=Q}UqW#&qZZHM1*IB?i_ z5cvw-u1CfPj>UTN^AoE~kwfeO0E#OfGFRypbm)SOwitfJL$Z>UU1-O}Ok@a*PYB!)qq{J!)y= zZdr}WrEV8%tx3RZNMk-n8GfkgHX{D%^%$gW?EnPmq)RE|^oCdS32Q;IJrKytK_b*Ivh#Jy1yEKvx#@hP)XY8WVKwGN#C$q=MnYs z_R3nEcMp*~&P!jW4nu?1WjDn7J`P{2p83)1WJaJg1jEl|IbQlz=Ff)Br4FsuTo(VT z5+YW#06{BINk3Tw1x0Fk$PWs4X^!@KAjU*7>sMNuVC(15a?|QG` z%1`M?T{a;RpA;Bjw~fJW?;oFSOIhIULj%RLt!#~<4O#g%Y?Vw+#}$x4o{`S&jgOF~ z7628BjIs_@;!C3*3&3b1XT*{WFUeDP0rM(pDnJ@vK!JYOt#gC)i*JYVjZlPo++;s^ z@;#WQD*T;BFn|8)MuA!yE0M||>c9Z46IJIsuketKzTEC^-%X@$)z;bL%3N%=(-zLk zWOYl*Cp*tp5dC6HLI031Ze=%x9qnCk9)qI%!Mj zq};HwCN8}GU0H~zBM7G8o+f^_jlLAeC_?=EuP@Ly`q72HPV*0Y0$&u!XF6GtGDL%O zg$YorFL_lLuWvEp>Kt&8NoQPLgK2i?mgFtBej`72pW{NZU;`32=AwKA!;)0-IL)*2 z8IY1WH4R2esr_+lqqpXllcsxAQ#w-r!f|Z~5mC{^v@{sP`;~5ge~0I~^MPUeCng3N z4&!&c%&ZPXhMf3GdaJGcUl0eUc|XZ&ASz#nxPJn!Q|y@;d2#7ayeog8J}yey+qlp6 z{tzX?nAM>qt)in=?mC$ts?~2p?iOzrBiH`)vufMy^NXL2XMJ8}+D@&wqe|&wizFW1 z&X!`om8tYh_owob0Hm3s3av6ud^G|0^rtXM_d*OZ)-3oOl&UxmKz?VYt&d+=y<-a-n4wbWi90#yluSEEYrZc8qI7XJZZY%WCQe5^?Qq!c+!F*VzTxeOD@6a9N zlc^>!LJyh#H0_UR;%q=N(dduDt82x)p)w7qdNA(}^MzNR`pe_l6hiZy;YiJ4>pUg>E*uzw_|M;{z zi`LzEXR*!<=2M|5cC>*FMvZYm2HSe#it8m+)b>sbnFACpI5$ql$3N&dstTkFU9bF( zS;&8cBe~+L=g{1&73KHqE7nrons>C3>e6G~Wj#;ycg%U^Byy^@@N+mrCgZSP)cgsw z&UbH`91apAis1_;^)fQ*pmmrWM*=L0M_=a-zlVipG%4B0QA=o$@q?#|{rggffbdw0 zljPND`W|ZBA4bBxx~P!z<@uxGcBRQ$Q!b4Ov|6`WBPY1q_lXTtWRqqY{fLy-8vIR9 zmGqJt9@}4;2o`quX?59T^3A@SKto&rv+KI-Pa7pquq@3UnffN$)1IhYbfN7?#i<>((U-Sfeq86cQ_G;w+m_9t*+ z*)h|ip6Oxme!k6$chj(4V2&^q7>OwqH$v)^kznh-tqva z{e=!ULSN0yKFbYh?!-Xu=oP_%*+A%xn2j~rA5QzL0|9L&4ZmSOHG=Hqu(M7tAgTHRhR)W;DR}E=W zO41<8dGEtrJpRB+ua@c~nns)WG?N_~*W!t|X9+&JjAiTKLwT$QX?qL@vG|-wFu>~I z==!)=+|cv(0pmXD4h|cjf-n!$E$5EIg7#Ljm~l~Z?9t{ai&9KlWfK%R^YmBMn2Tm#b?69X+5bD z)rV?;Aqv+;Muvi6F5*yHrKO~&Re((m1zs925<&!ZIA@ZiJ~!u55KYd5HtTt6T1X}t zHAW+yiv5d0o=FMx{s4lG;Uy))d4FC-CxUcL#YIjwqeKlsqM6Sc8&-gHYjB#va3;bm zhv!yJ$;uIh%35ZtV1(uyOj@ahPH+E(&M+&om(>Zsr`PGZm1Aafw6gRPFO6+POgwnD z;`t$1B4~yIEdx#Ak&O^}Dw-F^#Md1@)M`x5COVKrdH`{hg_SX={Hi!<$!C<7VGr|v_yYE@((w^J0m7H$4d->3)-Px>h2`{h{evNP?*%eEVGcaFfkW^8l<|3 zK+AU@)V1QC`3vz@YSzmSPh8~t+1v>D-g#AxJnWaBC!>lZK-c@>eynO3xG=#|AjKy= z)-?yq&sQ6GgMJcpXloGPy*=oijDwt;5#@n<&Xe{Nz#|ZWZTPw~j-9(_%nuvrSJQjE z8y*Uu@_AbSy?Z|+gnFud@(_ITT;6Y1X71jfpz!=y?yr*$JhwMo?92SJa))+yBV|>u zU49C;AKsV|)CSRvm6Ew`kOtmGe;l%9W$0lct=?$-55uTH2nHI@m(7=4L>?)0D=HN8 z9k`sc-h3r?xfRUyO=FpV&|PYHWVSGGv(W%oaqaAm@^G$@*0TnsHx?cfoL?z^NhU-!?&rIJRsd*ZxC`Jl3NCqg5H?Dnm=qNxv1Pmvkjgyh; z9T_@7iSgS!q$k4u8|5H^fiUNLmfE%n3fUcit>6CEKpm#4rCQ7?tv^g1$} zU_4JG3T2qux{`G)#lvmpFPBd}gaBm7co>ntqNviW&@0Ss;KT#UqM;G-9V$~}N%*<2W6(Bxu!s-4UrP#0hBGW`s!nu{#jn-vKba736mOnJK2B9$kT(2kGll zP@UP-gnnG+xOTmBGlaUR`UtOFfKlf32&BT+n9N+xHa*M|e zP`tL-xVr_*uj^(+wN3$BeoTEsN|ijGY zea4xUynMAYO?<0EOK~GM1qZPTQC!)IWrW0-rtZ!Ux}sK*)0;>R@FsOCPEGJO7%{v> z<@&Y*59eyGtYCJ$=0beOG?sTd%0SgH)`5dB8ey~){g#r?Tl6F4cH!^(I_)@xI&EQ3 zovyWmTk?3yoB2d-jT~%|6Jf`+^D+<$9AE|duu^tC7+X3KgK#N@be`njVlPgIbjSPK zgIKIEShn9m)C(DgSJ+ImlauD*{Bk<@t~Qgd9Zgr8aT#Ba_-)qE0(lKQp$bv4aV5h^ zUJ!^*%o48V^P}Np~?v>yby79b_ay}xR0~SS4EBAPszf00b}mF#ol4N ziSgOxk&hm)b&tMxv6x_|T~QBtb~Nk{*FzLqZN)3n-{lBns7<{|`shWcECq8^%>3?I zqfp!OByCz6_)!PcAq98euABzwajdc(o~xXVU}eH+TdxTwH$&*zXR&A4r({d=fE_%J z4-qi%tawh%Hlp%2j4rWoa9SndRGzdbT(-!TsDN^p}7TrtGT+Y4HYJWgi;1W-wskU2TGS%gO( zHKpJA1tN}A!uQe%-A&9N_>b|hKxI9de>ESFF=U-JoQX@1Yd(egIAZL&>c`r88%-=A zN|6+n78tQ(`n40b4to{2%RauHi)8|SrZ%BBqJU4~I&;jGJ>Mh|q4r%U@lEA=aTK)5 z&xzB7{YfrLfO=n4NVCws43zXam)#FZ%&6vK-n zWidCp^iCRLiVxy-S%65ZMe{2WgiHcX_nQ|?iQ7fM+aI9!lCsPBvJOQJ{osqaq3Ib( zgOmNa6Ji_YM+9u!__LdUTk~lwjw(w+voU?ei(=ok+g+HcV3_N83h(@YD^VnntWCLJ zIrcsptFSL9xq;=G8 zQ9F=~&s*I-bZFJsa$dUopFqS}TQ_1_f7AS4N2s)~EqE!4pfK&_vlrhP-b8-I%dR9+ zfugm#n}?e99nP3-c*eubP-vR(t|5?B^CXdTcYK*}s4fI;+lRHpmvewCr?gz(JH!|| zDt;Y#ZmeCKZt@SLtltV~#twMj9<$RPmrHc@;3=a9jj`ZVZn(Vuu*pl=17h5%_@p)b z5063v`Iw#_95q+uZC@b5LrQ%;M(`{vqbLnUUCKAW_@Lk})w+JQm(dypnNYh0%2w8# z@yJTN$>Nc0oh(VzaO?cn0#)?k)r96t@7Fq4K{W88OYHS~q;n-ay@_$FP-abeVeih| zJKU}|CbaKVw^rn-u8(tu$;6+0=@d4^hG9J}$@)@oH%aYRc(P%=i1!N@s2bF3En##_K>ah%o%n(vmg;Lpu|tuVUr zL&kd(*38ah4r=F5UkvRu!q+M*CKkA=L_My)wCo2NFkNj&ykXj{xLPi&vIn&U0x!fo zK)2Ff!9+yA8Evx8?|65Nb3)_Lpm5D28;OP!7_J7);R=Dl)o2&EMb_!yx&n)qZ&uok zArs!YpqgEAv~{)~`Y?{!A8NU-=I^QK)`BSj4<;tH!*zW6$hG@rw6pDF`l01&ACLX7Xxlv-aaimaFWI(w&~WZFVJk~sde4` zVZ1x%%4OmjkKtb2&XKT-9#6e&5Ug>~aO!x8K9RwWraF8ltTIHU)BvyFhb))N4LS<- zang{2vJEItV>#22!`lpwk^LvbA~4{QHYEb+h_G)<=6;LVvw$(%#1Z1YGh51ML(2yz zx<=T0Nt&lVqa#LIFipsFhvj;0z+`V zd%{I)3*NPr4rjgHWi;2)tLmWOMOXcYZ9YP^Y%Xp7_mJh!LoMrr)hOPUXxZ6QMPN*J z$=Q}a%$r)1SkhxM?&;X(xWmyV5h~|CkVY~eV zh%%%1m|qytP$XR!ehw%47&Ih>q41+6VRc?wAoQV;KX==yIK0>(thDErI)Xcv@ zNMhVa)6h55Mu6kyCf9jnpcZBHb*Wh-L_a)`)lRrn1`$~yUTjQdro-}uRKOJVeY1CP z!oXJYtjHl`-S-Sq@aE^;UD#D`G?Cbu*`pP5etk!dCyWO!N(@N2qdu}@jwZc~9>#)> zsB7{PpJV5K?heT*?RN78FXls)dtJHZSBW_}f^hY7!)RWj7ZBM}MIjeYigW&RGcFV) z%)UYc@__E7%4+^u$O(0=KeIk8+H=5EJc*tnbpt?cMS(`d8i71*(SU|JZAMuVZtYiU zPf?WbFrnq9o zzL25z{q*5+0wt<({B5?RR9&nVkh70$B4mCu3>GwX8^^oyv<-ltanuTfRwGD7R*8S9 zR{btLgcR?rrhY5dF`VFO3D!1O5MsD&Ka>zBYFtIrGo-&5ahY7AQ~?zmi|ZV0Iv_1a zxuWD0VURgDzqq7pI&+K`ws$x@HT)D_JFvP=x%@!>{A|jEQ0F%VX}c1-r$^;shyB zG@L4)WGI#}VDBXtEr_?Z;*YnE`p1PWuO(R57uF4lgjioHO^TY-_ITlO!OvU1Y@5js zQt+E3{@DbpYEm3*$d;dKBdYR~wpe9PlZEzE+?gTYPQ4aBULJ^mpPERtE@ zY6KHjlIGwr{?%(bLgLZvd#xIrxa{R&ZPOfHWUmmE9KG8WRbGo&Bhd1YEAV+lSgSyV z$z;Q9Wh*=W(;LHP;X%FshR>rG>pXQD;1wx`!I*N=haER) zGr>~}?&Zp4yjUUb;cUwDZ3;UtMxoqZGJhn^hXm30iCw_MxpGb_jN+l6LUe7ei#d5F zbS)W5gRQ5D?XBeXdu2U4D;n7puVNzLoe~yH;O@)&A8YH4mqDxrkC;CCpMx({&=fc* zmvL7owz|(}QOfX$UFtC;np~Y6yfMg^*lfFfY`Y%uY_32P#6D~%z~PK4JE=Z%P^vpF zp_Asx#(Y^|92FMXC+GSkEVRHKhj}2E#A_(+kxTg=nMj!^v9T_ydfih}Z0R^Hj=gD7 z6D@v>5%HO!BAXL1v3xaDc02EgHy9QMKU_(>B?cYkTx(iWbpK2Do1?UnxJuaVJFC z?xK*GUuI}@S=(%sngNOo%9S`5)YK~Q>BqE&vB4G%#aB98wi+JJWC_5Dh84N!tal#; z*UPGf7Mp#lNN`oE4`#;#J9I@<)z^w$pM+9zO)S7Q_1W3SRGxadq+}EQ3H!O83E0plQ z3t{^{j46BpaI$5Cz926rfs~>kII$Dl>B`1RW6P&Ha&fl#91zuJ?s&hi+Hf5B3HqL- zCMNc7WJ3j$#=O_@n=+0Kcz!wBO+6DFnAI$y>EEbAz{3(L$#XZ*&bgJK&~VzpVK94v zD=n&OUg9?QDj^9~OL{E@jz>4%BLgNfy>abh8rwc`tq^g~fy4rPms9J3e&-;TyG;2Z ziyYxY^vz@MawT)4Cgs4em{9F9?Vb1IW$5hj+IXxdaN6g*>=$0E04Z#}^64w)xcX_t zWDo!okF=m*W9ya6?*4SKsZEt+H2aM5#Kxh1SSPqp02{ek8?QTZf%DYXzKCVKfj?FEfA43ehgUTBPt zHAYoa{(@V@)tC%dQ?o|K+&u4ZLL+X0`JW1hV>)6Pec5fS)jN}ZpE$?@3@nF3sW6rg zQZ>B=XnbUoG-98ZZ@XBIPzD(C1j>=^Tj3$~#@F^K+E&8W)8DHsNHnb%%?=vh#5OW_rHLoB^y*9 zeT($#LJ~hM*TrB3HR=61Q8^+)jGAGZ@n$93Am`-E0++<*)EgsZ_?nulk~tjsx8Ew7fe|-hRkoM< zu?5ivAyy}P^%aUU5e|lpI=ItMZF>-k)0I+y(dZMdDa{s2B;hk!z6hrLC@Iu3OJV;C zL&eP3<+mNNY_s6-GraBS+%pufB>pz_k_*1mvan94A)sU)bP!;zsR;q6#JSY?*h-eaEuXE?1&L$g(8kaigZ86@=k57Yf6i_fWKhpyu?x-$;giodwj? z-D@Sq3?-}$(`>MdTU~}SHikGt3L?}R7QQXcN}X}={F8~F33`-O)@#$3BEx?uP&RM~ z1!{mawnUc$fNNGLx{{H{-#FuN%eI$)j4WfZzR~_YLpn<@EI{ z@2^_Dtb3t9G3Dhj@AEFb5dUA17iEF}up8;XM( zD0`tAJ6HvDl9c>W``oohjYsb;YTQrzGx|QEQP~U>G&b~XWF6-4YASqb`Pn(t*43i z=}W_ROA%d?l&KGNNSBUDX=nd1xv7m@ipL_*b+#adDnd)u`fcUfGN;fJ5oGDyRX8jq z0*ry1zX{D!5E(dNHA_|L)R<*FI)#z*5NlMl2)whEXS6F0s0hCN%N5%W7#B(wGxO4V zaocH?AF=A*W9r~Bu}rywa2k3qSXDe`K(s4HeY6nGi&AhY#zCUS-dubAOyUk{Gs0p2 zGSQrd`~iP~3^6;j9YDiG7L-$q$`5P2>PKx({&dw%@>zasHf2 z;&Z241}l==_VgUX8e)w8c8jB#yODt}m(NaXZ!wFKe?R*jKqgPgFh5!fd!=X6)AC{K zIubW@S7n>FNuZo^Nl;BnTTqAWR6Ade>|F<2m4D%@i4NN6|c#@A#S-B;mo&v#dt?d>Y&zW&-I zlO`HMD!Xe>+k?kxLB-bQ=Wb+LkD%TzC^2n~nSU65hEjbM{MbxX+KBo3+O3Ph10LGh zoIf(ESZVR_>nNLr@z3^GN_qXZh&%Q0B_lV%Y*N#nN&DRh@E;kX-Fx-azUl7cx$yMcvuvj+I0b}2;{B+zt}DGZyq8U>BbQb5x5{6F zLkpD#0~T5SZ`@iP*LHe}7ASu1s)jr{dr)SxHMf=99=Mf=GnRyBa;h$HL~R4*wLO7E zH3`Lw(wUmh4=fWdMtQj`ileDgWVH;h3g~ctP`yj?3S`;;{GGru?k@$bN^Dhyx9jx{=y9{2-$_o;lLA zYsvE3VHsa0&Qd*?X{j;Ggq1VgndEu|7`0 z&G_oz{qemCH%Y5jKHj{k_6mK`<#>0-DA#w7UJJ{pcw;_b@ToKgw+#)dpPpk!pY~si z$8Nc94k;SXI%EZxol}f^P4fc`k8*`h9kb!hivBS)*C-6cebeUUX1n4qF9ydx7*qs$ zd+9v>`M9^&1{KM%qjdrvmyzT&AJS}HBT+l_%JFPrsbOsNs%POZdM`r&t=K{>sceVZ zJy@0)d`UJ3o^=5_|8l6`%7(j%;Y%e+ccsiFFaymO5O-G5MJ-xw4MuMkj5E)$ zWYYU zM#CY|%Gi|`ar!PCEwHmo0*^36o%eGhO;U;|mw1^FQWc``g*D{HNLiIBg7H67?tZpI z+t)TyidC&%l_IZ=4eIF)hEBE>d(z_-h>C2cMYh-jzAN-i_chMv#ggi={7t7P2aAmnQW{b% zRamI!3^XmIqn4g8{F9K+p0iTRo)dyX@p-)5_m-dpH_l+}0eHKHA!`L?Eu3si?p0Zt z%!l9y)HO{{7jzS5C^GiHdpY7Z?b%;4q&t22mb0EB@vUUO5ZjtAJnzaQC7)s^gRF$DV6*2R`F<1PrAO!#cACURcHUhSXabsPT9}CXLBf zR9mFZ*jzfkK7fPoN`woyqd+IE*pr%^X+N8lcoN{0u@-;R_T^(&s5TmSIR9ASo@rgj z)(s0-=Y=;w61d^;fY{sHoB3_4JLrH4m9%7uI`nhN!KB*-H4sfWAojlGv{pfkDE)9j zG5`r`sZHr(viuS&wz`Ew-OuDWVg8pX1zVqazE#_W$3?}12NW99(an|=9tUC5L+A+q z5-bi5&d71L%vq{l236Y$@9qy?m#4cwn-M2Viuq?VDF}2uX&V?TUhoHF`VbO&H=IVJ z%Vjo+B{TP@GfNk2o}m*OQTpvNv)&VO6MuH(*XljFj|;oJTz4B$vY~di5|mzBI>9!z zw_~;}(Td%}1n+K+@(#4>2pA=RjyGZh0hEyAw2R6_{?D;er;(9Ol}uOBwjrw28eI<& zzYa&o@ws#2Ar03Ym+3i;JesBOSGq!5cqMXp3t%F=sB0+4BuoA# zN0Nj2bNoG%_907#7jQY(Vu66wJm@J|aji6z>K|UF;;;URzAj%_3L^dYJoamSaGK-f zG~%*h5Tbj7vOD;NVMui<9l}FquuK&Xcax9T6Xi0YZ}2?E%de;nSBeq4(a|oDNYnea zrdY_V;BAAiRd{2hSZG{~>A#V*kC73vLjx_{)eX#Q&zNopKMMT$+XiN2JEO$C`F?!x z>uh-<_DOxC%GMEl%chG0T-VONjhO+V7 z1~{4S4>-|~$i1sdVE&)zEr$X+bRI3^X?feB)pOjsKM=(K0QWmkbJ4wfB`Sc|t}WoN ze=H80v{DSN@3Pa$DzaRNq)f5$7#;g5X;BKBz^AfyJYl&L5?@hKbFxZKS{k-k`=D*w z0g-!TK?{FY&<~DQSO!1v()$8vNle`MCPl!5b-M-skB3s+MQ*XzWFhbL^@qtfDh@L~ zdrc2eq0Ay4`OnTfm?S%5G?JXbt<2Yv0jXmi0L^IL++e1pxBNE{VE6mWL4xIKU9QV| zeEHR3^A^lEnq=_Mz?@b%XbLGb@UL^$PYDKGa7rjEGc$5$7Z)W(#Xys>&DC>gdphlA zL1}4NZjYGD0plD1A?o$AUqgX%5^ zK2|HqRw&*q$>n}mxSP(~+;Tz2Cu4H3|Ia$ujTI>v7*&y%M~o-ni~juhLrP3MKpFUc zv@x5@gMLu1K=`)8i>%QC@wfBZ&T@vyNb#1f`&w3(y)P!LE07wAKMExAYBZHG&$A?I zuGZv)@3aI*r7p2j5H0y12K^u7iH3ye4cWztiN=a7|M@qIm3Y0Gfg=t7vVg=1B(4JQ z5xeWDVqZ9_g$$OO&1#)k|EP+mg)Xxl4qXlfI<5Obwb{9n5MfP1o;Sa-loX71tL-n&ta&&!n12UE#wn6;E~-2y$x z(375bYL1>uQdkmhLPM;w=P^*__wa2Sai=WCp_7diI0B8gOGUV|xcpc~rWZF);9OJw z!zb038NgB&>porSE}Fz9+fkK-tFaCZ`GNz%HLLR9tp4mdYgt8eSEV4O-^l+;g5#ir z*yUpQ_pUtJs6vo6sSpIc-k;EVwZH7gpy1+;gMQOU6U%f7`^F;>AjXuF6rmS?PJbH8 zhwC5Cl{IBb&i}ExbL*${!`|~#%5)yCgCQi+D_qrXOyKX60UCD1Uf4;UuqQsl4s1_; zSZnW#ja%yHaoktgOa|24tdri}qcaS8I#Dg={wx;Tttv)foIs_xnvCxk_W`}Vu{rVQ zEBO0JsfwG#WWE4lDJQDIr}n!b1@j-3b53;kEis_XmHLmWXdnWmKBBUwW+b^x+O8-( z)ts-+2qIay&p@ry9jKi=T zru#^1f!-Pg7blCNg|;Z)KIFVB6?0M@+lR9yUf zb!1hJu^Mvz=z(KFHHq6qsa)i^d`B{76zr{}TPQk1BqT?t&XBn7f2+J)=(kmi(phCa zbPan<(H=~PJ>TWeEJ^%{)=biyZJ%o{1WI)_CMKYj;K=YWcu9{A;l3o$?!k+(?iCtJ zy(g}(PAmGp+#o?Fyd{K*4Gg~?(#NQOUZ!sJbcr#A&?@1qO?IT;{mAqD7PG-@!uT*Z zfI&IpA;@UvxqhLQP6g0`R=MYXn0+ReYYs|_=cj9B1yQqXuDbX!kT0MFU;bjWy6b?7 z`ZLuHzEBoZk)BM+MeOT`MzyHPa$h*k2MkW$ZeoRpRUU|aT8ffRK3r^`TB=CzU2MK^ z;}~~R&B~8OF0EIkDp7CW%pXSrlx71_sk6`6WFv6t7Y^DSs_KQh(bmn9n3s(bv#tA%iT=()gyF&^)^x5ovle=*y`>wm5(FfO=p_qlVVz zk0W$_ui z9VE=wn`I7iFv-Un8ETF1e8g4h%p*O(zcrlW-@I$^FK>VardjH z{pSG*f^b>1zBj+554qZL&g72bGWRMbTXs9c>P8|TVdD@-w6+EnAN0`utwjBJ+>nM8 z_2~59Zx68*C^gKX3H&mi!rr!H|ETBhx;pkCm7hVso1h1S_`%O(s+kD2;wJ;*M~}GS z=<`nivK@>0KS%#Xv@`OpW=Tyc0AHgFb#I+@A>f##-fFRon}uOgMNhQXGKJ9uQ`i0E zAfx`Ah4&#pm1?^Yom}Gy`2?rJ4A_b5a_t=?US#aFVut!Na!4~%rmNR6vlP>`yVH>?GN`3T-x^hnizV*+CY`p%?`q+l_Z17MPXj4DcK%Zra>|e`B<cTwOjqJ-S~ zeh7A&hYBhx%xM@MzMg!^-kYtLrLA6gtwayTzseFeL;Xz+yuE9<>-$F(WlHn9Ro85? z{TL}`44Ve=Dc38cD}hQpY$HxQ9)B!)9A^^ggX=T`P1{EVj$-aei)e%kPk8@eE++Pm zkOfv5Fm~EQfJcB_xziSfNKV!t-ntxJ^S3ZYPW^h4w1y_W`EJS8j{&oIb*2S9oQ=!?Uz~KkG70A=A`P zTZ{srXU_(NFEsmVKob@%*FtG!+jR}XGOwA5h&@mFQAq+A4+b7X(FF)|5s@}Af(D6cG9#lv;!g7Y9ymm1=u_#jrmHwd!|g zNSJo*Vcdz1N!q0;LdGlL@#k$XPTBqb29Qb6sH+r;VvW2a$d2bCcwY1~IuWJky#vZY zLBuf0<_f_;g80JX@ZF8D^P8b}3q{s@diZn00$`cL&2aJ3m3x_VmQL)54EsS1E(@c# z;P+#P%@~h-|L-jMz8HFtPZ353((j)$#=}npQCH{!A@1y|Z#9iu=Qp>LBQI;jEUoug zu?ie-mvCX$ckA6Kmi4#867)Ba8G2prV%h9=-LT*s!4VTbe~Cc>m}>!`Gsv%S03?3- z@{h2E2R%_}Y%;W2hr_t53MA5~HPKwHzph~7E1HP`@>t)iY+u{~h$p~txGB2n+R?t9 zCS$o2kQj%AesaI6n|tmoFeD_gb9ru6`^MdYe7iOEYW`qm8R$><@~GL8;oMd`y~c*jwROXLV)VtV5U;Ly*YTV_q4D%GS~00O!Kv2 zN#>jj&$C2l)909e+GJ9uE6g_%GIBhmq(bE!vjG8tHsT^XDrs126yQ!^Op?vb?`qSV z52LaETU$@Bat7{%*KotL?gLHJmA!#%l0;!+t+K8~qwm=A5$YPd;S4ZsPogSTW8G?O z?a#LO{+gzvlwHf7mRbyHvj z?Q;u!Da)0xq&^ivA3NU4BRW>wT0A8I`(E#qZM$nB0;)vfo~m+~On$Yv53@N^qWz>y zk@!Hgfc4E4FC4~E`0lHOY`bF#0pJ8YEzMR#3{c2avKmk|pAb9DbU^C#>CFS7iGY~L3PV`_1+8!Mp%-kUEfo^yH#!Bk~=-KEp-qwgVG`8Mx3m(No4ot zk$f>Qv#dU6OpDUtyr~PpCfq34y)CQWtNzS8bZT)?OiHS_5uYXBT-+BsO&3YPNyF(@ zMVjMtD|8|;=&3@m(37?w}J=&*2=i`00s;RD(% z&ik>Zyui*MI6W`BP`Gs9NMp_Iie~kb@==oCd1;z-;?d*g>_>Q z6cP*TS0Wa=o|?qIhxhHEM_p@Z-c0*X>a-L1c`g-6CtzO93(ZmPDno4rA5m#p=mjRm zqa;=~S!oK-6)O;1+B6>o`{yKUPY0Ca^d%ydMo)x&j!`om8QtgMpdnwX`7d}#uMV`X z24gmI>CyGhB=pOn{8&WwSWk|L9@QF(^DGU60H|zh0d^7`I~CC`Dumca68Q5aioP8pf6;<(X$wc>0bC8(@kI#|OF-l)a% znv3P%olD(}hoy8|oW9^VXh;xxm%R9)!C2Pe#~p$ zpNy1|SdK+d0s>>mSL{_6UpE%Uk7(;T1~YP8cte76vMV>WB{(&Q*5)I_=3j=w<$q1f zPy$vI^;GPUaFBqq*KY30S=YB581Im}2fU0Qo+L+`6Tk=yGR^Rl78gyFN>KVT!}+SI zW)dCqGQco+o=%M$qVY^j9=<+D>q!s+C=>|>K=eV2!v%OQhDyHK?t%8vYg!_4)s3^^ z=-tQO_sVYUMOJ*e1H}dCOjq=`3T@VHgSzY@{YHZtm+zyf23)#Wa{KF^IFj-fIG~?z zI0|^8*l2rs&m=j3-ha}DFE<>Bs{CDYzziIS8;Wco_wCTryVD*t5|RdDs+q{-YJvc* z8BW=wECcLMz)g6lQ<~?DWbt1-e1yja&7V>fPzhwfbR~zxA0fqH7ov+^-QZ{C3Ur>= zlF<8F*hLn4%{GZ>TakousD=}p&b~=Vx}J8dpTHrVYkj^4NruM#)(-P`@kQ1sJGTn< zjryQ6K+$(AZtGfffWO1YZ@=dDHMw~F9eYyZ=Cy6l;j;N3CDdcgMX#p#*|OZzIYxnr zS3E7NLFQ;qHKEMHDq>M+>T>wq*AMO&oRUKuRz~e4#G+c+E^Zt+a!L!HrvSK-=Ac5) z^JMAGdd<{dLHehNDJ}|xMX_j17&095gt-K(NN{TPS>oZw;0czsrT9nK7H`XzFK|P~ zbC9%gKS3@x!gg5(1D$rQ_>R2>1o@&_su@FiV|nFSsbOdwAbgIOl_T^^WcEgBruFP3 zY|P-Waav%>=zvQ%VC1=g;&-Pk+VFZW-H^+OC5zf}vBDncslJ7A65MKa5({p)fdU0K zLmY(6TkSR{KPnefY-Vs01KDyq2L7qQWc_8WSiA<5W2AJxE^Fu*q0{DicN&jwgR%GLc-?KjudK`+i# z@!b8D{HmXRg#-szkprArpEM=FpLGXv?YHU9s+FxFG91!lTpVH`E~nKji+)O20e)!l z(_HbGPqalT0)FA5hVIvjyfvG^qGLb}Y(j)SD`_GIiNI$pNP-Iu5m&|O;2kjHF*0#^f5S$=@8gh}Je$4H2 zA;5M3;#q9J%%{i+wUmy$SY83kbI*Q$uL?V{-7ma|SK4aeWy4fA1+UTgPpMIcC}*w$ z+^EFIYH&Jx=zhGFk|s}_)#dp6S}c;4HFd7%eMJfu!ht3&Ncm*-0KUV#>uwy%%NsdC zFUbK}`FB$N#=Su)ad_u(vzXhV)OZw}u;@>`MkBKz{DV1Wqf0GQQFNfByfwH!uF%m* zk@teKvv>EQFyjOBf>xr*n8$TSLyqg^4GUjP_0SD0A1+*6DEf+Gq0??L;>Wq;6dzN2 zu9o7f-`N?>VEl?B*L^9k{D`Hg>_;jH9iPdtJbuSrL=5`19i!nBk@wXHbainR3axmT zDq`FpjU_2M4g9#~yY$AwLSHvKuR#W_{qoZ1>*t-i7li|CQm=N>1-u)}3`DtK1|249 zt9nCs1DY)5MRXloOQN%~pGqVAHI{JL&6W zh6!t-cS~(~&M(4LN$5T&Hhz~Cg1MeNuwhc3cdL@~J%e3=|Ita|;5yzZJOaM3%@d<{ zp%IY?-(7|59vN#ny>*UDyjuW0<6#w6v@~>=6We%t=nT*6mb{A%1>b0hHmr5N*fno% zX+q?jOUTxD50nN;q>{JBEk&s|WAy3o{iZ`=N{T4UVl%sff=>K=Y5=d_n82Pz$DreW z^*>%-plAUXQ9qO_(idB+Qu+&O8?&F3#zsD&|Hn>={>M&nBCTdi^9`!9i$u!E{d4_?B-Q9E z7X4D#0XrL>KgcQn8_x|TDe@A(LO^4kTlBN94ja~>qOP(ysanpopLW=_B^3Oaz}y=5 z2&-2~mCiwo6_tU)G>(CYM?AvvYHs%VmFdY4`L8|&>L^vk@?P$|C) zO-zc?q{-?5VXHl&soLmKyeBOb2cVzTM{FRSfjVPG7Co503< z+n=N)Bvz~UmNgsoFg-rc!oKGi&>W{xvp^{&oU1J1(@11Qm9nSPD$my^G9C@e$yX;! zAoLrRZ&YzK>OJ|Vttn)NfSotglC<<_7xfQ^T3+;s-G|3?x^$x=qJkvq8ZN>r_yDVH z)*MluII<@jJ>)s5?2EZ47p>}|+F2w*>VfM%2a%YywB5IHGjSb-usjmk&8ce9f=XoM zk1&SM-bvywJBh2|=e~8v*;9Sj&Wb4~-OC2sd?(qX2>`u*UbFBQ4L{*hJ8Xwe&rv;J zlmVM0x0{_06oEa@{p6@$*By;C;!(m`xkw=n3Gc`7iXBe_&s=<7&)Z8$CCkkk$a@7I ziHt;q>9zRwnyG71c*e8${@_t{AX6U|_sRH#fGY_fG7cLyaQ#=}`Yl z@g;4ma{FpTeD0PaIf~`WObCN=`MlgLmS7%Z=6+sTl){8L&N2;dF^BuG_yBU!W_Sz{ zGVYer#a=e&wd$j(=R9XFStsq4ny#5(KBxo{O#F~1x|Zb~AwZAiqI3IJFk$bU7?YHY zw2XKv%A46$k{38)Jflqd#|h71DUhJYxgx4&zvLq&(D{A zH%=c+<}LP}ZC*Sl_72pCiAYK! zJ%7p?j!7vhqb+0GI+BSLgz5Qx>%qHa6A8NDN2Yq?yAN`JKsY6j7D zdj=>yWiR3gZ)4iE9N_ouJ7SF2UPff@b@7lboTVSO9!;orNz_x8>p&0_K4H~e>LM4u zWVikF;%UW85d8Zf`Q95fOJ=o$t?}d#^&O}3et(${T&pYoz)}=$-HkhH)~$F^#Qk~{ zNlQZMm^j(#!(@)Yx2+Zn-Sqy9YR1dLLx?iDjmd=18QG?Z*2s1lgLHi?@LJ&g2-%ps zjHvbv>Af+bSDIDSXn5TI^vFtdP1-1u50d?e`{6VS{Bl;deIHL_*#wnvH=~-gZ_ZR{9 zqkTg!h@vha-z|4aZd~avjE9Tt*s4vx@iG(hLF0W!CsoQ}Cw5wyn`H%``uth$rOcv4 z>&D`O;pn^}ZzD&Wzv6{ZypP+kU=avZPcgXzB6vKZO+q81DYFeBoU1(SWun}dsbRN0 z<@e~vQl!`9pa?>NvqPh5bAI8}xwbD}+h(5cWwS4qN3ut}$_l-cG12;ddxv0SJvpTE zm1aixx4Y+rq-oH8J-bUIQ3KDCp zoNG3Gu~L^3-vxXOK(W-E{{Ee4_%-AA6Iqfo`P&|1xC%X2Xf(QLDcysH-Ex&?wOxan z%=u37qh*YmO@nqzw7Pa-1yKiYHGxAVMx(YtkVOhg7c z+kZTh{W|N>d+&8`&VS9*ap*cR7GmsmUj0$jtPr{6ER42%pxDNAdix9E20NfP86ON=c0m)$nD8wjq)%d8Nad+YSeKrPTbLMf? zj_*$5D|>}R$Qg#3CWr<0VN^{$_PHQF*=9tF(nZqrrQp1kjJX%N;$r#WCrG8-KaL6^ zKy`51`N;(f$tUv<7*(h4OX~Ux7HAg<4Y~AasZK5v5t>ZTW%w;`Q*$=axN=Fy^ttt;+Z_+jOMR4#4AfNxyQ3GiUhGC}#bJKSx}x ztOrB<7XuOCBQ0YP+sBt46seAZteZNaX+b=1^j7sQ3oAG5YfF6-xSkc;cIWnsYrHVl zhT=iIE!2{%7>g^lyuQm9nPSlaaJx1&FoztHznvJ*FP8ao`RBDhU2TDgKp~lASw_#Q>Q?PD;pAkhIl8y+46s zho4kpKOCgaPv`OQb<&|o>KAgD(~u7_(VVPk)pv_IWUo`yc%r=e%FfUM5yk*ZaJr4P zdOyE-4L!Nm`)FT3#c#2Pwa{de__}#A5E+c-n~Ed2Bix9D0CiWglBa>w&%?0E%Hgw7 z?G#Q>l+vz-(*cMe*20F%&l;82iWBI_{y&1HoSJ zC$)MzuHb2B68977o4y1ZM*DT>5>zF$OI${8GV@0`ajz$(8eY}7#(nFhF-4PY%j|6Df!Af@5Vss${+X^yTPSvOt$?*x60*UnItoNODTwYeV z(;c&DUoOHL; z9}GAG8SKa?ha63*mtqur`Z0Ijc&euBe>HyMQr~PIj&_7$iOb!k{Q3uN8THtt3Y|-H6c7>YrVk|h z1%Kq>!AXF9(B}!rnTr;A3P|6rsXk2e#C})D6B;HP6*Q9~RDfrt{SyJ1NU*95J@%_O zs0^pOk3B?&`Xo-35*6J{Au<4293igExe7N8CXqu=R#g)-094Y!(C|mu1MPshwmOf0 zWEun#P?X0P`nYUCpH6P<*N~Ry5##FQ(NffpKu1SN>o)EVCH@Uj?-4J{zU@;P(b~Kl zm5u&KZ&w&|{AOI+(HbW#zcOgTD=I;B1STI348t|KkLIUL#(IN{h29E#qmr1slJUjm zHBfec(_-cF03zY`+N=TH+*~f+oyqf(q&F{orEm|yb*hxQ*i?pc+}2C zqEZV4Zam0X!mu+Xwzc|8GUq3Yg~k)f&4oS?LjN4GP${Bv{RpDbX@xHOfi0%8a;1ir zC@HQDvGxdXmO5*$D~tX08|9`2jUp0c`9?K4^AxRYML-G*kuEJ|UBrU^6^m;50upcC zYQbI)imWEJzSN4)>%&1jO*}<$(wKRLpZp^?_2an{@{G-i(oa18=!9e}AYa*9&DxBe z`ZY^y!nFV~K*0IYmp(+apc-XYVh0^*dI*+#w&UEY`%e!wbUmR9hX}l+Gz}8$^In6F zMPQW1yIUJ5piYpjwzDglG8ZY_YqHS+W;Qbq~58FdA zIVY`}xPE!;a>~zERELjy#+uiSAU)FYlcmVbEr-ItdJaT%X?e{*gJBjI8%ooPn05!6ygjh3MIF>MsrnI1fO-z1%w+dWd3EkfM?f9aKP?d08=xP3A z{B`&uiaWoPy#{go_qRDPZYVQUhLKx}&?_-09p2A2{&{2L)3g+S6W@puKNl-Njt6n!? zO~vUQY3}45wA|tlrCwO!BincZc{YRmYSFGFfjk1>*n%sLxq%4q3nk9AL4X7YZ zW3}`^!+ukyf0+aQEt%X&UOLqqn+4@zm9@;n;N7$rCg3k)y8m05{_SK%JEN7##z}oW zNBG0ADAAAp3)F1yJ6Y=XM2)coTgL?@>+t?1g(eNdotj!QxIXCVJRdfS)YQEgFfUH; z?f6)R7^XLhH}i6PXLo&{Mmnms(A^Bh*f<7ZkcQGb8aPzz-u zzIJ8j;!`!Y_#3|^&OPLHMp}w^16mC>r8>`X3CxRJDL4^Xsg6id6l9X4>FpcTXJ1!Nb1hT<8>IE2~38- z@IgeDI#H+ub5ff|&%RRazcrG~KAj#6^2;X+k<4R5-{h6^qy>auBdlzWxXO#Pe9w7q z$c0I^pOr3(5K#;5hQO=*Zn2#p^l{T)e(v;`*5dh5D5lwZ3Q^PN(oK`SgkXFrT%ERX zDVk!b@>XKi?yk!E;|V*%PlJQR#$B_bDw1H(<*0`{bNvx|9(m(-*Y0M~#*WT_p*rEj z>@ZPdn2=+e6~to}H*SO@VKkS+AkJx$7x_TFQ`KtEhlGGI~g- z^@!n+o{mWN0Ky;rX|XD6Z_Csg9i)V5f~X5nVhdcC zF26j$H=L^z(*rPkoX;ojg#d<%6iwVU8~!+yGS-f)w1l_`*{Mn*v!+nofBr#1yEbi1F&KXoN)$*10u;{hzb?_vm=MK47id6Eo`{)#;X&~XgPqG40K1U%_I;<2T1E5291 zR*bEyFWIv6bAgJMwuRNVUk!81+_wxsnPN3!x%A?3ErZk=gL7(Up7-)=dNw6J z_eE&EqzX-S#r^JOpuqtTec&AW>-dN5>pn6+U-A0Dssyn_R<3XEgS$^&ldq;QiFcBH zUzv6Hy%NQ?W^QRxfHVu_&-7_HM9ieo>^_8`-w^<$F?1ci2cC)VI}Wz#M7pkXFbe49 zFbQZdoDN>Z=dwRhq68_4`u}8%?_Cj~VI&9fzO^=`ruF=FeU>?d_(sZ*Oe z*~1YGpOPegOOgKRpbXG(4DV+}3U1Qt0!8q)jJCVw>-HCI>ZCKOiV~#9>5;Dg3d{m| zfg|-vv5bU2iN{rXZt6@oqe4*qs6R>8CSi*33Dro;<|qArZSuC zK+Bf$@w9ES!rQRpk5%5E+)QAeq|g*YJ{8D~K;E@0`62MaEDVcG#b3-z%gPMtZZsTs zLSmhjsY}H(YoR#(pyNwGeNY0G3kO?NNi-!B8;+0iPePO-{P%L z!|&AxC_+1iF;vq-UJLhLf>e-C@B>nB?3;Uj7L18c!W{;fkKFQavsjw!_yYs?ZfmzA zFk&XZ4?nSrbq%g)yQ2)`63&*9;LOQ_nL3`m@kVy~Qz$eWtLNl7of-fs!9x%lGhEhe;+|csR7ojwcEEstTvq`({YQ zIq`XSbugUYO#I)7ul`Q){>XZb)uz8Rs`0Ytf1A?`)@l1O$mjA`3IW&YL4?1NRIj1#kM!vg%t8~&!_-ix>4?jPoLkIA~Iq?ILc z-F$YPb~W`w9+U8cpLY9Y<}5B=d<1dU#UIDYAo)eHS6M$FkZJ}NYR}INCNc6Ne`G@^ zieuS9y(Spd$%{*;$Bw*b1c(Uo^X}y3(DNO9)1E&{p(JO2BjB|o4o-wF6t)YslE;6T z;m3gwZW*KgFg;vBOjpil^3%dlo=nbt#K5J?{?1hdSdmD=mo9LquzTN0=QLzD8W?0c zmFRW2Ya{+42`01pnO0v(xoMdwH4vx4lS&l7Ug@lOv(Tb;T24OY&7)`X9hNkhD!7-9 zZlCDkXSnv8xG^-UgR-(iE6KcQXt>>NvxEl%p1M7U9$_aWq!75~ws^afK#>D7jbB)j zu3zwZQR`2CZ7z3+a=N;b72^|yd@(6S&bMK#vyBLg+x$4MW)4Q^d(8MD!QYJDc-o80 z%KYcIPe#UMX;+*Ew-3r$jk;qcdc$nj$%~{TN4}@F6Ns6VD3%By4lc0iw`_=x+|OdP zeDnR|I^0V*qTF(XOZm7)b%hMe^M6j(D9P_srJnn&33{~0YzrfZ)eL0r-WF^_axq{; zgvUwieu=rt*7Ah@Ub*FO)e5f@BD#L@FDWnQp>>&Ioh^A1`|k5NkZ3~5Napr!ziTm` zknfC0&DTjrdJd?p&fa6zt&pACwnWXT;7Y^KEVVzNvo!O%Ukp(6db3kKLNgX)U_E3G z_H@F5DwHr#cSunBp?MBLWlxq6n4LULy0cNqpy#cnyiv5>dOCk@qo8YuBI*$N1mB&w zy)wRyM?!x{$D9Z?)_Axo zFhUBHCBYO>UIQTGKsr|&%cc>TvW^-GH^%bo2omvmsD$1xRTZ_C?HxW`v&P&jk3cT-ojomdvLbCSz6=ahF+b9L(J?F=9 zBUH|K z-Xxy*YYzUCgh*Nmp|l4H5_#cp*!KNg=mb?dlv8NXB7n~#aJxJ0u;=Wg8IJluCSA6# z9ojX6S4>oV;VD~55}#R?m~wmC1n{%-Z#h@~6U9y%E3guLV$Y>vf4~W+EEha_?saHK zU71%^G|dzFq%n`sZ%eq1Uv6{@CGrfVy0>_OPb<$21yq`_v`6XVPefr~0(Zx?2dn3A z^!9?3AsyG#hpxShxriAg zlc{KDJLpI*vu)FBbVwYjNr|r5_`OHPvN)qPzEe48b&sRksoo7o@%~R0GxaTKDv9^j zkXAAt%kx!t7~4arAcgfw5C}#;6=~3O(mr39Kx;;=0Qoh&0xr-upV)(aT|&&zD4Bb( z*zeuQlz!N0D4k27cYy!2uP&I=N| zjpPdObV&WdH<3bGNF79S(U{RtPI0pNI*cjim!l^7QrrBqk^P#pdm^Pk_FGeTF0c-6c}Ap}NQ9(Lps|)DKjB>| z!cD{5qFLg13bb!F@(r2DzAhd%915m{-+CJi?XQ(Op*aE<$%^k^(c0Z7 za%4JAi7|;7I-8(fO3_aUC4JMn_4aTPByI{VH8GjIq9 z%!f3O*te)%>E7YlniJpM~5)4`79!cxUa3Ke=)iR`0W1PO|xsv zuO0&E*NH=*#E>J65z|$ey|N!^*S;8f@J+jpoG^J=s}#zkRkax}+p|nRtkixv9P|1e zK)M<0lv@7_Ym#6;<}DC+mEgOoUPc@e|E361kYD{nZ>2XIvws-rywP3r<2_9d75qqS zwm6ZQA}=T!QgAd*pGjreXsk`yoW}REc>J(yS=<#w$!x`HDI`Mtc%cEr!=sEurOiNe zhxGm7IHz_G&u_9H!cH44LXnd2)=u{vJDistOuPtoGF}QFlt@d)9u0)>!U!s03Te z=`uLsCL;e!g41uH2<5k@CFS)(oE~-X4O_;H&iy6s zq($6%po})|SPPLEM%v(r{=SG)Prq)D!f`g4d0`V%)oouJ5uMH;V=qwyi@?-8-IFtU z`K;Y~pu!GTcaTKTa?P))#uj-lGQ9LSe?O@j`BOXZM|$p`ObMJ1kZnu>597NFRMpQG;D*W89wj8l$;fM~^(hTyZp5SOMc_a3eWU zAc>pPVc8xv?-(fLK{A-=;H82PZ8g=xds23O{FIn4uUU?tRyLe$mfU2|%o~N?X{oLf zloBPty=g;`%>i}Xa!U*iLIL7HFz{&xQBna&GSbv2p@tL}12O+*9pGub~l8{=w=AI zmU+&v9uzq0*%K3t-Vm@8Pu{lOVgY_Rq*}3dzdK{z&Nt*dy&H^D!ofkQ=iN#-F})2c zEuHj#t3)UTEtd_RX2ZVSbk+*21&G+w z*PwEqcdb-EGByxcJZoj((vscQX0|a@7zzFwfgjJrjnKj5{ECc|=&k`+9j!Z4@hL`O zBC!`eo8H|#+=!U*CBu=#@xPX>$N%g#@_cRMyPf1i8ULj%;}aPv`ABZe!G(5kpfwuQ zQ=XOF7D6`xnSGPK<35H2-8C>I{Bv^Kwo8q;%FuCCKYalNwC_pi4G@BGO z{&GWBWtBraxfT^+P(8X~lRq%?A%cvR9!e?TPvGF;0(KM%Rl(edy)w zKa+p6>n3uOe8i#XO_Emo#zK&>+=ObEUc~?b&YCX-B}*)06Ym*)m9J`TX0H;i&Ap20&eT^qTl*%&5>%&i9lez zP&8G5;cp9gxWSl7O@iM|F#u64Lq<0$^S-*_1x8!MPUMD0XD3jgN0flmPD{Q*3FCOY zy4g~bNPnu?SO<-%}mK*8JAQklwDA3?(6<$3tNq z$S8W1%Iy^{;WkeVIRCusgh?F?^g!abD2$wBK9w z^+$zU&Q}q9+58AEx|3NIx44vjRoQiGDpgW?tRbo`x$5QM07fTR_(4)6cM)ZOa{tbx z5->G|xQfL#i3qas^C5y9#`QnRCwBQs69%Ljhep*(6GHj?zw-zScRnhrbx3`DRaO>8 z3z(T(bZP{`%Q8>#3RVr39CyzT)WZtIp4o^8ez|+p`W|m1E@OxYgX{OOq~IG%Lzi%8 zi~K+8-nl!IrVaa@*tTsa6Wg{X*2K1L8xz~M?M!Ujwv#tA_wzjOz1P|wVDGj1uWQv+ zUDaJ(b@tJ}I*&^&mhP+vmx_W04?>xeJrE5JnwSUk#NyWmWv}WEh5XWpa0vBsVlDFGgm5F5 z_AhXUbT0Ptx#>4~Lr2|f>kquJa7f#;VFZv0TO?9^%M}N=oRfq5@2F_MyMrOz&v};PEleKfZfztdbc;2$HRPp!`$?xU-K{3jD(d-@4Fx&pkpos_={Ggu(3k=gUue< z#b_tSsQL2FL^Xs-tzZ?X?B4B|7Tx^gAix0w;8reVDUE}P1th1c9X9##Kpd$`bp3E9 zG&<6+BB9AG-MML{8A2K;VAwx3pBICIPs^6XY#y zN(w#i%TphsZg-ZB2d+OTjF7%QAg*Ssk}anV?pVk=qvg=VWJ`T~n(1JWC#7u|>ElV& z#i{S9o6EG&n8YH9V%u;W1cU}=x9^=h2WF1niJnQHJsE@lP`oo4OL zrHJNm>U{2d6wTEZ(oX0!Aa?eUjOJGh8zL_USu4^kRb?c-vjJ=AZtab^&|NypqUVo- zFAehO0>E^(9?%zA7Wt|KY}Kwv=i6>so#SG-->LQkVY0^$S3YW~31t?5{SP{bs`Rl& z2v%wSvz&qhN@Z%dI8zKpf3h3>c13K7!ccshFsz2JPy^k#Pv;t4JW7`2 z5jk=84#9nYC5-9Bvg4fI?eOmtr|P{t{9hw6`4=G z9c*LR^b_+??Fh>$oBKQ12Hn8eFW9@>k=J9j6Md$x@m1WdXnH4>>3BZjJiOfD%7u*% zd57N-Z@M?o17O`M3emh3#Uc={Di_{txOp&@k3#ctUa+L=Sbfi$Rb9-lfz)H6Y^PVK zch8-7C--xVEJC>fo-bUo?{3TADmbs8hN(=%8Pl%}J4YCO2|nm&YH?S4pBCbSDN}BK zy+WiRa~`LvrW`B72}$*te5UY(*iGwS#?vZpifGkF{G$^%5!eveL#l4E%Y@mEn-$)} zxQ*^F7c@5Fl7`(f;YF(nh(zeX+!>a1%K@Du+v2_c=z>nB0~GQN0csJ8IS6jK0*#cp zj4FR$MZjQ0@KlKJc{RtZmqEf1>klQJnN@2cU+aRRUd_bk$g5%mTR<=!MQHI#VHi9j zCg^#|*c<%T4_<>B<$;vGrW3fB&Y`@W4>^i${N z0glh>mhEk z9hIc%yoqex_pPR196prZ5bGBYqQd(lieb#gPuD*KcfMVw{jd*migJg5;3f)XBheqc z^VPPa<2o*7uW7y|?g$Q5x>7xPZF#u^p~`5*$A!34q#Q^*VLf>%1UBHi+y4aKJ}2$c zjiJrnV%7QtNudt^6LoCb0k1GxH3%PQ!0w_fzkQwWV*OY+l(lG@Gdiee`>GT0NY>m1 z)Gd);Zfd}Auh0ajFsh}QgFkH(44PiA8^yK7Fz-JZUH-1U*pA7O)g+!*H)Jw64HOyn?Ge^ zz&)VgWA=_UY+?BXKIw|I(@^#pX^E#s?rp=0@dTq4R|A|mc<)Ew*U=^=O2q%3@JlNI1KvxwW7jMg zTNY`taAw!xHf{Uc@;^cXF`!nI53#HkY)kon{lfpmZ(|q$;+eFEWn`BBa@WZoLx|Zc z86!wW`A;kUW~N~$!XKI4_qfkL|5rC1*a6R98nsk47U%ZfgM-{>ouvPp3m_0@^Uiv@ z#xqj6;K$=WM!=o);91XhZ@o%%MT0bX^Aroq;w7KcpzRqA(99rQ@79!?N9QT=D4+p@ zvp!)&^{#~}%n8Lpkvllx`&$4+hSObrP2~c#B$29US28+wfZR*05iDAmufoz0-u}w` z!j3}&3_?xpQQ`bPqVtfsSpKJQU0>b0rm+FdeV&%F&r)VHK}Q?b$HtzPQ>%W4Z~Rnc z69L^VI?r<}_5mM|f+%#4v&PY*^aF>*`mggd<3ida)n22&A`dZOMj&7Ch@z(__l=!w zZgKUu`FUmS^N5fTA^ob2qpSqKtu5UuMs1{u0+)w^4)TX#XxxM-{s2TSPkk%{tx01Ah{yLXi0gN>ZqgM{1;+(VqN=IlhyMYB&CB8TJ2KD1U< zJy;8AU%zE;4m;-q)aXSn#Vf87y82c0R%AKShIO7Nu_FSA@O{E>g@!Z*!zEdl|L#5T zbkbz9NB46GWSf+CTr{CTrX^ANwYSJ6)%Slbg=v^jD5(VP7sT zqaDhJP$giHNDR-|N| zAx-!Dxs#(*J8HLLgwheDbR!mDpthlv`?Nt(BSCIc%nMITP6k`Tk1m67U9o7Y_xZxr zH{>Uw$leG%pQyKYVEKI7TaE*uBI_EDCw-}Otgb_#SB|W=MdfPZssgTL%PR03g6|s* zk{D#o2u1UA>5p!GQUNbe<0H2wBe3M#xe?O}kcBix0vy%{Grp?K7XtYNhoLfNPA*IM zo6O_#j9ac*X;Oc6TOTLx0F0HIhR$A&MQsPfh`&R1>0UZEpEdBPHP27P(j0)7K(`5#6Qx`n3t1^|sV=kg~y=>qIsGw{uH!&j6>q93=MGj_0 z>TLV(?iU*q3XDPZ&Cc2!3O(pA)m(REEGd}wp4G@NwUVGI*Gna&^FXrRmvon1njh}Y zT;G=MPRV&7dTVBcFU9)DXgVQc-O}XKVY6;90xaI$!Rl=E2tF-72q?*~2UWAZ9lho; z#3gxpfa)_a%$bqENpSMD!BnwCt*I<@ytVVMik5|Xq`7>b89z+b> zzcX9bBj4S)gT*SF5^+jZySg~7oz*NDTrRq@A+4aP7B~qO-*ahuF##Fpx*2TnQK?u_ zOnUg={q4}_F;f* zqDBuMS$}?UX)@HV;!8L{(Lc?>j5y8o0r2ot?;BI_bzY?3m;l_d6RnYV;$j>*VfsK#@Z+9ePy*Pn=51RtZEdI zwuEL_U!ErHNnNW7d69|`%KXXhlMZ_SrV0g`tG{T~ZWpi$`_pnG42(5+w~})<_I(@H zf?G6mhLcn`B=mIIH}X*G#1(426Q&}O5Yab0>RYsU6Rn2-v$)fl$GiJe-#W$9WfC6q z?StLGD3D0@i;dx(QE1Z8WFGiQbU{^nFgYSJ9O(-o&G8NCq#=99vcJj?j+aHZEcd(n z5tl>4sqLJK+S*?6D0Rj}2;?#CuM{JznI0al^mv?%lm73_@1k&g0{$?Dfn?V&nS|v! zpUh$B{3+~}So@S zA5#4gdR#rX&!*``DIGacVlj~uOf@wj?L;16%jQ=--VZr3xtZG@2RvM|LV2Ue!KXtc z_|*^C8!m_R2=%A}mJlCJr)&$>d6Tp^Qzf_Y>e-FoWIuwF>!nh@Gk|ykQZKbyvBK6w zj7Lzf^nJJ5pE$j5@7Rlzw6BxlHJ+Si_QZm8lxuX)F^W6 z!DdM>X~-$1N24m{CXm2hYZXDtm2UyEY(z9UTV{3izrBwaqAhpJRm0@ox7g%SXvbE^rO$Ag2 ztKXHjvk(?L7Cx9Kf-W$o868?SRFG{PQ#lcJ>4>9PNUdVXu`*7O33kCyxnFlA@lzg`kqc90}TYl)_zP#>O}W;rv>`*CE5?$zmZk_ zLcVYIY}mT-+SWIAM2EFZ`d}|di7!P-Rjhye_xb|n+77*ShP9?^fz@17pKJZ%fF(jH z3I~9)$_jiXd+hc3;AWtdClPBgcF9HBpw@l5D{X#d6p=) z_Tm1Hx!L6kSZRIWNChik8K?}Naf+STc-hb2uHdfjatCWcH>?~5t{MUzj0=1x(7 zg{*>oYP;46Suy~_-Qd6^*v{^k-XwHM@Ih!jmY#5VYytZ3p`6VyvFUtBCAjIsouNlP z`rsne;nSXug{c>awzA}|n(8p@tDW6djizK18Vc#|*pUz}cgA^BkyxUc1x( zI0fV57-&zZACy#f{2!gSfB;Pw{6dAYxw>Z_w3HFd_J6~7AdusRkT01&i^239i6_$p zx9k6M@I=X##EA8eWAGup;i!q0Vwy^(v3FybbpVmn!n&@_>bs@0v_akL8Ap*y;Rxc+ zGJ|&=e-%I~tv?|YROYNV3d`+`rnyIfqM7Zy=-_QeZe1mxQ4}RMk-xu?z#Z-nb8D+b zlhm|4*-Xu0X+2!Ogl`p*fOr-qv@s>>*5Fm;qKBh8VmquXy1sZU#P7afXgmS)4(weE z9Fv7*_?B8-Lgy4VJ5XB%~Qf=hGbcDjT%=Z zGx&atU}8bsQ4f(~{(u2}H}5f_6abhmdr{hMmY*}E%hg66YQ@O&NXEDlkcZVJ7lP!D zc#(zft6VkHboW$9Se)Kdm}INM^$fA?5L4LQCv1UbT~-Zm__}W8=@+mL=GB>3+Vg@eB{sOm7Hyzijh@e zPVP~#uK?(zgQv53ic*{5=^hAaDx4#_*YS9Tg&87M$kCl4k6lD1ed;%Ia=$Pv_t_(t zWzJV>W~b)+kM5WDrD2_Zo9BoW;+8D;)Er(k%j}9k2UoKkc9(lXM%uph$tlO65X9ZkI~&uVZa z2usn|ZnuZH^Cc%f5}CUM2f`-Q9M4vwI22~&-Ihn#tp-+>vM~jY{Y1<{cz4li`PpNTtT;}{eB4G7~`szU1@_47$no!2xwC@Wof3Mk|IF8AKQhO;~G z29krX7jQQ)4tqt|RG&|vK=bdFJ8WKAr{WKabhf9X!!v|E??HsK13L-Z1vXxQ zl2H~X)ux5RiZeB!7Cx-;lHT8;_FTSa*PV2T9Y`{7G6K7>fZuqeD!*8O-awY=(X58^ zuD>I?`U1P=8eEY6OjpU8yMe7ebS2zbg4v&51WAZwHkOm)FT>hw0fKusgS@ipZ1oP? zw+~FXK%c@=-2^=2>Ar-P8u0Ng35h;{j5uQE!U@(^4{uKeO$mS8acnsh2U}xBL*aT& ztIHlpeb3NNnH{>dRUZar*5@;ztt}>!N#%u%f1SPZEX6Ln9d~iJr2Vk7w^pB^=-;l@ zUm+8wyOE3%P?jN)!Q=4zT~*gh#K5@S`jPCQUag-({5evWeW7aC%%WBE8P)1 z*NX)9O)>O`FfMP<&Tc(_S|(Pkh<>CznX{(XE@8_pb^*2p@EB0laI1-WhCWkf(F8%7jM_`ClK#i z?PmmruK`8vr`mAcDOp}2ub+%?;YK`pNs{j9=9y{HA52MG3pC+2lWU)HZXAWPnPl@g zTS|09o8JJv>gN^;$LTTd#&bXXE6t2{f-kFj=fn*VR%r%LJINk6uDyzCZ}*oPZ>fH# zb!GpG8lB-1a=hH6UQae9Tu*BnOI#F61FQgtX7P1_!!)C*o}JMP$uXq+wE@0h$aBx^ z_3A}1hsH8E(^QA4mutOueS>)DUdKN@Z6b=&K}JAN51&JNk2*aD$$5X@fL-LQE$_}3 zkKTnOriPmb=;ampQxU0mn6wbeaZbj{9-iiu;$F@4*Yt|mGbzJ6<$S$~`&dwO(`sIZ zOhYSi3d2OQ`*xHSuhW4pR>G!f#7#W5Kj=cxQg%VYmeX@O*(O{NPo zxcj!G7yruasj@1`n0vy&rfW`t3{%;8(3tkgrFUk{m}W-sb-i>JC+CI@_CA=8fD z*Nsq>Up(xAuQt?;7owQM2g84fSUUtAV})13(2kd_;Tk0LIF4`E>$?fz6^q?yP5F1& zQAUQd8iVsB=$>AZ*HFjkxe-}eyuG~xqm1XL3j-RZb!wT7$%Ew{{=c(G!5+nB=;%&V zW=w#@l$tM%tytAM5VNkoH9f#H%@Dtr;HnZ#8)9jRl zE3VIQ(JM%ncngLSH=?dNJi|MU*mT)Lgno4A&oVWSx*TYBxA?7sB*Y7UX~Q8tRXlVv zkvVT9xd#M%1k?cF1Uq|F;5e0E>9(dDX_^r=B0=OaYIKhfty-*-(<2;F{)pxj6hN__ zgix!_=LK2D@{0l7kQzu%YCYwD^1Av5{{ZHILtJ8^%E(@D8 zuI5e+NtX`4aApa6=({B z$+W+`Y!Qy=UBf~m`mbX1VmN-Hrye?@p@TgP~i$oTI`j|uS)6C@v3G$E22!nuS+sV)naKx~UYKL)Du$ueLW6{`m+I)*Mlb@Ndl?1u%}w z5#{Tn*1x(_3H}E(OjMoQNlp8RP~&|%MFB{+2>4%jzdpOj{2N7#cP+3KX)P&%n27E` zAlY}?SJYky$J%E<3J$3_JiWezqq^!@Y*e|gt&BrzaJlc3IHPoIb~rt`F6gf-FTy@d zPFA|;5aJb{Ko4| z59=>^QnQ^6LU2#I#@0LKhs#X$X#>+}%#vfp$5t72Yp$$Kmrci6)5a6L3)_Bghrvkh zbXeuSlL|lSae3%*>k2AUm%i^bPj#Nk+TNEuQWqw9_tr~4$2y3WnSOb=*lAv|Nb!d? z?oRaesk-LhY<-A8nGalk+Unyb7s8|NurAe(jL_KzcW_bJcyUFMK@QR%lqQN{B*z5+ z;F2#XW}quBVc1PQzD*1$G5FdXGaJ+ZqyV<@t^vWKBp2KLP19KmcqaGV<{}-%9d5Ket#}u+#(k?syra2aGExUE-LiJPdRhE!$FZf^jeXx&G8G96@vdRc z=Vw>0n!X^4riX6X*XM>GL8sS7(N!;otzTop<`?0V4Q8~A8zh#)S2*VkbkS@MI(Wx; ztgwn$(Z70|tT$u{WvhW_P{$w=MPb=$%+A;U00zkrZ&A)J`@Si=98|?K&o1$Jqtl`s5suF0xhx#R%yH7Ysc&l z@^~?YmjLoL)^QR3H9q&$Wzn5)0!e<5r_ao1PIIGn2bD$wy-eE=qzSIII43v;jToiXeN2I;7ck zT9Y{fFDc;e$!YtMLWlchoOATM`?RNE&E0~HLletZG@Vj5Ap@ZIlODO03f0Hc<1IM^ z+`z802!5)C#>b+4F%C|6a3Q=)Yj%~rt|`pI0oxbyrav9sy3N_~rpZzeXn=nntdX`W zn%Vy&eUbi*sW+hZ{P4P08!K+eq0hRoM0;T6jPlLH5a{Af82cjpD zcBk5NKn*!6s%O{!2y0jzwl?BjouDwmi1(6pM zBkU#&(c@PgK97N>%0Bl@$Z#`w+)bSM?+nWG8uEsPc@N7i?szI4O%edt&Rw41hKDJ! z_1r&kEad_OK?B=D6sZQNU{!Y>PgB^Q4F((Z#9%B02BRRk-fBuEktdgwBCA4W7#8VO z=XOU>9ECtqW0I`}@lvNQ+i9UYj=2m(*?EZk=G{yL^lETWP*{{$YU_c@R8=YZDLn0n zti zM`65KxkBO8frp3bGj&!L0TTwd9k;7q9$;(g9IklaXP!5$USD)k+Jxox7JAM@*62s$ z&k}J5iBo{4&`7gMB_hk?29K})t_A}pBw2fGfP{t-vOXtZ%y)+mx>xFmocwH%B9Q4L z3owxI!`qhkRgEqYl9;Fc>r|)8@x^uY>i6nF!|+dPzuTVHlGVhBq%5+c0m2wxuW?`M z-X74PGacZT4>RumzKHxGX3K+DcZGw^He?V4g76_~uu4WZm{$h3y$oV4|3W~W z?f{pmP9{=Lg)?q#{mLPgIVFUZPXk;2bDh1#Ob zP)yD2u(A-wImGvEdwS5eG&ObIfoVmSIZ9&TswrPM-d!~uu`Ik`yjkKAT;$9IN!Zk! z%~D)mYDDW8*3*Fjgp-@iUv7KMFWW=Bm%ar|g7j5sUEY!K10qeGfZ}W~ek6;`zXMJ~T<> zOLqHg-}~i=nY?ShZ>6kw&@OYi%o;Z6uG;}~c^&gdgjr}J)@F2Z+fC!+VE+FR4-wQXktCO)1l-p2cqm# z*>Clzx)%M9K@E`96|#(6~}j8A~RHwWrom_*lwQ1V^w7E#MY0@Qdz#gO00{H8hN=?O#(Ey3asDmey{i%05 zD0@AVl^M`iDRb%kafZe=4L`mrT};$yU5c3^!5#A>j;06_FB7Jx9oK{54L(<@_k=OJ zY77adP&y?tl1qmQH@#D-Qi>{DU6c$)-HQ)C*977QPbj^ABR}3;vL+(wcLu_YBv&HkM3ehvH@=hAw7p{kV}4q+uRKcE(cRn45aL5+hSz#S>r0U6EZ zrm65Ejq^Qz#5=MUzy~G5tt1Np?mhW%xgdaYjfoR5r0UGg;x)%v;qBe8iU>r^(44Yp ziE%2HVz7p+mk31l8q@A0_<*xwzh@VeOg0%3#9sahhPliyhh!`B#nG+nfp6;J{qd4* z6alifLM5=2b_pL!-i`KP`A5X>cM(DsO4AAkD}4XWv$i;E4N7HuYj; z8a;sNtW`GM=raZ57hMIv#0kAgCsqZ;(&Tg>%LW~b8yD)?@*pPKt zCV_4DTUKzcA|D#Q%6A_noj?(CE_fv&i1`zO8PXJI-Gel9B8JlIg5BqHc=m&3&dv*k zcI_=~xL{GK*Hxb?J0_B2PF5WC@%Tsw56Zh`s!0ZYdQhD!I)ar)9OP-&{t?Zi$CONj z84tzZt)gk9GIYAr11iY?okjcq781;Bt0*c3ogWx5wZ>ZDgsQL#|gc!<8fv*T{Nx3Pi#{*rG~;fxRu#PJG|h@5ct3 z5%QJf+u>gLR9(Rs8A+qYnJ!ZWl`0Wtu>(w4*UEYwF|MwjxAIrEn+@!2;YF8)Ui#W93c0pgwk3yb z`V`EgEXxco0T_#QX@B^oYAow8WSJ`MY;EPQ&qW;j_!8YnluN{84_85&2Q&z$onVH( zIn-ci#%XOLe8YeP%Uo*;*vFQ=LgtCR;Ptyoi2YT+JxmP;)p`;AbxTA!7yaR5CNi_c zDX#+d)WwO#hS4#(s`)L3oY5N-2-jh4&7SX5K05DQn0+I4+OgJyV`amIUvr;&p4*bp z`wjV@x2a`tdRMpojRJJukHZv(L-DgP^#J8GG$Q5IS%O9iF;i=)hV|i<_6#a@@TQeah zVgAfaNi&UvO-SO@%`;4akLS$|6$XQYGpql^9Dz zNFjeawdCyXEuUpM{q1Yn8+;)GRZiMi^YLSe&^Wvcdi{f4skW-*z`dQOv859UYE`+Y zgF3W!y15X@Ho6qstrP8+Oa;~P;^3*d7fy6&tBaGlIEpI7Hg&IZkHjJr<=0?NF{*Kb zQx7k`S~+Ymx|rh6>OP14tojEYT!S~6P2VYy&)khdH!nBBOc9iB=LlX|aZ4ee5Cn_H z`r<(e{=EP!4^L#UEI41NcKK1uyt&snM zDd90QfK%$4tnq%X%_nX383-65|N5@FQ$pxO*__@TkoBfH1@O!sMg;fa?%xie}PmFuB`DutM2JZ1(@?Fy9T_7*koYx|2UWk<2GTw7M_4N$w(u zcM?J{X2DvG>+KFI3{Jx9lfJTyxw!A&?{g}sR29)&SC)?@bK^7+P_qL@Z%9ZM+)Wff zLEuz;3Y;Vu{v&aX?xAHMyM4D|4YN-DlG#FP9>i+Mu*!%sFn?Ti)vOOl%M`eH}Tc#UfR zA4-cp@JfSTdoE!~e0C|{;qe7=e1ZCZw3=oigc$o#5;r|sgF3r)3u`o2Ge)5nluj2i zlq0VHui>B_%eFOM%c-Xp<&*=hbd&h`)?d~tu#YNQX*2E1I*G~Akz{7@ob(X0FTq>- z%j=~6-wCXpo+&@2mk!5JfBlm)`p@;01o%T0zr48Z?Pm(f>j9LOm9;lg5E3GWcDUtv zD?s}B`~Tg0nzXZ={E(Z%2*VA!9UZQqI|p3HYCR+wZ01v~?Q&8X3w~R=)r&||AiU(3 zkKU8?1RG0c-H^rMhRqqhs#`5YwKtL`rJB!-{Bwu3>?X1FC87;el2pWee&wnM3#+C9 zF$6m`imYq4j_P-;<6rH~S4KFy0xnWOhKntX2y4jo|H6+eOG_bVmT9`;8A03EgVuZe zEs6l|NmQ5guA}24B3h2@q|(ihp%5FKit^VO*b~16tYnaaV6moQhrd9`d`sioSW|KS z)=(s~>t%$mn4w%+BKToHZ)c+N^39B3QK!n)_r$D_fN%ah&MCT~a8*m48;SvgTwhR3 z*GIpJ6(hE=NqTLHP}UpS)y>tjv&o^YW17P5Wy@3v!8L3zp;X^PygyVh{=9c(6O(r1 zOx`@f`|rT!BO}n|4mCEW>&;ee+!^%E;WHrkV7Mh!qVa@-8$T0GWYHZ`jJMT)!0hL8 zK|1;9gy6---Zp}cwk*Ja+ktCjy)rG^8}S2*=7wConMA(XD-_GreE74+KVx)k;I3rX z6Ol|rET)uZwbO#kl`uLUNgv7yr$pGz4IMKqJjQU5@w@NfEr`p9_mEBNLg7zf(ShBC zORd1394}(;|1yRQqjACWQtc7)&4xW}QlZZCTjirfb}URtV@)X8r9J%YA7Wy-KJEtv zKGtRJ+&bHr?kDu7FMKJFcs-enSbhfX+nsn3LRI)GKYLz2&j$xRnC49dZ2SX}w7%S* zoELHXKLnWMDItC3n_n|S(Q^|VLn#={nN>Vvd^3p02-sL~p$@eI5fIF_ z&|fFN>}sq&hR3LdI&#+hpD=}Rc>|oCQ1YaDppW8ta3u?! z>?sl%{yx)FiC=cugDK{|kFaR)t`foq%eL*4_jsc{bjA;YF%93+SOIQP$#NglK7+VZ z!BGSKWO4g5vb6mjz27T2@IVUkcvl!5K?x~gilODWRl*q0qpQHQb&f45#u#sUl%kam z2RYmI+W*DnHL}v@fJZ?B&^_#9Q+|7uWPC(rQ8b{S8)6sQeo-T$r((yxPb6{Koh1~W z*pLgRY=dFP&_S}T#h(!yw<_)rZj($szD|N@=ysP1m8)Nk^N5~oi!HocES=jR6HoLI4|QM$!Sd&q>b`Q8uMTR zGRl{GLm{`B74}l~5RQ|AR;!}m?ql`(YemE?Kph3Bkuiq$>svE88I(vg26bm4VGYG1 zW}YBV{5OagzF~-dLihf0a*7QiRmJSLEA5Wp26uT{QRUA^l*U|e?s&=??{T{`8vE>% zy`iUZKbY=JUfr7`jHkU~>162%q^0IFy$HOF;Ol6bK8ANE{ONMVA5AtvNxbe8n$&AR zM_K-sOU+k$?JXZn)JZILsGK@{fnfgd zs;jn3d|(ptg95J$+mXfyc4{nAI|J79`L~fhJ^?q><3uxR2ar$(B}s2nnS%O2;dYM3 ztKQlZLdu-lz2(r$WrTBOIw<>KCQz$6_81)Y&~Y82ZyZn1fx``w4NUYVH{x;Al@96l z^DBv&k&}RXl_d2zZQGnk#AKhf2>r<(=>QobzHe3tU(uQT%X z3r+#bJo)-F4`cYvlnl>K=K4DVk@kB1uYJoYC1A>(wk3Cr#{Zyi7Ws`kjk!k@Vu94JVK zm3>1x7DGFPTLS}F`)k-4px%eQ;NMK_S46S^W%IT-Ej4ztXC~}EN+=06o?X=eg9E~b%s8U!%0+iOVCdBPUog&5yIBSGZvwBlk~wp=xOSC?bTTt$G#L7?;n z6{XqTKIB$rmy}Qu`p9rIY@X6DN`+%&o7diC`a;=Sud0aCK->xItLM~7IWP55Lp(9& z+-dJKEpk^~vLJN7w%$}woU zI(8-8lKh3Gvp*^W;=_)RrF*jqon~R0Srf4q(u44s7|pn`#vGHeKY03`#D*^M(tW^4 zABBSuf1r(3pE2Fbl#&e$hB?g528Op*)LVsajPgfKQ!E6*E(nj4gmM)8e4Kixr)IHz zyEArURo6I=Y2Wmm>^BWf?NHO91xwJGD|TA<=sAVvWpHL%cn*$h@ltme`F zXy7~|dMj@;l+h7hN5O>lBaI49|=<@@j%rx10uLyby}b95PGBJu2atgI_k|9 z>AdKXSE1Kuiwib^SvydqKp%`-u`4v<>0okeMZN9;c<_KT&7_tUwfEF&t!pj<>Q)+*I8-CVQiT^MeMZHsVYGKY z!*R{Lx@S<-9KS@@hh*4#k@_=*^1eW5T+Dt@#}QR>b0r+-a8FSw>*0cgk7);UGi~!{ zms%|F*YNctgOB8C;a&VnIfY3q>A;r^@Z9>#&m$jXio1s5xYog`W&+LhL^ZDs!^s70hKPZfy!qj!J;(sS0)&~ptWhj0k*p#bn&0(i<}{HbT0ot@c1__| zF{*hwkuizD$|mk&o~d`irCFR$DIP}f{#Z5$L<*e*3YqYa|%;kl~{}S z99^=cPcoMh~`) zx9+x~x!T*Ut2a48ACIATa4<%e!Hw_>?aS$eKa7*!R97%Cw|Hwo-_y(9ob)dU+l#0)Brb6R3k7{oZcZjG05n|{{@80E_VS;cwE z52O%awJnyw>~3tusthzglyNG2SZDE1tPEYB);u@6KYMj?%XYFTubUf>1FC$=u!JA( zc3jTJxZZ(KYuf*?P&Z8L`|jF_(O0`qi6vVBpgM+o72)k@PM~bK@fw<_O?*pVskIp9l{6mX`o@GD!>gF%y0de*#ac~}C8D-);jsek_fw=3YUmlp zYh6U=vlJjPA0RIXz;5f@k4+Ko%X^K6ka^%JZ*A_RT!ZP#jL!uq!`bT7RZ0$#fOt+K zjtQzpzF-P6|(5ky49n4|@mQmgzT zxlVdPQ>vMkY2H5*t%*1=pjH>qKC|)foZ8v+&Slc?z?DC^NQg=GVvirZkf>Qv56~*l2GyJcrBpLi-m|gja zuAtTk^>rYK`#dIuq$i>7DhUZ?#>E4@amJzgrvMTBZ9;P@u#0R~blb=Nf9b#1KpAq!u=$6DMB$U;5b`knh(?3V7w9gJj?P2i7|GFrI{?!)nH27A* z|CWMZ1}4uWboV;K`Ck_T5VBpusv&YX|Nmc6FJUIFPG`gmZceZKs+VP3>6%(26qSl) z9|^k=;s3q2a`5zly<+%SI&>MqI~9hRg!U6sZ$llfnYX&I3Gk&Ev^pz6uZQhHrcj?@ z2n#C(FRuGFbTuD}x&r*TE1;FVn*Y7E0EpmWd`taEnk?C;z`vFKj^m&Tok|?Bp@_qj zNQihTDJ9JFGTLBwrZ;H?x^jh^&9gzOq50&uu^qzh7aq^~8d@yA_Ld;e>GTy^<2aVz zM0$CQ0e*Q+-lqKbK4&{3f~UFILCV@ofX*nke&<{8y{F?1Omx2oSJqtgcstE=&awY) zb5#NI!?6>OQN}Bz!Z=9Zp-8J3UQ#0*uUl1XXy;cjnEv^VGsDNRXbkziR#D?FU3jvA zci;&Iy4U6{`BYT9w~VEBSW&WdVCl5uoYe2MNW|+F7=>IbZ12W@}eJ$kKf&W8UC)mvxnOpf?-WVOYW zQjurpW_Nv?e(qr2#0ept=hw{p#o*sjuq*YQl26Wm-DkF6xWbw~!G4M4 z$@+`-MBOrkJ8*)7Ez2Rbd2IJSy6fz^+gjXif4unU>5pr68qd>t5qX-In=S4-`)&Kk zUVgu+if&V_zMc@A6YB8(WkrV9ynRp7UM}cdzl87Zgp{ohKB=zPygu=+h01c_e?QHZ zM5lUO)9aZ0)FRU2%M#_=-!qR}->>1ke~3@?wU+g32Da3Nt7X>L{o)QsZ;lD|fx}ww;ceD|D7A)&HsJk^IapA}0258{@}K<*W3c z>CWnJdL6tmwbW29;)fW&xLW=A}9I+L-Rvp#N%l>&aa=;Zd=+!QqBk&vvFV86{ZKaL`sMxT@^Gc7kC$xCRHQz0kxFV6%0> z$tMBO+NOc!kUy|)`O|Zg8Lp3830U(rsZDlX)+X1XExo7$C zwZh6Xg+IXh=g|2Xw$M75fpd!jlZd}=hLNQREN&V<2rsDMvQPd7_b1RnEUNReva;3{ zym(bF;C`{KouRTKAcOshquf81Kj(k^%9v?mv0k@0?&^xDOLu;qzZ5Fw&*oFF-eeyv zT(q>)><{N8_R1f^AM78$ZnpWu`D1?U%h!*+ZfoafC~qltTK3Q8@A)Pn|3B$F|9z-% zgVmK6et~RVS95dSSH@=@p0N0f5^FTm&9;!?g#`t$T`=7tVB+${?)`1orK8jl6%B{F zfa>oVP8(0_9cuUd4i8KZ1rwGXIpR`%Tb$r_d4Ym_Qs*-RxIIqJz#un~$%Kd9f~f)v nDza3C&n8ZSwMkmq4*X}1`NW;H@{hGB0}yz+`njxgN@xNA49Rf{ literal 38545 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
      ;sW-O;#K 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

      (?:%s).*?[a-zA-Z].*?

      \s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL) trailing_empty_content_re = re.compile(r'(?:

      (?: |\s|
      )*?

      \s*)+\Z') -del x # Temporary variable + def escape(text): """ From d01eaf7104e96b2fcf373ddfbc80ef4568bd0387 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 3 Aug 2012 18:46:30 +0200 Subject: [PATCH 285/519] [py3] Removed uses of sys.maxint under Python 3. Also fixed #18706: improved exceptions raised by int_to_base36. --- django/utils/http.py | 14 ++++++++++---- docs/ref/utils.txt | 6 ++++-- docs/releases/1.5.txt | 3 +++ tests/regressiontests/utils/http.py | 26 ++++++++++++++------------ 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/django/utils/http.py b/django/utils/http.py index f3a3dce58c..272e73f190 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -167,8 +167,9 @@ def base36_to_int(s): if len(s) > 13: raise ValueError("Base36 input too large") value = int(s, 36) - # ... then do a final check that the value will fit into an int. - if value > sys.maxint: + # ... then do a final check that the value will fit into an int to avoid + # returning a long (#15067). The long type was removed in Python 3. + if not six.PY3 and value > sys.maxint: raise ValueError("Base36 input too large") return value @@ -178,8 +179,13 @@ def int_to_base36(i): """ digits = "0123456789abcdefghijklmnopqrstuvwxyz" factor = 0 - if not 0 <= i <= sys.maxint: - raise ValueError("Base36 conversion input too large or incorrect type.") + if i < 0: + raise ValueError("Negative base36 conversion input.") + if not six.PY3: + if not isinstance(i, six.integer_types): + raise TypeError("Non-integer base36 conversion input.") + if i > sys.maxint: + raise ValueError("Base36 conversion input too large.") # Find starting factor while True: factor += 1 diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index c2f2025bc3..5157c399da 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -504,11 +504,13 @@ escaping HTML. .. function:: base36_to_int(s) - Converts a base 36 string to an integer. + Converts a base 36 string to an integer. On Python 2 the output is + guaranteed to be an :class:`int` and not a :class:`long`. .. function:: int_to_base36(i) - Converts a positive integer less than sys.maxint to a base 36 string. + Converts a positive integer to a base 36 string. On Python 2 ``i`` must be + smaller than :attr:`sys.maxint`. ``django.utils.safestring`` =========================== diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index aae8b25e07..968d63e1f3 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -244,6 +244,9 @@ Miscellaneous * GeoDjango dropped support for GDAL < 1.5 +* :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError` + instead of :exc:`ValueError` for non-integer inputs. + Features deprecated in 1.5 ========================== diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py index 67dcd7af89..f22e05496d 100644 --- a/tests/regressiontests/utils/http.py +++ b/tests/regressiontests/utils/http.py @@ -1,10 +1,11 @@ import sys -from django.utils import http -from django.utils import unittest -from django.utils.datastructures import MultiValueDict from django.http import HttpResponse, utils from django.test import RequestFactory +from django.utils.datastructures import MultiValueDict +from django.utils import http +from django.utils import six +from django.utils import unittest class TestUtilsHttp(unittest.TestCase): @@ -110,22 +111,23 @@ class TestUtilsHttp(unittest.TestCase): def test_base36(self): # reciprocity works - for n in [0, 1, 1000, 1000000, sys.maxint]: + for n in [0, 1, 1000, 1000000]: self.assertEqual(n, http.base36_to_int(http.int_to_base36(n))) + if not six.PY3: + self.assertEqual(sys.maxint, http.base36_to_int(http.int_to_base36(sys.maxint))) # bad input - for n in [-1, sys.maxint+1, '1', 'foo', {1:2}, (1,2,3)]: - self.assertRaises(ValueError, http.int_to_base36, n) - + self.assertRaises(ValueError, http.int_to_base36, -1) + if not six.PY3: + self.assertRaises(ValueError, http.int_to_base36, sys.maxint + 1) + for n in ['1', 'foo', {1: 2}, (1, 2, 3), 3.141]: + self.assertRaises(TypeError, http.int_to_base36, n) + for n in ['#', ' ']: self.assertRaises(ValueError, http.base36_to_int, n) - - for n in [123, {1:2}, (1,2,3)]: + for n in [123, {1: 2}, (1, 2, 3), 3.141]: self.assertRaises(TypeError, http.base36_to_int, n) - # non-integer input - self.assertRaises(TypeError, http.int_to_base36, 3.141) - # more explicit output testing for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]: self.assertEqual(http.int_to_base36(n), b36) From b496be331c19f41bee3a8f6c79de0722f91d6662 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 3 Aug 2012 16:06:41 -0400 Subject: [PATCH 286/519] Fixed #15932 - Documented how to supress multiple reverse relations to the same model. Thanks Claude Paroz for the patch. --- docs/ref/models/fields.txt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 5039ba4373..67bb0f141f 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1002,9 +1002,10 @@ define the details of how the relation works. `; and when you do so :ref:`some special syntax ` is available. - If you'd prefer Django didn't create a backwards relation, set ``related_name`` - to ``'+'``. For example, this will ensure that the ``User`` model won't get a - backwards relation to this model:: + If you'd prefer Django not to create a backwards relation, set + ``related_name`` to ``'+'`` or end it with ``'+'``. For example, this will + ensure that the ``User`` model won't have a backwards relation to this + model:: user = models.ForeignKey(User, related_name='+') @@ -1095,6 +1096,13 @@ that control how the relationship functions. Same as :attr:`ForeignKey.related_name`. + If you have more than one ``ManyToManyField`` pointing to the same model + and want to suppress the backwards relations, set each ``related_name`` + to a unique value ending with ``'+'``:: + + users = models.ManyToManyField(User, related_name='u+') + referents = models.ManyToManyField(User, related_name='ref+') + .. attribute:: ManyToManyField.limit_choices_to Same as :attr:`ForeignKey.limit_choices_to`. From 10f979fd92000de1ac9713351f5cb749e2cbca03 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Fri, 3 Aug 2012 10:03:52 +1000 Subject: [PATCH 287/519] Fixed #18700 -- Added URL reversal for i18n set_language view. --- django/conf/urls/i18n.py | 4 ++-- docs/topics/i18n/translation.txt | 2 +- tests/regressiontests/views/tests/i18n.py | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/django/conf/urls/i18n.py b/django/conf/urls/i18n.py index 6e56af8271..426c2b2d30 100644 --- a/django/conf/urls/i18n.py +++ b/django/conf/urls/i18n.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.conf.urls import patterns +from django.conf.urls import patterns, url from django.core.urlresolvers import LocaleRegexURLResolver def i18n_patterns(prefix, *args): @@ -16,5 +16,5 @@ def i18n_patterns(prefix, *args): urlpatterns = patterns('', - (r'^setlang/$', 'django.views.i18n.set_language'), + url(r'^setlang/$', 'django.views.i18n.set_language', name='set_language'), ) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 988948e259..bdbb04823d 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1272,7 +1272,7 @@ Here's example HTML template code: .. code-block:: html+django - + {% csrf_token %}
      • This field is required.
      • This field is required.
      """) @@ -145,11 +141,7 @@ class FormsTestCase(TestCase): * This field is required. * birthday * This field is required.""") - try: - p.cleaned_data - self.fail('Attempts to access cleaned_data when validation fails should fail.') - except AttributeError: - pass + self.assertEqual(p.cleaned_data, {'last_name': 'Lennon'}) self.assertEqual(p['first_name'].errors, ['This field is required.']) self.assertHTMLEqual(p['first_name'].errors.as_ul(), '
      • This field is required.
      ') self.assertEqual(p['first_name'].errors.as_text(), '* This field is required.') @@ -1678,11 +1670,7 @@ class FormsTestCase(TestCase): form = SongForm(data, empty_permitted=False) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, {'name': ['This field is required.'], 'artist': ['This field is required.']}) - try: - form.cleaned_data - self.fail('Attempts to access cleaned_data when validation fails should fail.') - except AttributeError: - pass + self.assertEqual(form.cleaned_data, {}) # Now let's show what happens when empty_permitted=True and the form is empty. form = SongForm(data, empty_permitted=True) @@ -1696,11 +1684,7 @@ class FormsTestCase(TestCase): form = SongForm(data, empty_permitted=False) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, {'name': ['This field is required.']}) - try: - form.cleaned_data - self.fail('Attempts to access cleaned_data when validation fails should fail.') - except AttributeError: - pass + self.assertEqual(form.cleaned_data, {'artist': 'The Doors'}) # If a field is not given in the data then None is returned for its data. Lets # make sure that when checking for empty_permitted that None is treated From 09a719a4e6820d462fc796606cb242583ad4e79d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 4 Aug 2012 14:55:13 +0200 Subject: [PATCH 289/519] Fixed #7833 -- Improved UserCreationForm password validation Make UserCreationForm password validation similar to SetPasswordForm and AdminPasswordChangeForm, so as the match check is only done when both passwords are supplied. Thanks Mitar for the suggestion. --- django/contrib/auth/forms.py | 6 +++--- django/contrib/auth/tests/forms.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index d17c41132e..dfd039f018 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -89,9 +89,9 @@ class UserCreationForm(forms.ModelForm): raise forms.ValidationError(self.error_messages['duplicate_username']) def clean_password2(self): - password1 = self.cleaned_data.get("password1", "") - password2 = self.cleaned_data["password2"] - if password1 != password2: + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: raise forms.ValidationError( self.error_messages['password_mismatch']) return password2 diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 2ab895804f..13b8dd1216 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -65,6 +65,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form['password1'].errors, required_error) + self.assertEqual(form['password2'].errors, []) def test_success(self): # The success case. From 542c20b38263afb8161d354e6a2cc4ef6bb21de5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 4 Aug 2012 17:38:18 +0200 Subject: [PATCH 290/519] Fixed #18713 -- Fixed custom comment app name in comments docs Thanks Pratyush for the report. --- docs/ref/contrib/comments/custom.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/comments/custom.txt b/docs/ref/contrib/comments/custom.txt index 5007ddff69..0ef37a9a0b 100644 --- a/docs/ref/contrib/comments/custom.txt +++ b/docs/ref/contrib/comments/custom.txt @@ -51,9 +51,9 @@ To make this kind of customization, we'll need to do three things: custom :setting:`COMMENTS_APP`. So, carrying on the example above, we're dealing with a typical app structure in -the ``my_custom_app`` directory:: +the ``my_comment_app`` directory:: - my_custom_app/ + my_comment_app/ __init__.py models.py forms.py @@ -98,11 +98,11 @@ Django provides a couple of "helper" classes to make writing certain types of custom comment forms easier; see :mod:`django.contrib.comments.forms` for more. -Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to +Finally, we'll define a couple of methods in ``my_comment_app/__init__.py`` to point Django at these classes we've created:: - from my_comments_app.models import CommentWithTitle - from my_comments_app.forms import CommentFormWithTitle + from my_comment_app.models import CommentWithTitle + from my_comment_app.forms import CommentFormWithTitle def get_model(): return CommentWithTitle From 865ff32b84316a416661abc3e3e15c753395486c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 4 Aug 2012 15:24:40 -0400 Subject: [PATCH 291/519] Fixed #16980 - Misc updates to the auth docs. Thanks Preston Holmes for the patch. --- docs/topics/auth.txt | 65 +++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 7e1353210a..307691bd4a 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -98,12 +98,13 @@ Fields This doesn't necessarily control whether or not the user can log in. Authentication backends aren't required to check for the ``is_active`` - flag, so if you want to reject a login based on ``is_active`` being - ``False``, it's up to you to check that in your own login view. - However, the :class:`~django.contrib.auth.forms.AuthenticationForm` - used by the :func:`~django.contrib.auth.views.login` view *does* - perform this check, as do the permission-checking methods such as - :meth:`~models.User.has_perm` and the authentication in the Django + flag, and the default backends do not. If you want to reject a login + based on ``is_active`` being ``False``, it's up to you to check that in + your own login view or a custom authentication backend. However, the + :class:`~django.contrib.auth.forms.AuthenticationForm` used by the + :func:`~django.contrib.auth.views.login` view (which is the default) + *does* perform this check, as do the permission-checking methods such + as :meth:`~models.User.has_perm` and the authentication in the Django admin. All of those functions/methods will return ``False`` for inactive users. @@ -1748,7 +1749,11 @@ By default, :setting:`AUTHENTICATION_BACKENDS` is set to:: ('django.contrib.auth.backends.ModelBackend',) -That's the basic authentication scheme that checks the Django users database. +That's the basic authentication backend that checks the Django users database +and queries the builtin permissions. It does not provide protection against +brute force attacks via any rate limiting mechanism. You may either implement +your own rate limiting mechanism in a custom auth backend, or use the +mechanisms provided by most Web servers. The order of :setting:`AUTHENTICATION_BACKENDS` matters, so if the same username and password is valid in multiple backends, Django will stop @@ -1768,8 +1773,9 @@ processing at the first positive match. Writing an authentication backend --------------------------------- -An authentication backend is a class that implements two methods: -``get_user(user_id)`` and ``authenticate(**credentials)``. +An authentication backend is a class that implements two required methods: +``get_user(user_id)`` and ``authenticate(**credentials)``, as well as a set of +optional permission related :ref:`authorization methods `. The ``get_user`` method takes a ``user_id`` -- which could be a username, database ID or whatever -- and returns a ``User`` object. @@ -1838,6 +1844,8 @@ object the first time a user authenticates:: except User.DoesNotExist: return None +.. _authorization_methods: + Handling authorization in custom backends ----------------------------------------- @@ -1868,13 +1876,16 @@ fairly simply:: return False This gives full permissions to the user granted access in the above example. -Notice that the backend auth functions all take the user object as an argument, -and they also accept the same arguments given to the associated -:class:`django.contrib.auth.models.User` functions. +Notice that in addition to the same arguments given to the associated +:class:`django.contrib.auth.models.User` functions, the backend auth functions +all take the user object, which may be an anonymous user, as an argument. -A full authorization implementation can be found in -`django/contrib/auth/backends.py`_, which is the default backend and queries -the ``auth_permission`` table most of the time. +A full authorization implementation can be found in the ``ModelBackend`` class +in `django/contrib/auth/backends.py`_, which is the default backend and queries +the ``auth_permission`` table most of the time. If you wish to provide +custom behavior for only part of the backend API, you can take advantage of +Python inheritence and subclass ``ModelBackend`` instead of implementing the +complete API in a custom backend. .. _django/contrib/auth/backends.py: https://github.com/django/django/blob/master/django/contrib/auth/backends.py @@ -1890,25 +1901,27 @@ authorize anonymous users to browse most of the site, and many allow anonymous posting of comments etc. Django's permission framework does not have a place to store permissions for -anonymous users. However, it has a foundation that allows custom authentication -backends to specify authorization for anonymous users. This is especially useful -for the authors of re-usable apps, who can delegate all questions of authorization -to the auth backend, rather than needing settings, for example, to control -anonymous access. +anonymous users. However, the user object passed to an authentication backend +may be an :class:`django.contrib.auth.models.AnonymousUser` object, allowing +the backend to specify custom authorization behavior for anonymous users. This +is especially useful for the authors of re-usable apps, who can delegate all +questions of authorization to the auth backend, rather than needing settings, +for example, to control anonymous access. +.. _inactive_auth: Authorization for inactive users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.3 +.. versionchanged:: 1.3 An inactive user is a one that is authenticated but has its attribute ``is_active`` set to ``False``. However this does not mean they are not authorized to do anything. For example they are allowed to activate their account. -The support for anonymous users in the permission system allows for -anonymous users to have permissions to do something while inactive +The support for anonymous users in the permission system allows for a scenario +where anonymous users have permissions to do something while inactive authenticated users do not. Do not forget to test for the ``is_active`` attribute of the user in your own @@ -1916,9 +1929,11 @@ backend permission methods. Handling object permissions ---------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Django's permission framework has a foundation for object permissions, though there is no implementation for it in the core. That means that checking for object permissions will always return ``False`` or an empty list (depending on -the check performed). +the check performed). An authentication backend will receive the keyword +parameters ``obj`` and ``user_obj`` for each object related authorization +method and can return the object level permission as appropriate. From 1fefd91e1e9facc786d27e3234cb69c4212f6d1d Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Sat, 4 Aug 2012 13:01:40 -0700 Subject: [PATCH 292/519] For #210, cleaned up text and formatting. --- .../ref/class-based-views/generic-editing.txt | 24 ++++++++++++++----- docs/ref/class-based-views/mixins-editing.txt | 4 +++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 78f46851f7..a65a59bc8b 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -10,7 +10,9 @@ editing content: * :class:`django.views.generic.edit.UpdateView` * :class:`django.views.generic.edit.DeleteView` -.. note:: Some of the examples on this page assume that a model titled 'authors' +.. 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 in `myapp/models.py`:: @@ -92,7 +94,10 @@ editing content: .. attribute:: template_name_suffix The CreateView page displayed to a GET request uses a - ``template_name_suffix`` of ``'_form'``. + ``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'``. **Example views.py**:: @@ -127,8 +132,11 @@ editing content: .. attribute:: template_name_suffix - The CreateView page displayed to a GET request uses a - ``template_name_suffix`` of ``'_form'``. + The UpdateView page displayed to a GET request uses a + ``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'``. **Example views.py**:: @@ -162,8 +170,12 @@ editing content: .. attribute:: template_name_suffix - The CreateView page displayed to a GET request uses a - ``template_name_suffix`` of ``'_confirm_delete'``. + The DeleteView page displayed to a GET request uses a + ``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'``. + **Example views.py**:: diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index f68c279ff3..89610889db 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -9,7 +9,9 @@ The following mixins are used to construct Django's editing views: * :class:`django.views.generic.edit.ProcessFormView` * :class:`django.views.generic.edit.DeletionMixin` -.. note:: Examples of how these are combined into editing views can be found at +.. note:: + + Examples of how these are combined into editing views can be found at the documentation on ``Generic editing views``. .. class:: django.views.generic.edit.FormMixin From 62ae711cecc823f0499cbddac3465c256dee106a Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sat, 4 Aug 2012 23:58:31 +0200 Subject: [PATCH 293/519] Added a missing space to the description of the `cut` filter. --- docs/ref/templates/builtins.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 500a47c6f1..072eebf69f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1184,7 +1184,7 @@ Removes all values of arg from the given string. For example:: - {{ value|cut:" "}} + {{ value|cut:" " }} If ``value`` is ``"String with spaces"``, the output will be ``"Stringwithspaces"``. From 9ea4dc90b9d72648f0f26cc60b19727b35267da0 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 4 Aug 2012 15:46:15 -0700 Subject: [PATCH 294/519] Small improvement to the `contrib.messages` doc introduction. --- docs/ref/contrib/messages.txt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 6929a3b0d0..4cf90ee381 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -5,12 +5,14 @@ The messages framework .. module:: django.contrib.messages :synopsis: Provides cookie- and session-based temporary message storage. -Django provides full support for cookie- and session-based messaging, for -both anonymous and authenticated clients. The messages framework allows you -to temporarily store messages in one request and retrieve them for display -in a subsequent request (usually the next one). Every message is tagged -with a specific ``level`` that determines its priority (e.g., ``info``, -``warning``, or ``error``). +Quite commonly in web applications, you may need to display a one-time +notification message (also know as "flash message") to the user after +processing a form or some other types of user input. For this, Django provides +full support for cookie- and session-based messaging, for both anonymous and +authenticated users. The messages framework allows you to temporarily store +messages in one request and retrieve them for display in a subsequent request +(usually the next one). Every message is tagged with a specific ``level`` that +determines its priority (e.g., ``info``, ``warning``, or ``error``). Enabling messages ================= From 86c5c0154f69728eba4aad6204621f07cdd3459d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 4 Aug 2012 18:56:43 -0400 Subject: [PATCH 295/519] Fixed a mistake in function documentation 'django.utils.functional.partition' Thanks Raman Barkholenka for the patch. --- django/utils/functional.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index 2dca182b99..69aae09887 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -312,8 +312,8 @@ def partition(predicate, values): Splits the values into two sets, based on the return value of the function (True/False). e.g.: - >>> partition(lambda: x > 3, range(5)) - [1, 2, 3], [4] + >>> partition(lambda x: x > 3, range(5)) + [0, 1, 2, 3], [4] """ results = ([], []) for item in values: From 197863523a7631ae1d11d4fdf49b747a96e011a3 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 4 Aug 2012 16:05:12 -0700 Subject: [PATCH 296/519] Restructured the documentation's index page and added some introductory sentences to each section. --- docs/index.txt | 140 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 104 insertions(+), 36 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 50e8471b14..b01116124f 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -34,6 +34,8 @@ Having trouble? We'd like to help! First steps =========== +Are you new to Django or to programming? This is the place to start! + * **From scratch:** :doc:`Overview ` | :doc:`Installation ` @@ -47,6 +49,9 @@ First steps The model layer =============== +Django provides an abstration layer (the "models") for structuring and +manipulating the data of your Web application. Learn more about it below: + * **Models:** :doc:`Model syntax ` | :doc:`Field types ` | @@ -74,20 +79,13 @@ The model layer :doc:`Providing initial data ` | :doc:`Optimize database access ` -The template layer -================== - -* **For designers:** - :doc:`Syntax overview ` | - :doc:`Built-in tags and filters ` - -* **For programmers:** - :doc:`Template API ` | - :doc:`Custom tags and filters ` - The view layer ============== +Django offers the concept of "views" to encapsulate the logic reponsible for +processing a user's request and for returning the response. Find all you need +to know about views via the links below: + * **The basics:** :doc:`URLconfs ` | :doc:`View functions ` | @@ -118,9 +116,29 @@ The view layer :doc:`Overview ` | :doc:`Built-in middleware classes ` +The template layer +================== + +The template layer provides a designer-friendly syntax for rendering the +information to be presented to the user. Learn how this syntax can be used by +designers and how it can be extended by programmers: + +* **For designers:** + :doc:`Syntax overview ` | + :doc:`Built-in tags and filters ` | + :doc:`Web design helpers ` | + :doc:`Humanization ` + +* **For programmers:** + :doc:`Template API ` | + :doc:`Custom tags and filters ` + Forms ===== +Django provides a rich framework to facilitate the creation of forms and the +manipulation of form data. + * **The basics:** :doc:`Overview ` | :doc:`Form API ` | @@ -140,6 +158,9 @@ Forms The development process ======================= +Learn about the various components and tools to help you in the development and +testing of Django applications: + * **Settings:** :doc:`Overview ` | :doc:`Full list of settings ` @@ -161,46 +182,93 @@ The development process :doc:`Handling static files ` | :doc:`Tracking code errors by email ` -Other batteries included -======================== +The admin +========= -* :doc:`Admin site ` | :doc:`Admin actions ` | :doc:`Admin documentation generator` -* :doc:`Authentication ` -* :doc:`Cache system ` +Find all you need to know about the automated admin interface, one of Django's +most popular features: + +* :doc:`Admin site ` +* :doc:`Admin actions ` +* :doc:`Admin documentation generator` + +Security +======== + +Security is a topic of paramount importance in the development of Web +applications and Django provides multiple protection tools and mechanisms: + +* :doc:`Security overview ` * :doc:`Clickjacking protection ` -* :doc:`Comments ` | :doc:`Moderation ` | :doc:`Custom comments ` -* :doc:`Conditional content processing ` -* :doc:`Content types and generic relations ` * :doc:`Cross Site Request Forgery protection ` * :doc:`Cryptographic signing ` -* :doc:`Databrowse ` -* :doc:`E-mail (sending) ` -* :doc:`Flatpages ` -* :doc:`GeoDjango ` -* :doc:`Humanize ` + +Internationalization and localization +===================================== + +Django offers a robust internationalization and localization framework to +assist you in the development of applications for multiple languages and world +regions: + * :doc:`Internationalization ` -* :doc:`Jython support ` * :doc:`"Local flavor" ` -* :doc:`Logging ` -* :doc:`Messages ` -* :doc:`Pagination ` + +Python compatibility +==================== + +Django aims to be compatible with multiple different flavors and versions of +Python: + +* :doc:`Jython support ` * :doc:`Python 3 compatibility ` -* :doc:`Redirects ` -* :doc:`Security ` + +Geographic framework +==================== + +:doc:`GeoDjango ` intends to be a world-class geographic +Web framework. Its goal is to make it as easy as possible to build GIS Web +applications and harness the power of spatially enabled data. + +Common Web application tools +============================ + +Django offers multiple tools commonly needed in the development of Web +applications: + +* :doc:`Authentication ` +* :doc:`Caching ` +* :doc:`Logging ` +* :doc:`Sending e-mails ` +* :doc:`Syndication feeds (RSS/Atom) ` +* :doc:`Comments `, :doc:`comment moderation ` and :doc:`custom comments ` +* :doc:`Pagination ` +* :doc:`Messages framework ` * :doc:`Serialization ` * :doc:`Sessions ` -* :doc:`Signals ` * :doc:`Sitemaps ` -* :doc:`Sites ` -* :doc:`Static Files ` -* :doc:`Syndication feeds (RSS/Atom) ` +* :doc:`Static files management ` +* :doc:`Data validation ` + +Other core functionalities +========================== + +Learn about some other core functionalities of the Django framework: + +* :doc:`Conditional content processing ` +* :doc:`Content types and generic relations ` +* :doc:`Databrowse ` +* :doc:`Flatpages ` +* :doc:`Redirects ` +* :doc:`Signals ` +* :doc:`The sites framework ` * :doc:`Unicode in Django ` -* :doc:`Web design helpers ` -* :doc:`Validators ` The Django open-source project ============================== +Learn about the development process for the Django project itself and about how +you can contribute: + * **Community:** :doc:`How to get involved ` | :doc:`The release process ` | From 5d4f993bb1f01c3a79194031827380dc763ce628 Mon Sep 17 00:00:00 2001 From: Angeline Tan Date: Sat, 4 Aug 2012 15:05:57 -0700 Subject: [PATCH 297/519] Moved a note about django-admin.py errors from Tutorial Part 1 to a new FAQ Troubleshooting page. This is to avoid confusion for beginners. --- docs/faq/index.txt | 3 ++- docs/faq/troubleshooting.txt | 16 ++++++++++++++++ docs/intro/tutorial01.txt | 10 ++-------- 3 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 docs/faq/troubleshooting.txt diff --git a/docs/faq/index.txt b/docs/faq/index.txt index 347cabaabc..b7281946ee 100644 --- a/docs/faq/index.txt +++ b/docs/faq/index.txt @@ -11,4 +11,5 @@ Django FAQ help models admin - contributing \ No newline at end of file + contributing + troubleshooting \ No newline at end of file diff --git a/docs/faq/troubleshooting.txt b/docs/faq/troubleshooting.txt new file mode 100644 index 0000000000..f984be4bf5 --- /dev/null +++ b/docs/faq/troubleshooting.txt @@ -0,0 +1,16 @@ +=============== +Troubleshooting +=============== + +This page contains some advice about errors and problems commonly encountered +during the development of Django applications. + +"command not found: django-admin.py" +------------------------------------ + +:doc:`django-admin.py ` should be on your system path if you +installed Django via ``python setup.py``. If it's not on your path, you can +find it in ``site-packages/django/bin``, where ``site-packages`` is a directory +within your Python installation. Consider symlinking to :doc:`django-admin.py +` from some place on your path, such as +:file:`/usr/local/bin`. \ No newline at end of file diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 250c0f1f41..1e2231d1e0 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -52,7 +52,8 @@ code, then run the following command: django-admin.py startproject mysite -This will create a ``mysite`` directory in your current directory. +This will create a ``mysite`` directory in your current directory. If it didn't +work, see :doc:`Troubleshooting `. .. admonition:: Script name may differ in distribution packages @@ -78,13 +79,6 @@ This will create a ``mysite`` directory in your current directory. ``django`` (which will conflict with Django itself) or ``test`` (which conflicts with a built-in Python package). -:doc:`django-admin.py ` should be on your system path if you -installed Django via ``python setup.py``. If it's not on your path, you can find -it in ``site-packages/django/bin``, where ``site-packages`` is a directory -within your Python installation. Consider symlinking to :doc:`django-admin.py -` from some place on your path, such as -:file:`/usr/local/bin`. - .. admonition:: Where should this code live? If your background is in PHP, you're probably used to putting code under the From 1c3464e809bf84a18cf909959e910b8c6e5df0db Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 4 Aug 2012 18:10:34 -0700 Subject: [PATCH 298/519] Fixed testing on SpatiaLite 2.4, which has support for `InitSpatialMetaData`. --- django/contrib/gis/db/backends/spatialite/creation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py index 31f2fca1bd..d0a5f82033 100644 --- a/django/contrib/gis/db/backends/spatialite/creation.py +++ b/django/contrib/gis/db/backends/spatialite/creation.py @@ -99,14 +99,14 @@ class SpatiaLiteCreation(DatabaseCreation): """ This routine loads up the SpatiaLite SQL file. """ - if self.connection.ops.spatial_version[:2] >= (3, 0): - # Spatialite >= 3.0.x -- No need to load any SQL file, calling + if self.connection.ops.spatial_version[:2] >= (2, 4): + # Spatialite >= 2.4 -- No need to load any SQL file, calling # InitSpatialMetaData() transparently creates the spatial metadata # tables cur = self.connection._cursor() cur.execute("SELECT InitSpatialMetaData()") else: - # Spatialite < 3.0.x -- Load the initial SQL + # Spatialite < 2.4 -- Load the initial SQL # Getting the location of the SpatiaLite SQL file, and confirming # it exists. From 19642a7a09a0f9c7fbeeaec687316f7f9f8027bb Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 4 Aug 2012 18:19:25 -0700 Subject: [PATCH 299/519] Fixed some typos on the documentation's index page. --- docs/index.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index b01116124f..d5723186e6 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -49,7 +49,7 @@ Are you new to Django or to programming? This is the place to start! The model layer =============== -Django provides an abstration layer (the "models") for structuring and +Django provides an abstraction layer (the "models") for structuring and manipulating the data of your Web application. Learn more about it below: * **Models:** @@ -82,7 +82,7 @@ manipulating the data of your Web application. Learn more about it below: The view layer ============== -Django offers the concept of "views" to encapsulate the logic reponsible for +Django has the concept of "views" to encapsulate the logic responsible for processing a user's request and for returning the response. Find all you need to know about views via the links below: From 9a3026a920286e9932401300d14f72a85ba03a43 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 5 Aug 2012 22:47:29 +0200 Subject: [PATCH 300/519] Fixed a rst error introduced in f2abfe1e. --- docs/ref/django-admin.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index c4295c68d5..5ff7ecba2c 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -747,7 +747,7 @@ use the ``--plain`` option, like so:: If you would like to specify either IPython or bpython as your interpreter if you have both installed you can specify an alternative interpreter interface -with the ``-i`` or ``--interface`` options like so:: +with the ``-i`` or ``--interface`` options like so: IPython:: From ad237fb72f769bbd99b5ed6e3292bead614e1c94 Mon Sep 17 00:00:00 2001 From: Brendan MacDonell Date: Mon, 6 Aug 2012 10:42:21 +0200 Subject: [PATCH 301/519] Fixed #18724 -- Fixed IntegerField validation with value 0 --- django/db/models/fields/__init__.py | 3 ++- tests/regressiontests/model_fields/tests.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 9606b1b843..de24a24ed1 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -179,7 +179,8 @@ class Field(object): if not self.editable: # Skip validation for non-editable fields. return - if self._choices and value: + + if self._choices and value not in validators.EMPTY_VALUES: for option_key, option_value in self.choices: if isinstance(option_value, (list, tuple)): # This is an optgroup, so look inside the group for diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index e86159463d..7d6071accc 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -274,6 +274,10 @@ class ValidationTest(test.TestCase): self.assertRaises(ValidationError, f.clean, None, None) self.assertRaises(ValidationError, f.clean, '', None) + def test_integerfield_validates_zero_against_choices(self): + f = models.IntegerField(choices=((1, 1),)) + self.assertRaises(ValidationError, f.clean, '0', None) + def test_charfield_raises_error_on_empty_input(self): f = models.CharField(null=False) self.assertRaises(ValidationError, f.clean, None, None) From ede49c7ee03dd1519d0c375d953cb73e106837b6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Aug 2012 07:58:54 -0700 Subject: [PATCH 302/519] Fixed #15754 -- avoid recursively computing the tree of media widgets more times than is necessary for a wiget --- django/forms/widgets.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 6b1be37ec2..3c4da2444d 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -110,9 +110,10 @@ class Media(StrAndUnicode): def media_property(cls): def _media(self): # Get the media property of the superclass, if it exists - if hasattr(super(cls, self), 'media'): - base = super(cls, self).media - else: + sup_cls = super(cls, self) + try: + base = sup_cls.media + except AttributeError: base = Media() # Get the media definition for this class From 4f3a6b853a6160a9f96d0b73199524a9bb062f90 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 6 Aug 2012 16:15:09 -0400 Subject: [PATCH 303/519] Fixed #17053 - Added a note about USE_THOUSAND_SEPARATOR setting to localizations docs. Thanks shelldweller for the draft patch. --- docs/topics/i18n/formatting.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt index d18781c0a9..b09164769e 100644 --- a/docs/topics/i18n/formatting.txt +++ b/docs/topics/i18n/formatting.txt @@ -21,7 +21,10 @@ necessary to set :setting:`USE_L10N = True ` in your settings file. The default :file:`settings.py` file created by :djadmin:`django-admin.py startproject ` includes :setting:`USE_L10N = True ` - for convenience. + for convenience. Note, however, that to enable number formatting with + thousand separators it is necessary to set :setting:`USE_THOUSAND_SEPARATOR + = True ` in your settings file. Alternatively, you + could use :tfilter:`intcomma` to format numbers in your template. .. note:: From c8780d9f4d32a1ff7b78f0b6bbb052e1e30d90f7 Mon Sep 17 00:00:00 2001 From: Michael Blume Date: Mon, 6 Aug 2012 14:28:58 -0700 Subject: [PATCH 304/519] change "has now" -> "now has" in 1.5 release notes I believe this is standard. --- docs/releases/1.5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 3a9b2d859a..d58d3cadf4 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -107,7 +107,7 @@ Django 1.5 also includes several smaller improvements worth noting: connect to more than one signal by supplying a list of signals. * :meth:`QuerySet.bulk_create() - ` has now a batch_size + ` now has a batch_size argument. By default the batch_size is unlimited except for SQLite where single batch is limited so that 999 parameters per query isn't exceeded. From ee191715eae73362768184aa95206cf61bac5d38 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 21:14:27 +0200 Subject: [PATCH 305/519] [py3] Fixed access to dict keys/values/items. --- django/conf/__init__.py | 2 +- django/contrib/admin/helpers.py | 4 +-- django/contrib/admin/options.py | 6 ++--- django/contrib/admin/sites.py | 7 +++--- .../contrib/admin/templatetags/admin_list.py | 3 ++- django/contrib/admindocs/views.py | 5 ++-- django/contrib/auth/admin.py | 3 ++- django/contrib/auth/tests/forms.py | 3 ++- django/contrib/contenttypes/management.py | 5 ++-- django/contrib/databrowse/datastructures.py | 8 +++--- .../contrib/databrowse/plugins/calendars.py | 2 +- .../databrowse/plugins/fieldchoices.py | 2 +- .../contrib/formtools/wizard/storage/base.py | 7 +++--- django/contrib/formtools/wizard/views.py | 8 +++--- .../gis/db/backends/mysql/operations.py | 4 ++- .../gis/db/backends/oracle/operations.py | 2 +- .../gis/db/backends/postgis/operations.py | 4 +-- .../gis/db/backends/spatialite/operations.py | 2 +- django/contrib/gis/db/models/query.py | 5 ++-- django/contrib/gis/db/models/sql/compiler.py | 5 ++-- django/contrib/gis/geometry/test_data.py | 3 ++- django/contrib/gis/measure.py | 2 +- django/contrib/gis/sitemaps/views.py | 3 ++- django/contrib/messages/storage/cookie.py | 3 ++- django/contrib/sessions/tests.py | 19 +++++++------- django/contrib/sitemaps/views.py | 3 ++- django/contrib/staticfiles/finders.py | 3 ++- django/core/management/__init__.py | 5 ++-- .../core/management/commands/diffsettings.py | 4 +-- django/core/serializers/__init__.py | 5 ++-- django/core/serializers/python.py | 3 ++- django/core/urlresolvers.py | 2 +- django/core/validators.py | 2 +- django/db/backends/mysql/introspection.py | 3 ++- django/db/models/base.py | 4 +-- django/db/models/deletion.py | 25 ++++++++++--------- django/db/models/fields/related.py | 8 +++--- django/db/models/loading.py | 5 ++-- django/db/models/options.py | 17 ++++++------- django/db/models/query.py | 20 +++++++-------- django/db/models/query_utils.py | 5 ++-- django/db/models/sql/compiler.py | 7 +++--- django/db/models/sql/query.py | 25 ++++++++++--------- django/db/models/sql/subqueries.py | 5 ++-- django/forms/extras/widgets.py | 2 +- django/forms/forms.py | 6 ++--- django/forms/models.py | 3 ++- django/forms/widgets.py | 3 +-- django/template/base.py | 2 +- django/template/defaulttags.py | 5 ++-- django/template/loader_tags.py | 5 ++-- django/templatetags/i18n.py | 2 +- django/utils/dateparse.py | 7 +++--- django/utils/dictconfig.py | 2 +- django/utils/functional.py | 2 +- django/utils/html.py | 2 +- django/utils/termcolors.py | 4 ++- django/views/debug.py | 2 +- django/views/generic/base.py | 3 ++- tests/modeltests/timezones/tests.py | 9 ++++--- .../aggregation_regress/tests.py | 3 ++- tests/regressiontests/db_typecasts/tests.py | 3 ++- .../templates/templatetags/custom.py | 6 ++--- tests/regressiontests/templates/tests.py | 3 ++- 64 files changed, 187 insertions(+), 155 deletions(-) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 77454b3fb9..6325ccb1a9 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -158,7 +158,7 @@ class UserSettingsHolder(BaseSettings): return getattr(self.default_settings, name) def __dir__(self): - return self.__dict__.keys() + dir(self.default_settings) + return list(six.iterkeys(self.__dict__)) + dir(self.default_settings) # For Python < 2.6: __members__ = property(lambda self: self.__dir__()) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 1bc843cdf9..4fc78784f4 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -325,11 +325,11 @@ class AdminErrorList(forms.util.ErrorList): """ def __init__(self, form, inline_formsets): if form.is_bound: - self.extend(form.errors.values()) + self.extend(list(six.itervalues(form.errors))) for inline_formset in inline_formsets: self.extend(inline_formset.non_form_errors()) for errors_in_inline_form in inline_formset.errors: - self.extend(errors_in_inline_form.values()) + self.extend(list(six.itervalues(errors_in_inline_form))) def normalize_fieldsets(fieldsets): """ diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c13a6bc5cc..ea28125a5f 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -425,7 +425,7 @@ class ModelAdmin(BaseModelAdmin): if self.declared_fieldsets: return self.declared_fieldsets form = self.get_form(request, obj) - fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) + fields = list(six.iterkeys(form.base_fields)) + list(self.get_readonly_fields(request, obj)) return [(None, {'fields': fields})] def get_form(self, request, obj=None, **kwargs): @@ -608,7 +608,7 @@ class ModelAdmin(BaseModelAdmin): tuple (name, description). """ choices = [] + default_choices - for func, name, description in self.get_actions(request).itervalues(): + for func, name, description in six.itervalues(self.get_actions(request)): choice = (name, description % model_format_dict(self.opts)) choices.append(choice) return choices @@ -1415,7 +1415,7 @@ class InlineModelAdmin(BaseModelAdmin): if self.declared_fieldsets: return self.declared_fieldsets form = self.get_formset(request, obj).form - fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) + fields = list(six.iterkeys(form.base_fields)) + list(self.get_readonly_fields(request, obj)) return [(None, {'fields': fields})] def queryset(self, request): diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 515cc33eca..05773ceac0 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -10,6 +10,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse, NoReverseMatch from django.template.response import TemplateResponse from django.utils.safestring import mark_safe +from django.utils import six from django.utils.text import capfirst from django.utils.translation import ugettext as _ from django.views.decorators.cache import never_cache @@ -133,7 +134,7 @@ class AdminSite(object): """ Get all the enabled actions as an iterable of (name, func). """ - return self._actions.iteritems() + return six.iteritems(self._actions) def has_permission(self, request): """ @@ -239,7 +240,7 @@ class AdminSite(object): ) # Add in each model's views. - for model, model_admin in self._registry.iteritems(): + for model, model_admin in six.iteritems(self._registry): urlpatterns += patterns('', url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name), include(model_admin.urls)) @@ -370,7 +371,7 @@ class AdminSite(object): } # Sort the apps alphabetically. - app_list = app_dict.values() + app_list = list(six.itervalues(app_dict)) app_list.sort(key=lambda x: x['name']) # Sort the models alphabetically within each app. diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 0f15781fa9..a16226a70e 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -12,6 +12,7 @@ from django.db import models from django.utils import formats from django.utils.html import format_html from django.utils.safestring import mark_safe +from django.utils import six from django.utils.text import capfirst from django.utils.translation import ugettext as _ from django.utils.encoding import smart_unicode, force_unicode @@ -125,7 +126,7 @@ def result_headers(cl): if i in ordering_field_columns: sorted = True order_type = ordering_field_columns.get(i).lower() - sort_priority = ordering_field_columns.keys().index(i) + 1 + sort_priority = list(six.iterkeys(ordering_field_columns)).index(i) + 1 th_classes.append('sorted %sending' % order_type) new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type] diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 5649398cc8..94963b4d39 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -14,6 +14,7 @@ from django.core import urlresolvers from django.contrib.admindocs import utils from django.contrib.sites.models import Site from django.utils.importlib import import_module +from django.utils import six from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe @@ -48,7 +49,7 @@ def template_tag_index(request): load_all_installed_template_libraries() tags = [] - app_libs = template.libraries.items() + app_libs = list(six.iteritems(template.libraries)) builtin_libs = [(None, lib) for lib in template.builtins] for module_name, library in builtin_libs + app_libs: for tag_name, tag_func in library.tags.items(): @@ -83,7 +84,7 @@ def template_filter_index(request): load_all_installed_template_libraries() filters = [] - app_libs = template.libraries.items() + app_libs = list(six.iteritems(template.libraries)) builtin_libs = [(None, lib) for lib in template.builtins] for module_name, library in builtin_libs + app_libs: for filter_name, filter_func in library.filters.items(): diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index ad61904041..cb09822f52 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -12,6 +12,7 @@ from django.template.response import TemplateResponse from django.utils.html import escape from django.utils.decorators import method_decorator from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import ugettext, ugettext_lazy as _ from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters @@ -128,7 +129,7 @@ class UserAdmin(admin.ModelAdmin): else: form = self.change_password_form(user) - fieldsets = [(None, {'fields': form.base_fields.keys()})] + fieldsets = [(None, {'fields': list(six.iterkeys(form.base_fields))})] adminForm = admin.helpers.AdminForm(form, fieldsets, {}) context = { diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 13b8dd1216..2bfe35ac88 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -9,6 +9,7 @@ from django.forms.fields import Field, EmailField from django.test import TestCase from django.test.utils import override_settings from django.utils.encoding import force_unicode +from django.utils import six from django.utils import translation from django.utils.translation import ugettext as _ @@ -203,7 +204,7 @@ class PasswordChangeFormTest(TestCase): def test_field_order(self): # Regression test - check the order of fields: user = User.objects.get(username='testclient') - self.assertEqual(PasswordChangeForm(user, {}).fields.keys(), + self.assertEqual(list(six.iterkeys(PasswordChangeForm(user, {}).fields)), ['old_password', 'new_password1', 'new_password2']) diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 6a23ef5287..1b1c9c8562 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import get_apps, get_models, signals from django.utils.encoding import smart_unicode +from django.utils import six def update_contenttypes(app, created_models, verbosity=2, **kwargs): """ @@ -24,7 +25,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs): ) to_remove = [ ct - for (model_name, ct) in content_types.iteritems() + for (model_name, ct) in six.iteritems(content_types) if model_name not in app_models ] @@ -34,7 +35,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs): app_label=app_label, model=model_name, ) - for (model_name, model) in app_models.iteritems() + for (model_name, model) in six.iteritems(app_models) if model_name not in content_types ]) if verbosity >= 2: diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 687aa87f03..95d347cac0 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -17,7 +17,7 @@ class EasyModel(object): def __init__(self, site, model): self.site = site self.model = model - self.model_list = site.registry.keys() + self.model_list = list(site.registry.keys()) self.verbose_name = model._meta.verbose_name self.verbose_name_plural = model._meta.verbose_name_plural @@ -176,8 +176,6 @@ class EasyInstanceField(object): for plugin_name, plugin in self.model.model_databrowse().plugins.items(): urls = plugin.urls(plugin_name, self) if urls is not None: - #plugin_urls.append(urls) - values = self.values() return zip(self.values(), urls) if self.field.rel: m = EasyModel(self.model.site, self.field.rel.to) @@ -196,10 +194,10 @@ class EasyInstanceField(object): url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)) lst.append((value, url)) elif isinstance(self.field, models.URLField): - val = self.values()[0] + val = list(self.values())[0] lst = [(val, iri_to_uri(val))] else: - lst = [(self.values()[0], None)] + lst = [(list(self.values())[0], None)] return lst class EasyQuerySet(QuerySet): diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 7bdd1e0032..923adb90f6 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -96,7 +96,7 @@ class CalendarPlugin(DatabrowsePlugin): def homepage_view(self, request): easy_model = EasyModel(self.site, self.model) - field_list = self.fields.values() + field_list = list(self.fields.values()) field_list.sort(key=lambda k:k.verbose_name) return render_to_response('databrowse/calendar_homepage.html', { 'root_url': self.site.root_url, diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index c016385ffb..73298b8d56 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -63,7 +63,7 @@ class FieldChoicePlugin(DatabrowsePlugin): def homepage_view(self, request): easy_model = EasyModel(self.site, self.model) - field_list = self.fields.values() + field_list = list(self.fields.values()) field_list.sort(key=lambda k: k.verbose_name) return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list}) diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py index 274e07ffbe..7c802712c1 100644 --- a/django/contrib/formtools/wizard/storage/base.py +++ b/django/contrib/formtools/wizard/storage/base.py @@ -2,6 +2,7 @@ from django.core.files.uploadedfile import UploadedFile from django.utils.datastructures import MultiValueDict from django.utils.encoding import smart_str from django.utils.functional import lazy_property +from django.utils import six from django.contrib.formtools.wizard.storage.exceptions import NoFileStorageConfigured @@ -72,9 +73,9 @@ class BaseStorage(object): raise NoFileStorageConfigured files = {} - for field, field_dict in wizard_files.iteritems(): + for field, field_dict in six.iteritems(wizard_files): field_dict = dict((smart_str(k), v) - for k, v in field_dict.iteritems()) + for k, v in six.iteritems(field_dict)) tmp_name = field_dict.pop('tmp_name') files[field] = UploadedFile( file=self.file_storage.open(tmp_name), **field_dict) @@ -87,7 +88,7 @@ class BaseStorage(object): if step not in self.data[self.step_files_key]: self.data[self.step_files_key][step] = {} - for field, field_file in (files or {}).iteritems(): + for field, field_file in six.iteritems(files or {}): tmp_filename = self.file_storage.save(field_file.name, field_file) file_dict = { 'tmp_name': tmp_filename, diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index 466af1cac9..741b7e52b6 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -44,7 +44,7 @@ class StepsHelper(object): @property def all(self): "Returns the names of all steps/forms." - return self._wizard.get_form_list().keys() + return list(six.iterkeys(self._wizard.get_form_list())) @property def count(self): @@ -164,14 +164,14 @@ class WizardView(TemplateView): init_form_list[six.text_type(i)] = form # walk through the new created list of forms - for form in init_form_list.itervalues(): + for form in six.itervalues(init_form_list): if issubclass(form, formsets.BaseFormSet): # if the element is based on BaseFormSet (FormSet/ModelFormSet) # we need to override the form variable. form = form.form # check if any form contains a FileField, if yes, we need a # file_storage added to the wizardview (by subclassing). - for field in form.base_fields.itervalues(): + for field in six.itervalues(form.base_fields): if (isinstance(field, forms.FileField) and not hasattr(cls, 'file_storage')): raise NoFileStorageConfigured @@ -196,7 +196,7 @@ class WizardView(TemplateView): could use data from other (maybe previous forms). """ form_list = SortedDict() - for form_key, form_class in self.form_list.iteritems(): + for form_key, form_class in six.iteritems(self.form_list): # try to fetch the value from condition list, by default, the form # gets passed to the new list. condition = self.condition_dict.get(form_key, True) diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index c0e5aa6691..277b764810 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -3,6 +3,8 @@ from django.db.backends.mysql.base import DatabaseOperations from django.contrib.gis.db.backends.adapter import WKTAdapter from django.contrib.gis.db.backends.base import BaseSpatialOperations +from django.utils import six + class MySQLOperations(DatabaseOperations, BaseSpatialOperations): compiler_module = 'django.contrib.gis.db.backends.mysql.compiler' @@ -30,7 +32,7 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations): 'within' : 'MBRWithin', } - gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']]) + gis_terms = dict([(term, None) for term in list(six.iterkeys(geometry_functions)) + ['isnull']]) def geo_db_type(self, f): return f.geom_type diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 4e33942f7a..5db30e6fc6 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -128,7 +128,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations): geometry_functions.update(distance_functions) gis_terms = ['isnull'] - gis_terms += geometry_functions.keys() + gis_terms += list(six.iterkeys(geometry_functions)) gis_terms = dict([(term, None) for term in gis_terms]) truncate_params = {'relate' : None} diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index bd249df179..92f8925a7d 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -217,8 +217,8 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): # Creating a dictionary lookup of all GIS terms for PostGIS. gis_terms = ['isnull'] - gis_terms += self.geometry_operators.keys() - gis_terms += self.geometry_functions.keys() + gis_terms += list(six.iterkeys(self.geometry_operators)) + gis_terms += list(six.iterkeys(self.geometry_functions)) self.gis_terms = dict([(term, None) for term in gis_terms]) self.area = prefix + 'Area' diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 1d7c4fab52..31c98212e9 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -131,7 +131,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): # Creating the GIS terms dictionary. gis_terms = ['isnull'] - gis_terms += self.geometry_functions.keys() + gis_terms += list(six.iterkeys(self.geometry_functions)) self.gis_terms = dict([(term, None) for term in gis_terms]) if version >= (2, 4, 0): diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index dd2983aecc..6dc36d6ab8 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -1,5 +1,6 @@ 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 @@ -25,7 +26,7 @@ class GeoQuerySet(QuerySet): flat = kwargs.pop('flat', False) if kwargs: raise TypeError('Unexpected keyword arguments to values_list: %s' - % (kwargs.keys(),)) + % (list(six.iterkeys(kwargs)),)) if flat and len(fields) > 1: raise TypeError("'flat' is not valid when values_list is called with more than one field.") return self._clone(klass=GeoValuesListQuerySet, setup=True, flat=flat, @@ -531,7 +532,7 @@ class GeoQuerySet(QuerySet): if settings.get('setup', True): default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name, geo_field_type=settings.get('geo_field_type', None)) - for k, v in default_args.iteritems(): settings['procedure_args'].setdefault(k, v) + for k, v in six.iteritems(default_args): settings['procedure_args'].setdefault(k, v) else: geo_field = settings['geo_field'] diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index d016357f1b..64d1a4d869 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -3,6 +3,7 @@ from django.utils.six.moves import zip from django.db.backends.util import truncate_name, typecast_timestamp from django.db.models.sql import compiler from django.db.models.sql.constants import MULTI +from django.utils import six SQLCompiler = compiler.SQLCompiler @@ -24,7 +25,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): qn = self.quote_name_unless_alias qn2 = self.connection.ops.quote_name result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias)) - for alias, col in self.query.extra_select.iteritems()] + for alias, col in six.iteritems(self.query.extra_select)] aliases = set(self.query.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() @@ -170,7 +171,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): objects. """ values = [] - aliases = self.query.extra_select.keys() + aliases = list(six.iterkeys(self.query.extra_select)) # Have to set a starting row number offset that is used for # determining the correct starting row index -- needed for diff --git a/django/contrib/gis/geometry/test_data.py b/django/contrib/gis/geometry/test_data.py index f92740248e..505f0e4f4b 100644 --- a/django/contrib/gis/geometry/test_data.py +++ b/django/contrib/gis/geometry/test_data.py @@ -7,6 +7,7 @@ import json import os from django.contrib import gis +from django.utils import six # This global used to store reference geometry data. @@ -25,7 +26,7 @@ def tuplize(seq): def strconvert(d): "Converts all keys in dictionary to str type." - return dict([(str(k), v) for k, v in d.iteritems()]) + return dict([(str(k), v) for k, v in six.iteritems(d)]) def get_ds_file(name, ext): diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index ba7817e51c..fa8bab1340 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -169,7 +169,7 @@ class MeasureBase(object): """ val = 0.0 default_unit = self.STANDARD_UNIT - for unit, value in kwargs.iteritems(): + for unit, value in six.iteritems(kwargs): if not isinstance(value, float): value = float(value) if unit in self.UNITS: val += self.UNITS[unit] * value diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index eb42d0cae8..6753b9e34a 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -9,6 +9,7 @@ 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_str +from django.utils import six from django.utils.translation import ugettext as _ from django.contrib.gis.shortcuts import render_to_kml, render_to_kmz @@ -46,7 +47,7 @@ def sitemap(request, sitemaps, section=None): raise Http404(_("No sitemap available for section: %r") % section) maps.append(sitemaps[section]) else: - maps = sitemaps.values() + maps = list(six.itervalues(sitemaps)) page = request.GET.get("p", 1) current_site = get_current_site(request) diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py index 07620050c7..5f64ccd0c5 100644 --- a/django/contrib/messages/storage/cookie.py +++ b/django/contrib/messages/storage/cookie.py @@ -4,6 +4,7 @@ from django.conf import settings from django.contrib.messages.storage.base import BaseStorage, Message from django.http import SimpleCookie from django.utils.crypto import salted_hmac, constant_time_compare +from django.utils import six class MessageEncoder(json.JSONEncoder): @@ -33,7 +34,7 @@ class MessageDecoder(json.JSONDecoder): return [self.process_messages(item) for item in obj] if isinstance(obj, dict): return dict([(key, self.process_messages(value)) - for key, value in obj.iteritems()]) + for key, value in six.iteritems(obj)]) return obj def decode(self, s, **kwargs): diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 328b085f1e..7de2941122 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -16,6 +16,7 @@ from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.http import HttpResponse from django.test import TestCase, RequestFactory from django.test.utils import override_settings +from django.utils import six from django.utils import timezone from django.utils import unittest @@ -86,16 +87,16 @@ class SessionTestsMixin(object): self.assertFalse(self.session.modified) def test_values(self): - self.assertEqual(self.session.values(), []) + self.assertEqual(list(self.session.values()), []) self.assertTrue(self.session.accessed) self.session['some key'] = 1 - self.assertEqual(self.session.values(), [1]) + self.assertEqual(list(self.session.values()), [1]) def test_iterkeys(self): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.iterkeys() + i = six.iterkeys(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -105,7 +106,7 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.itervalues() + i = six.itervalues(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -115,7 +116,7 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.iteritems() + i = six.iteritems(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -125,9 +126,9 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - self.assertEqual(self.session.items(), [('x', 1)]) + self.assertEqual(list(self.session.items()), [('x', 1)]) self.session.clear() - self.assertEqual(self.session.items(), []) + self.assertEqual(list(self.session.items()), []) self.assertTrue(self.session.accessed) self.assertTrue(self.session.modified) @@ -154,10 +155,10 @@ class SessionTestsMixin(object): self.session['a'], self.session['b'] = 'c', 'd' self.session.save() prev_key = self.session.session_key - prev_data = self.session.items() + prev_data = list(self.session.items()) self.session.cycle_key() self.assertNotEqual(self.session.session_key, prev_key) - self.assertEqual(self.session.items(), prev_data) + self.assertEqual(list(self.session.items()), prev_data) def test_invalid_key(self): # Submitting an invalid session key (either by guessing, or if the db has diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py index b90a39e954..cfe3aa66a9 100644 --- a/django/contrib/sitemaps/views.py +++ b/django/contrib/sitemaps/views.py @@ -3,6 +3,7 @@ from django.core import urlresolvers from django.core.paginator import EmptyPage, PageNotAnInteger from django.http import Http404 from django.template.response import TemplateResponse +from django.utils import six def index(request, sitemaps, template_name='sitemap_index.xml', mimetype='application/xml', @@ -35,7 +36,7 @@ def sitemap(request, sitemaps, section=None, raise Http404("No sitemap available for section: %r" % section) maps = [sitemaps[section]] else: - maps = sitemaps.values() + maps = list(six.itervalues(sitemaps)) page = request.GET.get("p", 1) urls = [] diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index 766687cf7d..9b06c2cf60 100644 --- a/django/contrib/staticfiles/finders.py +++ b/django/contrib/staticfiles/finders.py @@ -6,6 +6,7 @@ from django.utils.datastructures import SortedDict from django.utils.functional import empty, memoize, LazyObject from django.utils.importlib import import_module from django.utils._os import safe_join +from django.utils import six from django.contrib.staticfiles import utils from django.contrib.staticfiles.storage import AppStaticStorage @@ -132,7 +133,7 @@ class AppDirectoriesFinder(BaseFinder): """ List all files in all app storages. """ - for storage in self.storages.itervalues(): + for storage in six.itervalues(self.storages): if storage.exists(''): # check if storage location exists for path in utils.get_files(storage, ignore_patterns): yield path, storage diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 68048e5672..e2f9798dcd 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -8,6 +8,7 @@ import warnings from django.core.management.base import BaseCommand, CommandError, handle_default_options from django.core.management.color import color_style from django.utils.importlib import import_module +from django.utils import six # For backwards compatibility: get_version() used to be in this module. from django import get_version @@ -228,7 +229,7 @@ class ManagementUtility(object): "Available subcommands:", ] commands_dict = collections.defaultdict(lambda: []) - for name, app in get_commands().iteritems(): + for name, app in six.iteritems(get_commands()): if app == 'django.core': app = 'django' else: @@ -294,7 +295,7 @@ class ManagementUtility(object): except IndexError: curr = '' - subcommands = get_commands().keys() + ['help'] + subcommands = list(six.iterkeys(get_commands())) + ['help'] options = [('--help', None)] # subcommand diff --git a/django/core/management/commands/diffsettings.py b/django/core/management/commands/diffsettings.py index 98b53b405d..aa7395e5ee 100644 --- a/django/core/management/commands/diffsettings.py +++ b/django/core/management/commands/diffsettings.py @@ -22,9 +22,7 @@ class Command(NoArgsCommand): default_settings = module_to_dict(global_settings) output = [] - keys = user_settings.keys() - keys.sort() - for key in keys: + for key in sorted(user_settings.keys()): if key not in default_settings: output.append("%s = %s ###" % (key, user_settings[key])) elif user_settings[key] != default_settings[key]: diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py index 09bcb651d5..2d5e7624a4 100644 --- a/django/core/serializers/__init__.py +++ b/django/core/serializers/__init__.py @@ -18,6 +18,7 @@ To add your own serializers, use the SERIALIZATION_MODULES setting:: from django.conf import settings from django.utils import importlib +from django.utils import six from django.core.serializers.base import SerializerDoesNotExist # Built-in serializers @@ -75,12 +76,12 @@ def get_serializer(format): def get_serializer_formats(): if not _serializers: _load_serializers() - return _serializers.keys() + return list(six.iterkeys(_serializers)) def get_public_serializer_formats(): if not _serializers: _load_serializers() - return [k for k, v in _serializers.iteritems() if not v.Serializer.internal_use_only] + return [k for k, v in six.iteritems(_serializers) if not v.Serializer.internal_use_only] def get_deserializer(format): if not _serializers: diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 333161c929..83c6eb6739 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -9,6 +9,7 @@ from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS from django.utils.encoding import smart_unicode, is_protected_type +from django.utils import six class Serializer(base.Serializer): """ @@ -87,7 +88,7 @@ def Deserializer(object_list, **options): m2m_data = {} # Handle each field - for (field_name, field_value) in d["fields"].iteritems(): + for (field_name, field_value) in six.iteritems(d["fields"]): if isinstance(field_value, str): field_value = smart_unicode(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 7397cf3b3d..c17168f8cb 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -376,7 +376,7 @@ class RegexURLResolver(LocaleRegexProvider): unicode_args = [force_unicode(val) for val in args] candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args)) else: - if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys() + prefix_args): + if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args): continue matches = True for k, v in defaults.items(): diff --git a/django/core/validators.py b/django/core/validators.py index 03ff8be3bc..91d6f62dcf 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -138,7 +138,7 @@ def ip_address_validators(protocol, unpack_ipv4): return ip_address_validator_map[protocol.lower()] except KeyError: raise ValueError("The protocol '%s' is unknown. Supported: %s" - % (protocol, ip_address_validator_map.keys())) + % (protocol, list(six.iterkeys(ip_address_validator_map)))) comma_separated_int_list_re = re.compile('^[\d,]+$') validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid') diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index e6f18b819f..6aab0b99ab 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -1,4 +1,5 @@ from django.db.backends import BaseDatabaseIntrospection +from django.utils import six from MySQLdb import ProgrammingError, OperationalError from MySQLdb.constants import FIELD_TYPE import re @@ -79,7 +80,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): """ Returns the name of the primary key column for the given table """ - for column in self.get_indexes(cursor, table_name).iteritems(): + for column in six.iteritems(self.get_indexes(cursor, table_name)): if column[1]['primary_key']: return column[0] return None diff --git a/django/db/models/base.py b/django/db/models/base.py index 8dd5bf864f..a25c106290 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -364,14 +364,14 @@ class Model(six.with_metaclass(ModelBase, object)): setattr(self, field.attname, val) if kwargs: - for prop in kwargs.keys(): + for prop in list(six.iterkeys(kwargs)): try: if isinstance(getattr(self.__class__, prop), property): setattr(self, prop, kwargs.pop(prop)) except AttributeError: pass if kwargs: - raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]) + raise TypeError("'%s' is an invalid keyword argument for this function" % list(six.iterkeys(kwargs))[0]) super(Model, self).__init__() signals.post_init.send(sender=self.__class__, instance=self) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index d8bb8f2e66..2e5ed53e63 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -4,6 +4,7 @@ from operator import attrgetter from django.db import connections, transaction, IntegrityError from django.db.models import signals, sql from django.utils.datastructures import SortedDict +from django.utils import six class ProtectedError(IntegrityError): @@ -157,7 +158,7 @@ class Collector(object): # Recursively collect concrete model's parent models, but not their # related objects. These will be found by meta.get_all_related_objects() concrete_model = model._meta.concrete_model - for ptr in concrete_model._meta.parents.itervalues(): + for ptr in six.itervalues(concrete_model._meta.parents): if ptr: parent_objs = [getattr(obj, ptr.name) for obj in new_objs] self.collect(parent_objs, source=model, @@ -199,14 +200,14 @@ class Collector(object): ) def instances_with_model(self): - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): for obj in instances: yield model, obj def sort(self): sorted_models = [] concrete_models = set() - models = self.data.keys() + models = list(six.iterkeys(self.data)) while len(sorted_models) < len(models): found = False for model in models: @@ -241,24 +242,24 @@ class Collector(object): ) # update fields - for model, instances_for_fieldvalues in self.field_updates.iteritems(): + for model, instances_for_fieldvalues in six.iteritems(self.field_updates): query = sql.UpdateQuery(model) - for (field, value), instances in instances_for_fieldvalues.iteritems(): + for (field, value), instances in six.iteritems(instances_for_fieldvalues): query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) # reverse instance collections - for instances in self.data.itervalues(): + for instances in six.itervalues(self.data): instances.reverse() # delete batches - for model, batches in self.batches.iteritems(): + for model, batches in six.iteritems(self.batches): query = sql.DeleteQuery(model) - for field, instances in batches.iteritems(): + for field, instances in six.iteritems(batches): query.delete_batch([obj.pk for obj in instances], self.using, field) # delete instances - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): query = sql.DeleteQuery(model) pk_list = [obj.pk for obj in instances] query.delete_batch(pk_list, self.using) @@ -271,10 +272,10 @@ class Collector(object): ) # update collected instances - for model, instances_for_fieldvalues in self.field_updates.iteritems(): - for (field, value), instances in instances_for_fieldvalues.iteritems(): + for model, instances_for_fieldvalues in six.iteritems(self.field_updates): + for (field, value), instances in six.iteritems(instances_for_fieldvalues): for obj in instances: setattr(obj, field.attname, value) - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): for instance in instances: setattr(instance, model._meta.pk.attname, None) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 2a2502b54f..bfa8feee9f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -241,7 +241,7 @@ class SingleRelatedObjectDescriptor(object): rel_obj_attr = attrgetter(self.related.field.attname) instance_attr = lambda obj: obj._get_pk_val() instances_dict = dict((instance_attr(inst), inst) for inst in instances) - params = {'%s__pk__in' % self.related.field.name: instances_dict.keys()} + params = {'%s__pk__in' % self.related.field.name: list(six.iterkeys(instances_dict))} qs = self.get_query_set(instance=instances[0]).filter(**params) # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. @@ -335,9 +335,9 @@ class ReverseSingleRelatedObjectDescriptor(object): instance_attr = attrgetter(self.field.attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) if other_field.rel: - params = {'%s__pk__in' % self.field.rel.field_name: instances_dict.keys()} + params = {'%s__pk__in' % self.field.rel.field_name: list(six.iterkeys(instances_dict))} else: - params = {'%s__in' % self.field.rel.field_name: instances_dict.keys()} + params = {'%s__in' % self.field.rel.field_name: list(six.iterkeys(instances_dict))} qs = self.get_query_set(instance=instances[0]).filter(**params) # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. @@ -488,7 +488,7 @@ class ForeignRelatedObjectsDescriptor(object): instance_attr = attrgetter(attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) db = self._db or router.db_for_read(self.model, instance=instances[0]) - query = {'%s__%s__in' % (rel_field.name, attname): instances_dict.keys()} + query = {'%s__%s__in' % (rel_field.name, attname): list(six.iterkeys(instances_dict))} qs = super(RelatedManager, self).get_query_set().using(db).filter(**query) # Since we just bypassed this class' get_query_set(), we must manage # the reverse relation manually. diff --git a/django/db/models/loading.py b/django/db/models/loading.py index d651584e7a..7a9cb2cb41 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -5,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.datastructures import SortedDict from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule +from django.utils import six import imp import sys @@ -193,9 +194,9 @@ class AppCache(object): else: if only_installed: app_list = [self.app_models.get(app_label, SortedDict()) - for app_label in self.app_labels.iterkeys()] + for app_label in six.iterkeys(self.app_labels)] else: - app_list = self.app_models.itervalues() + app_list = six.itervalues(self.app_models) model_list = [] for app in app_list: model_list.extend( diff --git a/django/db/models/options.py b/django/db/models/options.py index 7308a15c6b..9e8d4120e9 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -127,7 +127,7 @@ class Options(object): if self.parents: # Promote the first parent link in lieu of adding yet another # field. - field = next(self.parents.itervalues()) + field = next(six.itervalues(self.parents)) # Look for a local field with the same name as the # first parent link. If a local field has already been # created, use it instead of promoting the parent @@ -147,13 +147,13 @@ class Options(object): # self.duplicate_targets will map each duplicate field column to the # columns it duplicates. collections = {} - for column, target in self.duplicate_targets.iteritems(): + for column, target in six.iteritems(self.duplicate_targets): try: collections[target].add(column) except KeyError: collections[target] = set([column]) self.duplicate_targets = {} - for elt in collections.itervalues(): + for elt in six.itervalues(collections): if len(elt) == 1: continue for column in elt: @@ -258,7 +258,7 @@ class Options(object): self._m2m_cache except AttributeError: self._fill_m2m_cache() - return self._m2m_cache.keys() + return list(six.iterkeys(self._m2m_cache)) many_to_many = property(_many_to_many) def get_m2m_with_model(self): @@ -269,7 +269,7 @@ class Options(object): self._m2m_cache except AttributeError: self._fill_m2m_cache() - return self._m2m_cache.items() + return list(six.iteritems(self._m2m_cache)) def _fill_m2m_cache(self): cache = SortedDict() @@ -326,8 +326,7 @@ class Options(object): cache = self._name_map except AttributeError: cache = self.init_name_map() - names = cache.keys() - names.sort() + names = sorted(cache.keys()) # Internal-only names end with "+" (symmetrical m2m related names being # the main example). Trim them. return [val for val in names if not val.endswith('+')] @@ -417,7 +416,7 @@ class Options(object): cache = self._fill_related_many_to_many_cache() if local_only: return [k for k, v in cache.items() if not v] - return cache.keys() + return list(six.iterkeys(cache)) def get_all_related_m2m_objects_with_model(self): """ @@ -428,7 +427,7 @@ class Options(object): cache = self._related_many_to_many_cache except AttributeError: cache = self._fill_related_many_to_many_cache() - return cache.items() + return list(six.iteritems(cache)) def _fill_related_many_to_many_cache(self): cache = SortedDict() diff --git a/django/db/models/query.py b/django/db/models/query.py index 8b6b42b7b1..6013233a0f 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -245,8 +245,8 @@ class QuerySet(object): requested = None max_depth = self.query.max_depth - extra_select = self.query.extra_select.keys() - aggregate_select = self.query.aggregate_select.keys() + extra_select = list(six.iterkeys(self.query.extra_select)) + aggregate_select = list(six.iterkeys(self.query.aggregate_select)) only_load = self.query.get_loaded_field_names() if not fill_cache: @@ -593,7 +593,7 @@ class QuerySet(object): flat = kwargs.pop('flat', False) if kwargs: raise TypeError('Unexpected keyword arguments to values_list: %s' - % (kwargs.keys(),)) + % (list(six.iterkeys(kwargs)),)) if flat and len(fields) > 1: raise TypeError("'flat' is not valid when values_list is called with more than one field.") return self._clone(klass=ValuesListQuerySet, setup=True, flat=flat, @@ -693,7 +693,7 @@ class QuerySet(object): depth = kwargs.pop('depth', 0) if kwargs: raise TypeError('Unexpected keyword arguments to select_related: %s' - % (kwargs.keys(),)) + % (list(six.iterkeys(kwargs)),)) obj = self._clone() if fields: if depth: @@ -751,7 +751,7 @@ class QuerySet(object): obj = self._clone() - obj._setup_aggregate_query(kwargs.keys()) + obj._setup_aggregate_query(list(six.iterkeys(kwargs))) # Add the aggregates to the query for (alias, aggregate_expr) in kwargs.items(): @@ -966,9 +966,9 @@ class ValuesQuerySet(QuerySet): def iterator(self): # Purge any extra columns that haven't been explicitly asked for - extra_names = self.query.extra_select.keys() + extra_names = list(six.iterkeys(self.query.extra_select)) field_names = self.field_names - aggregate_names = self.query.aggregate_select.keys() + aggregate_names = list(six.iterkeys(self.query.aggregate_select)) names = extra_names + field_names + aggregate_names @@ -1097,9 +1097,9 @@ class ValuesListQuerySet(ValuesQuerySet): # When extra(select=...) or an annotation is involved, the extra # cols are always at the start of the row, and we need to reorder # the fields to match the order in self._fields. - extra_names = self.query.extra_select.keys() + extra_names = list(six.iterkeys(self.query.extra_select)) field_names = self.field_names - aggregate_names = self.query.aggregate_select.keys() + aggregate_names = list(six.iterkeys(self.query.aggregate_select)) names = extra_names + field_names + aggregate_names @@ -1527,7 +1527,7 @@ class RawQuerySet(object): # Associate fields to values if skip: model_init_kwargs = {} - for attname, pos in model_init_field_names.iteritems(): + for attname, pos in six.iteritems(model_init_field_names): model_init_kwargs[attname] = values[pos] instance = model_cls(**model_init_kwargs) else: diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index 60bdb2bcb4..c1a690a524 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -8,6 +8,7 @@ circular import difficulties. from __future__ import unicode_literals from django.db.backends import util +from django.utils import six from django.utils import tree @@ -40,7 +41,7 @@ class Q(tree.Node): default = AND def __init__(self, *args, **kwargs): - super(Q, self).__init__(children=list(args) + kwargs.items()) + super(Q, self).__init__(children=list(args) + list(six.iteritems(kwargs))) def _combine(self, other, conn): if not isinstance(other, Q): @@ -114,7 +115,7 @@ class DeferredAttribute(object): def _check_parent_chain(self, instance, name): """ - Check if the field value can be fetched from a parent field already + Check if the field value can be fetched from a parent field already loaded in the instance. This can be done if the to-be fetched field is a primary key field. """ diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 7a0afa349d..1311ea546c 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -9,6 +9,7 @@ from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.query import get_order_dir, Query from django.db.utils import DatabaseError +from django.utils import six class SQLCompiler(object): @@ -82,7 +83,7 @@ class SQLCompiler(object): where, w_params = self.query.where.as_sql(qn=qn, connection=self.connection) having, h_params = self.query.having.as_sql(qn=qn, connection=self.connection) params = [] - for val in self.query.extra_select.itervalues(): + for val in six.itervalues(self.query.extra_select): params.extend(val[1]) result = ['SELECT'] @@ -177,7 +178,7 @@ class SQLCompiler(object): """ qn = self.quote_name_unless_alias qn2 = self.connection.ops.quote_name - result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in self.query.extra_select.iteritems()] + result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in six.iteritems(self.query.extra_select)] aliases = set(self.query.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() @@ -553,7 +554,7 @@ class SQLCompiler(object): group_by = self.query.group_by or [] extra_selects = [] - for extra_select, extra_params in self.query.extra_select.itervalues(): + for extra_select, extra_params in six.itervalues(self.query.extra_select): extra_selects.append(extra_select) params.extend(extra_params) cols = (group_by + self.query.select + diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 53dad608bf..9cf732f263 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -12,6 +12,7 @@ import copy from django.utils.datastructures import SortedDict from django.utils.encoding import force_unicode from django.utils.tree import Node +from django.utils import six from django.db import connections, DEFAULT_DB_ALIAS from django.db.models import signals from django.db.models.expressions import ExpressionNode @@ -602,22 +603,22 @@ class Query(object): # slight complexity here is handling fields that exist on parent # models. workset = {} - for model, values in seen.iteritems(): + for model, values in six.iteritems(seen): for field, m in model._meta.get_fields_with_model(): if field in values: continue add_to_dict(workset, m or model, field) - for model, values in must_include.iteritems(): + for model, values in six.iteritems(must_include): # If we haven't included a model in workset, we don't add the # corresponding must_include fields for that model, since an # empty set means "include all fields". That's why there's no # "else" branch here. if model in workset: workset[model].update(values) - for model, values in workset.iteritems(): + for model, values in six.iteritems(workset): callback(target, model, values) else: - for model, values in must_include.iteritems(): + for model, values in six.iteritems(must_include): if model in seen: seen[model].update(values) else: @@ -631,7 +632,7 @@ class Query(object): for model in orig_opts.get_parent_list(): if model not in seen: seen[model] = set() - for model, values in seen.iteritems(): + for model, values in six.iteritems(seen): callback(target, model, values) @@ -770,7 +771,7 @@ class Query(object): for k, aliases in self.join_map.items(): aliases = tuple([change_map.get(a, a) for a in aliases]) self.join_map[k] = aliases - for old_alias, new_alias in change_map.iteritems(): + for old_alias, new_alias in six.iteritems(change_map): alias_data = self.alias_map[old_alias] alias_data = alias_data._replace(rhs_alias=new_alias) self.alias_refcount[new_alias] = self.alias_refcount[old_alias] @@ -792,7 +793,7 @@ class Query(object): self.included_inherited_models[key] = change_map[alias] # 3. Update any joins that refer to the old alias. - for alias, data in self.alias_map.iteritems(): + for alias, data in six.iteritems(self.alias_map): lhs = data.lhs_alias if lhs in change_map: data = data._replace(lhs_alias=change_map[lhs]) @@ -842,7 +843,7 @@ class Query(object): count. Note that after execution, the reference counts are zeroed, so tables added in compiler will not be seen by this method. """ - return len([1 for count in self.alias_refcount.itervalues() if count]) + return len([1 for count in six.itervalues(self.alias_refcount) if count]) def join(self, connection, always_create=False, exclusions=(), promote=False, outer_if_first=False, nullable=False, reuse=None): @@ -1302,7 +1303,7 @@ class Query(object): field, model, direct, m2m = opts.get_field_by_name(f.name) break else: - names = opts.get_all_field_names() + self.aggregate_select.keys() + names = opts.get_all_field_names() + list(six.iterkeys(self.aggregate_select)) raise FieldError("Cannot resolve keyword %r into field. " "Choices are: %s" % (name, ", ".join(names))) @@ -1571,7 +1572,7 @@ class Query(object): # Tag.objects.exclude(parent__parent__name='t1'), a tag with no parent # would otherwise be overlooked). active_positions = [pos for (pos, count) in - enumerate(query.alias_refcount.itervalues()) if count] + enumerate(six.itervalues(query.alias_refcount)) if count] if active_positions[-1] > 1: self.add_filter(('%s__isnull' % prefix, False), negate=True, trim=True, can_reuse=can_reuse) @@ -1660,8 +1661,8 @@ class Query(object): # from the model on which the lookup failed. raise else: - names = sorted(opts.get_all_field_names() + self.extra.keys() - + self.aggregate_select.keys()) + names = sorted(opts.get_all_field_names() + list(six.iterkeys(self.extra)) + + list(six.iterkeys(self.aggregate_select))) raise FieldError("Cannot resolve keyword %r into field. " "Choices are: %s" % (name, ", ".join(names))) self.remove_inherited_models() diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 7b92394e90..cc7da0eeaf 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -11,6 +11,7 @@ from django.db.models.sql.where import AND, Constraint from django.utils.datastructures import SortedDict from django.utils.functional import Promise from django.utils.encoding import force_unicode +from django.utils import six __all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery', @@ -87,7 +88,7 @@ class UpdateQuery(Query): querysets. """ values_seq = [] - for name, val in values.iteritems(): + for name, val in six.iteritems(values): field, model, direct, m2m = self.model._meta.get_field_by_name(name) if not direct or m2m: raise FieldError('Cannot update model field %r (only non-relations and foreign keys permitted).' % field) @@ -129,7 +130,7 @@ class UpdateQuery(Query): if not self.related_updates: return [] result = [] - for model, values in self.related_updates.iteritems(): + for model, values in six.iteritems(self.related_updates): query = UpdateQuery(model) query.values = values if self.related_ids is not None: diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py index 4e11a4ee06..c5ca1424c8 100644 --- a/django/forms/extras/widgets.py +++ b/django/forms/extras/widgets.py @@ -79,7 +79,7 @@ class SelectDateWidget(Widget): year_val, month_val, day_val = [int(v) for v in match.groups()] choices = [(i, i) for i in self.years] year_html = self.create_select(name, self.year_field, value, year_val, choices) - choices = MONTHS.items() + choices = list(six.iteritems(MONTHS)) month_html = self.create_select(name, self.month_field, value, month_val, choices) choices = [(i, i) for i in range(1, 32)] day_html = self.create_select(name, self.day_field, value, day_val, choices) diff --git a/django/forms/forms.py b/django/forms/forms.py index 4d4cdbe3db..0f3fdb2e40 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -38,7 +38,7 @@ def get_declared_fields(bases, attrs, with_base_fields=True): used. The distinction is useful in ModelForm subclassing. Also integrates any additional media definitions """ - fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in list(six.iteritems(attrs)) if isinstance(obj, Field)] fields.sort(key=lambda x: x[1].creation_counter) # If this class is subclassing another Form, add that Form's fields. @@ -47,11 +47,11 @@ def get_declared_fields(bases, attrs, with_base_fields=True): if with_base_fields: for base in bases[::-1]: if hasattr(base, 'base_fields'): - fields = base.base_fields.items() + fields + fields = list(six.iteritems(base.base_fields)) + fields else: for base in bases[::-1]: if hasattr(base, 'declared_fields'): - fields = base.declared_fields.items() + fields + fields = list(six.iteritems(base.declared_fields)) + fields return SortedDict(fields) diff --git a/django/forms/models.py b/django/forms/models.py index e6ae357d19..a2b5448b14 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -18,6 +18,7 @@ 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__ = ( @@ -206,7 +207,7 @@ class ModelFormMetaclass(type): fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback) # make sure opts.fields doesn't specify an invalid field - none_model_fields = [k for k, v in fields.iteritems() if not v] + none_model_fields = [k for k, v in six.iteritems(fields) if not v] missing_fields = set(none_model_fields) - \ set(declared_fields.keys()) if missing_fields: diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 3c4da2444d..13b7d8e7f6 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -63,8 +63,7 @@ class Media(StrAndUnicode): def render_css(self): # To keep rendering order consistent, we can't just iterate over items(). # We need to sort the keys, and iterate over the sorted list. - media = self._css.keys() - media.sort() + media = sorted(self._css.keys()) return chain(*[ [format_html('', self.absolute_path(path), medium) for path in self._css[medium]] diff --git a/django/template/base.py b/django/template/base.py index 489b1681e0..d5c2438500 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -961,7 +961,7 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults, kwarg = token_kwargs([bit], parser) if kwarg: # The kwarg was successfully extracted - param, value = kwarg.items()[0] + param, value = list(six.iteritems(kwarg))[0] if param not in params and varkw is None: # An unexpected keyword argument was supplied raise TemplateSyntaxError( diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index fb45fe722e..7a00d60361 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -17,6 +17,7 @@ from django.template.defaultfilters import date from django.utils.encoding import smart_unicode from django.utils.safestring import mark_safe from django.utils.html import format_html +from django.utils import six from django.utils import timezone register = Library() @@ -473,7 +474,7 @@ class WithNode(Node): def render(self, context): values = dict([(key, val.resolve(context)) for key, val in - self.extra_context.iteritems()]) + six.iteritems(self.extra_context)]) context.update(values) output = self.nodelist.render(context) context.pop() @@ -1188,7 +1189,7 @@ def templatetag(parser, token): if tag not in TemplateTagNode.mapping: raise TemplateSyntaxError("Invalid templatetag argument: '%s'." " Must be one of: %s" % - (tag, TemplateTagNode.mapping.keys())) + (tag, list(six.iterkeys(TemplateTagNode.mapping)))) return TemplateTagNode(tag) @register.tag diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index b63938abb0..d295d058d0 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -3,6 +3,7 @@ from django.template.base import TemplateSyntaxError, Library, Node, TextNode,\ token_kwargs, Variable from django.template.loader import get_template from django.utils.safestring import mark_safe +from django.utils import six register = Library() @@ -17,7 +18,7 @@ class BlockContext(object): self.blocks = {} def add_blocks(self, blocks): - for name, block in blocks.iteritems(): + for name, block in six.iteritems(blocks): if name in self.blocks: self.blocks[name].insert(0, block) else: @@ -130,7 +131,7 @@ class BaseIncludeNode(Node): def render_template(self, template, context): values = dict([(name, var.resolve(context)) for name, var - in self.extra_context.iteritems()]) + in six.iteritems(self.extra_context)]) if self.isolated_context: return template.render(context.new(values)) context.update(values) diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 509ab6707d..30eb6b5f76 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -425,7 +425,7 @@ def do_block_translate(parser, token): options[option] = value if 'count' in options: - countervar, counter = options['count'].items()[0] + countervar, counter = list(six.iteritems(options['count']))[0] else: countervar, counter = None, None if 'context' in options: diff --git a/django/utils/dateparse.py b/django/utils/dateparse.py index 532bb259c3..032eb493b6 100644 --- a/django/utils/dateparse.py +++ b/django/utils/dateparse.py @@ -7,6 +7,7 @@ import datetime import re +from django.utils import six from django.utils.timezone import utc from django.utils.tzinfo import FixedOffset @@ -34,7 +35,7 @@ def parse_date(value): """ match = date_re.match(value) if match: - kw = dict((k, int(v)) for k, v in match.groupdict().iteritems()) + kw = dict((k, int(v)) for k, v in six.iteritems(match.groupdict())) return datetime.date(**kw) def parse_time(value): @@ -53,7 +54,7 @@ def parse_time(value): kw = match.groupdict() if kw['microsecond']: kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) + kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None) return datetime.time(**kw) def parse_datetime(value): @@ -80,6 +81,6 @@ def parse_datetime(value): if tzinfo[0] == '-': offset = -offset tzinfo = FixedOffset(offset) - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) + kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None) kw['tzinfo'] = tzinfo return datetime.datetime(**kw) diff --git a/django/utils/dictconfig.py b/django/utils/dictconfig.py index b4d6d66b3c..f8d6eebf89 100644 --- a/django/utils/dictconfig.py +++ b/django/utils/dictconfig.py @@ -363,7 +363,7 @@ class DictConfigurator(BaseConfigurator): #which were in the previous configuration but #which are not in the new configuration. root = logging.root - existing = root.manager.loggerDict.keys() + existing = list(six.iterkeys(root.manager.loggerDict)) #The list needs to be sorted so that we can #avoid disabling child loggers of explicitly #named loggers. With a sorted list it is easier diff --git a/django/utils/functional.py b/django/utils/functional.py index 69aae09887..177325dfb6 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -178,7 +178,7 @@ def allow_lazy(func, *resultclasses): """ @wraps(func) def wrapper(*args, **kwargs): - for arg in list(args) + kwargs.values(): + for arg in list(args) + list(six.itervalues(kwargs)): if isinstance(arg, Promise): break else: diff --git a/django/utils/html.py b/django/utils/html.py index e1263fbd66..4e888fc59b 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -84,7 +84,7 @@ def format_html(format_string, *args, **kwargs): """ args_safe = map(conditional_escape, args) kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in - kwargs.iteritems()]) + six.iteritems(kwargs)]) return mark_safe(format_string.format(*args_safe, **kwargs_safe)) def format_html_join(sep, format_string, args_generator): diff --git a/django/utils/termcolors.py b/django/utils/termcolors.py index 1eebaa2316..4f74b564a5 100644 --- a/django/utils/termcolors.py +++ b/django/utils/termcolors.py @@ -2,6 +2,8 @@ termcolors.py """ +from django.utils import six + color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white') foreground = dict([(color_names[x], '3%s' % x) for x in range(8)]) background = dict([(color_names[x], '4%s' % x) for x in range(8)]) @@ -41,7 +43,7 @@ def colorize(text='', opts=(), **kwargs): code_list = [] if text == '' and len(opts) == 1 and opts[0] == 'reset': return '\x1b[%sm' % RESET - for k, v in kwargs.iteritems(): + for k, v in six.iteritems(kwargs): if k == 'fg': code_list.append(foreground[v]) elif k == 'bg': diff --git a/django/views/debug.py b/django/views/debug.py index 8e81b8239b..08341fe145 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -111,7 +111,7 @@ class ExceptionReporterFilter(object): return request.POST def get_traceback_frame_variables(self, request, tb_frame): - return tb_frame.f_locals.items() + return list(six.iteritems(tb_frame.f_locals)) class SafeExceptionReporterFilter(ExceptionReporterFilter): """ diff --git a/django/views/generic/base.py b/django/views/generic/base.py index 69751727bb..6554e898ca 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -6,6 +6,7 @@ from django.core.exceptions import ImproperlyConfigured from django.template.response import TemplateResponse from django.utils.log import getLogger from django.utils.decorators import classonlymethod +from django.utils import six logger = getLogger('django.request') @@ -35,7 +36,7 @@ class View(object): """ # Go through keyword arguments, and either save their values to our # instance, or raise an error. - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): setattr(self, key, value) @classonlymethod diff --git a/tests/modeltests/timezones/tests.py b/tests/modeltests/timezones/tests.py index aadb8ba842..a38e4b3f75 100644 --- a/tests/modeltests/timezones/tests.py +++ b/tests/modeltests/timezones/tests.py @@ -20,6 +20,7 @@ from django.http import HttpRequest from django.template import Context, RequestContext, Template, TemplateSyntaxError from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import override_settings +from django.utils import six from django.utils import timezone from django.utils.tzinfo import FixedOffset from django.utils.unittest import skipIf, skipUnless @@ -690,8 +691,8 @@ class TemplateTests(TestCase): } } - for k1, dt in datetimes.iteritems(): - for k2, tpl in templates.iteritems(): + for k1, dt in six.iteritems(datetimes): + for k2, tpl in six.iteritems(templates): ctx = Context({'dt': dt, 'ICT': ICT}) actual = tpl.render(ctx) expected = results[k1][k2] @@ -703,8 +704,8 @@ class TemplateTests(TestCase): results['ict']['notag'] = t('ict', 'eat', 'utc', 'ict') with self.settings(USE_TZ=False): - for k1, dt in datetimes.iteritems(): - for k2, tpl in templates.iteritems(): + for k1, dt in six.iteritems(datetimes): + for k2, tpl in six.iteritems(templates): ctx = Context({'dt': dt, 'ICT': ICT}) actual = tpl.render(ctx) expected = results[k1][k2] diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index e5f12e5781..e24eb43b87 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -8,6 +8,7 @@ from operator import attrgetter from django.core.exceptions import FieldError from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q from django.test import TestCase, Approximate, skipUnlessDBFeature +from django.utils import six from .models import Author, Book, Publisher, Clues, Entries, HardbackBook @@ -16,7 +17,7 @@ class AggregationTests(TestCase): fixtures = ["aggregation_regress.json"] def assertObjectAttrs(self, obj, **kwargs): - for attr, value in kwargs.iteritems(): + for attr, value in six.iteritems(kwargs): self.assertEqual(getattr(obj, attr), value) def test_aggregates_in_where_clause(self): diff --git a/tests/regressiontests/db_typecasts/tests.py b/tests/regressiontests/db_typecasts/tests.py index 83bd1e6851..2cf835d94e 100644 --- a/tests/regressiontests/db_typecasts/tests.py +++ b/tests/regressiontests/db_typecasts/tests.py @@ -3,6 +3,7 @@ import datetime from django.db.backends import util as typecasts +from django.utils import six from django.utils import unittest @@ -49,7 +50,7 @@ TEST_CASES = { class DBTypeCasts(unittest.TestCase): def test_typeCasts(self): - for k, v in TEST_CASES.iteritems(): + for k, v in six.iteritems(TEST_CASES): for inpt, expected in v: got = getattr(typecasts, k)(inpt) self.assertEqual(got, expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)) diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/regressiontests/templates/templatetags/custom.py index 95fcd551de..3f51353880 100644 --- a/tests/regressiontests/templates/templatetags/custom.py +++ b/tests/regressiontests/templates/templatetags/custom.py @@ -70,7 +70,7 @@ simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dic def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected simple_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) + sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) return "simple_unlimited_args_kwargs - Expected result: %s / %s" % ( ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) @@ -221,7 +221,7 @@ inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__" def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected inclusion_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) + sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % ( ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) @@ -292,7 +292,7 @@ assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_ar def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected assignment_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) + sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % ( ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 402cbb19d2..edbb21b6bd 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -30,6 +30,7 @@ from django.utils import unittest from django.utils.formats import date_format from django.utils.translation import activate, deactivate, ugettext as _ from django.utils.safestring import mark_safe +from django.utils import six from django.utils.tzinfo import LocalTimezone from .callables import CallableVariablesTests @@ -402,7 +403,7 @@ class Templates(unittest.TestCase): template_tests.update(filter_tests) cache_loader = setup_test_template_loader( - dict([(name, t[0]) for name, t in template_tests.iteritems()]), + dict([(name, t[0]) for name, t in six.iteritems(template_tests)]), use_cached_loader=True, ) From c5ef65bcf324f4c90b53be90f4aec069a68e8c59 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 21 Jul 2012 10:00:10 +0200 Subject: [PATCH 306/519] [py3] Ported django.utils.encoding. * Renamed smart_unicode to smart_text (but kept the old name under Python 2 for backwards compatibility). * Renamed smart_str to smart_bytes. * Re-introduced smart_str as an alias for smart_text under Python 3 and smart_bytes under Python 2 (which is backwards compatible). Thus smart_str always returns a str objects. * Used the new smart_str in a few places where both Python 2 and 3 want a str. --- django/contrib/admin/actions.py | 8 +-- django/contrib/admin/filters.py | 8 +-- django/contrib/admin/helpers.py | 8 +-- django/contrib/admin/models.py | 6 +- django/contrib/admin/options.py | 58 ++++++++-------- .../contrib/admin/templatetags/admin_list.py | 12 ++-- django/contrib/admin/util.py | 18 ++--- django/contrib/admin/views/main.py | 6 +- django/contrib/admin/widgets.py | 8 +-- django/contrib/admindocs/utils.py | 4 +- django/contrib/auth/hashers.py | 8 +-- django/contrib/auth/tests/forms.py | 28 ++++---- django/contrib/auth/tests/views.py | 4 +- django/contrib/comments/forms.py | 4 +- django/contrib/comments/managers.py | 4 +- .../contrib/comments/templatetags/comments.py | 4 +- django/contrib/contenttypes/generic.py | 4 +- django/contrib/contenttypes/management.py | 4 +- django/contrib/contenttypes/models.py | 10 +-- django/contrib/databrowse/datastructures.py | 16 ++--- .../contrib/databrowse/plugins/calendars.py | 4 +- .../databrowse/plugins/fieldchoices.py | 4 +- .../contrib/formtools/wizard/storage/base.py | 4 +- django/contrib/gis/sitemaps/views.py | 4 +- .../contrib/humanize/templatetags/humanize.py | 4 +- django/contrib/localflavor/au/forms.py | 4 +- django/contrib/localflavor/br/forms.py | 8 +-- django/contrib/localflavor/ca/forms.py | 4 +- django/contrib/localflavor/ch/forms.py | 4 +- django/contrib/localflavor/cl/forms.py | 4 +- django/contrib/localflavor/fr/forms.py | 4 +- django/contrib/localflavor/hk/forms.py | 4 +- django/contrib/localflavor/hr/forms.py | 6 +- django/contrib/localflavor/id/forms.py | 10 +-- django/contrib/localflavor/in_/forms.py | 6 +- django/contrib/localflavor/is_/forms.py | 4 +- django/contrib/localflavor/it/forms.py | 4 +- django/contrib/localflavor/it/util.py | 6 +- django/contrib/localflavor/nl/forms.py | 4 +- django/contrib/localflavor/pt/forms.py | 6 +- django/contrib/localflavor/tr/forms.py | 4 +- django/contrib/localflavor/us/forms.py | 4 +- django/contrib/markup/templatetags/markup.py | 20 +++--- django/contrib/messages/storage/base.py | 14 ++-- django/contrib/sessions/backends/db.py | 4 +- .../management/commands/collectstatic.py | 6 +- .../management/commands/findstatic.py | 6 +- django/contrib/staticfiles/storage.py | 10 +-- django/contrib/syndication/views.py | 12 ++-- django/core/cache/backends/base.py | 6 +- django/core/context_processors.py | 4 +- django/core/exceptions.py | 6 +- django/core/files/base.py | 6 +- django/core/files/storage.py | 4 +- django/core/files/uploadedfile.py | 4 +- django/core/handlers/base.py | 8 +-- django/core/handlers/wsgi.py | 6 +- django/core/mail/message.py | 12 ++-- .../management/commands/createcachetable.py | 4 +- django/core/management/commands/loaddata.py | 4 +- django/core/serializers/base.py | 2 +- django/core/serializers/json.py | 2 +- django/core/serializers/python.py | 14 ++-- django/core/serializers/pyyaml.py | 2 +- django/core/serializers/xml_serializer.py | 18 ++--- django/core/signing.py | 12 ++-- django/core/urlresolvers.py | 12 ++-- django/core/validators.py | 6 +- django/db/backends/__init__.py | 10 +-- django/db/backends/oracle/base.py | 18 ++--- django/db/models/base.py | 10 +-- django/db/models/fields/__init__.py | 14 ++-- django/db/models/fields/files.py | 4 +- django/db/models/fields/related.py | 6 +- django/db/models/options.py | 6 +- django/db/models/related.py | 6 +- django/db/models/sql/query.py | 4 +- django/db/models/sql/subqueries.py | 6 +- django/forms/fields.py | 20 +++--- django/forms/forms.py | 20 +++--- django/forms/models.py | 10 +-- django/forms/util.py | 12 ++-- django/forms/widgets.py | 42 ++++++------ django/http/__init__.py | 24 +++---- django/http/multipartparser.py | 10 +-- django/template/base.py | 12 ++-- django/template/debug.py | 4 +- django/template/defaultfilters.py | 18 ++--- django/template/defaulttags.py | 6 +- django/templatetags/l10n.py | 6 +- django/test/client.py | 10 +-- django/test/html.py | 4 +- django/test/testcases.py | 8 +-- django/utils/_os.py | 6 +- django/utils/cache.py | 4 +- django/utils/crypto.py | 8 +-- django/utils/dateformat.py | 6 +- django/utils/encoding.py | 66 ++++++++++++------- django/utils/feedgenerator.py | 12 ++-- django/utils/html.py | 22 +++---- django/utils/http.py | 10 +-- django/utils/text.py | 24 +++---- django/utils/translation/__init__.py | 4 +- django/utils/translation/trans_null.py | 6 +- django/utils/tzinfo.py | 6 +- django/views/debug.py | 8 +-- django/views/generic/dates.py | 6 +- django/views/i18n.py | 6 +- docs/howto/custom-model-fields.txt | 2 +- docs/ref/databases.txt | 2 +- docs/ref/models/instances.txt | 4 +- docs/ref/settings.txt | 2 +- docs/ref/unicode.txt | 24 +++---- docs/ref/utils.txt | 50 ++++++++++---- docs/releases/1.5.txt | 2 +- docs/topics/cache.txt | 2 +- docs/topics/serialization.txt | 4 +- tests/modeltests/field_subclassing/fields.py | 8 +-- tests/modeltests/field_subclassing/models.py | 4 +- tests/regressiontests/admin_filters/tests.py | 60 ++++++++--------- tests/regressiontests/admin_views/admin.py | 2 +- tests/regressiontests/cache/tests.py | 6 +- tests/regressiontests/forms/tests/extra.py | 14 ++-- tests/regressiontests/signing/tests.py | 4 +- .../staticfiles_tests/tests.py | 6 +- 125 files changed, 629 insertions(+), 583 deletions(-) diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index 5b56402428..201101736e 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -7,7 +7,7 @@ from django.contrib.admin import helpers from django.contrib.admin.util import get_deleted_objects, model_ngettext from django.db import router from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy, ugettext as _ def delete_selected(modeladmin, request, queryset): @@ -42,7 +42,7 @@ def delete_selected(modeladmin, request, queryset): n = queryset.count() if n: for obj in queryset: - obj_display = force_unicode(obj) + obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) queryset.delete() modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { @@ -52,9 +52,9 @@ def delete_selected(modeladmin, request, queryset): return None if len(queryset) == 1: - objects_name = force_unicode(opts.verbose_name) + objects_name = force_text(opts.verbose_name) else: - objects_name = force_unicode(opts.verbose_name_plural) + objects_name = force_text(opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 538bf54df9..cecae216c0 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -9,7 +9,7 @@ import datetime from django.db import models from django.core.exceptions import ImproperlyConfigured -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -195,7 +195,7 @@ class RelatedFieldListFilter(FieldListFilter): } for pk_val, val in self.lookup_choices: yield { - 'selected': self.lookup_val == smart_unicode(pk_val), + 'selected': self.lookup_val == smart_text(pk_val), 'query_string': cl.get_query_string({ self.lookup_kwarg: pk_val, }, [self.lookup_kwarg_isnull]), @@ -272,7 +272,7 @@ class ChoicesFieldListFilter(FieldListFilter): } for lookup, title in self.field.flatchoices: yield { - 'selected': smart_unicode(lookup) == self.lookup_val, + 'selected': smart_text(lookup) == self.lookup_val, 'query_string': cl.get_query_string({ self.lookup_kwarg: lookup}), 'display': title, @@ -381,7 +381,7 @@ class AllValuesFieldListFilter(FieldListFilter): if val is None: include_none = True continue - val = smart_unicode(val) + val = smart_text(val) yield { 'selected': self.lookup_val == val, 'query_string': cl.get_query_string({ diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 4fc78784f4..aeacd234a3 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -9,7 +9,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields.related import ManyToManyRel from django.forms.util import flatatt from django.template.defaultfilters import capfirst -from django.utils.encoding import force_unicode, smart_unicode +from django.utils.encoding import force_text, smart_text from django.utils.html import conditional_escape, format_html from django.utils.safestring import mark_safe from django.utils import six @@ -122,7 +122,7 @@ class AdminField(object): def label_tag(self): classes = [] - contents = conditional_escape(force_unicode(self.field.label)) + contents = conditional_escape(force_text(self.field.label)) if self.is_checkbox: classes.append('vCheckboxLabel') else: @@ -166,7 +166,7 @@ class AdminReadonlyField(object): label = self.field['label'] return format_html('{1}:', flatatt(attrs), - capfirst(force_unicode(label))) + capfirst(force_text(label))) def contents(self): from django.contrib.admin.templatetags.admin_list import _boolean_icon @@ -182,7 +182,7 @@ class AdminReadonlyField(object): if boolean: result_repr = _boolean_icon(value) else: - result_repr = smart_unicode(value) + result_repr = smart_text(value) if getattr(attr, "allow_tags", False): result_repr = mark_safe(result_repr) else: diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 58bbbabfdf..e31c6d84ed 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType 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_unicode +from django.utils.encoding import smart_text ADDITION = 1 CHANGE = 2 @@ -13,7 +13,7 @@ DELETION = 3 class LogEntryManager(models.Manager): def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): - e = self.model(None, None, user_id, content_type_id, smart_unicode(object_id), object_repr[:200], action_flag, change_message) + e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message) e.save() class LogEntry(models.Model): @@ -34,7 +34,7 @@ class LogEntry(models.Model): ordering = ('-action_time',) def __repr__(self): - return smart_unicode(self.action_time) + return smart_text(self.action_time) def __unicode__(self): if self.action_flag == ADDITION: diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index ea28125a5f..7708050e6f 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -28,7 +28,7 @@ from django.utils import six from django.utils.text import capfirst, get_text_list from django.utils.translation import ugettext as _ from django.utils.translation import ungettext -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text HORIZONTAL, VERTICAL = 1, 2 # returns the
      ', flatatt(self.attrs), format_html_join('\n', '
    • {0}
    • ', - ((force_unicode(w),) for w in self))) + ((force_text(w),) for w in self))) class AdminRadioSelect(forms.RadioSelect): renderer = AdminRadioFieldRenderer @@ -197,7 +197,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): # The related object is registered with the same AdminSite attrs['class'] = 'vManyToManyRawIdAdminField' if value: - value = ','.join([force_unicode(v) for v in value]) + value = ','.join([force_text(v) for v in value]) else: value = '' return super(ManyToManyRawIdWidget, self).render(name, value, attrs) @@ -221,7 +221,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): if len(initial) != len(data): return True for pk1, pk2 in zip(initial, data): - if force_unicode(pk1) != force_unicode(pk2): + if force_text(pk1) != force_text(pk2): return True return False diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py index 4bf1250ac6..0e10eb4fa3 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_str +from django.utils.encoding import smart_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_str("<%s>" % thing_being_parsed) + thing_being_parsed = smart_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/hashers.py b/django/contrib/auth/hashers.py index 96ec40ba60..c676cf84db 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -7,7 +7,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_str +from django.utils.encoding import smart_bytes from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import ( pbkdf2, constant_time_compare, get_random_string) @@ -298,7 +298,7 @@ class SHA1PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.sha1(smart_str(salt + password)).hexdigest() + hash = hashlib.sha1(smart_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -326,7 +326,7 @@ class MD5PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.md5(smart_str(salt + password)).hexdigest() + hash = hashlib.md5(smart_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -360,7 +360,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): - return hashlib.md5(smart_str(password)).hexdigest() + return hashlib.md5(smart_bytes(password)).hexdigest() def verify(self, password, encoded): encoded_2 = self.encode(password, '') diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 2bfe35ac88..f917ea2601 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -8,7 +8,7 @@ from django.core import mail from django.forms.fields import Field, EmailField from django.test import TestCase from django.test.utils import override_settings -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils import six from django.utils import translation from django.utils.translation import ugettext as _ @@ -28,7 +28,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["username"].errors, - [force_unicode(form.error_messages['duplicate_username'])]) + [force_text(form.error_messages['duplicate_username'])]) def test_invalid_data(self): data = { @@ -39,7 +39,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["username"].errors, - [force_unicode(form.fields['username'].error_messages['invalid'])]) + [force_text(form.fields['username'].error_messages['invalid'])]) def test_password_verification(self): # The verification password is incorrect. @@ -51,13 +51,13 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_both_passwords(self): # One (or both) passwords weren't given data = {'username': 'jsmith'} form = UserCreationForm(data) - required_error = [force_unicode(Field.default_error_messages['required'])] + required_error = [force_text(Field.default_error_messages['required'])] self.assertFalse(form.is_valid()) self.assertEqual(form['password1'].errors, required_error) self.assertEqual(form['password2'].errors, required_error) @@ -96,7 +96,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['invalid_login'])]) + [force_text(form.error_messages['invalid_login'])]) def test_inactive_user(self): # The user is inactive. @@ -107,7 +107,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['inactive'])]) + [force_text(form.error_messages['inactive'])]) def test_inactive_user_i18n(self): with self.settings(USE_I18N=True): @@ -120,7 +120,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['inactive'])]) + [force_text(form.error_messages['inactive'])]) def test_success(self): # The success case @@ -148,7 +148,7 @@ class SetPasswordFormTest(TestCase): form = SetPasswordForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["new_password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_success(self): user = User.objects.get(username='testclient') @@ -175,7 +175,7 @@ class PasswordChangeFormTest(TestCase): form = PasswordChangeForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["old_password"].errors, - [force_unicode(form.error_messages['password_incorrect'])]) + [force_text(form.error_messages['password_incorrect'])]) def test_password_verification(self): # The two new passwords do not match. @@ -188,7 +188,7 @@ class PasswordChangeFormTest(TestCase): form = PasswordChangeForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["new_password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_success(self): # The success case. @@ -219,7 +219,7 @@ class UserChangeFormTest(TestCase): form = UserChangeForm(data, instance=user) self.assertFalse(form.is_valid()) self.assertEqual(form['username'].errors, - [force_unicode(form.fields['username'].error_messages['invalid'])]) + [force_text(form.fields['username'].error_messages['invalid'])]) def test_bug_14242(self): # A regression test, introduce by adding an optimization for the @@ -274,7 +274,7 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form['email'].errors, - [force_unicode(EmailField.default_error_messages['invalid'])]) + [force_text(EmailField.default_error_messages['invalid'])]) def test_nonexistant_email(self): # Test nonexistant email address @@ -282,7 +282,7 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, - {'email': [force_unicode(form.error_messages['unknown'])]}) + {'email': [force_text(form.error_messages['unknown'])]}) def test_cleaned_data(self): # Regression test diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index e76e7dd10f..3c847f456a 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -7,7 +7,7 @@ from django.contrib.auth.models import User from django.core import mail from django.core.urlresolvers import reverse, NoReverseMatch from django.http import QueryDict -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.html import escape from django.utils.http import urlquote from django.test import TestCase @@ -46,7 +46,7 @@ class AuthViewsTestCase(TestCase): self.assertTrue(SESSION_KEY in self.client.session) def assertContainsEscaped(self, response, text, **kwargs): - return self.assertContains(response, escape(force_unicode(text)), **kwargs) + return self.assertContains(response, escape(force_text(text)), **kwargs) class AuthViewNamedURLTests(AuthViewsTestCase): diff --git a/django/contrib/comments/forms.py b/django/contrib/comments/forms.py index 830e24bca9..bd254d2733 100644 --- a/django/contrib/comments/forms.py +++ b/django/contrib/comments/forms.py @@ -5,7 +5,7 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.comments.models import Comment from django.utils.crypto import salted_hmac, constant_time_compare -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.text import get_text_list from django.utils import timezone from django.utils.translation import ungettext, ugettext, ugettext_lazy as _ @@ -133,7 +133,7 @@ class CommentDetailsForm(CommentSecurityForm): """ return dict( content_type = ContentType.objects.get_for_model(self.target_object), - object_pk = force_unicode(self.target_object._get_pk_val()), + object_pk = force_text(self.target_object._get_pk_val()), user_name = self.cleaned_data["name"], user_email = self.cleaned_data["email"], user_url = self.cleaned_data["url"], diff --git a/django/contrib/comments/managers.py b/django/contrib/comments/managers.py index 499feee6c3..bc0fc5f332 100644 --- a/django/contrib/comments/managers.py +++ b/django/contrib/comments/managers.py @@ -1,6 +1,6 @@ from django.db import models from django.contrib.contenttypes.models import ContentType -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class CommentManager(models.Manager): @@ -18,5 +18,5 @@ class CommentManager(models.Manager): ct = ContentType.objects.get_for_model(model) qs = self.get_query_set().filter(content_type=ct) if isinstance(model, models.Model): - qs = qs.filter(object_pk=force_unicode(model._get_pk_val())) + qs = qs.filter(object_pk=force_text(model._get_pk_val())) return qs diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py index ce1825e529..4d4eb2322f 100644 --- a/django/contrib/comments/templatetags/comments.py +++ b/django/contrib/comments/templatetags/comments.py @@ -3,7 +3,7 @@ from django.template.loader import render_to_string from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib import comments -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text register = template.Library() @@ -75,7 +75,7 @@ class BaseCommentNode(template.Node): qs = self.comment_model.objects.filter( content_type = ctype, - object_pk = smart_unicode(object_pk), + object_pk = smart_text(object_pk), site__pk = settings.SITE_ID, ) diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index d5062cabf3..29e93eefe7 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -17,7 +17,7 @@ from django.forms import ModelForm from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets from django.contrib.contenttypes.models import ContentType -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text class GenericForeignKey(object): """ @@ -169,7 +169,7 @@ class GenericRelation(RelatedField, Field): def value_to_string(self, obj): qs = getattr(obj, self.name).all() - return smart_unicode([instance._get_pk_val() for instance in qs]) + return smart_text([instance._get_pk_val() for instance in qs]) def m2m_db_table(self): return self.rel.to._meta.db_table diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 1b1c9c8562..11ca7e4763 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,6 +1,6 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import get_apps, get_models, signals -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils import six def update_contenttypes(app, created_models, verbosity=2, **kwargs): @@ -31,7 +31,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs): cts = ContentType.objects.bulk_create([ ContentType( - name=smart_unicode(model._meta.verbose_name_raw), + name=smart_text(model._meta.verbose_name_raw), app_label=app_label, model=model_name, ) diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 867351f6c8..e6d547a491 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -1,6 +1,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text class ContentTypeManager(models.Manager): @@ -37,13 +37,13 @@ class ContentTypeManager(models.Manager): try: ct = self._get_from_cache(opts) except KeyError: - # Load or create the ContentType entry. The smart_unicode() is + # Load or create the ContentType entry. The smart_text() is # needed around opts.verbose_name_raw because name_raw might be a # django.utils.functional.__proxy__ object. ct, created = self.get_or_create( app_label = opts.app_label, model = opts.object_name.lower(), - defaults = {'name': smart_unicode(opts.verbose_name_raw)}, + defaults = {'name': smart_text(opts.verbose_name_raw)}, ) self._add_to_cache(self.db, ct) @@ -86,7 +86,7 @@ class ContentTypeManager(models.Manager): ct = self.create( app_label=opts.app_label, model=opts.object_name.lower(), - name=smart_unicode(opts.verbose_name_raw), + name=smart_text(opts.verbose_name_raw), ) self._add_to_cache(self.db, ct) results[ct.model_class()] = ct @@ -147,7 +147,7 @@ class ContentType(models.Model): if not model or self.name != model._meta.verbose_name_raw: return self.name else: - return force_unicode(model._meta.verbose_name) + return force_text(model._meta.verbose_name) def model_class(self): "Returns the Python model class for this type of content." diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 95d347cac0..810e039894 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -7,7 +7,7 @@ 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_unicode, smart_str, iri_to_uri +from django.utils.encoding import smart_text, smart_bytes, iri_to_uri from django.db.models.query import QuerySet EMPTY_VALUE = '(None)' @@ -22,7 +22,7 @@ class EasyModel(object): self.verbose_name_plural = model._meta.verbose_name_plural def __repr__(self): - return '' % smart_str(self.model._meta.object_name) + return '' % smart_bytes(self.model._meta.object_name) def model_databrowse(self): "Returns the ModelDatabrowse class for this model." @@ -61,7 +61,7 @@ class EasyField(object): self.model, self.field = easy_model, field def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return smart_bytes('' % (self.model.model._meta.object_name, self.field.name)) def choices(self): for value, label in self.field.choices: @@ -79,7 +79,7 @@ class EasyChoice(object): self.value, self.label = value, label def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return smart_bytes('' % (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)) @@ -89,10 +89,10 @@ class EasyInstance(object): self.model, self.instance = easy_model, instance def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) + return smart_bytes('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) def __unicode__(self): - val = smart_unicode(self.instance) + val = smart_text(self.instance) if len(val) > DISPLAY_SIZE: return val[:DISPLAY_SIZE] + '...' return val @@ -136,7 +136,7 @@ class EasyInstanceField(object): self.raw_value = getattr(instance.instance, field.name) def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return smart_bytes('' % (self.model.model._meta.object_name, self.field.name)) def values(self): """ @@ -185,7 +185,7 @@ class EasyInstanceField(object): if value is None: continue url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())) - lst.append((smart_unicode(value), url)) + lst.append((smart_text(value), url)) else: lst = [(value, None) for value in self.values()] elif self.field.choices: diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 923adb90f6..a548c33c8f 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -7,7 +7,7 @@ from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response from django.utils.html import format_html, format_html_join from django.utils.text import capfirst -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.views.generic import dates from django.utils import datetime_safe @@ -66,7 +66,7 @@ class CalendarPlugin(DatabrowsePlugin): return '' return format_html('

      View calendar by: {0}

      ', format_html_join(', ', '
      {1}', - ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()))) + ((f.name, force_text(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if isinstance(easy_instance_field.field, models.DateField): diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index 73298b8d56..dc5e9aef14 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -8,7 +8,7 @@ from django.shortcuts import render_to_response from django.utils.html import format_html, format_html_join from django.utils.http import urlquote from django.utils.text import capfirst -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class FieldChoicePlugin(DatabrowsePlugin): @@ -35,7 +35,7 @@ class FieldChoicePlugin(DatabrowsePlugin): return '' return format_html('

      View by: {0}

      ', format_html_join(', ', '{1}', - ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()))) + ((f.name, force_text(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py index 7c802712c1..05c9f6f121 100644 --- a/django/contrib/formtools/wizard/storage/base.py +++ b/django/contrib/formtools/wizard/storage/base.py @@ -1,6 +1,6 @@ from django.core.files.uploadedfile import UploadedFile from django.utils.datastructures import MultiValueDict -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils.functional import lazy_property from django.utils import six @@ -74,7 +74,7 @@ class BaseStorage(object): files = {} for field, field_dict in six.iteritems(wizard_files): - field_dict = dict((smart_str(k), v) + field_dict = dict((smart_bytes(k), v) for k, v in six.iteritems(field_dict)) tmp_name = field_dict.pop('tmp_name') files[field] = UploadedFile( diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index 6753b9e34a..8bcdba1b44 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -8,7 +8,7 @@ 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_str +from django.utils.encoding import smart_bytes from django.utils import six from django.utils.translation import ugettext as _ @@ -61,7 +61,7 @@ def sitemap(request, sitemaps, section=None): raise Http404(_("Page %s empty") % page) except PageNotAnInteger: raise Http404(_("No page '%s'") % page) - xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})) + xml = smart_bytes(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/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 8f6c2602c9..7e8f163174 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -5,7 +5,7 @@ from datetime import date, datetime from django import template from django.conf import settings from django.template import defaultfilters -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.formats import number_format from django.utils.translation import pgettext, ungettext, ugettext as _ from django.utils.timezone import is_aware, utc @@ -41,7 +41,7 @@ def intcomma(value, use_l10n=True): return intcomma(value, False) else: return number_format(value, force_grouping=True) - orig = force_unicode(value) + orig = force_text(value) new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', orig) if orig == new: return new diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py index 34170fabc8..d3a00e200c 100644 --- a/django/contrib/localflavor/au/forms.py +++ b/django/contrib/localflavor/au/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.au.au_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -44,7 +44,7 @@ class AUPhoneNumberField(Field): super(AUPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+|-)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+|-)', '', smart_text(value)) phone_match = PHONE_DIGITS_RE.search(value) if phone_match: return '%s' % phone_match.group(1) diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py index f287d46a9a..4d0d6815ba 100644 --- a/django/contrib/localflavor/br/forms.py +++ b/django/contrib/localflavor/br/forms.py @@ -11,7 +11,7 @@ from django.contrib.localflavor.br.br_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -35,7 +35,7 @@ class BRPhoneNumberField(Field): super(BRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) @@ -68,10 +68,10 @@ class BRStateChoiceField(Field): value = super(BRStateChoiceField, self).clean(value) if value in EMPTY_VALUES: value = '' - value = smart_unicode(value) + value = smart_text(value) if value == '': return value - valid_values = set([smart_unicode(k) for k, v in self.widget.choices]) + valid_values = set([smart_text(k) for k, v in self.widget.choices]) if value not in valid_values: raise ValidationError(self.error_messages['invalid']) return value diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py index daa40044f9..4ebfb06c2b 100644 --- a/django/contrib/localflavor/ca/forms.py +++ b/django/contrib/localflavor/ca/forms.py @@ -9,7 +9,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -53,7 +53,7 @@ class CAPhoneNumberField(Field): super(CAPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py index e844a3c57c..bf71eeea32 100644 --- a/django/contrib/localflavor/ch/forms.py +++ b/django/contrib/localflavor/ch/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.ch.ch_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -41,7 +41,7 @@ class CHPhoneNumberField(Field): super(CHPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\.|\s|/|-)', '', smart_unicode(value)) + value = re.sub('(\.|\s|/|-)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) diff --git a/django/contrib/localflavor/cl/forms.py b/django/contrib/localflavor/cl/forms.py index 5991176382..a5340141ce 100644 --- a/django/contrib/localflavor/cl/forms.py +++ b/django/contrib/localflavor/cl/forms.py @@ -8,7 +8,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import RegexField, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from .cl_regions import REGION_CHOICES @@ -75,7 +75,7 @@ class CLRutField(RegexField): Turns the RUT into one normalized format. Returns a (rut, verifier) tuple. """ - rut = smart_unicode(rut).replace(' ', '').replace('.', '').replace('-', '') + rut = smart_text(rut).replace(' ', '').replace('.', '').replace('-', '') return rut[:-1], rut[-1].upper() def _format(self, code, verifier=None): diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index d836dd6397..8b841fff5f 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -9,7 +9,7 @@ from django.contrib.localflavor.fr.fr_department import DEPARTMENT_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import CharField, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -43,7 +43,7 @@ class FRPhoneNumberField(CharField): super(FRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\.|\s)', '', smart_unicode(value)) + value = re.sub('(\.|\s)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) diff --git a/django/contrib/localflavor/hk/forms.py b/django/contrib/localflavor/hk/forms.py index 8cf9360e19..ab4f70f193 100644 --- a/django/contrib/localflavor/hk/forms.py +++ b/django/contrib/localflavor/hk/forms.py @@ -8,7 +8,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import CharField from django.forms import ValidationError -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -53,7 +53,7 @@ class HKPhoneNumberField(CharField): if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+|\+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+|\+)', '', smart_text(value)) m = hk_phone_digits_re.search(value) if not m: raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/hr/forms.py b/django/contrib/localflavor/hr/forms.py index eb4436a78c..b935fd8a3a 100644 --- a/django/contrib/localflavor/hr/forms.py +++ b/django/contrib/localflavor/hr/forms.py @@ -12,7 +12,7 @@ from django.contrib.localflavor.hr.hr_choices import ( from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select, RegexField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -159,7 +159,7 @@ class HRLicensePlateField(Field): if value in EMPTY_VALUES: return '' - value = re.sub(r'[\s\-]+', '', smart_unicode(value.strip())).upper() + value = re.sub(r'[\s\-]+', '', smart_text(value.strip())).upper() matches = plate_re.search(value) if matches is None: @@ -225,7 +225,7 @@ class HRPhoneNumberField(Field): if value in EMPTY_VALUES: return '' - value = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) + value = re.sub(r'[\-\s\(\)]', '', smart_text(value)) matches = phone_re.search(value) if matches is None: diff --git a/django/contrib/localflavor/id/forms.py b/django/contrib/localflavor/id/forms.py index f22b06134e..2005dbc75c 100644 --- a/django/contrib/localflavor/id/forms.py +++ b/django/contrib/localflavor/id/forms.py @@ -11,7 +11,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text postcode_re = re.compile(r'^[1-9]\d{4}$') @@ -77,10 +77,10 @@ class IDPhoneNumberField(Field): if value in EMPTY_VALUES: return '' - phone_number = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) + phone_number = re.sub(r'[\-\s\(\)]', '', smart_text(value)) if phone_re.search(phone_number): - return smart_unicode(value) + return smart_text(value) raise ValidationError(self.error_messages['invalid']) @@ -120,7 +120,7 @@ class IDLicensePlateField(Field): return '' plate_number = re.sub(r'\s+', ' ', - smart_unicode(value.strip())).upper() + smart_text(value.strip())).upper() matches = plate_re.search(plate_number) if matches is None: @@ -181,7 +181,7 @@ class IDNationalIdentityNumberField(Field): if value in EMPTY_VALUES: return '' - value = re.sub(r'[\s.]', '', smart_unicode(value)) + value = re.sub(r'[\s.]', '', smart_text(value)) if not nik_re.search(value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/in_/forms.py b/django/contrib/localflavor/in_/forms.py index b62ec7bdb2..5c1d009ef4 100644 --- a/django/contrib/localflavor/in_/forms.py +++ b/django/contrib/localflavor/in_/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.in_.in_states import STATES_NORMALIZED, STATE_CH from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -74,7 +74,7 @@ class INStateField(Field): pass else: try: - return smart_unicode(STATES_NORMALIZED[value.strip().lower()]) + return smart_text(STATES_NORMALIZED[value.strip().lower()]) except KeyError: pass raise ValidationError(self.error_messages['invalid']) @@ -107,7 +107,7 @@ class INPhoneNumberField(CharField): super(INPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = smart_unicode(value) + value = smart_text(value) m = phone_digits_re.match(value) if m: return '%s' % (value) diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py index 7af9f51cfb..1ae3e012a1 100644 --- a/django/contrib/localflavor/is_/forms.py +++ b/django/contrib/localflavor/is_/forms.py @@ -9,7 +9,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import RegexField from django.forms.widgets import Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -58,7 +58,7 @@ class ISIdNumberField(RegexField): Takes in the value in canonical form and returns it in the common display format. """ - return smart_unicode(value[:6]+'-'+value[6:]) + return smart_text(value[:6]+'-'+value[6:]) class ISPhoneNumberField(RegexField): """ diff --git a/django/contrib/localflavor/it/forms.py b/django/contrib/localflavor/it/forms.py index 60b1eff951..916ce9bb3d 100644 --- a/django/contrib/localflavor/it/forms.py +++ b/django/contrib/localflavor/it/forms.py @@ -13,7 +13,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text class ITZipCodeField(RegexField): @@ -85,4 +85,4 @@ class ITVatNumberField(Field): check_digit = vat_number_check_digit(vat_number[0:10]) if not vat_number[10] == check_digit: raise ValidationError(self.error_messages['invalid']) - return smart_unicode(vat_number) + return smart_text(vat_number) diff --git a/django/contrib/localflavor/it/util.py b/django/contrib/localflavor/it/util.py index ec1b7e3f83..e1aa9c0419 100644 --- a/django/contrib/localflavor/it/util.py +++ b/django/contrib/localflavor/it/util.py @@ -1,4 +1,4 @@ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text def ssn_check_digit(value): "Calculate Italian social security number check digit." @@ -34,11 +34,11 @@ def ssn_check_digit(value): def vat_number_check_digit(vat_number): "Calculate Italian VAT number check digit." - normalized_vat_number = smart_unicode(vat_number).zfill(10) + normalized_vat_number = smart_text(vat_number).zfill(10) total = 0 for i in range(0, 10, 2): total += int(normalized_vat_number[i]) for i in range(1, 11, 2): quotient , remainder = divmod(int(normalized_vat_number[i]) * 2, 10) total += quotient + remainder - return smart_unicode((10 - total % 10) % 10) + return smart_text((10 - total % 10) % 10) diff --git a/django/contrib/localflavor/nl/forms.py b/django/contrib/localflavor/nl/forms.py index bdd769bd39..a05dd38f7f 100644 --- a/django/contrib/localflavor/nl/forms.py +++ b/django/contrib/localflavor/nl/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.nl.nl_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -61,7 +61,7 @@ class NLPhoneNumberField(Field): if value in EMPTY_VALUES: return '' - phone_nr = re.sub('[\-\s\(\)]', '', smart_unicode(value)) + phone_nr = re.sub('[\-\s\(\)]', '', smart_text(value)) if len(phone_nr) == 10 and numeric_re.search(phone_nr): return value diff --git a/django/contrib/localflavor/pt/forms.py b/django/contrib/localflavor/pt/forms.py index b27fb577bd..01cdd101b2 100644 --- a/django/contrib/localflavor/pt/forms.py +++ b/django/contrib/localflavor/pt/forms.py @@ -8,7 +8,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ phone_digits_re = re.compile(r'^(\d{9}|(00|\+)\d*)$') @@ -29,7 +29,7 @@ class PTZipCodeField(RegexField): return '%s-%s' % (cleaned[:4],cleaned[4:]) else: return cleaned - + class PTPhoneNumberField(Field): """ Validate local Portuguese phone number (including international ones) @@ -43,7 +43,7 @@ class PTPhoneNumberField(Field): super(PTPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\.|\s)', '', smart_unicode(value)) + value = re.sub('(\.|\s)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s' % value diff --git a/django/contrib/localflavor/tr/forms.py b/django/contrib/localflavor/tr/forms.py index 15bc1f99bb..c4f928e670 100644 --- a/django/contrib/localflavor/tr/forms.py +++ b/django/contrib/localflavor/tr/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.tr.tr_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select, CharField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -46,7 +46,7 @@ class TRPhoneNumberField(CharField): super(TRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s%s' % (m.group(2), m.group(4)) diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py index ef565163cd..437bb7c466 100644 --- a/django/contrib/localflavor/us/forms.py +++ b/django/contrib/localflavor/us/forms.py @@ -9,7 +9,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select, CharField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -34,7 +34,7 @@ class USPhoneNumberField(CharField): super(USPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 84251cf30a..af9c842f42 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_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils.safestring import mark_safe register = template.Library() @@ -25,9 +25,9 @@ def textile(value): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.") - return force_unicode(value) + return force_text(value) else: - return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))) + return mark_safe(force_text(textile.textile(smart_bytes(value), encoding='utf-8', output='utf-8'))) @register.filter(is_safe=True) def markdown(value, arg=''): @@ -52,23 +52,23 @@ def markdown(value, arg=''): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'markdown' filter: The Python markdown library isn't installed.") - return force_unicode(value) + return force_text(value) else: markdown_vers = getattr(markdown, "version_info", 0) if markdown_vers < (2, 1): if settings.DEBUG: raise template.TemplateSyntaxError( "Error in 'markdown' filter: Django does not support versions of the Python markdown library < 2.1.") - return force_unicode(value) + return force_text(value) else: extensions = [e for e in arg.split(",") if e] if extensions and extensions[0] == "safe": extensions = extensions[1:] return mark_safe(markdown.markdown( - force_unicode(value), extensions, safe_mode=True, enable_attributes=False)) + force_text(value), extensions, safe_mode=True, enable_attributes=False)) else: return mark_safe(markdown.markdown( - force_unicode(value), extensions, safe_mode=False)) + force_text(value), extensions, safe_mode=False)) @register.filter(is_safe=True) def restructuredtext(value): @@ -77,8 +77,8 @@ def restructuredtext(value): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'restructuredtext' filter: The Python docutils library isn't installed.") - return force_unicode(value) + return force_text(value) else: docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {}) - parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings) - return mark_safe(force_unicode(parts["fragment"])) + parts = publish_parts(source=smart_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 e80818e84e..5433bbff28 100644 --- a/django/contrib/messages/storage/base.py +++ b/django/contrib/messages/storage/base.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from django.conf import settings -from django.utils.encoding import force_unicode, StrAndUnicode +from django.utils.encoding import force_text, StrAndUnicode from django.contrib.messages import constants, utils @@ -26,22 +26,22 @@ class Message(StrAndUnicode): and ``extra_tags`` to unicode in case they are lazy translations. Known "safe" types (None, int, etc.) are not converted (see Django's - ``force_unicode`` implementation for details). + ``force_text`` implementation for details). """ - self.message = force_unicode(self.message, strings_only=True) - self.extra_tags = force_unicode(self.extra_tags, strings_only=True) + self.message = force_text(self.message, strings_only=True) + self.extra_tags = force_text(self.extra_tags, strings_only=True) def __eq__(self, other): return isinstance(other, Message) and self.level == other.level and \ self.message == other.message def __unicode__(self): - return force_unicode(self.message) + return force_text(self.message) def _get_tags(self): - label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''), + label_tag = force_text(LEVEL_TAGS.get(self.level, ''), strings_only=True) - extra_tags = force_unicode(self.extra_tags, strings_only=True) + extra_tags = force_text(self.extra_tags, strings_only=True) if extra_tags and label_tag: return ' '.join([extra_tags, label_tag]) elif extra_tags: diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py index 3dd0d9516c..0cc17b44c3 100644 --- a/django/contrib/sessions/backends/db.py +++ b/django/contrib/sessions/backends/db.py @@ -1,7 +1,7 @@ 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_unicode +from django.utils.encoding import force_text from django.utils import timezone @@ -18,7 +18,7 @@ class SessionStore(SessionBase): session_key = self.session_key, expire_date__gt=timezone.now() ) - return self.decode(force_unicode(s.session_data)) + return self.decode(force_text(s.session_data)) except (Session.DoesNotExist, SuspiciousOperation): self.create() return {} diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index d3977213a9..45c5ecfe1f 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -6,7 +6,7 @@ from optparse import make_option from django.core.files.storage import FileSystemStorage from django.core.management.base import CommandError, NoArgsCommand -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.datastructures import SortedDict from django.contrib.staticfiles import finders, storage @@ -198,9 +198,9 @@ Type 'yes' to continue, or 'no' to cancel: """ fpath = os.path.join(path, f) if self.dry_run: self.log("Pretending to delete '%s'" % - smart_unicode(fpath), level=1) + smart_text(fpath), level=1) else: - self.log("Deleting '%s'" % smart_unicode(fpath), level=1) + self.log("Deleting '%s'" % smart_text(fpath), level=1) self.storage.delete(fpath) for d in dirs: self.clear_dir(os.path.join(path, d)) diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py index 772220b342..dc1e88d778 100644 --- a/django/contrib/staticfiles/management/commands/findstatic.py +++ b/django/contrib/staticfiles/management/commands/findstatic.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import os from optparse import make_option from django.core.management.base import LabelCommand -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.contrib.staticfiles import finders @@ -19,12 +19,12 @@ class Command(LabelCommand): def handle_label(self, path, **options): verbosity = int(options.get('verbosity', 1)) result = finders.find(path, all=options['all']) - path = smart_unicode(path) + path = smart_text(path) if result: if not isinstance(result, (list, tuple)): result = [result] output = '\n '.join( - (smart_unicode(os.path.realpath(path)) for path in result)) + (smart_text(os.path.realpath(path)) for path in result)) self.stdout.write("Found '%s' here:\n %s" % (path, output)) else: if verbosity >= 1: diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index a0133e1c6a..2ca54dde71 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_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.functional import LazyObject from django.utils.importlib import import_module @@ -112,7 +112,7 @@ class CachedFilesMixin(object): return urlunsplit(unparsed_name) def cache_key(self, name): - return 'staticfiles:%s' % hashlib.md5(smart_str(name)).hexdigest() + return 'staticfiles:%s' % hashlib.md5(smart_bytes(name)).hexdigest() def url(self, name, force=False): """ @@ -248,9 +248,9 @@ class CachedFilesMixin(object): if hashed_file_exists: self.delete(hashed_name) # then save the processed result - content_file = ContentFile(smart_str(content)) + content_file = ContentFile(smart_bytes(content)) saved_name = self._save(hashed_name, content_file) - hashed_name = force_unicode(saved_name.replace('\\', '/')) + hashed_name = force_text(saved_name.replace('\\', '/')) processed = True else: # or handle the case in which neither processing nor @@ -258,7 +258,7 @@ class CachedFilesMixin(object): if not hashed_file_exists: processed = True saved_name = self._save(hashed_name, original_file) - hashed_name = force_unicode(saved_name.replace('\\', '/')) + hashed_name = force_text(saved_name.replace('\\', '/')) # and then set the cache accordingly hashed_paths[self.cache_key(name)] = hashed_name diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 3c84f1f60c..bce7ef7cfb 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -6,7 +6,7 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import HttpResponse, Http404 from django.template import loader, TemplateDoesNotExist, RequestContext from django.utils import feedgenerator, tzinfo -from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode +from django.utils.encoding import force_text, iri_to_uri, smart_text from django.utils.html import escape from django.utils.timezone import is_naive @@ -43,10 +43,10 @@ class Feed(object): def item_title(self, item): # Titles should be double escaped by default (see #6533) - return escape(force_unicode(item)) + return escape(force_text(item)) def item_description(self, item): - return force_unicode(item) + return force_text(item) def item_link(self, item): try: @@ -154,9 +154,9 @@ class Feed(object): enc_url = self.__get_dynamic_attr('item_enclosure_url', item) if enc_url: enc = feedgenerator.Enclosure( - url = smart_unicode(enc_url), - length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)), - mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item)) + url = smart_text(enc_url), + length = smart_text(self.__get_dynamic_attr('item_enclosure_length', item)), + mime_type = smart_text(self.__get_dynamic_attr('item_enclosure_mime_type', item)) ) author_name = self.__get_dynamic_attr('item_author_name', item) if author_name is not None: diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index f7573b2e31..d527e44d8b 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -3,7 +3,7 @@ import warnings from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning -from django.utils.encoding import smart_str +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_str(key)]) + return ':'.join([key_prefix, str(version), smart_bytes(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_str(params.get('KEY_PREFIX', '')) + self.key_prefix = smart_bytes(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/context_processors.py b/django/core/context_processors.py index 325f64d224..a503270cf4 100644 --- a/django/core/context_processors.py +++ b/django/core/context_processors.py @@ -9,7 +9,7 @@ RequestContext. from django.conf import settings from django.middleware.csrf import get_token -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils.functional import lazy def csrf(request): @@ -25,7 +25,7 @@ def csrf(request): # instead of returning an empty dict. return b'NOTPROVIDED' else: - return smart_str(token) + return smart_bytes(token) _get_val = lazy(_get_val, str) return {'csrf_token': _get_val() } diff --git a/django/core/exceptions.py b/django/core/exceptions.py index e3d1dc9c7e..f0f14cffda 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -43,7 +43,7 @@ class ValidationError(Exception): """An error while validating data.""" def __init__(self, message, code=None, params=None): import operator - from django.utils.encoding import force_unicode + from django.utils.encoding import force_text """ ValidationError can be passed any object that can be printed (usually a string), a list of objects or a dictionary. @@ -54,11 +54,11 @@ class ValidationError(Exception): message = reduce(operator.add, message.values()) if isinstance(message, list): - self.messages = [force_unicode(msg) for msg in message] + self.messages = [force_text(msg) for msg in message] else: self.code = code self.params = params - message = force_unicode(message) + message = force_text(message) self.messages = [message] def __str__(self): diff --git a/django/core/files/base.py b/django/core/files/base.py index 04853fad0c..37b1be89b3 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import os from io import BytesIO -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_bytes, smart_text from django.core.files.utils import FileProxyMixin class File(FileProxyMixin): @@ -18,10 +18,10 @@ class File(FileProxyMixin): self.mode = file.mode def __str__(self): - return smart_str(self.name or '') + return smart_bytes(self.name or '') def __unicode__(self): - return smart_unicode(self.name or '') + return smart_text(self.name or '') def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self or "None") diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 5179980513..7542dcda46 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -11,7 +11,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.core.files import locks, File from django.core.files.move import file_move_safe -from django.utils.encoding import force_unicode, filepath_to_uri +from django.utils.encoding import force_text, filepath_to_uri from django.utils.functional import LazyObject from django.utils.importlib import import_module from django.utils.text import get_valid_filename @@ -48,7 +48,7 @@ class Storage(object): name = self._save(name, content) # Store filenames with forward slashes, even on Windows - return force_unicode(name.replace('\\', '/')) + return force_text(name.replace('\\', '/')) # These methods are part of the public API, with default implementations. diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py index 97d53482e4..3a6c632975 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_str +from django.utils.encoding import smart_bytes __all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile', 'SimpleUploadedFile') @@ -30,7 +30,7 @@ class UploadedFile(File): self.charset = charset def __repr__(self): - return smart_str("<%s: %s (%s)>" % ( + return smart_bytes("<%s: %s (%s)>" % ( self.__class__.__name__, self.name, self.content_type)) def _get_name(self): diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 7fd7d19c4a..5a6825f0a7 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -4,7 +4,7 @@ import sys from django import http from django.core import signals -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.importlib import import_module from django.utils.log import getLogger from django.utils import six @@ -250,7 +250,7 @@ def get_script_name(environ): """ from django.conf import settings if settings.FORCE_SCRIPT_NAME is not None: - return force_unicode(settings.FORCE_SCRIPT_NAME) + return force_text(settings.FORCE_SCRIPT_NAME) # If Apache's mod_rewrite had a whack at the URL, Apache set either # SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any @@ -261,5 +261,5 @@ def get_script_name(environ): if not script_url: script_url = environ.get('REDIRECT_URL', '') if script_url: - return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))]) - return force_unicode(environ.get('SCRIPT_NAME', '')) + return force_text(script_url[:-len(environ.get('PATH_INFO', ''))]) + return force_text(environ.get('SCRIPT_NAME', '')) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 51ad2be002..70b23f8515 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_unicode, smart_str, iri_to_uri +from django.utils.encoding import force_text, smart_bytes, iri_to_uri from django.utils.log import getLogger logger = getLogger('django.request') @@ -127,7 +127,7 @@ class LimitedStream(object): class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = base.get_script_name(environ) - path_info = force_unicode(environ.get('PATH_INFO', '/')) + path_info = force_text(environ.get('PATH_INFO', '/')) if not path_info or path_info == script_name: # Sometimes PATH_INFO exists, but is empty (e.g. accessing # the SCRIPT_NAME URL without a trailing slash). We really need to @@ -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_str(status), response_headers) + start_response(smart_bytes(status), response_headers) return response diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 629ad464f9..8f589ae33d 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -15,7 +15,7 @@ from io import BytesIO from django.conf import settings from django.core.mail.utils import DNS_NAME -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils import six @@ -79,7 +79,7 @@ ADDRESS_HEADERS = set([ def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET - val = force_unicode(val) + val = force_text(val) if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: @@ -93,12 +93,12 @@ def forbid_multi_line_headers(name, val, encoding): else: if name.lower() == 'subject': val = Header(val) - return smart_str(name), val + return smart_bytes(name), val def sanitize_address(addr, encoding): if isinstance(addr, six.string_types): - addr = parseaddr(force_unicode(addr)) + addr = parseaddr(force_text(addr)) nm, addr = addr nm = str(Header(nm, encoding)) try: @@ -210,7 +210,7 @@ class EmailMessage(object): def message(self): encoding = self.encoding or settings.DEFAULT_CHARSET - msg = SafeMIMEText(smart_str(self.body, encoding), + msg = SafeMIMEText(smart_bytes(self.body, encoding), self.content_subtype, encoding) msg = self._create_message(msg) msg['Subject'] = self.subject @@ -293,7 +293,7 @@ class EmailMessage(object): basetype, subtype = mimetype.split('/', 1) if basetype == 'text': encoding = self.encoding or settings.DEFAULT_CHARSET - attachment = SafeMIMEText(smart_str(content, encoding), subtype, encoding) + attachment = SafeMIMEText(smart_bytes(content, encoding), subtype, encoding) else: # Encode non-text attachments with base64. attachment = MIMEBase(basetype, subtype) diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index fd6dbbbd2c..411042ee76 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -4,7 +4,7 @@ from django.core.cache.backends.db import BaseDatabaseCache from django.core.management.base import LabelCommand, CommandError from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.db.utils import DatabaseError -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class Command(LabelCommand): @@ -60,7 +60,7 @@ class Command(LabelCommand): transaction.rollback_unless_managed(using=db) raise CommandError( "Cache table '%s' could not be created.\nThe error was: %s." % - (tablename, force_unicode(e))) + (tablename, force_text(e))) for statement in index_output: curs.execute(statement) transaction.commit_unless_managed(using=db) diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 34f8041d33..1896e53cee 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -14,7 +14,7 @@ from django.core.management.color import no_style from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS, IntegrityError, DatabaseError) from django.db.models import get_apps -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from itertools import product try: @@ -189,7 +189,7 @@ class Command(BaseCommand): 'app_label': obj.object._meta.app_label, 'object_name': obj.object._meta.object_name, 'pk': obj.object.pk, - 'error_msg': force_unicode(e) + 'error_msg': force_text(e) },) raise diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 19886f7d53..78a01c7098 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -5,7 +5,7 @@ Module for abstract serializer/unserializer base classes. from io import BytesIO from django.db import models -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils import six class SerializerDoesNotExist(KeyError): diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index 8b56d0e7b8..3bac24d33a 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -12,7 +12,7 @@ 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_str +from django.utils.encoding import smart_bytes from django.utils import six from django.utils.timezone import is_aware diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 83c6eb6739..348ff1dada 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -8,7 +8,7 @@ from __future__ import unicode_literals from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS -from django.utils.encoding import smart_unicode, is_protected_type +from django.utils.encoding import smart_text, is_protected_type from django.utils import six class Serializer(base.Serializer): @@ -34,8 +34,8 @@ class Serializer(base.Serializer): def get_dump_object(self, obj): return { - "pk": smart_unicode(obj._get_pk_val(), strings_only=True), - "model": smart_unicode(obj._meta), + "pk": smart_text(obj._get_pk_val(), strings_only=True), + "model": smart_text(obj._meta), "fields": self._current } @@ -65,7 +65,7 @@ class Serializer(base.Serializer): if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'): m2m_value = lambda value: value.natural_key() else: - m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True) + m2m_value = lambda value: smart_text(value._get_pk_val(), strings_only=True) self._current[field.name] = [m2m_value(related) for related in getattr(obj, field.name).iterator()] @@ -90,7 +90,7 @@ def Deserializer(object_list, **options): # Handle each field for (field_name, field_value) in six.iteritems(d["fields"]): if isinstance(field_value, str): - field_value = smart_unicode(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True) + field_value = smart_text(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True) field = Model._meta.get_field(field_name) @@ -101,9 +101,9 @@ def Deserializer(object_list, **options): if hasattr(value, '__iter__'): return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk else: - return smart_unicode(field.rel.to._meta.pk.to_python(value)) + return smart_text(field.rel.to._meta.pk.to_python(value)) else: - m2m_convert = lambda v: smart_unicode(field.rel.to._meta.pk.to_python(v)) + m2m_convert = lambda v: smart_text(field.rel.to._meta.pk.to_python(v)) m2m_data[field.name] = [m2m_convert(pk) for pk in field_value] # Handle FK fields diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index ac0e6cf82d..9be1ea4492 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -12,7 +12,7 @@ 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_str +from django.utils.encoding import smart_bytes from django.utils import six diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 9d9c023b64..c4e4dd189e 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -8,7 +8,7 @@ from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS from django.utils.xmlutils import SimplerXMLGenerator -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from xml.dom import pulldom class Serializer(base.Serializer): @@ -46,11 +46,11 @@ class Serializer(base.Serializer): self.indent(1) obj_pk = obj._get_pk_val() if obj_pk is None: - attrs = {"model": smart_unicode(obj._meta),} + attrs = {"model": smart_text(obj._meta),} else: attrs = { - "pk": smart_unicode(obj._get_pk_val()), - "model": smart_unicode(obj._meta), + "pk": smart_text(obj._get_pk_val()), + "model": smart_text(obj._meta), } self.xml.startElement("object", attrs) @@ -96,10 +96,10 @@ class Serializer(base.Serializer): # Iterable natural keys are rolled out as subelements for key_value in related: self.xml.startElement("natural", {}) - self.xml.characters(smart_unicode(key_value)) + self.xml.characters(smart_text(key_value)) self.xml.endElement("natural") else: - self.xml.characters(smart_unicode(related_att)) + self.xml.characters(smart_text(related_att)) else: self.xml.addQuickElement("None") self.xml.endElement("field") @@ -120,13 +120,13 @@ class Serializer(base.Serializer): self.xml.startElement("object", {}) for key_value in natural: self.xml.startElement("natural", {}) - self.xml.characters(smart_unicode(key_value)) + self.xml.characters(smart_text(key_value)) self.xml.endElement("natural") self.xml.endElement("object") else: def handle_m2m(value): self.xml.addQuickElement("object", attrs={ - 'pk' : smart_unicode(value._get_pk_val()) + 'pk' : smart_text(value._get_pk_val()) }) for relobj in getattr(obj, field.name).iterator(): handle_m2m(relobj) @@ -141,7 +141,7 @@ class Serializer(base.Serializer): self.xml.startElement("field", { "name" : field.name, "rel" : field.rel.__class__.__name__, - "to" : smart_unicode(field.rel.to._meta), + "to" : smart_text(field.rel.to._meta), }) class Deserializer(base.Deserializer): diff --git a/django/core/signing.py b/django/core/signing.py index cd9759e536..9ab8c5b8b0 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -41,7 +41,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_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.importlib import import_module @@ -135,7 +135,7 @@ def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, ma """ Reverse of dumps(), raises BadSignature if signature fails """ - base64d = smart_str( + base64d = smart_bytes( TimestampSigner(key, salt=salt).unsign(s, max_age=max_age)) decompress = False if base64d[0] == '.': @@ -159,16 +159,16 @@ class Signer(object): return base64_hmac(self.salt + 'signer', value, self.key) def sign(self, value): - value = smart_str(value) + value = smart_bytes(value) return '%s%s%s' % (value, self.sep, self.signature(value)) def unsign(self, signed_value): - signed_value = smart_str(signed_value) + signed_value = smart_bytes(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) if constant_time_compare(sig, self.signature(value)): - return force_unicode(value) + return force_text(value) raise BadSignature('Signature "%s" does not match' % sig) @@ -178,7 +178,7 @@ class TimestampSigner(Signer): return baseconv.base62.encode(int(time.time())) def sign(self, value): - value = smart_str('%s%s%s' % (value, self.sep, self.timestamp())) + value = smart_bytes('%s%s%s' % (value, self.sep, self.timestamp())) return '%s%s%s' % (value, self.sep, self.signature(value)) def unsign(self, value, max_age=None): diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index c17168f8cb..2fe744e8eb 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_unicode, smart_str +from django.utils.encoding import iri_to_uri, force_text, smart_bytes from django.utils.functional import memoize, lazy from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule @@ -163,7 +163,7 @@ class LocaleRegexProvider(object): if isinstance(self._regex, six.string_types): regex = self._regex else: - regex = force_unicode(self._regex) + regex = force_text(self._regex) try: compiled_regex = re.compile(regex, re.UNICODE) except re.error as e: @@ -190,7 +190,7 @@ class RegexURLPattern(LocaleRegexProvider): self.name = name def __repr__(self): - return smart_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) + return smart_bytes('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) def add_prefix(self, prefix): """ @@ -240,7 +240,7 @@ class RegexURLResolver(LocaleRegexProvider): self._app_dict = {} def __repr__(self): - return smart_str('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) + return smart_bytes('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) def _populate(self): lookups = MultiValueDict() @@ -373,7 +373,7 @@ class RegexURLResolver(LocaleRegexProvider): if args: if len(args) != len(params) + len(prefix_args): continue - unicode_args = [force_unicode(val) for val in args] + unicode_args = [force_text(val) for val in args] candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args)) else: if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args): @@ -385,7 +385,7 @@ class RegexURLResolver(LocaleRegexProvider): break if not matches: continue - unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()]) + unicode_kwargs = dict([(k, force_text(v)) for (k, v) in kwargs.items()]) candidate = (prefix_norm + result) % unicode_kwargs if re.search('^%s%s' % (_prefix, pattern), candidate, re.UNICODE): return candidate diff --git a/django/core/validators.py b/django/core/validators.py index 91d6f62dcf..fd5dfa28d6 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_unicode +from django.utils.encoding import smart_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_unicode(value)): + if not self.regex.search(smart_text(value)): raise ValidationError(self.message, code=self.code) class URLValidator(RegexValidator): @@ -54,7 +54,7 @@ class URLValidator(RegexValidator): except ValidationError as e: # Trivial case failed. Try for possible IDN domain if value: - value = smart_unicode(value) + value = smart_text(value) scheme, netloc, path, query, fragment = urlsplit(value) try: netloc = netloc.encode('idna') # IDN -> ACE diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 6e23ad5bb5..9606245162 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -607,16 +607,16 @@ class BaseDatabaseOperations(object): exists for database backends to provide a better implementation according to their own quoting schemes. """ - from django.utils.encoding import smart_unicode, force_unicode + from django.utils.encoding import smart_text, force_text # Convert params to contain Unicode values. - to_unicode = lambda s: force_unicode(s, strings_only=True, errors='replace') + to_unicode = lambda s: force_text(s, strings_only=True, errors='replace') if isinstance(params, (list, tuple)): u_params = tuple([to_unicode(val) for val in params]) else: u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()]) - return smart_unicode(sql) % u_params + return smart_text(sql) % u_params def last_insert_id(self, cursor, table_name, pk_name): """ @@ -800,8 +800,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_unicode - return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") + from django.utils.encoding import smart_text + return smart_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. diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index b08113fed7..0f16130477 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -53,7 +53,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_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils import six from django.utils import timezone @@ -64,9 +64,9 @@ IntegrityError = Database.IntegrityError # Check whether cx_Oracle was compiled with the WITH_UNICODE option. This will # also be True in Python 3.0. if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'): - convert_unicode = force_unicode + convert_unicode = force_text else: - convert_unicode = smart_str + convert_unicode = smart_bytes class DatabaseFeatures(BaseDatabaseFeatures): @@ -162,7 +162,7 @@ WHEN (new.%(col_name)s IS NULL) if isinstance(value, Database.LOB): value = value.read() if field and field.get_internal_type() == 'TextField': - value = force_unicode(value) + value = force_text(value) # Oracle stores empty strings as null. We need to undo this in # order to adhere to the Django convention of using the empty @@ -245,7 +245,7 @@ WHEN (new.%(col_name)s IS NULL) def process_clob(self, value): if value is None: return '' - return force_unicode(value.read()) + return force_text(value.read()) def quote_name(self, name): # SQL92 requires delimited (quoted) names to be case-sensitive. When @@ -595,9 +595,9 @@ class OracleParam(object): param = param.astimezone(timezone.utc).replace(tzinfo=None) if hasattr(param, 'bind_parameter'): - self.smart_str = param.bind_parameter(cursor) + self.smart_bytes = param.bind_parameter(cursor) else: - self.smart_str = convert_unicode(param, cursor.charset, + self.smart_bytes = convert_unicode(param, cursor.charset, strings_only) if hasattr(param, 'input_size'): # If parameter has `input_size` attribute, use that. @@ -676,7 +676,7 @@ class FormatStylePlaceholderCursor(object): self.setinputsizes(*sizes) def _param_generator(self, params): - return [p.smart_str for p in params] + return [p.smart_bytes for p in params] def execute(self, query, params=None): if params is None: @@ -831,7 +831,7 @@ def to_unicode(s): unchanged). """ if isinstance(s, six.string_types): - return force_unicode(s) + return force_text(s) return s diff --git a/django/db/models/base.py b/django/db/models/base.py index a25c106290..4568430bfa 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_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils import six from django.utils.text import get_text_list, capfirst @@ -380,11 +380,11 @@ class Model(six.with_metaclass(ModelBase, object)): u = six.text_type(self) except (UnicodeEncodeError, UnicodeDecodeError): u = '[Bad Unicode data]' - return smart_str('<%s: %s>' % (self.__class__.__name__, u)) + return smart_bytes('<%s: %s>' % (self.__class__.__name__, u)) def __str__(self): if hasattr(self, '__unicode__'): - return force_unicode(self).encode('utf-8') + return force_text(self).encode('utf-8') return '%s object' % self.__class__.__name__ def __eq__(self, other): @@ -605,14 +605,14 @@ class Model(six.with_metaclass(ModelBase, object)): def _get_FIELD_display(self, field): value = getattr(self, field.attname) - return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True) + return force_text(dict(field.flatchoices).get(value, value), strings_only=True) def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs): if not self.pk: 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_str(getattr(self, field.attname)) + param = smart_bytes(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) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index de24a24ed1..2c738d6a20 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -19,7 +19,7 @@ from django.utils.functional import curry, total_ordering from django.utils.text import capfirst from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.ipv6 import clean_ipv6_address from django.utils import six @@ -386,7 +386,7 @@ class Field(object): if self.has_default(): if callable(self.default): return self.default() - return force_unicode(self.default, strings_only=True) + return force_text(self.default, strings_only=True) if (not self.empty_strings_allowed or (self.null and not connection.features.interprets_empty_strings_as_nulls)): return None @@ -404,11 +404,11 @@ class Field(object): rel_model = self.rel.to if hasattr(self.rel, 'get_related_field'): lst = [(getattr(x, self.rel.get_related_field().attname), - smart_unicode(x)) + smart_text(x)) for x in rel_model._default_manager.complex_filter( self.rel.limit_choices_to)] else: - lst = [(x._get_pk_val(), smart_unicode(x)) + lst = [(x._get_pk_val(), smart_text(x)) for x in rel_model._default_manager.complex_filter( self.rel.limit_choices_to)] return first_choice + lst @@ -435,7 +435,7 @@ class Field(object): Returns a string value of this field from the passed obj. This is used by the serialization framework. """ - return smart_unicode(self._get_val_from_obj(obj)) + return smart_text(self._get_val_from_obj(obj)) def bind(self, fieldmapping, original, bound_field_class): return bound_field_class(self, fieldmapping, original) @@ -629,7 +629,7 @@ class CharField(Field): def to_python(self, value): if isinstance(value, six.string_types) or value is None: return value - return smart_unicode(value) + return smart_text(value) def get_prep_value(self, value): return self.to_python(value) @@ -1189,7 +1189,7 @@ class TextField(Field): def get_prep_value(self, value): if isinstance(value, six.string_types) or value is None: return value - return smart_unicode(value) + return smart_text(value) def formfield(self, **kwargs): defaults = {'widget': forms.Textarea} diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index b51ef1d5d6..ad4c36ca0d 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_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes 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_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) + return os.path.normpath(force_text(datetime.datetime.now().strftime(smart_bytes(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/related.py b/django/db/models/fields/related.py index bfa8feee9f..eaa62c6061 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -9,7 +9,7 @@ from django.db.models.related import RelatedObject from django.db.models.query import QuerySet from django.db.models.query_utils import QueryWrapper from django.db.models.deletion import CASCADE -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils import six from django.utils.translation import ugettext_lazy as _, string_concat from django.utils.functional import curry, cached_property @@ -999,7 +999,7 @@ class ForeignKey(RelatedField, Field): if not self.blank and self.choices: choice_list = self.get_choices_default() if len(choice_list) == 2: - return smart_unicode(choice_list[1][0]) + return smart_text(choice_list[1][0]) return Field.value_to_string(self, obj) def contribute_to_class(self, cls, name): @@ -1205,7 +1205,7 @@ class ManyToManyField(RelatedField, Field): choices_list = self.get_choices_default() if len(choices_list) == 1: data = [choices_list[0][0]] - return smart_unicode(data) + return smart_text(data) def contribute_to_class(self, cls, name): # To support multiple relations to self, it's useful to have a non-None diff --git a/django/db/models/options.py b/django/db/models/options.py index 9e8d4120e9..239ad30b06 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -8,7 +8,7 @@ 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_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.datastructures import SortedDict from django.utils import six @@ -199,7 +199,7 @@ class Options(object): return '' % self.object_name def __str__(self): - return "%s.%s" % (smart_str(self.app_label), smart_str(self.module_name)) + return "%s.%s" % (smart_bytes(self.app_label), smart_bytes(self.module_name)) def verbose_name_raw(self): """ @@ -209,7 +209,7 @@ class Options(object): """ lang = get_language() deactivate_all() - raw = force_unicode(self.verbose_name) + raw = force_text(self.verbose_name) activate(lang) return raw verbose_name_raw = property(verbose_name_raw) diff --git a/django/db/models/related.py b/django/db/models/related.py index 90995d749f..a0dcec7132 100644 --- a/django/db/models/related.py +++ b/django/db/models/related.py @@ -1,4 +1,4 @@ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.db.models.fields import BLANK_CHOICE_DASH class BoundRelatedObject(object): @@ -34,9 +34,9 @@ class RelatedObject(object): if limit_to_currently_related: queryset = queryset.complex_filter( {'%s__isnull' % self.parent_model._meta.module_name: False}) - lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset] + lst = [(x._get_pk_val(), smart_text(x)) for x in queryset] return first_choice + lst - + def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): # Defer to the actual field definition for db prep return self.field.get_db_prep_lookup(lookup_type, value, diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 9cf732f263..69dda228bd 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -10,7 +10,7 @@ all about the internals of models in order to get the information it needs. import copy from django.utils.datastructures import SortedDict -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.tree import Node from django.utils import six from django.db import connections, DEFAULT_DB_ALIAS @@ -1776,7 +1776,7 @@ class Query(object): else: param_iter = iter([]) for name, entry in select.items(): - entry = force_unicode(entry) + entry = force_text(entry) entry_params = [] pos = entry.find("%s") while pos != -1: diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index cc7da0eeaf..937505b9b0 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -10,7 +10,7 @@ from django.db.models.sql.query import Query from django.db.models.sql.where import AND, Constraint from django.utils.datastructures import SortedDict from django.utils.functional import Promise -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils import six @@ -105,7 +105,7 @@ class UpdateQuery(Query): saving models. """ # Check that no Promise object passes to the query. Refs #10498. - values_seq = [(value[0], value[1], force_unicode(value[2])) + values_seq = [(value[0], value[1], force_text(value[2])) if isinstance(value[2], Promise) else value for value in values_seq] self.values.extend(values_seq) @@ -171,7 +171,7 @@ class InsertQuery(Query): for obj in objs: value = getattr(obj, field.attname) if isinstance(value, Promise): - setattr(obj, field.attname, force_unicode(value)) + setattr(obj, field.attname, force_text(value)) self.objs = objs self.raw = raw diff --git a/django/forms/fields.py b/django/forms/fields.py index cdb1d7be67..7f0d26d1aa 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -23,7 +23,7 @@ from django.forms.widgets import (TextInput, PasswordInput, HiddenInput, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION) from django.utils import formats -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.ipv6 import clean_ipv6_address from django.utils import six from django.utils.translation import ugettext_lazy as _ @@ -78,13 +78,13 @@ class Field(object): # validators -- List of addtional validators to use # localize -- Boolean that specifies if the field should be localized. if label is not None: - label = smart_unicode(label) + label = smart_text(label) self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial if help_text is None: self.help_text = '' else: - self.help_text = smart_unicode(help_text) + self.help_text = smart_text(help_text) widget = widget or self.widget if isinstance(widget, type): widget = widget() @@ -195,7 +195,7 @@ class CharField(Field): "Returns a Unicode object." if value in validators.EMPTY_VALUES: return '' - return smart_unicode(value) + return smart_text(value) def widget_attrs(self, widget): attrs = super(CharField, self).widget_attrs(widget) @@ -288,7 +288,7 @@ class DecimalField(Field): return None if self.localize: value = formats.sanitize_separators(value) - value = smart_unicode(value).strip() + value = smart_text(value).strip() try: value = Decimal(value) except DecimalException: @@ -333,7 +333,7 @@ class BaseTemporalField(Field): def to_python(self, value): # Try to coerce the value to unicode. - unicode_value = force_unicode(value, strings_only=True) + unicode_value = force_text(value, strings_only=True) if isinstance(unicode_value, six.text_type): value = unicode_value.strip() # If unicode, try to strptime against each input format. @@ -692,7 +692,7 @@ class ChoiceField(Field): "Returns a Unicode object." if value in validators.EMPTY_VALUES: return '' - return smart_unicode(value) + return smart_text(value) def validate(self, value): """ @@ -708,10 +708,10 @@ class ChoiceField(Field): if isinstance(v, (list, tuple)): # This is an optgroup, so look inside the group for options for k2, v2 in v: - if value == smart_unicode(k2): + if value == smart_text(k2): return True else: - if value == smart_unicode(k): + if value == smart_text(k): return True return False @@ -752,7 +752,7 @@ class MultipleChoiceField(ChoiceField): return [] elif not isinstance(value, (list, tuple)): raise ValidationError(self.error_messages['invalid_list']) - return [smart_unicode(val) for val in value] + return [smart_text(val) for val in value] def validate(self, value): """ diff --git a/django/forms/forms.py b/django/forms/forms.py index 0f3fdb2e40..45b758202a 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_unicode, force_unicode +from django.utils.encoding import StrAndUnicode, smart_text, force_text from django.utils.safestring import mark_safe from django.utils import six @@ -150,7 +150,7 @@ class BaseForm(StrAndUnicode): bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. if bf.is_hidden: if bf_errors: - top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) + top_errors.extend(['(Hidden field %s) %s' % (name, force_text(e)) for e in bf_errors]) hidden_fields.append(six.text_type(bf)) else: # Create a 'class="..."' atribute if the row should have any @@ -160,10 +160,10 @@ class BaseForm(StrAndUnicode): html_class_attr = ' class="%s"' % css_classes if errors_on_separate_row and bf_errors: - output.append(error_row % force_unicode(bf_errors)) + output.append(error_row % force_text(bf_errors)) if bf.label: - label = conditional_escape(force_unicode(bf.label)) + label = conditional_escape(force_text(bf.label)) # Only add the suffix if the label does not end in # punctuation. if self.label_suffix: @@ -174,20 +174,20 @@ class BaseForm(StrAndUnicode): label = '' if field.help_text: - help_text = help_text_html % force_unicode(field.help_text) + help_text = help_text_html % force_text(field.help_text) else: help_text = '' output.append(normal_row % { - 'errors': force_unicode(bf_errors), - 'label': force_unicode(label), + 'errors': force_text(bf_errors), + 'label': force_text(label), 'field': six.text_type(bf), 'help_text': help_text, 'html_class_attr': html_class_attr }) if top_errors: - output.insert(0, error_row % force_unicode(top_errors)) + output.insert(0, error_row % force_text(top_errors)) if hidden_fields: # Insert any hidden fields in the last row. str_hidden = ''.join(hidden_fields) @@ -535,8 +535,8 @@ class BoundField(StrAndUnicode): associated Form has specified auto_id. Returns an empty string otherwise. """ auto_id = self.form.auto_id - if auto_id and '%s' in smart_unicode(auto_id): - return smart_unicode(auto_id) % self.html_name + if auto_id and '%s' in smart_text(auto_id): + return smart_text(auto_id) % self.html_name elif auto_id: return self.html_name return '' diff --git a/django/forms/models.py b/django/forms/models.py index a2b5448b14..80d2a6536f 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -13,7 +13,7 @@ from django.forms.formsets import BaseFormSet, formset_factory from django.forms.util import ErrorList from django.forms.widgets import (SelectMultiple, HiddenInput, MultipleHiddenInput, media_property) -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.datastructures import SortedDict from django.utils import six from django.utils.text import get_text_list, capfirst @@ -875,7 +875,7 @@ class InlineForeignKeyField(Field): orig = getattr(self.parent_instance, self.to_field) else: orig = self.parent_instance.pk - if force_unicode(value) != force_unicode(orig): + if force_text(value) != force_text(orig): raise ValidationError(self.error_messages['invalid_choice']) return self.parent_instance @@ -953,7 +953,7 @@ class ModelChoiceField(ChoiceField): generate the labels for the choices presented by this object. Subclasses can override this method to customize the display of the choices. """ - return smart_unicode(obj) + return smart_text(obj) def _get_choices(self): # If self._choices is set, then somebody must have manually set @@ -1025,9 +1025,9 @@ class ModelMultipleChoiceField(ModelChoiceField): except ValueError: raise ValidationError(self.error_messages['invalid_pk_value'] % pk) qs = self.queryset.filter(**{'%s__in' % key: value}) - pks = set([force_unicode(getattr(o, key)) for o in qs]) + pks = set([force_text(getattr(o, key)) for o in qs]) for val in value: - if force_unicode(val) not in pks: + if force_text(val) not in pks: raise ValidationError(self.error_messages['invalid_choice'] % val) # Since this overrides the inherited ModelChoiceField.clean # we run custom validators here diff --git a/django/forms/util.py b/django/forms/util.py index 8cf03d38af..cd6b52df6f 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_unicode +from django.utils.encoding import StrAndUnicode, force_text from django.utils.safestring import mark_safe from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -35,12 +35,12 @@ class ErrorDict(dict, StrAndUnicode): if not self: return '' return format_html('
        {0}
      ', format_html_join('', '
    • {0}{1}
    • ', - ((k, force_unicode(v)) + ((k, force_text(v)) for k, v in self.items()) )) def as_text(self): - return '\n'.join(['* %s\n%s' % (k, '\n'.join([' * %s' % force_unicode(i) for i in v])) for k, v in self.items()]) + 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): """ @@ -53,16 +53,16 @@ class ErrorList(list, StrAndUnicode): if not self: return '' return format_html('
        {0}
      ', format_html_join('', '
    • {0}
    • ', - ((force_unicode(e),) for e in self) + ((force_text(e),) for e in self) ) ) def as_text(self): if not self: return '' - return '\n'.join(['* %s' % force_unicode(e) for e in self]) + return '\n'.join(['* %s' % force_text(e) for e in self]) def __repr__(self): - return repr([force_unicode(e) for e in self]) + return repr([force_text(e) for e in self]) # Utilities for time zone support in DateTimeField et al. diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 13b7d8e7f6..be9ac8eb8f 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -17,7 +17,7 @@ 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_unicode +from django.utils.encoding import StrAndUnicode, force_text from django.utils.safestring import mark_safe from django.utils import six from django.utils import datetime_safe, formats @@ -223,7 +223,7 @@ class Widget(six.with_metaclass(MediaDefiningClass)): initial_value = '' else: initial_value = initial - if force_unicode(initial_value) != force_unicode(data_value): + if force_text(initial_value) != force_text(data_value): return True return False @@ -257,7 +257,7 @@ class Input(Widget): final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) if value != '': # Only add the 'value' attribute if a value is non-empty. - final_attrs['value'] = force_unicode(self._format_value(value)) + final_attrs['value'] = force_text(self._format_value(value)) return format_html('', flatatt(final_attrs)) class TextInput(Input): @@ -294,7 +294,7 @@ class MultipleHiddenInput(HiddenInput): id_ = final_attrs.get('id', None) inputs = [] for i, v in enumerate(value): - input_attrs = dict(value=force_unicode(v), **final_attrs) + input_attrs = dict(value=force_text(v), **final_attrs) if id_: # An ID attribute was given. Add a numeric index as a suffix # so that the inputs don't all have the same ID attribute. @@ -361,7 +361,7 @@ class ClearableFileInput(FileInput): template = self.template_with_initial substitutions['initial'] = format_html('{1}', value.url, - force_unicode(value)) + force_text(value)) if not self.is_required: checkbox_name = self.clear_checkbox_name(name) checkbox_id = self.clear_checkbox_id(checkbox_name) @@ -398,7 +398,7 @@ class Textarea(Widget): final_attrs = self.build_attrs(attrs, name=name) return format_html('{1}', flatatt(final_attrs), - force_unicode(value)) + force_text(value)) class DateInput(Input): input_type = 'text' @@ -515,7 +515,7 @@ class CheckboxInput(Widget): final_attrs['checked'] = 'checked' if not (value is True or value is False or value is None or value == ''): # Only add the 'value' attribute if a value is non-empty. - final_attrs['value'] = force_unicode(value) + final_attrs['value'] = force_text(value) return format_html('', flatatt(final_attrs)) def value_from_datadict(self, data, files, name): @@ -556,7 +556,7 @@ class Select(Widget): return mark_safe('\n'.join(output)) def render_option(self, selected_choices, option_value, option_label): - option_value = force_unicode(option_value) + option_value = force_text(option_value) if option_value in selected_choices: selected_html = mark_safe(' selected="selected"') if not self.allow_multiple_selected: @@ -567,15 +567,15 @@ class Select(Widget): return format_html('', option_value, selected_html, - force_unicode(option_label)) + force_text(option_label)) def render_options(self, choices, selected_choices): # Normalize to strings. - selected_choices = set(force_unicode(v) for v in selected_choices) + selected_choices = set(force_text(v) for v in selected_choices) output = [] for option_value, option_label in chain(self.choices, choices): if isinstance(option_label, (list, tuple)): - output.append(format_html('', force_unicode(option_value))) + output.append(format_html('', force_text(option_value))) for option in option_label: output.append(self.render_option(selected_choices, *option)) output.append('') @@ -643,8 +643,8 @@ class SelectMultiple(Select): data = [] if len(initial) != len(data): return True - initial_set = set([force_unicode(value) for value in initial]) - data_set = set([force_unicode(value) for value in data]) + initial_set = set([force_text(value) for value in initial]) + data_set = set([force_text(value) for value in data]) return data_set != initial_set class RadioInput(SubWidget): @@ -656,8 +656,8 @@ class RadioInput(SubWidget): def __init__(self, name, value, attrs, choice, index): self.name, self.value = name, value self.attrs = attrs - self.choice_value = force_unicode(choice[0]) - self.choice_label = force_unicode(choice[1]) + self.choice_value = force_text(choice[0]) + self.choice_label = force_text(choice[1]) self.index = index def __unicode__(self): @@ -671,7 +671,7 @@ class RadioInput(SubWidget): label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index) else: label_for = '' - choice_label = force_unicode(self.choice_label) + choice_label = force_text(self.choice_label) return format_html('{1} {2}', label_for, self.tag(), choice_label) def is_checked(self): @@ -709,7 +709,7 @@ class RadioFieldRenderer(StrAndUnicode): """Outputs a

      J*;AJZ!q>rlc-QO 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 diff --git a/docs/ref/contrib/admin/_images/user_actions.png b/docs/ref/contrib/admin/_images/user_actions.png index fdbe2ad897950b0a6cee133a76155b6c299aaf5e..22d40e0181e5ec0acfacb709b878aa982fc72f3e 100644 GIT binary patch literal 35765 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 bzaUIVhHxHlwNKE@D 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 HEvrKOV7T9rxy*vJ=*-~34WUR}l}O{L zI%{{3O)m|$=3E+TZsJ_FkkiKu6M%m_|E5S(s27-e3y0r58>NA!6=l;z!wgh&63YGksK(?jho~nfv>ndmU=<>v7&kzckjHJsr_VVN;^~Ydr zcd_nufsDVQ05$>+eC^kW4Yqn{zVIpBtMj>(tn)fYU)aKxGotcliY!D;ub*Y7c8^2@j6Z~tQEo7 z^*2yol?}RQXadA74a5c#5EW@iiN7n)P?;&D~9Y46@&M3RC8 z!dow7fC>sN;m!32m7vwfZVT|_<5PX&aW%CGtuq9F>EkdJmvNiH7XF#;K~cTgJJ{lc zzxg?_M$GnXvvIN3Sk}3Ry^4M-9T1=`I)z=M(Lv;Moc@y466vzvZAl`t-F`W;Wi1_d zS1j}fcH&HF`ed`!>dH*?hr>ESK#mS!#H zg3WM$K1u4zW{&AZY!63#~fc6Tyk|Zp78O2IC60RX*aBm`eRdAif z)q7~t5DUfV;?=)Dla~jtud1uF-8$ueBaB;mE(en8^ znOTguYZ{AjaBc>nht?t!EHW#U8A6h>;9bOvPmj+xnRK#O`x}?>8}hrkYVcN!4sV0f zcDA+bY=Ff?!APL0$ljKlC*lWs!oeNT1dgmWm?lXY?+-C%f_6-OzCSA|DjG3q zn3+-Nkx)~^>T30r+<=2S&Gak$G$?>*@E<6FN%R4!@gkPgM*eHX@B2}uD$#&z9Bv*S z4vyp3ZYj~F8izzvH5#N6C0Ph#<6IZuJks37vOQ(fb+Gr6FMLjJxJU$(m3M z4kD6XIpa`@?{euVReASHGdalw8<8e+e>EGrFkyMpKs(fy{$dQq#I9~pFPq15&qnJg z7VWG>b$WErN)w8-<_c^IDFkNbN~7iNBze)t?lH3*?-fbBIhUYdu}D-cB>yEK%SZBR zJ)G3H{5^2%z-VwDfD!uhjCncZrj1FP4;l3+4$spkdqoncB$?kJT6UxmhyMm2=^Y`2 zv!Y%qQ!F;O5kf=lhjD5x{@hTS_8?aVh7e+ri)`GLo+Z%25mbUcQM=OnLuBrcCSS#S z`9#Rb$wASBgM+;|2mYgC3swIJB>~^*K`9?X$$qcKQXtxLPpy#E1?s|Z4wD%(zmW!H z+EU6mBYvZY6+49ZSlI;n!N;RFsR++#s}vRStI-3JfFTXLfqn_h{Wiqa&O}Cl5!}V3 zLGNWu$)cA39qX}S)S8;Q3?5!uZN|;x0n*Y!(uf_F!Ir}@xB(}Nm;#O}JCkq18#qYT zAbPOiU@QQ3-QP-B3xO~b5D)+Y0sWP8@Yb8wvlnVO#@T0vNbTlF^_g36OAX&|A9Fn7qxDl>#V%XvFUxc~MHQ z+wkEW{=8P}qeP~KbmqV&^e}DO%EM#@81nZV(J@v(TUNd)wlXVtFIk_=nka%}H!xv= z_*xAT&BhX%8caKdOpC;b|6`#PAZR~IO$~9)zLEk5wO9B%TefZ(+NMf~v_L77%p+>Z zoGL41jGhc%H972b|*(>_%j5VD0djPgXk%K>uZegv@3U+8J#w|J`)^VjL9WvjXT>O8Hy<3SRQ! z0}!^qyy4iS-u1sp6EX5Xp96QBU+>%GY(}dzvW{-PEh1W>h^MRaPOQ zw3OjLq4^>$n+;u%X>TgE^lBwvh@&5OP|Yh(F{bLa0O1vo@lU{k@{9IMBdb6GG0v(j zDkW3Z9Gmj;As!0`{r7@ zF^Il9%*GSD?X*SzqQAD^mcP9i^X*OlgEihBEc;Kq)+mH?H$x*u|DFyieE@n>kiOfm z7lD3>KFf;~uPfB~^1Dr@!=$KK6+$q$k|_t0ibOO`7YxMs(wWJo&=t}0 z`gGUE29pRsjCYf}w#dC2x~OochHrm*L!~#=cc8hc?GgI&Jdm$3)tA_PF#Eif^WcDx z^{Vg>qN*)-4)wFgV1wOzTIS4hd%NBw`%}+ULt1D>>BB=W^EU|}O~9!%iOp>Bk1l>j zSorR`9_EWy-@B<@@T}KaAcQ}uC3|)%(sf{FI0P%x6Z4$b)DkjBaW*!F*1_tg zwVMuy?L2e0BHwrv*=NT<8Ev@w>`vzCxciFh95}~Ea1f(e8k?Qi_jp71#vMnNS@@;M z(Mc6rtaid=q8WL9eFZF!HP+mQ*eFmlf+^EQ_(~CThQ^9$%}uMm0`xgtboTI9Q7kyzA-Z2lf zv`T4Xj6?H9Yr3f^WR6~kd&KfZFM)qdNq(&Q1 zQ(wQZ7tRI>y^)62y>r9jE6K|0*PC@|0`ov~e*@CeP|u)oRL?T(i*qp7&`- zL}%DFv_v@O4k*dYFyu5_mfBi7xKM-3`TsuicuX$_$G77%iO-9fibAV7Vtc&YG~A7D z7kp)htn^`YP?FsC3-iY8rIWCDlFk(Eye@|j3Wi+yArXd+acHPVw*Z)cub|QS-T5XX z!HG!C`j6}WcXZHlEm#(gP}S=m8H2nWiWs`ry_|+R-wA#F7&oMh1BI1VH zN4za<7`9`(ws-O4t$m+43;>4P^(#F+RxkUaoo%fCr=kPh6{d)XalxOdB^^k=b=c-@Mt>sm)geKS_)QH z)3L<(RXtu?u55F6V|P6E?f@X$`$kN0o7bV0#f(>Tz}MvFM+WOna97Frw0r>$hV9NZ zJsVu+T*KVoaZEi)Dco5_kPtf&o-kDXqS z$L@*mH7Zw=*WasJP`$3Eqv*Z2HpT`)jF_AFTx1*o@BA|!w};E?u-V|<{th%|0(xcl zNxS=V^D{2MhxLPJ^&eiw@>KZe>#$R1RhQ>(PV4%8@rUzGN-)7U$91QY>&v{@QwU&a zJ$lRj#%v+CV^ykv$&Pdti1)6+-LmAP(Q>-CAcOA|EXO}eYu?pBaa`|t@M@#&?5gj! zLT*o^or2!vF_Ptx{Oa9#_a*CIRBi=PWsBvoDfMv{Gjt6755Q2h1c1fiHIY&9;bQZT zEa4znZ(H5I-FKjOB{<<7vJU+`{7Sg`W4p5%r;&`*As{{&J^b{~r8#a-{~iuO(d{Ae!i42;tq%WqpwAcMSe#-hEb? zZB^TFO@jFqhHXuoT7|&6DC8G!8n8C0t1K<=DbkM4c-iZE24Xf(iznz>`_79@(ZGHZ z`1L!{AD_)=md^EHFdcQAtN_H)>p`0cY1{P$6}S0RJ^jPT_2JFT+r&WHg0sy-waMY+Jg3-w}!IwD$U{{G>%2^)F|m-m|V(f|^1$sX)gglaAZ|78ss@ zTRLUx!p`9%pun@Xnu$iAmLyQuSJa7XIS*e{ zAKqMRSo@(`7Lrb#(@L5P82LJPiJAoFV&ez^X|>;bLD5+Xtl0HlDltP-;)_#|Ss8!M z@erY@Y~~=$$4=WIh_kQd=6Y94=l=}H8?N{0o-C8v10Y=PJ?uP~7b@g*!r(1t=qizz zHuh#HW@}Xn`frv!A2&{VK85?Q$=y_(G2pdZuA`Q0#P3Id-tVgx=2Mn^v1+D`WT)^;;~FA16asKV;f3_Rke;75VoPnmfPpIZ z0A#!RQNhA5S}*U{PoN>tWhx{((Z@4pf2gQ|>!}on3s1&(9K6<>^uF9;3`Udd2KQykq3+kqcE$x!xWK66x-s zt(TWV(_F$~(Mscb8}7>e<%!eMx`Xe75DLD;t$)pNQ1sOJ33bN7x>v>wO42wdq87X_ z&+}+Aau1Gyw8VwX^kV0@J@=TajI-tH@>5v~Y;q=^<-QP>-RbK_eEc!{|L} zk6G~6b@XR7P!FQptTvTbS=u;C zl24^X!aMe7p~-Iy)e_>rMy9vX3`Itvsh>zEUSE+pZppKv*-2y(*@m&nIJ3^~f-AIY zc9cRc9^M(UUw_P+k~}8idSry&r9w5g+Di5mT)5UZu)K%hlKFc{Wx(@?RZC6ad8&;` z&6(_i#GhKo!1xtq8k3DbO7MD-xbI{oq^ze8a$alFIe2j z1Qxi(6+Tv5;C5)F`R>Uhf3fXNGZYLE>q7}**r2lb3F5qu7u}HL(;cpCd&cN2o{|FV zYzcTS)WBEDoW1BUz?qerm=X+ATEPg zwz@XsjygRjfu2`IsO|IuGur0S8t*B7;h&sR-_ZQ|bM~S8YmL29>m-GXxs5IS+v-hM zr{Lzae~kurzCmtVV06kFF4*rgD)KPwb`77XY+9?J?dqf6cG@P#xb3;^_TKCRmnvo+ z$Zw##;(Dx;vdsrpjc22dtY|CfQcJB!j7G}#Zk3A`FV1rL-(XJ77hGb{6WsNaZOB{i zY7=vF_wD$Ct~1WVoAcGkV~#g!isiD)hg;)kL-r=;Mcd(SLYL8P_IW_S&Osk{;njD! z9AKvRuL)vWJ8=*Pk7;M{d%KnzM-47wYp&yz&yCSpqAz;o3pq3yQOZmVdh~q;S24L! zC(pGRWBnT8Z=x}fKW)+keh5{=z}RhutKpWvKdj#26Fptk z+mireoV`09Z#F@r>(crzo~X6tZ)$G_b)#?+fr*lNC;>kI!z3X8F@@FPT99%R2b$i0 zYYX0wDYieiL=*(PyFgne)5@GPV43kJ3xTT#y;SWWkUq%3hgAPdAnr2TWyY$RNdG*Z zl8egc^zD!slM)O0&7&G;e(F$H+hOjC%N6$lx1f(Ln7~f?B)DdQmhO$4N3;)Mg}FkB zq)Oz!jiYLtUg?cT%SOvn-F=*)ZEXwa8*Bbi2pbEd-Zu zMH)Y*Ut6Xu8OF2m*0V07Pu%Ml^V_DcCuU2RV6hYFR?Uj_Il_7-%*uE#-6Qvwg+HEx za<+$ujR88TG?5dURi5t@lqLj3(wgerEhXEhWWAR3RJ?MJD4P{7fm)R8VrDbWRnNQ;}w z7($FoE*ndTsPVYG=ByV&?{vY!!0+?yWHEfo_h4qkeJ8l`BQZUxqyOnKQTolCIywQq zv?A{BQAo}IYG+|c%p#7|cDfkSu$maH)WVJcLWW~8hSVb%A)0>fzMJT9(O6fdGQP5! zW`TtPPgY<28MH7Sv>UAUXb(p*?C>|Vu`qGmSSK!xwc1i3h^x6SNiua*($RY}{)N#J z!(nq&5i2RZ7{OiS>$D^sjT%gq6AoA1SqEXE=w}7~o1UKKa4m}q01K0vG1XDd#mVX6 zwh)>D=3r`ykCoPL>2oE8?E6J{vkV60koUV?K3G4FB84k{uxZuKn^gw*ux|-W8-X%d z>5Tww__PKt?s$7FDR0SmU0#C0AQaT9-uZ$F7Dsoqy@=)ELY9E;WgqcQFF)Q7$c|%; zmyBH6m`##&g4VKT|KwodH${s%e{NT!vOXtnUD%*uSm7BRF&J98>Zqo}v*ydx@;%fB z*@Zi*==e{+BoOdY`#+=l`Xd00Sve}r*;9?=PE1qL953@Aw)&T?MimH~?M1457xvh# zAd-IQ>OJe8NYZ=9xx{$E59`au1KBV*X;6}2`M*3ZkVp^AkI$(N91{OO`sRPCMEHML z;5U$RdHnzJ!pa{O7>%Y+@m~}33lvN`o2)cXh1(lxT6lCm)8T%or!1Ta5I~VNMrHXwET&BDGlw8e0om7<(2x^ez65)3kM`O~YLDBz0((yoi z9)k+En%vC)GR4Lp?exA8O40ql9xD5Pyi;82vH#UjDL~{R{sS>12$TMsD-I{VInX^e z*eXzipgp^tnhVDe<@oQUNX2>%u$qVa495VzA~~{;w+?k=gI%v^o81+YVk{kJVHc}x z$yz9s8P&_DPXv@50tTvLOHHK1~UNifFqE(2T2W+A{Ts{&pwWCdM zI)cJ3F0UBxN_{6S5u?j{Kujxaja5E2vPa{tid1{Gw(BSY+}nHgLhU&%X^l2_Pte39 zkSi%<5iLDPU9@~SC|vyhY<6k$9z;M{tLiamT`HJW3zMd~1ie1{_jMFdSc72keHq^C zs0N??jPeyYk^=jN8^s5e1P2R$wMbjrv9@%pu~yLcoo_9x>7qjIP8uu34CQy1BsZ=ks2%e%k&d+4TCIZZo|&O-llvNPPw3L zyV?wT{Z-CVYCO6QczJ)J=2b)tB^Y97H*@N?p5*ds08=_!@n#s{2zvL(UO+3hvGZhv9?%Sb1ODvB2&YHJ{0k(zqK< ztOU5?Ybg={gK zA177hN0qNMN136PP=s|%8v;Q%N@A&@l&@@bAT8TaN(G*mwqF8#`aYzx+-DUl20-ZH zzi45JmcuKIA2EHZ5`XV_B#+{} zHpy*A@uYd@la;TI7C4%1y70;(<`@G{>!@jXX02-kH=Qj}Qp_D;nBS>r-nssM+Gw`+ z_0MANdShZM5j>B5e|8SXjmifrb7XOiGqU3=`H~SX=Y9;|oG34;J^5Cp?m1*>X_OEysZeewmYbM6n!m{Sg*U% z;hX_Q1Jify*!F#-;$EmINK8fP74Kxhd|b9vLrBUR(rk70YzJ9Gb6nu})tn4uTf?oN zf&NsZua{*hq3-XYySh%bHnZ&QEb&kRUwe0L822ZGk+Bg$Bq#Uq`A)-YOK&}D5TX3u z!{2FPvh*3r><2WJ9taH32+iKR78*K+U(=<%s7+8B_GI#ex&HOhh=R>>xM9WbKI^|% zvyI~DHlkSKHeP(?E{0JjM+tXSJZ9DqIvzf%6TV_2zr8?Jq_1D0QNUFnq(DroHIk!h zNKhi^d84mR_j{!0u-o^T;MMjRIa2~KGO3VokE>SyR-x;c_TLge7$r3<+=GCHbfm`QZiSciMhi%bnP z!j*9k*RjGnNz8KQ)MCE6`3AU>*~#E;gA$AghzI!vj1~D2x&HtqRCQYj7===woIxa z_C)&hT}))KU3;6lY)@SX7Ta{b_Ge)LY`a=+`=eIOB@*a)$`*r{YmY=LSx}wjiyDQs zkuU(3b~` zKI%~Eq}8Sqd&mKx-|Z5tdHvSQ()A`LQ1m@$A%9;7J(mi$nsLsdvPNPym36i-=Z`qO zm;u&io*i#(ZbI$~)lrmD5WjN7qV@LK3)ItZWo`c0#M`z zGdY{-X8fbFH*tC0O>-tI=n8FK40i5M`%&&{SEUnghr7ID1`3^L9miFJ@FwC?KG2@M zhUc;e)ezvdz1l5(pDUBZ3=pTTv*ILtd2ytv__%M4FE862-^C(m5-qO$+mPf}1AW&a&s`!(Qlzf39|6RC&K zahq%0S4Xb~Cl$jXU?6YKd?no(d3X10kQlW-t*JgNbgHH5Ro{HFSqQ#h0m{a>obAj| zqpMxvqkEdoA`W(UZ>TA)?#%8ZdG`!?1$!GW&Nf;&VtZAH$UTR0`iFf!YMu$}V|x1Pe-qR-t5;k+L<4ITrNyEjpfWX=IEYpXqRQa} z@Wy+eynQvheHB3r*J8~jdfZ6L!()Y_5;QLYkDyEv_jxmDu(lkA#ByyQ;5oRjGM&~+ z)zefMFD+ndeGnLpZ<#CC(ns$D4ue@g42S2Yp%z+TO>7B>CeMzBOQfFepccuZluO%Q!l!rem@P8cTk{pR*3nU6 z{R+<~6%IzoJKBWc^Q+f#eG zmG`khFxnanzJhxa6c#jM?l{-3Qx#Whi}BETJqEqSt$cOvFL^o|A7OHlihBTWYE)5% zGW#}MWXA<;O*DFsKR|ro`gBlyeo<+ZxDobK;K&)|P^}|YYP%xNW=T;8C`;twX@#8J zoSnwvy5gbk*dGW?gMD)rYCQ=gIEr~_7VMkCKbe|i3CyI8{Xu~wgHP$=dycc>g z3NZHANC|ahe{kiI8uZ$hGkxa&rLV~jpLW1(zrCrk*~(~kVnERDoEYa0s6NRy^GCQu zTC3ZEUD~Mq^48zR)Od#z%9&iZsx#b3-Q7}d4|#7%^hM$fj0N?FWU_LMko)8vr}GAT zg_Wag(cHfjQ<5&XU;OK3Ttrd<1rC>k`Ns`P+4jR?vA2^?$Yd?_{#w#14+iYvPepSn zlMg)thiz!n>SLNU4o$I#a*(<-)m$myFm=AB1G@B>|8k8|NXnhm`_WVNpICmmc4yV> zQE-gv!%xdO-Ps~QiTvYdgSd6aCutBl4%MCTQL0j0dh^ypovP3-GXxR}4nK!dG#J2q zq4`(wut?+|c-@XWFPNF6Yflf+mw>>h>!l{|7(A67Rf=L<&+J?PC#ng_GmNHB%Ha$k zCI!+P^xwF9veUCPsUyiSRT)?sI&QGD=D0ML@lg+(Cp`4oYqyTnQ*XVpBdIC+kdM_3X5};qABt&r?;$l+aoW`*5yd~}^V3|;r;2n*JzHz%ou2Oy z(MC)Yxj)ycsUq(}Birq{E8i=Yin{x$vfdJ?OAcw1^6jJc=8R|ds#|0v>KtBVul}T4 zyz&LPO8iYjp54YHvW0;d{>rbdsm|mk zoG?he-Lp`y?TOuadg_{a?t0rZIn(WtrJHqXvz^H4Q~`Q=y*&$HN==7WbN6%0z@PU} z+lvnyZ8sg*1XWp0p(p<1v>-2+-9iBxZ5g-X<#e{t?UtbZ@}kUr_nd31XD@Yg2pXiw zOlmVz25^+319jon-Dt!9USBM!mmDiZfsDuRPot(mg;F@*$)m7%X3FZ^A}%uwUxPE`Wgb*)Uw%q)c=XDYYo z%ce#4iQlapUCBlFB!q=!CateUq7a0DL7=yZQpJ8f3R^utVxC9n`@^QE7lvfpO@*45)!Hr?Vc>5--7`hK{6`VIZXi@lhgG3JdjR=WB3TwN!O zgjOq+adL7HZfJSi8Y<`0z})<_uSjZ8dZ9kJwXzQm%nAFHGWYtQ0pjJP5BjLDl;Rhi zVN>%>bNmJ!<+g|5hN)MCGtu1$n8MQG9z_d;1C1DjmJeq__ghK+I;DtZ9q-Wkz9xLF zND?x0huaZDkXl4nfsOy?RwHb6+sWzanp=2}15_HfTu!Tx=P`VC7&izRf@64E$Ncxi zggN9fH(rzdjoix0jSkMx&sGV*=Ab1EHhDiF`h3rnlli2bdAD~Ew$I+~fyZtGJ^vmeY<<~ibLU6oqrlkz8RG_r^JilJ>fk0Z0SA|b&Iw=HQ$2Bph#=an z^knIk@S8UF@m*h4nE*#w10n_P45IL`ao|*A6! z!KCI5y5eEg$HC-VWZZ;C8}FbyOOfTXBJatfy!ID~M*wzuabu=}7n3~FUIW%6%a{Um z-jnFAS|_Ffi@|*P@i)M0gS&kIE%0;6b|KYS%jls5o;;QL83tEUh2r zi)P~r>)E+QTONa!!j%1lW0q;|3r5F1OcAZtJ&8GV3tcHH2${6Qa4fxsMNamN%}?%6rxG zT3eGT)1(GmLo=?CO~~C?tTKWxY3||_Nrz@4wlQDc#>6sgo1dDdtg)U+O%_&JQ_(-w1Xqf=MF=A6CdAMjt1D$! zoyw?3dC@XLeWkAhJX(;wZ0xju=ySmjs9TUy3{G@Z^;>R*YpHu1{EJtpNS@;GR{vFz z2z9J=fhma2$blUl?n556otPdY{16v^Ya)NRG`}yhxAYf!MB0)zYm~U1>D~@}!rn6C zV~>`V2XYbELSGxI#iLRm)4$=rM6K!+e;rrd)|I+RnhP(MXJTf{;ZZi^>rxHit0?Zx z+z)~L^%Mt;?rA2HNG*yfKLa@1h{NP*gskwnHx-nMmiURavJ9{V;T>`r$M!O-6pl$U zGjsN?Me(*arV%D8Lluf$1)_!zv0KZT^MX&yDi(iDhr^^DRVG|Of38f-bj*z>Y~`z! zE`t{VTxtJ0$^9fV2Ft`?_bDYOP1zSNMYMK(d6tSGZBcq)t#WN8dg^tpJ$>CAj{4(6 zAO2=r_i|}h$vl3VFS6(%mM?#3Zhw)dO?ww)xk8eGN|YW_(=HM&W@s`Y%nf`KU^1`(oh*pQZbV{J8~&0M2Nvu!w_(YO`uh$ zFE1|s=WM{?hhNyHCO2;ev)$rF9R7n1>telR*q3^M=P*+vLuko?&S#l%2$OtS3kYM& z(&E5E{QniqwPVhhl_1NYC%oxp*)MG^~YwnkR&rFF$@+qDncsO_5pJUqozP~YB( zQJd$QD{7Z!7E_#F$O{ZLf86GH)GD;iQw@Lz%Stk1$3$zP=6D|$O3ad0DyDm{54~1R zFX!$fmbEqEJ7=#^`AuYUoQrWBdVGW|v&iLOV#qf;GoRSm5Lqi&;tN2(S@=qJa-*%+ zEiUZJLeIy>!aV^4h~Q{6Tu(UJFYc|o61lVflmupir9mYuauG#8DdySbho5TJNEI0A z!QknLV7u35EAtAdg~z#wD?R|6~eDupGc8Y~cCkTdB0l1!GlgjON`&vE6m)=Yz70i$q>QkasW8W{Kn`QwF8q?5t;s|FRHnS}8_ zXXgJo0lk!;a8?Sb_!`Q8{tG|ULN-tsXOpQ22{f>v-!DQi-Q=G!y;N)?IT+M`J}Kn^ zMs~KC4i`uM-{U)I{6+t9-aH#q8ym02=o!IU+&4+npA(t?ce31If3`B0m?|nOFML#o z_A@W`3@>dtYnzX_INDoUJ=HZeEt<+F^KQ1HY7mbLp1QW9oU5a}P!jxWEACDXzpwJz zSd%m#C&pd#sweHb6rcVaURz!T2CUWUE$Z>ISn(4t&4Cgjqb3uF6eY1pU1mIBJ3wK_ z`Kd04n3Czh8WAdaT~DyonQq%yQ-@!X=p@5K_5HMvSz2bFG>Ws<2?;5XQ{QLTzKCpZ z@nqrRHEx8|Y_$69yQrENr&Mn?l{X?do8l+ht$WJi&crXSsRC3h+?_+6gf_9tv=p;K zhG@HgT4GAP-e+s8A1y_nT6q}9mrB|_b1Nfv9rM50id|Xsm^8aL%gqxG#DwXhuY95N zWgDdCGCQfaS6!l?*6EL?CuKR>x6;C%a^U7azWPi+MbnWuNEXiD6AHNQV!6p#U$XLx zJTbuA`%=VnH0H0bVnJy!x`@}jxEI#X%-8xB57%RyEUDc+D9@x8s7oV0Y_vxrO(Lr$ zrjdjcj(a~V1wcJcLvVovF|ygc^M*=6`<`oxzrE-H`~lt3ZCQFX;GbHOy4*ZjPa|!x z+tb0G-+M(e7{N8&vt5LjQYtYTmqQthlL zVviR#)?}lT>i6s3Tsg8l1JBRfGy>?Xe}_A_x}$b$K7hfaVjBafaXC9%*;Ka_D>_({ zPeZgQ@(im;jFr0ob%1jr=r|1Ii8`?O_>9!&2Q3PnS8pOR#h1> zc>VhN?bL=*m0V8OXmKi}Lc1whLHLQ_I7kqX@Wk2<5@<6f_V(VU6UyV@lV%D=H0aaUBcf7jl1A2jN!?Pc4UOC zU{l5(<58_dMnRbpHkI9#6n2v`fOcr))yaH`6ftG0)9>k-pT(E$AKPzx&6^(~qapD1 zWL@(8Q;w%dLOm_r-uzvhYHJWp5)erF%CbJsj@-BKf+hq4y7Qe!LzGI`;!b!wrZgf> zfET9jNci|drp@L2dAYIP?G-urdrzq_v25D*^ObVZZe;kp21C4*Oe}A7aekd57*@%l zuJ1U7RRpz)r_CckM&RuF*1ntIwPb&Qw!&JCM7#g*!^fI`T4MYOG22e<$y$vNh?+KS ztdMS^vysWpn-yjJafz_(pbjE*rt`@fbE)7QWF0|`T&pn7FOQ3*z{Hkdb$tzOI#yXX zGK<~o<9Nd|RU|$#Gy`uH2~9iF^zwA9$C4*>+Ek}=aj;9Zop4V(E+Su-Juyjleg2C$ z>cs0l6m{P_7Z~x2Tb$=a3S^=M4)lxTcaI1;pv24f zLT*^Zlp1ZLxhbr0PBi6sm4uVE^sE|+l4`=k-$u7Xv3};ZjdSJ!);q2zZ<7zXlsfFO zfb%`r*DyRn2o)n9G^s1oPf+se;J+`avVox;jQO-95whDVQ`9Eo9QP2uU(K+8Xr@Cc ztrN=#tydAnRIjBtJRaP(n^(jPdJnaNnMOv8@)Nt4t);GOx$JFIAG6)BgKxL^YTw_{ zDES7Ab`w}gou0;)^AUa^<6TBqE9|g!Knqm@blzxww>uXy0<983qK7th0Jwq-?I zU?@gO4NSQ6x=++0u4K9D#JB|E;1X@xuvb53(M5<7_Yr6`$v zfzuJd`7*n&o7=6P#Q(jnW!NUZA9{@bqJ8B+^D{0ANaO=wnZXu6y2NEWbm=J$N}#l# z18ubhmgiIiH7(=Ix-|Zw~+jyME|HVK% zYc1=C7SGe|AWS&lp`uV;DOlotHKO!a>?DUO;^=mZqtkByV;sVePGZwmhpE^;JIhRPCV(A&1C z%%i}kjfr3=I3L-yPLF_ssLqCp9O{TLVMIEN?xWFUaxrodVega8YDdo>jh4vee5#99 zy_Jn#h%E&fVDo+0109r&wu^c7-CfY&r0K3S3`X0keDs@#_U$z`MJ#qy$5*9#m*@>m)kS1QlxAw(c;b7VGjkx2i?Hb?mAYuHz!dyb3+MZd6s~V}>dm=H@LlvzM#3-IRont(nMi1?G&-v$Q*pAvakPac^ebq)HeaK`m1iHpOV8JLSCK(Q;}S1~tOC{z zN`W-07NHQ+ycX_K7)mUXlFSPfc5XZV8PDxuYrJaDBn66MXF(Dp=0xwltuA?pYX2$p zM%08bf)S-HDxKnY#%8(fW53#huyx41?F1VI9Agy-RVhU}$efqR@^c!h{w<-q0#PLn zoO0zf`RXRU(&6SCtnRjFm!|qhgr|4s)ktd%6c6^^i%Ok2URNWAj7Qm|WLCE`wjvUV z;q`c%v^34{Jb91f=u;0(p#^_(2M>ZV6}4)}poiC~?Kve_tSqkP!s;rTYD4$}F;!cn zpTZ>sd7{x(7tZ&sD4g3gh964-pY{O;$26~lguXrYoX_bjAN+InDipiIx23~~vT~_2 zo6`lyuYO5yr0yV!ldkxeuL=#0sifq0f6CH(iy`5aIHO~GKkYukh8pGtM_{Dj- zzdltbr56;)3e>HO#FBaCGSdA6PxRi6IiO>qmVU8gh!%5r(E*`?i#sM#Gm3GSVO zXpkDuWeb%s=VHX|q(QYlz=~=;<>aa0>Ey7Q1=Wn|2!`WtPSN#DuMj2?85s|j8Mo+j zxnM=lA&{8z3(XcVZ|jJBwztb$Sg^n}7S{PNalV23TibxP^=?K9VW=|_|7ivk+(8Jf zTPsseyF^@XIxx4Y)E zlyc7E{tZ^9$7_!;Tk&Nsc=u;p-t6^=Rt9sq9h`T*h`$6N7o;t*qM3p8{v zSg#c(IrVEOMC?3xA^`3mG?6Y`)k=dZS$u*^s2JD4tfNvHtu5?G=Ut@hsO+Zxm-;+6oiU@wUl!6=Fi(2*Oo0(jM`2=K* zKgDc$I8k(Lcl>oeI&ddwyNdF(9)fE$+lu`(n^}Rj(Ks*oa=X0m72lt$X+(jLs+&5@ z+iMT|3yKk-nKNVi8{{IckbiYx_xs6ejmcoh&SJvrXh5WE|8Y-tFZr=lO8#5bB~fBG zuiG%<&GzN~eCys9TI*?Rr3WpsylFfzOTm~@nB@_ig{N)7Z{K7nS+~$OtCcscd5qlY zay`rH_A>}M9T`@ohDJHw>1%tc2`ND^)GN$#SnLA-z)XR17BOeZRxrowIhz-%^XMFg z^?uzsutSc|cFhM<&m$}nV@|Uq&>{XjlPhxT-ZhjKo`qXQ`dil4dyJB4U2HTa z+0L+?Q}2u=Y)?w%ewZ+I1TLXWbhqVD)go8uqH0MtdenW&vEAC8Go`Hf?_5XIiO!35_rnOSF_mq*Fm#t$mG= zDq31pHE9&J)>g}?C6-X@P^!b_dModfFn_}P<9VKQu5-Tkx$o}}pX)j2>20{$;0V`p zt7*x4Ne(|5z)#Kz@7VnEwLAlCJSdbS5z&pU_&^-og%yE^p2<3!xTP! z{?eTi4_yCbIF1=o*(h@qJ*dH3D4(b8;Mq48)4jia$_+?GS+!~!jW_7I)>2` z_T9zyI`-jwhsf9sucFYj_f|~1)ggZ)TYbR)<3`@ydyBK%K<1mP%U$e3Z(x83}B7KVeHs0LCgx-`x z+^GK2+|bZKqZuMKYU>)@we6bct}+{Q7Zl}^_r(My^dJk5Tdu8o6;Y|i)l?;UQ)cBp zcTey~YYnp?$rHc2B153FQ+{1gjLo9HUsS*;IfdE;ph^(Lf)frOSpT)+KGA%8GZ<{P z*k&A^b8^|JU&}fS8fMALzbwAlkI~lFs;^Gfg`c|rA*d?csbRI-mjHMS1lbJS5~>5&=Yb?LnB zNfovz4h{sI17NZDil?h()wpkBRInG6iwRttCsN9iv<++1ZOAYBYUtVmE zz+zki2OS-4`Xd>I_XQDz_;TKNy-*nIe)gy#s5Bkm+tF-T>Xz>vb zFrPgDfhtTY+Ew@u(y{^O(_s#8Kg)0C!2slw$Ig+=;1eK&>mb!txy^BjUVbx#2+%An z(ccmI3twm~PbkCRTIW#c3QybAapBTtS;?P=>K!#!My}%+lIS`$1kc< z(%=Bf=hl9*wL^Yxu6(#{=n>um3(6!RY@j5P`4X9)HH^b`=uyGZrPdjairsuz$75t< z@W`1}bQ=04k#QF)B6B1E?fPupwfC;^PorG5l}8^Ooa_pzq1HRr|8)NT6Gj5FJnLxF zZFmC>{yx2~$%@d16<4j$BDmX&z^N>C!lLpP)xB7i(GVUd4r{&{+uP9|g;x5!Q&b24 zD672nrqFxSF)mFatR-teWAC-(%;%rCZaU*LrdTe=H4>!)A9<;@BgkE=|QZ(s1$Smbd(YzuahJdQy^uD?4#yU5rIbdN#D?%!^TBv!m28z=8eZ%KWE`T)Wod{ zoU7nbrbS=Z-tPs{_4V)NQe{za%e(At>$2_hI?w6CYW?%SY3+n`=}*hC)5qvf)z0w~ zQ%T4vA5}Ppm}ejb7oX4Wt3doB2`a73$=$&DbsS`}7ozxAk5J5pbhKDeQ}8eqdQWl4_dO^?SlWG{?}fi_QH}gls=v#64(x5p(<3L|MK1+=ITW z`jku0zNRdiRQ9@F>=ro;2ue!Xr-H~a)79-87#O&74eg?S3hX%d+6K3NV>$Lu;;Y;- z6{!XBWde@10}io)A})+{ z-$NJ=i5U!L`-+jn)&jI3>W4NG5(Od<^Gk+m)Q%lRCI_CBjakcS+-)Zw84DRG_e*Wt z>dZsZ&ES!4MrUy>9}c710MbqOjeEpCG!iz?NZ6dq3_fZu@#S@{WjNsZT-pc^xQ@~0 zmIj*-st0R%ozd)a6JVyxSL(9_1O)0GY|gtW3FVpa>}q#UYLj2%A!v|-Grg>=ES>4u zI%DhrnKBcThyM$4oj z3S!|hZVX#OF*w>HjA)2WVe@9~XoLB`OnV?^F}=MkAzx4_6id;);LU4rq31I>rLlFW_K%$>zD0PxAi&TDdhx literal 27047 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 BFlqn* From 07e10fbe9f1a77fe03cfceeb008e1e56caba3a40 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 2 Aug 2012 06:54:19 -0400 Subject: [PATCH 275/519] Fixed #16941 - Clarified naturaltime output when the time is more than a day old. Thanks antoviaque for the patch. --- docs/ref/contrib/humanize.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/ref/contrib/humanize.txt b/docs/ref/contrib/humanize.txt index cdc3009a51..57978288b1 100644 --- a/docs/ref/contrib/humanize.txt +++ b/docs/ref/contrib/humanize.txt @@ -103,9 +103,9 @@ naturaltime .. versionadded:: 1.4 For datetime values, returns a string representing how many seconds, -minutes or hours ago it was -- falling back to a longer date format if the -value is more than a day old. In case the datetime value is in the future -the return value will automatically use an appropriate phrase. +minutes or hours ago it was -- falling back to the :tfilter:`timesince` +format if the value is more than a day old. In case the datetime value is in +the future the return value will automatically use an appropriate phrase. Examples (when 'now' is 17 Feb 2007 16:30:00): @@ -115,13 +115,14 @@ Examples (when 'now' is 17 Feb 2007 16:30:00): * ``17 Feb 2007 16:25:35`` becomes ``4 minutes ago``. * ``17 Feb 2007 15:30:29`` becomes ``an hour ago``. * ``17 Feb 2007 13:31:29`` becomes ``2 hours ago``. -* ``16 Feb 2007 13:31:29`` becomes ``1 day ago``. +* ``16 Feb 2007 13:31:29`` becomes ``1 day, 3 hours ago``. * ``17 Feb 2007 16:30:30`` becomes ``29 seconds from now``. * ``17 Feb 2007 16:31:00`` becomes ``a minute from now``. * ``17 Feb 2007 16:34:35`` becomes ``4 minutes from now``. * ``17 Feb 2007 16:30:29`` becomes ``an hour from now``. * ``17 Feb 2007 18:31:29`` becomes ``2 hours from now``. * ``18 Feb 2007 16:31:29`` becomes ``1 day from now``. +* ``26 Feb 2007 18:31:29`` becomes ``1 week, 2 days from now``. .. templatefilter:: ordinal From 2a16eb07927c866b4d8e1d318f71bd1038b34cae Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 2 Aug 2012 19:21:48 -0400 Subject: [PATCH 276/519] Fixed #17704 - Updated the StackedInline section in Tutorial 2; thanks xbito for the draft patch. --- docs/intro/_images/admin15t.png | Bin 0 -> 22805 bytes docs/intro/tutorial02.txt | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 docs/intro/_images/admin15t.png diff --git a/docs/intro/_images/admin15t.png b/docs/intro/_images/admin15t.png new file mode 100644 index 0000000000000000000000000000000000000000..999d0519fb59c8cb64cc41d9912a9b5886feec86 GIT binary patch literal 22805 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 Date: Fri, 3 Aug 2012 09:26:11 +0200 Subject: [PATCH 277/519] Fixed #18684 -- Added Finnish DATETIME_FORMAT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Guttorm Flatabø for the report and the initial patch. --- django/conf/locale/fi/formats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/conf/locale/fi/formats.py b/django/conf/locale/fi/formats.py index 9a658eed40..e76144a9e4 100644 --- a/django/conf/locale/fi/formats.py +++ b/django/conf/locale/fi/formats.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. E Y' TIME_FORMAT = 'G.i.s' -# DATETIME_FORMAT = +DATETIME_FORMAT = r'j. E Y \k\e\l\l\o G.i.s' YEAR_MONTH_FORMAT = 'F Y' MONTH_DAY_FORMAT = 'j. F' SHORT_DATE_FORMAT = 'j.n.Y' From a55cde8ab1dc33723b4bac472b6fac6bb45d725a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 3 Aug 2012 09:35:39 +0200 Subject: [PATCH 278/519] Fixed #18363 -- Improved Galician date and time format strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Guttorm Flatabø for the report and the initial patch, and Fran Dieguez for the review. --- django/conf/locale/gl/formats.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/django/conf/locale/gl/formats.py b/django/conf/locale/gl/formats.py index 1ff9f34521..ba7f6c52a0 100644 --- a/django/conf/locale/gl/formats.py +++ b/django/conf/locale/gl/formats.py @@ -1,17 +1,18 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date -DATE_FORMAT = 'd F Y' +DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i:s' -# DATETIME_FORMAT = -YEAR_MONTH_FORMAT = 'F Y' -MONTH_DAY_FORMAT = 'j F' -SHORT_DATE_FORMAT = 'j M, Y' -# SHORT_DATETIME_FORMAT = -# FIRST_DAY_OF_WEEK = +DATETIME_FORMAT = r'j \d\e F \d\e Y \á\s H:i' +YEAR_MONTH_FORMAT = r'F \d\e Y' +MONTH_DAY_FORMAT = r'j \d\e F' +SHORT_DATE_FORMAT = 'd-m-Y' +SHORT_DATETIME_FORMAT = 'd-m-Y, H:i' +FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior From c5d6f6d6829e730bdddf63c1252304f0c49a9053 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 3 Aug 2012 10:47:27 +0200 Subject: [PATCH 279/519] Reorganized geoapp gis tests Removed the numbering of tests and moved lookup/geoqueryset tests in their own test class. --- .../contrib/gis/tests/geoapp/test_regress.py | 14 +- django/contrib/gis/tests/geoapp/tests.py | 817 +++++++++--------- 2 files changed, 419 insertions(+), 412 deletions(-) diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index a9d802d8f1..fffd7d3cab 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -12,7 +12,7 @@ from .models import City, PennsylvaniaCity, State, Truth class GeoRegressionTests(TestCase): - def test01_update(self): + def test_update(self): "Testing GeoQuerySet.update(). See #10411." pnt = City.objects.get(name='Pueblo').point bak = pnt.clone() @@ -24,7 +24,7 @@ class GeoRegressionTests(TestCase): City.objects.filter(name='Pueblo').update(point=bak) self.assertEqual(bak, City.objects.get(name='Pueblo').point) - def test02_kmz(self): + def test_kmz(self): "Testing `render_to_kmz` with non-ASCII data. See #11624." name = '\xc3\x85land Islands'.decode('iso-8859-1') places = [{'name' : name, @@ -35,7 +35,7 @@ class GeoRegressionTests(TestCase): @no_spatialite @no_mysql - def test03_extent(self): + def test_extent(self): "Testing `extent` on a table with a single point. See #11827." pnt = City.objects.get(name='Pueblo').point ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) @@ -43,14 +43,14 @@ class GeoRegressionTests(TestCase): for ref_val, val in zip(ref_ext, extent): self.assertAlmostEqual(ref_val, val, 4) - def test04_unicode_date(self): + def test_unicode_date(self): "Testing dates are converted properly, even on SpatiaLite. See #16408." founded = datetime(1857, 5, 23) mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)', founded=founded) self.assertEqual(founded, PennsylvaniaCity.objects.dates('founded', 'day')[0]) - def test05_empty_count(self): + def test_empty_count(self): "Testing that PostGISAdapter.__eq__ does check empty strings. See #13670." # contrived example, but need a geo lookup paired with an id__in lookup pueblo = City.objects.get(name='Pueblo') @@ -60,12 +60,12 @@ class GeoRegressionTests(TestCase): # .count() should not throw TypeError in __eq__ self.assertEqual(cities_within_state.count(), 1) - def test06_defer_or_only_with_annotate(self): + def test_defer_or_only_with_annotate(self): "Regression for #16409. Make sure defer() and only() work with annotate()" self.assertIsInstance(list(City.objects.annotate(Count('point')).defer('name')), list) self.assertIsInstance(list(City.objects.annotate(Count('point')).only('name')), list) - def test07_boolean_conversion(self): + def test_boolean_conversion(self): "Testing Boolean value conversion with the spatial backend, see #15169." t1 = Truth.objects.create(val=True) t2 = Truth.objects.create(val=False) diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index bcdbe734ff..b06d6b5e1b 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -15,19 +15,24 @@ from django.utils import six from .models import Country, City, PennsylvaniaCity, State, Track +from .test_feeds import GeoFeedTest +from .test_regress import GeoRegressionTests +from .test_sitemaps import GeoSitemapTest + + if not spatialite: from .models import Feature, MinusOneSRID class GeoModelTest(TestCase): - def test01_fixtures(self): + def test_fixtures(self): "Testing geographic model initialization from fixtures." # Ensuring that data was loaded from initial data fixtures. self.assertEqual(2, Country.objects.count()) self.assertEqual(8, City.objects.count()) self.assertEqual(2, State.objects.count()) - def test02_proxy(self): + def test_proxy(self): "Testing Lazy-Geometry support (using the GeometryProxy)." ## Testing on a Point pnt = Point(0, 0) @@ -95,165 +100,97 @@ class GeoModelTest(TestCase): self.assertEqual(ply, State.objects.get(name='NullState').poly) ns.delete() - def test03a_kml(self): - "Testing KML output from the database using GeoQuerySet.kml()." - # Only PostGIS and Spatialite (>=2.4.0-RC4) support KML serialization - if not (postgis or (spatialite and connection.ops.kml)): - self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly') - return - - # Should throw a TypeError when trying to obtain KML from a - # non-geometry field. - qs = City.objects.all() - self.assertRaises(TypeError, qs.kml, 'name') - - # The reference KML depends on the version of PostGIS used - # (the output stopped including altitude in 1.3.3). - if connection.ops.spatial_version >= (1, 3, 3): - ref_kml = '-104.609252,38.255001' - else: - ref_kml = '-104.609252,38.255001,0' - - # Ensuring the KML is as expected. - ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo') - ptown2 = City.objects.kml(precision=9).get(name='Pueblo') - for ptown in [ptown1, ptown2]: - self.assertEqual(ref_kml, ptown.kml) - - def test03b_gml(self): - "Testing GML output from the database using GeoQuerySet.gml()." - if mysql or (spatialite and not connection.ops.gml) : - self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly') - return - - # Should throw a TypeError when tyring to obtain GML from a - # non-geometry field. - qs = City.objects.all() - self.assertRaises(TypeError, qs.gml, field_name='name') - ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo') - ptown2 = City.objects.gml(precision=9).get(name='Pueblo') + @no_mysql + def test_lookup_insert_transform(self): + "Testing automatic transform for lookups and inserts." + # San Antonio in 'WGS84' (SRID 4326) + sa_4326 = 'POINT (-98.493183 29.424170)' + wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84 + # Oracle doesn't have SRID 3084, using 41157. if oracle: - # No precision parameter for Oracle :-/ - gml_regex = re.compile(r'^-104.60925\d+,38.25500\d+ ') - elif spatialite: - # Spatialite has extra colon in SrsName - gml_regex = re.compile(r'^-104.609251\d+,38.255001') + # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157) + # Used the following Oracle SQL to get this value: + # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL; + nad_wkt = 'POINT (300662.034646583 5416427.45974934)' + nad_srid = 41157 else: - gml_regex = re.compile(r'^-104\.60925\d+,38\.255001') + # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084) + nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform + nad_srid = 3084 - for ptown in [ptown1, ptown2]: - self.assertTrue(gml_regex.match(ptown.gml)) - - - def test03c_geojson(self): - "Testing GeoJSON output from the database using GeoQuerySet.geojson()." - # Only PostGIS 1.3.4+ supports GeoJSON. - if not connection.ops.geojson: - self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly') - return - - if connection.ops.spatial_version >= (1, 4, 0): - pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}' - houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}' - victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}' - chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + # Constructing & querying with a point from a different SRID. Oracle + # `SDO_OVERLAPBDYINTERSECT` operates differently from + # `ST_Intersects`, so contains is used instead. + nad_pnt = fromstr(nad_wkt, srid=nad_srid) + if oracle: + tx = Country.objects.get(mpoly__contains=nad_pnt) else: - pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}' - houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}' - victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}' - chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + tx = Country.objects.get(mpoly__intersects=nad_pnt) + self.assertEqual('Texas', tx.name) - # Precision argument should only be an integer - self.assertRaises(TypeError, City.objects.geojson, precision='foo') + # Creating San Antonio. Remember the Alamo. + sa = City.objects.create(name='San Antonio', point=nad_pnt) - # Reference queries and values. - # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo'; - self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson) + # Now verifying that San Antonio was transformed correctly + sa = City.objects.get(name='San Antonio') + self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6) + self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6) - # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # This time we want to include the CRS by using the `crs` keyword. - self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json) + # If the GeometryField SRID is -1, then we shouldn't perform any + # transformation if the SRID of the input geometry is different. + # SpatiaLite does not support missing SRID values. + if not spatialite: + m1 = MinusOneSRID(geom=Point(17, 23, srid=4326)) + m1.save() + self.assertEqual(-1, m1.geom.srid) - # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria'; - # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # This time we include the bounding box by using the `bbox` keyword. - self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson) + def test_createnull(self): + "Testing creating a model instance and the geometry being None" + c = City() + self.assertEqual(c.point, None) - # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago'; - # Finally, we set every available keyword. - self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson) + @no_spatialite # SpatiaLite does not support abstract geometry columns + def test_geometryfield(self): + "Testing the general GeometryField." + Feature(name='Point', geom=Point(1, 1)).save() + Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save() + Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save() + Feature(name='GeometryCollection', + geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), + Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save() - def test03d_svg(self): - "Testing SVG output using GeoQuerySet.svg()." - if mysql or oracle: - self.assertRaises(NotImplementedError, City.objects.svg) - return + f_1 = Feature.objects.get(name='Point') + self.assertEqual(True, isinstance(f_1.geom, Point)) + self.assertEqual((1.0, 1.0), f_1.geom.tuple) + f_2 = Feature.objects.get(name='LineString') + self.assertEqual(True, isinstance(f_2.geom, LineString)) + self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) - self.assertRaises(TypeError, City.objects.svg, precision='foo') - # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo'; - svg1 = 'cx="-104.609252" cy="-38.255001"' - # Even though relative, only one point so it's practically the same except for - # the 'c' letter prefix on the x,y values. - svg2 = svg1.replace('c', '') - self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg) - self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg) + f_3 = Feature.objects.get(name='Polygon') + self.assertEqual(True, isinstance(f_3.geom, Polygon)) + f_4 = Feature.objects.get(name='GeometryCollection') + self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) + self.assertEqual(f_3.geom, f_4.geom[2]) @no_mysql - def test04_transform(self): - "Testing the transform() GeoManager method." - # Pre-transformed points for Houston and Pueblo. - htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) - ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774) - prec = 3 # Precision is low due to version variations in PROJ and GDAL. + def test_inherited_geofields(self): + "Test GeoQuerySet methods on inherited Geometry fields." + # Creating a Pennsylvanian city. + mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)') - # Asserting the result of the transform operation with the values in - # the pre-transformed points. Oracle does not have the 3084 SRID. - if not oracle: - h = City.objects.transform(htown.srid).get(name='Houston') - self.assertEqual(3084, h.point.srid) - self.assertAlmostEqual(htown.x, h.point.x, prec) - self.assertAlmostEqual(htown.y, h.point.y, prec) + # All transformation SQL will need to be performed on the + # _parent_ table. + qs = PennsylvaniaCity.objects.transform(32128) - p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo') - p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo') - for p in [p1, p2]: - self.assertEqual(2774, p.point.srid) - self.assertAlmostEqual(ptown.x, p.point.x, prec) - self.assertAlmostEqual(ptown.y, p.point.y, prec) + self.assertEqual(1, qs.count()) + for pc in qs: self.assertEqual(32128, pc.point.srid) + + +class GeoLookupTest(TestCase): @no_mysql - @no_spatialite # SpatiaLite does not have an Extent function - def test05_extent(self): - "Testing the `extent` GeoQuerySet method." - # Reference query: - # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` - # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) - expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) - - qs = City.objects.filter(name__in=('Houston', 'Dallas')) - extent = qs.extent() - - for val, exp in zip(extent, expected): - self.assertAlmostEqual(exp, val, 4) - - # Only PostGIS has support for the MakeLine aggregate. - @no_mysql - @no_oracle - @no_spatialite - def test06_make_line(self): - "Testing the `make_line` GeoQuerySet method." - # Ensuring that a `TypeError` is raised on models without PointFields. - self.assertRaises(TypeError, State.objects.make_line) - self.assertRaises(TypeError, Country.objects.make_line) - # Reference query: - # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; - ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326) - self.assertEqual(ref_line, City.objects.make_line()) - - @no_mysql - def test09_disjoint(self): + def test_disjoint_lookup(self): "Testing the `disjoint` lookup type." ptown = City.objects.get(name='Pueblo') qs1 = City.objects.filter(point__disjoint=ptown.point) @@ -263,7 +200,7 @@ class GeoModelTest(TestCase): self.assertEqual(1, qs2.count()) self.assertEqual('Kansas', qs2[0].name) - def test10_contains_contained(self): + def test_contains_contained_lookups(self): "Testing the 'contained', 'contains', and 'bbcontains' lookup types." # Getting Texas, yes we were a country -- once ;) texas = Country.objects.get(name='Texas') @@ -308,86 +245,11 @@ class GeoModelTest(TestCase): self.assertEqual(1, len(qs)) self.assertEqual('Texas', qs[0].name) - @no_mysql - def test11_lookup_insert_transform(self): - "Testing automatic transform for lookups and inserts." - # San Antonio in 'WGS84' (SRID 4326) - sa_4326 = 'POINT (-98.493183 29.424170)' - wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84 - - # Oracle doesn't have SRID 3084, using 41157. - if oracle: - # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157) - # Used the following Oracle SQL to get this value: - # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL; - nad_wkt = 'POINT (300662.034646583 5416427.45974934)' - nad_srid = 41157 - else: - # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084) - nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform - nad_srid = 3084 - - # Constructing & querying with a point from a different SRID. Oracle - # `SDO_OVERLAPBDYINTERSECT` operates differently from - # `ST_Intersects`, so contains is used instead. - nad_pnt = fromstr(nad_wkt, srid=nad_srid) - if oracle: - tx = Country.objects.get(mpoly__contains=nad_pnt) - else: - tx = Country.objects.get(mpoly__intersects=nad_pnt) - self.assertEqual('Texas', tx.name) - - # Creating San Antonio. Remember the Alamo. - sa = City.objects.create(name='San Antonio', point=nad_pnt) - - # Now verifying that San Antonio was transformed correctly - sa = City.objects.get(name='San Antonio') - self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6) - self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6) - - # If the GeometryField SRID is -1, then we shouldn't perform any - # transformation if the SRID of the input geometry is different. - # SpatiaLite does not support missing SRID values. - if not spatialite: - m1 = MinusOneSRID(geom=Point(17, 23, srid=4326)) - m1.save() - self.assertEqual(-1, m1.geom.srid) - - @no_mysql - def test12_null_geometries(self): - "Testing NULL geometry support, and the `isnull` lookup type." - # Creating a state with a NULL boundary. - State.objects.create(name='Puerto Rico') - - # Querying for both NULL and Non-NULL values. - nullqs = State.objects.filter(poly__isnull=True) - validqs = State.objects.filter(poly__isnull=False) - - # Puerto Rico should be NULL (it's a commonwealth unincorporated territory) - self.assertEqual(1, len(nullqs)) - self.assertEqual('Puerto Rico', nullqs[0].name) - - # The valid states should be Colorado & Kansas - self.assertEqual(2, len(validqs)) - state_names = [s.name for s in validqs] - self.assertEqual(True, 'Colorado' in state_names) - self.assertEqual(True, 'Kansas' in state_names) - - # Saving another commonwealth w/a NULL geometry. - nmi = State.objects.create(name='Northern Mariana Islands', poly=None) - self.assertEqual(nmi.poly, None) - - # Assigning a geomery and saving -- then UPDATE back to NULL. - nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))' - nmi.save() - State.objects.filter(name='Northern Mariana Islands').update(poly=None) - self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly) - # Only PostGIS has `left` and `right` lookup types. @no_mysql @no_oracle @no_spatialite - def test13_left_right(self): + def test_left_right_lookups(self): "Testing the 'left' and 'right' lookup types." # Left: A << B => true if xmax(A) < xmin(B) # Right: A >> B => true if xmin(A) > xmax(B) @@ -423,7 +285,7 @@ class GeoModelTest(TestCase): self.assertEqual(2, len(qs)) for c in qs: self.assertEqual(True, c.name in cities) - def test14_equals(self): + def test_equals_lookups(self): "Testing the 'same_as' and 'equals' lookup types." pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326) c1 = City.objects.get(point=pnt) @@ -432,7 +294,37 @@ class GeoModelTest(TestCase): for c in [c1, c2, c3]: self.assertEqual('Houston', c.name) @no_mysql - def test15_relate(self): + def test_null_geometries(self): + "Testing NULL geometry support, and the `isnull` lookup type." + # Creating a state with a NULL boundary. + State.objects.create(name='Puerto Rico') + + # Querying for both NULL and Non-NULL values. + nullqs = State.objects.filter(poly__isnull=True) + validqs = State.objects.filter(poly__isnull=False) + + # Puerto Rico should be NULL (it's a commonwealth unincorporated territory) + self.assertEqual(1, len(nullqs)) + self.assertEqual('Puerto Rico', nullqs[0].name) + + # The valid states should be Colorado & Kansas + self.assertEqual(2, len(validqs)) + state_names = [s.name for s in validqs] + self.assertEqual(True, 'Colorado' in state_names) + self.assertEqual(True, 'Kansas' in state_names) + + # Saving another commonwealth w/a NULL geometry. + nmi = State.objects.create(name='Northern Mariana Islands', poly=None) + self.assertEqual(nmi.poly, None) + + # Assigning a geomery and saving -- then UPDATE back to NULL. + nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))' + nmi.save() + State.objects.filter(name='Northern Mariana Islands').update(poly=None) + self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly) + + @no_mysql + def test_relate_lookup(self): "Testing the 'relate' lookup type." # To make things more interesting, we will have our Texas reference point in # different SRIDs. @@ -474,60 +366,12 @@ class GeoModelTest(TestCase): self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name) self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name) - def test16_createnull(self): - "Testing creating a model instance and the geometry being None" - c = City() - self.assertEqual(c.point, None) + +class GeoQuerySetTest(TestCase): + # Please keep the tests in GeoQuerySet method's alphabetic order @no_mysql - def test17_unionagg(self): - "Testing the `unionagg` (aggregate union) GeoManager method." - tx = Country.objects.get(name='Texas').mpoly - # Houston, Dallas -- Oracle has different order. - union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') - union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') - qs = City.objects.filter(point__within=tx) - self.assertRaises(TypeError, qs.unionagg, 'name') - # Using `field_name` keyword argument in one query and specifying an - # order in the other (which should not be used because this is - # an aggregate method on a spatial column) - u1 = qs.unionagg(field_name='point') - u2 = qs.order_by('name').unionagg() - tol = 0.00001 - if oracle: - union = union2 - else: - union = union1 - self.assertEqual(True, union.equals_exact(u1, tol)) - self.assertEqual(True, union.equals_exact(u2, tol)) - qs = City.objects.filter(name='NotACity') - self.assertEqual(None, qs.unionagg(field_name='point')) - - @no_spatialite # SpatiaLite does not support abstract geometry columns - def test18_geometryfield(self): - "Testing the general GeometryField." - Feature(name='Point', geom=Point(1, 1)).save() - Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save() - Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save() - Feature(name='GeometryCollection', - geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), - Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save() - - f_1 = Feature.objects.get(name='Point') - self.assertEqual(True, isinstance(f_1.geom, Point)) - self.assertEqual((1.0, 1.0), f_1.geom.tuple) - f_2 = Feature.objects.get(name='LineString') - self.assertEqual(True, isinstance(f_2.geom, LineString)) - self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) - - f_3 = Feature.objects.get(name='Polygon') - self.assertEqual(True, isinstance(f_3.geom, Polygon)) - f_4 = Feature.objects.get(name='GeometryCollection') - self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) - self.assertEqual(f_3.geom, f_4.geom[2]) - - @no_mysql - def test19_centroid(self): + def test_centroid(self): "Testing the `centroid` GeoQuerySet method." qs = State.objects.exclude(poly__isnull=True).centroid() if oracle: @@ -540,84 +384,7 @@ class GeoModelTest(TestCase): self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol)) @no_mysql - def test20_pointonsurface(self): - "Testing the `point_on_surface` GeoQuerySet method." - # Reference values. - if oracle: - # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY; - ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326), - 'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326), - } - - elif postgis or spatialite: - # Using GEOSGeometry to compute the reference point on surface values - # -- since PostGIS also uses GEOS these should be the same. - ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface, - 'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface - } - - for c in Country.objects.point_on_surface(): - if spatialite: - # XXX This seems to be a WKT-translation-related precision issue? - tol = 0.00001 - else: - tol = 0.000000001 - self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol)) - - @no_mysql - @no_oracle - def test21_scale(self): - "Testing the `scale` GeoQuerySet method." - xfac, yfac = 2, 3 - tol = 5 # XXX The low precision tolerance is for SpatiaLite - qs = Country.objects.scale(xfac, yfac, model_att='scaled') - for c in qs: - for p1, p2 in zip(c.mpoly, c.scaled): - for r1, r2 in zip(p1, p2): - for c1, c2 in zip(r1.coords, r2.coords): - self.assertAlmostEqual(c1[0] * xfac, c2[0], tol) - self.assertAlmostEqual(c1[1] * yfac, c2[1], tol) - - @no_mysql - @no_oracle - def test22_translate(self): - "Testing the `translate` GeoQuerySet method." - xfac, yfac = 5, -23 - qs = Country.objects.translate(xfac, yfac, model_att='translated') - for c in qs: - for p1, p2 in zip(c.mpoly, c.translated): - for r1, r2 in zip(p1, p2): - for c1, c2 in zip(r1.coords, r2.coords): - # XXX The low precision is for SpatiaLite - self.assertAlmostEqual(c1[0] + xfac, c2[0], 5) - self.assertAlmostEqual(c1[1] + yfac, c2[1], 5) - - @no_mysql - def test23_numgeom(self): - "Testing the `num_geom` GeoQuerySet method." - # Both 'countries' only have two geometries. - for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom) - for c in City.objects.filter(point__isnull=False).num_geom(): - # Oracle will return 1 for the number of geometries on non-collections, - # whereas PostGIS will return None. - if postgis: - self.assertEqual(None, c.num_geom) - else: - self.assertEqual(1, c.num_geom) - - @no_mysql - @no_spatialite # SpatiaLite can only count vertices in LineStrings - def test24_numpoints(self): - "Testing the `num_points` GeoQuerySet method." - for c in Country.objects.num_points(): - self.assertEqual(c.mpoly.num_points, c.num_points) - - if not oracle: - # Oracle cannot count vertices in Point geometries. - for c in City.objects.num_points(): self.assertEqual(1, c.num_points) - - @no_mysql - def test25_geoset(self): + def test_diff_intersection_union(self): "Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods." geom = Point(5, 23) tol = 1 @@ -644,22 +411,232 @@ class GeoModelTest(TestCase): self.assertEqual(c.mpoly.union(geom), c.union) @no_mysql - def test26_inherited_geofields(self): - "Test GeoQuerySet methods on inherited Geometry fields." - # Creating a Pennsylvanian city. - mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)') + @no_spatialite # SpatiaLite does not have an Extent function + def test_extent(self): + "Testing the `extent` GeoQuerySet method." + # Reference query: + # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` + # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) + expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) - # All transformation SQL will need to be performed on the - # _parent_ table. - qs = PennsylvaniaCity.objects.transform(32128) + qs = City.objects.filter(name__in=('Houston', 'Dallas')) + extent = qs.extent() - self.assertEqual(1, qs.count()) - for pc in qs: self.assertEqual(32128, pc.point.srid) + for val, exp in zip(extent, expected): + self.assertAlmostEqual(exp, val, 4) @no_mysql @no_oracle @no_spatialite - def test27_snap_to_grid(self): + def test_force_rhr(self): + "Testing GeoQuerySet.force_rhr()." + rings = ( ( (0, 0), (5, 0), (0, 5), (0, 0) ), + ( (1, 1), (1, 3), (3, 1), (1, 1) ), + ) + rhr_rings = ( ( (0, 0), (0, 5), (5, 0), (0, 0) ), + ( (1, 1), (3, 1), (1, 3), (1, 1) ), + ) + State.objects.create(name='Foo', poly=Polygon(*rings)) + s = State.objects.force_rhr().get(name='Foo') + self.assertEqual(rhr_rings, s.force_rhr.coords) + + @no_mysql + @no_oracle + @no_spatialite + def test_geohash(self): + "Testing GeoQuerySet.geohash()." + if not connection.ops.geohash: return + # Reference query: + # SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston'; + # SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston'; + ref_hash = '9vk1mfq8jx0c8e0386z6' + h1 = City.objects.geohash().get(name='Houston') + h2 = City.objects.geohash(precision=5).get(name='Houston') + self.assertEqual(ref_hash, h1.geohash) + self.assertEqual(ref_hash[:5], h2.geohash) + + def test_geojson(self): + "Testing GeoJSON output from the database using GeoQuerySet.geojson()." + # Only PostGIS 1.3.4+ supports GeoJSON. + if not connection.ops.geojson: + self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly') + return + + if connection.ops.spatial_version >= (1, 4, 0): + pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}' + houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}' + victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}' + chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + else: + pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}' + houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}' + victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}' + chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + + # Precision argument should only be an integer + self.assertRaises(TypeError, City.objects.geojson, precision='foo') + + # Reference queries and values. + # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo'; + self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson) + + # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # This time we want to include the CRS by using the `crs` keyword. + self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json) + + # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria'; + # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # This time we include the bounding box by using the `bbox` keyword. + self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson) + + # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago'; + # Finally, we set every available keyword. + self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson) + + def test_gml(self): + "Testing GML output from the database using GeoQuerySet.gml()." + if mysql or (spatialite and not connection.ops.gml) : + self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly') + return + + # Should throw a TypeError when tyring to obtain GML from a + # non-geometry field. + qs = City.objects.all() + self.assertRaises(TypeError, qs.gml, field_name='name') + ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo') + ptown2 = City.objects.gml(precision=9).get(name='Pueblo') + + if oracle: + # No precision parameter for Oracle :-/ + gml_regex = re.compile(r'^-104.60925\d+,38.25500\d+ ') + elif spatialite: + # Spatialite has extra colon in SrsName + gml_regex = re.compile(r'^-104.609251\d+,38.255001') + else: + gml_regex = re.compile(r'^-104\.60925\d+,38\.255001') + + for ptown in [ptown1, ptown2]: + self.assertTrue(gml_regex.match(ptown.gml)) + + def test_kml(self): + "Testing KML output from the database using GeoQuerySet.kml()." + # Only PostGIS and Spatialite (>=2.4.0-RC4) support KML serialization + if not (postgis or (spatialite and connection.ops.kml)): + self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly') + return + + # Should throw a TypeError when trying to obtain KML from a + # non-geometry field. + qs = City.objects.all() + self.assertRaises(TypeError, qs.kml, 'name') + + # The reference KML depends on the version of PostGIS used + # (the output stopped including altitude in 1.3.3). + if connection.ops.spatial_version >= (1, 3, 3): + ref_kml = '-104.609252,38.255001' + else: + ref_kml = '-104.609252,38.255001,0' + + # Ensuring the KML is as expected. + ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo') + ptown2 = City.objects.kml(precision=9).get(name='Pueblo') + for ptown in [ptown1, ptown2]: + self.assertEqual(ref_kml, ptown.kml) + + # Only PostGIS has support for the MakeLine aggregate. + @no_mysql + @no_oracle + @no_spatialite + def test_make_line(self): + "Testing the `make_line` GeoQuerySet method." + # Ensuring that a `TypeError` is raised on models without PointFields. + self.assertRaises(TypeError, State.objects.make_line) + self.assertRaises(TypeError, Country.objects.make_line) + # Reference query: + # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; + ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326) + self.assertEqual(ref_line, City.objects.make_line()) + + @no_mysql + def test_num_geom(self): + "Testing the `num_geom` GeoQuerySet method." + # Both 'countries' only have two geometries. + for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom) + for c in City.objects.filter(point__isnull=False).num_geom(): + # Oracle will return 1 for the number of geometries on non-collections, + # whereas PostGIS will return None. + if postgis: + self.assertEqual(None, c.num_geom) + else: + self.assertEqual(1, c.num_geom) + + @no_mysql + @no_spatialite # SpatiaLite can only count vertices in LineStrings + def test_num_points(self): + "Testing the `num_points` GeoQuerySet method." + for c in Country.objects.num_points(): + self.assertEqual(c.mpoly.num_points, c.num_points) + + if not oracle: + # Oracle cannot count vertices in Point geometries. + for c in City.objects.num_points(): self.assertEqual(1, c.num_points) + + @no_mysql + def test_point_on_surface(self): + "Testing the `point_on_surface` GeoQuerySet method." + # Reference values. + if oracle: + # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY; + ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326), + 'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326), + } + + elif postgis or spatialite: + # Using GEOSGeometry to compute the reference point on surface values + # -- since PostGIS also uses GEOS these should be the same. + ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface, + 'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface + } + + for c in Country.objects.point_on_surface(): + if spatialite: + # XXX This seems to be a WKT-translation-related precision issue? + tol = 0.00001 + else: + tol = 0.000000001 + self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol)) + + @no_mysql + @no_spatialite + def test_reverse_geom(self): + "Testing GeoQuerySet.reverse_geom()." + coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ] + Track.objects.create(name='Foo', line=LineString(coords)) + t = Track.objects.reverse_geom().get(name='Foo') + coords.reverse() + self.assertEqual(tuple(coords), t.reverse_geom.coords) + if oracle: + self.assertRaises(TypeError, State.objects.reverse_geom) + + @no_mysql + @no_oracle + def test_scale(self): + "Testing the `scale` GeoQuerySet method." + xfac, yfac = 2, 3 + tol = 5 # XXX The low precision tolerance is for SpatiaLite + qs = Country.objects.scale(xfac, yfac, model_att='scaled') + for c in qs: + for p1, p2 in zip(c.mpoly, c.scaled): + for r1, r2 in zip(p1, p2): + for c1, c2 in zip(r1.coords, r2.coords): + self.assertAlmostEqual(c1[0] * xfac, c2[0], tol) + self.assertAlmostEqual(c1[1] * yfac, c2[1], tol) + + @no_mysql + @no_oracle + @no_spatialite + def test_snap_to_grid(self): "Testing GeoQuerySet.snap_to_grid()." # Let's try and break snap_to_grid() with bad combinations of arguments. for bad_args in ((), range(3), range(5)): @@ -695,48 +672,78 @@ class GeoModelTest(TestCase): ref = fromstr('MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,12.45 43.87,12.4 43.87)))') self.assertTrue(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23, 0.5, 0.17).get(name='San Marino').snap_to_grid, tol)) + def test_svg(self): + "Testing SVG output using GeoQuerySet.svg()." + if mysql or oracle: + self.assertRaises(NotImplementedError, City.objects.svg) + return + + self.assertRaises(TypeError, City.objects.svg, precision='foo') + # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo'; + svg1 = 'cx="-104.609252" cy="-38.255001"' + # Even though relative, only one point so it's practically the same except for + # the 'c' letter prefix on the x,y values. + svg2 = svg1.replace('c', '') + self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg) + self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg) + @no_mysql - @no_spatialite - def test28_reverse(self): - "Testing GeoQuerySet.reverse_geom()." - coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ] - Track.objects.create(name='Foo', line=LineString(coords)) - t = Track.objects.reverse_geom().get(name='Foo') - coords.reverse() - self.assertEqual(tuple(coords), t.reverse_geom.coords) + def test_transform(self): + "Testing the transform() GeoQuerySet method." + # Pre-transformed points for Houston and Pueblo. + htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) + ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774) + prec = 3 # Precision is low due to version variations in PROJ and GDAL. + + # Asserting the result of the transform operation with the values in + # the pre-transformed points. Oracle does not have the 3084 SRID. + if not oracle: + h = City.objects.transform(htown.srid).get(name='Houston') + self.assertEqual(3084, h.point.srid) + self.assertAlmostEqual(htown.x, h.point.x, prec) + self.assertAlmostEqual(htown.y, h.point.y, prec) + + p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo') + p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo') + for p in [p1, p2]: + self.assertEqual(2774, p.point.srid) + self.assertAlmostEqual(ptown.x, p.point.x, prec) + self.assertAlmostEqual(ptown.y, p.point.y, prec) + + @no_mysql + @no_oracle + def test_translate(self): + "Testing the `translate` GeoQuerySet method." + xfac, yfac = 5, -23 + qs = Country.objects.translate(xfac, yfac, model_att='translated') + for c in qs: + for p1, p2 in zip(c.mpoly, c.translated): + for r1, r2 in zip(p1, p2): + for c1, c2 in zip(r1.coords, r2.coords): + # XXX The low precision is for SpatiaLite + self.assertAlmostEqual(c1[0] + xfac, c2[0], 5) + self.assertAlmostEqual(c1[1] + yfac, c2[1], 5) + + @no_mysql + def test_unionagg(self): + "Testing the `unionagg` (aggregate union) GeoQuerySet method." + tx = Country.objects.get(name='Texas').mpoly + # Houston, Dallas -- Oracle has different order. + union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') + union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') + qs = City.objects.filter(point__within=tx) + self.assertRaises(TypeError, qs.unionagg, 'name') + # Using `field_name` keyword argument in one query and specifying an + # order in the other (which should not be used because this is + # an aggregate method on a spatial column) + u1 = qs.unionagg(field_name='point') + u2 = qs.order_by('name').unionagg() + tol = 0.00001 if oracle: - self.assertRaises(TypeError, State.objects.reverse_geom) - - @no_mysql - @no_oracle - @no_spatialite - def test29_force_rhr(self): - "Testing GeoQuerySet.force_rhr()." - rings = ( ( (0, 0), (5, 0), (0, 5), (0, 0) ), - ( (1, 1), (1, 3), (3, 1), (1, 1) ), - ) - rhr_rings = ( ( (0, 0), (0, 5), (5, 0), (0, 0) ), - ( (1, 1), (3, 1), (1, 3), (1, 1) ), - ) - State.objects.create(name='Foo', poly=Polygon(*rings)) - s = State.objects.force_rhr().get(name='Foo') - self.assertEqual(rhr_rings, s.force_rhr.coords) - - @no_mysql - @no_oracle - @no_spatialite - def test30_geohash(self): - "Testing GeoQuerySet.geohash()." - if not connection.ops.geohash: return - # Reference query: - # SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston'; - # SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston'; - ref_hash = '9vk1mfq8jx0c8e0386z6' - h1 = City.objects.geohash().get(name='Houston') - h2 = City.objects.geohash(precision=5).get(name='Houston') - self.assertEqual(ref_hash, h1.geohash) - self.assertEqual(ref_hash[:5], h2.geohash) - -from .test_feeds import GeoFeedTest -from .test_regress import GeoRegressionTests -from .test_sitemaps import GeoSitemapTest + union = union2 + else: + union = union1 + self.assertEqual(True, union.equals_exact(u1, tol)) + self.assertEqual(True, union.equals_exact(u2, tol)) + qs = City.objects.filter(name='NotACity') + self.assertEqual(None, qs.unionagg(field_name='point')) From 083a3a4e39bc3ead6090e862935d223d8719c4ce Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 3 Aug 2012 05:17:52 -0400 Subject: [PATCH 280/519] Fixed #13904 - Documented how to avoid garbage collection messages in GIS. Thanks Claude Peroz for the patch. --- docs/ref/contrib/gis/geos.txt | 11 +++++++++++ docs/ref/contrib/gis/install.txt | 2 ++ 2 files changed, 13 insertions(+) diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index eda9617381..f4e706d275 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -75,6 +75,17 @@ return a :class:`GEOSGeometry` object from an input string or a file:: >>> pnt = fromfile('/path/to/pnt.wkt') >>> pnt = fromfile(open('/path/to/pnt.wkt')) +.. _geos-exceptions-in-logfile: + +.. admonition:: My logs are filled with GEOS-related errors + + You find many ``TypeError`` or ``AttributeError`` exceptions filling your + Web server's log files. This generally means that you are creating GEOS + objects at the top level of some of your Python modules. Then, due to a race + condition in the garbage collector, your module is garbage collected before + the GEOS object. To prevent this, create :class:`GEOSGeometry` objects + inside the local scope of your functions/methods. + Geometries are Pythonic ----------------------- :class:`GEOSGeometry` objects are 'Pythonic', in other words components may diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 5ee6d5153d..72bd72a6f8 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -191,6 +191,8 @@ GEOS C library. For example: The setting must be the *full* path to the **C** shared library; in other words you want to use ``libgeos_c.so``, not ``libgeos.so``. +See also :ref:`My logs are filled with GEOS-related errors `. + .. _proj4: PROJ.4 From 2407c45c180703f935a2280cb3325beadc2ac8cc Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 3 Aug 2012 11:26:47 +0200 Subject: [PATCH 281/519] Removed some pre-1.3.0 postgis compatibility code --- .../gis/db/backends/postgis/operations.py | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index a6340ce22b..bd249df179 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -163,7 +163,9 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 'contains' : PostGISFunction(prefix, 'Contains'), 'intersects' : PostGISFunction(prefix, 'Intersects'), 'relate' : (PostGISRelate, six.string_types), - } + 'coveredby' : PostGISFunction(prefix, 'CoveredBy'), + 'covers' : PostGISFunction(prefix, 'Covers'), + } # Valid distance types and substitutions dtypes = (Decimal, Distance, float) + six.integer_types @@ -178,33 +180,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 'distance_gte' : (get_dist_ops('>='), dtypes), 'distance_lt' : (get_dist_ops('<'), dtypes), 'distance_lte' : (get_dist_ops('<='), dtypes), - } - - # Versions 1.2.2+ have KML serialization support. - if version < (1, 2, 2): - ASKML = False - else: - ASKML = 'ST_AsKML' - self.geometry_functions.update( - {'coveredby' : PostGISFunction(prefix, 'CoveredBy'), - 'covers' : PostGISFunction(prefix, 'Covers'), - }) - self.distance_functions['dwithin'] = (PostGISFunctionParam(prefix, 'DWithin'), dtypes) + 'dwithin' : (PostGISFunctionParam(prefix, 'DWithin'), dtypes) + } # Adding the distance functions to the geometries lookup. self.geometry_functions.update(self.distance_functions) - # The union aggregate and topology operation use the same signature - # in versions 1.3+. - if version < (1, 3, 0): - UNIONAGG = 'GeomUnion' - UNION = 'Union' - MAKELINE = False - else: - UNIONAGG = 'ST_Union' - UNION = 'ST_Union' - MAKELINE = 'ST_MakeLine' - # Only PostGIS versions 1.3.4+ have GeoJSON serialization support. if version < (1, 3, 4): GEOJSON = False @@ -256,11 +237,11 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.geojson = GEOJSON self.gml = prefix + 'AsGML' self.intersection = prefix + 'Intersection' - self.kml = ASKML + self.kml = prefix + 'AsKML' self.length = prefix + 'Length' self.length3d = prefix + 'Length3D' self.length_spheroid = prefix + 'length_spheroid' - self.makeline = MAKELINE + self.makeline = prefix + 'MakeLine' self.mem_size = prefix + 'mem_size' self.num_geom = prefix + 'NumGeometries' self.num_points =prefix + 'npoints' @@ -275,8 +256,8 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.sym_difference = prefix + 'SymDifference' self.transform = prefix + 'Transform' self.translate = prefix + 'Translate' - self.union = UNION - self.unionagg = UNIONAGG + self.union = prefix + 'Union' + self.unionagg = prefix + 'Union' def check_aggregate_support(self, aggregate): """ From 9908201d7fc3340b83db21298033c5b347f38d65 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 3 Aug 2012 15:18:13 +0200 Subject: [PATCH 282/519] Replaced some byte strings by str() calls This is a useful trick when Python 2 awaits byte strings and Python 3 Unicode (regular) strings. --- django/core/handlers/wsgi.py | 2 +- django/db/backends/sqlite3/base.py | 16 ++++++++-------- django/db/models/base.py | 4 ++-- django/forms/formsets.py | 2 +- django/forms/models.py | 4 ++-- django/http/__init__.py | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 617068a21c..51ad2be002 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -245,6 +245,6 @@ class WSGIHandler(base.BaseHandler): status = '%s %s' % (response.status_code, status_text) response_headers = [(str(k), str(v)) for k, v in response.items()] for c in response.cookies.values(): - response_headers.append((b'Set-Cookie', str(c.output(header='')))) + response_headers.append((str('Set-Cookie'), str(c.output(header='')))) start_response(smart_str(status), response_headers) return response diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 0a97449789..0880079189 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -54,15 +54,15 @@ def adapt_datetime_with_timezone_support(value): default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) value = value.astimezone(timezone.utc).replace(tzinfo=None) - return value.isoformat(b" ") + return value.isoformat(str(" ")) -Database.register_converter(b"bool", lambda s: str(s) == '1') -Database.register_converter(b"time", parse_time) -Database.register_converter(b"date", parse_date) -Database.register_converter(b"datetime", parse_datetime_with_timezone_support) -Database.register_converter(b"timestamp", parse_datetime_with_timezone_support) -Database.register_converter(b"TIMESTAMP", parse_datetime_with_timezone_support) -Database.register_converter(b"decimal", util.typecast_decimal) +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) 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): diff --git a/django/db/models/base.py b/django/db/models/base.py index 002e2aff65..8dd5bf864f 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -63,12 +63,12 @@ class ModelBase(type): new_class.add_to_class('_meta', Options(meta, **kwargs)) if not abstract: - new_class.add_to_class('DoesNotExist', subclass_exception(b'DoesNotExist', + new_class.add_to_class('DoesNotExist', subclass_exception(str('DoesNotExist'), tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), module, attached_to=new_class)) - new_class.add_to_class('MultipleObjectsReturned', subclass_exception(b'MultipleObjectsReturned', + new_class.add_to_class('MultipleObjectsReturned', subclass_exception(str('MultipleObjectsReturned'), tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 240cf71f43..1ec8340462 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -365,7 +365,7 @@ def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete, 'max_num': max_num} - return type(form.__name__ + b'FormSet', (formset,), attrs) + return type(form.__name__ + str('FormSet'), (formset,), attrs) def all_valid(formsets): """Returns true if every formset in formsets is valid.""" diff --git a/django/forms/models.py b/django/forms/models.py index 4d56f1d38a..b5939b6be3 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -389,10 +389,10 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, parent = (object,) if hasattr(form, 'Meta'): parent = (form.Meta, object) - Meta = type(b'Meta', parent, attrs) + Meta = type(str('Meta'), parent, attrs) # Give this new form class a reasonable name. - class_name = model.__name__ + b'Form' + class_name = model.__name__ + str('Form') # Class attributes for the new form class. form_class_attrs = { diff --git a/django/http/__init__.py b/django/http/__init__.py index 19b581f5cb..4c2db74890 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -22,7 +22,7 @@ _cookie_encodes_correctly = http_cookies.SimpleCookie().value_encode(';') == ('; # See ticket #13007, http://bugs.python.org/issue2193 and http://trac.edgewall.org/ticket/2256 _tc = http_cookies.SimpleCookie() try: - _tc.load(b'foo:bar=1') + _tc.load(str('foo:bar=1')) _cookie_allows_colon_in_names = True except http_cookies.CookieError: _cookie_allows_colon_in_names = False From 5aec69ed290552e7ea20bdef416c5bc23f6231f8 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 3 Aug 2012 15:40:29 +0200 Subject: [PATCH 283/519] Documented the trick used in 9908201d7f. --- docs/topics/python3.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 3f799edac7..b09c1d2347 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -36,6 +36,11 @@ 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:: + + str('my string') + Be cautious if you have to `slice bytestrings`_. .. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals From 129f1ac8484d63c2e61a44fb2a18dd17246c1c4d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 3 Aug 2012 07:10:04 -0700 Subject: [PATCH 284/519] Remove a temporary variable deletion, it's not a big deal and it doesn't exist on python3. --- django/utils/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/utils/html.py b/django/utils/html.py index ca3340ccb1..e1263fbd66 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -33,7 +33,7 @@ link_target_attribute_re = re.compile(r'(]*?)target=[^\s>]+') html_gunk_re = re.compile(r'(?:
      |<\/i>|<\/b>|<\/em>|<\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE) hard_coded_bullets_re = re.compile(r'((?: