From 108f8dddeae66efd47c13afa6033f36b6fd39c8f Mon Sep 17 00:00:00 2001 From: Daniel Hepper Date: Fri, 8 Jun 2012 15:28:07 +0200 Subject: [PATCH 001/176] Fixed #18450 -- Removed default values for i18n JavaScript URLs in Admin templates --- .../admin/templates/admin/auth/user/change_password.html | 3 +-- django/contrib/admin/templates/admin/change_form.html | 3 +-- django/contrib/admin/templates/admin/change_list.html | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html index 4fdccd13d2..f367223bc5 100644 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -3,8 +3,7 @@ {% load admin_urls %} {% block extrahead %}{{ block.super }} -{% url 'admin:jsi18n' as jsi18nurl %} - + {% endblock %} {% block extrastyle %}{{ block.super }}{% endblock %} {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index e455793f68..fd54924615 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -3,8 +3,7 @@ {% load admin_urls %} {% block extrahead %}{{ block.super }} -{% url 'admin:jsi18n' as jsi18nurl %} - + {{ media }} {% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 3c73ac8745..c72b6630a3 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -9,8 +9,7 @@ {% endif %} {% if cl.formset or action_form %} - {% url 'admin:jsi18n' as jsi18nurl %} - + {% endif %} {{ media.css }} {% if not actions_on_top and not actions_on_bottom %} From aee9eecb920cf281e8339a5f7edadc6f2dd04fea Mon Sep 17 00:00:00 2001 From: Daniel Hepper Date: Fri, 8 Jun 2012 12:32:16 +0200 Subject: [PATCH 002/176] Fixed #18444 -- Replace hard coded "View on Site" URLs --- django/contrib/admin/sites.py | 3 ++- .../admin/templates/admin/change_form.html | 2 +- .../templates/admin/edit_inline/stacked.html | 2 +- .../templates/admin/edit_inline/tabular.html | 2 +- docs/ref/contrib/admin/index.txt | 21 ++++++++++--------- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 4bb6440877..515cc33eca 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -231,7 +231,8 @@ class AdminSite(object): wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), url(r'^r/(?P\d+)/(?P.+)/$', - wrap(contenttype_views.shortcut)), + wrap(contenttype_views.shortcut), + name='view_on_site'), url(r'^(?P\w+)/$', wrap(self.app_index), name='app_list') diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index fd54924615..82d7296c85 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -30,7 +30,7 @@ {% endif %}{% endif %} diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 55463d97b1..d57d3dad49 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -6,7 +6,7 @@ {% for inline_admin_form in inline_admin_formset %}

{{ inline_admin_formset.opts.verbose_name|title }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %} - {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} + {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %}

{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index d5ac9b0fb6..4f49153819 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -27,7 +27,7 @@ {% if inline_admin_form.original or inline_admin_form.show_url %}

{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %} - {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} + {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %}

{% endif %} {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %} {{ inline_admin_form.fk_field.field }} diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 3ef9abe6da..7aca0981c3 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1948,16 +1948,17 @@ accessible using Django's :ref:`URL reversing system `. The :class:`AdminSite` provides the following named URL patterns: -====================== ======================== ============= -Page URL name Parameters -====================== ======================== ============= -Index ``index`` -Logout ``logout`` -Password change ``password_change`` -Password change done ``password_change_done`` -i18n javascript ``jsi18n`` -Application index page ``app_list`` ``app_label`` -====================== ======================== ============= +========================= ======================== ================================== +Page URL name Parameters +========================= ======================== ================================== +Index ``index`` +Logout ``logout`` +Password change ``password_change`` +Password change done ``password_change_done`` +i18n javascript ``jsi18n`` +Application index page ``app_list`` ``app_label`` +Redirect to object's page ``view_on_site`` ``content_type_id``, ``object_id`` +========================= ======================== ================================== Each :class:`ModelAdmin` instance provides an additional set of named URLs: From ffa6d95f65363b7f4f9047ab11561880be29049a Mon Sep 17 00:00:00 2001 From: Gabe Jackson Date: Thu, 7 Jun 2012 14:08:46 +0200 Subject: [PATCH 003/176] Fixed #18154 -- Documentation on closing File objects and best practices --- docs/topics/files.txt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 9ab8d5c496..c9b4327941 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -75,6 +75,29 @@ using a Python built-in ``file`` object:: Now you can use any of the documented attributes and methods of the :class:`~django.core.files.File` class. +Be aware that files created in this way are not automatically closed. +The following approach may be used to close files automatically:: + + >>> from django.core.files import File + + # Create a Python file object using open() and the with statement + >>> with open('/tmp/hello.world', 'w') as f: + >>> myfile = File(f) + >>> for line in myfile: + >>> print line + >>> myfile.closed + True + >>> f.closed + True + +Closing files is especially important when accessing file fields in a loop +over a large number of objects:: If files are not manually closed after +accessing them, the risk of running out of file descriptors may arise. This +may lead to the following error: + + IOError: [Errno 24] Too many open files + + File storage ============ From c57ba673312cb5774d544353044e2182b6223040 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 19 Jun 2012 10:49:30 +1200 Subject: [PATCH 004/176] Fixed #14502 again -- saner verbatim closing token Previously, the closing token for the verbatim tag was specified as the first argument of the opening token. As pointed out by Jannis, this is a rather major departure from the core tag standard. The new method reflects how you can give a specific closing name to {% block %} tags. --- django/template/base.py | 9 ++------- django/template/defaulttags.py | 14 +++++--------- docs/ref/templates/builtins.txt | 10 +++++----- tests/regressiontests/templates/tests.py | 2 +- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 5a91bfda99..89bc90971f 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -216,13 +216,8 @@ class Lexer(object): if token_string.startswith(VARIABLE_TAG_START): token = Token(TOKEN_VAR, token_string[2:-2].strip()) elif token_string.startswith(BLOCK_TAG_START): - 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' + if block_content[:9] in ('verbatim', 'verbatim '): + self.verbatim = 'end%s' % block_content token = Token(TOKEN_BLOCK, block_content) elif token_string.startswith(COMMENT_TAG_START): content = '' diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 0de5d9e3db..83b72e120b 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -1291,18 +1291,14 @@ def verbatim(parser, token): {% don't process this %} {% endverbatim %} - You can also specify an alternate closing tag:: + You can also designate a specific closing tag block (allowing the + unrendered use of ``{% endverbatim %}``):: - {% verbatim -- %} + {% verbatim myblock %} ... - {% -- %} + {% endverbatim myblock %} """ - bits = token.contents.split(' ', 1) - if len(bits) > 1: - closing_tag = bits[1] - else: - closing_tag = 'endverbatim' - nodelist = parser.parse((closing_tag,)) + nodelist = parser.parse(('endverbatim',)) parser.delete_first_token() return VerbatimNode(nodelist.render(Context())) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 6f341e9f97..cf228d72f6 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1047,12 +1047,12 @@ Django's syntax. For example:: {{if dying}}Still alive.{{/if}} {% endverbatim %} -You can also specify an alternate closing tag:: +You can also designate a specific closing tag, allowing the use of +``{% endverbatim %}`` as part of the unrendered contents:: - {% verbatim finished %} - The verbatim tag looks like this: - {% verbatim %}{% endverbatim %} - {% finished %} + {% verbatim myblock %} + Avoid template rendering via the {% verbatim %}{% endverbatim %} block. + {% endverbatim myblock %} .. templatetag:: widthratio diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 989fd72d94..35d01221ab 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1623,7 +1623,7 @@ class Templates(unittest.TestCase): '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"), + 'verbatim-tag06': ("{% verbatim special %}Don't {% endverbatim %} just yet{% endverbatim special %}", {}, "Don't {% endverbatim %} just yet"), } return tests From f8ef93a6576837ef3598066d6ed715171a91cd04 Mon Sep 17 00:00:00 2001 From: Tim Saylor Date: Wed, 13 Jun 2012 13:42:18 -0500 Subject: [PATCH 005/176] Fixed a documentation typo on the widget page. --- docs/ref/forms/widgets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 88d0d706cd..fb7657349a 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -75,7 +75,7 @@ changing :attr:`ChoiceField.choices` will update :attr:`Select.choices`. For example:: >>> from django import forms - >>> CHOICES = (('1', 'First',), ('2', 'Second',))) + >>> CHOICES = (('1', 'First',), ('2', 'Second',)) >>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES) >>> choice_field.choices [('1', 'First'), ('2', 'Second')] From 45a1a54b0b234996a9880bd89924df2aa86b8f9a Mon Sep 17 00:00:00 2001 From: danger Date: Sun, 10 Jun 2012 23:59:50 +0200 Subject: [PATCH 006/176] Added support for gdal 1.9. --- django/contrib/gis/gdal/libgdal.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index f991388835..69643371d2 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -15,31 +15,32 @@ if lib_path: lib_names = None elif os.name == 'nt': # Windows NT shared libraries - lib_names = ['gdal18', 'gdal17', 'gdal16', 'gdal15'] + lib_names = ['gdal19', 'gdal18', 'gdal17', 'gdal16', 'gdal15'] elif os.name == 'posix': # *NIX library names. - lib_names = ['gdal', 'GDAL', 'gdal1.8.0', 'gdal1.7.0', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] + lib_names = ['gdal', 'GDAL', 'gdal1.9.0', 'gdal1.8.0', 'gdal1.7.0', + 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] else: raise OGRException('Unsupported OS "%s"' % os.name) -# Using the ctypes `find_library` utility to find the +# Using the ctypes `find_library` utility to find the # path to the GDAL library from the list of library names. if lib_names: for lib_name in lib_names: lib_path = find_library(lib_name) if not lib_path is None: break - + if lib_path is None: raise OGRException('Could not find the GDAL library (tried "%s"). ' - 'Try setting GDAL_LIBRARY_PATH in your settings.' % + 'Try setting GDAL_LIBRARY_PATH in your settings.' % '", "'.join(lib_names)) # This loads the GDAL/OGR C library lgdal = CDLL(lib_path) -# On Windows, the GDAL binaries have some OSR routines exported with -# STDCALL, while others are not. Thus, the library will also need to -# be loaded up as WinDLL for said OSR functions that require the +# On Windows, the GDAL binaries have some OSR routines exported with +# STDCALL, while others are not. Thus, the library will also need to +# be loaded up as WinDLL for said OSR functions that require the # different calling convention. if os.name == 'nt': from ctypes import WinDLL @@ -66,11 +67,11 @@ def gdal_version(): "Returns only the GDAL version number information." return _version_info('RELEASE_NAME') -def gdal_full_version(): +def gdal_full_version(): "Returns the full GDAL version information." return _version_info('') -def gdal_release_date(date=False): +def gdal_release_date(date=False): """ Returns the release date in a string format, e.g, "2007/06/27". If the date keyword argument is set to True, a Python datetime object From 41eb70f7620dbae43bb72f265489fa3ac19c5c66 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 19 Jun 2012 14:55:40 +0200 Subject: [PATCH 007/176] Fixed #15271 -- Defined a to_python method for GeometryField Thanks volrath and copelco for their work on the patch. --- django/contrib/gis/forms/fields.py | 18 ++++++++++++------ django/contrib/gis/tests/test_geoforms.py | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index 432f0e1872..cefb6830ba 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ # While this couples the geographic forms to the GEOS library, # it decouples from database (by not importing SpatialBackend). -from django.contrib.gis.geos import GEOSGeometry +from django.contrib.gis.geos import GEOSException, GEOSGeometry class GeometryField(forms.Field): """ @@ -31,6 +31,15 @@ class GeometryField(forms.Field): self.null = kwargs.pop('null', True) super(GeometryField, self).__init__(**kwargs) + def to_python(self, value): + """ + Transforms the value to a Geometry object. + """ + try: + return GEOSGeometry(value) + except (GEOSException, ValueError, TypeError): + raise forms.ValidationError(self.error_messages['invalid_geom']) + def clean(self, value): """ Validates that the input value can be converted to a Geometry @@ -44,11 +53,8 @@ class GeometryField(forms.Field): else: raise forms.ValidationError(self.error_messages['no_geom']) - # Trying to create a Geometry object from the form value. - try: - geom = GEOSGeometry(value) - except: - raise forms.ValidationError(self.error_messages['invalid_geom']) + # Transform the value to a python object first + geom = self.to_python(value) # Ensuring that the geometry is of the correct type (indicated # using the OGC string label). diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py index 9d2b7c7c3c..ed851df0d2 100644 --- a/django/contrib/gis/tests/test_geoforms.py +++ b/django/contrib/gis/tests/test_geoforms.py @@ -56,8 +56,25 @@ class GeometryFieldTest(unittest.TestCase): pnt_fld = forms.GeometryField(geom_type='POINT') self.assertEqual(GEOSGeometry('POINT(5 23)'), pnt_fld.clean('POINT(5 23)')) + # a WKT for any other geom_type will be properly transformed by `to_python` + self.assertEqual(GEOSGeometry('LINESTRING(0 0, 1 1)'), pnt_fld.to_python('LINESTRING(0 0, 1 1)')) + # but rejected by `clean` self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)') + def test04_to_python(self): + """ + Testing to_python returns a correct GEOSGeometry object or + a ValidationError + """ + fld = forms.GeometryField() + # to_python returns the same GEOSGeometry for a WKT + 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 + 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) + + def suite(): s = unittest.TestSuite() s.addTest(unittest.makeSuite(GeometryFieldTest)) From f08fa5b5556e6a3e81fdd644f5b7bcc9e795821a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 19 Jun 2012 17:37:28 +0200 Subject: [PATCH 008/176] Removed unneeded u prefixes --- tests/modeltests/fixtures_model_package/tests.py | 2 +- tests/regressiontests/admin_views/models.py | 2 +- tests/regressiontests/forms/tests/fields.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/modeltests/fixtures_model_package/tests.py index 17538ed7e8..d147fe68a7 100644 --- a/tests/modeltests/fixtures_model_package/tests.py +++ b/tests/modeltests/fixtures_model_package/tests.py @@ -40,7 +40,7 @@ class TestNoInitialDataLoading(TransactionTestCase): # Test presence of fixture (flush called by TransactionTestCase) self.assertQuerysetEqual( Book.objects.all(), [ - u'Achieving self-awareness of Python programs' + 'Achieving self-awareness of Python programs' ], lambda a: a.name ) diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index ebb637ac6b..ab2bc202f9 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -101,7 +101,7 @@ class ModelWithStringPrimaryKey(models.Model): return self.string_pk def get_absolute_url(self): - return u'/dummy/%s/' % self.string_pk + return '/dummy/%s/' % self.string_pk class Color(models.Model): diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index ebeb19c8fc..12eb016c6e 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -486,7 +486,7 @@ class FieldsTests(SimpleTestCase): Refs #. """ f = RegexField('^\w+$') - self.assertEqual(u'éèøçÎÎ你好', f.clean(u'éèøçÎÎ你好')) + self.assertEqual('éèøçÎÎ你好', f.clean('éèøçÎÎ你好')) def test_change_regex_after_init(self): f = RegexField('^[a-z]+$') From a54a8bab0c6a96c03452040e92b2a3141695a363 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 22 Jun 2012 13:28:15 +0100 Subject: [PATCH 009/176] Fixed #17776 - DoesNotExist is not picklable Thanks to ambv for the report --- django/db/models/base.py | 39 ++++++++++++++++--- .../regressiontests/queryset_pickle/tests.py | 10 +++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index f52a626d8b..b2d92a2aee 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -61,12 +61,14 @@ class ModelBase(type): if not abstract: new_class.add_to_class('DoesNotExist', subclass_exception(b'DoesNotExist', tuple(x.DoesNotExist - for x in parents if hasattr(x, '_meta') and not x._meta.abstract) - or (ObjectDoesNotExist,), module)) + 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', tuple(x.MultipleObjectsReturned - for x in parents if hasattr(x, '_meta') and not x._meta.abstract) - or (MultipleObjectsReturned,), module)) + for x in parents if hasattr(x, '_meta') and not x._meta.abstract) + or (MultipleObjectsReturned,), + module, attached_to=new_class)) if base_meta and not base_meta.abstract: # Non-abstract child classes inherit some attributes from their # non-abstract parent (unless an ABC comes before it in the @@ -934,5 +936,30 @@ def model_unpickle(model, attrs, factory): return cls.__new__(cls) model_unpickle.__safe_for_unpickle__ = True -def subclass_exception(name, parents, module): - return type(name, parents, {'__module__': module}) +def subclass_exception(name, parents, module, attached_to=None): + """ + Create exception subclass. + + If 'attached_to' is supplied, the exception will be created in a way that + allows it to be pickled, assuming the returned exception class will be added + as an attribute to the 'attached_to' class. + """ + class_dict = {'__module__': module} + if attached_to is not None: + def __reduce__(self): + # Exceptions are special - they've got state that isn't + # in self.__dict__. We assume it is all in self.args. + return (unpickle_inner_exception, (attached_to, name), self.args) + + def __setstate__(self, args): + self.args = args + + class_dict['__reduce__'] = __reduce__ + class_dict['__setstate__'] = __setstate__ + + return type(name, parents, class_dict) + +def unpickle_inner_exception(klass, exception_name): + # Get the exception class from the class it is attached to: + exception = getattr(klass, exception_name) + return exception.__new__(exception) diff --git a/tests/regressiontests/queryset_pickle/tests.py b/tests/regressiontests/queryset_pickle/tests.py index f73e61a900..ab32e8f647 100644 --- a/tests/regressiontests/queryset_pickle/tests.py +++ b/tests/regressiontests/queryset_pickle/tests.py @@ -36,3 +36,13 @@ class PickleabilityTestCase(TestCase): def test_membermethod_as_default(self): self.assert_pickles(Happening.objects.filter(number4=1)) + + def test_doesnotexist_exception(self): + # Ticket #17776 + original = Event.DoesNotExist("Doesn't exist") + unpickled = pickle.loads(pickle.dumps(original)) + + # Exceptions are not equal to equivalent instances of themselves, so + # can't just use assertEqual(original, unpickled) + self.assertEqual(original.__class__, unpickled.__class__) + self.assertEqual(original.args, unpickled.args) From 6bc1b222994301782bd80780bdeec8c4eb44631a Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sat, 16 Jun 2012 20:49:50 +0200 Subject: [PATCH 010/176] Fixed our HTMLParser patches for python 2.7.4 --- django/utils/html_parser.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py index b28005705e..98f6545c41 100644 --- a/django/utils/html_parser.py +++ b/django/utils/html_parser.py @@ -1,6 +1,7 @@ import HTMLParser as _HTMLParser import re +tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*') class HTMLParser(_HTMLParser.HTMLParser): """ @@ -33,10 +34,10 @@ class HTMLParser(_HTMLParser.HTMLParser): # Now parse the data between i+1 and j into a tag and attrs attrs = [] - match = _HTMLParser.tagfind.match(rawdata, i + 1) + match = tagfind.match(rawdata, i + 1) assert match, 'unexpected call to parse_starttag()' k = match.end() - self.lasttag = tag = rawdata[i + 1:k].lower() + self.lasttag = tag = match.group(1).lower() while k < endpos: m = _HTMLParser.attrfind.match(rawdata, k) @@ -48,6 +49,7 @@ class HTMLParser(_HTMLParser.HTMLParser): elif attrvalue[:1] == '\'' == attrvalue[-1:] or \ attrvalue[:1] == '"' == attrvalue[-1:]: attrvalue = attrvalue[1:-1] + if attrvalue: attrvalue = self.unescape(attrvalue) attrs.append((attrname.lower(), attrvalue)) k = m.end() From 7f225880e4b7f846a1e910f6be0bce11f9a8d5ec Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 22 Jun 2012 15:43:57 +0200 Subject: [PATCH 011/176] Corrected the `instance_dict` description for form wizards. --- django/contrib/formtools/wizard/views.py | 5 +++-- docs/ref/contrib/formtools/form-wizard.txt | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index 4372c8aa6a..6222d1ddab 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -133,8 +133,9 @@ class WizardView(TemplateView): The key should be equal to the `step_name` in the `form_list` (or the str of the zero based counter - if no step_names added in the `form_list`) - * `instance_dict` - contains a dictionary of instance objects. This - is only used when `ModelForm`s are used. The key should be equal to + * `instance_dict` - contains a dictionary whose values are model + instances if the step is based on a ``ModelForm`` and querysets if + the step is based on a ``ModelFormSet``. The key should be equal to the `step_name` in the `form_list`. Same rules as for `initial_dict` apply. * `condition_dict` - contains a dictionary of boolean values or diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 7aafbe89f3..7d229a5d66 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -554,9 +554,8 @@ How to work with ModelForm and ModelFormSet WizardView supports :doc:`ModelForms ` and :ref:`ModelFormSets `. Additionally to :attr:`~WizardView.initial_dict`, the :meth:`~WizardView.as_view` method takes -an ``instance_dict`` argument that should contain instances of ``ModelForm`` and -``ModelFormSet``. Similarly to :attr:`~WizardView.initial_dict`, these -dictionary key values should be equal to the step number in the form list. +an ``instance_dict`` argument that should contain model instances for steps +based on ``ModelForm`` and querysets for steps based on ``ModelFormSet``. Usage of ``NamedUrlWizardView`` =============================== From ea9536b17fe16b2be45aa4a3552f919682c93e3e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Jun 2012 19:00:40 -0700 Subject: [PATCH 012/176] Note that Jython has an alpha with 2.7 support. --- docs/releases/1.5.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 3e274b5d98..719433f8fe 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -27,8 +27,9 @@ Django 1.4 until you can upgrade your Python version. Per :doc:`our support poli `, Django 1.4 will continue to receive security support until the release of Django 1.6. -Django 1.5 does not run on Jython, because Jython doesn't currently offer any -version compatible with Python 2.6. +Django 1.5 does not run on a Jython final release, because Jython's latest release +doesn't currently support Python 2.6. However, Jython currently does offer an alpha +release featuring 2.7 support. What's new in Django 1.5 ======================== From c864b36ba12f195cb1d3f9b1ad27ad71d5d8b5ea Mon Sep 17 00:00:00 2001 From: jnns Date: Thu, 21 Jun 2012 12:37:12 +0300 Subject: [PATCH 013/176] Updated TEMPLATE_CONTEXT_PROCESSORS defaults in the docs. django.core.context_processors.tz was missing from default TEMPLATE_CONTEXT_PROCESSORS in the template api documentation. --- docs/ref/templates/api.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 6142dd7017..7816e1d07d 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -370,6 +370,7 @@ and return a dictionary of items to be merged into the context. By default, "django.core.context_processors.i18n", "django.core.context_processors.media", "django.core.context_processors.static", + "django.core.context_processors.tz", "django.contrib.messages.context_processors.messages") In addition to these, ``RequestContext`` always uses From 946d3d9f84ea7979a4abf0857e4aa7ee33576303 Mon Sep 17 00:00:00 2001 From: Bojan Mihelac Date: Tue, 19 Jun 2012 14:42:02 +0300 Subject: [PATCH 014/176] Fixed url translation docs. ``include`` calls shouldn't have a $ sign at the end of the url pattern. --- docs/topics/i18n/translation.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 896ff87744..ec6159d538 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -890,7 +890,7 @@ prepend the current active language code to all url patterns defined within urlpatterns += i18n_patterns('', url(r'^about/$', 'about.view', name='about'), - url(r'^news/$', include(news_patterns, namespace='news')), + url(r'^news/', include(news_patterns, namespace='news')), ) @@ -945,7 +945,7 @@ URL patterns can also be marked translatable using the urlpatterns += i18n_patterns('', url(_(r'^about/$'), 'about.view', name='about'), - url(_(r'^news/$'), include(news_patterns, namespace='news')), + url(_(r'^news/'), include(news_patterns, namespace='news')), ) From d4da08375b634544b95859d4d4667b8f05e3a29a Mon Sep 17 00:00:00 2001 From: Dmitry Medvinsky Date: Fri, 8 Jun 2012 14:00:51 +0400 Subject: [PATCH 015/176] Fixed #18454 -- Added ability to pass a list of signals to `receiver`. Added ability to use receiver decorator in the following way: @receiver([post_save, post_delete], sender=MyModel) def signals_receiver(sender, **kwargs): ... --- django/dispatch/dispatcher.py | 11 +++++-- docs/releases/1.5.txt | 3 ++ docs/topics/signals.txt | 13 ++++++-- .../dispatch/tests/__init__.py | 2 +- .../dispatch/tests/test_dispatcher.py | 30 ++++++++++++++++++- 5 files changed, 52 insertions(+), 7 deletions(-) diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index 54e71c01cc..8f57b185c3 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -257,14 +257,21 @@ class Signal(object): def receiver(signal, **kwargs): """ A decorator for connecting receivers to signals. Used by passing in the - signal and keyword arguments to connect:: + signal (or list of signals) and keyword arguments to connect:: @receiver(post_save, sender=MyModel) def signal_receiver(sender, **kwargs): ... + @receiver([post_save, post_delete], sender=MyModel) + def signals_receiver(sender, **kwargs): + ... + """ def _decorator(func): - signal.connect(func, **kwargs) + if isinstance(signal, (list, tuple)): + [s.connect(func, **kwargs) for s in signal] + else: + signal.connect(func, **kwargs) return func return _decorator diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 719433f8fe..2f20f5f9f9 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -103,6 +103,9 @@ Django 1.5 also includes several smaller improvements worth noting: * In the localflavor for Canada, "pq" was added to the acceptable codes for Quebec. It's an old abbreviation. +* The :ref:`receiver ` decorator is now able to + connect to more than one signal by supplying a list of signals. + Backwards incompatible changes in 1.5 ===================================== diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index 3ef68316a9..fa668cc8c7 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -52,10 +52,10 @@ called when the signal is sent by using the :meth:`.Signal.connect` method: .. method:: Signal.connect(receiver, [sender=None, weak=True, dispatch_uid=None]) - + :param receiver: The callback function which will be connected to this signal. See :ref:`receiver-functions` for more information. - + :param sender: Specifies a particular sender to receive signals from. See :ref:`connecting-to-specific-signals` for more information. @@ -129,10 +129,17 @@ receiver: Now, our ``my_callback`` function will be called each time a request finishes. +Note that ``receiver`` can also take a list of signals to connect a function +to. + .. versionadded:: 1.3 The ``receiver`` decorator was added in Django 1.3. +.. versionchanged:: 1.5 + +The ability to pass a list of signals was added. + .. admonition:: Where should this code live? You can put signal handling and registration code anywhere you like. @@ -182,7 +189,7 @@ Preventing duplicate signals In some circumstances, the module in which you are connecting signals may be imported multiple times. This can cause your receiver function to be registered more than once, and thus called multiples times for a single signal -event. +event. If this behavior is problematic (such as when using signals to send an email whenever a model is saved), pass a unique identifier as diff --git a/tests/regressiontests/dispatch/tests/__init__.py b/tests/regressiontests/dispatch/tests/__init__.py index 447975ab85..b6d26217e1 100644 --- a/tests/regressiontests/dispatch/tests/__init__.py +++ b/tests/regressiontests/dispatch/tests/__init__.py @@ -4,5 +4,5 @@ Unit-tests for the dispatch project from __future__ import absolute_import -from .test_dispatcher import DispatcherTests +from .test_dispatcher import DispatcherTests, ReceiverTestCase from .test_saferef import SaferefTests diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py index 319d6553a0..5f7094d5fa 100644 --- a/tests/regressiontests/dispatch/tests/test_dispatcher.py +++ b/tests/regressiontests/dispatch/tests/test_dispatcher.py @@ -2,7 +2,7 @@ import gc import sys import time -from django.dispatch import Signal +from django.dispatch import Signal, receiver from django.utils import unittest @@ -33,6 +33,8 @@ class Callable(object): return val a_signal = Signal(providing_args=["val"]) +b_signal = Signal(providing_args=["val"]) +c_signal = Signal(providing_args=["val"]) class DispatcherTests(unittest.TestCase): """Test suite for dispatcher (barely started)""" @@ -123,3 +125,29 @@ class DispatcherTests(unittest.TestCase): garbage_collect() a_signal.disconnect(receiver_3) self._testIsClean(a_signal) + + +class ReceiverTestCase(unittest.TestCase): + """ + Test suite for receiver. + + """ + def testReceiverSingleSignal(self): + @receiver(a_signal) + def f(val, **kwargs): + self.state = val + self.state = False + a_signal.send(sender=self, val=True) + self.assertTrue(self.state) + + def testReceiverSignalList(self): + @receiver([a_signal, b_signal, c_signal]) + def f(val, **kwargs): + self.state.append(val) + self.state = [] + a_signal.send(sender=self, val='a') + c_signal.send(sender=self, val='c') + b_signal.send(sender=self, val='b') + self.assertIn('a', self.state) + self.assertIn('b', self.state) + self.assertIn('c', self.state) From e1b74d00945ae772300432a51a71e735143c8905 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 23 Jun 2012 07:54:45 -0700 Subject: [PATCH 016/176] Don't use a list comprehension when we don't need the resulting list. --- django/dispatch/dispatcher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index 8f57b185c3..b935df3c7f 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -270,7 +270,8 @@ def receiver(signal, **kwargs): """ def _decorator(func): if isinstance(signal, (list, tuple)): - [s.connect(func, **kwargs) for s in signal] + for s in signal: + s.connect(func, **kwargs) else: signal.connect(func, **kwargs) return func From e0fce8706d31f104f781676b49e7fe0df83438e0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 23 Jun 2012 08:11:15 -0700 Subject: [PATCH 017/176] Switch to using context managers for acquiring and releasing locks. --- django/core/handlers/wsgi.py | 5 +---- django/core/mail/backends/console.py | 28 +++++++++++++--------------- django/core/mail/backends/smtp.py | 5 +---- django/db/models/loading.py | 10 ++-------- django/dispatch/dispatcher.py | 17 ++++------------- tests/regressiontests/mail/tests.py | 20 +++++++------------- 6 files changed, 28 insertions(+), 57 deletions(-) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 5215101a35..617068a21c 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -210,8 +210,7 @@ class WSGIHandler(base.BaseHandler): # Set up middleware if needed. We couldn't do this earlier, because # settings weren't available. if self._request_middleware is None: - self.initLock.acquire() - try: + with self.initLock: try: # Check that middleware is still uninitialised. if self._request_middleware is None: @@ -220,8 +219,6 @@ class WSGIHandler(base.BaseHandler): # Unload whatever middleware we got self._request_middleware = None raise - finally: - self.initLock.release() set_script_prefix(base.get_script_name(environ)) signals.request_started.send(sender=self.__class__) diff --git a/django/core/mail/backends/console.py b/django/core/mail/backends/console.py index 705497520a..0baae0c01e 100644 --- a/django/core/mail/backends/console.py +++ b/django/core/mail/backends/console.py @@ -16,19 +16,17 @@ class EmailBackend(BaseEmailBackend): """Write all messages to the stream in a thread-safe way.""" if not email_messages: return - self._lock.acquire() - try: - stream_created = self.open() - for message in email_messages: - self.stream.write('%s\n' % message.message().as_string()) - self.stream.write('-'*79) - self.stream.write('\n') - self.stream.flush() # flush after each message - if stream_created: - self.close() - except: - if not self.fail_silently: - raise - finally: - self._lock.release() + with self._lock: + try: + stream_created = self.open() + for message in email_messages: + self.stream.write('%s\n' % message.message().as_string()) + self.stream.write('-'*79) + self.stream.write('\n') + self.stream.flush() # flush after each message + if stream_created: + self.close() + except: + if not self.fail_silently: + raise return len(email_messages) diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py index 3ee283b5f1..18437c6282 100644 --- a/django/core/mail/backends/smtp.py +++ b/django/core/mail/backends/smtp.py @@ -80,8 +80,7 @@ class EmailBackend(BaseEmailBackend): """ if not email_messages: return - self._lock.acquire() - try: + with self._lock: new_conn_created = self.open() if not self.connection: # We failed silently on open(). @@ -94,8 +93,6 @@ class EmailBackend(BaseEmailBackend): num_sent += 1 if new_conn_created: self.close() - finally: - self._lock.release() return num_sent def _send(self, email_message): diff --git a/django/db/models/loading.py b/django/db/models/loading.py index c34468643f..cbf62602d2 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -54,8 +54,7 @@ class AppCache(object): """ if self.loaded: return - self.write_lock.acquire() - try: + with self.write_lock: if self.loaded: return for app_name in settings.INSTALLED_APPS: @@ -66,8 +65,6 @@ class AppCache(object): for app_name in self.postponed: self.load_app(app_name) self.loaded = True - finally: - self.write_lock.release() def _label_for(self, app_mod): """ @@ -138,8 +135,7 @@ class AppCache(object): the app has no models in it and 'emptyOK' is True, returns None. """ self._populate() - self.write_lock.acquire() - try: + with self.write_lock: for app_name in settings.INSTALLED_APPS: if app_label == app_name.split('.')[-1]: mod = self.load_app(app_name, False) @@ -150,8 +146,6 @@ class AppCache(object): else: return mod raise ImproperlyConfigured("App with label %s could not be found" % app_label) - finally: - self.write_lock.release() def get_app_errors(self): "Returns the map of known problems with the INSTALLED_APPS." diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index b935df3c7f..e7f440a7c2 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -99,15 +99,12 @@ class Signal(object): if weak: receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver) - self.lock.acquire() - try: + with self.lock: for r_key, _ in self.receivers: if r_key == lookup_key: break else: self.receivers.append((lookup_key, receiver)) - finally: - self.lock.release() def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): """ @@ -135,16 +132,13 @@ class Signal(object): lookup_key = (dispatch_uid, _make_id(sender)) else: lookup_key = (_make_id(receiver), _make_id(sender)) - - self.lock.acquire() - try: + + with self.lock: for index in xrange(len(self.receivers)): (r_key, _) = self.receivers[index] if r_key == lookup_key: del self.receivers[index] break - finally: - self.lock.release() def send(self, sender, **named): """ @@ -237,8 +231,7 @@ class Signal(object): Remove dead receivers from connections. """ - self.lock.acquire() - try: + with self.lock: to_remove = [] for key, connected_receiver in self.receivers: if connected_receiver == receiver: @@ -250,8 +243,6 @@ class Signal(object): for idx, (r_key, _) in enumerate(reversed(self.receivers)): if r_key == key: del self.receivers[last_idx-idx] - finally: - self.lock.release() def receiver(signal, **kwargs): diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index cdd6dd1b9e..2215f56523 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -603,21 +603,16 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread): maddr = email.Utils.parseaddr(m.get('from'))[1] if mailfrom != maddr: return "553 '%s' != '%s'" % (mailfrom, maddr) - self.sink_lock.acquire() - self._sink.append(m) - self.sink_lock.release() + with self.sink_lock: + self._sink.append(m) def get_sink(self): - self.sink_lock.acquire() - try: + with self.sink_lock: return self._sink[:] - finally: - self.sink_lock.release() def flush_sink(self): - self.sink_lock.acquire() - self._sink[:] = [] - self.sink_lock.release() + with self.sink_lock: + self._sink[:] = [] def start(self): assert not self.active @@ -629,9 +624,8 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread): self.active = True self.__flag.set() while self.active and asyncore.socket_map: - self.active_lock.acquire() - asyncore.loop(timeout=0.1, count=1) - self.active_lock.release() + with self.active_lock: + asyncore.loop(timeout=0.1, count=1) asyncore.close_all() def stop(self): From 19a810b18cacea12f201b5a235d1af9218d9c2e9 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 23 Jun 2012 18:44:40 +0200 Subject: [PATCH 018/176] Fixed #14917 -- Hinted that view should redirect after form post success --- docs/topics/forms/modelforms.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index eb53b177c5..4cfde400a7 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -877,7 +877,8 @@ of a model. Here's how you can do that:: formset = BookInlineFormSet(request.POST, request.FILES, instance=author) if formset.is_valid(): formset.save() - # Do something. + # Do something. Should generally end with a redirect. For example: + return HttpResponseRedirect(author.get_absolute_url()) else: formset = BookInlineFormSet(instance=author) return render_to_response("manage_books.html", { From 4b722b31e1778310ba0c1ed96fdacf99aa203dd2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 24 Jun 2012 19:54:56 +0200 Subject: [PATCH 019/176] Fixed #16317 -- Fixed dumpdata for self-referencing models and natural keys Thanks aldaran for the patch. --- django/core/management/commands/dumpdata.py | 4 ++-- .../fixtures_regress/fixtures/forward_ref_lookup.json | 4 +++- tests/regressiontests/fixtures_regress/models.py | 1 + tests/regressiontests/fixtures_regress/tests.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 9059625dec..d3650b1eb8 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -150,11 +150,11 @@ def sort_dependencies(app_list): for field in model._meta.fields: if hasattr(field.rel, 'to'): rel_model = field.rel.to - if hasattr(rel_model, 'natural_key'): + if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) for field in model._meta.many_to_many: rel_model = field.rel.to - if hasattr(rel_model, 'natural_key'): + if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) model_dependencies.append((model, deps)) diff --git a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json b/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json index fe50c653cc..42e8ec0877 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json +++ b/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json @@ -10,6 +10,7 @@ "pk": "2", "model": "fixtures_regress.store", "fields": { + "main": null, "name": "Amazon" } }, @@ -17,6 +18,7 @@ "pk": "3", "model": "fixtures_regress.store", "fields": { + "main": null, "name": "Borders" } }, @@ -29,4 +31,4 @@ "stores": [["Amazon"], ["Borders"]] } } -] \ No newline at end of file +] diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index 5d23a21dcd..14cf880003 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -91,6 +91,7 @@ class TestManager(models.Manager): class Store(models.Model): objects = TestManager() name = models.CharField(max_length=255) + main = models.ForeignKey('self', null=True) class Meta: ordering = ('name',) diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index 405c566826..ab93341699 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -478,7 +478,7 @@ class NaturalKeyFixtureTests(TestCase): ) self.assertEqual( stdout.getvalue(), - """[{"pk": 2, "model": "fixtures_regress.store", "fields": {"name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]""" + """[{"pk": 2, "model": "fixtures_regress.store", "fields": {"main": null, "name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"main": null, "name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]""" ) def test_dependency_sorting(self): From d69f1d71c4ee382542d09e80b18ef01feac5884c Mon Sep 17 00:00:00 2001 From: Gabriel Grant Date: Sat, 23 Jun 2012 19:55:23 -0700 Subject: [PATCH 020/176] Fixed typo in JSONResponseMixin example. --- docs/topics/class-based-views/index.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index bdf649da48..6c2848944c 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -93,13 +93,13 @@ conversion to JSON once. For example, a simple JSON mixin might look something like this:: import json - from django import http + from django.http import HttpResponse class JSONResponseMixin(object): """ A mixin that can be used to render a JSON response. """ - reponse_class = HTTPResponse + response_class = HttpResponse def render_to_response(self, context, **response_kwargs): """ From 531878302735e6a2b36d82b584947bbf8eae8111 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 25 Jun 2012 20:24:26 +0200 Subject: [PATCH 021/176] Fixed #17966 -- Isolated ProfileTestCase from custom AUTH_PROFILE_MODULE Thanks Rob Golding for helping on the patch. --- django/contrib/auth/tests/models.py | 36 +++++++++++------------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/django/contrib/auth/tests/models.py b/django/contrib/auth/tests/models.py index b9565922a2..b40157bfe2 100644 --- a/django/contrib/auth/tests/models.py +++ b/django/contrib/auth/tests/models.py @@ -5,39 +5,29 @@ from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable, UserManager) -@override_settings(USE_TZ=False) +@override_settings(USE_TZ=False, AUTH_PROFILE_MODULE='') class ProfileTestCase(TestCase): - fixtures = ['authtestdata.json'] - - def setUp(self): - """Backs up the AUTH_PROFILE_MODULE""" - self.old_AUTH_PROFILE_MODULE = getattr(settings, - 'AUTH_PROFILE_MODULE', None) - - def tearDown(self): - """Restores the AUTH_PROFILE_MODULE -- if it was not set it is deleted, - otherwise the old value is restored""" - if self.old_AUTH_PROFILE_MODULE is None and \ - hasattr(settings, 'AUTH_PROFILE_MODULE'): - del settings.AUTH_PROFILE_MODULE - - if self.old_AUTH_PROFILE_MODULE is not None: - settings.AUTH_PROFILE_MODULE = self.old_AUTH_PROFILE_MODULE def test_site_profile_not_available(self): + user = User.objects.create(username='testclient') + # calling get_profile without AUTH_PROFILE_MODULE set - if hasattr(settings, 'AUTH_PROFILE_MODULE'): - del settings.AUTH_PROFILE_MODULE - user = User.objects.get(username='testclient') - self.assertRaises(SiteProfileNotAvailable, user.get_profile) + del settings.AUTH_PROFILE_MODULE + with self.assertRaisesRegexp(SiteProfileNotAvailable, + "You need to set AUTH_PROFILE_MODULE in your project"): + user.get_profile() # Bad syntax in AUTH_PROFILE_MODULE: settings.AUTH_PROFILE_MODULE = 'foobar' - self.assertRaises(SiteProfileNotAvailable, user.get_profile) + with self.assertRaisesRegexp(SiteProfileNotAvailable, + "app_label and model_name should be separated by a dot"): + user.get_profile() # module that doesn't exist settings.AUTH_PROFILE_MODULE = 'foo.bar' - self.assertRaises(SiteProfileNotAvailable, user.get_profile) + with self.assertRaisesRegexp(SiteProfileNotAvailable, + "Unable to load the profile model"): + user.get_profile() @override_settings(USE_TZ=False) From b6c356b7bb97f3d6d4831b31e67868313bbbc090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Tue, 26 Jun 2012 18:08:42 +0300 Subject: [PATCH 022/176] Fixed #17485 -- Made defer work with select_related This commit tackles a couple of issues. First, in certain cases there were some mixups if field.attname or field.name should be deferred. Field.attname is now always used. Another issue tackled is a case where field is both deferred by .only(), and selected by select_related. This case is now an error. A lot of thanks to koniiiik (Michal Petrucha) for the patch, and to Andrei Antoukh for review. --- django/db/models/query.py | 7 ++--- django/db/models/query_utils.py | 13 ++++++++-- django/db/models/sql/compiler.py | 7 +++-- django/db/models/sql/query.py | 12 ++++++--- docs/ref/models/querysets.txt | 11 +++++--- tests/modeltests/defer/tests.py | 20 ++++++++++---- tests/regressiontests/defer_regress/models.py | 4 +++ tests/regressiontests/defer_regress/tests.py | 26 +++++++++++++++++-- .../select_related_regress/tests.py | 2 +- 9 files changed, 81 insertions(+), 21 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 755820c3b0..0f1d87c642 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1296,7 +1296,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, # Build the list of fields that *haven't* been requested for field, model in klass._meta.get_fields_with_model(): if field.name not in load_fields: - skip.add(field.name) + skip.add(field.attname) elif local_only and model is not None: continue else: @@ -1327,7 +1327,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, related_fields = [] for f in klass._meta.fields: - if select_related_descend(f, restricted, requested): + if select_related_descend(f, restricted, requested, load_fields): if restricted: next = requested[f.name] else: @@ -1339,7 +1339,8 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, reverse_related_fields = [] if restricted: for o in klass._meta.get_all_related_objects(): - if o.field.unique and select_related_descend(o.field, restricted, requested, reverse=True): + if o.field.unique and select_related_descend(o.field, restricted, requested, + only_load.get(o.model), reverse=True): next = requested[o.field.related_query_name()] klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth+1, requested=next, only_load=only_load, local_only=True) diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index a7c176fd8f..60bdb2bcb4 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -126,18 +126,19 @@ class DeferredAttribute(object): return None -def select_related_descend(field, restricted, requested, reverse=False): +def select_related_descend(field, restricted, requested, load_fields, reverse=False): """ Returns True if this field should be used to descend deeper for select_related() purposes. Used by both the query construction code (sql.query.fill_related_selections()) and the model instance creation code - (query.get_cached_row()). + (query.get_klass_info()). Arguments: * field - the field to be checked * restricted - a boolean field, indicating if the field list has been manually restricted using a requested clause) * requested - The select_related() dictionary. + * load_fields - the set of fields to be loaded on this model * reverse - boolean, True if we are checking a reverse select related """ if not field.rel: @@ -151,6 +152,14 @@ def select_related_descend(field, restricted, requested, reverse=False): return False if not restricted and field.null: return False + if load_fields: + if field.name not in load_fields: + if restricted and field.name in requested: + raise InvalidQuery("Field %s.%s cannot be both deferred" + " and traversed using select_related" + " at the same time." % + (field.model._meta.object_name, field.name)) + return False return True # This function is needed because data descriptors must be defined on a class diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 5801b2f428..d44cdfe4a4 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -596,6 +596,7 @@ class SQLCompiler(object): if avoid_set is None: avoid_set = set() orig_dupe_set = dupe_set + only_load = self.query.get_loaded_field_names() # Setup for the case when only particular related fields should be # included in the related selection. @@ -607,7 +608,8 @@ class SQLCompiler(object): restricted = False for f, model in opts.get_fields_with_model(): - if not select_related_descend(f, restricted, requested): + if not select_related_descend(f, restricted, requested, + only_load.get(model or self.query.model)): continue # The "avoid" set is aliases we want to avoid just for this # particular branch of the recursion. They aren't permanently @@ -680,7 +682,8 @@ class SQLCompiler(object): if o.field.unique ] for f, model in related_fields: - if not select_related_descend(f, restricted, requested, reverse=True): + if not select_related_descend(f, restricted, requested, + only_load.get(model), reverse=True): continue # The "avoid" set is aliases we want to avoid just for this # particular branch of the recursion. They aren't permanently diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 7f331bfe7f..8fbba3dbc9 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1845,9 +1845,15 @@ class Query(object): If no fields are marked for deferral, returns an empty dictionary. """ - collection = {} - self.deferred_to_data(collection, self.get_loaded_field_names_cb) - return collection + # We cache this because we call this function multiple times + # (compiler.fill_related_selections, query.iterator) + try: + return self._loaded_field_names_cache + except AttributeError: + collection = {} + self.deferred_to_data(collection, self.get_loaded_field_names_cb) + self._loaded_field_names_cache = collection + return collection def get_loaded_field_names_cb(self, target, model, fields): """ diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index eef22728ab..2876f1474d 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1081,11 +1081,13 @@ to ``defer()``:: # Load all fields immediately. my_queryset.defer(None) +.. versionchanged:: 1.5 + Some fields in a model won't be deferred, even if you ask for them. You can never defer the loading of the primary key. If you are using :meth:`select_related()` to retrieve related models, you shouldn't defer the -loading of the field that connects from the primary model to the related one -(at the moment, that doesn't raise an error, but it will eventually). +loading of the field that connects from the primary model to the related +one, doing so will result in an error. .. note:: @@ -1145,9 +1147,12 @@ logically:: # existing set of fields). Entry.objects.defer("body").only("headline", "body") +.. versionchanged:: 1.5 + All of the cautions in the note for the :meth:`defer` documentation apply to ``only()`` as well. Use it cautiously and only after exhausting your other -options. +options. Also note that using :meth:`only` and omitting a field requested +using :meth:`select_related` is an error as well. using ~~~~~ diff --git a/tests/modeltests/defer/tests.py b/tests/modeltests/defer/tests.py index eb09162b01..50db5a76b4 100644 --- a/tests/modeltests/defer/tests.py +++ b/tests/modeltests/defer/tests.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -from django.db.models.query_utils import DeferredAttribute +from django.db.models.query_utils import DeferredAttribute, InvalidQuery from django.test import TestCase from .models import Secondary, Primary, Child, BigChild, ChildProxy @@ -73,9 +73,19 @@ class DeferTests(TestCase): self.assert_delayed(qs.defer("name").get(pk=p1.pk), 1) self.assert_delayed(qs.only("name").get(pk=p1.pk), 2) - # DOES THIS WORK? - self.assert_delayed(qs.only("name").select_related("related")[0], 1) - self.assert_delayed(qs.defer("related").select_related("related")[0], 0) + # When we defer a field and also select_related it, the query is + # invalid and raises an exception. + with self.assertRaises(InvalidQuery): + qs.only("name").select_related("related")[0] + with self.assertRaises(InvalidQuery): + qs.defer("related").select_related("related")[0] + + # With a depth-based select_related, all deferred ForeignKeys are + # deferred instead of traversed. + with self.assertNumQueries(3): + obj = qs.defer("related").select_related()[0] + self.assert_delayed(obj, 1) + self.assertEqual(obj.related.id, s1.pk) # Saving models with deferred fields is possible (but inefficient, # since every field has to be retrieved first). @@ -155,7 +165,7 @@ class DeferTests(TestCase): children = ChildProxy.objects.all().select_related().only('id', 'name') self.assertEqual(len(children), 1) child = children[0] - self.assert_delayed(child, 1) + self.assert_delayed(child, 2) self.assertEqual(child.name, 'p1') self.assertEqual(child.value, 'xx') diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index 812d2da206..bd4f845f27 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -47,3 +47,7 @@ class SimpleItem(models.Model): class Feature(models.Model): item = models.ForeignKey(SimpleItem) + +class ItemAndSimpleItem(models.Model): + item = models.ForeignKey(Item) + simple = models.ForeignKey(SimpleItem) diff --git a/tests/regressiontests/defer_regress/tests.py b/tests/regressiontests/defer_regress/tests.py index 1f07d4c9a8..53bb59f5b3 100644 --- a/tests/regressiontests/defer_regress/tests.py +++ b/tests/regressiontests/defer_regress/tests.py @@ -9,7 +9,7 @@ from django.db.models.loading import cache from django.test import TestCase from .models import (ResolveThis, Item, RelatedItem, Child, Leaf, Proxy, - SimpleItem, Feature) + SimpleItem, Feature, ItemAndSimpleItem) class DeferRegressionTest(TestCase): @@ -109,6 +109,7 @@ class DeferRegressionTest(TestCase): Child, Feature, Item, + ItemAndSimpleItem, Leaf, Proxy, RelatedItem, @@ -125,12 +126,16 @@ class DeferRegressionTest(TestCase): ), ) ) + # FIXME: This is dependent on the order in which tests are run -- + # this test case has to be the first, otherwise a LOT more classes + # appear. self.assertEqual( klasses, [ "Child", "Child_Deferred_value", "Feature", "Item", + "ItemAndSimpleItem", "Item_Deferred_name", "Item_Deferred_name_other_value_text", "Item_Deferred_name_other_value_value", @@ -139,7 +144,7 @@ class DeferRegressionTest(TestCase): "Leaf", "Leaf_Deferred_child_id_second_child_id_value", "Leaf_Deferred_name_value", - "Leaf_Deferred_second_child_value", + "Leaf_Deferred_second_child_id_value", "Leaf_Deferred_value", "Proxy", "RelatedItem", @@ -175,6 +180,23 @@ class DeferRegressionTest(TestCase): self.assertEqual(1, qs.count()) self.assertEqual('Foobar', qs[0].name) + def test_defer_with_select_related(self): + item1 = Item.objects.create(name="first", value=47) + item2 = Item.objects.create(name="second", value=42) + simple = SimpleItem.objects.create(name="simple", value="23") + related = ItemAndSimpleItem.objects.create(item=item1, simple=simple) + + obj = ItemAndSimpleItem.objects.defer('item').select_related('simple').get() + self.assertEqual(obj.item, item1) + self.assertEqual(obj.item_id, item1.id) + + obj.item = item2 + obj.save() + + obj = ItemAndSimpleItem.objects.defer('item').select_related('simple').get() + self.assertEqual(obj.item, item2) + self.assertEqual(obj.item_id, item2.id) + def test_deferred_class_factory(self): from django.db.models.query_utils import deferred_class_factory new_class = deferred_class_factory(Item, diff --git a/tests/regressiontests/select_related_regress/tests.py b/tests/regressiontests/select_related_regress/tests.py index e35157dbaf..73b0a8a875 100644 --- a/tests/regressiontests/select_related_regress/tests.py +++ b/tests/regressiontests/select_related_regress/tests.py @@ -133,7 +133,7 @@ class SelectRelatedRegressTests(TestCase): self.assertEqual(troy.state.name, 'Western Australia') # Also works if you use only, rather than defer - troy = SpecialClient.objects.select_related('state').only('name').get(name='Troy Buswell') + troy = SpecialClient.objects.select_related('state').only('name', 'state').get(name='Troy Buswell') self.assertEqual(troy.name, 'Troy Buswell') self.assertEqual(troy.value, 42) From 05d333ba3bb16af024c11966d2072de38fe9f82f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 26 Jun 2012 11:14:23 +0200 Subject: [PATCH 023/176] Fixed #18515 -- Conditionally regenerated filename in FileField validation When a FileField value has been saved, a new validation should not regenerate a new filename when checking the length. Refs #9893. --- django/db/models/fields/files.py | 6 +++++- tests/modeltests/files/models.py | 2 +- tests/modeltests/files/tests.py | 23 ++++++++++++++++----- tests/regressiontests/model_fields/tests.py | 12 ----------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index e0c095974d..b0dce381a6 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -242,7 +242,11 @@ class FileField(Field): # (ie. upload_to='path/to/upload/dir'), the length of the generated # name equals the length of the uploaded name plus a constant. Thus # we can tell the user how much shorter the name should be (roughly). - length = len(self.generate_filename(model_instance, value.name)) + if value and value._committed: + filename = value.name + else: + filename = self.generate_filename(model_instance, value.name) + length = len(filename) if self.max_length and length > self.max_length: error_values = {'extra': length - self.max_length} raise ValidationError(self.error_messages['max_length'] % error_values) diff --git a/tests/modeltests/files/models.py b/tests/modeltests/files/models.py index 4134472748..cefc7c7244 100644 --- a/tests/modeltests/files/models.py +++ b/tests/modeltests/files/models.py @@ -26,5 +26,5 @@ class Storage(models.Model): normal = models.FileField(storage=temp_storage, upload_to='tests') custom = models.FileField(storage=temp_storage, upload_to=custom_upload_to) - random = models.FileField(storage=temp_storage, upload_to=random_upload_to) + random = models.FileField(storage=temp_storage, upload_to=random_upload_to, max_length=16) default = models.FileField(storage=temp_storage, upload_to='tests', default='tests/default.txt') diff --git a/tests/modeltests/files/tests.py b/tests/modeltests/files/tests.py index 3e256f787f..565d4c8942 100644 --- a/tests/modeltests/files/tests.py +++ b/tests/modeltests/files/tests.py @@ -5,6 +5,7 @@ import shutil import tempfile from django.core.cache import cache +from django.core.exceptions import ValidationError from django.core.files import File from django.core.files.base import ContentFile from django.core.files.uploadedfile import SimpleUploadedFile @@ -102,11 +103,23 @@ class FileStorageTests(TestCase): obj4.random.save("random_file", ContentFile(b"random content")) self.assertTrue(obj4.random.name.endswith("/random_file")) - # Clean up the temporary files and dir. - obj1.normal.delete() - obj2.normal.delete() - obj3.default.delete() - obj4.random.delete() + def test_max_length(self): + """ + Test that FileField validates the length of the generated file name + that will be stored in the database. Regression for #9893. + """ + # upload_to = 'unused', so file names are saved as '456/xxxxx'. + # max_length = 16, so names longer than 12 characters are rejected. + s1 = Storage(random=SimpleUploadedFile(12 * 'x', b"content")) + s1.full_clean() + with self.assertRaises(ValidationError): + Storage(random=SimpleUploadedFile(13 * 'x', b"content")).full_clean() + + # Ticket #18515: validation for an already saved file should not check + # against a regenerated file name (and potentially raise a ValidationError + # if max_length is exceeded + s1.save() + s1.full_clean() class FileTests(unittest.TestCase): diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index 5d3d42ef2a..a89ffcae32 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -365,15 +365,3 @@ class FileFieldTests(unittest.TestCase): field = d._meta.get_field('myfile') field.save_form_data(d, 'else.txt') self.assertEqual(d.myfile, 'else.txt') - - def test_max_length(self): - """ - Test that FileField validates the length of the generated file name - that will be stored in the database. Regression for #9893. - - """ - # upload_to = 'unused', so file names are saved as 'unused/xxxxx'. - # max_length = 100, so names longer than 93 characters are rejected. - Document(myfile=93 * 'x').full_clean() - with self.assertRaises(ValidationError): - Document(myfile=94 * 'x').full_clean() From fa182e8ae82f33764d5e1f70bcd45899e1bf17e6 Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Tue, 12 Jun 2012 20:30:52 +1000 Subject: [PATCH 024/176] Fixed #18465 -- Set date formats correctly on Oracle Correctly configure NLS_SESSION_PARAMETERS to format Date and DateTime on Oracle backend. Thanks to Josh Smeaton for report & patch. --- django/db/backends/oracle/base.py | 16 +++++++++++----- tests/regressiontests/backends/tests.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 64e038e1a6..b90f6ea155 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -479,13 +479,19 @@ class DatabaseWrapper(BaseDatabaseWrapper): del conn_params['use_returning_into'] self.connection = Database.connect(conn_string, **conn_params) cursor = FormatStylePlaceholderCursor(self.connection) + # Set the territory first. The territory overrides NLS_DATE_FORMAT + # and NLS_TIMESTAMP_FORMAT to the territory default. When all of + # these are set in single statement it isn't clear what is supposed + # to happen. + cursor.execute("ALTER SESSION SET NLS_TERRITORY = 'AMERICA'") # Set oracle date to ansi date format. This only needs to execute # once when we create a new connection. We also set the Territory - # to 'AMERICA' which forces Sunday to evaluate to a '1' in TO_CHAR(). - cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" - " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'" - " NLS_TERRITORY = 'AMERICA'" - + (" TIME_ZONE = 'UTC'" if settings.USE_TZ else '')) + # to 'AMERICA' which forces Sunday to evaluate to a '1' in + # TO_CHAR(). + cursor.execute( + "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" + " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'" + + (" TIME_ZONE = 'UTC'" if settings.USE_TZ else '')) if 'operators' not in self.__dict__: # Ticket #14149: Check whether our LIKE implementation will diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index c6d28ab135..cb25ac0a32 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -66,6 +66,18 @@ class OracleChecks(unittest.TestCase): self.assertEqual(connection.connection.encoding, "UTF-8") self.assertEqual(connection.connection.nencoding, "UTF-8") + @unittest.skipUnless(connection.vendor == 'oracle', + "No need to check Oracle connection semantics") + def test_order_of_nls_parameters(self): + # an 'almost right' datetime should work with configured + # NLS parameters as per #18465. + c = connection.cursor() + query = "select 1 from dual where '1936-12-29 00:00' < sysdate" + # Test that the query succeeds without errors - pre #18465 this + # wasn't the case. + c.execute(query) + self.assertEqual(c.fetchone()[0], 1) + class MySQLTests(TestCase): @unittest.skipUnless(connection.vendor == 'mysql', "Test valid only for MySQL") From 24dec9edaac7849c7831ff0ee4245b6637a9639e Mon Sep 17 00:00:00 2001 From: Ben Spaulding Date: Tue, 26 Jun 2012 20:17:15 -0500 Subject: [PATCH 025/176] Add reST role to templates named in some view docs. This makes the templates link up correctly in the admindocs. --- django/contrib/comments/views/moderation.py | 6 +++--- django/contrib/comments/views/utils.py | 2 +- django/contrib/flatpages/views.py | 2 +- django/views/defaults.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/django/contrib/comments/views/moderation.py b/django/contrib/comments/views/moderation.py index fb9e91ef97..39933e75c8 100644 --- a/django/contrib/comments/views/moderation.py +++ b/django/contrib/comments/views/moderation.py @@ -17,7 +17,7 @@ def flag(request, comment_id, next=None): """ Flags a comment. Confirmation on GET, action on POST. - Templates: `comments/flag.html`, + Templates: :template:`comments/flag.html`, Context: comment the flagged `comments.comment` object @@ -43,7 +43,7 @@ def delete(request, comment_id, next=None): Deletes a comment. Confirmation on GET, action on POST. Requires the "can moderate comments" permission. - Templates: `comments/delete.html`, + Templates: :template:`comments/delete.html`, Context: comment the flagged `comments.comment` object @@ -70,7 +70,7 @@ def approve(request, comment_id, next=None): Approve a comment (that is, mark it as public and non-removed). Confirmation on GET, action on POST. Requires the "can moderate comments" permission. - Templates: `comments/approve.html`, + Templates: :template:`comments/approve.html`, Context: comment the `comments.comment` object for approval diff --git a/django/contrib/comments/views/utils.py b/django/contrib/comments/views/utils.py index cc985e52d2..8879e9fb8f 100644 --- a/django/contrib/comments/views/utils.py +++ b/django/contrib/comments/views/utils.py @@ -56,7 +56,7 @@ def confirmation_view(template, doc="Display a confirmation view."): confirmed.__doc__ = textwrap.dedent("""\ %s - Templates: `%s`` + Templates: :template:`%s`` Context: comment The posted comment diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py index ef7fda5d5c..0b462ac5a4 100644 --- a/django/contrib/flatpages/views.py +++ b/django/contrib/flatpages/views.py @@ -23,7 +23,7 @@ def flatpage(request, url): Models: `flatpages.flatpages` Templates: Uses the template defined by the ``template_name`` field, - or `flatpages/default.html` if template_name is not defined. + or :template:`flatpages/default.html` if template_name is not defined. Context: flatpage `flatpages.flatpages` object diff --git a/django/views/defaults.py b/django/views/defaults.py index a6d6a624eb..2bbc23321e 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -12,7 +12,7 @@ def page_not_found(request, template_name='404.html'): """ Default 404 handler. - Templates: `404.html` + Templates: :template:`404.html` Context: request_path The path of the requested URL (e.g., '/app/pages/bad_page/') @@ -26,7 +26,7 @@ def server_error(request, template_name='500.html'): """ 500 error handler. - Templates: `500.html` + Templates: :template:`500.html` Context: None """ t = loader.get_template(template_name) # You need to create a 500.html template. @@ -41,7 +41,7 @@ def permission_denied(request, template_name='403.html'): """ Permission denied (403) handler. - Templates: `403.html` + Templates: :template:`403.html` Context: None If the template does not exist, an Http403 response containing the text From ada961b0d20cc31c6d414b556ba2dafb4e73c406 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 27 Jun 2012 18:11:45 +0200 Subject: [PATCH 026/176] Fixed #18527 -- Removed superfluous backslash in CBV docs Thanks ramilzay at gmail.com for the report. --- docs/topics/class-based-views/generic-display.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt index 39fb41df04..4c2f95ce17 100644 --- a/docs/topics/class-based-views/generic-display.txt +++ b/docs/topics/class-based-views/generic-display.txt @@ -397,7 +397,7 @@ custom view:: urlpatterns = patterns('', #... - url(r'^authors/(?P\\d+)/$', AuthorDetailView.as_view(), name='author-detail'), + url(r'^authors/(?P\d+)/$', AuthorDetailView.as_view(), name='author-detail'), ) Then we'd write our new view -- ``get_object`` is the method that retrieves the From 1cf8287e3a6f252b79e9b9a8f945f11a92e17cd5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 20 Jun 2012 19:13:35 -0400 Subject: [PATCH 027/176] Fixed #18369 - Fixed argument name in render() function; thanks qsolo825@ for the report. --- docs/topics/http/shortcuts.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 2185d5497f..10be353e80 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -15,7 +15,7 @@ introduce controlled coupling for convenience's sake. ``render`` ========== -.. function:: render(request, template[, dictionary][, context_instance][, content_type][, status][, current_app]) +.. function:: render(request, template_name[, dictionary][, context_instance][, content_type][, status][, current_app]) .. versionadded:: 1.3 @@ -32,7 +32,7 @@ Required arguments ``request`` The request object used to generate this response. -``template`` +``template_name`` The full name of a template to use or sequence of template names. Optional arguments From c8928b91b515c0712bdb633c1d1b2656b80baff8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 20 Jun 2012 20:03:06 -0400 Subject: [PATCH 028/176] Fixed #17511 - Removed reference to deprecated "reset" management command in FAQ; thanks voxpuibr@ for the report. --- docs/faq/models.txt | 11 ++++------- docs/ref/django-admin.txt | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/faq/models.txt b/docs/faq/models.txt index d34a26a82e..4a83aa9f2c 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -40,18 +40,15 @@ Yes. See :doc:`Integrating with a legacy database `. If I make changes to a model, how do I update the database? ----------------------------------------------------------- -If you don't mind clearing data, your project's ``manage.py`` utility has an -option to reset the SQL for a particular application:: - - manage.py reset appname - -This drops any tables associated with ``appname`` and recreates them. +If you don't mind clearing data, your project's ``manage.py`` utility has a +:djadmin:`flush` option to reset the database to the state it was in +immediately after :djadmin:`syncdb` was executed. If you do care about deleting data, you'll have to execute the ``ALTER TABLE`` statements manually in your database. There are `external projects which handle schema updates -`_, of which the current +`_, of which the current defacto standard is `south `_. Do Django models support multiple-column primary keys? diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 360c0ae4d3..8fea699c1b 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -224,8 +224,8 @@ flush .. django-admin:: flush -Returns the database to the state it was in immediately after syncdb was -executed. This means that all data will be removed from the database, any +Returns the database to the state it was in immediately after :djadmin:`syncdb` +was executed. This means that all data will be removed from the database, any post-synchronization handlers will be re-executed, and the ``initial_data`` fixture will be re-installed. From 8a5d1a6b93e05546c5fdbfc497d7fb3a3377cf85 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 28 Jun 2012 10:49:07 +0200 Subject: [PATCH 029/176] Updated obsolete links in the documentation --- docs/howto/jython.txt | 2 +- docs/internals/committers.txt | 4 ++-- docs/internals/contributing/localizing.txt | 6 +++--- .../contributing/writing-code/unit-tests.txt | 2 +- .../contributing/writing-documentation.txt | 2 +- docs/intro/whatsnext.txt | 6 +++--- docs/ref/contrib/gis/geoquerysets.txt | 6 +++--- docs/ref/contrib/gis/geos.txt | 4 ++-- docs/ref/contrib/gis/install.txt | 16 ++++++++-------- docs/ref/contrib/gis/model-api.txt | 4 ++-- docs/ref/contrib/gis/sitemaps.txt | 9 ++++----- docs/ref/contrib/gis/tutorial.txt | 2 +- docs/ref/contrib/localflavor.txt | 6 +++--- docs/ref/contrib/sitemaps.txt | 2 +- docs/ref/databases.txt | 2 +- docs/ref/request-response.txt | 2 +- docs/ref/settings.txt | 2 +- docs/ref/templates/api.txt | 2 +- docs/ref/utils.txt | 6 +++--- docs/releases/1.0-beta-2.txt | 2 +- docs/releases/1.0.1.txt | 4 ++-- docs/releases/1.0.txt | 2 +- docs/releases/1.1.txt | 4 ++-- docs/releases/1.3.txt | 2 +- docs/topics/email.txt | 2 +- docs/topics/http/sessions.txt | 2 +- docs/topics/install.txt | 2 +- docs/topics/testing.txt | 2 +- 28 files changed, 53 insertions(+), 54 deletions(-) diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 2cee4e6daa..762250212a 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -36,7 +36,7 @@ such as `Apache Tomcat`_. Full JavaEE applications servers such as `GlassFish`_ or `JBoss`_ are also OK, if you need the extra features they include. .. _`Apache Tomcat`: http://tomcat.apache.org/ -.. _GlassFish: https://glassfish.dev.java.net/ +.. _GlassFish: http://glassfish.java.net/ .. _JBoss: http://www.jboss.org/ Installing Django diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 972e506155..b0eed95571 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -146,7 +146,7 @@ Joseph Kocherhans Brian lives in Denver, Colorado, USA. -.. _brian rosner: http://oebfare.com/ +.. _brian rosner: http://brosner.com/ .. _eldarion: http://eldarion.com/ .. _django dose: http://djangodose.com/ @@ -162,7 +162,7 @@ Joseph Kocherhans Gary lives in Austin, Texas, USA. -.. _Gary Wilson: http://gdub.wordpress.com/ +.. _Gary Wilson: http://thegarywilson.com/ .. _The University of Texas: http://www.utexas.edu/ Justin Bronn diff --git a/docs/internals/contributing/localizing.txt b/docs/internals/contributing/localizing.txt index ff957c44a4..263087b5fa 100644 --- a/docs/internals/contributing/localizing.txt +++ b/docs/internals/contributing/localizing.txt @@ -60,7 +60,7 @@ Django source tree, as for any code change: * Open a ticket in Django's ticket system, set its ``Component`` field to ``Translations``, and attach the patch to it. -.. _Transifex: https://www.transifex.net/ +.. _Transifex: https://www.transifex.com/ .. _Django i18n mailing list: http://groups.google.com/group/django-i18n/ -.. _Django project page: https://www.transifex.net/projects/p/django/ -.. _Transifex User Guide: http://help.transifex.net/ +.. _Django project page: https://www.transifex.com/projects/p/django/ +.. _Transifex User Guide: http://help.transifex.com/ diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 9d6e76e453..4de506a654 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -159,7 +159,7 @@ associated tests will be skipped. .. _Textile: http://pypi.python.org/pypi/textile .. _docutils: http://pypi.python.org/pypi/docutils/0.4 .. _setuptools: http://pypi.python.org/pypi/setuptools/ -.. _memcached: http://www.danga.com/memcached/ +.. _memcached: http://memcached.org/ .. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html .. _selenium: http://pypi.python.org/pypi/selenium diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index e91d1291e9..c8d7039a68 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -22,7 +22,7 @@ Getting the raw documentation ----------------------------- Though Django's documentation is intended to be read as HTML at -http://docs.djangoproject.com/, we edit it as a collection of text files for +https://docs.djangoproject.com/, we edit it as a collection of text files for maximum flexibility. These files live in the top-level ``docs/`` directory of a Django release. diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index 6e0d97fefd..cc793c8129 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -108,7 +108,7 @@ On the Web ---------- The most recent version of the Django documentation lives at -http://docs.djangoproject.com/en/dev/. These HTML pages are generated +https://docs.djangoproject.com/en/dev/. These HTML pages are generated automatically from the text files in source control. That means they reflect the "latest and greatest" in Django -- they include the very latest corrections and additions, and they discuss the latest Django features, which may only be @@ -124,7 +124,7 @@ rather than asking broad tech-support questions. If you need help with your particular Django setup, try the `django-users mailing list`_ or the `#django IRC channel`_ instead. -.. _ticket system: https://code.djangoproject.com/simpleticket?component=Documentation +.. _ticket system: https://code.djangoproject.com/newticket?component=Documentation .. _django-users mailing list: http://groups.google.com/group/django-users .. _#django IRC channel: irc://irc.freenode.net/django @@ -228,4 +228,4 @@ We follow this policy: * The `main documentation Web page`_ includes links to documentation for all previous versions. -.. _main documentation Web page: http://docs.djangoproject.com/en/dev/ +.. _main documentation Web page: https://docs.djangoproject.com/en/dev/ diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index da00aa97f8..eeec2e2133 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -1026,7 +1026,7 @@ Keyword Argument Description representation -- the default value is 8. ===================== ===================================================== -__ http://code.google.com/apis/kml/documentation/ +__ https://developers.google.com/kml/documentation/ ``svg`` ~~~~~~~ @@ -1185,7 +1185,7 @@ Keyword Argument Description details. ===================== ===================================================== -__ http://download.oracle.com/docs/html/B14255_01/sdo_intro.htm#sthref150 +__ http://docs.oracle.com/html/B14255_01/sdo_intro.htm#sthref150 Aggregate Functions ------------------- @@ -1232,6 +1232,6 @@ Returns the same as the :meth:`GeoQuerySet.union` aggregate method. .. rubric:: Footnotes .. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL `_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model). -.. [#fnsdorelate] *See* `SDO_RELATE documentation `_, from Ch. 11 of the Oracle Spatial User's Guide and Manual. +.. [#fnsdorelate] *See* `SDO_RELATE documentation `_, from Ch. 11 of the Oracle Spatial User's Guide and Manual. .. [#fncovers] For an explanation of this routine, read `Quirks of the "Contains" Spatial Predicate `_ by Martin Davis (a PostGIS developer). .. [#fncontainsproperly] Refer to the PostGIS ``ST_ContainsProperly`` `documentation `_ for more details. diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 1b32265e55..eda9617381 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -324,7 +324,7 @@ and Z values that are a part of this geometry. Returns the Well-Known Text of the geometry (an OGC standard). -__ http://code.google.com/apis/kml/documentation/ +__ https://developers.google.com/kml/documentation/ Spatial Predicate Methods ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -891,7 +891,7 @@ Returns the WKT of the given geometry. Example:: .. rubric:: Footnotes -.. [#fnogc] *See* `PostGIS EWKB, EWKT and Canonical Forms `_, PostGIS documentation at Ch. 4.1.2. +.. [#fnogc] *See* `PostGIS EWKB, EWKT and Canonical Forms `_, PostGIS documentation at Ch. 4.1.2. .. [#fncascadedunion] For more information, read Paul Ramsey's blog post about `(Much) Faster Unions in PostGIS 1.4 `_ and Martin Davis' blog post on `Fast polygon merging in JTS using Cascaded Union `_. Settings diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 4b7ee89a52..00f8f8a370 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -102,7 +102,7 @@ Program Description Required .. _PROJ.4: http://trac.osgeo.org/proj/ __ http://postgis.refractions.net/ -__ http://www.gaia-gis.it/spatialite/index.html +__ http://www.gaia-gis.it/gaia-sins/ .. _build_from_source: @@ -233,7 +233,7 @@ installed prior to building PostGIS. The `psycopg2`_ module is required for use as the database adaptor when using GeoDjango with PostGIS. -.. _psycopg2: http://initd.org/projects/psycopg2 +.. _psycopg2: http://initd.org/psycopg/ First download the source archive, and extract:: @@ -376,7 +376,7 @@ SpatiaLite. After installation is complete, don't forget to read the post-installation docs on :ref:`create_spatialite_db`. -__ http://www.gaia-gis.it/spatialite/index.html +__ http://www.gaia-gis.it/gaia-sins/ .. _sqlite: @@ -457,7 +457,7 @@ Finally, do the same for the SpatiaLite tools:: $ ./configure --target=macosx -__ http://www.gaia-gis.it/spatialite/sources.html +__ http://www.gaia-gis.it/gaia-sins/libspatialite-sources/ .. _pysqlite2: @@ -664,7 +664,7 @@ community! You can: and specify the component as "GIS". __ http://groups.google.com/group/geodjango -__ https://code.djangoproject.com/simpleticket +__ https://code.djangoproject.com/newticket .. _libsettings: @@ -903,7 +903,7 @@ add the following to your ``settings.py``: SPATIALITE_LIBRARY_PATH='/Library/Frameworks/SQLite3.framework/SQLite3' -__ http://www.gaia-gis.it/spatialite/binaries.html +__ http://www.gaia-gis.it/spatialite-2.3.1/binaries.html .. _fink: @@ -1314,8 +1314,8 @@ may be executed from the SQL Shell as the ``postgres`` user:: .. rubric:: Footnotes .. [#] The datum shifting files are needed for converting data to and from certain projections. - For example, the PROJ.4 string for the `Google projection (900913) - `_ requires the + For example, the PROJ.4 string for the `Google projection (900913 or 3857) + `_ requires the ``null`` grid file only included in the extra datum shifting files. It is easier to install the shifting files now, then to have debug a problem caused by their absence later. diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index 462df50d64..8c5274e6d3 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -142,7 +142,7 @@ __ http://en.wikipedia.org/wiki/Geodesy __ http://en.wikipedia.org/wiki/Great_circle __ http://www.spatialreference.org/ref/epsg/2796/ __ http://spatialreference.org/ -__ http://welcome.warnercnr.colostate.edu/class_info/nr502/lg3/datums_coordinates/spcs.html +__ http://web.archive.org/web/20080302095452/http://welcome.warnercnr.colostate.edu/class_info/nr502/lg3/datums_coordinates/spcs.html ``spatial_index`` ----------------- @@ -252,7 +252,7 @@ for example:: qs = Address.objects.filter(zipcode__poly__contains='POINT(-104.590948 38.319914)') .. rubric:: Footnotes -.. [#fnogc] OpenGIS Consortium, Inc., `Simple Feature Specification For SQL `_, Document 99-049 (May 5, 1999). +.. [#fnogc] OpenGIS Consortium, Inc., `Simple Feature Specification For SQL `_. .. [#fnogcsrid] *See id.* at Ch. 2.3.8, p. 39 (Geometry Values and Spatial Reference Systems). .. [#fnsrid] Typically, SRID integer corresponds to an EPSG (`European Petroleum Survey Group `_) identifier. However, it may also be associated with custom projections defined in spatial database's spatial reference systems table. .. [#fnharvard] Harvard Graduate School of Design, `An Overview of Geodesy and Geographic Referencing Systems `_. This is an excellent resource for an overview of principles relating to geographic and Cartesian coordinate systems. diff --git a/docs/ref/contrib/gis/sitemaps.txt b/docs/ref/contrib/gis/sitemaps.txt index 75bddd3b86..0ab8f75825 100644 --- a/docs/ref/contrib/gis/sitemaps.txt +++ b/docs/ref/contrib/gis/sitemaps.txt @@ -2,10 +2,10 @@ Geographic Sitemaps =================== -Google's sitemap protocol has been recently extended to support geospatial -content. [#]_ This includes the addition of the ```` child element +Google's sitemap protocol used to include geospatial content support. [#]_ +This included the addition of the ```` child element ````, which tells Google that the content located at the URL is -geographic in nature. [#]_ +geographic in nature. This is now obsolete. Example ======= @@ -23,5 +23,4 @@ Reference ----------------- .. rubric:: Footnotes -.. [#] Google, Inc., `What is a Geo Sitemap? `_. -.. [#] Google, Inc., `Submit Your Geo Content to Google `_. +.. [#] Google, Inc., `What is a Geo Sitemap? `_. diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 395eac1821..3a63493137 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -784,4 +784,4 @@ option class in your ``admin.py`` file:: .. [#] Special thanks to Bjørn Sandvik of `thematicmapping.org `_ for providing and maintaining this data set. .. [#] GeoDjango basic apps was written by Dane Springmeyer, Josh Livni, and Christopher Schmidt. .. [#] Here the point is for the `University of Houston Law Center `_. -.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_, Document 99-049. +.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_. diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 61c8c7ae47..4595f51d9e 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -93,7 +93,7 @@ Here's an example of how to use them:: class MyForm(forms.Form): my_date_field = generic.forms.DateField() -.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm +.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes.htm .. _Argentina: `Argentina (ar)`_ .. _Australia: `Australia (au)`_ .. _Austria: `Austria (at)`_ @@ -158,7 +158,7 @@ any code you'd like to contribute. One thing we ask is that you please use Unicode objects (``u'mystring'``) for strings, rather than setting the encoding in the file. See any of the existing flavors for examples. -.. _create a ticket: https://code.djangoproject.com/simpleticket +.. _create a ticket: https://code.djangoproject.com/newticket Localflavor and backwards compatibility ======================================= @@ -713,7 +713,7 @@ Italy (``it``) A form field that validates input as an Italian social security number (`codice fiscale`_). -.. _codice fiscale: http://www.agenziaentrate.it/ilwwcm/connect/Nsi/Servizi/Codice+fiscale+-+tessera+sanitaria/NSI+Informazioni+sulla+codificazione+delle+persone+fisiche +.. _codice fiscale: http://www.agenziaentrate.gov.it/wps/content/Nsilib/Nsi/Home/CosaDeviFare/Richiedere/Codice+fiscale+e+tessera+sanitaria/Richiesta+TS_CF/SchedaI/Informazioni+codificazione+pf/ .. class:: it.forms.ITVatNumberField diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index d775092eae..2393a4a9a3 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -410,7 +410,7 @@ generate a Google News compatible sitemap: {% endspaceless %} -.. _`Google news sitemaps`: http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=74288 +.. _`Google news sitemaps`: http://support.google.com/webmasters/bin/answer.py?hl=en&answer=74288 Pinging Google ============== diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 600de8ed3a..ff7a349eb5 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -31,7 +31,7 @@ attempt to use the ``StdDev(sample=False)`` or ``Variance(sample=False)`` aggregate with a database backend that falls within the affected release range. .. _known to be faulty: http://archives.postgresql.org/pgsql-bugs/2007-07/msg00046.php -.. _Release 8.2.5: http://developer.postgresql.org/pgdocs/postgres/release-8-2-5.html +.. _Release 8.2.5: http://www.postgresql.org/docs/devel/static/release-8-2-5.html Optimizing PostgreSQL's configuration ------------------------------------- diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index d2b6f35b84..a29ddc63cc 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -684,7 +684,7 @@ Methods risk of client side script accessing the protected cookie data. - .. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly + .. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. method:: HttpResponse.set_signed_cookie(key, value='', salt='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=True) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 627aa5007f..72d60453c3 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1675,7 +1675,7 @@ consistently by all browsers. However, when it is honored, it can be a useful way to mitigate the risk of client side script accessing the protected cookie data. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. versionchanged:: 1.4 The default value of the setting was changed from ``False`` to ``True``. diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 7816e1d07d..ec01fe2faa 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -774,7 +774,7 @@ Using an alternative template language 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 -`_ or `Cheetah `_. This +`_ or `Cheetah `_. This allows us to use third-party template libraries without giving up useful Django features like the Django ``Context`` object and handy shortcuts like :func:`~django.shortcuts.render_to_response()`. diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index b323f0629f..0974409453 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -246,13 +246,13 @@ For simplifying the selection of a generator use ``feedgenerator.DefaultFeed`` which is currently ``Rss201rev2Feed`` 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 .. function:: 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 SyndicationFeed --------------- @@ -330,7 +330,7 @@ Rss201rev2Feed .. class:: Rss201rev2Feed(RssFeed) - Spec: http://blogs.law.harvard.edu/tech/rss + Spec: http://cyber.law.harvard.edu/rss/rss.html RssUserland091Feed ------------------ diff --git a/docs/releases/1.0-beta-2.txt b/docs/releases/1.0-beta-2.txt index 2a2554432e..288ac8fbc1 100644 --- a/docs/releases/1.0-beta-2.txt +++ b/docs/releases/1.0-beta-2.txt @@ -46,7 +46,7 @@ Refactored documentation documentation files bundled with Django. .. _Sphinx: http://sphinx.pocoo.org/ -.. _online: http://docs.djangoproject.com/en/dev/ +.. _online: https://docs.djangoproject.com/ Along with these new features, the Django team has also been hard at work polishing Django's codebase for the final 1.0 release; this beta diff --git a/docs/releases/1.0.1.txt b/docs/releases/1.0.1.txt index 7901b8e219..d6d21afc01 100644 --- a/docs/releases/1.0.1.txt +++ b/docs/releases/1.0.1.txt @@ -18,7 +18,7 @@ Fixes and improvements in Django 1.0.1 Django 1.0.1 contains over two hundred fixes to the original Django 1.0 codebase; full details of every fix are available in `the -Subversion log of the 1.0.X branch`_, but here are some of the +history of the 1.0.X branch`_, but here are some of the highlights: * Several fixes in ``django.contrib.comments``, pertaining to RSS @@ -61,4 +61,4 @@ highlights: documentation, including both corrections to existing documents and expanded and new documentation. -.. _the Subversion log of the 1.0.X branch: https://code.djangoproject.com/log/django/branches/releases/1.0.X +.. _the history of the 1.0.X branch: https://github.com/django/django/commits/stable/1.0.x diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index e752b02e1b..1e44f57de8 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -57,7 +57,7 @@ there. In fact, new documentation is one of our favorite features of Django 1.0, so we might as well start there. First, there's a new documentation site: -* http://docs.djangoproject.com/ +* https://docs.djangoproject.com/ The documentation has been greatly improved, cleaned up, and generally made awesome. There's now dedicated search, indexes, and more. diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 12940114ed..852644dee4 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -101,7 +101,7 @@ If you've been relying on this middleware, the easiest upgrade path is: * Introduce your modified version of ``SetRemoteAddrFromForwardedFor`` as a piece of middleware in your own project. -__ https://code.djangoproject.com/browser/django/trunk/django/middleware/http.py?rev=11000#L33 +__ https://github.com/django/django/blob/91f18400cc0fb37659e2dbaab5484ff2081f1f30/django/middleware/http.py#L33 Names of uploaded files are available later ------------------------------------------- @@ -366,7 +366,7 @@ features: For more details, see the `GeoDjango documentation`_. .. _geodjango: http://geodjango.org/ -.. _spatialite: http://www.gaia-gis.it/spatialite/ +.. _spatialite: http://www.gaia-gis.it/gaia-sins/ .. _geodjango documentation: http://geodjango.org/docs/ Other improvements diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index a2b5447476..772dbdb2e7 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -329,7 +329,7 @@ requests. These include: * Support for combining :ref:`F() expressions ` with timedelta values when retrieving or updating database values. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. _backwards-incompatible-changes-1.3: diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 3a78a83ce5..0cc476e02c 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -192,7 +192,7 @@ from the request's POST data, sends that to admin@example.com and redirects to # to get proper validation errors. return HttpResponse('Make sure all fields are entered and valid.') -.. _Header injection: http://www.nyphp.org/phundamentals/email_header_injection.php +.. _Header injection: http://www.nyphp.org/phundamentals/8_Preventing-Email-Header-Injection .. _emailmessage-and-smtpconnection: diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 20dc61a222..a32d9b54dd 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -521,7 +521,7 @@ consistently by all browsers. However, when it is honored, it can be a useful way to mitigate the risk of client side script accessing the protected cookie data. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly SESSION_COOKIE_NAME ------------------- diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 0cdb8a49e7..4b0aca3548 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -70,7 +70,7 @@ server platforms. See the `server-arrangements wiki page`_ for specific installation instructions for each platform. .. _Apache: http://httpd.apache.org/ -.. _nginx: http://nginx.net/ +.. _nginx: http://nginx.org/ .. _mod_wsgi: http://code.google.com/p/modwsgi/ .. _server-arrangements wiki page: https://code.djangoproject.com/wiki/ServerArrangements diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index f7aadd68f3..aa274d83c9 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1940,7 +1940,7 @@ out the `full reference`_ for more details. .. _Selenium: http://seleniumhq.org/ .. _selenium package: http://pypi.python.org/pypi/selenium -.. _full reference: http://readthedocs.org/docs/selenium-python/en/latest/api.html +.. _full reference: http://selenium-python.readthedocs.org/en/latest/api.html .. _Firefox: http://www.mozilla.com/firefox/ .. note:: From 4f53e77f0720bd4d74b1b08857db8f7d32c65008 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 28 Jun 2012 13:42:36 +0200 Subject: [PATCH 030/176] Fixed #18306 -- Removed most of GeoDjango-specific deployment docs --- docs/ref/contrib/gis/deployment.txt | 59 +++-------------------------- 1 file changed, 6 insertions(+), 53 deletions(-) diff --git a/docs/ref/contrib/gis/deployment.txt b/docs/ref/contrib/gis/deployment.txt index d98fc51837..f90c9c2e91 100644 --- a/docs/ref/contrib/gis/deployment.txt +++ b/docs/ref/contrib/gis/deployment.txt @@ -2,6 +2,10 @@ Deploying GeoDjango =================== +Basically, the deployment of a GeoDjango application is not different from +the deployment of a normal Django application. Please consult Django's +:doc:`deployment documentation `. + .. warning:: GeoDjango uses the GDAL geospatial library which is @@ -10,58 +14,7 @@ Deploying GeoDjango appropriate configuration of Apache or the prefork method when using FastCGI through another Web server. -Apache -====== -In this section there are some example ``VirtualHost`` directives for -when deploying using ``mod_wsgi``, which is now officially the recommended -way to deploy Django applications with Apache. -As long as ``mod_wsgi`` is configured correctly, it does not -matter whether the version of Apache is prefork or worker. - -.. note:: - - The ``Alias`` and ``Directory`` configurations in the examples - below use an example path to a system-wide installation folder of Django. - Substitute in an appropriate location, if necessary, as it may be - different than the path on your system. - -``mod_wsgi`` ------------- - -Example:: - - - WSGIDaemonProcess geodjango user=geo group=geo processes=5 threads=1 - WSGIProcessGroup geodjango - WSGIScriptAlias / /home/geo/geodjango/world.wsgi - - Alias /media/ "/usr/lib/python2.6/site-packages/django/contrib/admin/media/" - - Order allow,deny - Options Indexes - Allow from all - IndexOptions FancyIndexing - - - - -.. warning:: - - If the ``WSGIDaemonProcess`` attribute ``threads`` is not set to ``1``, then + For example, when configuring your application with ``mod_wsgi``, + set the ``WSGIDaemonProcess`` attribute ``threads`` to ``1``, unless Apache may crash when running your GeoDjango application. Increase the number of ``processes`` instead. - -For more information, please consult Django's -:doc:`mod_wsgi documentation `. - -Lighttpd -======== - -FastCGI -------- - -Nginx -===== - -FastCGI -------- From 26cb227cfe42d2500eac62dc0d90fb5227b275b3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 28 Jun 2012 16:28:25 +0200 Subject: [PATCH 031/176] Fixed #15197 -- Fixed yaml serialization into HttpResponse Thanks fourga38 for the report and hirokiky at gmail.com for the initial patch. --- django/core/serializers/json.py | 5 ++--- django/core/serializers/pyyaml.py | 3 ++- .../serializers_regress/tests.py | 18 +++++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index 937d839239..941b9e0ba8 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -52,9 +52,8 @@ class Serializer(PythonSerializer): self._current = None def getvalue(self): - # overwrite PythonSerializer.getvalue() with base Serializer.getvalue() - if callable(getattr(self.stream, 'getvalue', None)): - return self.stream.getvalue() + # Grand-parent super + return super(PythonSerializer, self).getvalue() def Deserializer(stream_or_string, **options): diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index b639ad2dae..73e92d557f 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -44,7 +44,8 @@ class Serializer(PythonSerializer): yaml.dump(self.objects, self.stream, Dumper=DjangoSafeDumper, **self.options) def getvalue(self): - return self.stream.getvalue() + # Grand-parent super + return super(PythonSerializer, self).getvalue() def Deserializer(stream_or_string, **options): """ diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index f1b70a1d2e..4e73be015c 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -21,6 +21,7 @@ from django.core import serializers from django.core.serializers import SerializerDoesNotExist from django.core.serializers.base import DeserializationError from django.db import connection, models +from django.http import HttpResponse from django.test import TestCase from django.utils.functional import curry from django.utils.unittest import skipUnless @@ -501,15 +502,18 @@ def streamTest(format, self): obj.save_base(raw=True) # Serialize the test database to a stream - stream = BytesIO() - serializers.serialize(format, [obj], indent=2, stream=stream) + for stream in (BytesIO(), HttpResponse()): + serializers.serialize(format, [obj], indent=2, stream=stream) - # Serialize normally for a comparison - string_data = serializers.serialize(format, [obj], indent=2) + # Serialize normally for a comparison + string_data = serializers.serialize(format, [obj], indent=2) - # Check that the two are the same - self.assertEqual(string_data, stream.getvalue()) - stream.close() + # Check that the two are the same + if isinstance(stream, BytesIO): + self.assertEqual(string_data, stream.getvalue()) + else: + self.assertEqual(string_data, stream.content) + stream.close() for format in serializers.get_serializer_formats(): setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) From 54b1519dfd90ea042105fc42aa439763c8df93d4 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 28 Jun 2012 17:18:50 +0200 Subject: [PATCH 032/176] Fixed a formatting issue in the CBV docs. --- docs/ref/class-based-views/generic-display.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index 8ec66a8cc1..b90cbf95b2 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -76,11 +76,11 @@ many projects they are typically the most commonly used views. **Method Flowchart** - 1. :meth:`dispatch():` - 2. :meth:`http_method_not_allowed():` - 3. :meth:`get_template_names():` - 4. :meth:`get_queryset():` - 5. :meth:`get_objects():` - 6. :meth:`get_context_data():` - 7. :meth:`get():` - 8. :meth:`render_to_response():` + 1. :meth:`dispatch()` + 2. :meth:`http_method_not_allowed()` + 3. :meth:`get_template_names()` + 4. :meth:`get_queryset()` + 5. :meth:`get_objects()` + 6. :meth:`get_context_data()` + 7. :meth:`get()` + 8. :meth:`render_to_response()` From b9ecbedb31375a887d100ccf550a091c8d5d4fd7 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 29 Jun 2012 15:08:30 +0200 Subject: [PATCH 033/176] Fixed #18528 -- Fixed custom field value_to_string example Thanks anuraguniyal for the report. --- docs/howto/custom-model-fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 1377a62c89..99c5904fe9 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -666,7 +666,7 @@ data storage anyway, we can reuse some existing conversion code:: def value_to_string(self, obj): value = self._get_val_from_obj(obj) - return self.get_db_prep_value(value) + return self.get_prep_value(value) Some general advice -------------------- From 47da7b7a9aefce66fa2c54833b5ba308781fc95e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 13:18:07 +0200 Subject: [PATCH 034/176] Fixed #18102 -- Defined min/max_length on French localflavor form fields Thanks mothsART for the report and the initial patch. --- django/contrib/localflavor/fr/forms.py | 11 +++++++---- tests/regressiontests/localflavor/fr/tests.py | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index 47177db685..d836dd6397 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -8,7 +8,7 @@ import re 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 Field, RegexField, Select +from django.forms.fields import CharField, RegexField, Select from django.utils.encoding import smart_unicode from django.utils.translation import ugettext_lazy as _ @@ -20,11 +20,11 @@ class FRZipCodeField(RegexField): 'invalid': _('Enter a zip code in the format XXXXX.'), } - def __init__(self, max_length=None, min_length=None, *args, **kwargs): + def __init__(self, max_length=5, min_length=5, *args, **kwargs): super(FRZipCodeField, self).__init__(r'^\d{5}$', max_length, min_length, *args, **kwargs) -class FRPhoneNumberField(Field): +class FRPhoneNumberField(CharField): """ Validate local French phone number (not international ones) The correct format is '0X XX XX XX XX'. @@ -35,6 +35,10 @@ class FRPhoneNumberField(Field): 'invalid': _('Phone numbers must be in 0X XX XX XX XX format.'), } + def __init__(self, max_length=14, min_length=10, *args, **kwargs): + super(FRPhoneNumberField, self).__init__( + max_length, min_length, *args, **kwargs) + def clean(self, value): super(FRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: @@ -51,4 +55,3 @@ class FRDepartmentSelect(Select): """ def __init__(self, attrs=None): super(FRDepartmentSelect, self).__init__(attrs, choices=DEPARTMENT_CHOICES) - diff --git a/tests/regressiontests/localflavor/fr/tests.py b/tests/regressiontests/localflavor/fr/tests.py index 55f8a68b3b..8e99ef462b 100644 --- a/tests/regressiontests/localflavor/fr/tests.py +++ b/tests/regressiontests/localflavor/fr/tests.py @@ -16,7 +16,8 @@ class FRLocalFlavorTests(SimpleTestCase): } invalid = { '2A200': error_format, - '980001': error_format, + '980001': ['Ensure this value has at most 5 characters (it has 6).' + ] + error_format, } self.assertFieldOutput(FRZipCodeField, valid, invalid) From 596e15293c9f522a2b001d49a0c40005711682a6 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 14:30:32 +0200 Subject: [PATCH 035/176] Fixed #11162 -- Mentioned ValidationError in custom model field docs --- docs/howto/custom-model-fields.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 99c5904fe9..8b4c2303ab 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -434,10 +434,14 @@ database, so we need to be able to process strings and ``Hand`` instances in p1 = re.compile('.{26}') p2 = re.compile('..') args = [p2.findall(x) for x in p1.findall(value)] + if len(args) != 4: + raise ValidationError("Invalid input for a Hand instance") return Hand(*args) Notice that we always return a ``Hand`` instance from this method. That's the -Python object type we want to store in the model's attribute. +Python object type we want to store in the model's attribute. If anything is +going wrong during value conversion, you should return a +:exc:`~django.core.exceptions.ValidationError` exception. **Remember:** If your custom field needs the :meth:`to_python` method to be called when it is created, you should be using `The SubfieldBase metaclass`_ From db87016b1a92b790c4250a2b5c16ceadd737a00e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 15:06:42 +0200 Subject: [PATCH 036/176] Fixed #12493 -- Deprecated auto-correction of TEMPLATE_DIRS --- django/conf/__init__.py | 3 +++ docs/internals/deprecation.txt | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 531eed8658..5e4b412f32 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -103,6 +103,9 @@ class Settings(BaseSettings): setting_value = getattr(mod, setting) if setting in tuple_settings and \ isinstance(setting_value, basestring): + warnings.warn("The %s setting must be a tuple. Please fix your " + "settings, as auto-correction is now deprecated." % setting, + PendingDeprecationWarning) setting_value = (setting_value,) # In case the user forgot the comma. setattr(self, setting, setting_value) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 342ef5266a..f338adac33 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -276,6 +276,10 @@ these changes. * The function ``django.utils.itercompat.product`` will be removed. The Python builtin version should be used instead. +* Auto-correction of INSTALLED_APPS and TEMPLATE_DIRS settings when they are + specified as a plain string instead of a tuple will be removed and raise an + exception. + 2.0 --- From 5d81ad1af14ee3127b53f923687ed5c4e00329ee Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 30 Jun 2012 10:25:51 -0400 Subject: [PATCH 037/176] Fixed #17168 - Noted TransactionMiddleware only works with "default" database alias. Thanks codeinthehole for the draft patch. --- docs/topics/db/transactions.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 76b65b99e0..1f15949000 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -56,6 +56,13 @@ database cursor (which is mapped to its own database connection internally). .. _transaction-management-functions: +.. note:: + + The ``TransactionMiddleware`` only affects the database aliased + as "default" within your :setting:`DATABASES` setting. If you are using + multiple databases and want transaction control over databases other than + "default", you will need to write your own transaction middleware. + Controlling transaction management in views =========================================== From 2c2c8a63266b586e3782267fb361504e5ac05abe Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 18:49:11 +0200 Subject: [PATCH 038/176] Isolated sitemaps test from ABSOLUTE_URL_OVERRIDES Refs #15988. --- django/contrib/sitemaps/tests/generic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django/contrib/sitemaps/tests/generic.py b/django/contrib/sitemaps/tests/generic.py index 5f8b6b8be0..e392cbf909 100644 --- a/django/contrib/sitemaps/tests/generic.py +++ b/django/contrib/sitemaps/tests/generic.py @@ -1,7 +1,9 @@ from django.contrib.auth.models import User +from django.test.utils import override_settings from .base import SitemapTestsBase +@override_settings(ABSOLUTE_URL_OVERRIDES={}) class GenericViewsSitemapTests(SitemapTestsBase): def test_generic_sitemap(self): From c446bdee84efe42f5c0bbfee16986a80c83ec9a2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 20:50:24 +0200 Subject: [PATCH 039/176] Fixed #17024 -- Added import statements in tutorial code sample --- docs/intro/tutorial02.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 0d95f6ff37..dd315ed8a7 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -113,8 +113,8 @@ Just one thing to do: We need to tell the admin that ``Poll`` objects have an admin interface. To do this, create a file called ``admin.py`` in your ``polls`` directory, and edit it to look like this:: - from polls.models import Poll from django.contrib import admin + from polls.models import Poll admin.site.register(Poll) @@ -283,6 +283,9 @@ It'd be better if you could add a bunch of Choices directly when you create the 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 + class ChoiceInline(admin.StackedInline): model = Choice extra = 3 From deed192dda621f1cdc8eb7240f1e5914045402dd Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 21:19:07 +0200 Subject: [PATCH 040/176] Removed usage of mimetype kwarg of HttpResponse Refs #16519. --- django/shortcuts/__init__.py | 2 +- django/views/csrf.py | 2 +- django/views/debug.py | 8 ++++---- django/views/static.py | 4 ++-- tests/regressiontests/test_client_regress/views.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py index 9f97cae955..154f224671 100644 --- a/django/shortcuts/__init__.py +++ b/django/shortcuts/__init__.py @@ -16,7 +16,7 @@ def render_to_response(*args, **kwargs): Returns a HttpResponse whose content is filled with the result of calling django.template.loader.render_to_string() with the passed arguments. """ - httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype', None)} + httpresponse_kwargs = {'content_type': kwargs.pop('mimetype', None)} return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs) def render(request, *args, **kwargs): diff --git a/django/views/csrf.py b/django/views/csrf.py index d58a1404e9..c95d19d56d 100644 --- a/django/views/csrf.py +++ b/django/views/csrf.py @@ -101,4 +101,4 @@ def csrf_failure(request, reason=""): 'reason': reason, 'no_referer': reason == REASON_NO_REFERER }) - return HttpResponseForbidden(t.render(c), mimetype='text/html') + return HttpResponseForbidden(t.render(c), content_type='text/html') diff --git a/django/views/debug.py b/django/views/debug.py index 25eee4a91a..3414cb193c 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -63,10 +63,10 @@ def technical_500_response(request, exc_type, exc_value, tb): reporter = ExceptionReporter(request, exc_type, exc_value, tb) if request.is_ajax(): text = reporter.get_traceback_text() - return HttpResponseServerError(text, mimetype='text/plain') + return HttpResponseServerError(text, content_type='text/plain') else: html = reporter.get_traceback_html() - return HttpResponseServerError(html, mimetype='text/html') + return HttpResponseServerError(html, content_type='text/html') # Cache for the default exception reporter filter instance. default_exception_reporter_filter = None @@ -443,7 +443,7 @@ def technical_404_response(request, exception): 'request': request, 'settings': get_safe_settings(), }) - return HttpResponseNotFound(t.render(c), mimetype='text/html') + return HttpResponseNotFound(t.render(c), content_type='text/html') def empty_urlconf(request): "Create an empty URLconf 404 error response." @@ -451,7 +451,7 @@ def empty_urlconf(request): c = Context({ 'project_name': settings.SETTINGS_MODULE.split('.')[0] }) - return HttpResponse(t.render(c), mimetype='text/html') + return HttpResponse(t.render(c), content_type='text/html') # # Templates are embedded in the file so that we know the error handler will diff --git a/django/views/static.py b/django/views/static.py index 1d7891e3f4..64f0b1c262 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -58,9 +58,9 @@ def serve(request, path, document_root=None, show_indexes=False): mimetype = mimetype or 'application/octet-stream' if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), statobj.st_mtime, statobj.st_size): - return HttpResponseNotModified(mimetype=mimetype) + return HttpResponseNotModified(content_type=mimetype) with open(fullpath, 'rb') as f: - response = HttpResponse(f.read(), mimetype=mimetype) + response = HttpResponse(f.read(), content_type=mimetype) response["Last-Modified"] = http_date(statobj.st_mtime) if stat.S_ISREG(statobj.st_mode): response["Content-Length"] = statobj.st_size diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index 3a934ea047..8792a97dc0 100644 --- a/tests/regressiontests/test_client_regress/views.py +++ b/tests/regressiontests/test_client_regress/views.py @@ -84,7 +84,7 @@ def return_json_file(request): cls=DjangoJSONEncoder, ensure_ascii=False) response = HttpResponse(obj_json.encode(charset), status=200, - mimetype='application/json; charset=%s' % charset) + content_type='application/json; charset=%s' % charset) response['Content-Disposition'] = 'attachment; filename=testfile.json' return response From da200c5e35cdbb617572977bcbf97fae33920b2e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 21:25:16 +0200 Subject: [PATCH 041/176] Fixed #16519 -- Deprecated mimetype kwarg of HttpResponse __init__ This keyword was already deprecated in the code (supported for backwards compatibility only), but never formally deprecated. Thanks Paul McMillan for the report and yasar11732 for the initial patch. --- django/http/__init__.py | 8 +++++--- django/template/response.py | 14 +++++++------- docs/internals/deprecation.txt | 3 +++ docs/ref/request-response.txt | 21 ++++++++++----------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 30d7e5dc2f..51e6ca2304 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -524,14 +524,16 @@ class HttpResponse(object): status_code = 200 - def __init__(self, content='', mimetype=None, status=None, - content_type=None): + def __init__(self, content='', content_type=None, status=None, + mimetype=None): # _headers is a mapping of the lower-case name to the original case of # the header (required for working with legacy systems) and the header # value. Both the name of the header and its value are ASCII strings. self._headers = {} self._charset = settings.DEFAULT_CHARSET - if mimetype: # For backwards compatibility. + if mimetype: + warnings.warn("Using mimetype keyword argument is deprecated, use" + " content_type instead", PendingDeprecationWarning) content_type = mimetype if not content_type: content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, diff --git a/django/template/response.py b/django/template/response.py index bb0b9cbf9d..286417ccdf 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -9,8 +9,8 @@ class ContentNotRenderedError(Exception): class SimpleTemplateResponse(HttpResponse): rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks'] - def __init__(self, template, context=None, mimetype=None, status=None, - content_type=None): + def __init__(self, template, context=None, content_type=None, status=None, + mimetype=None): # It would seem obvious to call these next two members 'template' and # 'context', but those names are reserved as part of the test Client # API. To avoid the name collision, we use tricky-to-debug problems @@ -22,8 +22,8 @@ class SimpleTemplateResponse(HttpResponse): # content argument doesn't make sense here because it will be replaced # with rendered template so we always pass empty string in order to # prevent errors and provide shorter signature. - super(SimpleTemplateResponse, self).__init__('', mimetype, status, - content_type) + super(SimpleTemplateResponse, self).__init__('', content_type, status, + mimetype) # _is_rendered tracks whether the template and context has been baked # into a final response. @@ -137,8 +137,8 @@ class TemplateResponse(SimpleTemplateResponse): rendering_attrs = SimpleTemplateResponse.rendering_attrs + \ ['_request', '_current_app'] - def __init__(self, request, template, context=None, mimetype=None, - status=None, content_type=None, current_app=None): + def __init__(self, request, template, context=None, content_type=None, + status=None, mimetype=None, current_app=None): # self.request gets over-written by django.test.client.Client - and # unlike context_data and template_name the _request should not # be considered part of the public API. @@ -147,7 +147,7 @@ class TemplateResponse(SimpleTemplateResponse): # having to avoid needing to create the RequestContext directly self._current_app = current_app super(TemplateResponse, self).__init__( - template, context, mimetype, status, content_type) + template, context, content_type, status, mimetype) def resolve_context(self, context): """Convert context data into a full RequestContext object diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index f338adac33..9359c82e46 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -280,6 +280,9 @@ these changes. specified as a plain string instead of a tuple will be removed and raise an exception. +* The ``mimetype`` argument to :class:`~django.http.HttpResponse` ``__init__`` + will be removed (``content_type`` should be used instead). + 2.0 --- diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index a29ddc63cc..551ee00c6b 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -606,11 +606,10 @@ Attributes Methods ------- -.. method:: HttpResponse.__init__(content='', mimetype=None, status=200, content_type=DEFAULT_CONTENT_TYPE) +.. method:: HttpResponse.__init__(content='', content_type=None, status=200) - Instantiates an ``HttpResponse`` object with the given page content (a - string) and MIME type. The :setting:`DEFAULT_CONTENT_TYPE` is - ``'text/html'``. + Instantiates an ``HttpResponse`` object with the given page content and + content type. ``content`` should be an iterator or a string. If it's an iterator, it should return strings, and those strings will be @@ -618,15 +617,15 @@ Methods an iterator or a string, it will be converted to a string when accessed. + ``content_type`` is the MIME type optionally completed by a character set + encoding and is used to fill the HTTP ``Content-Type`` header. If not + specified, it is formed by the :setting:`DEFAULT_CONTENT_TYPE` and + :setting:`DEFAULT_CHARSET` settings, by default: "`text/html; charset=utf-8`". + + Historically, this parameter was called ``mimetype`` (now deprecated). + ``status`` is the `HTTP Status code`_ for the response. - ``content_type`` is an alias for ``mimetype``. Historically, this parameter - was only called ``mimetype``, but since this is actually the value included - in the HTTP ``Content-Type`` header, it can also include the character set - encoding, which makes it more than just a MIME type specification. - If ``mimetype`` is specified (not ``None``), that value is used. - Otherwise, ``content_type`` is used. If neither is given, the - :setting:`DEFAULT_CONTENT_TYPE` setting is used. .. method:: HttpResponse.__setitem__(header, value) From 03896eb5df74a47237ec3ed9a73aadc925e90dbb Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 22:21:23 +0200 Subject: [PATCH 042/176] Fixed #17026 -- Improved wording of contrib.messages' storage backends section --- docs/ref/contrib/messages.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index da6336e832..6929a3b0d0 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -54,7 +54,8 @@ Storage backends ---------------- The messages framework can use different backends to store temporary messages. -To change which backend is being used, add a `MESSAGE_STORAGE`_ to your +If the default FallbackStorage isn't suitable to your needs, you can change +which backend is being used by adding a `MESSAGE_STORAGE`_ to your settings, referencing the module and class of the storage class. For example:: @@ -62,7 +63,7 @@ example:: The value should be the full path of the desired storage class. -Four storage classes are included: +Three storage classes are available: ``'django.contrib.messages.storage.session.SessionStorage'`` This class stores all messages inside of the request's session. It @@ -74,6 +75,8 @@ Four storage classes are included: messages are dropped if the cookie data size would exceed 4096 bytes. ``'django.contrib.messages.storage.fallback.FallbackStorage'`` + This is the default storage class. + This class first uses CookieStorage for all messages, falling back to using SessionStorage for the messages that could not fit in a single cookie. From 55ffcf8e7b414a39e2dfc9c9eb4c5d3fa548e78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rau=CC=81l=20Cumplido?= Date: Thu, 7 Jun 2012 13:46:06 +0200 Subject: [PATCH 043/176] Fixed #18145 -- Improved documentation of unique_together type fields --- docs/ref/models/options.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 6ca3d3b2d0..9d076f6274 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -244,12 +244,12 @@ Django quotes column and table names behind the scenes. unique_together = (("driver", "restaurant"),) - This is a list of lists of fields that must be unique when considered together. + This is a tuple of tuples that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate ``UNIQUE`` statements are included in the ``CREATE TABLE`` statement). - For convenience, unique_together can be a single list when dealing with a single + For convenience, unique_together can be a single tuple when dealing with a single set of fields:: unique_together = ("driver", "restaurant") From c5fb8299ef9658a58221d4894dadd080cc25962b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 30 Jun 2012 18:20:34 -0400 Subject: [PATCH 044/176] Fixed #17705 - Updated TabularInline image and doc in tutorial 2. Thanks xbito for the draft patch. --- docs/intro/_images/admin12t.png | Bin 0 -> 17414 bytes docs/intro/tutorial02.txt | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/intro/_images/admin12t.png diff --git a/docs/intro/_images/admin12t.png b/docs/intro/_images/admin12t.png new file mode 100644 index 0000000000000000000000000000000000000000..14c5c31a4f36e4305646cf8cfa478fb0047bc4f4 GIT binary patch literal 17414 zcmcG#RX`j~7cGi~00|P@-Q6X)Gq}6EySoz{26uN0?hrh*n7*Q*~0-&7P)f5IPUP{(tbYw-YC6%&mD z7SUfMOc7z*M-(TQxs7uYff8_i#}kMz;66+_?R_mDg^b9T4@7Ws(+UQKhqCu>mlV>{ z)~WGnMoRuOhZ)d=Fd67}a1;xf8UCw!1eeb!e?BKvxZ#es1{8e7<+%(rj2W)z+F_O0 zh@NTp9T?VmkKfcDze77VkgCBTr*P`2qHVbAIWdi+8I6+>l&6t0o|lm=N>N%T1(+!` zxJar_{3b1Ft2^u91^htCcFm|(mu4C0!F6qiPoHk~8gwv`7ya8hA{` zh&cv+pdPs4R<_1CWL>TI{`@Nw#wk0>$?Q6J;CSct?#pcR@908KkI3mQk3)|*Smk4Q zSf^~Y8!;V@A!Ut@D!9X9|AN+V%1#t*jZo&p)qCj1x<8b?Omq`mWL76X$Vw)FgaWZ5 z7st3G`y$9YWe2JHwzi21W;QcR_FZPZVM03a#lLr2A_;F;j7$m(^49~QMyi-^Q=Ee< zp_!Z{F8<*28WQVQZb=)z^-dAA=kTNwR&upP?xJsYkq26!FP#JtV`p)x5~tzo5RSLP zYVYBxIY|RsyoRQ6zj!01u8p-Sc-<`KxIvD4aMnMa-dERf!qZaYj!%n@mvG*B42`c5 z4p@*Dxlua3MWAFMH@g|J4wcn?wL_r(lF>~<*STT#5ne_cl>Wul#2`bm_&_n(%Cv*U z2o19?{Meu<*`eZaz) z@g+r9?~*gUVEs_ub9ty0yvPmnFG2Y_@835*HP^XoqLtEK?Ci~6f7Rh|h_IiKB^4v# zLJL*@7MqM?Y$vFonEv&pt5V8_h&dtYS$AmvOG1{ZR@lM?;jnDgytzX$nW(6gI~1Vl zLDN}PO)^s`xOIh#{lfaZ zY3XPX)dJb_CuWZ^4W=S|aZ({g;2L7kMRs(1$u3Ih0*h&X`uQ8<h!RzX(qDLfaaSKKk$Qw>@@&-pVhA0c&oqnpRT6D< z96O8~{j;WE2Qne3HS%V$#;xn1Lv%tbGc~_=%mRT@_N#QvK}*U8(KwUzYx2ghipF!q zh`?o_rUp;dh<_VwQpmX(yaI>A*nWD7q_Hsq*Uv${dei6^i?u_)&h>RXRoJ&5fOv>- zW6jX=<{|lMi+C7!vHEf2Lzz*<2*8eaijlAGSKG!6(HXQSCd+6pCXJ+`9gb3&o8poK z%Gz|LmexRe`ZQntdxlQF z4F_h9!5Qwv)-6?Z@*v@wI_v6X&W1r%UTv~Uh|1xT-Ni0OIfk* zY#g-45);WRX7EbV566E0aK)^s?0p&rUQRTY!OH^osxotu^Y=1nWidk1q7N+TJX8#B zThp^`xh>dNtx@H+@KKy%acgz(To<6p=H7ybUq`-va(|z6_DOSKd`9t<4m&1hiY=6; z804+$bEQpX+?ad`Z1Ym}aZz73`v_&=6Nm+BB0UPJS^>%m^8 zQ_pEPOqY-e6A4GIm8U zk|tLT70xb%I#SGkZsQ*u=jh145y*_lV`5)R{!l3t4^N*br(^LKTM2)aM#Glju5q0h zjh?ZT#@0?mrmM7=9~uxR8!a1w_R-Xg%5+q2;&MzVWbZ5j7>Oou4g<+?N zAb;7CdV`!B{*`fv^w=;pbDZfHz?5%tcO7QzY0r-Ta;ci0ngi0nk(qL!Puw9y00o6LFyXMjXXpue|O`rHZ10(PmN7V`5q*f`^rfK z$8q7)UR<2%lX+^QdaaBsDueZ{lzjUuI%`e?KxG%FOEwaecYm;$op1zgJ3S%oZnZYCd>hV_EHkw%;6;6<~>< zc4NAlZEk^wx1CrQhil++6F()sBp<|;`UOAwWqdS%j2$`^vSZgg=A5bp(u+C8Lp8U( z@+@&G*W++VyZiNA%lr2{?r6X{=1-GQI^(xUB?4)L1A6Me`;uj-_7*`wBb5Df(m+Bt zMl%oODf#;Mr%IDM*$aGvAbj@EI3rEFu zJOq*V;=ns(q{a;+<7QlMbL7Vi0I=P?aOzq97YEraYB_hj~BRA)tvk3NEu z=L;Hl&CE-=?+2o|yyxc%+P<_KzEA-ZtR`FN&bmi->^von=ZzteW0LTsr=4$7vNkFM z8|2{WEe0=W&h3-0&J1TIIlRZvs&wftdf16z>~On?@tmTLY-dLJ1L}i|2OTy8tM_dLs$ta` zpkunr1*Hnu)g>jConOX=Jt>=MOSZYQ}4L@kYVnN%;!<@)|P zv$|!vxmyb7Ay?&tj$OOAF{VCd={tffu9Zn}fMMsZ@q}7Q8kBqfHLGFe#cj`?`2>72 zp7Y5KJ<`EaSm^tP1KZH^&IO6bvtysvq8VsOhHx=np$pBNPpC=Zj6+qo%xDI29XK=X z`fABNr>6EaktM`GT_CAP2k2ry+J^ba+*dbXt7&ze#^4-xLb0yrqP`(i&a0|fQ#yoC zo90U6THCGi%I_X~*7M@Dr8#tq3xD)}1$rE4uSLhb05(z3Zb~SC;FsT{1~VzJvP;*l zyxM;CFdh1&-uWaLoo#sddlh5y%p!x#JnVg+uKVHkuF5mS1d#T4T2)i#Oh3h;cB2@6 zxkpRcVGZWGx7g6~xlb9rt^T*PFUzOO8lvk~lggxlhd1ZqbNH%} z!16?-5?d`KHp5{m1{#^sw9qDc`v=u$nRxd$#vq@i2X&sa<_-Oz_Zp;Sh`H*SIB4pi0JKvu%+Z70`2nlF~DF5k1%X_F<% z&R!h47LBjyF_S)tk6L@Rc>xm=QX7BzEUKpm4y$lD;MbIthzqFKm&Dj>O#*?y<$5zj zy@hQpknhzC^w%vB%?SNLHuLk=Ry*tD%v??ufTxB+gzsS|*DP!Idl*a6aZ4f-X;M%E zA1T!w;)o3T)-JzF*4L}l33BN*Idd9-V8Vt?=lbw-b&r913;eH>N5~0F@l$4Q#Qv*@f-24c;cHUqRqiV}dl!l(p1V@hJ45|0W}@G#p>0)nvQ*KHg(Fz6ZgYxySg@H}#%Q!_NOS z5@w@psVi%`z8zgeE`)|sq?p^8zrHA1AC;;e&+PMFiORK7Z1Wh&27v*3Eh0fM6*ap7 zGo1Ef61{2-Kd?8UhRmnO^TSd2^2qXro7+&i1v~z6lQk03OxzripGQ1k(;w~Z9k=fQ zwxp~q-K)DuKlhI#eHkJ-TD9^-#RJ+D@DziaQM(JyMW){z#*-HXXjf%Qg~@FnU4ITq zvNoh~2!=R^PHGxUx(U1~j;pJy_x6mj306pdEnnxTZ7$0CZP~d^$Vo_R{5-oRjeqLD zhg?UPEla@pxLZIUh@!bc{_|$Xe|77XfpGTZdB`-oMq~PY((Y;1$Ig((HvQXTkgmr^RvrF~osKR(@czI3f=Xx=VvGJJEv_ubo|XwKoiJ;ph``m{LEboqLsAF@34 z?8ED_4x|d>^LVTJu;qSyD{gErH>K01wp)HUO2rTF)K=L5UPa@AAaN;AHv?d|l&(m}$ z^U3pmBsy(oPJD9akGqSFR)(9;{G)22A~X@+>>1+-VI=JAuBCzB-5J7`z0-V_dKe=` zrCLZG6mE?W0ucHIKg4Gv5xk_QcVqduW=rYHs@+^JeIB@XYqGu1=Q*;YDqbx6rt!-} zh?7Q(gz7A3#c}42YdW5;Ki+Pl7gZT*p6>gi%~+qD%ns$of8u+6@DCk-CYd0H0}lX0 z!gPahhA(*-5XI10S5hoCL3h4BnLeX?QT{Sg&(fUJQ&1XMv+$4E#AQFA^b(IZKI}0#V!Wz0g zfPB+rdN#^+WY;Uo;ozMoiDhP6Y0Z0mwL)ULyw=L%G=psE)=}F$8-D-R>n>DQuwQk^ zSC2N9%3|h;JNW{$LJ84>CIoEUoMl&(TGA(2hj2_&w<}7F z@Ag&oYB*I& zD*w<1_Dp(9fFogY1q8^HMD`=kU>IX4+r60US-sxT)G?I+%xd)k4yAz=JETza1rD59 zM}Is5e;TDKtrwtGTpws(_vNg;oDAU*JP9sja({SFvKsEH;cR$ZXaBJjq!pr(N~9=cjnk9NEt6op(LFmzK77T}&Y zx_Dk?wx+ceY@*^I?*QsVc|d`waNRvVdH>$V9sPT0(qL{#Jz}b>5ACdu{BeJHo8HjbGOef+CjnF zXf;_z%bmv8ZLw&X8Fs_bScx}{!l~4)#%GxC#dN6F#_V>Q#@ErOsq5}{nNcw9@|3&h z>W0xLsp{#>_l`BfL<+MBDrsec4Q<05WEl&@YM*kdn`Er3LS{ZTFH14)L?G8T2`POc z2WZFJHY2k9WPg;MDJm+OEs+yn$55>cHgs^ii$9^K0K@@P8`A2TS0knHt~5lIOp?MR z|J<&)Vf;d(n?{1bgthwQtSmc2S|J6PFyK?a1s5^-K* zc}vHr`OwofmK4U|PGQx9w}VW(OyW<&9@eonU0u}*x2YQes!v~xdCe^6=o=z=swK5p zjmp7pUJeG`+c|ZwNQ#cn&t)VH-3Yc|gAYcG(4Zqkd;}D%2Rn{iy=SP9VA&3(MzlPq z!RaB&%FQQeheEE|!}9etvnBT`c|9uEg%IPZfOTejW@+gW6nf0wqq z-glh(9G5FeCsHnijDQhP!2ft;jnXt&0C!n-A-yY=Q;8XaF{4(KTy*MVunMk`dNdz^-`+?`j zsD(HkJzUhRk|>N{DVS)8c+Fz0q9fdwFFs@x%HDy_pr|!tHq?@;Fe&m~LgrNiCcXnA zG1vC?l0=6<$dEo`l$hhl$jJ4zJy}B~@n>4t|kP2e#`4nmde1$SX z2WZ8eM7ir%b#zGQMVz0iIyn;rf`iuc1pCwMAY{yZ+!!JP8mG@?FHVa+S9FA3*|%6W z>$~z}&!OYUs3h~XS%S7M`9U%g%%}gN^9Hj1VXb4l(4D?}otbsO+qGr$W1Un;h=>0| z_Js0K_G=~zIPiT{>bq@KX1PG9tN#V<`;ozh!x7-}RmdF$6(7AwC(5@N-59jt-z1mS9(Uao zu%9ks5K6j5F9-Aej@XP*zgFSxeL4EY*`^?Hc}j=0L(D|J&t(~7$31VHAjSBuFD@30 zPA38X*Tw~H)c`Px;U0HP1Ey3NGQApZLo)d92|+_cmU3sCA8+pwrje;ry5^_m+sQr(DHEg_s1^W1y}0~L#{d<)SEN@Gb6Vx@@1fIfs=+LH zt4)uZY7CS-nV?7SX8k{m;m=1EmsK{G7901|TA!)uGdFJUvw<-)cy@N&B?<%|moKXk zt1*6UzAp|mdV!wo5(QCWJ~utF8_Uj7J4|Nyzy2reZ3+L1J^jRl#Ie@>rc7ZkS*gOA zK_JR?hgJ2*c5Fd=xz*iH6lDOh#r=rXm;Ix8#X`9jLTV)|1!bJ~nz5r5Pp)z>gntAn{{fiT2 z*?@7wws(=@vafUB#SVGJ0JF=mq}S76BZ1wLPj!%^W+i+3xOLqUUL4i`fE|PiUHenK zc>&3@R(5*SB&C#4AcN;ZZeXbys6DGVYt)+MFUD0MH4ON<$Yt=5F`QG#}1j=A#2F&&roHwiY_1QA--76jJMCS%&eDF!4-^ zBJM&!Rf9Gl7}OdR=NaEG8ONgwq?idM(J-2;sdAQ)w=d~u;=%^E;Zo+Gu%FX=rG|!^Kt^oy`$^dZF901U}p6QXa1^%eYL*p;B8T+65BB{)G|7 z3j&uk?DQ9QsO+}sgrYD`jfbCL30$jaVE)vlZD-!6;QYy-VrEOos4HqoZ{hDG@|ezF z=%jAL&d!g;5ayeWC#ZF4@6n0 z*U9}Utxx`p4uMz*0aL~}EZ49YNcJcKFQLKx9yX}y_}S8AO2l~AWWxQkwi(i0_EleT z4;)`55Pb}3Em8l=c!Bt+GuB&4|I#I9j9ZYwq~L&3mc zDcV?B*^KWR%Ab$NhX^_twiJ%=JQ$4IV2c<_W{DESKlWm{`{Z3f*+m}hf(wbLBq|jJ zUnvSG7)XdiNy++avz}vbgSaGchX6vD&SxQ8U0(r192e5yQ5B~yCN>f`WWdDAhxgCt zi%n(a#h0_)5ZBZ_6B&Ts$8xBa-~G++L|j>6Q~HKdtZEn{BxEDXZ@r$#E#cq0%mEft zC?}4sQ|%ana)UpR{X>6vda}xl+kEc?r5;bdnug7mU0kqZem$!ACWQ@sfVOkZZ(Ajx+U0W{uEw5%$hhcvloY1R z!`92HjoWefdvNCWxkn5Zfe0AXV5GqMDQpXMXwILZtgKxVV(XnEO8i)gl!yZ5I?5z% z`V;^uazhnU`bzj-kvd5d?naSAB%;r?Eq7>M=NzLa3hvo2j2a&MkGILL2R3XQ-mCqT zP4DMh9|HVa^o%B8ogKgL_I4P)_kH~?{ARburVk=d^A+cF)A3S#0voS6xcGjIkBKFX zX1dqj?BYA7Q=R^8&Ek=`wV9}h>*-3ZqV-4q z>koX;sU!=PDU%dpWAvhhiEU%gK7!QT7$xse3Js3zV9le+^Ed~;)BCs_Bj?dbm3GDV z`9e>J>22k6wzkN*dDPnHKneP^EiX#sT(>`Wg8ag^h}iLz9AlmDExTM+PE=|xND=UdNzmMth`&!oFx7cnrE zu`}xqBZzQbsWe~vTF+IfngQQe$xfl^7~PJ0qRRrr?WnF2q(L1>_llHT$kRWxDyKuh z128_Qk0B_6g(=BN0KiBDei49g&n-{X+|TAs_*Un9m$_i@hVr{~)|X1rnP026xMX*} zPyfzVOds7UWo7DHORL?nwjU+`9mLfD9?3v;6WzPPbEhA=6($#UdRo)3&Vh<9yF)sD zzDEa@WX)ghwL>;vn=nei?o35)d!U6_V+Tue;`*tnOj5KRUXsG}G|@Tg?WGDx@eem2 z=cEAK7vYhLv~TC9>w!n5>QpF@a^;CvxhW`lAt-AIY!Jf?cHWP=+E`?5$0KUG6R?eQ zA&Q<;?|57uPm$;hx-CaQ@Q3+#(MTQs5Y!jGkW`__Rr^jknw`bd5$E99`A%e3DgQBM2~vC^WQ8!uV+7Fx-^ z83-@;N_2qc36QC}7|vqnH~gF~Oe-9ig9{|hu!=+h0;d^zB;nJ+UGvXV06b(qGuEeA zw5ozlqXzopA0Aw!08TttbtJ64j!a5Wz?3ad0(rQmTn6ka`Pk2?r;#)qO{Lbr4-CDM}(H4!Rt7iNfcZ4wf+XP-W5knORpw}%At1cZmp zl!qWvyr!5lV`P_w;*^}=)Ce>G@9-fEg!#&m0{OXKR9+BF?cY5k6944(HrjQuAR-F0 z-xwjEQ!?x7>TaWG8|?M1Vg1|xH_;Ggi*vgHCwdSR>IVRZ&z9>+LQy#3p#B*h;DouV z6=5m3siUx9CO;tlL$w77Y>KvY4SlO zM;IwBt%_wfZ)!kiTcb{7jBU`D{~pzw38pq&YdPBeY=3^JXWOBV4U|xO3b5gxm?`n7 zQhr@A74iYnTumQ!hI zQ&y=)vA<6-5|Y|z^U!7J;&aUX@yL4IR)IIv^=v$QG0Fez7UXy#?Qp#L{&>4Fd)VfI z1t$Y&=N#=i%wfnx)wX;)h_9LFm%MD*-YCH?pX_*h4@l$JaJ_FF(%bYlei5(1C1=p! z%HqqMoA=l`Z{|4Le4iEO%2mgmHB6$7snLVeWQ5cV?Cn-E*OcS+0 z#ukjRWRf;@GF5(llA>Ac0(sKV;DDJlO(rjy7?#`dZ0xJ2SYVXc522g>#dU{|{rXC? zk+HPaWdOBG!_vw^9#{RH)i&@&*ZZK1yE@g7;!{=_-?Jv~T~Sy~a-MmSA-*1FmTtpy zwVnx?Th5Z}nE=-P5Jpay%d&F3|KvdwA(xq@bRsczvRx%l=g04>n5e^Suji4pW8zv$ z3rSbUL~6+sODo$(1II-9E`d^svDf7qRR+qbEV`yJxs5=IzouHiu$X_rp1fF9ae4WO zv`iXM(h#cX)KcMeOf{0~{N!_v-VKez{DT%k!$7gafv4w`Kt|=a!e%H@DN-tPa-pN8w2Cm4*;9`Luvt zuWE~v;;Pi`PgFOD6T?!`#Ky2JPP0^b9i@bFw0mazlhTnprFGyvU*bV~4-AU%uHYb ziP$!r8-zIqY^!#5RuW?wsTAl9W0?LUwgXJqlP}32vu=~?oC6YLM8%!y$}h3FsKo8`=frfTz9eqEN3a2 zN1H#KOitFSV&~iJjE8$z>Ln+YkXCaDo~MIMJSVgcx+^~bBcY_?EzVMopPIuM%zVxJc|^tL060f9@}>) zU3#T7&7C*be73!UXWnN$Sw7-~`mz;1naTmgIGKq@C%D?-OYn91`XSu#@zaq)TC_C3G#%091>XO}U5o0(0_=QfdV%_nxU;;E$> zcURnXvrpVA?|&(vZx0dkV5iBp`v$DHQhU3I`3R{;3}YZi70Y{~n5&N`TK*RK2_jFP zQm8u@aT#)u_y)Udk5Dqa_DBMZtJZ2fU9DN!Al2xcfnZ&JU8(J^k)QWz(Tv~;l%RO; zuG-Ax)@yo?PvOACwJjIQD>#`7ll$1e;!`e+{Bt}ZFy@jgrzwvyS;P!S_VZ{%R{j3M61&Oy z5~_ySY`9V`r|YvFz4&qUXT5t>Im9LV=^^Lh9l38}Es^hK`@`#fJ4f%WJ%xt29@l?S z;1@smVcDdY+J_5xsLacG&edg?8~@8wH-nIH?vNRNEHNx7sx8jF$DMUVh=m{1_iIIB z6tahsm0pz+RgviA*Y5&_q4-73=X0|hc?P?W`_UAF_NxsXhW7W6tjTp8GFR~nN2Ih- zg2&gONe+HDv(;lC%;YY8Y`?_`oy|*nZe{I@a*xZ%%I?dJ_kn7e7yJ&zkfwOP>oGk7 zF5vTW@}wUU!!{Kvaa7pNK8@eURa?z{XJZtP_H>)=OTcLM(`}?nhMw0}n|Z!nKfOzj zp_AQPb`VAPYHfvf$!f>l>Tzu4o8w!t-`nwldJ2k^c4F?7PxHgC>~rzhIX$GjiRuWx zKxfGbIevGi^BBC3g5S()TZp-FoS+dfQ>ShNnpwIDC9zAGA9vR4VYPC z1lvzh{sL&bl}6+aw=S+GQ6G@Ikft8YVu?vfcN;I4c1u6EXD9izntk6^yXd?)2L*L22F47GA}T!tf7C-t6# z2y}fdq!*IH&3xG(ru}eu!tC2{(F=NnO<)0>}% z#wYch*6GwX-JiPe@6y|pKfFJ({CJdZo2Bf$ef(+%OkMPY*UcF||YbV~*j`O|V9 za>RY@4|$uruVuKXZo4*dUi$PHxZhK(?Al)_CcS+I`xA7V%_kNps=LoGxU)Y_D&Y$b z);&vnW+&}lmgye4ynaO~&l<2{S(BPBK44FI!t|Q3oQ(g>)Mr+;leh^+N`0RUD^04m zZMB&;LF0cNo%zOwj{@w9W3D?{vGQy8yjyqM^pX4UJL*HDQ4LmBq+n(uS@rofx4cyD zHdt&%HZmQTF15ap?{<}6$W$9ny;g2zaqfB@3~Qk=jXbuXwP~)nF9R_4+AN$ktox&^ zfIMTs#O)#3+P|c*XwXmUFbpual%em&q zmtVh0DdGS)TH5Cn3%vJxtMCW?S_EECi2P%C)XaCS(XssRlbTLz(TE+lN9VJHrYyu4 z(8J+BtPjri27rA}3j)wk!?2{|S5Y*Kz#!93L&GDK+-*27P|(gGXMn^J2_Wm^<0B~8 z^Wy@}UB7)j=3YHWSN2yAG*9m@354`^abS=yG>;FGfy6g~_6=1PmGLZYjzYyx;jqNu zq+DodXira1=5H!LfdRV4f}OZovK*~VcVE$~w^rc&_i`I`*Vm6^uv>a_z%&_tsM_c2 z?DTD_{E1@;C@vD4!XMjYq^M?UNIw2B~e?b5LUYx()aQNPUhC%kj z7XIIq0O1_S3k=qb5%B!y1{z3#ky2RKqvQVPqhSa1Y>raV(PgPBA4P0M`wP+K5c2*r zUy&&M+3Dtw;l&CZQX4~pq+~TTEIjYO0%2C_ej`zQ8U8mPEFb{N9^YX0Z-Qu4a>mIF zMPv{E1S|yhztxC|fOQJ)*Of+xf~rH+hlvCo#rkm|Ai@8dk7;0^hXeK@C38n@Y+@D;!x9W6Hx@q6eos=$dB^h{dau=4|2`h8X_Xvp?E@ zjK?MAKu_o@sX)Wd8;;*Mk}VP;mXe)>p2uSOZ6EHRuZdG3v`s`xPA`a-1yhbP^N9uw z*w@E)64OHI;4Ez|(GC~>1u3B3Y|;c{BEX^pY&-LIIt~=ps2pXWgsY}-&8X_CWwVi_ z=lS}HgXC5HkdTYf7=4C|^D12UZ6%xo4qZ4MRCNN28wA9f2?=)E0p)n2${h}1|CLC5 zhM0nmhMdb483s580+|{A=f@j}fdnwdjJo!pBS#M&mpL_#BZ2i#R%3r=MaD}4nkjw)IQ!b$fFYG8ROts`%QzK%+ z^MnkzmtjRv0*W6Yd_6?>=XzUzR+&K^yywyv*znF*skF8}K?y=d!^Y&<@UG6M&wX85 zU@=eoHOv@h!~2$%613}Dp;4otMD(YyZ`cfml^h~boi-dG5ik_Vr0}&QgQ~o}o(n3Q zovMghtU4;R?~72I{U-s(IKQLdrY*Nh;dCcXG6b6(7*7;gtnaRR?s*CyRB$ooI9{eP zPG1A!GaY0}MG&F>(Ro|I9~7F=NB{q^}TjX;S7_JHjJM;Hi%G8or)f1iKcA|>)i zM{lFGEa*v1NMKP0{tLj&+*S$&+RGrz5vBdb5QM2Qg!x9!Pn@X1cqv&6i{r607H#^t zOhPlF)Xxy0A=2m&Joq)uYAo2pR8&-Hyk7UlS12?8vKe3yc>)4es)!6+KfMW2>tJX8 zE#(IcNN0SDAKxPVCtcLP(iNf?!2Y-NZ-V-N(5$+gxS)gb8XM7?<|Bi5{9RGvhmAEf zG~%~tAAg5{Q6taK&ua_CSYakc_4oHbK0Z1$Ld3^r`ER!3egJ2ur-_imcXoF4-3n?C z#eWR>{Q zCwyhC4(=^V^&j$I2h7UUgj-UCNAm<^&{bU_KrH+LV8UK-SjE<-zsm=Mf%;1;`==n6 zznBU@eFLRSbH||n3vu=Q<*N3(KHfhd1wrH_rmXK#`8U!Y78I6B;aC3$q6iX*#xNt+ ze_cZmFuo< z#enRyn3w~6d?Dx%0nkb*kD&|xV-Djn%wrO+tP@wlgpCB_e`p00f?gD93{?X8pYVA= zAOxFr1*-j>5r9O>_;?*KGc$8M8xhzdMg|2C1pZ@%fIcR~(I3?+Svyc+f`%4n%O1!` zDmIg&fW`Rp4;2t-zrw*LNUT?;C1gD`0G=1tfw`P%0RsQ61nCND3P+hfa>X3egCQ&l z7nzh|XA}wayF5_0J=rfoNBt)-wNxa8zumD3_0w^ z-E1;@ls*%3)*U<-3tb@&YI;>cOP-VN$25*-o1MK zEjNeX)BT5B;E)*~xqy;Lr-qo;p>*tX^K*evxbC9>Kgm~b_qI`hBjwjn`97AH9vVVM zE*OC?tX$W_e$-rmm=AM8sQ+XH2JQ!1+3UB!|0NCy1wc&*DCGQq%K86o?_Zr9xPwUI zjR@773>NdruLR3Hu(#O#X%&#Zyr4uhU}$Iv04Y}MIv*HuC>ke;M7ptjX=A$fB<$SB-^{X^-1d_Q@yg1zyCh#{hh7V7-`G$uqChWyBpdI&Q#0t3u`Z(x%ye81qFIgVOfM%=`q z0QrBaSRyxppS~f;o02oY81a@ImD#X`6-vxeXkAKz;D3Y%14jV%Sv(2Av_Js<5e_T> zAgABbiW}%JOik_s9R&H0`{5vE>>1@qUs74g%3JXRW&=AZ92`pZKl+2}Gp0KD4HiLB z@XXB2ydB(*6eUf~?@ohXQ%2EB3k7)oNAnG+Dai!gXjfLClAqol7!q)VY z#)Yn_>I2WJw3~5ZDAIYlHeC#^LGvhV;r0zCWm46Ickc?1O7_82k(U4Toa_H0&}ILX z!QSiWxeD_}5XM@?((5*etUf$1U@$1q#CGdm#K-~TD?&CcFitTDH!Bp z1*fMR)Y-eVzSZ-POgNQHs`~J8FqK`gjeBoVQ&}(p&}xw#Hy6>KI|^x;K6dnvs=*zY zA7*lNIMv>3Y}j6;w7R&9hO=|#w6vXiNz>9m;f9-ajwyrs6WWd?^Y~c8mtrK{K!kQr zFa!`djHVE!Xl(zdcJwzWza2aD(zKJ&&i`@8p|XmygOXp6ADf8#bUW5oRg__u<<_o- zd8U$d%KgX*p#L2srs^;@zhUEGc;I9L_QcNnP z$F_l!u~xme#2P(i!1S1j6PG7I`*)v1ow(67_Bnlk1hqXbk8;ho;W`x2^F~ct74l{j z)hG&#D~HS(t8rejjHDf^e(#h%KuhL%r)uC(0`YJz-8M!S|EsLkt#I>Lo8?8~C+72F z@^{ww#ss9nQ4h)_=320#CF=&~QyX^Vl*lKc0C^&eh)13oi_^14OdI+Db<(|f9Ubz{ z5ZPF1#z)C+CNnHhk{QH})JiBZ1}^h?K}AL6mG9U9SraXjnsTrEwPneoUSr0gK@q7v z#foXq)ei2X&Z1+yr8B*A>*r}3C(GEZ*l|EC+Bdp(Sw5}GQ=8N3%PqYo;&Inp_wlQX zjhKxHF~{X3rTpVnN>VPFB$;*^FHP)>F1i8w464n>`^QC@wuR?|SVT-ab>Nyu$KC)W z-nvfK%LI0PUEyI&Hp3;mk%K~n?cMfGdgu+^DZ)vOcBXZi-HLNtXRVaep0=B&Ah5Rb zHK!dLdo_x^%-b>$s-&G4&(6oj=P?@#79uLvsII?X)S_-;;a>P~4*q<7HKu`Lzhuyr zt)s4}saVOaEgv!xkbGj<&1smUMEQPDpU{pIRfLh=(2@F>Ts&Ff#r=IgY&1%A7TFjR zR!dKIAsze}8bUxH7bbUM!Lo7X{C@h@u>+{Iq0n^kXC-C-v z*dFJ)<;}O@=v%;dJTz+rXM>k>Y?$0{na#K3e#3Etjym9sehQcTW0T9=`(*J;GU}j( zlxIRp;Q&(!VH}U3ccRaFwrSR~uAZGnm!sri>-}0r@>(5XmYS~J@5oKPHJ4MFFKXlC z(if#XWWyuhz8_Skb`q*UwwHur4&IGvAoWuUI;a zkZ_>wk^PXs2uBSwJ6fKlZS$4I-6xiSv(W%l=!h$okeQrxF7B12wN*HfP0xs+jcNJ} z%pDW=U8q{BW>zRLP{lR)v1XJ0p>IHg>hKnW`#eLm)U|hB1Fp z02Qb(TEN=JXA-3#=0cBRHrO`xuD7RfAex6n z5T|ZvYm3LwGq8cHBe`J<)JP?#W(S(F?)X^+A3sutps8Z>i`(?j&Z1fu3Uu%k*7_iC&5 zBIDis)=}g2+eG{DxGLr@xgJBHR-h7c;z#%_$n9?%9x<-SVQW=)HD$qgly5Dg$sM~h z8bV126%+bf{uMQ3N3;po6+>`RVHyu}=Ae$qqKE-#-zGh5art_2jp6Gyzj9ckQz%d~ zTo=3r0r5|DbAWd{a_wo$3JF2HV9FsuA?~uBW_eSC8)5LVz4#Jn726qXiADm5F zQ**4}oT9nWPHMOSWTmABe0>kt4i3~L+bmD#;o&1z_8@oSXEZk@RPvsc`>md zc3H#_XW%r_tj1F*UW-DtaZ6HaV-yZ8406Di zIgqGVDuw)P^^oG0RcXoXSXxU$#n)GA)3aYcTXf!d9t}K7O6wICpCG6<=Uy}KG&Zo! zENJh@;_x#7-dm3+JL!Z;;&`g0@kgFN->BK#jh}P7Wvfk04)64#JNrsuK!JgL_duAR zotfJyh+kW`WfZ=x7{T9BuVI{+A60!XWF`)fpi<4}UuKX`8Mw$VBnJOJ7O0yd#yOry zD7IxepDPOPjW&TnBTC%6oY$Bj)P;N)o@9=l-I_48%WZuQjX^Jzb3;ZQFuT`ZB*YZ6 zJQWC0rcRaX*{E6{38XTvMb5PUhTRFD0@yRRmL^5IVQilVgp0y~7cFW<%aYQ)KB_Yv z*e_FMOaH!Ek56RSYOQR-PxwL>SNI6)fIM>Hyv-9Hcj(xX$*D)Rts%``m4qbOe+5UR z8Kh5PU#9Hu$-53sfeorx0H_d`3)?vCP;U1IV-n2|kq1Vgw*2#zF(bf$xShTYUX^I* z=+76mp*uUfV{OKsoegqMk>paTY~$7_leB6^7=_axZaEJjy^pWQXppE)dW@*h@D6zr zYu&kaX`d#{=OVKc1qo&$0C{%{QM}(VZ<@I=f&6pz2I$-@)3@#N`p)g*WELbxrmWtB z|y(uV72q%lrhBu-w3AhN*Ph4!Nxu@`t{+Ery%1Cr2c=CIa`=`PE|`U zamNiVg+>9>_Ft?o_R1`=QoiR2tasAV(i&9Sm1Q}gO_s?s7^K8D-OTYQbe@C5gy7#cZ&EvdOjj~wZVD7w&Oa^)!F$(a*un3Tb-ECa=cwCk;BOsCjy zDDBCXW6jMHluhw4y*npM2+e1%)2FiX;Xg@O1TaS?83{1OVcF(7ONt literal 0 HcmV?d00001 diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index dd315ed8a7..27dc82aa96 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -322,9 +322,12 @@ the ``ChoiceInline`` declaration to read:: With that ``TabularInline`` (instead of ``StackedInline``), the related objects are displayed in a more compact, table-based format: -.. image:: _images/admin12.png +.. image:: _images/admin12t.png :alt: Add poll page now has more compact choices +Note that there is an extra "Delete?" column that allows removing rows added +using the "Add Another Choice" button and rows that have already been saved. + Customize the admin change list =============================== From c68f4c514c7b1102772b6ea11e9e59c7c87f7fae Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Jul 2012 06:55:46 -0400 Subject: [PATCH 045/176] Fixed #18493 - Added instructions to locate the Django source files to the t Thanks Claude Paroz for the draft patch. --- docs/intro/tutorial02.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 27dc82aa96..16682c67c3 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -448,6 +448,19 @@ above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to ``/home/my_username/mytemplates/admin/base_site.html``. Don't forget that ``admin`` subdirectory. +.. admonition:: Where are the Django source files? + + If you have difficulty finding where the Django source files are located + on your system, run the following command: + + .. code-block:: bash + + python -c " + import sys + sys.path = sys.path[1:] + import django + print(django.__path__)" + Then, just edit the file and replace the generic Django text with your own site's name as you see fit. From 9974069620b0f0e6ac2f1e9bd64819ae0a0e623b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Jul 2012 07:25:24 -0400 Subject: [PATCH 046/176] Fixed #16882 - Clarified why one should not use 'init_command' after initial database creation. --- docs/ref/databases.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index ff7a349eb5..1f4d09f6cb 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -335,7 +335,9 @@ storage engine, you have a couple of options. } This sets the default storage engine upon connecting to the database. - After your tables have been created, you should remove this option. + After your tables have been created, you should remove this option as it + adds a query that is only needed during table creation to each database + connection. * Another method for changing the storage engine is described in AlterModelOnSyncDB_. From 2b9fb2e6443c04e4415b17083d727bd80047b6e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Fri, 11 May 2012 19:41:47 +0300 Subject: [PATCH 047/176] Fixed #18251 -- Removed a deadlock possibility in apploading Thanks to harm for the report and comments. --- django/db/models/loading.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/django/db/models/loading.py b/django/db/models/loading.py index cbf62602d2..d651584e7a 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -6,9 +6,9 @@ from django.utils.datastructures import SortedDict from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule +import imp import sys import os -import threading __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models', 'load_app', 'app_cache_ready') @@ -39,7 +39,6 @@ class AppCache(object): handled = {}, postponed = [], nesting_level = 0, - write_lock = threading.RLock(), _get_models_cache = {}, ) @@ -54,7 +53,14 @@ class AppCache(object): """ if self.loaded: return - with self.write_lock: + # Note that we want to use the import lock here - the app loading is + # in many cases initiated implicitly by importing, and thus it is + # possible to end up in deadlock when one thread initiates loading + # without holding the importer lock and another thread then tries to + # import something which also launches the app loading. For details of + # this situation see #18251. + imp.acquire_lock() + try: if self.loaded: return for app_name in settings.INSTALLED_APPS: @@ -65,6 +71,8 @@ class AppCache(object): for app_name in self.postponed: self.load_app(app_name) self.loaded = True + finally: + imp.release_lock() def _label_for(self, app_mod): """ @@ -135,7 +143,8 @@ class AppCache(object): the app has no models in it and 'emptyOK' is True, returns None. """ self._populate() - with self.write_lock: + imp.acquire_lock() + try: for app_name in settings.INSTALLED_APPS: if app_label == app_name.split('.')[-1]: mod = self.load_app(app_name, False) @@ -146,6 +155,8 @@ class AppCache(object): else: return mod raise ImproperlyConfigured("App with label %s could not be found" % app_label) + finally: + imp.release_lock() def get_app_errors(self): "Returns the map of known problems with the INSTALLED_APPS." From bd283aa844b04651b7c8b4e85f48c6dced1678f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Sat, 26 May 2012 05:55:33 +0300 Subject: [PATCH 048/176] Refactored the empty/full result logic in WhereNode.as_sql() Made sure the WhereNode.as_sql() handles various EmptyResultSet and FullResultSet conditions correctly. Also, got rid of the FullResultSet exception class. It is now represented by '', [] return value in the as_sql() methods. --- django/db/models/sql/datastructures.py | 3 - django/db/models/sql/where.py | 85 ++++++++++++++----------- tests/regressiontests/queries/tests.py | 86 ++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 39 deletions(-) diff --git a/django/db/models/sql/datastructures.py b/django/db/models/sql/datastructures.py index 92d64e15dd..b8e06daf01 100644 --- a/django/db/models/sql/datastructures.py +++ b/django/db/models/sql/datastructures.py @@ -6,9 +6,6 @@ the SQL domain. class EmptyResultSet(Exception): pass -class FullResultSet(Exception): - pass - class MultiJoin(Exception): """ Used by join construction code to indicate the point at which a diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 5515bc4f83..70ff5310f7 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -10,7 +10,7 @@ from itertools import repeat from django.utils import tree from django.db.models.fields import Field -from django.db.models.sql.datastructures import EmptyResultSet, FullResultSet +from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.aggregates import Aggregate # Connection types @@ -75,17 +75,21 @@ class WhereNode(tree.Node): def as_sql(self, qn, connection): """ Returns the SQL version of the where clause and the value to be - substituted in. Returns None, None if this node is empty. - - If 'node' is provided, that is the root of the SQL generation - (generally not needed except by the internal implementation for - recursion). + substituted in. Returns '', [] if this node matches everything, + None, [] if this node is empty, and raises EmptyResultSet if this + node can't match anything. """ - if not self.children: - return None, [] + # Note that the logic here is made slightly more complex than + # necessary because there are two kind of empty nodes: Nodes + # containing 0 children, and nodes that are known to match everything. + # A match-everything node is different than empty node (which also + # technically matches everything) for backwards compatibility reasons. + # Refs #5261. result = [] result_params = [] - empty = True + everything_childs, nothing_childs = 0, 0 + non_empty_childs = len(self.children) + for child in self.children: try: if hasattr(child, 'as_sql'): @@ -93,39 +97,48 @@ class WhereNode(tree.Node): else: # A leaf node in the tree. sql, params = self.make_atom(child, qn, connection) - except EmptyResultSet: - if self.connector == AND and not self.negated: - # We can bail out early in this particular case (only). - raise - elif self.negated: - empty = False - continue - except FullResultSet: - if self.connector == OR: - if self.negated: - empty = True - break - # We match everything. No need for any constraints. - return '', [] + nothing_childs += 1 + else: + if sql: + result.append(sql) + result_params.extend(params) + else: + if sql is None: + # Skip empty childs totally. + non_empty_childs -= 1 + continue + everything_childs += 1 + # Check if this node matches nothing or everything. + # First check the amount of full nodes and empty nodes + # to make this node empty/full. + if self.connector == AND: + full_needed, empty_needed = non_empty_childs, 1 + else: + full_needed, empty_needed = 1, non_empty_childs + # Now, check if this node is full/empty using the + # counts. + if empty_needed - nothing_childs <= 0: if self.negated: - empty = True - continue - - empty = False - if sql: - result.append(sql) - result_params.extend(params) - if empty: - raise EmptyResultSet + return '', [] + else: + raise EmptyResultSet + if full_needed - everything_childs <= 0: + if self.negated: + raise EmptyResultSet + else: + return '', [] + if non_empty_childs == 0: + # All the child nodes were empty, so this one is empty, too. + return None, [] conn = ' %s ' % self.connector sql_string = conn.join(result) if sql_string: - if self.negated: - sql_string = 'NOT (%s)' % sql_string - elif len(self.children) != 1: + if len(result) > 1: sql_string = '(%s)' % sql_string + if self.negated: + sql_string = 'NOT %s' % sql_string return sql_string, result_params def make_atom(self, child, qn, connection): @@ -261,7 +274,7 @@ class EverythingNode(object): """ def as_sql(self, qn=None, connection=None): - raise FullResultSet + return '', [] def relabel_aliases(self, change_map, node=None): return diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 4cc7208a96..3d2aafd2d9 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -10,6 +10,8 @@ from django.core.exceptions import FieldError from django.db import DatabaseError, connection, connections, DEFAULT_DB_ALIAS from django.db.models import Count from django.db.models.query import Q, ITER_CHUNK_SIZE, EmptyQuerySet +from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode +from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature from django.test.utils import str_prefix from django.utils import unittest @@ -1316,10 +1318,23 @@ class Queries5Tests(TestCase): ) def test_ticket5261(self): + # Test different empty excludes. self.assertQuerysetEqual( Note.objects.exclude(Q()), ['', ''] ) + self.assertQuerysetEqual( + Note.objects.filter(~Q()), + ['', ''] + ) + self.assertQuerysetEqual( + Note.objects.filter(~Q()|~Q()), + ['', ''] + ) + self.assertQuerysetEqual( + Note.objects.exclude(~Q()&~Q()), + ['', ''] + ) class SelectRelatedTests(TestCase): @@ -2020,3 +2035,74 @@ class ProxyQueryCleanupTest(TestCase): self.assertEqual(qs.count(), 1) str(qs.query) self.assertEqual(qs.count(), 1) + +class WhereNodeTest(TestCase): + class DummyNode(object): + def as_sql(self, qn, connection): + return 'dummy', [] + + def test_empty_full_handling_conjunction(self): + qn = connection.ops.quote_name + w = WhereNode(children=[EverythingNode()]) + self.assertEquals(w.as_sql(qn, connection), ('', [])) + w.negate() + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w = WhereNode(children=[NothingNode()]) + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w.negate() + self.assertEquals(w.as_sql(qn, connection), ('', [])) + w = WhereNode(children=[EverythingNode(), EverythingNode()]) + self.assertEquals(w.as_sql(qn, connection), ('', [])) + w.negate() + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w = WhereNode(children=[EverythingNode(), self.DummyNode()]) + self.assertEquals(w.as_sql(qn, connection), ('dummy', [])) + w = WhereNode(children=[self.DummyNode(), self.DummyNode()]) + self.assertEquals(w.as_sql(qn, connection), ('(dummy AND dummy)', [])) + w.negate() + self.assertEquals(w.as_sql(qn, connection), ('NOT (dummy AND dummy)', [])) + w = WhereNode(children=[NothingNode(), self.DummyNode()]) + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w.negate() + self.assertEquals(w.as_sql(qn, connection), ('', [])) + + def test_empty_full_handling_disjunction(self): + qn = connection.ops.quote_name + w = WhereNode(children=[EverythingNode()], connector='OR') + self.assertEquals(w.as_sql(qn, connection), ('', [])) + w.negate() + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w = WhereNode(children=[NothingNode()], connector='OR') + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w.negate() + self.assertEquals(w.as_sql(qn, connection), ('', [])) + w = WhereNode(children=[EverythingNode(), EverythingNode()], connector='OR') + self.assertEquals(w.as_sql(qn, connection), ('', [])) + w.negate() + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w = WhereNode(children=[EverythingNode(), self.DummyNode()], connector='OR') + self.assertEquals(w.as_sql(qn, connection), ('', [])) + w.negate() + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) + w = WhereNode(children=[self.DummyNode(), self.DummyNode()], connector='OR') + self.assertEquals(w.as_sql(qn, connection), ('(dummy OR dummy)', [])) + w.negate() + self.assertEquals(w.as_sql(qn, connection), ('NOT (dummy OR dummy)', [])) + w = WhereNode(children=[NothingNode(), self.DummyNode()], connector='OR') + self.assertEquals(w.as_sql(qn, connection), ('dummy', [])) + w.negate() + self.assertEquals(w.as_sql(qn, connection), ('NOT dummy', [])) + + def test_empty_nodes(self): + qn = connection.ops.quote_name + empty_w = WhereNode() + w = WhereNode(children=[empty_w, empty_w]) + self.assertEquals(w.as_sql(qn, connection), (None, [])) + w.negate() + self.assertEquals(w.as_sql(qn, connection), (None, [])) + w.connector = 'OR' + self.assertEquals(w.as_sql(qn, connection), (None, [])) + w.negate() + self.assertEquals(w.as_sql(qn, connection), (None, [])) + w = WhereNode(children=[empty_w, NothingNode()], connector='OR') + self.assertRaises(EmptyResultSet, w.as_sql, qn, connection) From da573fbb4172fb962c9f021fc0c03cf91b13e746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Sun, 27 May 2012 02:24:57 +0300 Subject: [PATCH 049/176] Fixed some locations to work with autocommit=True - backends: supports_transactions() - select_for_update tests --- django/db/backends/__init__.py | 27 ++++++++++++++------- tests/modeltests/select_for_update/tests.py | 5 +++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index d70fe54bdb..dab9b7e213 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -417,15 +417,24 @@ class BaseDatabaseFeatures(object): @cached_property def supports_transactions(self): "Confirm support for transactions" - cursor = self.connection.cursor() - cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)') - self.connection._commit() - cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)') - self.connection._rollback() - cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST') - count, = cursor.fetchone() - cursor.execute('DROP TABLE ROLLBACK_TEST') - self.connection._commit() + try: + # Make sure to run inside a managed transaction block, + # otherwise autocommit will cause the confimation to + # fail. + self.connection.enter_transaction_management() + self.connection.managed(True) + cursor = self.connection.cursor() + cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)') + self.connection._commit() + cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)') + self.connection._rollback() + cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST') + count, = cursor.fetchone() + cursor.execute('DROP TABLE ROLLBACK_TEST') + self.connection._commit() + self.connection._dirty = False + finally: + self.connection.leave_transaction_management() return count == 0 @cached_property diff --git a/tests/modeltests/select_for_update/tests.py b/tests/modeltests/select_for_update/tests.py index 243f6b50e7..cb8f19f1e8 100644 --- a/tests/modeltests/select_for_update/tests.py +++ b/tests/modeltests/select_for_update/tests.py @@ -36,6 +36,8 @@ class SelectForUpdateTests(TransactionTestCase): # issuing a SELECT ... FOR UPDATE will block. new_connections = ConnectionHandler(settings.DATABASES) self.new_connection = new_connections[DEFAULT_DB_ALIAS] + self.new_connection.enter_transaction_management() + self.new_connection.managed(True) # We need to set settings.DEBUG to True so we can capture # the output SQL to examine. @@ -48,6 +50,7 @@ class SelectForUpdateTests(TransactionTestCase): # this in the course of their run. transaction.managed(False) transaction.leave_transaction_management() + self.new_connection.leave_transaction_management() except transaction.TransactionManagementError: pass self.new_connection.close() @@ -66,7 +69,7 @@ class SelectForUpdateTests(TransactionTestCase): 'for_update': self.new_connection.ops.for_update_sql(), } self.cursor.execute(sql, ()) - result = self.cursor.fetchone() + self.cursor.fetchone() def end_blocking_transaction(self): # Roll back the blocking transaction. From f572ee0c65ec5eac9edb0cb3e35c96ec86d89ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Sat, 26 May 2012 23:19:13 +0300 Subject: [PATCH 050/176] Fixed #16047 -- Restore autocommit state correctly on psycopg2 When the postgresql_psycopg2 backend was used with DB-level autocommit mode enabled, after entering transaction management and then leaving it, the isolation level was never set back to autocommit mode. Thanks brodie for report and working on this issue. --- django/db/backends/__init__.py | 14 +++-- docs/releases/1.5.txt | 9 +++ .../transactions_regress/tests.py | 60 ++++++++++++++++++- 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index dab9b7e213..05ef7bf62a 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -109,16 +109,18 @@ class BaseDatabaseWrapper(object): over to the surrounding block, as a commit will commit all changes, even those from outside. (Commits are on connection level.) """ - self._leave_transaction_management(self.is_managed()) if self.transaction_state: del self.transaction_state[-1] else: - raise TransactionManagementError("This code isn't under transaction " - "management") + raise TransactionManagementError( + "This code isn't under transaction management") + # We will pass the next status (after leaving the previous state + # behind) to subclass hook. + self._leave_transaction_management(self.is_managed()) if self._dirty: self.rollback() - raise TransactionManagementError("Transaction managed block ended with " - "pending COMMIT/ROLLBACK") + raise TransactionManagementError( + "Transaction managed block ended with pending COMMIT/ROLLBACK") self._dirty = False def validate_thread_sharing(self): @@ -176,6 +178,8 @@ class BaseDatabaseWrapper(object): """ if self.transaction_state: return self.transaction_state[-1] + # Note that this setting isn't documented, and is only used here, and + # in enter_transaction_management() return settings.TRANSACTIONS_MANAGED def managed(self, flag=True): diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 2f20f5f9f9..4544be0eac 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -168,6 +168,15 @@ number was inside the existing page range. It does check it now and raises an :exc:`InvalidPage` exception when the number is either too low or too high. +Behavior of autocommit database option on PostgreSQL changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PostgreSQL's autocommit option didn't work as advertised previously. It did +work for single transaction block, but after the first block was left the +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. + Features deprecated in 1.5 ========================== diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/regressiontests/transactions_regress/tests.py index abd7a4ceaa..fb26138eed 100644 --- a/tests/regressiontests/transactions_regress/tests.py +++ b/tests/regressiontests/transactions_regress/tests.py @@ -1,11 +1,11 @@ from __future__ import absolute_import from django.core.exceptions import ImproperlyConfigured -from django.db import connection, transaction +from django.db import connection, connections, transaction, DEFAULT_DB_ALIAS from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError from django.test import TransactionTestCase, skipUnlessDBFeature from django.test.utils import override_settings -from django.utils.unittest import skipIf +from django.utils.unittest import skipIf, skipUnless from .models import Mod, M2mA, M2mB @@ -175,6 +175,62 @@ class TestTransactionClosing(TransactionTestCase): self.test_failing_query_transaction_closed() +class TestPostgresAutocommit(TransactionTestCase): + """ + Tests to make sure psycopg2's autocommit mode is restored after entering + and leaving transaction management. Refs #16047. + """ + def setUp(self): + from psycopg2.extensions import (ISOLATION_LEVEL_AUTOCOMMIT, + ISOLATION_LEVEL_READ_COMMITTED) + self._autocommit = ISOLATION_LEVEL_AUTOCOMMIT + self._read_committed = ISOLATION_LEVEL_READ_COMMITTED + + # We want a clean backend with autocommit = True, so + # first we need to do a bit of work to have that. + self._old_backend = connections[DEFAULT_DB_ALIAS] + settings = self._old_backend.settings_dict.copy() + opts = settings['OPTIONS'].copy() + opts['autocommit'] = True + settings['OPTIONS'] = opts + new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS) + connections[DEFAULT_DB_ALIAS] = new_backend + + def test_initial_autocommit_state(self): + self.assertTrue(connection.features.uses_autocommit) + self.assertEqual(connection.isolation_level, self._autocommit) + + def test_transaction_management(self): + transaction.enter_transaction_management() + transaction.managed(True) + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.leave_transaction_management() + self.assertEqual(connection.isolation_level, self._autocommit) + + def test_transaction_stacking(self): + transaction.enter_transaction_management() + transaction.managed(True) + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.enter_transaction_management() + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.leave_transaction_management() + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.leave_transaction_management() + self.assertEqual(connection.isolation_level, self._autocommit) + + def tearDown(self): + connections[DEFAULT_DB_ALIAS] = self._old_backend + +TestPostgresAutocommit = skipUnless(connection.vendor == 'postgresql', + "This test only valid for PostgreSQL")(TestPostgresAutocommit) +TestPostgresAutoCommit = skipUnlessDBFeature('supports_transactions')( + TestPostgresAutocommit) + + class TestManyToManyAddTransaction(TransactionTestCase): def test_manyrelated_add_commit(self): "Test for https://code.djangoproject.com/ticket/16818" From e74787391e8fdf87d9a92138028911746809bd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Sun, 1 Jul 2012 22:49:38 +0300 Subject: [PATCH 051/176] Fixed a regression introduced in where.as_sql() refactor At least Oracle needs parentheses in negated where conditions, even if there is only single condition negated. Fixed this by reverting to old logic in that part of as_sql() and adding a comment about this. I did not investigate why the parentheses are needed. The original offending commit was bd283aa844b04651b7c8b4e85f48c6dced1678f0. --- django/db/models/sql/where.py | 9 ++++++--- tests/regressiontests/queries/tests.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 70ff5310f7..827046553d 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -135,10 +135,13 @@ class WhereNode(tree.Node): conn = ' %s ' % self.connector sql_string = conn.join(result) if sql_string: - if len(result) > 1: - sql_string = '(%s)' % sql_string if self.negated: - sql_string = 'NOT %s' % sql_string + # Note that some backends (Oracle at least) need the + # parentheses even around single experssion in the + # negated case. + sql_string = 'NOT (%s)' % sql_string + elif len(result) > 1: + sql_string = '(%s)' % sql_string return sql_string, result_params def make_atom(self, child, qn, connection): diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 3d2aafd2d9..9bb3a29430 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -2091,7 +2091,7 @@ class WhereNodeTest(TestCase): w = WhereNode(children=[NothingNode(), self.DummyNode()], connector='OR') self.assertEquals(w.as_sql(qn, connection), ('dummy', [])) w.negate() - self.assertEquals(w.as_sql(qn, connection), ('NOT dummy', [])) + self.assertEquals(w.as_sql(qn, connection), ('NOT (dummy)', [])) def test_empty_nodes(self): qn = connection.ops.quote_name From 7313468f85c3ade1275fc7fef193a4429f4af4bf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Jul 2012 18:04:16 -0400 Subject: [PATCH 052/176] Fixed #17436 - Added warning about overriding Model.__init__() Thanks zsiciarz for the draft patch. --- docs/ref/models/instances.txt | 36 ++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 3ae994bc5b..509ea9d30e 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -25,6 +25,41 @@ The keyword arguments are simply the names of the fields you've defined on your model. Note that instantiating a model in no way touches your database; for that, you need to :meth:`~Model.save()`. +.. note:: + + You may be tempted to customize the model by overriding the ``__init__`` + method. If you do so, however, take care not to change the calling + signature as any change may prevent the model instance from being saved. + Rather than overriding ``__init__``, try using one of these approaches: + + 1. Add a classmethod on the model class:: + + class Book(models.Model): + title = models.CharField(max_length=100) + + @classmethod + def create(cls, title): + book = cls(title=title) + # do something with the book + return book + + book = Book.create("Pride and Prejudice") + + 2. Add a method on a custom manager (usually preferred):: + + class BookManager(models.Manager): + def create_book(title): + book = self.create(title=title) + # do something with the book + return book + + class Book(models.Model): + title = models.CharField(max_length=100) + + objects = BookManager() + + book = Book.objects.create_book("Pride and Prejudice") + .. _validating-objects: Validating objects @@ -604,4 +639,3 @@ described in :ref:`Field lookups `. Note that in the case of identical date values, these methods will use the primary key as a tie-breaker. This guarantees that no records are skipped or duplicated. That also means you cannot use those methods on unsaved objects. - From 2cd4cf58d32c5d4b163a466f608dff31bac66b87 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sun, 1 Jul 2012 18:40:50 -0700 Subject: [PATCH 053/176] Fixed #18550 -- Ensured that the admin history view works with escaped primary keys. Thanks to josh.oosterman for the report and patch. --- django/contrib/admin/options.py | 2 +- tests/regressiontests/admin_views/tests.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3b899e552a..665e3f76b8 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1321,7 +1321,7 @@ class ModelAdmin(BaseModelAdmin): opts = model._meta app_label = opts.app_label action_list = LogEntry.objects.filter( - object_id = object_id, + object_id = unquote(object_id), content_type__id__exact = ContentType.objects.get_for_model(model).id ).select_related().order_by('action_time') # If no history was found, see whether this object even exists. diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index f074d77b11..937b086d40 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -1344,15 +1344,20 @@ class AdminViewStringPrimaryKeyTest(TestCase): def setUp(self): self.client.login(username='super', password='secret') content_type_pk = ContentType.objects.get_for_model(ModelWithStringPrimaryKey).pk - LogEntry.objects.log_action(100, content_type_pk, self.pk, self.pk, 2, change_message='') + LogEntry.objects.log_action(100, content_type_pk, self.pk, self.pk, 2, change_message='Changed something') def tearDown(self): self.client.logout() def test_get_history_view(self): - "Retrieving the history for the object using urlencoded form of primary key should work" + """ + Retrieving the history for an object using urlencoded form of primary + key should work. + Refs #12349, #18550. + """ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/history/' % quote(self.pk)) self.assertContains(response, escape(self.pk)) + self.assertContains(response, 'Changed something') self.assertEqual(response.status_code, 200) def test_get_change_view(self): From 784d0c261c76535dc760bc8d76793d92f35c1513 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 2 Jul 2012 10:16:42 +0200 Subject: [PATCH 054/176] Replaced 'return' by 'raise' in custom model field docs Thanks Simon Charette for noticing it. Refs #11162. --- docs/howto/custom-model-fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 8b4c2303ab..706cc25129 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -440,7 +440,7 @@ database, so we need to be able to process strings and ``Hand`` instances in Notice that we always return a ``Hand`` instance from this method. That's the Python object type we want to store in the model's attribute. If anything is -going wrong during value conversion, you should return a +going wrong during value conversion, you should raise a :exc:`~django.core.exceptions.ValidationError` exception. **Remember:** If your custom field needs the :meth:`to_python` method to be From 925a6936b94c74b2e67d90da1fcf2e19efc335cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Mon, 2 Jul 2012 15:35:59 +0300 Subject: [PATCH 055/176] Stylistic cleanup of Postgres autocommit tests Cleaned up tests introduced in f572ee0c65ec5eac9edb0cb3e35c96ec86d89ffb. Thanks to Claude Paroz for suggesting the changes. --- tests/regressiontests/transactions_regress/tests.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/regressiontests/transactions_regress/tests.py index fb26138eed..90b3df03d4 100644 --- a/tests/regressiontests/transactions_regress/tests.py +++ b/tests/regressiontests/transactions_regress/tests.py @@ -175,6 +175,8 @@ class TestTransactionClosing(TransactionTestCase): self.test_failing_query_transaction_closed() +@skipUnless(connection.vendor == 'postgresql', + "This test only valid for PostgreSQL") class TestPostgresAutocommit(TransactionTestCase): """ Tests to make sure psycopg2's autocommit mode is restored after entering @@ -196,6 +198,9 @@ class TestPostgresAutocommit(TransactionTestCase): new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS) connections[DEFAULT_DB_ALIAS] = new_backend + def tearDown(self): + connections[DEFAULT_DB_ALIAS] = self._old_backend + def test_initial_autocommit_state(self): self.assertTrue(connection.features.uses_autocommit) self.assertEqual(connection.isolation_level, self._autocommit) @@ -222,14 +227,6 @@ class TestPostgresAutocommit(TransactionTestCase): transaction.leave_transaction_management() self.assertEqual(connection.isolation_level, self._autocommit) - def tearDown(self): - connections[DEFAULT_DB_ALIAS] = self._old_backend - -TestPostgresAutocommit = skipUnless(connection.vendor == 'postgresql', - "This test only valid for PostgreSQL")(TestPostgresAutocommit) -TestPostgresAutoCommit = skipUnlessDBFeature('supports_transactions')( - TestPostgresAutocommit) - class TestManyToManyAddTransaction(TransactionTestCase): def test_manyrelated_add_commit(self): From ab7f0710584a3cbb3737e9a7fd8e3fd21765e594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Tue, 3 Jul 2012 10:29:10 +0300 Subject: [PATCH 056/176] Fixed comment wording in sql/where.py Thanks to Simon Charette for noticing this. --- django/db/models/sql/where.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 827046553d..2602c3066c 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -136,9 +136,9 @@ class WhereNode(tree.Node): sql_string = conn.join(result) if sql_string: if self.negated: - # Note that some backends (Oracle at least) need the - # parentheses even around single experssion in the - # negated case. + # Some backends (Oracle at least) need parentheses + # around the inner SQL in the negated case, even if the + # inner SQL contains just a single expression. sql_string = 'NOT (%s)' % sql_string elif len(result) > 1: sql_string = '(%s)' % sql_string From cf731a543eaae43f989f237727411763c386af1b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 3 Jul 2012 11:56:41 +0200 Subject: [PATCH 057/176] Fixed widget parent class in generic_relations test --- tests/modeltests/generic_relations/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modeltests/generic_relations/tests.py b/tests/modeltests/generic_relations/tests.py index d3de71d917..14871e4e09 100644 --- a/tests/modeltests/generic_relations/tests.py +++ b/tests/modeltests/generic_relations/tests.py @@ -231,7 +231,7 @@ class GenericRelationsTests(TestCase): tag = TaggedItem.objects.create(content_object=tailless, tag="lizard") self.assertEqual(tag.content_object, tailless) -class CustomWidget(forms.CharField): +class CustomWidget(forms.TextInput): pass class TaggedItemForm(forms.ModelForm): From f33e15036907d6e4bda6116dc84097e9e590d2c8 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sat, 30 Jun 2012 16:41:51 +0100 Subject: [PATCH 058/176] Documented utils.html.escape and conditional_escape --- django/utils/html.py | 14 +++++++------- docs/ref/utils.txt | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/django/utils/html.py b/django/utils/html.py index 014d837bbb..fe2e6b7a29 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -31,11 +31,11 @@ hard_coded_bullets_re = re.compile(r'((?:

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

\s*)+)' % '| trailing_empty_content_re = re.compile(r'(?:

(?: |\s|
)*?

\s*)+\Z') del x # Temporary variable -def escape(html): +def escape(text): """ - Returns the given HTML with ampersands, quotes and angle brackets encoded. + Returns the given text with ampersands, quotes and angle brackets encoded for use in HTML. """ - return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) + return mark_safe(force_unicode(text).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) escape = allow_lazy(escape, unicode) _base_js_escapes = ( @@ -63,14 +63,14 @@ def escapejs(value): return value escapejs = allow_lazy(escapejs, unicode) -def conditional_escape(html): +def conditional_escape(text): """ Similar to escape(), except that it doesn't operate on pre-escaped strings. """ - if isinstance(html, SafeData): - return html + if isinstance(text, SafeData): + return text else: - return escape(html) + return escape(text) def linebreaks(value, autoescape=False): """Converts newlines into

and
s.""" diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 0974409453..549812296b 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -387,6 +387,28 @@ Atom1Feed input is a proper string, then add support for lazy translation objects at the end. +``django.utils.html`` +===================== + +.. module:: django.utils.html + :synopsis: HTML helper functions + +Usually you should build up HTML using Django's templates to make use of its +autoescape mechanism, using the utilities in :mod:`django.utils.safestring` +where appropriate. This module provides some additional low level utilitiesfor +escaping HTML. + +.. function:: escape(text) + + Returns the given text with ampersands, quotes and angle brackets encoded + for use in HTML. The input is first passed through + :func:`~django.utils.encoding.force_unicode` and the output has + :func:`~django.utils.safestring.mark_safe` applied. + +.. function:: conditional_escape(text) + + Similar to ``escape()``, except that it doesn't operate on pre-escaped strings, + so it will not double escape. ``django.utils.http`` ===================== From bee498f3a2f66210db39f0be244ec4fa888b6940 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sat, 30 Jun 2012 18:54:38 +0100 Subject: [PATCH 059/176] Added 'format_html' utility for formatting HTML fragments safely --- django/utils/html.py | 31 +++++++++++++++++++++++ docs/ref/utils.txt | 39 +++++++++++++++++++++++++++++ tests/regressiontests/utils/html.py | 11 ++++++++ 3 files changed, 81 insertions(+) diff --git a/django/utils/html.py b/django/utils/html.py index fe2e6b7a29..390c45dcec 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -72,6 +72,37 @@ def conditional_escape(text): else: return escape(text) +def format_html(format_string, *args, **kwargs): + """ + Similar to str.format, but passes all arguments through conditional_escape, + and calls 'mark_safe' on the result. This function should be used instead + of str.format or % interpolation to build up small HTML fragments. + """ + args_safe = map(conditional_escape, args) + kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in + kwargs.iteritems()]) + return mark_safe(format_string.format(*args_safe, **kwargs_safe)) + +def format_html_join(sep, format_string, args_generator): + """ + A wrapper format_html, for the common case of a group of arguments that need + to be formatted using the same format string, and then joined using + 'sep'. 'sep' is also passed through conditional_escape. + + 'args_generator' should be an iterator that returns the sequence of 'args' + that will be passed to format_html. + + Example: + + format_html_join('\n', "

  • {0} {1}
  • ", ((u.first_name, u.last_name) + for u in users)) + + """ + return mark_safe(conditional_escape(sep).join( + format_html(format_string, *tuple(args)) + for args in args_generator)) + + def linebreaks(value, autoescape=False): """Converts newlines into

    and
    s.""" value = normalize_newlines(value) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 549812296b..c74392df36 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -410,6 +410,45 @@ escaping HTML. Similar to ``escape()``, except that it doesn't operate on pre-escaped strings, so it will not double escape. +.. function:: format_html(format_string, *args, **kwargs) + + This is similar to `str.format`_, except that it is appropriate for + building up HTML fragments. All args and kwargs are passed through + :func:`conditional_escape` before being passed to ``str.format``. + + For the case of building up small HTML fragments, this function is to be + preferred over string interpolation using ``%`` or ``str.format`` directly, + because it applies escaping to all arguments - just like the Template system + applies escaping by default. + + So, instead of writing: + + .. code-block:: python + + mark_safe(u"%s %s %s" % (some_html, + escape(some_text), + escape(some_other_text), + )) + + you should instead use: + + .. code-block:: python + + format_html(u"%{0} {1} {2}", + mark_safe(some_html), some_text, some_other_text) + + This has the advantage that you don't need to apply :func:`escape` to each + argument and risk a bug and an XSS vulnerability if you forget one. + + Note that although this function uses ``str.format`` to do the + interpolation, some of the formatting options provided by `str.format`_ + (e.g. number formatting) will not work, since all arguments are passed + through :func:`conditional_escape` which (ultimately) calls + :func:`~django.utils.encoding.force_unicode` on the values. + + +.. _str.format: http://docs.python.org/library/stdtypes.html#str.format + ``django.utils.http`` ===================== diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py index 434873b9e0..389ae8ec75 100644 --- a/tests/regressiontests/utils/html.py +++ b/tests/regressiontests/utils/html.py @@ -34,6 +34,17 @@ class TestUtilsHtml(unittest.TestCase): # Verify it doesn't double replace &. self.check_output(f, '<&', '<&') + def test_format_html(self): + self.assertEqual( + html.format_html(u"{0} {1} {third} {fourth}", + u"< Dangerous >", + html.mark_safe(u"safe"), + third="< dangerous again", + fourth=html.mark_safe(u"safe again") + ), + u"< Dangerous > safe < dangerous again safe again" + ) + def test_linebreaks(self): f = html.linebreaks items = ( From a92e7f37c4ae84b6b8d8016cc6783211e9047219 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 3 Jul 2012 00:31:14 +0100 Subject: [PATCH 060/176] Changed a lot of internal code to use 'format_html' where appropriate/possible --- django/contrib/admin/helpers.py | 10 ++-- .../contrib/admin/templatetags/admin_list.py | 42 +++++++------ django/contrib/admin/util.py | 11 ++-- django/contrib/admin/widgets.py | 15 ++--- django/contrib/auth/forms.py | 13 ++-- .../contrib/databrowse/plugins/calendars.py | 6 +- .../databrowse/plugins/fieldchoices.py | 6 +- django/contrib/gis/maps/google/gmap.py | 14 +++-- django/forms/forms.py | 12 ++-- django/forms/util.py | 21 ++++--- django/forms/widgets.py | 59 ++++++++++--------- django/template/defaulttags.py | 5 +- 12 files changed, 121 insertions(+), 93 deletions(-) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 6c648ecb4a..ac29d19469 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -10,7 +10,7 @@ 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.html import escape, conditional_escape +from django.utils.html import conditional_escape, format_html from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -163,11 +163,9 @@ class AdminReadonlyField(object): if not self.is_first: attrs["class"] = "inline" label = self.field['label'] - contents = capfirst(force_unicode(escape(label))) + ":" - return mark_safe('%(contents)s' % { - "attrs": flatatt(attrs), - "contents": contents, - }) + return format_html('{1}:', + flatatt(attrs), + capfirst(force_unicode(label))) def contents(self): from django.contrib.admin.templatetags.admin_list import _boolean_icon diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 30a85ab7f7..29b5d71a16 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.utils import formats -from django.utils.html import escape, conditional_escape +from django.utils.html import format_html from django.utils.safestring import mark_safe from django.utils.text import capfirst from django.utils.translation import ugettext as _ @@ -31,9 +31,12 @@ def paginator_number(cl,i): if i == DOT: return '... ' elif i == cl.page_num: - return mark_safe('%d ' % (i+1)) + return format_html('{} ', i+1) else: - return mark_safe('%d ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) + return format_html('{2} ', + cl.get_query_string({PAGE_VAR: i}), + mark_safe(' class="end"' if i == cl.paginator.num_pages-1 else ''), + i+1) @register.inclusion_tag('admin/pagination.html') def pagination(cl): @@ -159,13 +162,14 @@ 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": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '') + "class_attrib": format_html(' class="{}"', ' '.join(th_classes)) + if th_classes else '', } def _boolean_icon(field_val): icon_url = static('admin/img/icon-%s.gif' % {True: 'yes', False: 'no', None: 'unknown'}[field_val]) - return mark_safe('%s' % (icon_url, field_val)) + return format_html('{1}', icon_url, field_val) def items_for_result(cl, result, form): """ @@ -182,7 +186,7 @@ def items_for_result(cl, result, form): else: if f is None: if field_name == 'action_checkbox': - row_class = ' class="action-checkbox"' + row_class = mark_safe(' class="action-checkbox"') allow_tags = getattr(attr, 'allow_tags', False) boolean = getattr(attr, 'boolean', False) if boolean: @@ -190,23 +194,21 @@ def items_for_result(cl, result, form): result_repr = display_for_value(value, boolean) # Strip HTML tags in the resulting text, except if the # function has an "allow_tags" attribute set to True. - if not allow_tags: - result_repr = escape(result_repr) - else: + if allow_tags: result_repr = mark_safe(result_repr) if isinstance(value, (datetime.date, datetime.time)): - row_class = ' class="nowrap"' + row_class = mark_safe(' class="nowrap"') else: if isinstance(f.rel, models.ManyToOneRel): field_val = getattr(result, f.name) if field_val is None: result_repr = EMPTY_CHANGELIST_VALUE else: - result_repr = escape(field_val) + result_repr = field_val else: result_repr = display_for_field(value, f) if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)): - row_class = ' class="nowrap"' + row_class = mark_safe(' class="nowrap"') if force_unicode(result_repr) == '': result_repr = mark_safe(' ') # If list_display_links not defined, add the link tag to the first field @@ -222,8 +224,14 @@ def items_for_result(cl, result, form): attr = pk value = result.serializable_value(attr) result_id = repr(force_unicode(value))[1:] - 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)) + yield format_html('<{0}{1}>{4}', + table_tag, + row_class, + url, + format_html(' onclick="opener.dismissRelatedLookupPopup(window, {0}); return false;"', result_id) + if cl.is_popup else '', + result_repr, + table_tag) else: # By default the fields come from ModelAdmin.list_editable, but if we pull # the fields out of the form instead of list_editable custom admins @@ -233,11 +241,9 @@ def items_for_result(cl, result, form): form[cl.model._meta.pk.name].is_hidden)): bf = form[field_name] result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf)) - else: - result_repr = conditional_escape(result_repr) - yield mark_safe('%s' % (row_class, result_repr)) + yield format_html('{1}', row_class, result_repr) if form and not form[cl.model._meta.pk.name].is_hidden: - yield mark_safe('%s' % force_unicode(form[cl.model._meta.pk.name])) + yield format_html('{0}', 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 18b10f3cfa..92e1c0efd5 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -9,8 +9,7 @@ from django.db.models.deletion import Collector from django.db.models.related import RelatedObject from django.forms.forms import pretty_name from django.utils import formats -from django.utils.html import escape -from django.utils.safestring import mark_safe +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 @@ -124,10 +123,10 @@ 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('%s: %s' % - (escape(capfirst(opts.verbose_name)), - admin_url, - escape(obj))) + return format_html('{0}: {2}', + capfirst(opts.verbose_name), + admin_url, + obj) else: # Don't display link to edit, because it either has no # admin or is edited inline. diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 18897bdeb1..37f286d0d4 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static from django.core.urlresolvers import reverse from django.forms.widgets import RadioFieldRenderer from django.forms.util import flatatt -from django.utils.html import escape +from django.utils.html import escape, format_html, format_html_join from django.utils.text import Truncator from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe @@ -85,16 +85,17 @@ class AdminSplitDateTime(forms.SplitDateTimeWidget): forms.MultiWidget.__init__(self, widgets, attrs) def format_output(self, rendered_widgets): - return mark_safe('

    %s %s
    %s %s

    ' % \ - (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1])) + return format_html('

    {0} {1}
    {2} {3}

    ', + _('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('\n%s\n
    ' % ( - flatatt(self.attrs), - '\n'.join(['
  • %s
  • ' % force_unicode(w) for w in self])) - ) + return format_html('\n{1}\n', + 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 061/176] 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 062/176] 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 063/176] 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 064/176] 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 065/176] 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 066/176] 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 067/176] 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 068/176] 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 069/176] 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 070/176] 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 071/176] 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 072/176] 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 073/176] 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 074/176] 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 075/176] 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 076/176] 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 077/176] 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 078/176] 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 079/176] 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 080/176] 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 081/176] 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 082/176] 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 083/176] 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 084/176] 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 085/176] 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 086/176] 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 087/176] 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 088/176] 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 089/176] 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 090/176] 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 091/176] 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 092/176] 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 093/176] 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 094/176] 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 095/176] 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 096/176] 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 097/176] 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 098/176] 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 099/176] 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 100/176] 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 101/176] 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 102/176] 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 103/176] 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 104/176] 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 105/176] 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 106/176] 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 107/176] 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 108/176] 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 109/176] 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 110/176] 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 111/176] 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 bebbbb7af096ecffc0d7585617fdfacb196bc7c2 Mon Sep 17 00:00:00 2001 From: Nuno Maltez Date: Mon, 16 Jul 2012 19:52:31 +0300 Subject: [PATCH 112/176] 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 113/176] 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 114/176] 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 115/176] 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 116/176] 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 117/176] 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 118/176] 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 119/176] 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 120/176] 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 121/176] 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 122/176] 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 123/176] 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 124/176] 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 125/176] 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 126/176] 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 127/176] 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 128/176] 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 129/176] 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 130/176] 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 131/176] 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 132/176] 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 133/176] 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 134/176] 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 135/176] 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 136/176] 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 137/176] 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 138/176] 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 139/176] 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 140/176] 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 141/176] [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 142/176] [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 143/176] [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 144/176] [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 145/176] [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 146/176] [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 147/176] [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 148/176] [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 149/176] [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 150/176] [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 151/176] [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 152/176] [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 153/176] [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 154/176] [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 155/176] 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 156/176] 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 157/176] 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 158/176] 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 38ce709fe44ebf37e6c8531451eab98fe0a552a0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 24 Jul 2012 07:01:57 -0700 Subject: [PATCH 159/176] 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 160/176] 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 161/176] 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 162/176] 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 163/176] 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 164/176] 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 165/176] 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 166/176] 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 167/176] 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 168/176] 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 169/176] 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 170/176] 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 171/176] 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 172/176] 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 173/176] 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 174/176] 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 4b5cb116e30020c459ad8c9314ae9311b461beb5 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 25 Jul 2012 22:45:46 +0200 Subject: [PATCH 175/176] 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 176/176] [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())