From 81f7a6587f682960811330bb591d08e89439a39f Mon Sep 17 00:00:00 2001 From: James Bennett Date: Wed, 23 Mar 2011 06:01:52 +0000 Subject: [PATCH 001/225] Create 1.3 release branch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15905 bcc190cf-cafb-0310-a4f2-bffc1f526a37 From d0052fcc5c47fe38d64f50cf6bdd40c9283dc63a Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Wed, 23 Mar 2011 08:52:39 +0000 Subject: [PATCH 002/225] [1.3.X] Fixed a few typos in the 1.3 release notes. Backport of r15907 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15908 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/releases/1.3.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index f7fdda7951a8..598f7413876b 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -140,7 +140,7 @@ wherever you would have historically used:: import unittest -If you want to continue to use the base unittest libary, you can -- +If you want to continue to use the base unittest library, you can -- you just won't get any of the nice new unittest2 features. .. _unittest2: http://pypi.python.org/pypi/unittest2 @@ -566,7 +566,7 @@ Changed priority of translation loading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Work has been done to simplify, rationalize and properly document the algorithm -used by Django at runtime to build translations from the differents translations +used by Django at runtime to build translations from the different translations found on disk, namely: For translatable literals found in Python code and templates (``'django'`` @@ -785,7 +785,7 @@ and ignored the almost identical implementation in the already used auth app. A side effect of this duplication was the missing adoption of the changes made in r12634_ to support a broader set of characters for usernames. -This release refactores the admin's login mechanism to use a subclass of the +This release refactors the admin's login mechanism to use a subclass of the :class:`~django.contrib.auth.forms.AuthenticationForm` instead of a manual form validation. The previously undocumented method ``'django.contrib.admin.sites.AdminSite.display_login_form'`` has been removed From 258957f4b909b5f893a452ea3a13e82719037cbb Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 26 Mar 2011 13:19:04 +0000 Subject: [PATCH 003/225] [1.3.X] Bumped django_next_version so that "New in Django 1.3" links appear correctly. Backport of [15909] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15920 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c38d6be7c21e..3d88ffeddeed 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,7 @@ # The full version, including alpha/beta/rc tags. release = '1.3' # The next version to be released -django_next_version = '1.3' +django_next_version = '1.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 9b0f1de56693156ce78edf5456dc32e55d7b11e4 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 27 Mar 2011 23:01:47 +0000 Subject: [PATCH 004/225] [1.3.X] Fixed #15664 - Removed extra parens in commit_on_success example. Backport of r15923 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15924 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/transactions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index a5bb7470ad48..70b11570e12e 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -73,7 +73,7 @@ These functions, described in detail below, can be used in two different ways: from django.db import transaction - @transaction.commit_on_success() + @transaction.commit_on_success def viewfunc(request): # ... # this code executes inside a transaction From ce9b216882ea11bf351f222513458bc503556a91 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 28 Mar 2011 16:15:43 +0000 Subject: [PATCH 005/225] [1.3.X] Fixed #15679 - regression in HttpRequest.POST and raw_post_data access. Thanks to vkryachko for the report. This also fixes a slight inconsistency with raw_post_data after parsing of a multipart request, and adds a test for that. (Previously accessing raw_post_data would have returned the empty string rather than raising an Exception). Backport of [15938] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15939 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/http/__init__.py | 10 +++-- tests/regressiontests/requests/tests.py | 60 +++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 2e4f371fb1c7..6fccd24da07f 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -262,14 +262,18 @@ def _load_post_and_files(self): if self.method != 'POST': self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() return - if self._read_started: + if self._read_started and not hasattr(self, '_raw_post_data'): self._mark_post_parse_error() return if self.META.get('CONTENT_TYPE', '').startswith('multipart'): - self._raw_post_data = '' + if hasattr(self, '_raw_post_data'): + # Use already read data + data = StringIO(self._raw_post_data) + else: + data = self try: - self._post, self._files = self.parse_file_upload(self.META, self) + self._post, self._files = self.parse_file_upload(self.META, data) except: # An error occured while parsing POST data. Since when # formatting the error the request handler might access diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index a80ee9d57133..b68201494b46 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -179,6 +179,66 @@ def test_value_after_read(self): self.assertRaises(Exception, lambda: request.raw_post_data) self.assertEqual(request.POST, {}) + def test_raw_post_data_after_POST_multipart(self): + """ + Reading raw_post_data after parsing multipart is not allowed + """ + # Because multipart is used for large amounts fo data i.e. file uploads, + # we don't want the data held in memory twice, and we don't want to + # silence the error by setting raw_post_data = '' either. + payload = "\r\n".join([ + '--boundary', + 'Content-Disposition: form-data; name="name"', + '', + 'value', + '--boundary--' + '']) + request = WSGIRequest({'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', + 'CONTENT_LENGTH': len(payload), + 'wsgi.input': StringIO(payload)}) + self.assertEqual(request.POST, {u'name': [u'value']}) + self.assertRaises(Exception, lambda: request.raw_post_data) + def test_read_by_lines(self): request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) self.assertEqual(list(request), ['name=value']) + + def test_POST_after_raw_post_data_read(self): + """ + POST should be populated even if raw_post_data is read first + """ + request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) + raw_data = request.raw_post_data + self.assertEqual(request.POST, {u'name': [u'value']}) + + def test_POST_after_raw_post_data_read_and_stream_read(self): + """ + POST should be populated even if raw_post_data is read first, and then + the stream is read second. + """ + request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) + raw_data = request.raw_post_data + self.assertEqual(request.read(1), u'n') + self.assertEqual(request.POST, {u'name': [u'value']}) + + def test_POST_after_raw_post_data_read_and_stream_read_multipart(self): + """ + POST should be populated even if raw_post_data is read first, and then + the stream is read second. Using multipart/form-data instead of urlencoded. + """ + payload = "\r\n".join([ + '--boundary', + 'Content-Disposition: form-data; name="name"', + '', + 'value', + '--boundary--' + '']) + request = WSGIRequest({'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', + 'CONTENT_LENGTH': len(payload), + 'wsgi.input': StringIO(payload)}) + raw_data = request.raw_post_data + # Consume enough data to mess up the parsing: + self.assertEqual(request.read(13), u'--boundary\r\nC') + self.assertEqual(request.POST, {u'name': [u'value']}) From d935b232a26a8094a92fa2c56cd8b0dc5de42f23 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Tue, 29 Mar 2011 10:25:18 +0000 Subject: [PATCH 006/225] [1.2.X] Fixed #15710 - removed "that that" typos. Backport of r15942 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15943 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/multi-db.txt | 2 +- docs/topics/http/shortcuts.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index 5a052d4a1741..9a603b52fef0 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -454,7 +454,7 @@ Exposing multiple databases in Django's admin interface Django's admin doesn't have any explicit support for multiple databases. If you want to provide an admin interface for a model on a -database other than that that specified by your router chain, you'll +database other than that specified by your router chain, you'll need to write custom :class:`~django.contrib.admin.ModelAdmin` classes that will direct the admin to use a specific database for content. diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index cc24560295a0..4a0fa58c4156 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -24,7 +24,7 @@ introduce controlled coupling for convenience's sake. :func:`render()` is the same as a call to :func:`render_to_response()` with a `context_instance` argument that - that forces the use of a :class:`~django.template.RequestContext`. + forces the use of a :class:`~django.template.RequestContext`. Required arguments ------------------ From 032beb11da915c612831bb68ddc99bcf153169e4 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 30 Mar 2011 18:49:42 +0000 Subject: [PATCH 007/225] [1.3.X] Fixed integer overflows that occurred when `OFTReal` fields were treated as `OFTInteger`. Backport of 15946 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15960 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/gdal/field.py | 14 +++++++++++--- django/contrib/gis/gdal/tests/test_ds.py | 17 ++++++++++++++--- django/contrib/gis/tests/data/texas.dbf | Bin 0 -> 660 bytes 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100755 django/contrib/gis/tests/data/texas.dbf diff --git a/django/contrib/gis/gdal/field.py b/django/contrib/gis/gdal/field.py index 46dbc869b7cd..2b831b5516c2 100644 --- a/django/contrib/gis/gdal/field.py +++ b/django/contrib/gis/gdal/field.py @@ -20,7 +20,7 @@ def __init__(self, feat, index): # Setting the feature pointer and index. self._feat = feat self._index = index - + # Getting the pointer for this field. fld_ptr = capi.get_feat_field_defn(feat, index) if not fld_ptr: @@ -33,6 +33,7 @@ def __init__(self, feat, index): # OFTReal with no precision should be an OFTInteger. if isinstance(self, OFTReal) and self.precision == 0: self.__class__ = OFTInteger + self._double = True def __str__(self): "Returns the string representation of the Field." @@ -95,10 +96,17 @@ def width(self): ### The Field sub-classes for each OGR Field type. ### class OFTInteger(Field): + _double = False + @property def value(self): "Returns an integer contained in this field." - return self.as_int() + if self._double: + # If this is really from an OFTReal field with no precision, + # read as a double and cast as Python int (to prevent overflow). + return int(self.as_double()) + else: + return self.as_int() @property def type(self): @@ -137,7 +145,7 @@ def value(self): "Returns a Python `datetime` object for this OFTDateTime field." # TODO: Adapt timezone information. # See http://lists.maptools.org/pipermail/gdal-dev/2006-February/007990.html - # The `tz` variable has values of: 0=unknown, 1=localtime (ambiguous), + # The `tz` variable has values of: 0=unknown, 1=localtime (ambiguous), # 100=GMT, 104=GMT+1, 80=GMT-5, etc. try: yy, mm, dd, hh, mn, ss, tz = self.as_datetime() diff --git a/django/contrib/gis/gdal/tests/test_ds.py b/django/contrib/gis/gdal/tests/test_ds.py index e1083b2a35d3..16d6e8603b7a 100644 --- a/django/contrib/gis/gdal/tests/test_ds.py +++ b/django/contrib/gis/gdal/tests/test_ds.py @@ -1,7 +1,8 @@ -import os, os.path, unittest +import os +import unittest from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString -from django.contrib.gis.geometry.test_data import get_ds_file, TestDS +from django.contrib.gis.geometry.test_data import get_ds_file, TestDS, TEST_DATA # List of acceptable data sources. ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile', @@ -72,7 +73,7 @@ def test03a_layers(self): self.assertEqual(source.nfld, len(layer.fields)) # Testing the layer's extent (an Envelope), and it's properties - if source.driver == 'VRT' and (GDAL_VERSION > (1, 7, 0) and GDAL_VERSION < (1, 7, 3)): + if source.driver == 'VRT' and (GDAL_VERSION >= (1, 7, 0) and GDAL_VERSION < (1, 7, 3)): # There's a known GDAL regression with retrieving the extent # of a VRT layer in versions 1.7.0-1.7.2: # http://trac.osgeo.org/gdal/ticket/3783 @@ -217,6 +218,16 @@ def test06_spatial_filter(self): lyr.spatial_filter = None self.assertEqual(3, len(lyr)) + def test07_integer_overflow(self): + "Testing that OFTReal fields, treated as OFTInteger, do not overflow." + # Using *.dbf from Census 2010 TIGER Shapefile for Texas, + # which has land area ('ALAND10') stored in a Real field + # with no precision. + ds = DataSource(os.path.join(TEST_DATA, 'texas.dbf')) + feat = ds[0][0] + # Reference value obtained using `ogrinfo`. + self.assertEqual(676586997978, feat.get('ALAND10')) + def suite(): s = unittest.TestSuite() s.addTest(unittest.makeSuite(DataSourceTest)) diff --git a/django/contrib/gis/tests/data/texas.dbf b/django/contrib/gis/tests/data/texas.dbf new file mode 100755 index 0000000000000000000000000000000000000000..827c06507199fae9134db5d43eea9a142933e828 GIT binary patch literal 660 zcmcJMv2MaJ5QYO45)#k{VDesp&aoTYqZN|KQZNxVZP!X=WntmD`bK?|+75!qBqKMR zEPwa^zs`O+&E}&dN&ZH^u0PhksMJnla3uG19F1($hcfL%FE>`&qHMguFxTeBr;&eE z?9?X259tr43G#lf<+f<$E&qx9Ya_4r{rasf^SqNkiS*?`=YB^w687_N*)3U1eGSn5 z{y%@j62ANgA_qfj literal 0 HcmV?d00001 From e37fae7fb95238af00fee5b1087b01134ed391aa Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 30 Mar 2011 19:26:11 +0000 Subject: [PATCH 008/225] [1.3.X] Fixed `LayerMapping` to support `BigIntegerField`. Backport of r15961 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15962 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/utils/layermapping.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 0f3a72213dc4..bb569912b494 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -57,6 +57,7 @@ class LayerMapping(object): # This is a reminder that XMLField is deprecated # and this needs to be removed in 1.4 models.XMLField : OFTString, + models.BigIntegerField : (OFTInteger, OFTReal, OFTString), models.SmallIntegerField : (OFTInteger, OFTReal, OFTString), models.PositiveSmallIntegerField : (OFTInteger, OFTReal, OFTString), } From 6bf0fe6c4e134521c4f8762c796083f8eb6f4b5c Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 31 Mar 2011 08:35:20 +0000 Subject: [PATCH 009/225] [1.3.X] Fixed #15726 -- Fixed the name of a file of the Mexican Spanish translation added in r15433. Backport from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15963 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/es_MX/{__init__py => __init__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename django/conf/locale/es_MX/{__init__py => __init__.py} (100%) diff --git a/django/conf/locale/es_MX/__init__py b/django/conf/locale/es_MX/__init__.py similarity index 100% rename from django/conf/locale/es_MX/__init__py rename to django/conf/locale/es_MX/__init__.py From e6fe336f10d461b84989a88786a8fd0bfb489a3e Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 31 Mar 2011 23:20:55 +0000 Subject: [PATCH 010/225] [1.3.X] Fixed #15681 -- Fixed a documentation error regarding the default value of the STATIC_URL setting. Thanks, Chris Drackett. Backport from trunk (r15913 and r15914). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15966 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/static-files.txt | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 77e07141e320..5657e24516af 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -48,16 +48,24 @@ Here's the basic usage in a nutshell: See the documentation for the :setting:`STATICFILES_FINDERS` setting for details on how ``staticfiles`` finds your files. - 2. Make sure that ``django.contrib.staticfiles`` is in your + 2. Set the :setting:`STATIC_URL` setting to the URL you want to use + for pointing to your static files, e.g.:: + + STATIC_URL = '/static/' + + In projects freshly created with the :djadmin:`startproject` + management command this will be preset to ``'/static/'``. + + 3. Make sure that ``django.contrib.staticfiles`` is in your :setting:`INSTALLED_APPS`. For :ref:`local development`, if you are using :ref:`runserver` or adding :ref:`staticfiles_urlpatterns` to your URLconf, you're done! Your static files will automatically be served at the - default :setting:`STATIC_URL` of ``/static/``. + :setting:`STATIC_URL` you specified in step 2. - 3. You'll probably need to refer to these files in your templates. The + 4. You'll probably need to refer to these files in your templates. The easiest method is to use the included context processor which will allow template code like: @@ -70,24 +78,20 @@ Here's the basic usage in a nutshell: When you're ready to move out of local development and deploy your project: - 1. Set the :setting:`STATIC_URL` setting to the public URL for your static - files (in some cases, the default value of ``/static/`` may still be - fine). - - 2. Set the :setting:`STATIC_ROOT` setting to point to where you'd like your + 1. Set the :setting:`STATIC_ROOT` setting to point to where you'd like your static files collected to when you use the :djadmin:`collectstatic` management command. For example:: STATIC_ROOT = "/home/jacob/projects/mysite.com/sitestatic" - 3. Run the :djadmin:`collectstatic` management command:: + 2. Run the :djadmin:`collectstatic` management command:: ./manage.py collectstatic This'll churn through your static file storage and copy them into the directory given by :setting:`STATIC_ROOT`. - 4. Deploy those files by configuring your webserver of choice to serve the + 3. Deploy those files by configuring your webserver of choice to serve the files in :setting:`STATIC_ROOT` at :setting:`STATIC_URL`. :ref:`staticfiles-production` covers some common deployment strategies From 686ef6c7596ca1fcfde01212ca3ebcdeb9378e74 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 2 Apr 2011 08:50:05 +0000 Subject: [PATCH 011/225] [1.3.X] Fixed #15739 -- Added support to RedirectView for HEAD, OPTIONS, POST, PUT and DELETE requests Backport of r15992 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@15995 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/generic/base.py | 15 +++++++++++ tests/regressiontests/generic_views/base.py | 30 +++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/django/views/generic/base.py b/django/views/generic/base.py index d732af5d6679..1ac7fb89ee0f 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -161,3 +161,18 @@ def get(self, request, *args, **kwargs): 'request': self.request }) return http.HttpResponseGone() + + def head(self, request, *args, **kwargs): + return self.get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self.get(request, *args, **kwargs) + + def options(self, request, *args, **kwargs): + return self.get(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + return self.get(request, *args, **kwargs) + + def put(self, request, *args, **kwargs): + return self.get(request, *args, **kwargs) diff --git a/tests/regressiontests/generic_views/base.py b/tests/regressiontests/generic_views/base.py index e20932b7fe63..4003bbe73259 100644 --- a/tests/regressiontests/generic_views/base.py +++ b/tests/regressiontests/generic_views/base.py @@ -261,3 +261,33 @@ def test_parameter_substitution(self): response = RedirectView.as_view(url='/bar/%(object_id)d/')(self.rf.get('/foo/42/'), object_id=42) self.assertEqual(response.status_code, 301) self.assertEqual(response['Location'], '/bar/42/') + + def test_redirect_POST(self): + "Default is a permanent redirect" + response = RedirectView.as_view(url='/bar/')(self.rf.post('/foo/')) + self.assertEqual(response.status_code, 301) + self.assertEqual(response['Location'], '/bar/') + + def test_redirect_HEAD(self): + "Default is a permanent redirect" + response = RedirectView.as_view(url='/bar/')(self.rf.head('/foo/')) + self.assertEqual(response.status_code, 301) + self.assertEqual(response['Location'], '/bar/') + + def test_redirect_OPTIONS(self): + "Default is a permanent redirect" + response = RedirectView.as_view(url='/bar/')(self.rf.options('/foo/')) + self.assertEqual(response.status_code, 301) + self.assertEqual(response['Location'], '/bar/') + + def test_redirect_PUT(self): + "Default is a permanent redirect" + response = RedirectView.as_view(url='/bar/')(self.rf.put('/foo/')) + self.assertEqual(response.status_code, 301) + self.assertEqual(response['Location'], '/bar/') + + def test_redirect_DELETE(self): + "Default is a permanent redirect" + response = RedirectView.as_view(url='/bar/')(self.rf.delete('/foo/')) + self.assertEqual(response.status_code, 301) + self.assertEqual(response['Location'], '/bar/') From e5aa2bdcecf52b99ddbaa82ae98d557721d8ae61 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 3 Apr 2011 00:11:29 +0000 Subject: [PATCH 012/225] [1.3.X] Fixed #15672 -- Fixed bug in core/handlers/wsgi.py where we were referring to the 'request' variable before assigning to it. Thanks for the report, vkryachko Backport of r15918 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16009 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/handlers/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 537154c0cbb2..058f9c307f88 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -261,7 +261,7 @@ def __call__(self, environ, start_response): try: request = self.request_class(environ) except UnicodeDecodeError: - logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path, + logger.warning('Bad Request (UnicodeDecodeError)', exc_info=sys.exc_info(), extra={ 'status_code': 400, From 05054aba7684c8d5b2bbe4ea0e2aee24ae7deba2 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sun, 3 Apr 2011 23:09:39 +0000 Subject: [PATCH 013/225] [1.3.X] Fixed #15746. Clarified updated list_filter documentation. Backport of r16010 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16011 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index c28dc8b7b327..cd6ff31cfcdb 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -531,17 +531,19 @@ subclass:: list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') list_filter = ('is_staff', 'is_superuser') - Fields in ``list_filter`` can also span relations using the ``__`` lookup:: - - class UserAdminWithLookup(UserAdmin): - list_filter = ('groups__name') - The above code results in an admin change list page that looks like this: .. image:: _images/users_changelist.png (This example also has ``search_fields`` defined. See below.) + .. versionadded:: 1.3 + + Fields in ``list_filter`` can also span relations using the ``__`` lookup:: + + class UserAdminWithLookup(UserAdmin): + list_filter = ('groups__name') + .. attribute:: ModelAdmin.list_per_page Set ``list_per_page`` to control how many items appear on each paginated From 79bb9c145620ad6b4b424e2390c6e0b6905c122b Mon Sep 17 00:00:00 2001 From: Ian Kelly Date: Sat, 16 Apr 2011 18:43:01 +0000 Subject: [PATCH 014/225] [1.3.X] Fixed #15573: Forced the default site id to be 1 when creating test databases, to prevent a large number of errors when running the tests using the oracle backend. Backport of r16027 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16028 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/creation.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index ef594b7bfc5c..75f07926c1f6 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -374,6 +374,14 @@ def create_test_db(self, verbosity=1, autoclobber=False): verbosity=max(verbosity - 1, 0), interactive=False, database=self.connection.alias) + + # One effect of calling syncdb followed by flush is that the id of the + # default site may or may not be 1, depending on how the sequence was + # reset. If the sites app is loaded, then we coerce it. + from django.db.models import get_model + Site = get_model('sites', 'Site') + if Site is not None and Site.objects.using(self.connection.alias).count() == 1: + Site.objects.using(self.connection.alias).update(id=settings.SITE_ID) from django.core.cache import get_cache from django.core.cache.backends.db import BaseDatabaseCache From cdd75e078ab548a3690113b3bdaeda47decba92a Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sun, 17 Apr 2011 04:12:01 +0000 Subject: [PATCH 015/225] [1.3.X] Ensure stdin is a tty before handing it to termios, so as to prevent prolems when running under IDEs. Backport of [15911] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16029 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/autoreload.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index e5a421e586ca..ffa75e2c4f45 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -73,11 +73,12 @@ def code_changed(): def ensure_echo_on(): if termios: - fd = sys.stdin.fileno() - attr_list = termios.tcgetattr(fd) - if not attr_list[3] & termios.ECHO: - attr_list[3] |= termios.ECHO - termios.tcsetattr(fd, termios.TCSANOW, attr_list) + fd = sys.stdin + if fd.isatty(): + attr_list = termios.tcgetattr(fd) + if not attr_list[3] & termios.ECHO: + attr_list[3] |= termios.ECHO + termios.tcsetattr(fd, termios.TCSANOW, attr_list) def reloader_thread(): ensure_echo_on() From 9b21a0c92132f5b5c9c8ca33fba88c1eab2f2933 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sun, 17 Apr 2011 14:27:53 +0000 Subject: [PATCH 016/225] [1.3.X] Updated the contributing document to accurately reflect our security process. Backport of [16032] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16033 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/contributing.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 6e14083dbef8..33ee50a7b429 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -104,19 +104,19 @@ following actions: fix is forthcoming. We'll give a rough timeline and ask the reporter to keep the issue confidential until we announce it. - * Halt all other development as long as is needed to develop a fix, - including patches against the current and two previous releases. + * Focus on developing a fix as quickly as possible and produce patches + against the current and two previous releases. * Determine a go-public date for announcing the vulnerability and the fix. To try to mitigate a possible "arms race" between those applying the patch and those trying to exploit the hole, we will not announce security problems immediately. - * Pre-notify everyone we know to be running the affected version(s) of - Django. We will send these notifications through private e-mail - which will include documentation of the vulnerability, links to the - relevant patch(es), and a request to keep the vulnerability - confidential until the official go-public date. + * Pre-notify third-party distributors of Django ("vendors"). We will send + these vendor notifications through private email which will include + documentation of the vulnerability, links to the relevant patch(es), and a + request to keep the vulnerability confidential until the official + go-public date. * Publicly announce the vulnerability and the fix on the pre-determined go-public date. This will probably mean a new release of Django, but From 1d499d50d08f8b36aed9e008dadfc26e4fe1b208 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Mon, 18 Apr 2011 21:10:42 +0000 Subject: [PATCH 017/225] [1.3.X] Fixed #15848 -- Fixed regression introduced in [15882] in makemessages management command when processing multi-line comments that contain non-ASCCI characters in templates. Thanks for the report Denis Drescher. Backport of r16038/r16039 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16040 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/translation/trans_real.py | 6 +++--- tests/regressiontests/i18n/commands/extraction.py | 4 ++++ tests/regressiontests/i18n/commands/templates/test.html | 7 +++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index b8478354bde3..25ca6f247104 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -447,16 +447,16 @@ def templatize(src, origin=None): for t in Lexer(src, origin).tokenize(): if incomment: if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment': - content = u''.join(comment) + content = ''.join(comment) translators_comment_start = None for lineno, line in enumerate(content.splitlines(True)): if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK): translators_comment_start = lineno for lineno, line in enumerate(content.splitlines(True)): if translators_comment_start is not None and lineno >= translators_comment_start: - out.write(u' # %s' % line) + out.write(' # %s' % line) else: - out.write(u' #\n') + out.write(' #\n') incomment = False comment = [] else: diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py index 0d70d2641dc4..e5290406abd5 100644 --- a/tests/regressiontests/i18n/commands/extraction.py +++ b/tests/regressiontests/i18n/commands/extraction.py @@ -1,3 +1,4 @@ +# -*- encoding: utf-8 -*- import os import re import shutil @@ -63,6 +64,9 @@ def test_comments_extractor(self): self.assertTrue('#. Translators: One-line translator comment #4' in po_contents) self.assertTrue('#. Translators: Two-line translator comment #4\n#. continued here.' in po_contents) + self.assertTrue('#. Translators: One-line translator comment #5 -- with non ASCII characters: áéíóúö' in po_contents) + self.assertTrue('#. Translators: Two-line translator comment #5 -- with non ASCII characters: áéíóúö\n#. continued here.' in po_contents) + def test_templatize(self): os.chdir(self.test_dir) management.call_command('makemessages', locale=LOCALE, verbosity=0) diff --git a/tests/regressiontests/i18n/commands/templates/test.html b/tests/regressiontests/i18n/commands/templates/test.html index 86c7772580f7..b5d705c1327f 100644 --- a/tests/regressiontests/i18n/commands/templates/test.html +++ b/tests/regressiontests/i18n/commands/templates/test.html @@ -50,3 +50,10 @@ {% comment %} Translators: Two-line translator comment #4 continued here.{% endcomment %} {% trans "Translatable literal #4b" %} + +{% comment %} Translators: One-line translator comment #5 -- with non ASCII characters: áéíóúö{% endcomment %} +{% trans "Translatable literal #5a" %} + +{% comment %} Translators: Two-line translator comment #5 -- with non ASCII characters: áéíóúö +continued here.{% endcomment %} +{% trans "Translatable literal #6b" %} From b061cb97be8254f7b0f816cd10db0bf811f9509e Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Mon, 18 Apr 2011 23:20:07 +0000 Subject: [PATCH 018/225] [1.3.X] Fixed #15843 -- removed an extraneous quotation mark in the template tag docs. Thanks to Titan, Jer-ming Lin for the report. Backport of [16042] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16043 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- 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 1fa495e51a9e..eaa576ef1578 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -666,7 +666,7 @@ including it. This example produces the output ``"Hello, John"``: You can pass additional context to the template using keyword arguments:: - {% include "name_snippet.html" with person="Jane" greeting="Hello" "%} + {% include "name_snippet.html" with person="Jane" greeting="Hello" %} If you want to only render the context with the variables provided (or even no variables at all), use the ``only`` option:: From 24adaf76f14bb32fcd2389b0339fd9cfc2cc98c2 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Wed, 20 Apr 2011 19:51:54 +0000 Subject: [PATCH 019/225] [1.3.X] Fixed #15593 -- Added a note that the output of `reverse` is urlquoted. Thanks to guettli for the report and draft patch. Backport of [16054] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16055 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/unicode.txt | 2 ++ docs/topics/http/urls.txt | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index c84347844be8..6eccc26a36b5 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -140,6 +140,8 @@ Normally, you'll only need to use ``smart_unicode()``. Call it as early as possible on any input data that might be either Unicode or a bytestring, and from then on, you can treat the result as always being Unicode. +.. _uri-and-iri-handling: + URI and IRI handling ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 091dcb0007d2..d721012d3e74 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -772,9 +772,9 @@ reverse() If you need to use something similar to the :ttag:`url` template tag in your code, Django provides the following method (in the -``django.core.urlresolvers`` module): +:mod:`django.core.urlresolvers` module): -.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None) +.. function:: reverse(viewname, [urlconf=None, args=None, kwargs=None, current_app=None]) ``viewname`` is either the function name (either a function reference, or the string version of the name, if you used that form in ``urlpatterns``) or the @@ -815,6 +815,18 @@ namespaces into URLs on specific application instances, according to the be imported correctly. Do not include lines that reference views you haven't written yet, because those views will not be importable. +.. note:: + + The string returned by :meth:`~django.core.urlresolvers.reverse` is already + :ref:`urlquoted `. For example:: + + >>> reverse('cities', args=u'Orléans') + '.../Orl%C3%A9ans/' + + Applying further encoding (such as :meth:`~django.utils.http.urlquote` or + ``urllib.quote``) to the ouput of :meth:`~django.core.urlresolvers.reverse` + may produce undesirable results. + resolve() --------- From 64995cdd63e01b6bf5372cec76c3fe1d2a3cff62 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Wed, 20 Apr 2011 20:04:46 +0000 Subject: [PATCH 020/225] [1.3.X] Fixed #15794 -- Corrected an error in the docs which indicated applying decorators to any of the view-like methods would work when it will only work reliably with dispatch. Thanks to carbonXT for the report and patch. Backport of [16056] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16057 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/class-based-views.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index 2612ffedbac6..01d5f2fa654e 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -585,11 +585,8 @@ Decorating the class -------------------- To decorate every instance of a class-based view, you need to decorate -the class definition itself. To do this you apply the decorator to one -of the view-like methods on the class; that is, -:meth:`~django.views.generic.base.View.dispatch`, or one of the HTTP -methods (:meth:`~django.views.generic.base.View.get`, -:meth:`~django.views.generic.base.View.post` etc). +the class definition itself. To do this you apply the decorator to the +:meth:`~django.views.generic.base.View.dispatch` method of the class. A method on a class isn't quite the same as a standalone function, so you can't just apply a function decorator to the method -- you need to From 53678ef5083a20955e3f0192bbe9005cc7abbded Mon Sep 17 00:00:00 2001 From: Ian Kelly Date: Thu, 21 Apr 2011 17:43:22 +0000 Subject: [PATCH 021/225] [1.3.X] Refs #15573, #15850: Added a check for whether the sites app is installed when creating the test database, in order to work around a bug in get_model. Thanks to adsva and carljm. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16062 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/creation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 75f07926c1f6..57e3f7762dd8 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -379,9 +379,10 @@ def create_test_db(self, verbosity=1, autoclobber=False): # default site may or may not be 1, depending on how the sequence was # reset. If the sites app is loaded, then we coerce it. from django.db.models import get_model - Site = get_model('sites', 'Site') - if Site is not None and Site.objects.using(self.connection.alias).count() == 1: - Site.objects.using(self.connection.alias).update(id=settings.SITE_ID) + if 'django.contrib.sites' in settings.INSTALLED_APPS: + Site = get_model('sites', 'Site') + if Site is not None and Site.objects.using(self.connection.alias).count() == 1: + Site.objects.using(self.connection.alias).update(id=settings.SITE_ID) from django.core.cache import get_cache from django.core.cache.backends.db import BaseDatabaseCache From 4d62386cad7f50ae13e0af6b5856e2a12fff313b Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 22 Apr 2011 12:06:11 +0000 Subject: [PATCH 022/225] [1.3.X] Fixed #15698 -- Fixed inconsistant handling of context_object_name in paginated MultipleObjectMixin views. Thanks, Dave Hall. Backport from trunk (r16079). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16080 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/generic/list.py | 2 +- tests/regressiontests/generic_views/dates.py | 30 ++++++++++++++++++++ tests/regressiontests/generic_views/urls.py | 8 ++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/django/views/generic/list.py b/django/views/generic/list.py index 56684573a35d..5135763d5336 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -89,6 +89,7 @@ def get_context_data(self, **kwargs): """ queryset = kwargs.pop('object_list') page_size = self.get_paginate_by(queryset) + context_object_name = self.get_context_object_name(queryset) if page_size: paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size) context = { @@ -105,7 +106,6 @@ def get_context_data(self, **kwargs): 'object_list': queryset } context.update(kwargs) - context_object_name = self.get_context_object_name(queryset) if context_object_name is not None: context[context_object_name] = queryset return context diff --git a/tests/regressiontests/generic_views/dates.py b/tests/regressiontests/generic_views/dates.py index d5345befd0a7..5b784f68102c 100644 --- a/tests/regressiontests/generic_views/dates.py +++ b/tests/regressiontests/generic_views/dates.py @@ -119,6 +119,13 @@ def test_year_view_allow_future(self): self.assertEqual(res.status_code, 200) self.assertEqual(list(res.context['date_list']), [datetime.datetime(year, 1, 1)]) + def test_year_view_paginated(self): + res = self.client.get('/dates/books/2006/paginated/') + self.assertEqual(res.status_code, 200) + self.assertEqual(list(res.context['book_list']), list(Book.objects.filter(pubdate__year=2006))) + self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2006))) + self.assertTemplateUsed(res, 'generic_views/book_archive_year.html') + def test_year_view_invalid_pattern(self): res = self.client.get('/dates/books/no_year/') self.assertEqual(res.status_code, 404) @@ -190,6 +197,13 @@ def test_month_view_allow_future(self): self.assertEqual(res.context['next_month'], future) self.assertEqual(res.context['previous_month'], datetime.date(2006, 5, 1)) + def test_month_view_paginated(self): + res = self.client.get('/dates/books/2008/oct/paginated/') + self.assertEqual(res.status_code, 200) + self.assertEqual(list(res.context['book_list']), list(Book.objects.filter(pubdate__year=2008, pubdate__month=10))) + self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2008, pubdate__month=10))) + self.assertTemplateUsed(res, 'generic_views/book_archive_month.html') + def test_custom_month_format(self): res = self.client.get('/dates/books/2008/10/') self.assertEqual(res.status_code, 200) @@ -251,6 +265,15 @@ def test_week_view_allow_future(self): self.assertEqual(res.status_code, 200) self.assertEqual(list(res.context['book_list']), [b]) + def test_week_view_paginated(self): + week_start = datetime.date(2008, 9, 28) + week_end = week_start + datetime.timedelta(days=7) + res = self.client.get('/dates/books/2008/week/39/') + self.assertEqual(res.status_code, 200) + self.assertEqual(list(res.context['book_list']), list(Book.objects.filter(pubdate__gte=week_start, pubdate__lt=week_end))) + self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__gte=week_start, pubdate__lt=week_end))) + self.assertTemplateUsed(res, 'generic_views/book_archive_week.html') + def test_week_view_invalid_pattern(self): res = self.client.get('/dates/books/2007/week/no_week/') self.assertEqual(res.status_code, 404) @@ -327,6 +350,13 @@ def test_day_view_allow_future(self): self.assertEqual(res.context['next_day'], future) self.assertEqual(res.context['previous_day'], datetime.date(2006, 5, 1)) + def test_day_view_paginated(self): + res = self.client.get('/dates/books/2008/oct/1/') + self.assertEqual(res.status_code, 200) + self.assertEqual(list(res.context['book_list']), list(Book.objects.filter(pubdate__year=2008, pubdate__month=10, pubdate__day=1))) + self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2008, pubdate__month=10, pubdate__day=1))) + self.assertTemplateUsed(res, 'generic_views/book_archive_day.html') + def test_next_prev_context(self): res = self.client.get('/dates/books/2008/oct/01/') self.assertEqual(res.content, "Archive for Oct. 1, 2008. Previous day is May 1, 2006") diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py index c06b5d8a23c4..067c1f687ad3 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -146,6 +146,8 @@ views.BookYearArchive.as_view(allow_empty=True)), (r'^dates/books/(?P\d{4})/allow_future/$', views.BookYearArchive.as_view(allow_future=True)), + (r'^dates/books/(?P\d{4})/paginated/$', + views.BookYearArchive.as_view(make_object_list=True, paginate_by=30)), (r'^dates/books/no_year/$', views.BookYearArchive.as_view()), @@ -158,6 +160,8 @@ views.BookMonthArchive.as_view(allow_empty=True)), (r'^dates/books/(?P\d{4})/(?P[a-z]{3})/allow_future/$', views.BookMonthArchive.as_view(allow_future=True)), + (r'^dates/books/(?P\d{4})/(?P[a-z]{3})/paginated/$', + views.BookMonthArchive.as_view(paginate_by=30)), (r'^dates/books/(?P\d{4})/no_month/$', views.BookMonthArchive.as_view()), @@ -168,6 +172,8 @@ views.BookWeekArchive.as_view(allow_empty=True)), (r'^dates/books/(?P\d{4})/week/(?P\d{1,2})/allow_future/$', views.BookWeekArchive.as_view(allow_future=True)), + (r'^dates/books/(?P\d{4})/week/(?P\d{1,2})/paginated/$', + views.BookWeekArchive.as_view(paginate_by=30)), (r'^dates/books/(?P\d{4})/week/no_week/$', views.BookWeekArchive.as_view()), (r'^dates/books/(?P\d{4})/week/(?P\d{1,2})/monday/$', @@ -182,6 +188,8 @@ views.BookDayArchive.as_view(allow_empty=True)), (r'^dates/books/(?P\d{4})/(?P[a-z]{3})/(?P\d{1,2})/allow_future/$', views.BookDayArchive.as_view(allow_future=True)), + (r'^dates/books/(?P\d{4})/(?P[a-z]{3})/(?P\d{1,2})/paginated/$', + views.BookDayArchive.as_view(paginate_by=True)), (r'^dates/books/(?P\d{4})/(?P[a-z]{3})/no_day/$', views.BookDayArchive.as_view()), From e87c9da4374389f5189637803819110593556196 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 22 Apr 2011 12:21:58 +0000 Subject: [PATCH 023/225] [1.3.X] Fixed #15672 -- Refined changes made in r15918. Thanks, vung. Backport from trunk (r16082). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16083 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/handlers/modpython.py | 3 +-- django/core/handlers/wsgi.py | 1 - tests/regressiontests/handlers/tests.py | 9 +++++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index 7b25f0e11e89..7cef32ccc95e 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -179,11 +179,10 @@ def __call__(self, req): try: request = self.request_class(req) except UnicodeDecodeError: - logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path, + logger.warning('Bad Request (UnicodeDecodeError)', exc_info=sys.exc_info(), extra={ 'status_code': 400, - 'request': request } ) response = http.HttpResponseBadRequest() diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 058f9c307f88..434f91ccf3b2 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -265,7 +265,6 @@ def __call__(self, environ, start_response): exc_info=sys.exc_info(), extra={ 'status_code': 400, - 'request': request } ) response = http.HttpResponseBadRequest() diff --git a/tests/regressiontests/handlers/tests.py b/tests/regressiontests/handlers/tests.py index 5e84f711770e..40b0a8375aba 100644 --- a/tests/regressiontests/handlers/tests.py +++ b/tests/regressiontests/handlers/tests.py @@ -1,6 +1,8 @@ from django.utils import unittest from django.conf import settings from django.core.handlers.wsgi import WSGIHandler +from django.test import RequestFactory + class HandlerTests(unittest.TestCase): @@ -23,3 +25,10 @@ def test_lock_safety(self): # Reset settings settings.MIDDLEWARE_CLASSES = old_middleware_classes + def test_bad_path_info(self): + """Tests for bug #15672 ('request' referenced before assignment)""" + environ = RequestFactory().get('/').environ + environ['PATH_INFO'] = '\xed' + handler = WSGIHandler() + response = handler(environ, lambda *a, **k: None) + self.assertEqual(response.status_code, 400) From 5977193c018c1aff38cc3f8139798b027742550a Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 22 Apr 2011 12:28:58 +0000 Subject: [PATCH 024/225] [1.3.X] Fixed #15758 -- Removed stale constants that were missed in r15983. Backport from trunk (r16084). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16085 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/fields.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/django/forms/fields.py b/django/forms/fields.py index 4d7728f58c62..ae6d8a41bc63 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -26,16 +26,14 @@ from django.core.validators import EMPTY_VALUES from util import ErrorList -from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, \ - ClearableFileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \ - DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, \ - FILE_INPUT_CONTRADICTION +from widgets import (TextInput, PasswordInput, HiddenInput, + MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select, + NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, + SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION) __all__ = ( 'Field', 'CharField', 'IntegerField', - 'DEFAULT_DATE_INPUT_FORMATS', 'DateField', - 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', - 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'TimeField', + 'DateField', 'TimeField', 'DateTimeField', 'TimeField', 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', @@ -54,10 +52,6 @@ def en_format(name): ) return getattr(formats, name) -DEFAULT_DATE_INPUT_FORMATS = lazy(lambda: en_format('DATE_INPUT_FORMATS'), tuple, list)() -DEFAULT_TIME_INPUT_FORMATS = lazy(lambda: en_format('TIME_INPUT_FORMATS'), tuple, list)() -DEFAULT_DATETIME_INPUT_FORMATS = lazy(lambda: en_format('DATETIME_INPUT_FORMATS'), tuple, list)() - class Field(object): widget = TextInput # Default widget to use when rendering this type of Field. hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". From 9269b606ba96d68c3b6be843539bfa6f443c72dc Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Fri, 22 Apr 2011 21:05:29 +0000 Subject: [PATCH 025/225] [1.3.X] Fixes regression #15721 -- {% include %} and RequestContext not working together. Refs #15814. Backport of r16031, plus the utility from r16030. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16089 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/context.py | 34 +++++++++---------- django/test/utils.py | 43 +++++++++++++++++++++++- tests/regressiontests/templates/tests.py | 31 +++++++++++++++++ 3 files changed, 88 insertions(+), 20 deletions(-) diff --git a/django/template/context.py b/django/template/context.py index bcfaa3bf9ec5..cb1c708738f5 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -14,21 +14,16 @@ class ContextPopException(Exception): "pop() has been called more times than push()" pass -class EmptyClass(object): - # No-op class which takes no args to its __init__ method, to help implement - # __copy__ - pass - class BaseContext(object): def __init__(self, dict_=None): - dict_ = dict_ or {} - self.dicts = [dict_] + self._reset_dicts(dict_) + + def _reset_dicts(self, value=None): + self.dicts = [value or {}] def __copy__(self): - duplicate = EmptyClass() - duplicate.__class__ = self.__class__ - duplicate.__dict__ = self.__dict__.copy() - duplicate.dicts = duplicate.dicts[:] + duplicate = copy(super(BaseContext, self)) + duplicate.dicts = self.dicts[:] return duplicate def __repr__(self): @@ -78,6 +73,15 @@ def get(self, key, otherwise=None): return d[key] return otherwise + def new(self, values=None): + """ + Returns a new context with the same properties, but with only the + values given in 'values' stored. + """ + new_context = copy(self) + new_context._reset_dicts(values) + return new_context + class Context(BaseContext): "A stack container for variable context" def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None): @@ -99,14 +103,6 @@ def update(self, other_dict): self.dicts.append(other_dict) return other_dict - def new(self, values=None): - """ - Returns a new Context with the same 'autoescape' value etc, but with - only the values given in 'values' stored. - """ - return self.__class__(dict_=values, autoescape=self.autoescape, - current_app=self.current_app, use_l10n=self.use_l10n) - class RenderContext(BaseContext): """ A stack container for storing Template state. diff --git a/django/test/utils.py b/django/test/utils.py index 90a8b3e88fa0..6a41c1b70f5b 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -6,12 +6,15 @@ from django.core import mail from django.core.mail.backends import locmem from django.test import signals -from django.template import Template +from django.template import Template, loader, TemplateDoesNotExist +from django.template.loaders import cached from django.utils.translation import deactivate __all__ = ('Approximate', 'ContextList', 'setup_test_environment', 'teardown_test_environment', 'get_runner') +RESTORE_LOADERS_ATTR = '_original_template_source_loaders' + class Approximate(object): def __init__(self, val, places=7): @@ -125,3 +128,41 @@ def get_runner(settings): test_module = __import__(test_module_name, {}, {}, test_path[-1]) test_runner = getattr(test_module, test_path[-1]) return test_runner + + +def setup_test_template_loader(templates_dict, use_cached_loader=False): + """ + Changes Django to only find templates from within a dictionary (where each + key is the template name and each value is the corresponding template + content to return). + + Use meth:`restore_template_loaders` to restore the original loaders. + """ + if hasattr(loader, RESTORE_LOADERS_ATTR): + raise Exception("loader.%s already exists" % RESTORE_LOADERS_ATTR) + + def test_template_loader(template_name, template_dirs=None): + "A custom template loader that loads templates from a dictionary." + try: + return (templates_dict[template_name], "test:%s" % template_name) + except KeyError: + raise TemplateDoesNotExist(template_name) + + if use_cached_loader: + template_loader = cached.Loader(('test_template_loader',)) + template_loader._cached_loaders = (test_template_loader,) + else: + template_loader = test_template_loader + + setattr(loader, RESTORE_LOADERS_ATTR, loader.template_source_loaders) + loader.template_source_loaders = (template_loader,) + return template_loader + + +def restore_template_loaders(): + """ + Restores the original template loaders after + :meth:`setup_test_template_loader` has been run. + """ + loader.template_source_loaders = getattr(loader, RESTORE_LOADERS_ATTR) + delattr(loader, RESTORE_LOADERS_ATTR) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 10c7a3725854..074852c1c3f2 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -17,6 +17,8 @@ from django.core import urlresolvers from django.template import loader from django.template.loaders import app_directories, filesystem, cached +from django.test.utils import setup_test_template_loader,\ + restore_template_loaders from django.utils import unittest from django.utils.translation import activate, deactivate, ugettext as _ from django.utils.safestring import mark_safe @@ -1640,5 +1642,34 @@ def test_load_working_egg(self): settings.INSTALLED_APPS = ('tagsegg',) t = template.Template(ttext) + +class RequestContextTests(BaseTemplateResponseTest): + + def setUp(self): + templates = { + 'child': Template('{{ var|default:"none" }}'), + } + setup_test_template_loader(templates) + self.fake_request = RequestFactory().get('/') + + def tearDown(self): + restore_template_loaders() + + def test_include_only(self): + """ + Regression test for #15721, ``{% include %}`` and ``RequestContext`` + not playing together nicely. + """ + ctx = RequestContext(self.fake_request, {'var': 'parent'}) + self.assertEqual( + template.Template('{% include "child" %}').render(ctx), + 'parent' + ) + self.assertEqual( + template.Template('{% include "child" only %}').render(ctx), + 'none' + ) + + if __name__ == "__main__": unittest.main() From fe2713dd5ec2d659cfcb0da79979638ddee9deb7 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Fri, 22 Apr 2011 21:25:16 +0000 Subject: [PATCH 026/225] [1.3.X] Fixes #15862 -- Error in post_syncdb documentation example. Thanks for the report and patch andialbrecht. Backport of r16091 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16092 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/signals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 1d4244f208f1..3ce519616b83 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -388,7 +388,7 @@ Arguments sent with this signal: For example, the :mod:`django.contrib.auth` app only prompts to create a superuser when ``interactive`` is ``True``. -For example, yourapp/signals/__init__.py could be written like:: +For example, ``yourapp/management/__init__.py`` could be written like:: from django.db.models.signals import post_syncdb import yourapp.models From 6a3d91828fd6959e8a3e6d944754bad37480bfee Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 23 Apr 2011 04:40:06 +0000 Subject: [PATCH 027/225] [1.3.X] Fixed #15819 - Fixed 1.3 regression from r15526 causing duplicate search results in admin with search_fields traversing to non-M2M related models. Thanks to Adam Kochanowski for the report and Ryan Kaskel for the patch. Backport of r16093 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16094 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/views/main.py | 14 +++- .../regressiontests/admin_changelist/tests.py | 84 +++++++++++++++---- 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 170d1687871f..0cfc43dd03b9 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -26,6 +26,15 @@ # Text to display within change-list table cells if the value is blank. EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)') +def field_needs_distinct(field): + if ((hasattr(field, 'rel') and + isinstance(field.rel, models.ManyToManyRel)) or + (isinstance(field, models.related.RelatedObject) and + not field.field.unique)): + return True + return False + + class ChangeList(object): def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin): self.model = model @@ -189,8 +198,7 @@ def get_query_set(self): f = self.lookup_opts.get_field_by_name(field_name)[0] except models.FieldDoesNotExist: raise IncorrectLookupParameters - if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel): - use_distinct = True + use_distinct = field_needs_distinct(f) # if key ends with __in, split parameter into separate values if key.endswith('__in'): @@ -264,7 +272,7 @@ def construct_search(field_name): for search_spec in orm_lookups: field_name = search_spec.split('__', 1)[0] f = self.lookup_opts.get_field_by_name(field_name)[0] - if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel): + if field_needs_distinct(f): use_distinct = True break diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py index 0e61addc118d..5186508dd9c7 100644 --- a/tests/regressiontests/admin_changelist/tests.py +++ b/tests/regressiontests/admin_changelist/tests.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.contrib.admin.options import IncorrectLookupParameters -from django.contrib.admin.views.main import ChangeList +from django.contrib.admin.views.main import ChangeList, SEARCH_VAR from django.core.paginator import Paginator from django.template import Context, Template from django.test import TransactionTestCase @@ -148,12 +148,14 @@ def test_distinct_for_m2m_in_list_filter(self): band.genres.add(blues) m = BandAdmin(Band, admin.site) - cl = ChangeList(MockFilteredRequestA(blues.pk), Band, m.list_display, + request = MockFilterRequest('genres', blues.pk) + + cl = ChangeList(request, Band, m.list_display, m.list_display_links, m.list_filter, m.date_hierarchy, m.search_fields, m.list_select_related, m.list_per_page, m.list_editable, m) - cl.get_results(MockFilteredRequestA(blues.pk)) + cl.get_results(request) # There's only one Group instance self.assertEqual(cl.result_count, 1) @@ -169,12 +171,14 @@ def test_distinct_for_through_m2m_in_list_filter(self): Membership.objects.create(group=band, music=lead, role='bass player') m = GroupAdmin(Group, admin.site) - cl = ChangeList(MockFilteredRequestB(lead.pk), Group, m.list_display, + request = MockFilterRequest('members', lead.pk) + + cl = ChangeList(request, Group, m.list_display, m.list_display_links, m.list_filter, m.date_hierarchy, m.search_fields, m.list_select_related, m.list_per_page, m.list_editable, m) - cl.get_results(MockFilteredRequestB(lead.pk)) + cl.get_results(request) # There's only one Group instance self.assertEqual(cl.result_count, 1) @@ -191,12 +195,14 @@ def test_distinct_for_inherited_m2m_in_list_filter(self): Membership.objects.create(group=four, music=lead, role='guitar player') m = QuartetAdmin(Quartet, admin.site) - cl = ChangeList(MockFilteredRequestB(lead.pk), Quartet, m.list_display, + request = MockFilterRequest('members', lead.pk) + + cl = ChangeList(request, Quartet, m.list_display, m.list_display_links, m.list_filter, m.date_hierarchy, m.search_fields, m.list_select_related, m.list_per_page, m.list_editable, m) - cl.get_results(MockFilteredRequestB(lead.pk)) + cl.get_results(request) # There's only one Quartet instance self.assertEqual(cl.result_count, 1) @@ -213,16 +219,59 @@ def test_distinct_for_m2m_to_inherited_in_list_filter(self): Invitation.objects.create(band=three, player=lead, instrument='bass') m = ChordsBandAdmin(ChordsBand, admin.site) - cl = ChangeList(MockFilteredRequestB(lead.pk), ChordsBand, m.list_display, + request = MockFilterRequest('members', lead.pk) + + cl = ChangeList(request, ChordsBand, m.list_display, m.list_display_links, m.list_filter, m.date_hierarchy, m.search_fields, m.list_select_related, m.list_per_page, m.list_editable, m) - cl.get_results(MockFilteredRequestB(lead.pk)) + cl.get_results(request) # There's only one ChordsBand instance self.assertEqual(cl.result_count, 1) + def test_distinct_for_non_unique_related_object_in_list_filter(self): + """ + Regressions tests for #15819: If a field listed in list_filters + is a non-unique related object, distinct() must be called. + """ + parent = Parent.objects.create(name='Mary') + # Two children with the same name + Child.objects.create(parent=parent, name='Daniel') + Child.objects.create(parent=parent, name='Daniel') + + m = ParentAdmin(Parent, admin.site) + request = MockFilterRequest('child__name', 'Daniel') + + cl = ChangeList(request, Parent, m.list_display, m.list_display_links, + m.list_filter, m.date_hierarchy, m.search_fields, + m.list_select_related, m.list_per_page, + m.list_editable, m) + + # Make sure distinct() was called + self.assertEqual(cl.query_set.count(), 1) + + def test_distinct_for_non_unique_related_object_in_search_fields(self): + """ + Regressions tests for #15819: If a field listed in search_fields + is a non-unique related object, distinct() must be called. + """ + parent = Parent.objects.create(name='Mary') + Child.objects.create(parent=parent, name='Danielle') + Child.objects.create(parent=parent, name='Daniel') + + m = ParentAdmin(Parent, admin.site) + request = MockSearchRequest('daniel') + + cl = ChangeList(request, Parent, m.list_display, m.list_display_links, + m.list_filter, m.date_hierarchy, m.search_fields, + m.list_select_related, m.list_per_page, + m.list_editable, m) + + # Make sure distinct() was called + self.assertEqual(cl.query_set.count(), 1) + def test_pagination(self): """ Regression tests for #12893: Pagination in admins changelist doesn't @@ -254,6 +303,11 @@ def test_pagination(self): self.assertEqual(cl.paginator.page_range, [1, 2, 3]) +class ParentAdmin(admin.ModelAdmin): + list_filter = ['child__name'] + search_fields = ['child__name'] + + class ChildAdmin(admin.ModelAdmin): list_display = ['name', 'parent'] list_per_page = 10 @@ -288,10 +342,10 @@ class QuartetAdmin(admin.ModelAdmin): class ChordsBandAdmin(admin.ModelAdmin): list_filter = ['members'] -class MockFilteredRequestA(object): - def __init__(self, pk): - self.GET = { 'genres' : pk } +class MockFilterRequest(object): + def __init__(self, filter, q): + self.GET = {filter: q} -class MockFilteredRequestB(object): - def __init__(self, pk): - self.GET = { 'members': pk } +class MockSearchRequest(object): + def __init__(self, q): + self.GET = {SEARCH_VAR: q} From f53fe1e79eef77837c07670286a5157074f6fcf9 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 23 Apr 2011 21:49:22 +0000 Subject: [PATCH 028/225] [1.3.X] Fixed #15875 - typo in F() example; thanks jblaine. Backport of r16096 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16097 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/queries.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 130f378c90f4..d6bc2c9aaed8 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -540,7 +540,7 @@ references can then be used in query filters to compare the values of two different fields on the same model instance. For example, to find a list of all blog entries that have had more comments -than pingbacks, we construct an ``F()`` object to reference the comment count, +than pingbacks, we construct an ``F()`` object to reference the pingback count, and use that ``F()`` object in the query:: >>> from django.db.models import F From 44dbac6482223d28509f4d8002d8e7bc9138ca1f Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 24 Apr 2011 23:47:36 +0000 Subject: [PATCH 029/225] [1.3.X] Fixed #15853 - typo in m2m_changed signal documentation; thanks elbarto. Backport of r16098 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16099 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/signals.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 3ce519616b83..e83142e79700 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -223,17 +223,17 @@ Arguments sent with this signal: This can be one of the following: ``"pre_add"`` - Sent *before* one or more objects are added to the relation + Sent *before* one or more objects are added to the relation. ``"post_add"`` - Sent *after* one or more objects are added to the relation + Sent *after* one or more objects are added to the relation. ``"pre_remove"`` - Sent *after* one or more objects are removed from the relation + Sent *before* one or more objects are removed from the relation. ``"post_remove"`` - Sent *after* one or more objects are removed from the relation + Sent *after* one or more objects are removed from the relation. ``"pre_clear"`` - Sent *before* the relation is cleared + Sent *before* the relation is cleared. ``"post_clear"`` - Sent *after* the relation is cleared + Sent *after* the relation is cleared. ``reverse`` Indicates which side of the relation is updated (i.e., if it is the From 637cf5de3a5f6666b6d4527b7dcb0c76dd5e0595 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Thu, 28 Apr 2011 00:23:42 +0000 Subject: [PATCH 030/225] [1.3.X] Fixed #15830 -- Add documentation regarding localflavor i18n. Thanks framos. Backport of r16109 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16110 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/localflavor.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index f54341ee6eae..fa92cb96f57c 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -121,6 +121,17 @@ Here's an example of how to use them:: .. _United States of America: `United States of America (us)`_ .. _Uruguay: `Uruguay (uy)`_ +Internationalization of localflavor +=================================== + +Localflavor has its own catalog of translations, in the directory +``django/contrib/localflavor/locale``, and it's not loaded automatically like +Django's general catalog in ``django/conf/locale``. If you want localflavor's +texts to be translated, like form fields error messages, you must include +:mod:`django.contrib.localflavor` in the :setting:`INSTALLED_APPS` setting, so +the internationalization system can find the catalog, as explained in +:ref:`using-translations-in-your-own-projects`. + Adding flavors ============== From 5aeeafba26e413eb36bca5a73240ce45ae4ca46f Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Thu, 28 Apr 2011 01:47:21 +0000 Subject: [PATCH 031/225] [1.3.X] Fixed #15865 -- correct class name for BaseGenericInlineFormset. Thanks leonelfreire for the report. Backport of r16113 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16114 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/contenttypes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index e232289385af..cafbaf4a6296 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -406,7 +406,7 @@ Generic relations in forms and admin ------------------------------------ The :mod:`django.contrib.contenttypes.generic` module provides -:class:`~django.contrib.contenttypes.generic.GenericInlineFormSet`, +:class:`~django.contrib.contenttypes.generic.BaseGenericInlineFormSet`, :class:`~django.contrib.contenttypes.generic.GenericTabularInline` and :class:`~django.contrib.contenttypes.generic.GenericStackedInline` (the last two are subclasses of From 0e9e0f0b21b667ec3d2889f7912d6a49dab102ea Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 30 Apr 2011 02:48:56 +0000 Subject: [PATCH 032/225] [1.3.X] Fixed small typos in custom template tags docs. Backport of [16126] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16127 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/custom-template-tags.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 7dc6ccef4f69..362d3580bfdb 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -627,13 +627,13 @@ resolve the string passed to it in the current context of the page. Shortcut for simple tags ~~~~~~~~~~~~~~~~~~~~~~~~ -Many template tags take a number of arguments -- strings or a template variables +Many template tags take a number of arguments -- strings or template variables -- and return a string after doing some processing based solely on -the input argument and some external information. For example, the +the input arguments and some external information. For example, the ``current_time`` tag we wrote above is of this variety: we give it a format string, it returns the time as a string. -To ease the creation of the types of tags, Django provides a helper function, +To ease the creation of these types of tags, Django provides a helper function, ``simple_tag``. This function, which is a method of ``django.template.Library``, takes a function that accepts any number of arguments, wraps it in a ``render`` function and the other necessary bits From c05dd20fa5896ac9e983415a60ce13fb0f028dc0 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 30 Apr 2011 13:02:47 +0000 Subject: [PATCH 033/225] [1.3.X] Fixed #15876 - Document that test.client.RequestFactory doesn't support sessions or request-altering middleware; thanks slinkp for the suggestion, ShawnMilo for the patch. Backport of r16128 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16129 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 41768576af26..e01c3e6155de 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1060,6 +1060,10 @@ restricted subset of the test client API: ``follows``. Since this is just a factory for producing requests, it's up to you to handle the response. + * It does not support middleware. Session and authentication + attributes must be supplied by the test itself if required + for the view to function properly. + Example ~~~~~~~ From b44757ce5187fd7019593b838adfd81dfd401b1d Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sat, 30 Apr 2011 13:37:53 +0000 Subject: [PATCH 034/225] [1.3.X] Fixed #6581 -- Moved documentation of django.contrib.auth.views.redirect_to_login to an own "Helper functions" section. Backport form trunk (r16130). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16131 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 52ddf22e7ba1..2106c16add96 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -990,25 +990,6 @@ includes a few other useful built-in views located in default to :file:`registration/password_reset_done.html` if not supplied. -.. function:: redirect_to_login(next[, login_url, redirect_field_name]) - - Redirects to the login page, and then back to another URL after a - successful login. - - **Required arguments:** - - * ``next``: The URL to redirect to after a successful login. - - **Optional arguments:** - - * ``login_url``: The URL of the login page to redirect to. This will - default to :setting:`settings.LOGIN_URL ` if not supplied. - - * ``redirect_field_name``: The name of a ``GET`` field containing the - URL to redirect to after log out. Overrides ``next`` if the given - ``GET`` parameter is passed. - - .. function:: password_reset_confirm(request[, uidb36, token, template_name, token_generator, set_password_form, post_reset_redirect]) Presents a form for entering a new password. @@ -1038,6 +1019,29 @@ includes a few other useful built-in views located in * ``template_name``: The full name of a template to display the view. This will default to :file:`registration/password_reset_complete.html`. +Helper functions +---------------- + +.. module:: django.contrib.auth.views + +.. function:: redirect_to_login(next[, login_url, redirect_field_name]) + + Redirects to the login page, and then back to another URL after a + successful login. + + **Required arguments:** + + * ``next``: The URL to redirect to after a successful login. + + **Optional arguments:** + + * ``login_url``: The URL of the login page to redirect to. This will + default to :setting:`settings.LOGIN_URL ` if not supplied. + + * ``redirect_field_name``: The name of a ``GET`` field containing the + URL to redirect to after log out. Overrides ``next`` if the given + ``GET`` parameter is passed. + Built-in forms -------------- From fa261ea432c606d58dfcb09dde50fb89b43f8dcb Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 1 May 2011 20:11:04 +0000 Subject: [PATCH 035/225] [1.3.X] Fixed #15887 - Added clarification for required_*() decorators; thanks RoySmith for the sugggestion. Backport of r16139 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16140 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/decorators.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/topics/http/decorators.txt b/docs/topics/http/decorators.txt index 09ffecbdf90c..946bcb900958 100644 --- a/docs/topics/http/decorators.txt +++ b/docs/topics/http/decorators.txt @@ -10,8 +10,9 @@ various HTTP features. Allowed HTTP methods ==================== -The following decorators in :mod:`django.views.decorators.http` can be used to -restrict access to views based on the request method. +The decorators in :mod:`django.views.decorators.http` can be used to restrict +access to views based on the request method. These decorators will return +a :class:`django.http.HttpResponseNotAllowed` if the conditions are not met. .. function:: require_http_methods(request_method_list) From 53354f80f679f3b52fc51832eca580a6c6211917 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Tue, 3 May 2011 10:25:50 +0000 Subject: [PATCH 036/225] [1.3.X] Fixed #15942 - removed duplicate module id in docs; thanks magopian. Backport of r16142 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16143 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 2106c16add96..d1194345ffd5 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1022,7 +1022,7 @@ includes a few other useful built-in views located in Helper functions ---------------- -.. module:: django.contrib.auth.views +.. currentmodule:: django.contrib.auth.views .. function:: redirect_to_login(next[, login_url, redirect_field_name]) From 64e625b6a15da6848bda75941e2a5d8e781ac889 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Wed, 4 May 2011 23:44:54 +0000 Subject: [PATCH 037/225] [1.3.X] Fixed #15827 - Documented that OneToOneField in Profile should be named 'user'; thanks lawgon. Backport of r16155 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16156 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index d1194345ffd5..d8e381b1a157 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -476,8 +476,8 @@ profile" -- for this purpose. To make use of this feature, define a model with fields for the additional information you'd like to store, or additional methods you'd like to have available, and also add a -:class:`~django.db.models.Field.OneToOneField` from your model to the -:class:`~django.contrib.auth.models.User` model. This will ensure only +:class:`~django.db.models.Field.OneToOneField` named ``user`` from your model +to the :class:`~django.contrib.auth.models.User` model. This will ensure only one instance of your model can be created for each :class:`~django.contrib.auth.models.User`. From d06531d3f01347f51c494b6275b89ff4607037b3 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Thu, 5 May 2011 23:12:55 +0000 Subject: [PATCH 038/225] [1.3.X] Fixes #15975 -- Test failure in model validation tests due to us now having https://www.djangoproject.com Backport of r16163 from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16164 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/validation/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py index cbd7cb8034e2..4236d8e7c401 100644 --- a/tests/modeltests/validation/tests.py +++ b/tests/modeltests/validation/tests.py @@ -58,11 +58,11 @@ def test_correct_url_but_nonexisting_gives_404(self): self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.']) def test_correct_url_value_passes(self): - mtv = ModelToValidate(number=10, name='Some Name', url='http://www.djangoproject.com/') + mtv = ModelToValidate(number=10, name='Some Name', url='http://www.example.com/') self.assertEqual(None, mtv.full_clean()) # This will fail if there's no Internet connection def test_correct_https_url_but_nonexisting(self): - mtv = ModelToValidate(number=10, name='Some Name', url='https://www.djangoproject.com/') + mtv = ModelToValidate(number=10, name='Some Name', url='https://www.example.com/') self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.']) def test_correct_ftp_url_but_nonexisting(self): From b3a46135955d0f1d3e3d39185ef977996c8cce9a Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 9 May 2011 15:45:10 +0000 Subject: [PATCH 039/225] [1.3.X] Fixed #15869 - example AJAX code in CSRF docs fails sometimes for IE7 or absolute same origin URLs Thanks to nick for the report. Backport of [16183] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16184 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/csrf.txt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index c28bd0319f96..31f377312d59 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -96,7 +96,7 @@ that allow headers to be set on every request. In jQuery, you can use the .. code-block:: javascript - $('html').ajaxSend(function(event, xhr, settings) { + $(document).ajaxSend(function(event, xhr, settings) { function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { @@ -112,8 +112,19 @@ that allow headers to be set on every request. In jQuery, you can use the } return cookieValue; } - if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { - // Only send the token to relative URLs i.e. locally. + function sameOrigin(url) { + // url could be relative or scheme relative or absolute + var host = document.location.host; // host + port + var protocol = document.location.protocol; + var sr_origin = '//' + host; + var origin = protocol + sr_origin; + // Allow absolute or scheme relative URLs to same origin + return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || + (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || + // or any other URL that isn't scheme relative or absolute i.e relative. + !(/^(\/\/|http:|https:).*/.test(url)); + } + if (sameOrigin(settings.url)) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } }); From fb052a15edbaf81a60c1d64b6088f2d4bdeec926 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 9 May 2011 21:37:52 +0000 Subject: [PATCH 040/225] [1.3.X] Fixed #15469 - CSRF token is inserted on GET requests Thanks to goran for report. Backport of [16191] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16193 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/csrf.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index 31f377312d59..7e9700dc3fdf 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -124,7 +124,11 @@ that allow headers to be set on every request. In jQuery, you can use the // or any other URL that isn't scheme relative or absolute i.e relative. !(/^(\/\/|http:|https:).*/.test(url)); } - if (sameOrigin(settings.url)) { + function safeMethod(method) { + return (method === 'GET' || method === 'HEAD'); + } + + if (!safeMethod(settings.type) && sameOrigin(settings.url)) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } }); From af1943f1394d2f59ab433cb1170a7d22d7743665 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Mon, 9 May 2011 22:31:07 +0000 Subject: [PATCH 041/225] [1.3.X] Fixed #15989 -- typo in static-files howto. Thanks luizvital. Backport of r16195 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16196 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/static-files.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 5657e24516af..e41060583edf 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -286,7 +286,7 @@ parameter. Since it can become a bit cumbersome to define this URL pattern, Django ships with a small URL helper function -:func:`~django.conf.urls.static.static` that taks as parameters the prefix +:func:`~django.conf.urls.static.static` that takes as parameters the prefix such as :setting:`MEDIA_URL` and a dotted path to a view, such as ``'django.views.static.serve'``. Any other function parameter will be transparently passed to the view. From fda65ffea57bf6f15a94f37c4ab74352f679e282 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 9 May 2011 23:47:50 +0000 Subject: [PATCH 042/225] [1.3.X] Updated AJAX example code in CSRF docs to be consistent regarding what are safe HTTP methods Backport of [16202] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16203 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/csrf.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index 7e9700dc3fdf..f3b95a11d37d 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -125,7 +125,7 @@ that allow headers to be set on every request. In jQuery, you can use the !(/^(\/\/|http:|https:).*/.test(url)); } function safeMethod(method) { - return (method === 'GET' || method === 'HEAD'); + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } if (!safeMethod(settings.type) && sameOrigin(settings.url)) { From 4cb2b53c222a8b02941d7bd32f012e62c1acd94f Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 10 May 2011 00:20:21 +0000 Subject: [PATCH 043/225] [1.3.X] Fixes #15588 -- 1.3 release documentation for FileField no longer deleting files unclear. Thanks for the patch, Philip Neustrom. Backport of r16205 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16206 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/releases/1.3.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 598f7413876b..64026ba82e04 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -358,19 +358,20 @@ issues`_ reported to us, however, query string lookup arguments in the admin must be for fields or relations specified in ``list_filter`` or ``date_hierarchy``. -FileField no longer deletes files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Deleting a model doesn't delete associated files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In earlier Django versions, when a model instance containing a :class:`~django.db.models.FileField` was deleted, :class:`~django.db.models.FileField` took it upon itself to also delete the file from the backend storage. This opened the door to several data-loss scenarios, including rolled-back transactions and fields on different models -referencing the same file. In Django 1.3, :class:`~django.db.models.FileField` -will never delete files from the backend storage. If you need cleanup of -orphaned files, you'll need to handle it yourself (for instance, with a custom -management command that can be run manually or scheduled to run periodically -via e.g. cron). +referencing the same file. In Django 1.3, when a model is deleted the +:class:`~django.db.models.FileField`'s +:func:`~django.db.models.FileField.delete` method won't be called. If you +need cleanup of orphaned files, you'll need to handle it yourself (for +instance, with a custom management command that can be run manually or +scheduled to run periodically via e.g. cron). PasswordInput default rendering behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 7fd113e6184c1602e4ad6d6d7a92c78a158e78ff Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 10 May 2011 00:28:05 +0000 Subject: [PATCH 044/225] [1.3.X] Fixes #15963 -- Misleading FileField.save documentation. Thanks for the report and patch, ejucovy. Backport of r16207 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16208 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/fields.txt | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 66bd04791bb9..2fb5d494b15a 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -607,9 +607,26 @@ instances on your model, the ``save()`` method is used to persist that file data. Takes two required arguments: ``name`` which is the name of the file, and -``content`` which is a file-like object containing the file's contents. The -optional ``save`` argument controls whether or not the instance is saved after -the file has been altered. Defaults to ``True``. +``content`` which is an object containing the file's contents. The +optional ``save`` argument controls whether or not the instance is +saved after the file has been altered. Defaults to ``True``. + +Note that the ``content`` argument should be an instance of +:class:`django.core.files.File`, not Python's built-in file object. +You can construct a :class:`~django.core.files.File` from an existing +Python file object like this:: + + from django.core.files import File + # Open an existing file using Python's built-in open() + f = open('/tmp/hello.world') + myfile = File(f) + +Or you can construct one from a Python string like this:: + + from django.core.files.base import ContentFile + myfile = ContentFile("hello world") + +For more information, see :doc:`/topics/files`. .. method:: FieldFile.delete(save=True) From 5c08cda6113cf65ca489385bdbece43cd8b1913c Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 10 May 2011 12:28:29 +0000 Subject: [PATCH 045/225] [1.3.X] Fixed #13648 - '%s' escaping support for sqlite3 regression. Thanks to master for the report and initial patch, and salgado and others for work on the patch. Backport of [16209] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16210 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/sqlite3/base.py | 2 +- tests/regressiontests/backends/tests.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 8344bad2a091..2de2818100c7 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -220,7 +220,7 @@ def close(self): if self.settings_dict['NAME'] != ":memory:": BaseDatabaseWrapper.close(self) -FORMAT_QMARK_REGEX = re.compile(r'(?![^%])%s') +FORMAT_QMARK_REGEX = re.compile(r'(? Date: Wed, 11 May 2011 21:30:05 +0000 Subject: [PATCH 046/225] [1.3.X] Fixes #15595 -- emphasize the benefits of django.test.TestCase. Thanks for the patch Shawn Milochik Backport of r16214 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16215 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index e01c3e6155de..317a33f695c3 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -36,7 +36,8 @@ two test frameworks that ship in the Python standard library. The two frameworks are: * **Unit tests** -- tests that are expressed as methods on a Python class - that subclasses ``unittest.TestCase``. For example:: + that subclasses ``unittest.TestCase`` or Django's customized + :class:`TestCase`. For example:: import unittest @@ -1102,7 +1103,16 @@ Converting a normal ``unittest.TestCase`` to a Django ``TestCase`` is 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. +additions, including: + + * Automatic loading of fixtures. + + * Wraps each test in a transaction. + + * Creates a TestClient instance. + + * Django-specific assertions for testing for things + like redirection and form errors. .. class:: TransactionTestCase() From fc391631779f2e4cd35b4eb6e03505b63e9ba7f7 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Fri, 13 May 2011 00:49:26 +0000 Subject: [PATCH 047/225] [1.3.X] Fixed #16005 -- Error in blocktrans docs -- thanks bezidejni Backport of r16218 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16219 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index edb8236fc27f..20680bdc8fe8 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -493,7 +493,7 @@ for use within the translation block. Examples:: If you need to bind more than one expression inside a ``blocktrans`` tag, separate the pieces with ``and``:: - {% blocktrans with book_t=book|title author_t=author|title %} + {% blocktrans with book_t=book|title and author_t=author|title %} This is {{ book_t }} by {{ author_t }} {% endblocktrans %} From 8385b31c8989e7f44d545b58a3904bdfd86ac8e8 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Fri, 13 May 2011 04:39:49 +0000 Subject: [PATCH 048/225] [1.3.X] Fixed #16014 -- numerous documentation typos -- thanks psmith. Backport of r16220 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16221 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/custom-management-commands.txt | 2 +- docs/howto/custom-template-tags.txt | 2 +- docs/howto/jython.txt | 2 +- docs/howto/static-files.txt | 6 +++--- docs/internals/contributing.txt | 2 +- docs/internals/deprecation.txt | 6 +++--- docs/internals/svn.txt | 2 +- docs/intro/tutorial01.txt | 2 +- docs/ref/forms/validation.txt | 2 +- docs/ref/models/querysets.txt | 2 +- docs/releases/1.3-beta-1.txt | 2 +- docs/releases/1.3.txt | 4 ++-- docs/topics/auth.txt | 4 ++-- docs/topics/cache.txt | 4 ++-- docs/topics/class-based-views.txt | 2 +- docs/topics/db/aggregation.txt | 2 +- docs/topics/db/models.txt | 6 +++--- docs/topics/db/optimization.txt | 6 +++--- docs/topics/db/transactions.txt | 2 +- docs/topics/http/shortcuts.txt | 2 +- docs/topics/logging.txt | 2 +- docs/topics/signals.txt | 2 +- docs/topics/testing.txt | 2 +- 23 files changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 309e5c12edfe..a844e328170c 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -71,7 +71,7 @@ The new custom command can be called using ``python manage.py closepoll ``. The ``handle()`` method takes zero or more ``poll_ids`` and sets ``poll.opened`` -to ``False`` for each one. If the user referenced any nonexistant polls, a +to ``False`` for each one. If the user referenced any nonexistent polls, a :class:`CommandError` is raised. The ``poll.opened`` attribute does not exist in the :doc:`tutorial` and was added to ``polls.models.Poll`` for this example. diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 362d3580bfdb..535f01bd3c56 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -829,7 +829,7 @@ Here's how you'd use this new version of the tag: .. admonition:: Variable scope in context Any variable set in the context will only be available in the same ``block`` - of the template in which it was assigned. This behaviour is intentional; + of the template in which it was assigned. This behavior is intentional; it provides a scope for variables so that they don't conflict with context in other blocks. diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 1bf8d6c1f455..9adacdf87f30 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -65,7 +65,7 @@ At this point, Django on Jython should behave nearly identically to Django running on standard Python. However, are a few differences to keep in mind: * Remember to use the ``jython`` command instead of ``python``. The - documentation uses ``python`` for consistancy, but if you're using Jython + documentation uses ``python`` for consistency, but if you're using Jython you'll want to mentally replace ``python`` with ``jython`` every time it occurs. diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index e41060583edf..633a663786dc 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -137,7 +137,7 @@ A far better way is to use the value of the :setting:`STATIC_URL` setting directly in your templates. This means that a switch of static files servers only requires changing that single value. Much better! -``staticfiles`` inludes two built-in ways of getting at this setting in your +``staticfiles`` includes two built-in ways of getting at this setting in your templates: a context processor and a template tag. With a context processor @@ -170,7 +170,7 @@ As a brief refresher, context processors add variables into the contexts of every template. However, context processors require that you use :class:`~django.template.RequestContext` when rendering templates. This happens automatically if you're using a :doc:`generic view `, -but in views written by hand you'll need to explicitally use ``RequestContext`` +but in views written by hand you'll need to explicitly use ``RequestContext`` To see how that works, and to read more details, check out :ref:`subclassing-context-requestcontext`. @@ -439,7 +439,7 @@ For example, if you've written an S3 storage backend in Once that's done, all you have to do is run :djadmin:`collectstatic` and your static files would be pushed through your storage package up to S3. If you -later needed to swich to a different storage provider, it could be as simple +later needed to switch to a different storage provider, it could be as simple as changing your :setting:`STATICFILES_STORAGE` setting. For details on how you'd write one of these backends, diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 33ee50a7b429..220ed5db6111 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -472,7 +472,7 @@ to do: * Then, click the "Join this Team" button to become a member of this team. Every team has at least one coordinator who is responsible to review your membership request. You can of course also contact the team - coordinator to clarify procedual problems and handle the actual + coordinator to clarify procedural problems and handle the actual translation process. * Once you are a member of a team choose the translation resource you diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 72152544f0e1..66866c2a30c7 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -20,7 +20,7 @@ their deprecation, as per the :ref:`Django deprecation policy * 1.4 * ``CsrfResponseMiddleware``. This has been deprecated since the 1.2 - release, in favour of the template tag method for inserting the CSRF + release, in favor of the template tag method for inserting the CSRF token. ``CsrfMiddleware``, which combines ``CsrfResponseMiddleware`` and ``CsrfViewMiddleware``, is also deprecated. @@ -126,7 +126,7 @@ their deprecation, as per the :ref:`Django deprecation policy * The undocumented function :func:`django.contrib.formtools.utils.security_hash` - is deprecated, in favour of :func:`django.contrib.formtools.utils.form_hmac` + is deprecated, in favor of :func:`django.contrib.formtools.utils.form_hmac` * The function-based generic views have been deprecated in favor of their class-based cousins. The following modules @@ -158,7 +158,7 @@ their deprecation, as per the :ref:`Django deprecation policy a :class:`~django.contrib.gis.geos.GEOSException` when called on a geometry with no SRID value. - * :class:`~django.http.CompatCookie` will be removed in favour of + * :class:`~django.http.CompatCookie` will be removed in favor of :class:`~django.http.SimpleCookie`. * :class:`django.core.context_processors.PermWrapper` and diff --git a/docs/internals/svn.txt b/docs/internals/svn.txt index 9efbe28913e1..58cabd1ba072 100644 --- a/docs/internals/svn.txt +++ b/docs/internals/svn.txt @@ -199,7 +199,7 @@ branch ``django/branches/releases/1.0.X`` was created to receive bug fixes, and shortly after the release of Django 1.1 the branch ``django/branches/releases/1.1.X`` was created. -Prior to the Django 1.0 release, these branches were maintaind within +Prior to the Django 1.0 release, these branches were maintained within the top-level ``django/branches`` directory, and so the following branches exist there and provided support for older Django releases: diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 29a2cb73fc36..bf9044e474ba 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -593,7 +593,7 @@ automatically-generated admin. Unicode string, and ``str(p)`` will return a normal string, with characters encoded as UTF-8. - If all of this is jibberish to you, just remember to add + If all of this is gibberish to you, just remember to add :meth:`~django.db.models.Model.__unicode__` methods to your models. With any luck, things should Just Work for you. diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index d5f40706a195..63c98aa484b1 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -361,6 +361,6 @@ considering aren't valid, we must remember to remove them from the ``cleaned_data``. In fact, Django will currently completely wipe out the ``cleaned_data`` -dictionary if there are any errors in the form. However, this behaviour may +dictionary if there are any errors in the form. However, this behavior may change in the future, so it's not a bad idea to clean up after yourself in the first place. diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index badcf38f5307..5cb3fd43d589 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1084,7 +1084,7 @@ If you have a field named ``defaults`` and want to use it as an exact lookup in Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'}) -The ``get_or_create()`` method has similar error behaviour to ``create()`` +The ``get_or_create()`` method has similar error behavior to ``create()`` when you are using manually specified primary keys. If an object needs to be created and the key already exists in the database, an ``IntegrityError`` will be raised. diff --git a/docs/releases/1.3-beta-1.txt b/docs/releases/1.3-beta-1.txt index 5d924f8d92f6..0210ada68971 100644 --- a/docs/releases/1.3-beta-1.txt +++ b/docs/releases/1.3-beta-1.txt @@ -27,7 +27,7 @@ The :mod:`~django.contrib.staticfiles` app ships with the ability to automatically serve static files during development (if the :setting:`DEBUG` setting is ``True``) when using the :djadmin:`runserver` management command. Based on feedback from the community this release adds two new options to the -:djadmin:`runserver` command to modify this behaviour: +:djadmin:`runserver` command to modify this behavior: * ``--nostatic``: prevents the :djadmin:`runserver` command from serving files completely. diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 64026ba82e04..34d615f5a026 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -521,7 +521,7 @@ Callables in templates Previously, a callable in a template would only be called automatically as part of the variable resolution process if it was retrieved via attribute lookup. This was an inconsistency that could result in confusing and unhelpful -behaviour:: +behavior:: >>> Template("{{ user.get_full_name }}").render(Context({'user': user})) u'Joe Bloggs' @@ -529,7 +529,7 @@ behaviour:: u'<bound method User.get_full_name of <... This has been resolved in Django 1.3 - the result in both cases will be ``u'Joe -Bloggs'``. Although the previous behaviour was not useful for a template language +Bloggs'``. Although the previous behavior was not useful for a template language designed for web designers, and was never deliberately supported, it is possible that some templates may be broken by this change. diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index d8e381b1a157..db7f18ccf3c4 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -292,7 +292,7 @@ Manager functions The :attr:`~django.contrib.auth.models.User.username` and :attr:`~django.contrib.auth.models.User.password` are set as given. The domain portion of :attr:`~django.contrib.auth.models.User.email` is - automatically convered to lowercase, and the returned + automatically converted to lowercase, and the returned :class:`~django.contrib.auth.models.User` object will have :attr:`~models.User.is_active` set to ``True``. @@ -1243,7 +1243,7 @@ To create custom permissions for a given model object, use the ``permissions`` :ref:`model Meta attribute `. This example Task model creates three custom permissions, i.e., actions users -can or cannot do with Task instances, specific to your appication:: +can or cannot do with Task instances, specific to your application:: class Task(models.Model): ... diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 4eb9d2153603..f08ee8c45963 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -615,7 +615,7 @@ If :setting:`USE_I18N` is set to ``True`` the per-site middleware cache will :ref:`respect the active language`. For the ``cache`` template tag you could use one of the :ref:`translation-specific variables` available in -templates to archieve the same result: +templates to achieve the same result: .. code-block:: html+django @@ -843,7 +843,7 @@ keys unaffected. Continuing our previous example:: # Version 2 isn't available, either >>> cache.get('my_key', version=2) None - # But version 3 *is* availble + # But version 3 *is* available >>> cache.get('my_key', version=3) 'hello world!' diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index 01d5f2fa654e..c59151a91a9f 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -72,7 +72,7 @@ so we can just subclass it, and override the template name:: Then, we just need to add this new view into our URLconf. As the class-based views themselves are classes, we point the URL to the as_view class method -instead, which is the entrypoint for class-based views:: +instead, which is the entry point for class-based views:: # urls.py from django.conf.urls.defaults import * diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index d6fb148988cb..8267c277780c 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -233,7 +233,7 @@ the first query will provide the total number of all books published by the publisher; the second query will only include good books in the annotated count. In the first query, the annotation precedes the filter, so the filter has no effect on the annotation. In the second query, the filter -preceeds the annotation, and as a result, the filter constrains the objects +precedes the annotation, and as a result, the filter constrains the objects considered when calculating the annotation. ``order_by()`` diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 58f51e31103d..0e18205e5dd6 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -188,7 +188,7 @@ ones: :: - >>> p = Person(name="Fred Flinstone", gender="M") + >>> p = Person(name="Fred Flintstone", gender="M") >>> p.save() >>> p.gender u'M' @@ -791,7 +791,7 @@ There are three styles of inheritance that are possible in Django. 2. If you're subclassing an existing model (perhaps something from another application entirely) and want each model to have its own database table, :ref:`multi-table-inheritance` is the way to go. - 3. Finally, if you only want to modify the Python-level behaviour of a model, + 3. Finally, if you only want to modify the Python-level behavior of a model, without changing the models fields in any way, you can use :ref:`proxy-models`. @@ -1218,7 +1218,7 @@ cannot create another model field called ``author`` in any class that inherits from that base class. Overriding fields in a parent model leads to difficulties in areas such as -initialising new instances (specifying which field is being initialized in +initializing new instances (specifying which field is being initialized in ``Model.__init__``) and serialization. These are features which normal Python class inheritance doesn't have to deal with in quite the same way, so the difference between Django model inheritance and Python class inheritance isn't diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 4c5a3894a4d7..265ef55faec8 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -93,13 +93,13 @@ caching. Use the ``with`` template tag ----------------------------- -To make use of the caching behaviour of ``QuerySet``, you may need to use the +To make use of the caching behavior of ``QuerySet``, you may need to use the :ttag:`with` template tag. Use ``iterator()`` ------------------ -When you have a lot of objects, the caching behaviour of the ``QuerySet`` can +When you have a lot of objects, the caching behavior of the ``QuerySet`` can cause a large amount of memory to be used. In this case, :meth:`~django.db.models.QuerySet.iterator()` may help. @@ -252,7 +252,7 @@ individual, use a bulk SQL UPDATE statement, via :ref:`QuerySet.update() Note, however, that these bulk update methods cannot call the ``save()`` or ``delete()`` methods of individual instances, which means that any custom -behaviour you have added for these methods will not be executed, including +behavior you have added for these methods will not be executed, including anything driven from the normal database object :doc:`signals `. Use foreign key values directly diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 70b11570e12e..b2539d8f065b 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -234,7 +234,7 @@ provide the savepoint functions, but they are empty operations - they won't actually do anything. Savepoints aren't especially useful if you are using the default -``autocommit`` behaviour of Django. However, if you are using +``autocommit`` behavior of Django. However, if you are using ``commit_on_success`` or ``commit_manually``, each open transaction will build up a series of database operations, awaiting a commit or rollback. If you issue a rollback, the entire transaction is rolled back. Savepoints provide diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 4a0fa58c4156..37ec98e165ff 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -4,7 +4,7 @@ Django shortcut functions .. module:: django.shortcuts :synopsis: - Convience shortcuts that spam multiple levels of Django's MVC stack. + Convenience shortcuts that spam multiple levels of Django's MVC stack. .. index:: shortcuts diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 09a725a00d7b..bad85ce784b8 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -156,7 +156,7 @@ and handle logging calls on a per-module basis. However, if you have some other way of organizing your logging messages, you can provide any dot-separated name to identify your logger:: - # Get an instance of a specfic named logger + # Get an instance of a specific named logger logger = logging.getLogger('project.interesting.stuff') The dotted paths of logger names define a hierarchy. The diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index e48e0f57f9a4..6373f1f619e4 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -105,7 +105,7 @@ must be able to handle those new arguments. Connecting receiver functions ----------------------------- -There are two ways you can connect a receiever to a signal. You can take the +There are two ways you can connect a receiver to a signal. You can take the manual connect route: .. code-block:: python diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 317a33f695c3..ffbffe7495d5 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -95,7 +95,7 @@ module defines tests in class-based approach. import unittest - If you want to continue to use the base unittest libary, you can -- + If you want to continue to use the base unittest library, you can -- you just won't get any of the nice new unittest2 features. .. _unittest2: http://pypi.python.org/pypi/unittest2 From 5be8fdb03e9912516614f0b4c5f8b51003c1b771 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 15 May 2011 19:12:29 +0000 Subject: [PATCH 049/225] [1.3.X] Fixed #15769 - Documented FormWizard's initial argument; thanks aimaz for the suggestion; jrothenbuhler for the patch. Backport of r16229 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16230 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/formtools/form-wizard.txt | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 15680029f3fd..041672840965 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -308,3 +308,28 @@ Advanced ``FormWizard`` methods def process_step(self, request, form, step): # ... + +Providing initial data for the forms +==================================== + +.. attribute:: FormWizard.initial + + Initial data for a wizard's :class:`~django.forms.Form` objects can be + provided using the optional :attr:`~FormWizard.initial` keyword argument. + This argument should be a dictionary mapping a step to a dictionary + containing the initial data for that step. The dictionary of initial data + will be passed along to the constructor of the step's + :class:`~django.forms.Form`:: + + >>> from testapp.forms import ContactForm1, ContactForm2, ContactWizard + >>> initial = { + ... 0: {'subject': 'Hello', 'sender': 'user@example.com'}, + ... 1: {'message': 'Hi there!'} + ... } + >>> wiz = ContactWizard([ContactForm1, ContactForm2], initial=initial) + >>> form1 = wiz.get_form(0) + >>> form2 = wiz.get_form(1) + >>> form1.initial + {'sender': 'user@example.com', 'subject': 'Hello'} + >>> form2.initial + {'message': 'Hi there!'} From e1dfa95cd1619ac02cc010c3d3b1ab6b6ad85063 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 18 May 2011 09:52:44 +0000 Subject: [PATCH 050/225] [1.3.X] Fixed #15983 and #16032 -- Another pass over the staticfiles docs. Many thanks to Frank Wiles and EvilDMP. Backport form trunk (r16235). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16236 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/static-files.txt | 108 ++++++++++++++++--------------- docs/ref/contrib/staticfiles.txt | 2 +- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 633a663786dc..2851d070cc86 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -34,85 +34,91 @@ single location that can easily be served in production. Using ``django.contrib.staticfiles`` ==================================== -Here's the basic usage in a nutshell: +Basic usage +----------- - 1. Put your static files somewhere that ``staticfiles`` will find them. +1. Put your static files somewhere that ``staticfiles`` will find them. - By default, this means within ``static/`` subdirectories of apps in your - :setting:`INSTALLED_APPS`. + By default, this means within ``static/`` subdirectories of apps in your + :setting:`INSTALLED_APPS`. - Many projects will also have static assets that aren't tied to a - particular app; you can give ``staticfiles`` additional directories to - search via the :setting:`STATICFILES_DIRS` setting . + Your project will probably also have static assets that aren't tied to a + particular app. The :setting:`STATICFILES_DIRS` setting is a tuple of + filesystem directories to check when loading static files. Iv's a search + path that is by default empty. See the :setting:`STATICFILES_DIRS` docs + how to extend this list of additional paths. - See the documentation for the :setting:`STATICFILES_FINDERS` setting for - details on how ``staticfiles`` finds your files. + Additionally, see the documentation for the :setting:`STATICFILES_FINDERS` + setting for details on how ``staticfiles`` finds your files. - 2. Set the :setting:`STATIC_URL` setting to the URL you want to use - for pointing to your static files, e.g.:: +2. Make sure that ``django.contrib.staticfiles`` is included in your + :setting:`INSTALLED_APPS`. - STATIC_URL = '/static/' + For :ref:`local development`, if you are using + :ref:`runserver` or adding + :ref:`staticfiles_urlpatterns` to your + URLconf, you're done with the setup -- your static files will + automatically be served at the default (for + :djadmin:`newly created` projectq) :setting:`STATIC_URL` + of ``/static/``. - In projects freshly created with the :djadmin:`startproject` - management command this will be preset to ``'/static/'``. +3. You'll probably need to refer to these files in your templates. The + easiest method is to use the included context processor which allows + template code like: - 3. Make sure that ``django.contrib.staticfiles`` is in your - :setting:`INSTALLED_APPS`. + .. code-block:: html+django - For :ref:`local development`, if you are using - :ref:`runserver` or adding - :ref:`staticfiles_urlpatterns` to your URLconf, - you're done! Your static files will automatically be served at the - :setting:`STATIC_URL` you specified in step 2. + - - See :ref:`staticfiles-in-templates` for more details, including an - alternate method (using a template tag). +Deploying static files in a nutshell +------------------------------------ When you're ready to move out of local development and deploy your project: - 1. Set the :setting:`STATIC_ROOT` setting to point to where you'd like your - static files collected to when you use the :djadmin:`collectstatic` - management command. For example:: +1. Set the :setting:`STATIC_URL` setting to the public URL for your static + files (in most cases, the default value of ``/static/`` is just fine). - STATIC_ROOT = "/home/jacob/projects/mysite.com/sitestatic" +2. Set the :setting:`STATIC_ROOT` setting to point to the filesystem path + you'd like your static files collected to when you use the + :djadmin:`collectstatic` management command. For example:: - 2. Run the :djadmin:`collectstatic` management command:: + STATIC_ROOT = "/home/jacob/projects/mysite.com/sitestatic" - ./manage.py collectstatic +3. Run the :djadmin:`collectstatic` management command:: - This'll churn through your static file storage and copy them into the - directory given by :setting:`STATIC_ROOT`. + ./manage.py collectstatic - 3. Deploy those files by configuring your webserver of choice to serve the - files in :setting:`STATIC_ROOT` at :setting:`STATIC_URL`. + This'll churn through your static file storage and copy them into the + directory given by :setting:`STATIC_ROOT`. - :ref:`staticfiles-production` covers some common deployment strategies - for static files. +4. Deploy those files by configuring your webserver of choice to serve the + files in :setting:`STATIC_ROOT` at :setting:`STATIC_URL`. -Those are the basics. For more details on common configuration options, read on; -for a detailed reference of the settings, commands, and other bits included with -the framework see :doc:`the staticfiles reference `. + :ref:`staticfiles-production` covers some common deployment strategies + for static files. + +Those are the **basics**. For more details on common configuration options, +read on; for a detailed reference of the settings, commands, and other bits +included with the framework see +:doc:`the staticfiles reference `. .. note:: In previous versions of Django, it was common to place static assets in - :setting:`MEDIA_ROOT` along with user-uploaded files, and serve them both at - :setting:`MEDIA_URL`. Part of the purpose of introducing the ``staticfiles`` - app is to make it easier to keep static files separate from user-uploaded - files. For this reason, you need to make your :setting:`MEDIA_ROOT` and + :setting:`MEDIA_ROOT` along with user-uploaded files, and serve them both + at :setting:`MEDIA_URL`. Part of the purpose of introducing the + ``staticfiles`` app is to make it easier to keep static files separate + from user-uploaded files. + + For this reason, you need to make your :setting:`MEDIA_ROOT` and :setting:`MEDIA_URL` different from your :setting:`STATIC_ROOT` and :setting:`STATIC_URL`. You will need to arrange for serving of files in :setting:`MEDIA_ROOT` yourself; ``staticfiles`` does not deal with user-uploaded files at all. You can, however, use - :func:`~django.views.static.serve` view for serving :setting:`MEDIA_ROOT` + :func:`django.views.static.serve` view for serving :setting:`MEDIA_ROOT` in development; see :ref:`staticfiles-other-directories`. .. _staticfiles-in-templates: @@ -303,7 +309,7 @@ development:: .. note:: - The helper function will only be operational in debug mode and if + This helper function will only be operational in debug mode and if the given prefix is local (e.g. ``/static/``) and not a URL (e.g. ``http://static.example.com/``). diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index b06620d0854e..eb8bbec105e9 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -296,7 +296,7 @@ primary URL configuration:: url(r'^static/(?P.*)$', 'serve'), ) -Note, the begin of the pattern (``r'^static/'``) should be your +Note, the beginning of the pattern (``r'^static/'``) should be your :setting:`STATIC_URL` setting. Since this is a bit finicky, there's also a helper function that'll do this for you: From 49f4a28cce1a3325c6c0182005cc7daf2959bc82 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Wed, 18 May 2011 20:12:15 +0000 Subject: [PATCH 051/225] [1.3.X] Fixed #16044 -- Corrected a copy-and-paste error in the opening paragraph of the views topic guide. Thanks to aplanas for the report and suggested wording. Backport of [16240] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16241 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/views.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index 562be533ab06..cfdd00817b86 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -8,9 +8,9 @@ of a Web page, or a redirect, or a 404 error, or an XML document, or an image . . . or anything, really. The view itself contains whatever arbitrary logic is necessary to return that response. This code can live anywhere you want, as long as it's on your Python path. There's no other requirement--no "magic," so to -speak. For the sake of putting the code *somewhere*, let's create a file called -``views.py`` in the ``mysite`` directory, which you created in the previous -chapter. +speak. For the sake of putting the code *somewhere*, the convention is to +put views in a file called ``views.py``, placed in your project or +application directory. A simple view ============= @@ -47,7 +47,7 @@ Let's step through this code one line at a time: exceptions, but we'll get to those later.) .. admonition:: Django's Time Zone - + Django includes a ``TIME_ZONE`` setting that defaults to ``America/Chicago``. This probably isn't where you live, so you might want to change it in your settings file. From d12ac6d048e52a253f0efd5bad94377807f3343c Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Wed, 18 May 2011 20:35:32 +0000 Subject: [PATCH 052/225] git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16243 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- 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 37ec98e165ff..3a43fe12fb5c 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -89,7 +89,7 @@ This example is equivalent to:: ``render_to_response`` ====================== -.. function:: render_to_response(template[, dictionary][, context_instance][, mimetype]) +.. function:: render_to_response(template_name[, dictionary][, context_instance][, mimetype]) Renders a given template with a given context dictionary and returns an :class:`~django.http.HttpResponse` object with that rendered text. @@ -97,7 +97,7 @@ This example is equivalent to:: Required arguments ------------------ -``template`` +``template_name`` The full name of a template to use or sequence of template names. If a sequence is given, the first template that exists will be used. See the :ref:`template loader documentation ` From 770b91ca7bef42c29481320b4276ad7f38bdf87f Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Thu, 19 May 2011 02:51:39 +0000 Subject: [PATCH 053/225] [1.3.X] Fixed small typos in staticfiles howto document. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16244 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/static-files.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 2851d070cc86..215b927f6f73 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -44,7 +44,7 @@ Basic usage Your project will probably also have static assets that aren't tied to a particular app. The :setting:`STATICFILES_DIRS` setting is a tuple of - filesystem directories to check when loading static files. Iv's a search + filesystem directories to check when loading static files. It's a search path that is by default empty. See the :setting:`STATICFILES_DIRS` docs how to extend this list of additional paths. @@ -59,7 +59,7 @@ Basic usage :ref:`staticfiles_urlpatterns` to your URLconf, you're done with the setup -- your static files will automatically be served at the default (for - :djadmin:`newly created` projectq) :setting:`STATIC_URL` + :djadmin:`newly created` projects) :setting:`STATIC_URL` of ``/static/``. 3. You'll probably need to refer to these files in your templates. The From be776521dbe535497223b314c4d58874f1ae6992 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Fri, 20 May 2011 00:52:36 +0000 Subject: [PATCH 054/225] [1.3.X] Tidy up the sessions documentation creating links for session methods and crosslinking settings Backport of r16245 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16246 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/sessions.txt | 120 ++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index af27d542572c..1fd6a5c031fd 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -17,14 +17,15 @@ Sessions are implemented via a piece of :doc:`middleware `. To enable session functionality, do the following: - * Edit the ``MIDDLEWARE_CLASSES`` setting and make sure - ``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``. - The default ``settings.py`` created by ``django-admin.py startproject`` has - ``SessionMiddleware`` activated. + * Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure + it contains ``'django.contrib.sessions.middleware.SessionMiddleware'``. + The default ``settings.py`` created by ``django-admin.py startproject`` + has ``SessionMiddleware`` activated. If you don't want to use sessions, you might as well remove the -``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'`` -from your ``INSTALLED_APPS``. It'll save you a small bit of overhead. +``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and +``'django.contrib.sessions'`` from your :setting:`INSTALLED_APPS`. +It'll save you a small bit of overhead. Configuring the session engine ============================== @@ -86,56 +87,62 @@ configuration instructions for the `using database-backed sessions`_. Using file-based sessions ------------------------- -To use file-based sessions, set the ``SESSION_ENGINE`` setting to +To use file-based sessions, set the :setting:`SESSION_ENGINE` setting to ``"django.contrib.sessions.backends.file"``. -You might also want to set the ``SESSION_FILE_PATH`` setting (which defaults -to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to control -where Django stores session files. Be sure to check that your Web server has -permissions to read and write to this location. +You might also want to set the :setting:`SESSION_FILE_PATH` setting (which +defaults to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to +control where Django stores session files. Be sure to check that your Web +server has permissions to read and write to this location. Using sessions in views ======================= -When ``SessionMiddleware`` is activated, each ``HttpRequest`` object -- the -first argument to any Django view function -- will have a ``session`` -attribute, which is a dictionary-like object. You can read it and write to it. +When ``SessionMiddleware`` is activated, each :class:`~django.http.HttpRequest` +object -- the first argument to any Django view function -- will have a +``session`` attribute, which is a dictionary-like object. -A session object has the following standard dictionary methods: +You can read it and write to ``request.session`` at any point in your view. +You can edit it multiple times. - * ``__getitem__(key)`` +.. class:: backends.base.SessionBase + + This is the base class for all session objects. It has the following + standard dictionary methods: + + .. method:: __getitem__(key) Example: ``fav_color = request.session['fav_color']`` - * ``__setitem__(key, value)`` + .. method:: __setitem__(key, value) Example: ``request.session['fav_color'] = 'blue'`` - * ``__delitem__(key)`` + .. method:: __delitem__(key) Example: ``del request.session['fav_color']``. This raises ``KeyError`` if the given ``key`` isn't already in the session. - * ``__contains__(key)`` + .. method:: __contains__(key) Example: ``'fav_color' in request.session`` - * ``get(key, default=None)`` + .. method:: get(key, default=None) Example: ``fav_color = request.session.get('fav_color', 'red')`` - * ``keys()`` + .. method:: keys - * ``items()`` + .. method:: items - * ``setdefault()`` + .. method:: setdefault - * ``clear()`` + .. method:: clear -It also has these methods: + It also has these methods: - * ``flush()`` + .. method:: flush Delete the current session data from the session and regenerate the session key value that is sent back to the user in the cookie. This is @@ -143,25 +150,25 @@ It also has these methods: accessed again from the user's browser (for example, the :func:`django.contrib.auth.logout()` function calls it). - * ``set_test_cookie()`` + .. method:: set_test_cookie Sets a test cookie to determine whether the user's browser supports cookies. Due to the way cookies work, you won't be able to test this until the user's next page request. See `Setting test cookies`_ below for more information. - * ``test_cookie_worked()`` + .. method:: test_cookie_worked Returns either ``True`` or ``False``, depending on whether the user's browser accepted the test cookie. Due to the way cookies work, you'll have to call ``set_test_cookie()`` on a previous, separate page request. See `Setting test cookies`_ below for more information. - * ``delete_test_cookie()`` + .. method:: delete_test_cookie Deletes the test cookie. Use this to clean up after yourself. - * ``set_expiry(value)`` + .. method:: set_expiry(value) Sets the expiration time for the session. You can pass a number of different values: @@ -184,26 +191,23 @@ It also has these methods: purposes. Session expiration is computed from the last time the session was *modified*. - * ``get_expiry_age()`` + .. method:: get_expiry_age Returns the number of seconds until this session expires. For sessions with no custom expiration (or those set to expire at browser close), this - will equal ``settings.SESSION_COOKIE_AGE``. + will equal :setting:`SESSION_COOKIE_AGE`. - * ``get_expiry_date()`` + .. method:: get_expiry_date Returns the date this session will expire. For sessions with no custom expiration (or those set to expire at browser close), this will equal the - date ``settings.SESSION_COOKIE_AGE`` seconds from now. + date :setting:`SESSION_COOKIE_AGE` seconds from now. - * ``get_expire_at_browser_close()`` + .. method:: get_expire_at_browser_close Returns either ``True`` or ``False``, depending on whether the user's session cookie will expire when the user's Web browser is closed. -You can edit ``request.session`` at any point in your view. You can edit it -multiple times. - Session object guidelines ------------------------- @@ -249,25 +253,29 @@ This simplistic view logs in a "member" of the site:: pass return HttpResponse("You're logged out.") -The standard ``django.contrib.auth.logout()`` function actually does a bit -more than this to prevent inadvertent data leakage. It calls -``request.session.flush()``. We are using this example as a demonstration of -how to work with session objects, not as a full ``logout()`` implementation. +The standard :meth:`django.contrib.auth.logout` function actually does a bit +more than this to prevent inadvertent data leakage. It calls the +:meth:`~backends.base.SessionBase.flush` method of ``request.session``. +We are using this example as a demonstration of how to work with session +objects, not as a full ``logout()`` implementation. Setting test cookies ==================== As a convenience, Django provides an easy way to test whether the user's -browser accepts cookies. Just call ``request.session.set_test_cookie()`` in a -view, and call ``request.session.test_cookie_worked()`` in a subsequent view -- +browser accepts cookies. Just call the +:meth:`~backends.base.SessionBase.set_test_cookie` method of +``request.session`` in a view, and call +:meth:`~backends.base.SessionBase.test_cookie_worked` in a subsequent view -- not in the same view call. This awkward split between ``set_test_cookie()`` and ``test_cookie_worked()`` is necessary due to the way cookies work. When you set a cookie, you can't actually tell whether a browser accepted it until the browser's next request. -It's good practice to use ``delete_test_cookie()`` to clean up after yourself. -Do this after you've verified that the test cookie worked. +It's good practice to use +:meth:`~backends.base.SessionBase.delete_test_cookie()` to clean up after +yourself. Do this after you've verified that the test cookie worked. Here's a typical usage example:: @@ -346,9 +354,9 @@ the session object:: request.session.modified = True -To change this default behavior, set the ``SESSION_SAVE_EVERY_REQUEST`` setting -to ``True``. If ``SESSION_SAVE_EVERY_REQUEST`` is ``True``, Django will save -the session to the database on every single request. +To change this default behavior, set the :setting:`SESSION_SAVE_EVERY_REQUEST` +setting to ``True``. When set to ``True``, Django will save the session to the +database on every single request. Note that the session cookie is only sent when a session has been created or modified. If ``SESSION_SAVE_EVERY_REQUEST`` is ``True``, the session cookie @@ -361,12 +369,13 @@ Browser-length sessions vs. persistent sessions =============================================== You can control whether the session framework uses browser-length sessions vs. -persistent sessions with the ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` setting. +persistent sessions with the :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` +setting. By default, ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``False``, which means session cookies will be stored in users' browsers for as long as -``SESSION_COOKIE_AGE``. Use this if you don't want people to have to log in -every time they open a browser. +:setting:`SESSION_COOKIE_AGE`. Use this if you don't want people to have to +log in every time they open a browser. If ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``True``, Django will use browser-length cookies -- cookies that expire as soon as the user closes his or @@ -374,8 +383,8 @@ her browser. Use this if you want people to have to log in every time they open a browser. This setting is a global default and can be overwritten at a per-session level -by explicitly calling ``request.session.set_expiry()`` as described above in -`using sessions in views`_. +by explicitly calling the :meth:`~backends.base.SessionBase.set_expiry` method +of ``request.session`` as described above in `using sessions in views`_. Clearing the session table ========================== @@ -397,7 +406,8 @@ in the past -- but your application may have different requirements. Settings ======== -A few :doc:`Django settings ` give you control over session behavior: +A few :doc:`Django settings ` give you control over session +behavior: SESSION_ENGINE -------------- From b5c6b4f1d4603eee15bd426a5c5b0594c4d2e4f7 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Fri, 20 May 2011 01:48:41 +0000 Subject: [PATCH 055/225] [1.3.X] Tweaks to paginator documentation. Backport of 16248 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16249 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/pagination.txt | 49 +++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt index db776aaf00dc..6b9089b5d9fd 100644 --- a/docs/topics/pagination.txt +++ b/docs/topics/pagination.txt @@ -62,8 +62,8 @@ page:: .. note:: - Note that you can give ``Paginator`` a list/tuple, a Django ``QuerySet``, or - any other object with a ``count()`` or ``__len__()`` method. When + Note that you can give ``Paginator`` a list/tuple, a Django ``QuerySet``, + or any other object with a ``count()`` or ``__len__()`` method. When determining the number of objects contained in the passed object, ``Paginator`` will first try calling ``count()``, then fallback to using ``len()`` if the passed object has no ``count()`` method. This allows @@ -182,9 +182,9 @@ Attributes When determining the number of objects contained in ``object_list``, ``Paginator`` will first try calling ``object_list.count()``. If ``object_list`` has no ``count()`` method, then ``Paginator`` will - fallback to using ``object_list.__len__()``. This allows objects, such - as Django's ``QuerySet``, to use a more efficient ``count()`` method - when available. + fallback to using ``len(object_list)``. This allows objects, such as + Django's ``QuerySet``, to use a more efficient ``count()`` method when + available. .. attribute:: Paginator.num_pages @@ -197,30 +197,36 @@ Attributes ``InvalidPage`` exceptions ========================== -The ``page()`` method raises ``InvalidPage`` if the requested page is invalid -(i.e., not an integer) or contains no objects. Generally, it's enough to trap -the ``InvalidPage`` exception, but if you'd like more granularity, you can trap -either of the following exceptions: +.. exception:: InvalidPage + + A base class for exceptions raised when a paginator is passed an invalid + page number. + +The :meth:`Paginator.page` method raises an exception if the requested page is +invalid (i.e., not an integer) or contains no objects. Generally, it's enough +to trap the ``InvalidPage`` exception, but if you'd like more granularity, you +can trap either of the following exceptions: + +.. exception:: PageNotAnInteger -``PageNotAnInteger`` Raised when ``page()`` is given a value that isn't an integer. -``EmptyPage`` +.. exception:: EmptyPage + Raised when ``page()`` is given a valid value but no objects exist on that page. -Both of the exceptions are subclasses of ``InvalidPage``, so you can handle +Both of the exceptions are subclasses of :exc:`InvalidPage`, so you can handle them both with a simple ``except InvalidPage``. ``Page`` objects ================ -.. class:: Page(object_list, number, paginator) - -You usually won't construct :class:`Pages ` by hand -- you'll get them +You usually won't construct ``Page`` objects by hand -- you'll get them using :meth:`Paginator.page`. +.. class:: Page(object_list, number, paginator) Methods ------- @@ -251,15 +257,15 @@ Methods Returns the 1-based index of the first object on the page, relative to all of the objects in the paginator's list. For example, when paginating a list - of 5 objects with 2 objects per page, the second page's :meth:`~Page.start_index` - would return ``3``. + of 5 objects with 2 objects per page, the second page's + :meth:`~Page.start_index` would return ``3``. .. method:: Page.end_index() - Returns the 1-based index of the last object on the page, relative to all of - the objects in the paginator's list. For example, when paginating a list of - 5 objects with 2 objects per page, the second page's :meth:`~Page.end_index` - would return ``4``. + Returns the 1-based index of the last object on the page, relative to all + of the objects in the paginator's list. For example, when paginating a list + of 5 objects with 2 objects per page, the second page's + :meth:`~Page.end_index` would return ``4``. Attributes ---------- @@ -275,4 +281,3 @@ Attributes .. attribute:: Page.paginator The associated :class:`Paginator` object. - From 08f5ac3d51c2e4b9ddf28a1bae640c1970decd8c Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 22 May 2011 00:08:40 +0000 Subject: [PATCH 056/225] [1.3.X] Fixed #16021 - Minor documentation fixes for Generic Class Views; thanks Bradley Ayers. Backport of r16256 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16257 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/class-based-views.txt | 12 ++++++------ docs/topics/class-based-views.txt | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/ref/class-based-views.txt b/docs/ref/class-based-views.txt index 21bb58c9b615..79ff235f2ab2 100644 --- a/docs/ref/class-based-views.txt +++ b/docs/ref/class-based-views.txt @@ -81,20 +81,20 @@ TemplateResponseMixin The response class to be returned by ``render_to_response`` method. Default is :class:`TemplateResponse `. - The template and context of TemplateResponse instances can be + The template and context of ``TemplateResponse`` instances can be altered later (e.g. in :ref:`template response middleware `). - Create TemplateResponse subclass and pass set it to - ``template_response_class`` if you need custom template loading or - custom context object instantiation. + If you need custom template loading or custom context object + instantiation, create a ``TemplateResponse`` subclass and assign it to + ``response_class``. .. method:: render_to_response(context, **response_kwargs) - Returns a ``self.template_response_class`` instance. + Returns a ``self.response_class`` instance. If any keyword arguments are provided, they will be - passed to the constructor of the response instance. + passed to the constructor of the response class. Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the list of template names that will be searched looking for an existent diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index c59151a91a9f..0b1a9235354e 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -71,7 +71,7 @@ so we can just subclass it, and override the template name:: template_name = "about.html" Then, we just need to add this new view into our URLconf. As the class-based -views themselves are classes, we point the URL to the as_view class method +views themselves are classes, we point the URL to the ``as_view`` class method instead, which is the entry point for class-based views:: # urls.py @@ -83,7 +83,7 @@ instead, which is the entry point for class-based views:: ) Alternatively, if you're only changing a few simple attributes on a -class-based view, you can simply pass the new attributes into the as_view +class-based view, you can simply pass the new attributes into the ``as_view`` method call itself:: from django.conf.urls.defaults import * @@ -121,12 +121,12 @@ be using these models:: country = models.CharField(max_length=50) website = models.URLField() - def __unicode__(self): - return self.name - class Meta: ordering = ["-name"] + def __unicode__(self): + return self.name + class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField('Author') @@ -211,7 +211,7 @@ all the publishers in a variable named ``object_list``. While this works just fine, it isn't all that "friendly" to template authors: they have to "just know" that they're dealing with publishers here. -Well, if you're dealing with a Django object, this is already done for +Well, if you're dealing with a model object, this is already done for you. When you are dealing with an object or queryset, Django is able to populate the context using the verbose name (or the plural verbose name, in the case of a list of objects) of the object being displayed. @@ -353,7 +353,7 @@ key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but what if we wanted to write a view that displayed all the books by some arbitrary publisher? -Handily, the ListView has a +Handily, the ``ListView`` has a :meth:`~django.views.generic.detail.ListView.get_queryset` method we can override. Previously, it has just been returning the value of the ``queryset`` attribute, but now we can add more logic. @@ -444,8 +444,8 @@ custom view: **(r'^authors/(?P\\d+)/$', AuthorDetailView.as_view()),** ) -Then we'd write our new view - ``get_object`` is the method that retrieves the -object, so we simply override it and wrap the call:: +Then we'd write our new view -- ``get_object`` is the method that retrieves the +object -- so we simply override it and wrap the call:: import datetime from books.models import Author @@ -473,7 +473,7 @@ object, so we simply override it and wrap the call:: .. note:: The URLconf here uses the named group ``pk`` - this name is the default - name that DetailView uses to find the value of the primary key used to + name that ``DetailView`` uses to find the value of the primary key used to filter the queryset. If you want to change it, you'll need to do your own ``get()`` call From 2c3d3400ef5abfd6be15276d4b5d584319ccecc3 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 22 May 2011 00:13:08 +0000 Subject: [PATCH 057/225] [1.3.X] Fixed #16051 - Changed a "file" reference in the tutorial to be an actual file rather than a module; thanks felix.morency for the suggestion. Backport of r16258 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16259 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial03.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index bb851b7820eb..41a62a72d724 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -523,7 +523,7 @@ Here's what happens if a user goes to "/polls/34/" in this system: Now that we've decoupled that, we need to decouple the ``polls.urls`` URLconf by removing the leading "polls/" from each line, and removing the -lines registering the admin site. Your ``polls.urls`` file should now look like +lines registering the admin site. Your ``polls/urls.py`` file should now look like this:: from django.conf.urls.defaults import * From 82b9fed1c7a9faf7c9cc8de70aae45e9c66f05e5 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 22 May 2011 16:44:14 +0000 Subject: [PATCH 058/225] [1.3.X] Fixed #16067 - Couple reST fixes in ref/templates/builtins.txt; thanks julien. Backport of r16263 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16264 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/templates/builtins.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index eaa576ef1578..353a7c78452a 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -533,6 +533,8 @@ operators, from lowest to highest, is as follows: (This follows Python exactly). So, for example, the following complex if tag: +.. code-block:: django + {% if a == b or c == d and e %} ...will be interpreted as: @@ -1471,7 +1473,7 @@ If ``value`` is the list ``['a', 'b', 'c']``, the output will be ``'a'``. fix_ampersands ~~~~~~~~~~~~~~ -..note:: +.. note:: This is rarely useful as ampersands are automatically escaped. See escape_ for more information. From b9bdc96f9ea8e4d572a79471c68ec239136a4d01 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 22 May 2011 17:00:23 +0000 Subject: [PATCH 059/225] [1.3.X] Fixed #16056 - Memcached configuration mistake in docs; thanks antonio/d0ugal. Backport of r16265 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16266 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/cache.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index f08ee8c45963..d2c9bbabe475 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -116,7 +116,7 @@ One excellent feature of Memcached is its ability to share cache over multiple servers. This means you can run Memcached daemons on multiple machines, and the program will treat the group of machines as a *single* cache, without the need to duplicate cache values on each machine. To take advantage of this feature, -include all server addresses in :setting:`BACKEND `, either +include all server addresses in :setting:`LOCATION `, either separated by semicolons or as a list. In this example, the cache is shared over Memcached instances running on IP From 18ecfad76785746b14e441d7379fc3b086da8d7a Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Mon, 23 May 2011 01:54:37 +0000 Subject: [PATCH 060/225] [1.3.X] Fixes #16072 -- incorrect documentation for multiple expressions inside a blocktrans tag Backport of r16268 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16269 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 20680bdc8fe8..368b616bbb54 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -490,13 +490,15 @@ for use within the translation block. Examples:: This will have {{ myvar }} inside. {% endblocktrans %} -If you need to bind more than one expression inside a ``blocktrans`` tag, -separate the pieces with ``and``:: +You can use multiple expressions inside a single ``blocktrans`` tag:: {% blocktrans with book_t=book|title and author_t=author|title %} This is {{ book_t }} by {{ author_t }} {% endblocktrans %} +.. note:: The previous more verbose format is still supported: + ``{% blocktrans with book|title as book_t and author|title as author_t %}`` + This tag also provides for pluralization. To use it: * Designate and bind a counter value with the name ``count``. This value will @@ -528,9 +530,6 @@ construct is internally converted to an ``ungettext`` call. This means the same :ref:`notes regarding ungettext variables ` apply. -.. note:: The previous more verbose format is still supported: - ``{% blocktrans with book|title as book_t and author|title as author_t %}`` - Reverse URL lookups cannot be carried out within the ``blocktrans`` and should be retrieved (and stored) beforehand:: From afa092853fb0fde4abc172f7867121074f260618 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 25 May 2011 17:31:36 +0000 Subject: [PATCH 061/225] [1.3.X] Changed utils/decorators.py tests to use RequestFactory Backport of [16272] from trunk. Backported to make the backport of a bugfix (regression) easier. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16278 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/decorators.py | 26 ++++++++++++++++++++--- tests/regressiontests/utils/urls.py | 8 ------- tests/regressiontests/utils/views.py | 17 --------------- tests/urls.py | 2 -- 4 files changed, 23 insertions(+), 30 deletions(-) delete mode 100644 tests/regressiontests/utils/urls.py delete mode 100644 tests/regressiontests/utils/views.py diff --git a/tests/regressiontests/utils/decorators.py b/tests/regressiontests/utils/decorators.py index ca9214f7872f..db32418de7a9 100644 --- a/tests/regressiontests/utils/decorators.py +++ b/tests/regressiontests/utils/decorators.py @@ -1,19 +1,39 @@ -from django.test import TestCase +from django.http import HttpResponse +from django.middleware.doc import XViewMiddleware +from django.test import TestCase, RequestFactory +from django.utils.decorators import decorator_from_middleware + + +xview_dec = decorator_from_middleware(XViewMiddleware) + + +@xview_dec +def xview(request): + return HttpResponse() + + +class ClassXView(object): + def __call__(self, request): + return HttpResponse() + +class_xview = xview_dec(ClassXView()) + class DecoratorFromMiddlewareTests(TestCase): """ Tests for view decorators created using ``django.utils.decorators.decorator_from_middleware``. """ + rf = RequestFactory() def test_process_view_middleware(self): """ Test a middleware that implements process_view. """ - self.client.get('/utils/xview/') + xview(self.rf.get('/')) def test_callable_process_view_middleware(self): """ Test a middleware that implements process_view, operating on a callable class. """ - self.client.get('/utils/class_xview/') + class_xview(self.rf.get('/')) diff --git a/tests/regressiontests/utils/urls.py b/tests/regressiontests/utils/urls.py deleted file mode 100644 index ba09d14b3d62..000000000000 --- a/tests/regressiontests/utils/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.conf.urls.defaults import * - -import views - -urlpatterns = patterns('', - (r'^xview/$', views.xview), - (r'^class_xview/$', views.class_xview), -) diff --git a/tests/regressiontests/utils/views.py b/tests/regressiontests/utils/views.py deleted file mode 100644 index ef97c6502df9..000000000000 --- a/tests/regressiontests/utils/views.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.http import HttpResponse -from django.utils.decorators import decorator_from_middleware -from django.middleware.doc import XViewMiddleware - - -xview_dec = decorator_from_middleware(XViewMiddleware) - -def xview(request): - return HttpResponse() -xview = xview_dec(xview) - - -class ClassXView(object): - def __call__(self, request): - return HttpResponse() - -class_xview = xview_dec(ClassXView()) diff --git a/tests/urls.py b/tests/urls.py index d254407cc476..08cfc94b788f 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -29,8 +29,6 @@ # admin widget tests (r'widget_admin/', include('regressiontests.admin_widgets.urls')), - (r'^utils/', include('regressiontests.utils.urls')), - # test urlconf for syndication tests (r'^syndication/', include('regressiontests.syndication.urls')), From 7f3eda2f76ea41e938cf623030f3a68e573d5017 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 25 May 2011 17:31:47 +0000 Subject: [PATCH 062/225] [1.3.X] Fixed #16004 - csrf_protect does not send cookie if view returns TemplateResponse The root bug was in decorator_from_middleware, and the fix also corrects bugs with gzip_page and other decorators. Backport of [16276] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16279 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/handlers/base.py | 2 +- django/template/response.py | 7 ++- django/utils/decorators.py | 15 +++-- docs/ref/template-response.txt | 4 ++ tests/regressiontests/utils/decorators.py | 67 +++++++++++++++++++++++ 5 files changed, 88 insertions(+), 7 deletions(-) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index f216886d5617..d653860547bb 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -133,7 +133,7 @@ def get_response(self, request): if hasattr(response, 'render') and callable(response.render): for middleware_method in self._template_response_middleware: response = middleware_method(request, response) - response.render() + response = response.render() except http.Http404, e: logger.warning('Not Found: %s' % request.path, diff --git a/django/template/response.py b/django/template/response.py index a6ef89352078..73645a7d7260 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -92,11 +92,14 @@ def render(self): Returns the baked response instance. """ + retval = self if not self._is_rendered: self._set_content(self.rendered_content) for post_callback in self._post_render_callbacks: - post_callback(self) - return self + newretval = post_callback(retval) + if newretval is not None: + retval = newretval + return retval is_rendered = property(lambda self: self._is_rendered) diff --git a/django/utils/decorators.py b/django/utils/decorators.py index 17f2ea30b337..78a14f6e242b 100644 --- a/django/utils/decorators.py +++ b/django/utils/decorators.py @@ -97,10 +97,17 @@ def _wrapped_view(request, *args, **kwargs): if result is not None: return result raise - if hasattr(middleware, 'process_response'): - result = middleware.process_response(request, response) - if result is not None: - return result + if hasattr(response, 'render') and callable(response.render): + if hasattr(middleware, 'process_template_response'): + response = middleware.process_template_response(request, response) + # Defer running of process_response until after the template + # has been rendered: + if hasattr(middleware, 'process_response'): + callback = lambda response: middleware.process_response(request, response) + response.add_post_render_callback(callback) + else: + if hasattr(middleware, 'process_response'): + return middleware.process_response(request, response) return response return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) return _decorator diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt index 90da00220e15..1885edcf9515 100644 --- a/docs/ref/template-response.txt +++ b/docs/ref/template-response.txt @@ -119,6 +119,10 @@ Methods rendered :class:`~django.template.response.SimpleTemplateResponse` instance. + If the callback returns a value that is not `None`, this will be + used as the response instead of the original response object (and + will be passed to the next post rendering callback etc.) + .. method:: SimpleTemplateResponse.render(): Sets :attr:`response.content` to the result obtained by diff --git a/tests/regressiontests/utils/decorators.py b/tests/regressiontests/utils/decorators.py index db32418de7a9..837cc7d39e07 100644 --- a/tests/regressiontests/utils/decorators.py +++ b/tests/regressiontests/utils/decorators.py @@ -1,5 +1,7 @@ from django.http import HttpResponse from django.middleware.doc import XViewMiddleware +from django.template import Template, Context +from django.template.response import TemplateResponse from django.test import TestCase, RequestFactory from django.utils.decorators import decorator_from_middleware @@ -19,6 +21,26 @@ def __call__(self, request): class_xview = xview_dec(ClassXView()) +class FullMiddleware(object): + def process_request(self, request): + request.process_request_reached = True + + def process_view(sef, request, view_func, view_args, view_kwargs): + request.process_view_reached = True + + def process_template_response(self, request, response): + request.process_template_response_reached = True + return response + + def process_response(self, request, response): + # This should never receive unrendered content. + request.process_response_content = response.content + request.process_response_reached = True + return response + +full_dec = decorator_from_middleware(FullMiddleware) + + class DecoratorFromMiddlewareTests(TestCase): """ Tests for view decorators created using @@ -37,3 +59,48 @@ def test_callable_process_view_middleware(self): Test a middleware that implements process_view, operating on a callable class. """ class_xview(self.rf.get('/')) + + def test_full_dec_normal(self): + """ + Test that all methods of middleware are called for normal HttpResponses + """ + + @full_dec + def normal_view(request): + t = Template("Hello world") + return HttpResponse(t.render(Context({}))) + + request = self.rf.get('/') + response = normal_view(request) + self.assertTrue(getattr(request, 'process_request_reached', False)) + self.assertTrue(getattr(request, 'process_view_reached', False)) + # process_template_response must not be called for HttpResponse + self.assertFalse(getattr(request, 'process_template_response_reached', False)) + self.assertTrue(getattr(request, 'process_response_reached', False)) + + def test_full_dec_templateresponse(self): + """ + Test that all methods of middleware are called for TemplateResponses in + the right sequence. + """ + + @full_dec + def template_response_view(request): + t = Template("Hello world") + return TemplateResponse(request, t, {}) + + request = self.rf.get('/') + response = template_response_view(request) + self.assertTrue(getattr(request, 'process_request_reached', False)) + self.assertTrue(getattr(request, 'process_view_reached', False)) + self.assertTrue(getattr(request, 'process_template_response_reached', False)) + # response must not be rendered yet. + self.assertFalse(response._is_rendered) + # process_response must not be called until after response is rendered, + # otherwise some decorators like csrf_protect and gzip_page will not + # work correctly. See #16004 + self.assertFalse(getattr(request, 'process_response_reached', False)) + response.render() + self.assertTrue(getattr(request, 'process_response_reached', False)) + # Check that process_response saw the rendered content + self.assertEqual(request.process_response_content, "Hello world") From 879267f254946a26ebc179f3880e14a59e11e038 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 29 May 2011 17:50:52 +0000 Subject: [PATCH 063/225] [1.3.X] Fixed #15992 -- Added more references to settings. Thanks, aaugustin. Backport from trunk (r16290). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16291 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/faq/admin.txt | 6 +- docs/faq/models.txt | 15 ++- docs/howto/i18n.txt | 2 +- docs/internals/deprecation.txt | 6 +- docs/ref/contrib/admin/index.txt | 2 +- docs/ref/contrib/gis/geoip.txt | 2 +- docs/ref/contrib/gis/install.txt | 10 +- docs/ref/contrib/index.txt | 4 +- docs/ref/contrib/messages.txt | 2 +- docs/ref/databases.txt | 8 +- docs/ref/django-admin.txt | 29 ++-- docs/ref/forms/fields.txt | 2 +- docs/ref/generic-views.txt | 18 +-- docs/ref/settings.txt | 157 ++++++++++++---------- docs/ref/templates/builtins.txt | 32 +++-- docs/ref/utils.txt | 4 +- docs/releases/1.0-porting-guide.txt | 20 +-- docs/releases/1.2.txt | 4 +- docs/releases/1.3-alpha-1.txt | 2 +- docs/releases/1.3.txt | 8 +- docs/topics/cache.txt | 2 +- docs/topics/email.txt | 4 +- docs/topics/http/file-uploads.txt | 8 +- docs/topics/http/sessions.txt | 20 +-- docs/topics/http/views.txt | 2 +- docs/topics/i18n/deployment.txt | 21 +-- docs/topics/i18n/internationalization.txt | 10 +- docs/topics/logging.txt | 2 +- docs/topics/settings.txt | 2 +- docs/topics/templates.txt | 4 +- docs/topics/testing.txt | 6 +- 31 files changed, 215 insertions(+), 199 deletions(-) diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index ac2e594ed296..32cea674795b 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -8,8 +8,8 @@ The login cookie isn't being set correctly, because the domain of the cookie sent out by Django doesn't match the domain in your browser. Try these two things: - * Set the ``SESSION_COOKIE_DOMAIN`` setting in your admin config file - to match your domain. For example, if you're going to + * Set the :setting:`SESSION_COOKIE_DOMAIN` setting in your admin config + file to match your domain. For example, if you're going to "http://www.example.com/admin/" in your browser, in "myproject.settings" you should set ``SESSION_COOKIE_DOMAIN = 'www.example.com'``. @@ -17,7 +17,7 @@ things: don't have dots in them. If you're running the admin site on "localhost" or another domain that doesn't have a dot in it, try going to "localhost.localdomain" or "127.0.0.1". And set - ``SESSION_COOKIE_DOMAIN`` accordingly. + :setting:`SESSION_COOKIE_DOMAIN` accordingly. I can't log in. When I enter a valid username and password, it brings up the login page again, with a "Please enter a correct username and password" error. ----------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/docs/faq/models.txt b/docs/faq/models.txt index f00d453d887e..08c7c47b9e58 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -6,16 +6,17 @@ FAQ: Databases and models How can I see the raw SQL queries Django is running? ---------------------------------------------------- -Make sure your Django ``DEBUG`` setting is set to ``True``. Then, just do -this:: +Make sure your Django :setting:`DEBUG` setting is set to ``True``. +Then, just do this:: >>> from django.db import connection >>> connection.queries [{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls', 'time': '0.002'}] -``connection.queries`` is only available if ``DEBUG`` is ``True``. It's a list -of dictionaries in order of query execution. Each dictionary has the following:: +``connection.queries`` is only available if :setting:`DEBUG` is ``True``. +It's a list of dictionaries in order of query execution. Each dictionary has +the following:: ``sql`` -- The raw SQL statement ``time`` -- How long the statement took to execute, in seconds. @@ -90,13 +91,13 @@ Why is Django leaking memory? Django isn't known to leak memory. If you find your Django processes are allocating more and more memory, with no sign of releasing it, check to make -sure your ``DEBUG`` setting is set to ``False``. If ``DEBUG`` is ``True``, then -Django saves a copy of every SQL statement it has executed. +sure your :setting:`DEBUG` setting is set to ``False``. If :setting:`DEBUG` +is ``True``, then Django saves a copy of every SQL statement it has executed. (The queries are saved in ``django.db.connection.queries``. See `How can I see the raw SQL queries Django is running?`_.) -To fix the problem, set ``DEBUG`` to ``False``. +To fix the problem, set :setting:`DEBUG` to ``False``. If you need to clear the query list manually at any point in your functions, just call ``reset_queries()``, like this:: diff --git a/docs/howto/i18n.txt b/docs/howto/i18n.txt index 37439ae5a209..4a056d07f8c5 100644 --- a/docs/howto/i18n.txt +++ b/docs/howto/i18n.txt @@ -55,7 +55,7 @@ message file specific to the project you are composing. The choice is yours. All message file repositories are structured the same way. They are: - * All paths listed in ``LOCALE_PATHS`` in your settings file are + * All paths listed in :setting:`LOCALE_PATHS` in your settings file are searched for ``/LC_MESSAGES/django.(po|mo)`` * ``$PROJECTPATH/locale//LC_MESSAGES/django.(po|mo)`` -- deprecated, see above. diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 66866c2a30c7..c7f8bcbcc0f6 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -167,9 +167,9 @@ their deprecation, as per the :ref:`Django deprecation policy and :class:`django.contrib.auth.context_processors.PermLookupDict`, respectively. - * The ``MEDIA_URL`` or ``STATIC_URL`` settings are required to end - with a trailing slash to ensure there is a consistent way to - combine paths in templates. + * The :setting:`MEDIA_URL` or :setting:`STATIC_URL` settings are + required to end with a trailing slash to ensure there is a consistent + way to combine paths in templates. * 2.0 * ``django.views.defaults.shortcut()``. This function has been moved diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index cd6ff31cfcdb..2fcafb4df856 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1624,7 +1624,7 @@ In this example, we register the default ``AdminSite`` instance ) Above we used ``admin.autodiscover()`` to automatically load the -``INSTALLED_APPS`` admin.py modules. +:setting:`INSTALLED_APPS` admin.py modules. In this example, we register the ``AdminSite`` instance ``myproject.admin.admin_site`` at the URL ``/myadmin/`` :: diff --git a/docs/ref/contrib/gis/geoip.txt b/docs/ref/contrib/gis/geoip.txt index 784d69eb5392..6503be7c3fdb 100644 --- a/docs/ref/contrib/gis/geoip.txt +++ b/docs/ref/contrib/gis/geoip.txt @@ -18,7 +18,7 @@ the GeoIP C libary and either the GeoIP `Country`__ or `City`__ datasets in binary format (the CSV files will not work!). These datasets may be `downloaded from MaxMind`__. Grab the ``GeoIP.dat.gz`` and ``GeoLiteCity.dat.gz`` and unzip them in a directory corresponding to what you set -``GEOIP_PATH`` with in your settings. See the example and reference below +:setting:`GEOIP_PATH` with in your settings. See the example and reference below for more details. __ http://www.maxmind.com/app/c diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 8b785b874174..82df82793c00 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -184,9 +184,9 @@ If using a binary package of GEOS (e.g., on Ubuntu), you may need to :ref:`binut ~~~~~~~~~~~~~~~~~~~~~ If your GEOS library is in a non-standard location, or you don't want to -modify the system's library path then the :setting:`GEOS_LIBRARY_PATH` setting -may be added to your Django settings file with the full path to the GEOS -C library. For example:: +modify the system's library path then the :setting:`GEOS_LIBRARY_PATH` +setting may be added to your Django settings file with the full path to the +GEOS C library. For example:: GEOS_LIBRARY_PATH = '/home/bob/local/lib/libgeos_c.so' @@ -592,8 +592,8 @@ Now, the ``spatialite`` command can be used to initialize a spatial database:: __ http://www.gaia-gis.it/spatialite/resources.html -Add ``django.contrib.gis`` to ``INSTALLED_APPS`` ------------------------------------------------- +Add ``django.contrib.gis`` to :setting:`INSTALLED_APPS` +------------------------------------------------------- Like other Django contrib applications, you will *only* need to add :mod:`django.contrib.gis` to :setting:`INSTALLED_APPS` in your settings. diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt index 56ac9fd136a2..f62c8fc36efe 100644 --- a/docs/ref/contrib/index.txt +++ b/docs/ref/contrib/index.txt @@ -14,8 +14,8 @@ those packages have. For most of these add-ons -- specifically, the add-ons that include either models or template tags -- you'll need to add the package name (e.g., - ``'django.contrib.admin'``) to your ``INSTALLED_APPS`` setting and re-run - ``manage.py syncdb``. + ``'django.contrib.admin'``) to your :setting:`INSTALLED_APPS` setting and + re-run ``manage.py syncdb``. .. _"batteries included" philosophy: http://docs.python.org/tutorial/stdlib.html#batteries-included diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 838f492fd131..ca3212df1fd7 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -414,7 +414,7 @@ SESSION_COOKIE_DOMAIN Default: ``None`` The storage backends that use cookies -- ``CookieStorage`` and -``FallbackStorage`` -- use the value of ``SESSION_COOKIE_DOMAIN`` in +``FallbackStorage`` -- use the value of :setting:`SESSION_COOKIE_DOMAIN` in setting their cookies. See the :doc:`settings documentation ` for more information on how this works and why you might need to set it. diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 1715afda6ba2..5da23366272c 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -262,8 +262,8 @@ Connection settings are used in this order: :setting:`HOST`, :setting:`PORT` 3. MySQL option files. -In other words, if you set the name of the database in ``OPTIONS``, -this will take precedence over ``NAME``, which would override +In other words, if you set the name of the database in :setting:`OPTIONS`, +this will take precedence over :setting:`NAME`, which would override anything in a `MySQL option file`_. Here's a sample configuration which uses a MySQL option file:: @@ -551,7 +551,7 @@ Your Django settings.py file should look something like this for Oracle:: If you don't use a ``tnsnames.ora`` file or a similar naming method that recognizes the SID ("xe" in this example), then fill in both -``HOST`` and ``PORT`` like so:: +:setting:`HOST` and :setting:`PORT` like so:: DATABASES = { 'default': { @@ -564,7 +564,7 @@ recognizes the SID ("xe" in this example), then fill in both } } -You should supply both ``HOST`` and ``PORT``, or leave both +You should supply both :setting:`HOST` and :setting:`PORT`, or leave both as empty strings. Threaded option diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 33c4c8b8e1af..89bc43ff73f6 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -57,7 +57,7 @@ App names --------- Many commands take a list of "app names." An "app name" is the basename of -the package containing your models. For example, if your ``INSTALLED_APPS`` +the package containing your models. For example, if your :setting:`INSTALLED_APPS` contains the string ``'mysite.blog'``, the app name is ``blog``. Determining the version @@ -126,7 +126,7 @@ dbshell Runs the command-line client for the database engine specified in your ``ENGINE`` setting, with the connection parameters specified in your -``USER``, ``PASSWORD``, etc., settings. +:setting:`USER`, :setting:`PASSWORD`, etc., settings. * For PostgreSQL, this runs the ``psql`` command-line client. * For MySQL, this runs the ``mysql`` command-line client. @@ -151,8 +151,9 @@ Displays differences between the current settings file and Django's default settings. Settings that don't appear in the defaults are followed by ``"###"``. For -example, the default settings don't define ``ROOT_URLCONF``, so -``ROOT_URLCONF`` is followed by ``"###"`` in the output of ``diffsettings``. +example, the default settings don't define :setting:`ROOT_URLCONF`, so +:setting:`ROOT_URLCONF` is followed by ``"###"`` in the output of +``diffsettings``. Note that Django's default settings live in ``django/conf/global_settings.py``, if you're ever curious to see the full list of defaults. @@ -245,7 +246,7 @@ inspectdb .. django-admin:: inspectdb Introspects the database tables in the database pointed-to by the -``NAME`` setting and outputs a Django model module (a ``models.py`` +:setting:`NAME` setting and outputs a Django model module (a ``models.py`` file) to standard output. Use this if you have a legacy database with which you'd like to use Django. @@ -309,7 +310,7 @@ fixture can be distributed over multiple directories, in multiple applications. Django will search in three locations for fixtures: 1. In the ``fixtures`` directory of every installed application - 2. In any directory named in the ``FIXTURE_DIRS`` setting + 2. In any directory named in the :setting:`FIXTURE_DIRS` setting 3. In the literal path named by the fixture Django will load any and all fixtures it finds in these locations that match @@ -340,7 +341,7 @@ directories will be included in the search path. For example:: would search ``/fixtures/foo/bar/mydata.json`` for each installed application, ``/foo/bar/mydata.json`` for each directory in -``FIXTURE_DIRS``, and the literal path ``foo/bar/mydata.json``. +:setting:`FIXTURE_DIRS`, and the literal path ``foo/bar/mydata.json``. When fixture files are processed, the data is saved to the database as is. Model defined ``save`` methods and ``pre_save`` signals are not called. @@ -742,7 +743,7 @@ Serving static files with the development server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, the development server doesn't serve any static files for your site -(such as CSS files, images, things under ``MEDIA_URL`` and so forth). If +(such as CSS files, images, things under :setting:`MEDIA_URL` and so forth). If you want to configure Django to serve static media, read :doc:`/howto/static-files`. shell @@ -912,13 +913,13 @@ syncdb .. django-admin:: syncdb -Creates the database tables for all apps in ``INSTALLED_APPS`` whose tables -have not already been created. +Creates the database tables for all apps in :setting:`INSTALLED_APPS` whose +tables have not already been created. Use this command when you've added new applications to your project and want to install them in the database. This includes any apps shipped with Django that -might be in ``INSTALLED_APPS`` by default. When you start a new project, run -this command to install the default apps. +might be in :setting:`INSTALLED_APPS` by default. When you start a new project, +run this command to install the default apps. .. admonition:: Syncdb will not alter existing tables @@ -1032,8 +1033,8 @@ validate .. django-admin:: validate -Validates all installed models (according to the ``INSTALLED_APPS`` setting) -and prints validation errors to standard output. +Validates all installed models (according to the :setting:`INSTALLED_APPS` +setting) and prints validation errors to standard output. Commands provided by applications ================================= diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index b49864f7cfe0..59a6df82d062 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -759,7 +759,7 @@ Takes the following optional arguments: .. attribute:: URLField.validator_user_agent String used as the user-agent used when checking for a URL's existence. - Defaults to the value of the ``URL_VALIDATOR_USER_AGENT`` setting. + Defaults to the value of the :setting:`URL_VALIDATOR_USER_AGENT` setting. .. versionchanged:: 1.2 The URLField previously did not recognize URLs as valid that contained an IDN diff --git a/docs/ref/generic-views.txt b/docs/ref/generic-views.txt index 54114d07a60e..a787869c2bb3 100644 --- a/docs/ref/generic-views.txt +++ b/docs/ref/generic-views.txt @@ -58,7 +58,7 @@ which is a dictionary of the parameters captured in the URL. just before rendering the template. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. **Example:** @@ -198,7 +198,7 @@ a date in the *future* are not included unless you set ``allow_future`` to the view's template. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. * ``allow_future``: A boolean specifying whether to include "future" objects on this page, where "future" means objects in which the field @@ -290,7 +290,7 @@ to ``True``. this is ``False``. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. * ``allow_future``: A boolean specifying whether to include "future" objects on this page, where "future" means objects in which the field @@ -377,7 +377,7 @@ date in the *future* are not displayed unless you set ``allow_future`` to determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. * ``allow_future``: A boolean specifying whether to include "future" objects on this page, where "future" means objects in which the field @@ -465,7 +465,7 @@ in the *future* are not displayed unless you set ``allow_future`` to ``True``. determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. * ``allow_future``: A boolean specifying whether to include "future" objects on this page, where "future" means objects in which the field @@ -550,7 +550,7 @@ you set ``allow_future`` to ``True``. determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. * ``allow_future``: A boolean specifying whether to include "future" objects on this page, where "future" means objects in which the field @@ -660,7 +660,7 @@ future, the view will throw a 404 error by default, unless you set to use in the template context. By default, this is ``'object'``. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. * ``allow_future``: A boolean specifying whether to include "future" objects on this page, where "future" means objects in which the field @@ -738,7 +738,7 @@ A page representing a list of objects. determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. **Template name:** @@ -852,7 +852,7 @@ A page representing an individual object. to use in the template context. By default, this is ``'object'``. * ``mimetype``: The MIME type to use for the resulting document. Defaults - to the value of the ``DEFAULT_CONTENT_TYPE`` setting. + to the value of the :setting:`DEFAULT_CONTENT_TYPE` setting. **Template name:** diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 8bf0a4e8d008..81ff9e39a609 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -86,7 +86,7 @@ A tuple of strings representing allowed prefixes for the ``{% ssi %}`` template tag. This is a security measure, so that template authors can't access files that they shouldn't be accessing. -For example, if ``ALLOWED_INCLUDE_ROOTS`` is ``('/home/html', '/var/www')``, +For example, if :setting:`ALLOWED_INCLUDE_ROOTS` is ``('/home/html', '/var/www')``, then ``{% ssi /home/html/foo.txt %}`` would work, but ``{% ssi /etc/passwd %}`` wouldn't. @@ -102,7 +102,7 @@ in the URLconf and it doesn't end in a slash, an HTTP redirect is issued to the same URL with a slash appended. Note that the redirect may cause any data submitted in a POST request to be lost. -The ``APPEND_SLASH`` setting is only used if +The :setting:`APPEND_SLASH` setting is only used if :class:`~django.middleware.common.CommonMiddleware` is installed (see :doc:`/topics/http/middleware`). See also :setting:`PREPEND_WWW`. @@ -602,9 +602,9 @@ locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. .. versionchanged:: 1.2 - This setting can now be overriden by setting ``USE_L10N`` to ``True``. + This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. -See also ``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``SHORT_DATE_FORMAT``. +See also :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATE_FORMAT`. .. setting:: DATE_INPUT_FORMATS @@ -625,7 +625,7 @@ Note that these format strings are specified in Python's datetime_ module syntax, that is different from the one used by Django for formatting dates to be displayed. -See also ``DATETIME_INPUT_FORMATS`` and ``TIME_INPUT_FORMATS``. +See also :setting:`DATETIME_INPUT_FORMATS` and :setting:`TIME_INPUT_FORMATS`. .. _datetime: http://docs.python.org/library/datetime.html#strftime-strptime-behavior @@ -642,9 +642,9 @@ locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. .. versionchanged:: 1.2 - This setting can now be overriden by setting ``USE_L10N`` to ``True``. + This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. -See also ``DATE_FORMAT``, ``TIME_FORMAT`` and ``SHORT_DATETIME_FORMAT``. +See also :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATETIME_FORMAT`. .. setting:: DATETIME_INPUT_FORMATS @@ -665,7 +665,7 @@ Note that these format strings are specified in Python's datetime_ module syntax, that is different from the one used by Django for formatting dates to be displayed. -See also ``DATE_INPUT_FORMATS`` and ``TIME_INPUT_FORMATS``. +See also :setting:`DATE_INPUT_FORMATS` and :setting:`TIME_INPUT_FORMATS`. .. _datetime: http://docs.python.org/library/datetime.html#strftime-strptime-behavior @@ -688,11 +688,12 @@ Still, note that there are always going to be sections of your debug output that are inappropriate for public consumption. File paths, configuration options, and the like all give attackers extra information about your server. -It is also important to remember that when running with ``DEBUG`` turned on, Django -will remember every SQL query it executes. This is useful when you are debugging, -but on a production server, it will rapidly consume memory. +It is also important to remember that when running with :setting:`DEBUG` +turned on, Django will remember every SQL query it executes. This is useful +when you are debugging, but on a production server, it will rapidly consume +memory. -Never deploy a site into production with ``DEBUG`` turned on. +Never deploy a site into production with :setting:`DEBUG` turned on. .. _django/views/debug.py: http://code.djangoproject.com/browser/django/trunk/django/views/debug.py @@ -725,7 +726,7 @@ DEFAULT_CHARSET Default: ``'utf-8'`` Default charset to use for all ``HttpResponse`` objects, if a MIME type isn't -manually specified. Used with ``DEFAULT_CONTENT_TYPE`` to construct the +manually specified. Used with :setting:`DEFAULT_CONTENT_TYPE` to construct the ``Content-Type`` header. .. setting:: DEFAULT_CONTENT_TYPE @@ -736,8 +737,8 @@ DEFAULT_CONTENT_TYPE Default: ``'text/html'`` Default content type to use for all ``HttpResponse`` objects, if a MIME type -isn't manually specified. Used with ``DEFAULT_CHARSET`` to construct the -``Content-Type`` header. +isn't manually specified. Used with :setting:`DEFAULT_CHARSET` to construct +the ``Content-Type`` header. .. setting:: DEFAULT_FILE_STORAGE @@ -823,7 +824,7 @@ Default: ``'localhost'`` The host to use for sending e-mail. -See also ``EMAIL_PORT``. +See also :setting:`EMAIL_PORT`. .. setting:: EMAIL_HOST_PASSWORD @@ -832,12 +833,12 @@ EMAIL_HOST_PASSWORD Default: ``''`` (Empty string) -Password to use for the SMTP server defined in ``EMAIL_HOST``. This setting is -used in conjunction with ``EMAIL_HOST_USER`` when authenticating to the SMTP -server. If either of these settings is empty, Django won't attempt -authentication. +Password to use for the SMTP server defined in :setting:`EMAIL_HOST`. This +setting is used in conjunction with :setting:`EMAIL_HOST_USER` when +authenticating to the SMTP server. If either of these settings is empty, +Django won't attempt authentication. -See also ``EMAIL_HOST_USER``. +See also :setting:`EMAIL_HOST_USER`. .. setting:: EMAIL_HOST_USER @@ -846,10 +847,10 @@ EMAIL_HOST_USER Default: ``''`` (Empty string) -Username to use for the SMTP server defined in ``EMAIL_HOST``. If empty, -Django won't attempt authentication. +Username to use for the SMTP server defined in :setting:`EMAIL_HOST`. +If empty, Django won't attempt authentication. -See also ``EMAIL_HOST_PASSWORD``. +See also :setting:`EMAIL_HOST_PASSWORD`. .. setting:: EMAIL_PORT @@ -858,7 +859,7 @@ EMAIL_PORT Default: ``25`` -Port to use for the SMTP server defined in ``EMAIL_HOST``. +Port to use for the SMTP server defined in :setting:`EMAIL_HOST`. .. setting:: EMAIL_SUBJECT_PREFIX @@ -1005,8 +1006,8 @@ project locales. If not ``None``, Django will check for a ``formats.py`` file, under the directory named as the current locale, and will use the formats defined on this file. -For example, if ``FORMAT_MODULE_PATH`` is set to ``mysite.formats``, and -current language is ``en`` (English), Django will expect a directory tree +For example, if :setting:`FORMAT_MODULE_PATH` is set to ``mysite.formats``, +and current language is ``en`` (English), Django will expect a directory tree like:: mysite/ @@ -1016,10 +1017,12 @@ like:: __init__.py formats.py -Available formats are ``DATE_FORMAT``, ``TIME_FORMAT``, ``DATETIME_FORMAT``, -``YEAR_MONTH_FORMAT``, ``MONTH_DAY_FORMAT``, ``SHORT_DATE_FORMAT``, -``SHORT_DATETIME_FORMAT``, ``FIRST_DAY_OF_WEEK``, ``DECIMAL_SEPARATOR``, -``THOUSAND_SEPARATOR`` and ``NUMBER_GROUPING``. +Available formats are :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT`, +:setting:`DATETIME_FORMAT`, :setting:`YEAR_MONTH_FORMAT`, +:setting:`MONTH_DAY_FORMAT`, :setting:`SHORT_DATE_FORMAT`, +:setting:`SHORT_DATETIME_FORMAT`, :setting:`FIRST_DAY_OF_WEEK`, +:setting:`DECIMAL_SEPARATOR`, :setting:`THOUSAND_SEPARATOR` and +:setting:`NUMBER_GROUPING`. .. setting:: IGNORABLE_404_ENDS @@ -1070,7 +1073,7 @@ Default: ``()`` (Empty tuple) A tuple of IP addresses, as strings, that: - * See debug comments, when ``DEBUG`` is ``True`` + * See debug comments, when :setting:`DEBUG` is ``True`` * Receive X headers if the ``XViewMiddleware`` is installed (see :doc:`/topics/http/middleware`) @@ -1092,8 +1095,8 @@ LANGUAGE_COOKIE_NAME Default: ``'django_language'`` -The name of the cookie to use for the language cookie. This can be whatever you -want (but should be different from ``SESSION_COOKIE_NAME``). See +The name of the cookie to use for the language cookie. This can be whatever +you want (but should be different from :setting:`SESSION_COOKIE_NAME`). See :doc:`/topics/i18n/index`. .. setting:: LANGUAGES @@ -1117,12 +1120,13 @@ This specifies which languages are available for language selection. See Generally, the default value should suffice. Only set this setting if you want to restrict language selection to a subset of the Django-provided languages. -If you define a custom ``LANGUAGES`` setting, it's OK to mark the languages as -translation strings (as in the default value referred to above) -- but use a -"dummy" ``gettext()`` function, not the one in ``django.utils.translation``. -You should *never* import ``django.utils.translation`` from within your -settings file, because that module in itself depends on the settings, and that -would cause a circular import. +If you define a custom :setting:`LANGUAGES` setting, it's OK to mark the +languages as translation strings (as in the default value referred to above) +-- but use a "dummy" ``gettext()`` function, not the one in +``django.utils.translation``. You should *never* import +``django.utils.translation`` from within your settings file, because that +module in itself depends on the settings, and that would cause a circular +import. The solution is to use a "dummy" ``gettext()`` function. Here's a sample settings file:: @@ -1137,7 +1141,7 @@ settings file:: With this arrangement, ``django-admin.py makemessages`` will still find and mark these strings for translation, but the translation won't happen at runtime -- so you'll have to remember to wrap the languages in the *real* -``gettext()`` in any code that uses ``LANGUAGES`` at runtime. +``gettext()`` in any code that uses :setting:`LANGUAGES` at runtime. .. setting:: LOCALE_PATHS @@ -1233,7 +1237,7 @@ MANAGERS Default: ``()`` (Empty tuple) -A tuple in the same format as ``ADMINS`` that specifies who should get +A tuple in the same format as :setting:`ADMINS` that specifies who should get broken-link notifications when ``SEND_BROKEN_LINK_EMAILS=True``. .. setting:: MEDIA_ROOT @@ -1338,8 +1342,9 @@ drilldown, the header for a given day displays the day and month. Different locales have different formats. For example, U.S. English would say "January 1," whereas Spanish might say "1 Enero." -See :tfilter:`allowed date format strings `. See also ``DATE_FORMAT``, -``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``YEAR_MONTH_FORMAT``. +See :tfilter:`allowed date format strings `. See also +:setting:`DATE_FORMAT`, :setting:`DATETIME_FORMAT`, +:setting:`TIME_FORMAT` and :setting:`YEAR_MONTH_FORMAT`. .. setting:: NUMBER_GROUPING @@ -1433,11 +1438,11 @@ SEND_BROKEN_LINK_EMAILS Default: ``False`` -Whether to send an e-mail to the ``MANAGERS`` each time somebody visits a -Django-powered page that is 404ed with a non-empty referer (i.e., a broken +Whether to send an e-mail to the :setting:`MANAGERS` each time somebody visits +a Django-powered page that is 404ed with a non-empty referer (i.e., a broken link). This is only used if ``CommonMiddleware`` is installed (see -:doc:`/topics/http/middleware`. See also ``IGNORABLE_404_STARTS``, -``IGNORABLE_404_ENDS`` and :doc:`/howto/error-reporting`. +:doc:`/topics/http/middleware`. See also :setting:`IGNORABLE_404_STARTS`, +:setting:`IGNORABLE_404_ENDS` and :doc:`/howto/error-reporting`. .. setting:: SERIALIZATION_MODULES @@ -1460,7 +1465,7 @@ SERVER_EMAIL Default: ``'root@localhost'`` The e-mail address that error messages come from, such as those sent to -``ADMINS`` and ``MANAGERS``. +:setting:`ADMINS` and :setting:`MANAGERS`. .. setting:: SESSION_COOKIE_AGE @@ -1509,7 +1514,8 @@ SESSION_COOKIE_NAME Default: ``'sessionid'`` The name of the cookie to use for sessions. This can be whatever you want (but -should be different from ``LANGUAGE_COOKIE_NAME``). See the :doc:`/topics/http/sessions`. +should be different from :setting:`LANGUAGE_COOKIE_NAME`). +See the :doc:`/topics/http/sessions`. .. setting:: SESSION_COOKIE_PATH @@ -1599,7 +1605,7 @@ templates. Note that if :setting:`USE_L10N` is set to ``True``, then the corresponding locale-dictated format has higher precedence and will be applied. See :tfilter:`allowed date format strings `. -See also ``DATE_FORMAT`` and ``SHORT_DATETIME_FORMAT``. +See also :setting:`DATE_FORMAT` and :setting:`SHORT_DATETIME_FORMAT`. .. setting:: SHORT_DATETIME_FORMAT @@ -1615,7 +1621,7 @@ templates. Note that if :setting:`USE_L10N` is set to ``True``, then the corresponding locale-dictated format has higher precedence and will be applied. See :tfilter:`allowed date format strings `. -See also ``DATE_FORMAT`` and ``SHORT_DATETIME_FORMAT``. +See also :setting:`DATE_FORMAT` and :setting:`SHORT_DATETIME_FORMAT`. .. setting:: SITE_ID @@ -1724,10 +1730,10 @@ error page will display a detailed report for any ``TemplateSyntaxError``. This report contains the relevant snippet of the template, with the appropriate line highlighted. -Note that Django only displays fancy error pages if ``DEBUG`` is ``True``, so +Note that Django only displays fancy error pages if :setting:`DEBUG` is ``True``, so you'll want to set that to take advantage of this setting. -See also ``DEBUG``. +See also :setting:`DEBUG`. .. setting:: TEMPLATE_DIRS @@ -1759,9 +1765,9 @@ module, subsequent items are passed to the ``Loader`` during initialization. See .. versionchanged:: 1.2 The class-based API for template loaders was introduced in Django 1.2 - although the ``TEMPLATE_LOADERS`` setting will accept strings that specify - function-based loaders until compatibility with them is completely removed in - Django 1.4. + although the :setting:`TEMPLATE_LOADERS` setting will accept strings + that specify function-based loaders until compatibility with them is + completely removed in Django 1.4. .. setting:: TEMPLATE_STRING_IF_INVALID @@ -1795,10 +1801,11 @@ THOUSAND_SEPARATOR .. versionadded:: 1.2 -Default ``,`` (Comma) +Default: ``,`` (Comma) Default thousand separator used when formatting numbers. This setting is -used only when ``NUMBER_GROUPING`` and ``USE_THOUSAND_SEPARATOR`` are set. +used only when :setting:`NUMBER_GROUPING` and :setting:`USE_THOUSAND_SEPARATOR` +are set. See also :setting:`NUMBER_GROUPING`, :setting:`DECIMAL_SEPARATOR` and :setting:`USE_THOUSAND_SEPARATOR`. @@ -1816,9 +1823,9 @@ locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. .. versionchanged:: 1.2 - This setting can now be overriden by setting ``USE_L10N`` to ``True``. + This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. -See also ``DATE_FORMAT`` and ``DATETIME_FORMAT``. +See also :setting:`DATE_FORMAT` and :setting:`DATETIME_FORMAT`. .. setting:: TIME_INPUT_FORMATS @@ -1835,7 +1842,7 @@ Note that these format strings are specified in Python's datetime_ module syntax, that is different from the one used by Django for formatting dates to be displayed. -See also ``DATE_INPUT_FORMATS`` and ``DATETIME_INPUT_FORMATS``. +See also :setting:`DATE_INPUT_FORMATS` and :setting:`DATETIME_INPUT_FORMATS`. .. _datetime: http://docs.python.org/library/datetime.html#strftime-strptime-behavior @@ -1854,7 +1861,7 @@ A string representing the time zone for this installation, or choices lists more than one on the same line; you'll want to use just one of the choices for a given time zone. For instance, one line says ``'Europe/London GB GB-Eire'``, but you should use the first bit of -that -- ``'Europe/London'`` -- as your ``TIME_ZONE`` setting.) +that -- ``'Europe/London'`` -- as your :setting:`TIME_ZONE` setting.) Note that this is the time zone to which Django will convert all dates/times -- not necessarily the timezone of the server. For @@ -1862,7 +1869,7 @@ example, one server may serve multiple Django-powered sites, each with a separate time-zone setting. Normally, Django sets the ``os.environ['TZ']`` variable to the time -zone you specify in the ``TIME_ZONE`` setting. Thus, all your views +zone you specify in the :setting:`TIME_ZONE` setting. Thus, all your views and models will automatically operate in the correct time zone. However, Django won't set the ``TZ`` environment variable under the following conditions: @@ -1919,7 +1926,7 @@ enabled. This provides an easy way to turn it off, for performance. If this is set to ``False``, Django will make some optimizations so as not to load the internationalization machinery. -See also ``USE_L10N`` +See also :setting:`USE_L10N` .. setting:: USE_L10N @@ -1928,13 +1935,13 @@ USE_L10N .. versionadded:: 1.2 -Default ``False`` +Default: ``False`` A boolean that specifies if data will be localized by default or not. If this is set to ``True``, e.g. Django will display numbers and dates using the format of the current locale. -See also ``USE_I18N`` and ``LANGUAGE_CODE`` +See also :setting:`USE_I18N` and :setting:`LANGUAGE_CODE` .. setting:: USE_THOUSAND_SEPARATOR @@ -1943,14 +1950,15 @@ USE_THOUSAND_SEPARATOR .. versionadded:: 1.2 -Default ``False`` +Default: ``False`` A boolean that specifies wheter to display numbers using a thousand separator. -If this is set to ``True``, Django will use values from ``THOUSAND_SEPARATOR`` -and ``NUMBER_GROUPING`` from current locale, to format the number. -``USE_L10N`` must be set to ``True``, in order to format numbers. +If this is set to ``True``, Django will use values from +:setting:`THOUSAND_SEPARATOR` and :setting:`NUMBER_GROUPING` from current +locale, to format the number. :setting:`USE_L10N` must be set to ``True``, +in order to format numbers. -See also ``THOUSAND_SEPARATOR`` and ``NUMBER_GROUPING``. +See also :setting:`THOUSAND_SEPARATOR` and :setting:`NUMBER_GROUPING`. .. setting:: YEAR_MONTH_FORMAT @@ -1968,8 +1976,9 @@ drilldown, the header for a given month displays the month and the year. Different locales have different formats. For example, U.S. English would say "January 2006," whereas another locale might say "2006/January." -See :tfilter:`allowed date format strings `. See also ``DATE_FORMAT``, -``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``MONTH_DAY_FORMAT``. +See :tfilter:`allowed date format strings `. See also +:setting:`DATE_FORMAT`, :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT` +and :setting:`MONTH_DAY_FORMAT`. Deprecated settings =================== diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 353a7c78452a..695bc871723b 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1295,10 +1295,11 @@ If ``value`` is a ``datetime`` object (e.g., the result of ``datetime.datetime.now()``), the output will be the string ``'Wed 09 Jan 2008'``. -The format passed can be one of the predefined ones ``DATE_FORMAT``, -``DATETIME_FORMAT``, ``SHORT_DATE_FORMAT`` or ``SHORT_DATETIME_FORMAT``, or a -custom format that uses the format specifiers shown in the table above. Note -that predefined formats may vary depending on the current locale. +The format passed can be one of the predefined ones :setting:`DATE_FORMAT`, +:setting:`DATETIME_FORMAT`, :setting:`SHORT_DATE_FORMAT` or +:setting:`SHORT_DATETIME_FORMAT`, or a custom format that uses the format +specifiers shown in the table above. Note that predefined formats may vary +depending on the current locale. Assuming that :setting:`USE_L10N` is ``True`` and :setting:`LANGUAGE_CODE` is, for example, ``"es"``, then for:: @@ -1929,9 +1930,9 @@ time Formats a time according to the given format. -Given format can be the predefined one ``TIME_FORMAT``, or a custom format, -same as the :tfilter:`date` filter. Note that the predefined format is locale- -dependant. +Given format can be the predefined one :setting:`TIME_FORMAT`, or a custom +format, same as the :tfilter:`date` filter. Note that the predefined format +is locale-dependant. The time filter will only accept parameters in the format string that relate to the time of day, not the date (for obvious reasons). If you need to @@ -2231,7 +2232,7 @@ Other tags and filter libraries ------------------------------- Django comes with a couple of other template-tag libraries that you have to -enable explicitly in your ``INSTALLED_APPS`` setting and enable in your +enable explicitly in your :setting:`INSTALLED_APPS` setting and enable in your template with the ``{% load %}`` tag. django.contrib.humanize @@ -2262,15 +2263,18 @@ i18n Provides a couple of templatetags that allow specifying translatable text in Django templates. It is slightly different from the libraries described -above because you don't need to add any application to the ``INSTALLED_APPS`` -setting but rather set :setting:`USE_I18N` to True, then loading it with -``{% load i18n %}``. See :ref:`specifying-translation-strings-in-template-code`. +above because you don't need to add any application to the +:setting:`INSTALLED_APPS` setting but rather set :setting:`USE_I18N` to True, +then loading it with ``{% load i18n %}``. + +See :ref:`specifying-translation-strings-in-template-code`. l10n ~~~~ Provides a couple of templatetags that allow control over the localization of values in Django templates. It is slightly different from the libraries described -above because you don't need to add any application to the ``INSTALLED_APPS``; -you only need to load the library using ``{% load l10n %}``. See -:ref:`topic-l10n-templates`. +above because you don't need to add any application to the :setting:`INSTALLED_APPS`; +you only need to load the library using ``{% load l10n %}``. + +See :ref:`topic-l10n-templates`. diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 979c1867d118..0e8aaaf84fed 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -61,8 +61,8 @@ to distinguish caches by the ``Accept-language`` header. Each header is only added if it isn't already set. - ``cache_timeout`` is in seconds. The ``CACHE_MIDDLEWARE_SECONDS`` setting - is used by default. + ``cache_timeout`` is in seconds. The :setting:`CACHE_MIDDLEWARE_SECONDS` + setting is used by default. .. function:: add_never_cache_headers(response) diff --git a/docs/releases/1.0-porting-guide.txt b/docs/releases/1.0-porting-guide.txt index e12b34e6e495..45a0270dd729 100644 --- a/docs/releases/1.0-porting-guide.txt +++ b/docs/releases/1.0-porting-guide.txt @@ -443,10 +443,10 @@ The old :exc:`EnvironmentError` has split into an :exc:`ImportError` when Django fails to find the settings module and a :exc:`RuntimeError` when you try to reconfigure settings after having already used them -``LOGIN_URL`` has moved -~~~~~~~~~~~~~~~~~~~~~~~ +:setting:`LOGIN_URL` has moved +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``LOGIN_URL`` constant moved from ``django.contrib.auth`` into the +The :setting:`LOGIN_URL` constant moved from ``django.contrib.auth`` into the ``settings`` module. Instead of using ``from django.contrib.auth import LOGIN_URL`` refer to :setting:`settings.LOGIN_URL `. @@ -454,11 +454,11 @@ LOGIN_URL`` refer to :setting:`settings.LOGIN_URL `. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In 0.96, if a URL didn't end in a slash or have a period in the final -component of its path, and ``APPEND_SLASH`` was True, Django would redirect -to the same URL, but with a slash appended to the end. Now, Django checks to -see whether the pattern without the trailing slash would be matched by -something in your URL patterns. If so, no redirection takes place, because it -is assumed you deliberately wanted to catch that pattern. +component of its path, and :setting:`APPEND_SLASH` was True, Django would +redirect to the same URL, but with a slash appended to the end. Now, Django +checks to see whether the pattern without the trailing slash would be matched +by something in your URL patterns. If so, no redirection takes place, because +it is assumed you deliberately wanted to catch that pattern. For most people, this won't require any changes. Some people, though, have URL patterns that look like this:: @@ -548,8 +548,8 @@ need to reload your data. Do this after you have made the change to using **Back up your database first!** For SQLite, this means making a copy of the single file that stores the - database (the name of that file is the ``DATABASE_NAME`` in your settings.py - file). + database (the name of that file is the :setting:`DATABASE_NAME` in your + settings.py file). To upgrade each application to use a ``DecimalField``, you can do the following, replacing ```` in the code below with each app's name: diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt index 31ed59a9c1de..d466477dfb29 100644 --- a/docs/releases/1.2.txt +++ b/docs/releases/1.2.txt @@ -1067,8 +1067,8 @@ Up to version 1.1 Django used :ref:`technical message IDs` to provide localizers the possibility to translate date and time formats. They were translatable :term:`translation strings ` that could be recognized because they were all upper case (for example -``DATETIME_FORMAT``, ``DATE_FORMAT``, ``TIME_FORMAT``). They have been -deprecated in favor of the new :ref:`Format localization +:setting:`DATETIME_FORMAT`, :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT`). +They have been deprecated in favor of the new :ref:`Format localization ` infrastructure that allows localizers to specify that information in a ``formats.py`` file in the corresponding ``django/conf/locale//`` directory. diff --git a/docs/releases/1.3-alpha-1.txt b/docs/releases/1.3-alpha-1.txt index d6610d1c0e5b..ce376028a6ad 100644 --- a/docs/releases/1.3-alpha-1.txt +++ b/docs/releases/1.3-alpha-1.txt @@ -249,7 +249,7 @@ framework, we have "fixed" the problem by making the list of prohibited words an empty list. If you want to restore the old behavior, simply put a -``PROFANITIES_LIST`` setting in your settings file that includes the +:setting:`PROFANITIES_LIST` setting in your settings file that includes the words that you want to prohibit (see the `commit that implemented this change`_ if you want to see the list of words that was historically prohibited). However, if avoiding profanities is important to you, you diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 34d615f5a026..3e697d3b86b6 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -264,8 +264,8 @@ The GeoDjango test suite is now included when :ref:`running the Django test suite ` with ``runtests.py`` when using :ref:`spatial database backends `. -``MEDIA_URL`` and ``STATIC_URL`` must end in a slash -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:setting:`MEDIA_URL` and :setting:`STATIC_URL` must end in a slash +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Previously, the :setting:`MEDIA_URL` setting only required a trailing slash if it contained a suffix beyond the domain name. @@ -581,7 +581,7 @@ gettext domain): * Now it is possible to override the translations shipped with applications by using the :setting:`LOCALE_PATHS` setting whose translations have now higher - precedence than the translations of ``INSTALLED_APPS`` applications. + precedence than the translations of :setting:`INSTALLED_APPS` applications. The relative priority among the values listed in this setting has also been modified so the paths listed first have higher precedence than the ones listed later. @@ -589,7 +589,7 @@ gettext domain): * The ``locale`` subdirectory of the directory containing the settings, that usually coincides with and is know as the *project directory* is being deprecated in this release as a source of translations. (the precedence of - these translations is intermediate between applications and ``LOCALE_PATHS`` + these translations is intermediate between applications and :setting:`LOCALE_PATHS` translations). See the `corresponding deprecated features section`_ of this document. diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index d2c9bbabe475..81d6baa50dd5 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -420,7 +420,7 @@ Once the cache is set up, the simplest way to use caching is to cache your entire site. You'll need to add ``'django.middleware.cache.UpdateCacheMiddleware'`` and ``'django.middleware.cache.FetchFromCacheMiddleware'`` to your -``MIDDLEWARE_CLASSES`` setting, as in this example:: +:setting:`MIDDLEWARE_CLASSES` setting, as in this example:: MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', diff --git a/docs/topics/email.txt b/docs/topics/email.txt index ea6212029dd3..16da675a0bec 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -509,8 +509,8 @@ Defining a custom e-mail backend -------------------------------- If you need to change how e-mails are sent you can write your own e-mail -backend. The ``EMAIL_BACKEND`` setting in your settings file is then the -Python import path for your backend class. +backend. The :setting:`EMAIL_BACKEND` setting in your settings file is then +the Python import path for your backend class. Custom e-mail backends should subclass ``BaseEmailBackend`` that is located in the ``django.core.mail.backends.base`` module. A custom e-mail backend must diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index af4ab6423a47..532695334a7f 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -225,8 +225,8 @@ Upload Handlers When a user uploads a file, Django passes off the file data to an *upload handler* -- a small class that handles file data as it gets uploaded. Upload -handlers are initially defined in the ``FILE_UPLOAD_HANDLERS`` setting, which -defaults to:: +handlers are initially defined in the :setting:`FILE_UPLOAD_HANDLERS` setting, +which defaults to:: ("django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler",) @@ -246,8 +246,8 @@ Modifying upload handlers on the fly Sometimes particular views require different upload behavior. In these cases, you can override upload handlers on a per-request basis by modifying ``request.upload_handlers``. By default, this list will contain the upload -handlers given by ``FILE_UPLOAD_HANDLERS``, but you can modify the list as you -would any other list. +handlers given by :setting:`FILE_UPLOAD_HANDLERS`, but you can modify the list +as you would any other list. For instance, suppose you've written a ``ProgressBarUploadHandler`` that provides feedback on upload progress to some sort of AJAX widget. You'd add this diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 1fd6a5c031fd..8529f5362d44 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -23,7 +23,7 @@ To enable session functionality, do the following: has ``SessionMiddleware`` activated. If you don't want to use sessions, you might as well remove the -``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and +``SessionMiddleware`` line from :setting:`MIDDLEWARE_CLASSES` and ``'django.contrib.sessions'`` from your :setting:`INSTALLED_APPS`. It'll save you a small bit of overhead. @@ -39,7 +39,7 @@ Using database-backed sessions ------------------------------ If you want to use a database-backed session, you need to add -``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting. +``'django.contrib.sessions'`` to your :setting:`INSTALLED_APPS` setting. Once you have configured your installation, run ``manage.py syncdb`` to install the single database table that stores session data. @@ -359,8 +359,8 @@ setting to ``True``. When set to ``True``, Django will save the session to the database on every single request. Note that the session cookie is only sent when a session has been created or -modified. If ``SESSION_SAVE_EVERY_REQUEST`` is ``True``, the session cookie -will be sent on every request. +modified. If :setting:`SESSION_SAVE_EVERY_REQUEST` is ``True``, the session +cookie will be sent on every request. Similarly, the ``expires`` part of a session cookie is updated each time the session cookie is sent. @@ -372,15 +372,15 @@ You can control whether the session framework uses browser-length sessions vs. persistent sessions with the :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` setting. -By default, ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``False``, which -means session cookies will be stored in users' browsers for as long as +By default, :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` is set to ``False``, +which means session cookies will be stored in users' browsers for as long as :setting:`SESSION_COOKIE_AGE`. Use this if you don't want people to have to log in every time they open a browser. -If ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``True``, Django will use -browser-length cookies -- cookies that expire as soon as the user closes his or -her browser. Use this if you want people to have to log in every time they open -a browser. +If :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` is set to ``True``, Django will +use browser-length cookies -- cookies that expire as soon as the user closes +his or her browser. Use this if you want people to have to log in every time +they open a browser. This setting is a global default and can be overwritten at a per-session level by explicitly calling the :meth:`~backends.base.SessionBase.set_expiry` method diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index cfdd00817b86..99359ab4ad3e 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -48,7 +48,7 @@ Let's step through this code one line at a time: .. admonition:: Django's Time Zone - Django includes a ``TIME_ZONE`` setting that defaults to + Django includes a :setting:`TIME_ZONE` setting that defaults to ``America/Chicago``. This probably isn't where you live, so you might want to change it in your settings file. diff --git a/docs/topics/i18n/deployment.txt b/docs/topics/i18n/deployment.txt index 75dfd879abeb..f06fa5e1917a 100644 --- a/docs/topics/i18n/deployment.txt +++ b/docs/topics/i18n/deployment.txt @@ -13,7 +13,7 @@ use internationalization, you should take the two seconds to set optimizations so as not to load the internationalization machinery. You'll probably also want to remove ``'django.core.context_processors.i18n'`` -from your ``TEMPLATE_CONTEXT_PROCESSORS`` setting. +from your :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. .. note:: @@ -47,22 +47,22 @@ Django uses this language as the default translation -- the final attempt if no other translator finds a translation. If all you want to do is run Django with your native language, and a language -file is available for it, all you need to do is set ``LANGUAGE_CODE``. +file is available for it, all you need to do is set :setting:`LANGUAGE_CODE`. If you want to let each individual user specify which language he or she prefers, use ``LocaleMiddleware``. ``LocaleMiddleware`` enables language selection based on data from the request. It customizes content for each user. To use ``LocaleMiddleware``, add ``'django.middleware.locale.LocaleMiddleware'`` -to your ``MIDDLEWARE_CLASSES`` setting. Because middleware order matters, you -should follow these guidelines: +to your :setting:`MIDDLEWARE_CLASSES` setting. Because middleware order +matters, you should follow these guidelines: * Make sure it's one of the first middlewares installed. * It should come after ``SessionMiddleware``, because ``LocaleMiddleware`` makes use of session data. * If you use ``CacheMiddleware``, put ``LocaleMiddleware`` after it. -For example, your ``MIDDLEWARE_CLASSES`` might look like this:: +For example, your :setting:`MIDDLEWARE_CLASSES` might look like this:: MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', @@ -81,7 +81,7 @@ following this algorithm: * Failing that, it looks for a cookie. - The name of the cookie used is set by the ``LANGUAGE_COOKIE_NAME`` + The name of the cookie used is set by the :setting:`LANGUAGE_COOKIE_NAME` setting. (The default name is ``django_language``.) * Failing that, it looks at the ``Accept-Language`` HTTP header. This @@ -89,7 +89,7 @@ following this algorithm: prefer, in order by priority. Django tries each language in the header until it finds one with available translations. - * Failing that, it uses the global ``LANGUAGE_CODE`` setting. + * Failing that, it uses the global :setting:`LANGUAGE_CODE` setting. .. _locale-middleware-notes: @@ -107,7 +107,7 @@ Notes: * Only languages listed in the :setting:`LANGUAGES` setting can be selected. If you want to restrict the language selection to a subset of provided languages (because your application doesn't provide all those languages), - set ``LANGUAGES`` to a list of languages. For example:: + set :setting:`LANGUAGES` to a list of languages. For example:: LANGUAGES = ( ('de', _('German')), @@ -118,7 +118,7 @@ Notes: selection to German and English (and any sublanguage, like de-ch or en-us). - * If you define a custom ``LANGUAGES`` setting, as explained in the + * If you define a custom :setting:`LANGUAGES` setting, as explained in the previous bullet, it's OK to mark the languages as translation strings -- but use a "dummy" ``ugettext()`` function, not the one in ``django.utils.translation``. You should *never* import @@ -139,7 +139,8 @@ Notes: With this arrangement, ``django-admin.py makemessages`` will still find and mark these strings for translation, but the translation won't happen at runtime -- so you'll have to remember to wrap the languages in the - *real* ``ugettext()`` in any code that uses ``LANGUAGES`` at runtime. + *real* ``ugettext()`` in any code that uses :setting:`LANGUAGES` at + runtime. * The ``LocaleMiddleware`` can only select languages for which there is a Django-provided base translation. If you want to provide translations diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 368b616bbb54..daf7bd71146b 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -673,9 +673,9 @@ You hook it up like this:: ) Each string in ``packages`` should be in Python dotted-package syntax (the -same format as the strings in ``INSTALLED_APPS``) and should refer to a package -that contains a ``locale`` directory. If you specify multiple packages, all -those catalogs are merged into one catalog. This is useful if you have +same format as the strings in :setting:`INSTALLED_APPS`) and should refer to a +package that contains a ``locale`` directory. If you specify multiple packages, +all those catalogs are merged into one catalog. This is useful if you have JavaScript that uses strings from different applications. The precedence of translations is such that the packages appearing later in the @@ -706,8 +706,8 @@ the ones appearing first having higher precedence than the ones appearing later. .. versionchanged:: 1.3 - Directories listed in ``LOCALE_PATHS`` weren't included in the lookup - algorithm until version 1.3. + Directories listed in :setting:`LOCALE_PATHS` weren't included in the + lookup algorithm until version 1.3. Using the JavaScript translation catalog ---------------------------------------- diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index bad85ce784b8..540de869b25b 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -486,7 +486,7 @@ Python logging module. The ``include_html`` argument of ``AdminEmailHandler`` is used to control whether the traceback e-mail includes an HTML attachment containing the full content of the debug Web page that would have been - produced if ``DEBUG`` were ``True``. To set this value in your + produced if :setting:`DEBUG` were ``True``. To set this value in your configuration, include it in the handler definition for ``django.utils.log.AdminEmailHandler``, like this:: diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index 6d96190a9012..61ddf8cf32eb 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -209,7 +209,7 @@ provides the default settings as the ``default_settings`` argument (or as the first positional argument) in the call to ``configure()``. In this example, default settings are taken from ``myapp_defaults``, and the -``DEBUG`` setting is set to ``True``, regardless of its value in +:setting:`DEBUG` setting is set to ``True``, regardless of its value in ``myapp_defaults``:: from django.conf import settings diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 5e8748842393..83269aeb8ed1 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -101,8 +101,8 @@ In the above example, ``{{ section.title }}`` will be replaced with the ``title`` attribute of the ``section`` object. If you use a variable that doesn't exist, the template system will insert -the value of the ``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''`` -(the empty string) by default. +the value of the :setting:`TEMPLATE_STRING_IF_INVALID` setting, which is set +to ``''`` (the empty string) by default. Filters ======= diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index ffbffe7495d5..165395525ac2 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -383,7 +383,7 @@ entirely!). If you want to use a different database name, specify Aside from using a separate database, the test runner will otherwise use all of the same database settings you have in your settings file: :setting:`ENGINE`, :setting:`USER`, :setting:`HOST`, etc. The test -database is created by the user specified by ``USER``, so you'll need +database is created by the user specified by :setting:`USER`, so you'll need to make sure that the given user account has sufficient privileges to create a new database on the system. @@ -1309,8 +1309,8 @@ In order to provide a reliable URL space for your test, ``django.test.TestCase`` provides the ability to customize the URLconf configuration for the duration of the execution of a test suite. If your ``TestCase`` instance defines an ``urls`` attribute, the ``TestCase`` will use -the value of that attribute as the ``ROOT_URLCONF`` for the duration of that -test. +the value of that attribute as the :setting:`ROOT_URLCONF` for the duration +of that test. For example:: From 4124ef339c2827d1a337c1b7d58fa50676349412 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Mon, 30 May 2011 12:11:46 +0000 Subject: [PATCH 064/225] [1.3.X] Fixed #16093 - Typo in "Performing raw SQL queries"; thanks direvus. Backport of r16293 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16294 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/sql.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index e5f6c21a533c..fe7173609c50 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -228,7 +228,7 @@ is required. For example:: If you are using more than one database you can use ``django.db.connections`` to obtain the connection (and cursor) for a specific database. ``django.db.connections`` is a dictionary-like -object that allows you to retrieve a specific connection using it's +object that allows you to retrieve a specific connection using its alias:: from django.db import connections From 6e87dacf62e742b78787659b9a51561fd5465682 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 30 May 2011 16:19:53 +0000 Subject: [PATCH 065/225] [1.3.X] Fixed #15776 - delete regression in Django 1.3 involving nullable foreign keys Many thanks to aaron.l.madison for the detailed report and to emulbreh for the fix. Backport of [16295] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16296 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/deletion.py | 6 +++--- tests/regressiontests/delete_regress/models.py | 16 ++++++++++++++++ tests/regressiontests/delete_regress/tests.py | 10 +++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 73960f5e3c1d..8f023f41092d 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -83,8 +83,8 @@ def __init__(self, using): def add(self, objs, source=None, nullable=False, reverse_dependency=False): """ Adds 'objs' to the collection of objects to be deleted. If the call is - the result of a cascade, 'source' should be the model that caused it - and 'nullable' should be set to True, if the relation can be null. + the result of a cascade, 'source' should be the model that caused it, + and 'nullable' should be set to True if the relation can be null. Returns a list of all objects that were not already collected. """ @@ -100,7 +100,7 @@ def add(self, objs, source=None, nullable=False, reverse_dependency=False): # Nullable relationships can be ignored -- they are nulled out before # deleting, and therefore do not affect the order in which objects have # to be deleted. - if new_objs and source is not None and not nullable: + if source is not None and not nullable: if reverse_dependency: source, model = model, source self.dependencies.setdefault(source, set()).add(model) diff --git a/tests/regressiontests/delete_regress/models.py b/tests/regressiontests/delete_regress/models.py index 5c77117719a0..8e3d0d55b155 100644 --- a/tests/regressiontests/delete_regress/models.py +++ b/tests/regressiontests/delete_regress/models.py @@ -51,3 +51,19 @@ class Food(models.Model): class Eaten(models.Model): food = models.ForeignKey(Food, to_field="name") meal = models.CharField(max_length=20) + + +# Models for #15776 + +class Policy(models.Model): + policy_number = models.CharField(max_length=10) + +class Version(models.Model): + policy = models.ForeignKey(Policy) + +class Location(models.Model): + version = models.ForeignKey(Version, blank=True, null=True) + +class Item(models.Model): + version = models.ForeignKey(Version) + location = models.ForeignKey(Location, blank=True, null=True) diff --git a/tests/regressiontests/delete_regress/tests.py b/tests/regressiontests/delete_regress/tests.py index 20b9382c3152..2027780329e0 100644 --- a/tests/regressiontests/delete_regress/tests.py +++ b/tests/regressiontests/delete_regress/tests.py @@ -5,7 +5,8 @@ from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith, - PlayedWithNote, Contact, Email, Researcher, Food, Eaten) + PlayedWithNote, Contact, Email, Researcher, Food, Eaten, + Policy, Version, Location, Item) # Can't run this test under SQLite, because you can't @@ -102,6 +103,13 @@ def test_fk_to_m2m_through(self): # first two asserts just sanity checks, this is the kicker: self.assertEqual(PlayedWithNote.objects.count(), 0) + def test_15776(self): + policy = Policy.objects.create(pk=1, policy_number="1234") + version = Version.objects.create(policy=policy) + location = Location.objects.create(version=version) + item = Item.objects.create(version=version, location=location) + policy.delete() + class DeleteCascadeTransactionTests(TransactionTestCase): def test_inheritance(self): From d2abec535e33ee781a6d8909840b88048d034970 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Tue, 31 May 2011 09:44:18 +0000 Subject: [PATCH 066/225] [1.3.X] Fixed #15801 - Incorrect external link for dictConfig; thanks David Niergarth for the report; jonash for the patch. Backport of r16100 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16301 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 2 +- docs/topics/logging.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 81ff9e39a609..6d69a085d9ff 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1196,7 +1196,7 @@ configuration method by default. If you set :setting:`LOGGING_CONFIG` to ``None``, the logging configuration process will be skipped. -.. _dictConfig: http://docs.python.org/library/logging.html#logging.dictConfig +.. _dictConfig: http://docs.python.org/library/logging.config.html#configuration-dictionary-schema .. setting:: LOGIN_REDIRECT_URL diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 540de869b25b..64becd1799f9 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -225,7 +225,7 @@ Since the loading of settings is one of the first things that Django does, you can be certain that loggers are always ready for use in your project code. -.. _dictConfig format: http://docs.python.org/library/logging.html#configuration-dictionary-schema +.. _dictConfig format: http://docs.python.org/library/logging.config.html#configuration-dictionary-schema .. _a third-party library: http://bitbucket.org/vinay.sajip/dictconfig From 9f71bef7e993e3a12d36700fafff62d294b8f250 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Tue, 31 May 2011 09:44:21 +0000 Subject: [PATCH 067/225] [1.3.X] Fixed #16090, #16091 - Typos in docs; thanks teraom. Backport of r16300 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16302 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/class-based-views.txt | 2 +- docs/ref/models/instances.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/class-based-views.txt b/docs/ref/class-based-views.txt index 79ff235f2ab2..2698a8a3828a 100644 --- a/docs/ref/class-based-views.txt +++ b/docs/ref/class-based-views.txt @@ -431,7 +431,7 @@ FormMixin .. method:: get_form_kwargs() - Build the keyword arguments requried to instanciate an the form. + Build the keyword arguments required to instantiate the form. The ``initial`` argument is set to :meth:`.get_initial`. If the request is a ``POST`` or ``PUT``, the request data (``request.POST`` diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 02b9faed2257..3728a098048a 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -38,7 +38,7 @@ There are three steps involved in validating a model: 2. Validate the model as a whole 3. Validate the field uniqueness -All three steps are performed when you call by a model's +All three steps are performed when you call a model's ``full_clean()`` method. When you use a ``ModelForm``, the call to ``is_valid()`` will perform From 0e90de0a15de6308da07877bc482c07feb866442 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 2 Jun 2011 12:20:11 +0000 Subject: [PATCH 068/225] [1.3.X] Fixed #16144 - layout of admin changelist broken for RTL languages. Backport of [16314] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16315 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/css/rtl.css | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/media/css/rtl.css b/django/contrib/admin/media/css/rtl.css index b05537a26e92..c02241d13d34 100644 --- a/django/contrib/admin/media/css/rtl.css +++ b/django/contrib/admin/media/css/rtl.css @@ -46,6 +46,11 @@ th { float: left; } +thead th:first-child, +tfoot td:first-child { + border-left: 1px solid #ddd !important; +} + /* LAYOUT */ #user-tools { @@ -73,6 +78,19 @@ div.breadcrumbs { margin-right: 10px !important; } +/* SORTABLE TABLES */ + + +table thead th.sorted a { + padding-left: 13px; + padding-right: 0px; +} + +table thead th.ascending a, +table thead th.descending a { + background-position: left; +} + /* dashboard styles */ .dashboard .module table td a { @@ -102,7 +120,7 @@ div.breadcrumbs { border-right: 1px solid #ddd; } -.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { +.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right: 0px !important; margin-left: 160px !important; } @@ -123,6 +141,11 @@ div.breadcrumbs { margin-right:0 !important; } +#changelist table tbody td:first-child, #changelist table tbody th:first-child { + border-right: 0; + border-left: 1px solid #ddd; +} + /* FORMS */ .aligned label { From f578563291ba2f2112d99d138e1ca8cbdeec4eea Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 4 Jun 2011 14:39:16 +0000 Subject: [PATCH 069/225] [1.3.X] Fixed #16145 - typo in manager docs; thanks leereilly. Backport of r16324 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16325 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/managers.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt index d9a144bd6202..cbe381d46e7d 100644 --- a/docs/topics/db/managers.txt +++ b/docs/topics/db/managers.txt @@ -371,9 +371,9 @@ in ``get_query_set()`` is not appropriate for use as an automatic manager. Set ``use_for_related_fields`` when you define the class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``use_for_related_fields`` attribute must be set on the manager *class*, -object not on an *instance* of the class. The earlier example shows the -correct way to set it, whereas the following will not work:: +The ``use_for_related_fields`` attribute must be set on the manager *class*, not +on an *instance* of the class. The earlier example shows the correct way to set +it, whereas the following will not work:: # BAD: Incorrect code class MyManager(models.Manager): From c1baaa8c8799750f66d84d9ab35ce332596107ea Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sat, 4 Jun 2011 15:31:41 +0000 Subject: [PATCH 070/225] [1.3.X] Fix #15880: Prevent "stalling" when running dev server in background by ignoring SIGTTOU for the duration of tcsetattr. Backport of [16326] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16327 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/autoreload.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index ffa75e2c4f45..ec7b10306978 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -28,7 +28,7 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import os, sys, time +import os, sys, time, signal try: import thread @@ -78,7 +78,13 @@ def ensure_echo_on(): attr_list = termios.tcgetattr(fd) if not attr_list[3] & termios.ECHO: attr_list[3] |= termios.ECHO + if hasattr(signal, 'SIGTTOU'): + old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN) + else: + old_handler = None termios.tcsetattr(fd, termios.TCSANOW, attr_list) + if old_handler is not None: + signal.signal(signal.SIGTTOU, old_handler) def reloader_thread(): ensure_echo_on() From a5b44ed873e4a12eb958033859a52af43f691f85 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 10 Jun 2011 16:19:56 +0000 Subject: [PATCH 071/225] [1.3.X] Refs #15855 -- Recommended the csrf_protect decorator rather than vary_on_cookie as workaround for cache_page caching the response before it gets to middleware. Backport of r16361 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16362 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/csrf.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index f3b95a11d37d..4edccdd39853 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -408,15 +408,16 @@ middleware if it is used as instructed (``UpdateCacheMiddleware`` goes before all other middleware). However, if you use cache decorators on individual views, the CSRF middleware -will not yet have been able to set the Vary header. In this case, on any views -that will require a CSRF token to be inserted you should use the -:func:`django.views.decorators.vary.vary_on_cookie` decorator first:: +will not yet have been able to set the Vary header or the CSRF cookie, and the +response will be cached without either one. In this case, on any views that +will require a CSRF token to be inserted you should use the +:func:`django.views.decorators.csrf.csrf_protect` decorator first:: from django.views.decorators.cache import cache_page - from django.views.decorators.vary import vary_on_cookie + from django.views.decorators.csrf import csrf_protect @cache_page(60 * 15) - @vary_on_cookie + @csrf_protect def my_view(request): # ... From 1b51aa74b80d60ae3460e5fb29efbbdc3464c365 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 11 Jun 2011 09:35:36 +0000 Subject: [PATCH 072/225] [1.3.X] Fixed #16158 - Changed FALLBACK_DYLD_LIBRARY_PATH to DYLD_FALLBACK_LIBRARY_PATH in GIS documentation; thanks adam for the report. Backport of r16364 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16365 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/gis/install.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 82df82793c00..3c2dcdc333d9 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -904,10 +904,10 @@ Summary:: export PATH=/opt/local/bin:/opt/local/lib/postgresql83/bin - In addition, add the ``FALLBACK_DYLD_LIBRARY_PATH`` setting so that + In addition, add the ``DYLD_FALLBACK_LIBRARY_PATH`` setting so that the libraries can be found by Python:: - export FALLBACK_DYLD_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql83 + export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib:/opt/local/lib/postgresql83 __ http://www.macports.org/ From 5d71bec5e46d1b4953327ff505a575f268c655b2 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 11 Jun 2011 23:41:28 +0000 Subject: [PATCH 073/225] [1.3.X] Fixed #15949 - Clarified the docs for password_reset_done view; thanks cyclops for the suggestion; Horst Gutmann for the patch. Backport of r16378 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16379 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index db7f18ccf3c4..635f18f8b2bf 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -982,7 +982,9 @@ includes a few other useful built-in views located in .. function:: password_reset_done(request[, template_name]) - The page shown after a user has reset their password. + The page shown after a user has been emailed a link to reset their + password. This view is called by default if the :func:`password_reset` view + doesn't have an explicit ``post_reset_redirect`` URL set. **Optional arguments:** From 4f215cfcd78f4f46d8ca7fe541c6f47e6201a789 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 12 Jun 2011 00:26:43 +0000 Subject: [PATCH 074/225] Fixed #15764 - Corrected mixin docs for DeleteView; thanks linovia for the report. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16380 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/class-based-views.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/class-based-views.txt b/docs/ref/class-based-views.txt index 2698a8a3828a..b1dbd822095a 100644 --- a/docs/ref/class-based-views.txt +++ b/docs/ref/class-based-views.txt @@ -1062,8 +1062,8 @@ DeleteView **Mixins** - * :class:`django.views.generic.edit.ModelFormMixin` - * :class:`django.views.generic.edit.ProcessFormView` + * :class:`django.views.generic.edit.DeletionMixin` + * :class:`django.views.generic.detail.BaseDetailView` **Notes** From eb96665b7a176224d7287e50cd710708cb24d6ef Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 15 Jun 2011 10:50:08 +0000 Subject: [PATCH 075/225] [1.3.X] Added a few cross references to the i18n docs and documented pgettext and colleagues. Backport from trunk (r16403). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16404 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/utils.txt | 57 +++++++++++ docs/topics/i18n/internationalization.txt | 111 ++++++++++------------ 2 files changed, 105 insertions(+), 63 deletions(-) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 0e8aaaf84fed..457d734116c8 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -315,6 +315,48 @@ Atom1Feed Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html +``django.utils.functional`` +=========================== + +.. module:: django.utils.functional + :synopsis: Functional programming tools. + +.. function:: allow_lazy(func, *resultclasses) + + Django offers many utility functions (particularly in ``django.utils``) that + take a string as their first argument and do something to that string. These + functions are used by template filters as well as directly in other code. + + If you write your own similar functions and deal with translations, you'll + face the problem of what to do when the first argument is a lazy translation + object. You don't want to convert it to a string immediately, because you might + be using this function outside of a view (and hence the current thread's locale + setting will not be correct). + + For cases like this, use the ``django.utils.functional.allow_lazy()`` + decorator. It modifies the function so that *if* it's called with a lazy + translation as the first argument, the function evaluation is delayed until it + needs to be converted to a string. + + For example:: + + from django.utils.functional import allow_lazy + + def fancy_utility_function(s, ...): + # Do some conversion on string 's' + ... + fancy_utility_function = allow_lazy(fancy_utility_function, unicode) + + The ``allow_lazy()`` decorator takes, in addition to the function to decorate, + a number of extra arguments (``*args``) specifying the type(s) that the + original function can return. Usually, it's enough to include ``unicode`` here + and ensure that your function returns only Unicode strings. + + Using this decorator means you can write your function and assume that the + input is a proper string, then add support for lazy translation objects at the + end. + + ``django.utils.http`` ===================== @@ -428,14 +470,23 @@ For a complete discussion on the usage of the following see the Translates ``message`` and returns it in a unicode string +.. function:: pgettext(context, message) + + Translates ``message`` given the ``context`` and returns + it in a unicode string. + + For more information, see :ref:`contextual-markers`. + .. function:: gettext_lazy(message) .. function:: ugettext_lazy(message) +.. function:: pgettext_lazy(context, message) Same as the non-lazy versions above, but using lazy execution. See :ref:`lazy translations documentation `. .. function:: gettext_noop(message) +.. function:: ugettext_noop(message) Marks strings for translation but doesn't translate them now. This can be used to store strings in global variables that should stay in the base @@ -452,8 +503,14 @@ For a complete discussion on the usage of the following see the Translates ``singular`` and ``plural`` and returns the appropriate string based on ``number`` in a unicode string. +.. function:: npgettext(context, singular, plural, number) + + Translates ``singular`` and ``plural`` and returns the appropriate string + based on ``number`` and the ``context`` in a unicode string. + .. function:: ngettext_lazy(singular, plural, number) .. function:: ungettext_lazy(singular, plural, number) +.. function:: npgettext_lazy(singular, plural, number) Same as the non-lazy versions above, but using lazy execution. diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index daf7bd71146b..915043ca932b 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -2,6 +2,8 @@ Internationalization ==================== +.. module:: django.utils.translation + Overview ======== @@ -24,19 +26,22 @@ Specifying translation strings: In Python code Standard translation -------------------- -Specify a translation string by using the function ``ugettext()``. It's -convention to import this as a shorter alias, ``_``, to save typing. +Specify a translation string by using the function +:func:`~django.utils.translation.ugettext`. It's convention to import this +as a shorter alias, ``_``, to save typing. .. note:: Python's standard library ``gettext`` module installs ``_()`` into the global namespace, as an alias for ``gettext()``. In Django, we have chosen not to follow this practice, for a couple of reasons: - 1. For international character set (Unicode) support, ``ugettext()`` is - more useful than ``gettext()``. Sometimes, you should be using - ``ugettext_lazy()`` as the default translation method for a particular - file. Without ``_()`` in the global namespace, the developer has to - think about which is the most appropriate translation function. + 1. For international character set (Unicode) support, + :func:`~django.utils.translation.ugettext` is more useful than + ``gettext()``. Sometimes, you should be using + :func:`~django.utils.translation.ugettext_lazy` as the default + translation method for a particular file. Without ``_()`` in the + global namespace, the developer has to think about which is the + most appropriate translation function. 2. The underscore character (``_``) is used to represent "the previous result" in Python's interactive shell and doctest tests. Installing a @@ -127,20 +132,20 @@ displayed by most translation tools. Marking strings as no-op ------------------------ -Use the function ``django.utils.translation.ugettext_noop()`` to mark a string -as a translation string without translating it. The string is later translated -from a variable. +Use the function :func:`django.utils.translation.ugettext_noop()` to mark a +string as a translation string without translating it. The string is later +translated from a variable. Use this if you have constant strings that should be stored in the source -language because they are exchanged over systems or users -- such as strings in -a database -- but should be translated at the last possible point in time, such -as when the string is presented to the user. +language because they are exchanged over systems or users -- such as strings +in a database -- but should be translated at the last possible point in time, +such as when the string is presented to the user. Pluralization ------------- -Use the function ``django.utils.translation.ungettext()`` to specify pluralized -messages. +Use the function :func:`django.utils.translation.ungettext()` to specify +pluralized messages. ``ungettext`` takes three arguments: the singular translation string, the plural translation string and the number of objects. @@ -155,14 +160,18 @@ of its value.) For example:: from django.utils.translation import ungettext + def hello_world(request, count): - page = ungettext('there is %(count)d object', 'there are %(count)d objects', count) % { + page = ungettext( + 'there is %(count)d object', + 'there are %(count)d objects', + count) % { 'count': count, } return HttpResponse(page) -In this example the number of objects is passed to the translation languages as -the ``count`` variable. +In this example the number of objects is passed to the translation +languages as the ``count`` variable. Lets see a slightly more complex usage example:: @@ -226,8 +235,8 @@ Contextual markers Sometimes words have several meanings, such as ``"May"`` in English, which refers to a month name and to a verb. To enable translators to translate these words correctly in different contexts, you can use the -``django.utils.translation.pgettext()`` function, or the -``django.utils.translation.npgettext()`` function if the string needs +:func:`django.utils.translation.pgettext()` function, or the +:func:`django.utils.translation.npgettext()` function if the string needs pluralization. Both take a context string as the first variable. In the resulting .po file, the string will then appear as often as there are @@ -241,6 +250,14 @@ For example:: month = pgettext("month name", "May") +or:: + + from django.utils.translation import pgettext_lazy + + class MyThing(models.Model): + name = models.CharField(help_text=pgettext_lazy( + 'help text for MyThing model', 'This is the help text')) + will appear in the .po file as: .. code-block:: po @@ -254,7 +271,7 @@ will appear in the .po file as: Lazy translation ---------------- -Use the function ``django.utils.translation.ugettext_lazy()`` to translate +Use the function :func:`django.utils.translation.ugettext_lazy()` to translate strings lazily -- when the value is accessed rather than when the ``ugettext_lazy()`` function is called. @@ -308,6 +325,7 @@ name:: class MyThing(models.Model): name = models.CharField(_('name'), help_text=_('This is the help text')) + class Meta: verbose_name = _('my thing') verbose_name_plural = _('mythings') @@ -360,9 +378,9 @@ Joining strings: string_concat() Standard Python string joins (``''.join([...])``) will not work on lists containing lazy translation objects. Instead, you can use -``django.utils.translation.string_concat()``, which creates a lazy object that -concatenates its contents *and* converts them to strings only when the result -is included in a string. For example:: +:func:`django.utils.translation.string_concat()`, which creates a lazy object +that concatenates its contents *and* converts them to strings only when the +result is included in a string. For example:: from django.utils.translation import string_concat ... @@ -374,47 +392,13 @@ In this case, the lazy translations in ``result`` will only be converted to strings when ``result`` itself is used in a string (usually at template rendering time). -The allow_lazy() decorator -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Django offers many utility functions (particularly in ``django.utils``) that -take a string as their first argument and do something to that string. These -functions are used by template filters as well as directly in other code. - -If you write your own similar functions and deal with translations, you'll -face the problem of what to do when the first argument is a lazy translation -object. You don't want to convert it to a string immediately, because you might -be using this function outside of a view (and hence the current thread's locale -setting will not be correct). - -For cases like this, use the ``django.utils.functional.allow_lazy()`` -decorator. It modifies the function so that *if* it's called with a lazy -translation as the first argument, the function evaluation is delayed until it -needs to be converted to a string. - -For example:: - - from django.utils.functional import allow_lazy - - def fancy_utility_function(s, ...): - # Do some conversion on string 's' - ... - fancy_utility_function = allow_lazy(fancy_utility_function, unicode) - -The ``allow_lazy()`` decorator takes, in addition to the function to decorate, -a number of extra arguments (``*args``) specifying the type(s) that the -original function can return. Usually, it's enough to include ``unicode`` here -and ensure that your function returns only Unicode strings. +Localized names of languages +============================ -Using this decorator means you can write your function and assume that the -input is a proper string, then add support for lazy translation objects at the -end. +.. function:: get_language_info .. versionadded:: 1.3 -Localized names of languages -============================ - The ``get_language_info()`` function provides detailed information about languages:: @@ -456,7 +440,8 @@ require translation in the future:: {% trans "myvar" noop %} -Internally, inline translations use an ``ugettext`` call. +Internally, inline translations use an +:func:`~django.utils.translation.ugettext` call. In case a template var (``myvar`` above) is passed to the tag, the tag will first resolve such variable to a string at run-time and then look up that @@ -777,7 +762,7 @@ The ``set_language`` redirect view .. function:: set_language(request) -As a convenience, Django comes with a view, :meth:`django.views.i18n.set_language`, +As a convenience, Django comes with a view, :func:`django.views.i18n.set_language`, that sets a user's language preference and redirects back to the previous page. Activate this view by adding the following line to your URLconf:: From 25ee9b49139eec49ac2a44439f86fc1006e88a58 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 16 Jun 2011 15:28:12 +0000 Subject: [PATCH 076/225] [1.3.X] Fixed #16273 -- Fixed typo in staticfiles docs. Thanks, BernhardEssl. Backport from trunk (r16407). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16409 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/static-files.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 215b927f6f73..3d2a37e97288 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -68,7 +68,7 @@ Basic usage .. code-block:: html+django - See :ref:`staticfiles-in-templates` for more details, including an alternate method using a template tag. @@ -167,7 +167,7 @@ Once that's done, you can refer to :setting:`STATIC_URL` in your templates: .. code-block:: html+django - If ``{{ STATIC_URL }}`` isn't working in your template, you're probably not using :class:`~django.template.RequestContext` when rendering the template. From 7880d999005170745040d1725ab0d8899235a794 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 16 Jun 2011 16:42:54 +0000 Subject: [PATCH 077/225] [1.3.X] Fixed #16031 -- Corrected comments template examples. Thanks, teraom. Backport from trunk (r16412). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16421 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/comments/example.txt | 17 ++++++++++------- docs/ref/contrib/comments/index.txt | 19 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/docs/ref/contrib/comments/example.txt b/docs/ref/contrib/comments/example.txt index 921fe03ffae0..253701b8da1f 100644 --- a/docs/ref/contrib/comments/example.txt +++ b/docs/ref/contrib/comments/example.txt @@ -103,13 +103,16 @@ But let's look at a simple example:: - - {{ form }} - - - - - + + {% csrf_token %} + {{ form }} + + + +
+ + +
Flagging diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 817871e97654..7072ed14ac91 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -218,13 +218,18 @@ you can use in the template:: A complete form might look like:: {% get_comment_form for event as form %} -
- {{ form }} - - - - -
+ + + {% csrf_token %} + {{ form }} + + + + +
+ + +
Be sure to read the `notes on the comment form`_, below, for some special considerations you'll need to make if you're using this approach. From d90bd88d734b1327661c0718601ec22708520aa6 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sat, 18 Jun 2011 12:01:10 +0000 Subject: [PATCH 078/225] [1.3.X] Fixed #16292 -- Removed broken link for Swiss localflavor documentation. Thanks, BernhardEssl. Backport from trunk (r16435). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16436 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/localflavor/ch/forms.py | 1 - docs/ref/contrib/localflavor.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py index eb1ae68844a7..1e268370ca95 100644 --- a/django/contrib/localflavor/ch/forms.py +++ b/django/contrib/localflavor/ch/forms.py @@ -59,7 +59,6 @@ class CHIdentityCardNumberField(Field): * Conforms to the X1234567<0 or 1234567890 format. * Included checksums match calculated checksums - Algorithm is documented at http://adi.kousz.ch/artikel/IDCHE.htm """ default_error_messages = { 'invalid': _('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'), diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index fa92cb96f57c..a9a48802f55c 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -849,7 +849,7 @@ Switzerland (``ch``) A form field that validates input as a Swiss identity card number. A valid number must confirm to the X1234567<0 or 1234567890 format and - have the correct checksums -- see http://adi.kousz.ch/artikel/IDCHE.htm. + have the correct checksums. .. class:: ch.forms.CHPhoneNumberField From a0285bb612a607d1af6a6a5afdf7205434a4ec7a Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 19 Jun 2011 19:40:50 +0000 Subject: [PATCH 079/225] [1.3.X] Fixed #16258 - typo in middleware docs. Backport of r16441 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16442 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/middleware.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index cb9068484729..135322806161 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -6,7 +6,7 @@ Middleware :synopsis: Django's built-in middleware classes. This document explains all middleware components that come with Django. For -information on how how to use them and how to write your own middleware, see +information on how to use them and how to write your own middleware, see the :doc:`middleware usage guide `. Available middleware From d8ef686e2451c86e327af6a665e24b3a157d90b6 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sun, 26 Jun 2011 21:29:13 +0000 Subject: [PATCH 080/225] [1.3.X] Fixed #16297 -- make_list documentation error regarding integers. Thanks ned and teraom. Backport of r16468 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16469 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/templates/builtins.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 695bc871723b..7d24c1dc008e 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1714,8 +1714,9 @@ If ``value`` is ``Still MAD At Yoko``, the output will be ``still mad at yoko``. make_list ~~~~~~~~~ -Returns the value turned into a list. For an integer, it's a list of -digits. For a string, it's a list of characters. +Returns the value turned into a list. For a string, it's a list of characters. +For an integer, the argument is cast into an unicode string before creating a +list. For example:: @@ -1723,7 +1724,7 @@ For example:: If ``value`` is the string ``"Joel"``, the output would be the list ``[u'J', u'o', u'e', u'l']``. If ``value`` is ``123``, the output will be the -list ``[1, 2, 3]``. +list ``[u'1', u'2', u'3']``. .. templatefilter:: phone2numeric From a441032e0e5363f2151ccfe95150fb29c6907579 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 29 Jun 2011 16:40:31 +0000 Subject: [PATCH 081/225] [1.3.X] Fixed #16232 -- Corrected typo in geographic admin reference. Thanks, Issac Kelly. Backport of r16484 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16486 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/gis/admin.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/gis/admin.txt b/docs/ref/contrib/gis/admin.txt index 4ec10003f60e..aa6ba58630c9 100644 --- a/docs/ref/contrib/gis/admin.txt +++ b/docs/ref/contrib/gis/admin.txt @@ -50,7 +50,7 @@ GeoDjango's admin site .. attribute:: modifiable - Defaults to ``False``. When set to ``True``, disables editing of + Defaults to ``True``. When set to ``False``, disables editing of existing geometry fields in the admin. .. note:: From 5a0787f904945e2d1b18b65389b6f76a98b36bf5 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 1 Jul 2011 15:19:34 +0000 Subject: [PATCH 082/225] [1.3.X] Fixed #15974 -- Correctly link to static files handling in deployment docs. Thanks, RogueBean. Backport from trunk (r16491). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16492 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/deployment/modpython.txt | 40 ++++++++++++----------- docs/howto/deployment/modwsgi.txt | 50 ++++++++++++++++------------- docs/howto/static-files.txt | 2 +- docs/ref/contrib/admin/index.txt | 4 +-- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/docs/howto/deployment/modpython.txt b/docs/howto/deployment/modpython.txt index 2d4a9dafa4e4..f5030e9c750a 100644 --- a/docs/howto/deployment/modpython.txt +++ b/docs/howto/deployment/modpython.txt @@ -246,9 +246,9 @@ Django -- for serving media. Here are some good choices: * A stripped-down version of Apache_ * Cherokee_ -If, however, you have no option but to serve media files on the same Apache -``VirtualHost`` as Django, here's how you can turn off mod_python for a -particular part of the site:: +If, however, you have no option but to serve media or static files on the +same Apache ``VirtualHost`` as Django, here's how you can turn off mod_python +for a particular part of the site:: SetHandler None @@ -257,9 +257,9 @@ particular part of the site:: Just change ``Location`` to the root URL of your media files. You can also use ```` to match a regular expression. -This example sets up Django at the site root but explicitly disables Django for -the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or -``.png``:: +This example sets up Django at the site root but explicitly disables Django +for the ``media`` and ``static`` subdirectories and any URL that ends with +``.jpg``, ``.gif`` or ``.png``:: SetHandler python-program @@ -271,11 +271,14 @@ the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or SetHandler None + + SetHandler None + + SetHandler None - .. _lighttpd: http://www.lighttpd.net/ .. _Nginx: http://wiki.nginx.org/Main .. _TUX: http://en.wikipedia.org/wiki/TUX_web_server @@ -285,22 +288,21 @@ the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or Serving the admin files ======================= -Note that the Django development server automagically serves admin media files, -but this is not the case when you use any other server arrangement. You're -responsible for setting up Apache, or whichever media server you're using, to -serve the admin files. +Note that the Django development server automagically serves the static files +of the admin app, but this is not the case when you use any other server +arrangement. You're responsible for setting up Apache, or whichever media +server you're using, to serve the admin files. -The admin files live in (:file:`django/contrib/admin/media`) of the Django -distribution. +The admin files live in (:file:`django/contrib/admin/static/admin`) of the +Django distribution. -Here are two recommended approaches: +We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle +the admin files, but here are two other approaches: - 1. Create a symbolic link to the admin media files from within your - document root. This way, all of your Django-related files -- code **and** - templates -- stay in one place, and you'll still be able to ``svn - update`` your code to get the latest admin templates, if they change. + 1. Create a symbolic link to the admin static files from within your + document root. - 2. Or, copy the admin media files so that they live within your Apache + 2. Or, copy the admin static files so that they live within your Apache document root. Using "eggs" with mod_python diff --git a/docs/howto/deployment/modwsgi.txt b/docs/howto/deployment/modwsgi.txt index 0a24db97554a..de3a5b63aa1b 100644 --- a/docs/howto/deployment/modwsgi.txt +++ b/docs/howto/deployment/modwsgi.txt @@ -55,12 +55,12 @@ just below the ``import sys`` line to place your project on the path. Remember t replace 'mysite.settings' with your correct settings file, and '/path/to/mysite' with your own project's location. -.. _serving-media-files: +.. _serving-files: -Serving media files -=================== +Serving files +============= -Django doesn't serve media files itself; it leaves that job to whichever Web +Django doesn't serve files itself; it leaves that job to whichever Web server you choose. We recommend using a separate Web server -- i.e., one that's not also running @@ -76,22 +76,29 @@ If, however, you have no option but to serve media files on the same Apache ``VirtualHost`` as Django, you can set up Apache to serve some URLs as static media, and others using the mod_wsgi interface to Django. -This example sets up Django at the site root, but explicitly serves ``robots.txt``, -``favicon.ico``, any CSS file, and anything in the ``/media/`` URL space as a static -file. All other URLs will be served using mod_wsgi:: +This example sets up Django at the site root, but explicitly serves +``robots.txt``, ``favicon.ico``, any CSS file, and anything in the +``/static/`` and ``/media/`` URL space as a static file. All other URLs +will be served using mod_wsgi:: Alias /robots.txt /usr/local/wsgi/static/robots.txt Alias /favicon.ico /usr/local/wsgi/static/favicon.ico AliasMatch ^/([^/]*\.css) /usr/local/wsgi/static/styles/$1 - Alias /media/ /usr/local/wsgi/static/media/ + Alias /media/ /usr/local/wsgi/media/ + Alias /static/ /usr/local/wsgi/static/ Order deny,allow Allow from all + + Order deny,allow + Allow from all + + WSGIScriptAlias / /usr/local/wsgi/scripts/django.wsgi @@ -105,8 +112,8 @@ file. All other URLs will be served using mod_wsgi:: .. _Apache: http://httpd.apache.org/ .. _Cherokee: http://www.cherokee-project.com/ -More details on configuring a mod_wsgi site to serve static files can be found -in the mod_wsgi documentation on `hosting static files`_. +.. More details on configuring a mod_wsgi site to serve static files can be found +.. in the mod_wsgi documentation on `hosting static files`_. .. _hosting static files: http://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Hosting_Of_Static_Files @@ -115,22 +122,21 @@ in the mod_wsgi documentation on `hosting static files`_. Serving the admin files ======================= -Note that the Django development server automagically serves admin media files, -but this is not the case when you use any other server arrangement. You're -responsible for setting up Apache, or whichever media server you're using, to -serve the admin files. +Note that the Django development server automagically serves the static files +of the admin app, but this is not the case when you use any other server +arrangement. You're responsible for setting up Apache, or whichever media +server you're using, to serve the admin files. -The admin files live in (:file:`django/contrib/admin/media`) of the Django -distribution. +The admin files live in (:file:`django/contrib/admin/static/admin`) of the +Django distribution. -Here are two recommended approaches: +We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle +the admin files, but here are two other approaches: - 1. Create a symbolic link to the admin media files from within your - document root. This way, all of your Django-related files -- code **and** - templates -- stay in one place, and you'll still be able to ``svn - update`` your code to get the latest admin templates, if they change. + 1. Create a symbolic link to the admin static files from within your + document root. - 2. Or, copy the admin media files so that they live within your Apache + 2. Or, copy the admin static files so that they live within your Apache document root. Details diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 3d2a37e97288..d6e269770939 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -337,7 +337,7 @@ serving your site, the basic outline gets modified to look something like: * On the server, run :djadmin:`collectstatic` to copy all the static files into :setting:`STATIC_ROOT`. * Point your web server at :setting:`STATIC_ROOT`. For example, here's - :ref:`how to do this under Apache and mod_wsgi `. + :ref:`how to do this under Apache and mod_wsgi `. You'll probably want to automate this process, especially if you've got multiple web servers. There's any number of ways to do this automation, but diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 2fcafb4df856..beff94ee7bbb 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -46,8 +46,8 @@ Other topics .. seealso:: - For information about serving the media files (images, JavaScript, and CSS) - associated with the admin in production, see :ref:`serving-media-files`. + For information about serving the static files (images, JavaScript, and + CSS) associated with the admin in production, see :ref:`serving-files`. ``ModelAdmin`` objects ====================== From 00886dfd2bb20f5119ecb77e295fbe40b15f6901 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sun, 10 Jul 2011 21:33:42 +0000 Subject: [PATCH 083/225] [1.3.X] Fixed #16440 -- minor ungettext documentation issue, thanks Bradley Ayers. Backport of r16532 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16533 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 915043ca932b..5d50fa71f63c 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -154,7 +154,7 @@ This function is useful when you need your Django application to be localizable to languages where the number and complexity of `plural forms `_ is greater than the two forms used in English ('object' for the singular and -'objects' for all the cases where ``count`` is different from zero, irrespective +'objects' for all the cases where ``count`` is different from one, irrespective of its value.) For example:: From c828cc1ba62d9a4ebc144a081fff44c60d339e3a Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sun, 10 Jul 2011 21:40:14 +0000 Subject: [PATCH 084/225] [1.3.X] Fixed #15715 -- added non-trivial decorator example to CBV docs. Thanks toofishes. Backport of r16534 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16535 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/class-based-views.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index 0b1a9235354e..38310462c239 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -570,11 +570,14 @@ result of the :meth:`~django.views.generic.base.View.as_view` method. The easiest place to do this is in the URLconf where you deploy your view:: - from django.contrib.auth.decorators import login_required + from django.contrib.auth.decorators import login_required, permission_required from django.views.generic import TemplateView + from .views import VoteView + urlpatterns = patterns('', - (r'^about/',login_required(TemplateView.as_view(template_name="secret.html"))), + (r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))), + (r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())), ) This approach applies the decorator on a per-instance basis. If you From 220ce42333b8bff16b4b0b434cfc2570dcce9985 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sun, 10 Jul 2011 21:52:50 +0000 Subject: [PATCH 085/225] [1.3.X] Fixed #16000 -- reference natural keys in contenttypes documentation. Thanks jsdalton. Backport of r16536 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16537 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/contenttypes.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index cafbaf4a6296..e97116736d11 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -192,6 +192,15 @@ The ``ContentTypeManager`` :class:`~django.contrib.contenttypes.models.ContentType` instance representing that model. + .. method:: get_by_natural_key(app_label, model) + + Returns the :class:`~django.contrib.contenttypes.models.ContentType` + instance uniquely identified by the given application label and model + name. The primary purpose of this method is to allow + :class:`~django.contrib.contenttypes.models.ContentType` objects to be + referenced via a :ref:`natural key` + during deserialization. + The :meth:`~ContentTypeManager.get_for_model()` method is especially useful when you know you need to work with a :class:`ContentType ` but don't @@ -285,6 +294,15 @@ model: should evaluate the models you expect to be pointing to and determine which solution will be most effective for your use case. +.. admonition:: Serializing references to ``ContentType`` objects + + If you're serializing data (for example, when generating + :class:`~django.test.TestCase.fixtures`) from a model that implements + generic relations, you should probably be using a natural key to uniquely + identify related :class:`~django.contrib.contenttypes.models.ContentType` + objects. See :ref:`natural keys` and + :djadminopt:`dumpdata --natural <--natural>` for more information. + This will enable an API similar to the one used for a normal :class:`~django.db.models.ForeignKey`; each ``TaggedItem`` will have a ``content_object`` field that returns the From a925b3780e09986131059b7a12f78e968267e0fa Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Thu, 14 Jul 2011 00:27:55 +0000 Subject: [PATCH 086/225] [1.3.X] Reverted [14563] because it introduced a dependency from core on a contrib app (contenttypes). Fixes #16283, Refs #3055. Thanks TheRoSS for the report and Aymeric Augustin for finding the problem. This caused models shipped with some contrib apps to pollute the namespace when user's apps had the same name (e.g. auth, sites), even when these contrib apps weren't installed. This undesired loading of contrib apps happened when model validation was executed, for example when running management commands that set or inherit `requires_model_validation=True`: cleanup, dumpdata, flush, loaddata, reset, runfcgi, sql, sqlall, sqlclear, sqlcustom, sqlflush, sqlindexes, sqlinitialdata, sqlreset, sqlsequencereset, syncdb, createsuperusers, ping_google, collectstatic, findstatic. This could also cause hard to diagnose problems e.g. when performing reverse URL resolving. Backport of [16493] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16541 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/validation.py | 7 ------- tests/modeltests/invalid_models/models.py | 17 ----------------- 2 files changed, 24 deletions(-) diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 2eb6340de94c..6fdbfca0f024 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -1,6 +1,5 @@ import sys -from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation from django.core.management.color import color_style from django.utils.itercompat import is_iterable @@ -240,12 +239,6 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' specifies an m2m relation through model %s, " "which has not been installed" % (f.name, f.rel.through) ) - elif isinstance(f, GenericRelation): - if not any([isinstance(vfield, GenericForeignKey) for vfield in f.rel.to._meta.virtual_fields]): - e.add(opts, "Model '%s' must have a GenericForeignKey in " - "order to create a GenericRelation that points to it." - % f.rel.to.__name__ - ) rel_opts = f.rel.to._meta rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index ab9edd64614c..45f302413dda 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -4,7 +4,6 @@ This example exists purely to point out errors in models. """ -from django.contrib.contenttypes import generic from django.db import models class FieldErrors(models.Model): @@ -219,21 +218,6 @@ class InvalidSetNull(models.Model): class InvalidSetDefault(models.Model): fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT) -class Tag(models.Model): - name = models.CharField("name", max_length=20) - -class TaggedObject(models.Model): - object_id = models.PositiveIntegerField("Object ID") - tag = models.ForeignKey(Tag) - content_object = generic.GenericForeignKey() - -class UserTaggedObject(models.Model): - object_tag = models.ForeignKey(TaggedObject) - -class ArticleAttachment(models.Model): - tags = generic.GenericRelation(TaggedObject) - user_tags = generic.GenericRelation(UserTaggedObject) - model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer. @@ -343,5 +327,4 @@ class ArticleAttachment(models.Model): invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist. invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null. invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value. -invalid_models.articleattachment: Model 'UserTaggedObject' must have a GenericForeignKey in order to create a GenericRelation that points to it. """ From 2a1874521e92d569d9daf88567247bfbf0fcfc25 Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Thu, 14 Jul 2011 19:40:30 +0000 Subject: [PATCH 087/225] [1.3.X] Added a note about the AJAX CSRF example not working on jQuery 1.5 Backport of [16543] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16544 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/csrf.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index 4edccdd39853..04ca6d6fdaac 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -133,6 +133,11 @@ that allow headers to be set on every request. In jQuery, you can use the } }); +.. note:: + + Due to a bug introduced in jQuery 1.5, the example above will not work + correctly on that version. Make sure you are running at least jQuery 1.5.1. + Adding this to a javascript file that is included on your site will ensure that AJAX POST requests that are made via jQuery will not be caught by the CSRF protection. From 41e086cfb5c25901b39e7d0e387ca0a5425868f9 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Thu, 28 Jul 2011 22:10:27 +0000 Subject: [PATCH 088/225] =?UTF-8?q?[1.3.X]=20Fixes=20#16532=20--=20Clearer?= =?UTF-8?q?=20explanation=20of=20how=20the=20test=20client=20expects=20HTT?= =?UTF-8?q?P=20headers=20to=20be=20passed.=20Thanks=20for=20the=20patch,?= =?UTF-8?q?=20Ricardo=20B=C3=A1nffy.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of r16554 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16555 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 165395525ac2..325e73bb35c4 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -710,6 +710,15 @@ arguments at time of construction: details view, which is a good way to test code paths that use the :meth:`django.http.HttpRequest.is_ajax()` method. + .. admonition:: CGI specification + + The headers sent via ``**extra`` should follow CGI_ specification. + For example, emulating a different "Host" header as sent in the + HTTP request from the browser to the server should be passed + as ``HTTP_HOST``. + + .. _CGI: http://www.w3.org/CGI/ + If you already have the GET arguments in URL-encoded form, you can use that encoding instead of using the data argument. For example, the previous GET request could also be posed as:: From c0fa1965e2f45e1b56377216cac3b8154c430496 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 29 Jul 2011 09:48:05 +0000 Subject: [PATCH 089/225] =?UTF-8?q?[1.3.X]=20Fixed=20#16531=20--=20Fixed?= =?UTF-8?q?=20various=20instances=20of=20"undefined=20name"=20issues.=20Th?= =?UTF-8?q?anks,=20Bruno=20Reni=C3=A9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport from trunk (r16557). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16571 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/db/backends/base.py | 2 +- django/views/generic/dates.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py index 0eaacae63f5c..ba8775bcaf7f 100644 --- a/django/contrib/gis/db/backends/base.py +++ b/django/contrib/gis/db/backends/base.py @@ -122,7 +122,7 @@ def spatial_aggregate_sql(self, agg): raise NotImplementedError('Aggregate support not implemented for this spatial backend.') def spatial_lookup_sql(self, lvalue, lookup_type, value, field): - raise NotImplmentedError + raise NotImplementedError # Routines for getting the OGC-compliant models. def geometry_columns(self): diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index dc1f6e336fd5..0ba5ff4dd036 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -211,9 +211,9 @@ def get_date_list(self, queryset, date_type): date_list = queryset.dates(date_field, date_type)[::-1] if date_list is not None and not date_list and not allow_empty: - raise Http404(_(u"No %(verbose_name_plural)s available") % { - 'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural) - }) + name = force_unicode(queryset.model._meta.verbose_name_plural) + raise Http404(_(u"No %(verbose_name_plural)s available") % + {'verbose_name_plural': name}) return date_list From 1959aa939da799dea339014721a109d28c52ae2d Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 6 Aug 2011 18:41:00 +0000 Subject: [PATCH 090/225] [1.3.X] Fixed #16566 - Typo in docs/ref/files/storage.txt; thanks thejaswi_puthraya. Backport of r16580 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16581 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/files/storage.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/files/storage.txt b/docs/ref/files/storage.txt index 73a8fb095575..b3f890984753 100644 --- a/docs/ref/files/storage.txt +++ b/docs/ref/files/storage.txt @@ -75,7 +75,7 @@ The Storage Class .. method:: exists(name) - Returns ``True`` if a file referened by the given name already exists + Returns ``True`` if a file referenced by the given name already exists in the storage system, or ``False`` if the name is available for a new file. From fe96e20a3e8da374a1207caf9886b25b32bbbd26 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 6 Aug 2011 18:50:19 +0000 Subject: [PATCH 091/225] [1.3.X] Fixed #16580 - Typo in docs/ref/models/querysets.txt Backport of r16582 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16583 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/querysets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 5cb3fd43d589..3777d776eb26 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -672,7 +672,7 @@ This is also valid:: ...and would also pull in the ``building`` relation. You can refer to any ``ForeignKey`` or ``OneToOneField`` relation in -the list of fields passed to ``select_related``. Ths includes foreign +the list of fields passed to ``select_related``. This includes foreign keys that have ``null=True`` (unlike the default ``select_related()`` call). It's an error to use both a list of fields and the ``depth`` parameter in the same ``select_related()`` call, since they are From 4217f358c0b2765ec1824d994c5926ad7ad26a10 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 6 Aug 2011 18:54:40 +0000 Subject: [PATCH 092/225] [1.3.X] Fixed #16528 - Documented test runner returns 1, regardless of the number of test failures; thanks teraom. Backport of r16584 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16585 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 325e73bb35c4..0a86f0d1b9f4 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -555,7 +555,7 @@ A full explanation of this error output is beyond the scope of this document, but it's pretty intuitive. You can consult the documentation of Python's ``unittest`` library for details. -Note that the return code for the test-runner script is the total number of +Note that the return code for the test-runner script is 1 for any number of failed and erroneous tests. If all the tests pass, the return code is 0. This feature is useful if you're using the test-runner script in a shell script and need to test for success or failure at that level. From 199f10f9c09c5e5073c2e3a3a03f4a66d5c36855 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 6 Aug 2011 19:02:56 +0000 Subject: [PATCH 093/225] [1.3.X] Fixed #16513 - Add forms import to example; thanks teraom. Backport of r16586 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16587 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/forms/modelforms.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 99164ecdae28..07bc5e300165 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -182,6 +182,8 @@ With these models, the ``ModelForm`` subclasses above would be roughly equivalent to this (the only difference being the ``save()`` method, which we'll discuss in a moment.):: + from django import forms + class AuthorForm(forms.Form): name = forms.CharField(max_length=100) title = forms.CharField(max_length=3, From 3e5fc7ebb1ebbaa2d6815b8e5b98d8c845012bca Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 6 Aug 2011 20:34:19 +0000 Subject: [PATCH 094/225] [1.3.X] Fixed #16430 - Stronger wording for CSRF protection in `modifying upload handlers on the fly`; thanks tomchristie. Backport of r16588 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16589 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/file-uploads.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index 532695334a7f..b845772e979c 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -278,13 +278,13 @@ list:: Also, ``request.POST`` is accessed by :class:`~django.middleware.csrf.CsrfViewMiddleware` which is enabled by - default. This means you will probably need to use + default. This means you will need to use :func:`~django.views.decorators.csrf.csrf_exempt` on your view to allow you - to change the upload handlers. Assuming you do need CSRF protection, you - will then need to use :func:`~django.views.decorators.csrf.csrf_protect` on - the function that actually processes the request. Note that this means that - the handlers may start receiving the file upload before the CSRF checks have - been done. Example code: + to change the upload handlers. You will then need to use + :func:`~django.views.decorators.csrf.csrf_protect` on the function that + actually processes the request. Note that this means that the handlers may + start receiving the file upload before the CSRF checks have been done. + Example code: .. code-block:: python From 329d80faabc05ef896337b2f7bebbd60100a03cd Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 20 Aug 2011 19:23:16 +0000 Subject: [PATCH 095/225] [1.3.X] Fixed #16595 - Add pop() to session docs; thanks wilfred. Backport of r16628 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16629 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/sessions.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 8529f5362d44..eb076f4b7a2e 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -132,6 +132,10 @@ You can edit it multiple times. Example: ``fav_color = request.session.get('fav_color', 'red')`` + .. method:: pop(key) + + Example: ``fav_color = request.session.pop('fav_color')`` + .. method:: keys .. method:: items From 6f9d250698b55a021c67bd3c9e22c70fd52846f4 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 20 Aug 2011 19:28:25 +0000 Subject: [PATCH 096/225] [1.3.X] Fixed #16654 - Syntax error in reverse() example; thanks jedie. Backport of r16630 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16631 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/urls.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index d721012d3e74..1caa801c5d76 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -820,7 +820,7 @@ namespaces into URLs on specific application instances, according to the The string returned by :meth:`~django.core.urlresolvers.reverse` is already :ref:`urlquoted `. For example:: - >>> reverse('cities', args=u'Orléans') + >>> reverse('cities', args=[u'Orléans']) '.../Orl%C3%A9ans/' Applying further encoding (such as :meth:`~django.utils.http.urlquote` or From e71d0133bdd86b4d2941b4c6501fc37e1aecdee5 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Tue, 23 Aug 2011 05:59:54 +0000 Subject: [PATCH 097/225] [1.3.X] Fixed #16669 -- Made the startproject instruction formatting easier to read and more consistent with other formatting in the tutorial part 1. Thanks to Daniel Lawrence and Aymeric Augustin. Backport of r16664 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16665 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index bf9044e474ba..23c05ba3a4b8 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -36,8 +36,13 @@ including database configuration, Django-specific options and application-specific settings. From the command line, ``cd`` into a directory where you'd like to store your -code, then run the command ``django-admin.py startproject mysite``. This will -create a ``mysite`` directory in your current directory. +code, then run the following command: + +.. code-block:: bash + + django-admin.py startproject mysite + +This will create a ``mysite`` directory in your current directory. .. admonition:: Script name may differ in distribution packages From a7ec5c433c5545edea8ca4b9ef5240990bb1fd04 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Tue, 23 Aug 2011 06:00:08 +0000 Subject: [PATCH 098/225] [1.3.X] Fixed #16680 -- Used single quotes for the TEMPLATE_DIRS examples in part 2 of the tutorial to be consistent with the settings.py file generated by the startproject command. Thanks, Michael Tomkins. Backport of r16660 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16666 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial02.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index bae47c8696bd..4bd31fb99a90 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -405,14 +405,14 @@ By default, :setting:`TEMPLATE_DIRS` is empty. So, let's add a line to it, to tell Django where our templates live:: TEMPLATE_DIRS = ( - "/home/my_username/mytemplates", # Change this to your own directory. + '/home/my_username/mytemplates', # Change this to your own directory. ) Now copy the template ``admin/base_site.html`` from within the default Django admin template directory in the source code of Django itself (``django/contrib/admin/templates``) into an ``admin`` subdirectory of whichever directory you're using in :setting:`TEMPLATE_DIRS`. For example, if -your :setting:`TEMPLATE_DIRS` includes ``"/home/my_username/mytemplates"``, as +your :setting:`TEMPLATE_DIRS` includes ``'/home/my_username/mytemplates'``, as 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. From 671483f37b0e356023828cd98433777b53d95a33 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 23 Aug 2011 15:54:45 +0000 Subject: [PATCH 099/225] [1.3.X] Fixed #14876 -- Ensure that join promotion works correctly when there are nullable related fields. Thanks to simonpercivall for the report, oinopion and Aleksandra Sendecka for the original patch, and to Malcolm for helping me wrestle the edge cases to the ground. Backport of r16648 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16671 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/query.py | 50 ++++++++++++++++++-------- tests/regressiontests/queries/tests.py | 34 +++++++++++++++--- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index ea89771ed1b6..ca46e31192d7 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -440,8 +440,6 @@ def combine(self, rhs, connector): "Cannot combine a unique query with a non-unique query." self.remove_inherited_models() - l_tables = set([a for a in self.tables if self.alias_refcount[a]]) - r_tables = set([a for a in rhs.tables if rhs.alias_refcount[a]]) # Work out how to relabel the rhs aliases, if necessary. change_map = {} used = set() @@ -462,16 +460,27 @@ def combine(self, rhs, connector): # all joins exclusive to either the lhs or the rhs must be converted # to an outer join. if not conjunction: + l_tables = set(self.tables) + r_tables = set(rhs.tables) # Update r_tables aliases. for alias in change_map: if alias in r_tables: - r_tables.remove(alias) - r_tables.add(change_map[alias]) + # r_tables may contain entries that have a refcount of 0 + # if the query has references to a table that can be + # trimmed because only the foreign key is used. + # We only need to fix the aliases for the tables that + # actually have aliases. + if rhs.alias_refcount[alias]: + r_tables.remove(alias) + r_tables.add(change_map[alias]) # Find aliases that are exclusive to rhs or lhs. # These are promoted to outer joins. - outer_aliases = (l_tables | r_tables) - (l_tables & r_tables) - for alias in outer_aliases: - self.promote_alias(alias, True) + outer_tables = (l_tables | r_tables) - (l_tables & r_tables) + for alias in outer_tables: + # Again, some of the tables won't have aliases due to + # the trimming of unnecessary tables. + if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias): + self.promote_alias(alias, True) # Now relabel a copy of the rhs where-clause and add it to the current # one. @@ -656,7 +665,7 @@ def promote_alias(self, alias, unconditional=False): False, the join is only promoted if it is nullable, otherwise it is always promoted. - Returns True if the join was promoted. + Returns True if the join was promoted by this call. """ if ((unconditional or self.alias_map[alias][NULLABLE]) and self.alias_map[alias][JOIN_TYPE] != self.LOUTER): @@ -1063,17 +1072,20 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, can_reuse) return + table_promote = False + join_promote = False + if (lookup_type == 'isnull' and value is True and not negate and len(join_list) > 1): # If the comparison is against NULL, we may need to use some left # outer joins when creating the join chain. This is only done when # needed, as it's less efficient at the database level. self.promote_alias_chain(join_list) + join_promote = True # Process the join list to see if we can remove any inner joins from # the far end (fewer tables in a query is better). col, alias, join_list = self.trim_joins(target, join_list, last, trim) - if connector == OR: # Some joins may need to be promoted when adding a new filter to a # disjunction. We walk the list of new joins and where it diverges @@ -1083,19 +1095,29 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, join_it = iter(join_list) table_it = iter(self.tables) join_it.next(), table_it.next() - table_promote = False - join_promote = False + unconditional = False for join in join_it: table = table_it.next() + # Once we hit an outer join, all subsequent joins must + # also be promoted, regardless of whether they have been + # promoted as a result of this pass through the tables. + unconditional = (unconditional or + self.alias_map[join][JOIN_TYPE] == self.LOUTER) if join == table and self.alias_refcount[join] > 1: + # We have more than one reference to this join table. + # This means that we are dealing with two different query + # subtrees, so we don't need to do any join promotion. continue - join_promote = self.promote_alias(join) + join_promote = join_promote or self.promote_alias(join, unconditional) if table != join: table_promote = self.promote_alias(table) + # We only get here if we have found a table that exists + # in the join list, but isn't on the original tables list. + # This means we've reached the point where we only have + # new tables, so we can break out of this promotion loop. break self.promote_alias_chain(join_it, join_promote) - self.promote_alias_chain(table_it, table_promote) - + self.promote_alias_chain(table_it, table_promote or join_promote) if having_clause or force_having: if (alias, col) not in self.group_by: diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index c87ecd32df82..cd6b7e2b8243 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -972,12 +972,36 @@ def setUp(self): e1 = ExtraInfo.objects.create(info='e1', note=n1) e2 = ExtraInfo.objects.create(info='e2', note=n2) - a1 = Author.objects.create(name='a1', num=1001, extra=e1) - a3 = Author.objects.create(name='a3', num=3003, extra=e2) + self.a1 = Author.objects.create(name='a1', num=1001, extra=e1) + self.a3 = Author.objects.create(name='a3', num=3003, extra=e2) - Report.objects.create(name='r1', creator=a1) - Report.objects.create(name='r2', creator=a3) - Report.objects.create(name='r3') + self.r1 = Report.objects.create(name='r1', creator=self.a1) + self.r2 = Report.objects.create(name='r2', creator=self.a3) + self.r3 = Report.objects.create(name='r3') + + Item.objects.create(name='i1', created=datetime.datetime.now(), note=n1, creator=self.a1) + Item.objects.create(name='i2', created=datetime.datetime.now(), note=n1, creator=self.a3) + + def test_ticket14876(self): + q1 = Report.objects.filter(Q(creator__isnull=True) | Q(creator__extra__info='e1')) + q2 = Report.objects.filter(Q(creator__isnull=True)) | Report.objects.filter(Q(creator__extra__info='e1')) + self.assertQuerysetEqual(q1, ["", ""]) + self.assertEqual(str(q1.query), str(q2.query)) + + q1 = Report.objects.filter(Q(creator__extra__info='e1') | Q(creator__isnull=True)) + q2 = Report.objects.filter(Q(creator__extra__info='e1')) | Report.objects.filter(Q(creator__isnull=True)) + self.assertQuerysetEqual(q1, ["", ""]) + self.assertEqual(str(q1.query), str(q2.query)) + + q1 = Item.objects.filter(Q(creator=self.a1) | Q(creator__report__name='r1')).order_by() + q2 = Item.objects.filter(Q(creator=self.a1)).order_by() | Item.objects.filter(Q(creator__report__name='r1')).order_by() + self.assertQuerysetEqual(q1, [""]) + self.assertEqual(str(q1.query), str(q2.query)) + + q1 = Item.objects.filter(Q(creator__report__name='e1') | Q(creator=self.a1)).order_by() + q2 = Item.objects.filter(Q(creator__report__name='e1')).order_by() | Item.objects.filter(Q(creator=self.a1)).order_by() + self.assertQuerysetEqual(q1, [""]) + self.assertEqual(str(q1.query), str(q2.query)) def test_ticket7095(self): # Updates that are filtered on the model being updated are somewhat From e9a1c03dba16755697a4ee8969dc38a4a883565a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 23 Aug 2011 15:55:22 +0000 Subject: [PATCH 100/225] [1.3.X] Fixed #10571 -- Factored out the payload encoding code to make sure it is used for PUT requests. Thanks to kennu for the report, pterk for the patch, and wildfire for the review comments. Backport of r16651 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16672 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/client.py | 40 ++++++++----------- .../test_client_regress/models.py | 16 +++++++- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/django/test/client.py b/django/test/client.py index dd0d811b0299..1d50e9674efd 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -207,6 +207,18 @@ def request(self, **request): "Construct a generic request object." return WSGIRequest(self._base_environ(**request)) + def _encode_data(self, data, content_type, ): + if content_type is MULTIPART_CONTENT: + return encode_multipart(BOUNDARY, data) + else: + # Encode the content so that the byte representation is correct. + match = CONTENT_TYPE_RE.match(content_type) + if match: + charset = match.group(1) + else: + charset = settings.DEFAULT_CHARSET + return smart_str(data, encoding=charset) + def _get_path(self, parsed): # If there are parameters, add them if parsed[3]: @@ -232,16 +244,7 @@ def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra): "Construct a POST request." - if content_type is MULTIPART_CONTENT: - post_data = encode_multipart(BOUNDARY, data) - else: - # Encode the content so that the byte representation is correct. - match = CONTENT_TYPE_RE.match(content_type) - if match: - charset = match.group(1) - else: - charset = settings.DEFAULT_CHARSET - post_data = smart_str(data, encoding=charset) + post_data = self._encode_data(data, content_type) parsed = urlparse(path) r = { @@ -286,25 +289,16 @@ def put(self, path, data={}, content_type=MULTIPART_CONTENT, **extra): "Construct a PUT request." - if content_type is MULTIPART_CONTENT: - post_data = encode_multipart(BOUNDARY, data) - else: - post_data = data - - # Make `data` into a querystring only if it's not already a string. If - # it is a string, we'll assume that the caller has already encoded it. - query_string = None - if not isinstance(data, basestring): - query_string = urlencode(data, doseq=True) + put_data = self._encode_data(data, content_type) parsed = urlparse(path) r = { - 'CONTENT_LENGTH': len(post_data), + 'CONTENT_LENGTH': len(put_data), 'CONTENT_TYPE': content_type, 'PATH_INFO': self._get_path(parsed), - 'QUERY_STRING': query_string or parsed[4], + 'QUERY_STRING': parsed[4], 'REQUEST_METHOD': 'PUT', - 'wsgi.input': FakePayload(post_data), + 'wsgi.input': FakePayload(put_data), } r.update(extra) return self.request(**r) diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index b24032d0e9ad..ad2cd0413900 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -756,7 +756,9 @@ def test_put(self): class QueryStringTests(TestCase): def test_get_like_requests(self): - for method_name in ('get','head','options','put','delete'): + # See: https://code.djangoproject.com/ticket/10571. + # Removed 'put' and 'delete' here as they are 'GET-like requests' + for method_name in ('get','head','options'): # A GET-like request can pass a query string as data method = getattr(self.client, method_name) response = method("/test_client_regress/request_data/", data={'foo':'whiz'}) @@ -813,6 +815,9 @@ def test_simple_unicode_payload(self): response = self.client.post("/test_client_regress/parse_unicode_json/", json, content_type="application/json") self.assertEqual(response.content, json) + response = self.client.put("/test_client_regress/parse_unicode_json/", json, + content_type="application/json") + self.assertEqual(response.content, json) def test_unicode_payload_utf8(self): "A non-ASCII unicode data encoded as UTF-8 can be POSTed" @@ -821,6 +826,9 @@ def test_unicode_payload_utf8(self): response = self.client.post("/test_client_regress/parse_unicode_json/", json, content_type="application/json; charset=utf-8") self.assertEqual(response.content, json.encode('utf-8')) + response = self.client.put("/test_client_regress/parse_unicode_json/", json, + content_type="application/json; charset=utf-8") + self.assertEqual(response.content, json.encode('utf-8')) def test_unicode_payload_utf16(self): "A non-ASCII unicode data encoded as UTF-16 can be POSTed" @@ -829,6 +837,9 @@ def test_unicode_payload_utf16(self): response = self.client.post("/test_client_regress/parse_unicode_json/", json, content_type="application/json; charset=utf-16") self.assertEqual(response.content, json.encode('utf-16')) + response = self.client.put("/test_client_regress/parse_unicode_json/", json, + content_type="application/json; charset=utf-16") + self.assertEqual(response.content, json.encode('utf-16')) def test_unicode_payload_non_utf(self): "A non-ASCII unicode data as a non-UTF based encoding can be POSTed" @@ -837,6 +848,9 @@ def test_unicode_payload_non_utf(self): response = self.client.post("/test_client_regress/parse_unicode_json/", json, content_type="application/json; charset=koi8-r") self.assertEqual(response.content, json.encode('koi8-r')) + response = self.client.put("/test_client_regress/parse_unicode_json/", json, + content_type="application/json; charset=koi8-r") + self.assertEqual(response.content, json.encode('koi8-r')) class DummyFile(object): def __init__(self, filename): From 3e7d79b6ace41587ce4a63f688caa34447f4aef6 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 23 Aug 2011 15:55:48 +0000 Subject: [PATCH 101/225] [1.3.X] Fixed #15499 -- Ensure that cache control headers don't try to set public and private as a result of multiple calls to patch_cache_control with different arguments. Thanks to AndiDog for the report and patch. Backport of r16657 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16673 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/cache.py | 6 ++++++ docs/topics/cache.txt | 22 ++++++++++++++++++++++ tests/regressiontests/cache/tests.py | 28 +++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/django/utils/cache.py b/django/utils/cache.py index 63c4af349493..a0960286701b 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -67,6 +67,12 @@ def dictvalue(t): if 'max-age' in cc and 'max_age' in kwargs: kwargs['max_age'] = min(cc['max-age'], kwargs['max_age']) + # Allow overriding private caching and vice versa + if 'private' in cc and 'public' in kwargs: + del cc['private'] + elif 'public' in cc and 'private' in kwargs: + del cc['public'] + for (k, v) in kwargs.items(): cc[k.replace('_', '-')] = v cc = ', '.join([dictvalue(el) for el in cc.items()]) diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 81d6baa50dd5..8ef4ea2f3086 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -1062,6 +1062,28 @@ Django, use the ``cache_control`` view decorator. Example:: This decorator takes care of sending out the appropriate HTTP header behind the scenes. +Note that the cache control settings "private" and "public" are mutually +exclusive. The decorator ensures that the "public" directive is removed if +"private" should be set (and vice versa). An example use of the two directives +would be a blog site that offers both private and public entries. Public +entries may be cached on any shared cache. The following code uses +``patch_cache_control``, the manual way to modify the cache control header +(it is internally called by the ``cache_control`` decorator):: + + from django.views.decorators.cache import patch_cache_control + from django.views.decorators.vary import vary_on_cookie + + @vary_on_cookie + def list_blog_entries_view(request): + if request.user.is_anonymous(): + response = render_only_public_entries() + patch_cache_control(response, public=True) + else: + response = render_private_and_public_entries(request.user) + patch_cache_control(response, private=True) + + return response + There are a few other ways to control cache parameters. For example, HTTP allows applications to do the following: diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 2832bcbdd94f..dc86619effb4 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -4,6 +4,7 @@ # Uses whatever cache backend is set in the test settings file. import os +import re import tempfile import time import warnings @@ -18,7 +19,7 @@ from django.test.utils import get_warnings_state, restore_warnings_state from django.utils import translation from django.utils import unittest -from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key +from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key, patch_cache_control from django.utils.hashcompat import md5_constructor from django.views.decorators.cache import cache_page @@ -970,6 +971,31 @@ def test_learn_cache_key(self): learn_cache_key(request, response) self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') + def test_patch_cache_control(self): + tests = ( + # Initial Cache-Control, kwargs to patch_cache_control, expected Cache-Control parts + (None, {'private' : True}, set(['private'])), + + # Test whether private/public attributes are mutually exclusive + ('private', {'private' : True}, set(['private'])), + ('private', {'public' : True}, set(['public'])), + ('public', {'public' : True}, set(['public'])), + ('public', {'private' : True}, set(['private'])), + ('must-revalidate,max-age=60,private', {'public' : True}, set(['must-revalidate', 'max-age=60', 'public'])), + ('must-revalidate,max-age=60,public', {'private' : True}, set(['must-revalidate', 'max-age=60', 'private'])), + ('must-revalidate,max-age=60', {'public' : True}, set(['must-revalidate', 'max-age=60', 'public'])), + ) + + cc_delim_re = re.compile(r'\s*,\s*') + + for initial_cc, newheaders, expected_cc in tests: + response = HttpResponse() + if initial_cc is not None: + response['Cache-Control'] = initial_cc + patch_cache_control(response, **newheaders) + parts = set(cc_delim_re.split(response['Cache-Control'])) + self.assertEqual(parts, expected_cc) + class PrefixedCacheUtils(CacheUtils): def setUp(self): super(PrefixedCacheUtils, self).setUp() From 38530700bfa1bbee900d1a87811acf74305a0040 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 23 Aug 2011 15:56:18 +0000 Subject: [PATCH 102/225] [1.3.X] Fixed #16681 -- Refactored the invalid_models unit test so that it can be invoked manually. Thanks to Anthony Briggs for the report and patch. Backport of r16661 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16674 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + .../invalid_models/invalid_models/__init__.py | 0 .../invalid_models/invalid_models/models.py | 330 ++++++++++++++++++ tests/modeltests/invalid_models/models.py | 330 ------------------ tests/modeltests/invalid_models/tests.py | 37 ++ tests/runtests.py | 64 +--- 6 files changed, 372 insertions(+), 390 deletions(-) create mode 100644 tests/modeltests/invalid_models/invalid_models/__init__.py create mode 100644 tests/modeltests/invalid_models/invalid_models/models.py create mode 100644 tests/modeltests/invalid_models/tests.py diff --git a/AUTHORS b/AUTHORS index a19e49b10cbc..8e7842e02517 100644 --- a/AUTHORS +++ b/AUTHORS @@ -92,6 +92,7 @@ answer newbie questions, and generally made Django that much better: Sean Brant Andrew Brehaut David Brenneman + Anthony Briggs brut.alll@gmail.com bthomas btoll@bestweb.net diff --git a/tests/modeltests/invalid_models/invalid_models/__init__.py b/tests/modeltests/invalid_models/invalid_models/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py new file mode 100644 index 000000000000..45f302413dda --- /dev/null +++ b/tests/modeltests/invalid_models/invalid_models/models.py @@ -0,0 +1,330 @@ +""" +26. Invalid models + +This example exists purely to point out errors in models. +""" + +from django.db import models + +class FieldErrors(models.Model): + charfield = models.CharField() + charfield2 = models.CharField(max_length=-1) + charfield3 = models.CharField(max_length="bad") + decimalfield = models.DecimalField() + decimalfield2 = models.DecimalField(max_digits=-1, decimal_places=-1) + decimalfield3 = models.DecimalField(max_digits="bad", decimal_places="bad") + decimalfield4 = models.DecimalField(max_digits=9, decimal_places=10) + decimalfield5 = models.DecimalField(max_digits=10, decimal_places=10) + filefield = models.FileField() + choices = models.CharField(max_length=10, choices='bad') + choices2 = models.CharField(max_length=10, choices=[(1,2,3),(1,2,3)]) + index = models.CharField(max_length=10, db_index='bad') + field_ = models.CharField(max_length=10) + nullbool = models.BooleanField(null=True) + +class Target(models.Model): + tgt_safe = models.CharField(max_length=10) + clash1 = models.CharField(max_length=10) + clash2 = models.CharField(max_length=10) + + clash1_set = models.CharField(max_length=10) + +class Clash1(models.Model): + src_safe = models.CharField(max_length=10) + + foreign = models.ForeignKey(Target) + m2m = models.ManyToManyField(Target) + +class Clash2(models.Model): + src_safe = models.CharField(max_length=10) + + foreign_1 = models.ForeignKey(Target, related_name='id') + foreign_2 = models.ForeignKey(Target, related_name='src_safe') + + m2m_1 = models.ManyToManyField(Target, related_name='id') + m2m_2 = models.ManyToManyField(Target, related_name='src_safe') + +class Target2(models.Model): + clash3 = models.CharField(max_length=10) + foreign_tgt = models.ForeignKey(Target) + clashforeign_set = models.ForeignKey(Target) + + m2m_tgt = models.ManyToManyField(Target) + clashm2m_set = models.ManyToManyField(Target) + +class Clash3(models.Model): + src_safe = models.CharField(max_length=10) + + foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt') + foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt') + + m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt') + m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt') + +class ClashForeign(models.Model): + foreign = models.ForeignKey(Target2) + +class ClashM2M(models.Model): + m2m = models.ManyToManyField(Target2) + +class SelfClashForeign(models.Model): + src_safe = models.CharField(max_length=10) + selfclashforeign = models.CharField(max_length=10) + + selfclashforeign_set = models.ForeignKey("SelfClashForeign") + foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id') + foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe') + +class ValidM2M(models.Model): + src_safe = models.CharField(max_length=10) + validm2m = models.CharField(max_length=10) + + # M2M fields are symmetrical by default. Symmetrical M2M fields + # on self don't require a related accessor, so many potential + # clashes are avoided. + validm2m_set = models.ManyToManyField("self") + + m2m_1 = models.ManyToManyField("self", related_name='id') + m2m_2 = models.ManyToManyField("self", related_name='src_safe') + + m2m_3 = models.ManyToManyField('self') + m2m_4 = models.ManyToManyField('self') + +class SelfClashM2M(models.Model): + src_safe = models.CharField(max_length=10) + selfclashm2m = models.CharField(max_length=10) + + # Non-symmetrical M2M fields _do_ have related accessors, so + # there is potential for clashes. + selfclashm2m_set = models.ManyToManyField("self", symmetrical=False) + + m2m_1 = models.ManyToManyField("self", related_name='id', symmetrical=False) + m2m_2 = models.ManyToManyField("self", related_name='src_safe', symmetrical=False) + + m2m_3 = models.ManyToManyField('self', symmetrical=False) + m2m_4 = models.ManyToManyField('self', symmetrical=False) + +class Model(models.Model): + "But it's valid to call a model Model." + year = models.PositiveIntegerField() #1960 + make = models.CharField(max_length=10) #Aston Martin + name = models.CharField(max_length=10) #DB 4 GT + +class Car(models.Model): + colour = models.CharField(max_length=5) + model = models.ForeignKey(Model) + +class MissingRelations(models.Model): + rel1 = models.ForeignKey("Rel1") + rel2 = models.ManyToManyField("Rel2") + +class MissingManualM2MModel(models.Model): + name = models.CharField(max_length=5) + missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") + +class Person(models.Model): + name = models.CharField(max_length=5) + +class Group(models.Model): + name = models.CharField(max_length=5) + primary = models.ManyToManyField(Person, through="Membership", related_name="primary") + secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary") + tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary") + +class GroupTwo(models.Model): + name = models.CharField(max_length=5) + primary = models.ManyToManyField(Person, through="Membership") + secondary = models.ManyToManyField(Group, through="MembershipMissingFK") + +class Membership(models.Model): + person = models.ForeignKey(Person) + group = models.ForeignKey(Group) + not_default_or_null = models.CharField(max_length=5) + +class MembershipMissingFK(models.Model): + person = models.ForeignKey(Person) + +class PersonSelfRefM2M(models.Model): + name = models.CharField(max_length=5) + friends = models.ManyToManyField('self', through="Relationship") + too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK") + +class PersonSelfRefM2MExplicit(models.Model): + name = models.CharField(max_length=5) + friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True) + +class Relationship(models.Model): + first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") + second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") + date_added = models.DateTimeField() + +class ExplicitRelationship(models.Model): + first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set") + second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set") + date_added = models.DateTimeField() + +class RelationshipTripleFK(models.Model): + first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2") + second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2") + third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far") + date_added = models.DateTimeField() + +class RelationshipDoubleFK(models.Model): + first = models.ForeignKey(Person, related_name="first_related_name") + second = models.ForeignKey(Person, related_name="second_related_name") + third = models.ForeignKey(Group, related_name="rel_to_set") + date_added = models.DateTimeField() + +class AbstractModel(models.Model): + name = models.CharField(max_length=10) + class Meta: + abstract = True + +class AbstractRelationModel(models.Model): + fk1 = models.ForeignKey('AbstractModel') + fk2 = models.ManyToManyField('AbstractModel') + +class UniqueM2M(models.Model): + """ Model to test for unique ManyToManyFields, which are invalid. """ + unique_people = models.ManyToManyField(Person, unique=True) + +class NonUniqueFKTarget1(models.Model): + """ Model to test for non-unique FK target in yet-to-be-defined model: expect an error """ + tgt = models.ForeignKey('FKTarget', to_field='bad') + +class UniqueFKTarget1(models.Model): + """ Model to test for unique FK target in yet-to-be-defined model: expect no error """ + tgt = models.ForeignKey('FKTarget', to_field='good') + +class FKTarget(models.Model): + bad = models.IntegerField() + good = models.IntegerField(unique=True) + +class NonUniqueFKTarget2(models.Model): + """ Model to test for non-unique FK target in previously seen model: expect an error """ + tgt = models.ForeignKey(FKTarget, to_field='bad') + +class UniqueFKTarget2(models.Model): + """ Model to test for unique FK target in previously seen model: expect no error """ + tgt = models.ForeignKey(FKTarget, to_field='good') + +class NonExistingOrderingWithSingleUnderscore(models.Model): + class Meta: + ordering = ("does_not_exist",) + +class InvalidSetNull(models.Model): + fk = models.ForeignKey('self', on_delete=models.SET_NULL) + +class InvalidSetDefault(models.Model): + fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT) + +model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. +invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. +invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer. +invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute that is a non-negative integer. +invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute that is a positive integer. +invalid_models.fielderrors: "decimalfield2": DecimalFields require a "decimal_places" attribute that is a non-negative integer. +invalid_models.fielderrors: "decimalfield2": DecimalFields require a "max_digits" attribute that is a positive integer. +invalid_models.fielderrors: "decimalfield3": DecimalFields require a "decimal_places" attribute that is a non-negative integer. +invalid_models.fielderrors: "decimalfield3": DecimalFields require a "max_digits" attribute that is a positive integer. +invalid_models.fielderrors: "decimalfield4": DecimalFields require a "max_digits" attribute value that is greater than the value of the "decimal_places" attribute. +invalid_models.fielderrors: "decimalfield5": DecimalFields require a "max_digits" attribute value that is greater than the value of the "decimal_places" attribute. +invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute. +invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list). +invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. +invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. +invalid_models.fielderrors: "index": "db_index" should be either None, True or False. +invalid_models.fielderrors: "field_": Field names cannot end with underscores, because this would lead to ambiguous queryset filters. +invalid_models.fielderrors: "nullbool": BooleanFields do not accept null values. Use a NullBooleanField instead. +invalid_models.clash1: Accessor for field 'foreign' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: Accessor for field 'foreign' clashes with related m2m field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: Reverse query name for field 'foreign' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: Accessor for m2m field 'm2m' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash1: Accessor for m2m field 'm2m' clashes with related field 'Target.clash1_set'. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash1: Reverse query name for m2m field 'm2m' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash2: Accessor for field 'foreign_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Accessor for field 'foreign_1' clashes with related m2m field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Reverse query name for field 'foreign_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Reverse query name for field 'foreign_1' clashes with related m2m field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Accessor for field 'foreign_2' clashes with related m2m field 'Target.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash2: Reverse query name for field 'foreign_2' clashes with related m2m field 'Target.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash2: Accessor for m2m field 'm2m_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Accessor for m2m field 'm2m_1' clashes with related field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Reverse query name for m2m field 'm2m_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Reverse query name for m2m field 'm2m_1' clashes with related field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Accessor for m2m field 'm2m_2' clashes with related field 'Target.src_safe'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash2: Reverse query name for m2m field 'm2m_2' clashes with related field 'Target.src_safe'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Accessor for field 'foreign_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Accessor for field 'foreign_1' clashes with related m2m field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Reverse query name for field 'foreign_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Reverse query name for field 'foreign_1' clashes with related m2m field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Accessor for field 'foreign_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Accessor for field 'foreign_2' clashes with related m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Reverse query name for field 'foreign_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Reverse query name for field 'foreign_2' clashes with related m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Accessor for m2m field 'm2m_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Accessor for m2m field 'm2m_1' clashes with related field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_1' clashes with related field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Accessor for m2m field 'm2m_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Accessor for m2m field 'm2m_2' clashes with related field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_2' clashes with related field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clashforeign: Accessor for field 'foreign' clashes with field 'Target2.clashforeign_set'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clashm2m: Accessor for m2m field 'm2m' clashes with m2m field 'Target2.clashm2m_set'. Add a related_name argument to the definition for 'm2m'. +invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.selfclashforeign: Accessor for field 'selfclashforeign_set' clashes with field 'SelfClashForeign.selfclashforeign_set'. Add a related_name argument to the definition for 'selfclashforeign_set'. +invalid_models.selfclashforeign: Reverse query name for field 'selfclashforeign_set' clashes with field 'SelfClashForeign.selfclashforeign'. Add a related_name argument to the definition for 'selfclashforeign_set'. +invalid_models.selfclashforeign: Accessor for field 'foreign_1' clashes with field 'SelfClashForeign.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.selfclashforeign: Reverse query name for field 'foreign_1' clashes with field 'SelfClashForeign.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.selfclashforeign: Accessor for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.selfclashforeign: Reverse query name for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'selfclashm2m_set' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'selfclashm2m_set'. +invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_3'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'. +invalid_models.missingrelations: 'rel1' has a relation with model Rel1, which has either not been installed or is abstract. +invalid_models.missingrelations: 'rel2' has an m2m relation with model Rel2, which has either not been installed or is abstract. +invalid_models.grouptwo: 'primary' is a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo +invalid_models.grouptwo: 'secondary' is a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo +invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed +invalid_models.group: The model Group has two manually-defined m2m relations through the model Membership, which is not permitted. Please consider using an extra field on your intermediary model instead. +invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted. +invalid_models.personselfrefm2m: Many-to-many fields with intermediate tables cannot be symmetrical. +invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M, which is ambiguous and is not permitted. +invalid_models.personselfrefm2mexplicit: Many-to-many fields with intermediate tables cannot be symmetrical. +invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract. +invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract. +invalid_models.uniquem2m: ManyToManyFields cannot be unique. Remove the unique argument on 'unique_people'. +invalid_models.nonuniquefktarget1: Field 'bad' under model 'FKTarget' must have a unique=True constraint. +invalid_models.nonuniquefktarget2: Field 'bad' under model 'FKTarget' must have a unique=True constraint. +invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist. +invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null. +invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value. +""" diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index 45f302413dda..e69de29bb2d1 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -1,330 +0,0 @@ -""" -26. Invalid models - -This example exists purely to point out errors in models. -""" - -from django.db import models - -class FieldErrors(models.Model): - charfield = models.CharField() - charfield2 = models.CharField(max_length=-1) - charfield3 = models.CharField(max_length="bad") - decimalfield = models.DecimalField() - decimalfield2 = models.DecimalField(max_digits=-1, decimal_places=-1) - decimalfield3 = models.DecimalField(max_digits="bad", decimal_places="bad") - decimalfield4 = models.DecimalField(max_digits=9, decimal_places=10) - decimalfield5 = models.DecimalField(max_digits=10, decimal_places=10) - filefield = models.FileField() - choices = models.CharField(max_length=10, choices='bad') - choices2 = models.CharField(max_length=10, choices=[(1,2,3),(1,2,3)]) - index = models.CharField(max_length=10, db_index='bad') - field_ = models.CharField(max_length=10) - nullbool = models.BooleanField(null=True) - -class Target(models.Model): - tgt_safe = models.CharField(max_length=10) - clash1 = models.CharField(max_length=10) - clash2 = models.CharField(max_length=10) - - clash1_set = models.CharField(max_length=10) - -class Clash1(models.Model): - src_safe = models.CharField(max_length=10) - - foreign = models.ForeignKey(Target) - m2m = models.ManyToManyField(Target) - -class Clash2(models.Model): - src_safe = models.CharField(max_length=10) - - foreign_1 = models.ForeignKey(Target, related_name='id') - foreign_2 = models.ForeignKey(Target, related_name='src_safe') - - m2m_1 = models.ManyToManyField(Target, related_name='id') - m2m_2 = models.ManyToManyField(Target, related_name='src_safe') - -class Target2(models.Model): - clash3 = models.CharField(max_length=10) - foreign_tgt = models.ForeignKey(Target) - clashforeign_set = models.ForeignKey(Target) - - m2m_tgt = models.ManyToManyField(Target) - clashm2m_set = models.ManyToManyField(Target) - -class Clash3(models.Model): - src_safe = models.CharField(max_length=10) - - foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt') - foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt') - - m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt') - m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt') - -class ClashForeign(models.Model): - foreign = models.ForeignKey(Target2) - -class ClashM2M(models.Model): - m2m = models.ManyToManyField(Target2) - -class SelfClashForeign(models.Model): - src_safe = models.CharField(max_length=10) - selfclashforeign = models.CharField(max_length=10) - - selfclashforeign_set = models.ForeignKey("SelfClashForeign") - foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id') - foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe') - -class ValidM2M(models.Model): - src_safe = models.CharField(max_length=10) - validm2m = models.CharField(max_length=10) - - # M2M fields are symmetrical by default. Symmetrical M2M fields - # on self don't require a related accessor, so many potential - # clashes are avoided. - validm2m_set = models.ManyToManyField("self") - - m2m_1 = models.ManyToManyField("self", related_name='id') - m2m_2 = models.ManyToManyField("self", related_name='src_safe') - - m2m_3 = models.ManyToManyField('self') - m2m_4 = models.ManyToManyField('self') - -class SelfClashM2M(models.Model): - src_safe = models.CharField(max_length=10) - selfclashm2m = models.CharField(max_length=10) - - # Non-symmetrical M2M fields _do_ have related accessors, so - # there is potential for clashes. - selfclashm2m_set = models.ManyToManyField("self", symmetrical=False) - - m2m_1 = models.ManyToManyField("self", related_name='id', symmetrical=False) - m2m_2 = models.ManyToManyField("self", related_name='src_safe', symmetrical=False) - - m2m_3 = models.ManyToManyField('self', symmetrical=False) - m2m_4 = models.ManyToManyField('self', symmetrical=False) - -class Model(models.Model): - "But it's valid to call a model Model." - year = models.PositiveIntegerField() #1960 - make = models.CharField(max_length=10) #Aston Martin - name = models.CharField(max_length=10) #DB 4 GT - -class Car(models.Model): - colour = models.CharField(max_length=5) - model = models.ForeignKey(Model) - -class MissingRelations(models.Model): - rel1 = models.ForeignKey("Rel1") - rel2 = models.ManyToManyField("Rel2") - -class MissingManualM2MModel(models.Model): - name = models.CharField(max_length=5) - missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") - -class Person(models.Model): - name = models.CharField(max_length=5) - -class Group(models.Model): - name = models.CharField(max_length=5) - primary = models.ManyToManyField(Person, through="Membership", related_name="primary") - secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary") - tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary") - -class GroupTwo(models.Model): - name = models.CharField(max_length=5) - primary = models.ManyToManyField(Person, through="Membership") - secondary = models.ManyToManyField(Group, through="MembershipMissingFK") - -class Membership(models.Model): - person = models.ForeignKey(Person) - group = models.ForeignKey(Group) - not_default_or_null = models.CharField(max_length=5) - -class MembershipMissingFK(models.Model): - person = models.ForeignKey(Person) - -class PersonSelfRefM2M(models.Model): - name = models.CharField(max_length=5) - friends = models.ManyToManyField('self', through="Relationship") - too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK") - -class PersonSelfRefM2MExplicit(models.Model): - name = models.CharField(max_length=5) - friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True) - -class Relationship(models.Model): - first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") - second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") - date_added = models.DateTimeField() - -class ExplicitRelationship(models.Model): - first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set") - second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set") - date_added = models.DateTimeField() - -class RelationshipTripleFK(models.Model): - first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2") - second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2") - third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far") - date_added = models.DateTimeField() - -class RelationshipDoubleFK(models.Model): - first = models.ForeignKey(Person, related_name="first_related_name") - second = models.ForeignKey(Person, related_name="second_related_name") - third = models.ForeignKey(Group, related_name="rel_to_set") - date_added = models.DateTimeField() - -class AbstractModel(models.Model): - name = models.CharField(max_length=10) - class Meta: - abstract = True - -class AbstractRelationModel(models.Model): - fk1 = models.ForeignKey('AbstractModel') - fk2 = models.ManyToManyField('AbstractModel') - -class UniqueM2M(models.Model): - """ Model to test for unique ManyToManyFields, which are invalid. """ - unique_people = models.ManyToManyField(Person, unique=True) - -class NonUniqueFKTarget1(models.Model): - """ Model to test for non-unique FK target in yet-to-be-defined model: expect an error """ - tgt = models.ForeignKey('FKTarget', to_field='bad') - -class UniqueFKTarget1(models.Model): - """ Model to test for unique FK target in yet-to-be-defined model: expect no error """ - tgt = models.ForeignKey('FKTarget', to_field='good') - -class FKTarget(models.Model): - bad = models.IntegerField() - good = models.IntegerField(unique=True) - -class NonUniqueFKTarget2(models.Model): - """ Model to test for non-unique FK target in previously seen model: expect an error """ - tgt = models.ForeignKey(FKTarget, to_field='bad') - -class UniqueFKTarget2(models.Model): - """ Model to test for unique FK target in previously seen model: expect no error """ - tgt = models.ForeignKey(FKTarget, to_field='good') - -class NonExistingOrderingWithSingleUnderscore(models.Model): - class Meta: - ordering = ("does_not_exist",) - -class InvalidSetNull(models.Model): - fk = models.ForeignKey('self', on_delete=models.SET_NULL) - -class InvalidSetDefault(models.Model): - fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT) - -model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. -invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. -invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer. -invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute that is a non-negative integer. -invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute that is a positive integer. -invalid_models.fielderrors: "decimalfield2": DecimalFields require a "decimal_places" attribute that is a non-negative integer. -invalid_models.fielderrors: "decimalfield2": DecimalFields require a "max_digits" attribute that is a positive integer. -invalid_models.fielderrors: "decimalfield3": DecimalFields require a "decimal_places" attribute that is a non-negative integer. -invalid_models.fielderrors: "decimalfield3": DecimalFields require a "max_digits" attribute that is a positive integer. -invalid_models.fielderrors: "decimalfield4": DecimalFields require a "max_digits" attribute value that is greater than the value of the "decimal_places" attribute. -invalid_models.fielderrors: "decimalfield5": DecimalFields require a "max_digits" attribute value that is greater than the value of the "decimal_places" attribute. -invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute. -invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list). -invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. -invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. -invalid_models.fielderrors: "index": "db_index" should be either None, True or False. -invalid_models.fielderrors: "field_": Field names cannot end with underscores, because this would lead to ambiguous queryset filters. -invalid_models.fielderrors: "nullbool": BooleanFields do not accept null values. Use a NullBooleanField instead. -invalid_models.clash1: Accessor for field 'foreign' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'. -invalid_models.clash1: Accessor for field 'foreign' clashes with related m2m field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'. -invalid_models.clash1: Reverse query name for field 'foreign' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'foreign'. -invalid_models.clash1: Accessor for m2m field 'm2m' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'm2m'. -invalid_models.clash1: Accessor for m2m field 'm2m' clashes with related field 'Target.clash1_set'. Add a related_name argument to the definition for 'm2m'. -invalid_models.clash1: Reverse query name for m2m field 'm2m' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'm2m'. -invalid_models.clash2: Accessor for field 'foreign_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash2: Accessor for field 'foreign_1' clashes with related m2m field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash2: Reverse query name for field 'foreign_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash2: Reverse query name for field 'foreign_1' clashes with related m2m field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash2: Accessor for field 'foreign_2' clashes with related m2m field 'Target.src_safe'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash2: Reverse query name for field 'foreign_2' clashes with related m2m field 'Target.src_safe'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash2: Accessor for m2m field 'm2m_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash2: Accessor for m2m field 'm2m_1' clashes with related field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash2: Reverse query name for m2m field 'm2m_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash2: Reverse query name for m2m field 'm2m_1' clashes with related field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash2: Accessor for m2m field 'm2m_2' clashes with related field 'Target.src_safe'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clash2: Reverse query name for m2m field 'm2m_2' clashes with related field 'Target.src_safe'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clash3: Accessor for field 'foreign_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash3: Accessor for field 'foreign_1' clashes with related m2m field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash3: Reverse query name for field 'foreign_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash3: Reverse query name for field 'foreign_1' clashes with related m2m field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash3: Accessor for field 'foreign_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash3: Accessor for field 'foreign_2' clashes with related m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash3: Reverse query name for field 'foreign_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash3: Reverse query name for field 'foreign_2' clashes with related m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash3: Accessor for m2m field 'm2m_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash3: Accessor for m2m field 'm2m_1' clashes with related field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash3: Reverse query name for m2m field 'm2m_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash3: Reverse query name for m2m field 'm2m_1' clashes with related field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash3: Accessor for m2m field 'm2m_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clash3: Accessor for m2m field 'm2m_2' clashes with related field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clash3: Reverse query name for m2m field 'm2m_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clash3: Reverse query name for m2m field 'm2m_2' clashes with related field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clashforeign: Accessor for field 'foreign' clashes with field 'Target2.clashforeign_set'. Add a related_name argument to the definition for 'foreign'. -invalid_models.clashm2m: Accessor for m2m field 'm2m' clashes with m2m field 'Target2.clashm2m_set'. Add a related_name argument to the definition for 'm2m'. -invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. -invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. -invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. -invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. -invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. -invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. -invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.selfclashforeign: Accessor for field 'selfclashforeign_set' clashes with field 'SelfClashForeign.selfclashforeign_set'. Add a related_name argument to the definition for 'selfclashforeign_set'. -invalid_models.selfclashforeign: Reverse query name for field 'selfclashforeign_set' clashes with field 'SelfClashForeign.selfclashforeign'. Add a related_name argument to the definition for 'selfclashforeign_set'. -invalid_models.selfclashforeign: Accessor for field 'foreign_1' clashes with field 'SelfClashForeign.id'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.selfclashforeign: Reverse query name for field 'foreign_1' clashes with field 'SelfClashForeign.id'. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.selfclashforeign: Accessor for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.selfclashforeign: Reverse query name for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'. -invalid_models.selfclashm2m: Reverse query name for m2m field 'selfclashm2m_set' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'selfclashm2m_set'. -invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'. -invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'. -invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_3'. -invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'. -invalid_models.missingrelations: 'rel1' has a relation with model Rel1, which has either not been installed or is abstract. -invalid_models.missingrelations: 'rel2' has an m2m relation with model Rel2, which has either not been installed or is abstract. -invalid_models.grouptwo: 'primary' is a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo -invalid_models.grouptwo: 'secondary' is a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo -invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed -invalid_models.group: The model Group has two manually-defined m2m relations through the model Membership, which is not permitted. Please consider using an extra field on your intermediary model instead. -invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted. -invalid_models.personselfrefm2m: Many-to-many fields with intermediate tables cannot be symmetrical. -invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M, which is ambiguous and is not permitted. -invalid_models.personselfrefm2mexplicit: Many-to-many fields with intermediate tables cannot be symmetrical. -invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract. -invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract. -invalid_models.uniquem2m: ManyToManyFields cannot be unique. Remove the unique argument on 'unique_people'. -invalid_models.nonuniquefktarget1: Field 'bad' under model 'FKTarget' must have a unique=True constraint. -invalid_models.nonuniquefktarget2: Field 'bad' under model 'FKTarget' must have a unique=True constraint. -invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist. -invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null. -invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value. -""" diff --git a/tests/modeltests/invalid_models/tests.py b/tests/modeltests/invalid_models/tests.py new file mode 100644 index 000000000000..1aaa36bc8c7b --- /dev/null +++ b/tests/modeltests/invalid_models/tests.py @@ -0,0 +1,37 @@ +import sys + +from django.utils import unittest + + +class InvalidModelTestCase(unittest.TestCase): + """Import an appliation with invalid models and test the exceptions.""" + + def test_invalid_models(self): + from django.core.management.validation import get_validation_errors + from django.db.models.loading import load_app + from cStringIO import StringIO + + try: + module = load_app("modeltests.invalid_models.invalid_models") + except Exception, e: + self.fail('Unable to load invalid model module') + + # Make sure sys.stdout is not a tty so that we get errors without + # coloring attached (makes matching the results easier). We restore + # sys.stderr afterwards. + orig_stdout = sys.stdout + s = StringIO() + sys.stdout = s + count = get_validation_errors(s, module) + sys.stdout = orig_stdout + s.seek(0) + error_log = s.read() + actual = error_log.split('\n') + expected = module.model_errors.split('\n') + + unexpected = [err for err in actual if err not in expected] + missing = [err for err in expected if err not in actual] + self.assertFalse(unexpected, "Unexpected Errors: " + '\n'.join(unexpected)) + self.assertFalse(missing, "Missing Errors: " + '\n'.join(missing)) + + diff --git a/tests/runtests.py b/tests/runtests.py index 7fed5c1e956c..5c9b62740ff0 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -2,7 +2,6 @@ import os, subprocess, sys import django.contrib as contrib -from django.utils import unittest CONTRIB_DIR_NAME = 'django.contrib' MODEL_TESTS_DIR_NAME = 'modeltests' @@ -40,57 +39,14 @@ def get_test_modules(): modules = [] for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR), (CONTRIB_DIR_NAME, CONTRIB_DIR): for f in os.listdir(dirpath): - if f.startswith('__init__') or f.startswith('.') or \ - f.startswith('sql') or f.startswith('invalid') or \ - os.path.basename(f) in REGRESSION_SUBDIRS_TO_SKIP: + if (f.startswith('__init__') or + f.startswith('.') or + f.startswith('sql') or + os.path.basename(f) in REGRESSION_SUBDIRS_TO_SKIP): continue modules.append((loc, f)) return modules -def get_invalid_modules(): - modules = [] - for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR), (CONTRIB_DIR_NAME, CONTRIB_DIR): - for f in os.listdir(dirpath): - if f.startswith('__init__') or f.startswith('.') or f.startswith('sql'): - continue - if f.startswith('invalid'): - modules.append((loc, f)) - return modules - -class InvalidModelTestCase(unittest.TestCase): - def __init__(self, module_label): - unittest.TestCase.__init__(self) - self.module_label = module_label - - def runTest(self): - from django.core.management.validation import get_validation_errors - from django.db.models.loading import load_app - from cStringIO import StringIO - - try: - module = load_app(self.module_label) - except Exception, e: - self.fail('Unable to load invalid model module') - - # Make sure sys.stdout is not a tty so that we get errors without - # coloring attached (makes matching the results easier). We restore - # sys.stderr afterwards. - orig_stdout = sys.stdout - s = StringIO() - sys.stdout = s - count = get_validation_errors(s, module) - sys.stdout = orig_stdout - s.seek(0) - error_log = s.read() - actual = error_log.split('\n') - expected = module.model_errors.split('\n') - - unexpected = [err for err in actual if err not in expected] - missing = [err for err in expected if err not in actual] - - self.assertTrue(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected)) - self.assertTrue(not missing, "Missing Errors: " + '\n'.join(missing)) - def setup(verbosity, test_labels): from django.conf import settings state = { @@ -162,19 +118,7 @@ def teardown(state): def django_tests(verbosity, interactive, failfast, test_labels): from django.conf import settings state = setup(verbosity, test_labels) - - # Add tests for invalid models apps. extra_tests = [] - for module_dir, module_name in get_invalid_modules(): - module_label = '.'.join([module_dir, module_name]) - if not test_labels or module_name in test_labels: - extra_tests.append(InvalidModelTestCase(module_label)) - try: - # Invalid models are not working apps, so we cannot pass them into - # the test runner with the other test_labels - test_labels.remove(module_name) - except ValueError: - pass # If GeoDjango is used, add it's tests that aren't a part of # an application (e.g., GEOS, GDAL, Distance objects). From f317bd20d762616ad5cf502c59eb55835a75056a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 23 Aug 2011 15:56:40 +0000 Subject: [PATCH 103/225] [1.3.X] Fixed #16299 -- Ensure that unicode strings can be used to identify classes in ForeignKey and ManyToManyFields. Unicode strings aren't actually legal as class names, but this is an issue if you use from __future__ import unicode_literals in your models.py file. Thanks to Martijn Bastiaan for the report, and Anthony Briggs for the final patch. Backport of r16663 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16675 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/options.py | 4 ++-- .../invalid_models/invalid_models/models.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/django/db/models/options.py b/django/db/models/options.py index 10617dc9e99b..5b1f66835a2f 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -390,7 +390,7 @@ def _fill_related_objects_cache(self): cache[obj] = model for klass in get_models(include_auto_created=True): for f in klass._meta.local_fields: - if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, basestring) and self == f.rel.to._meta: cache[RelatedObject(f.rel.to, klass, f)] = None self._related_objects_cache = cache @@ -427,7 +427,7 @@ def _fill_related_many_to_many_cache(self): cache[obj] = model for klass in get_models(): for f in klass._meta.local_many_to_many: - if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, basestring) 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/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py index 45f302413dda..e32ff04ea815 100644 --- a/tests/modeltests/invalid_models/invalid_models/models.py +++ b/tests/modeltests/invalid_models/invalid_models/models.py @@ -1,3 +1,4 @@ +#encoding=utf-8 """ 26. Invalid models @@ -218,6 +219,16 @@ class InvalidSetNull(models.Model): class InvalidSetDefault(models.Model): fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT) +class UnicodeForeignKeys(models.Model): + """Foreign keys which can translate to ascii should be OK, but fail if they're not.""" + good = models.ForeignKey(u'FKTarget') + also_good = models.ManyToManyField(u'FKTarget', related_name='unicode2') + + # In Python 3 this should become legal, but currently causes unicode errors + # when adding the errors in core/management/validation.py + #bad = models.ForeignKey(u'★') + + model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer. From e2d7a784c8aa56883a84ba536bc4ba4803fcb94e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 23 Aug 2011 15:57:01 +0000 Subject: [PATCH 104/225] [1.3.X] Fixed #16201 -- Ensure that requests with Content-Length=0 don't break the multipart parser. Thanks to albsen for the report and patch Backport of r16353 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16676 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/http/multipartparser.py | 7 ++++++- tests/regressiontests/requests/tests.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index e45d5d103504..e3a03ff89701 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -75,7 +75,7 @@ def __init__(self, META, input_data, upload_handlers, encoding=None): # For now set it to 0; we'll try again later on down. content_length = 0 - if content_length <= 0: + if content_length < 0: # This means we shouldn't continue...raise an error. raise MultiPartParserError("Invalid content length: %r" % content_length) @@ -105,6 +105,11 @@ def parse(self): encoding = self._encoding handlers = self._upload_handlers + # HTTP spec says that Content-Length >= 0 is valid + # handling content-length == 0 before continuing + if self._content_length == 0: + return QueryDict(MultiValueDict(), encoding=self._encoding), MultiValueDict() + limited_input_data = LimitBytes(self._input_data, self._content_length) # See if the handler will want to take care of the parsing. diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index b68201494b46..154c1fa07dde 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -200,6 +200,27 @@ def test_raw_post_data_after_POST_multipart(self): self.assertEqual(request.POST, {u'name': [u'value']}) self.assertRaises(Exception, lambda: request.raw_post_data) + def test_POST_multipart_with_content_length_zero(self): + """ + Multipart POST requests with Content-Length >= 0 are valid and need to be handled. + """ + # According to: + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13 + # Every request.POST with Content-Length >= 0 is a valid request, + # this test ensures that we handle Content-Length == 0. + payload = "\r\n".join([ + '--boundary', + 'Content-Disposition: form-data; name="name"', + '', + 'value', + '--boundary--' + '']) + request = WSGIRequest({'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', + 'CONTENT_LENGTH': 0, + 'wsgi.input': StringIO(payload)}) + self.assertEqual(request.POST, {}) + def test_read_by_lines(self): request = WSGIRequest({'REQUEST_METHOD': 'POST', 'wsgi.input': StringIO('name=value')}) self.assertEqual(list(request), ['name=value']) From 8b42dfa47e448ed4d6755b32425b1c33821f5776 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 23 Aug 2011 15:57:19 +0000 Subject: [PATCH 105/225] [1.3.X] Corrected the setup and teardown of the refactored invalid_models test so that it guarantees that stdout is restored, and purges all the temporary models from the app cache after running the test. Backport of r16670 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16677 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/invalid_models/tests.py | 43 +++++++++++++++--------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/tests/modeltests/invalid_models/tests.py b/tests/modeltests/invalid_models/tests.py index 1aaa36bc8c7b..d6a1a25f1861 100644 --- a/tests/modeltests/invalid_models/tests.py +++ b/tests/modeltests/invalid_models/tests.py @@ -1,3 +1,8 @@ +import copy + +from django.core.management.validation import get_validation_errors +from django.db.models.loading import cache, load_app +from cStringIO import StringIO import sys from django.utils import unittest @@ -6,26 +11,36 @@ class InvalidModelTestCase(unittest.TestCase): """Import an appliation with invalid models and test the exceptions.""" + def setUp(self): + # Make sure sys.stdout is not a tty so that we get errors without + # coloring attached (makes matching the results easier). We restore + # sys.stderr afterwards. + self.old_stdout = sys.stdout + self.stdout = StringIO() + sys.stdout = self.stdout + + # This test adds dummy applications to the app cache. These + # need to be removed in order to prevent bad interactions + # with the flush operation in other tests. + self.old_app_models = copy.deepcopy(cache.app_models) + self.old_app_store = copy.deepcopy(cache.app_store) + + def tearDown(self): + cache.app_models = self.old_app_models + cache.app_store = self.old_app_store + cache._get_models_cache = {} + sys.stdout = self.old_stdout + def test_invalid_models(self): - from django.core.management.validation import get_validation_errors - from django.db.models.loading import load_app - from cStringIO import StringIO try: module = load_app("modeltests.invalid_models.invalid_models") except Exception, e: self.fail('Unable to load invalid model module') - # Make sure sys.stdout is not a tty so that we get errors without - # coloring attached (makes matching the results easier). We restore - # sys.stderr afterwards. - orig_stdout = sys.stdout - s = StringIO() - sys.stdout = s - count = get_validation_errors(s, module) - sys.stdout = orig_stdout - s.seek(0) - error_log = s.read() + count = get_validation_errors(self.stdout, module) + self.stdout.seek(0) + error_log = self.stdout.read() actual = error_log.split('\n') expected = module.model_errors.split('\n') @@ -33,5 +48,3 @@ def test_invalid_models(self): missing = [err for err in expected if err not in actual] self.assertFalse(unexpected, "Unexpected Errors: " + '\n'.join(unexpected)) self.assertFalse(missing, "Missing Errors: " + '\n'.join(missing)) - - From 71836f4c7693ac90b9cc38bbc575a55b9f74713e Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Thu, 25 Aug 2011 08:13:28 +0000 Subject: [PATCH 106/225] [1.3.X] Fixed a small admin CSS issue where the "Save and continue editing" and "Save and add another" buttons were wrongly aligned with left-to-right languages in IE7. Backport of r16683 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16684 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/css/forms.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css index 35d0ed796dfd..1cedf24b5b45 100644 --- a/django/contrib/admin/media/css/forms.css +++ b/django/contrib/admin/media/css/forms.css @@ -352,3 +352,9 @@ fieldset.monospace textarea { .empty-form { display: none; } + +/* IE7 specific bug fixes */ + +.submit-row input { + float: right; +} \ No newline at end of file From 1f7c6c011a54583237c9f7d9bfcee77faec73e1f Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Fri, 9 Sep 2011 21:43:11 +0000 Subject: [PATCH 107/225] [1.3.X] Fixed #16791 -- Updated a broken URL in the README file. Thanks to paulcwatts for the report and patch. Backport of r16743 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16744 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index a0ad13fe8b89..c7d225c1e2f9 100644 --- a/README +++ b/README @@ -42,5 +42,5 @@ To run Django's test suite: * Follow the instructions in the "Unit tests" section of docs/internals/contributing.txt, published online at - http://docs.djangoproject.com/en/dev/internals/contributing/#running-the-unit-tests + https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests From 52279a41137e13ab0056728a730b5d6ecf132841 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Fri, 9 Sep 2011 22:50:03 +0000 Subject: [PATCH 108/225] [1.3.X] Fixed #16408 -- Fixed conversion of dates, and other problems with the SpatiaLite backend. Backport of r16749 and r16750 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16751 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../gis/db/backends/spatialite/base.py | 10 ++-- .../gis/db/backends/spatialite/compiler.py | 32 +++++++++++++ .../gis/db/backends/spatialite/creation.py | 47 +++++++++++++++++-- .../gis/db/backends/spatialite/operations.py | 2 +- django/contrib/gis/db/models/sql/compiler.py | 2 +- django/contrib/gis/tests/geoapp/models.py | 1 + .../contrib/gis/tests/geoapp/test_regress.py | 14 ++++-- 7 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 django/contrib/gis/db/backends/spatialite/compiler.py diff --git a/django/contrib/gis/db/backends/spatialite/base.py b/django/contrib/gis/db/backends/spatialite/base.py index 729fc152e7f2..c12bcad78214 100644 --- a/django/contrib/gis/db/backends/spatialite/base.py +++ b/django/contrib/gis/db/backends/spatialite/base.py @@ -2,15 +2,16 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.db.backends.sqlite3.base import * -from django.db.backends.sqlite3.base import DatabaseWrapper as SqliteDatabaseWrapper, \ - _sqlite_extract, _sqlite_date_trunc, _sqlite_regexp +from django.db.backends.sqlite3.base import ( + _sqlite_extract, _sqlite_date_trunc, _sqlite_regexp, _sqlite_format_dtdelta, + connection_created, Database, DatabaseWrapper as SQLiteDatabaseWrapper, + SQLiteCursorWrapper) from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient from django.contrib.gis.db.backends.spatialite.creation import SpatiaLiteCreation from django.contrib.gis.db.backends.spatialite.introspection import SpatiaLiteIntrospection from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations -class DatabaseWrapper(SqliteDatabaseWrapper): +class DatabaseWrapper(SQLiteDatabaseWrapper): def __init__(self, *args, **kwargs): # Before we get too far, make sure pysqlite 2.5+ is installed. if Database.version_info < (2, 5, 0): @@ -51,6 +52,7 @@ def _cursor(self): self.connection.create_function("django_extract", 2, _sqlite_extract) self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) self.connection.create_function("regexp", 2, _sqlite_regexp) + self.connection.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta) connection_created.send(sender=self.__class__, connection=self) ## From here on, customized for GeoDjango ## diff --git a/django/contrib/gis/db/backends/spatialite/compiler.py b/django/contrib/gis/db/backends/spatialite/compiler.py new file mode 100644 index 000000000000..3f81ae68a13d --- /dev/null +++ b/django/contrib/gis/db/backends/spatialite/compiler.py @@ -0,0 +1,32 @@ +from django.db.backends.util import typecast_timestamp +from django.db.models.sql import compiler +from django.db.models.sql.constants import MULTI +from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler + +SQLCompiler = compiler.SQLCompiler + +class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler): + pass + +class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler): + pass + +class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler): + pass + +class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler): + pass + +class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): + pass + +class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): + """ + This is overridden for GeoDjango to properly cast date columns, see #16757. + """ + def results_iter(self): + offset = len(self.query.extra_select) + for rows in self.execute_sql(MULTI): + for row in rows: + date = typecast_timestamp(str(row[offset])) + yield date diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py index 41baf53e7f23..c107d96e72da 100644 --- a/django/contrib/gis/db/backends/spatialite/creation.py +++ b/django/contrib/gis/db/backends/spatialite/creation.py @@ -3,7 +3,6 @@ from django.core.cache import get_cache from django.core.cache.backends.db import BaseDatabaseCache from django.core.exceptions import ImproperlyConfigured -from django.core.management import call_command from django.db.backends.sqlite3.creation import DatabaseCreation class SpatiaLiteCreation(DatabaseCreation): @@ -16,26 +15,64 @@ def create_test_db(self, verbosity=1, autoclobber=False): This method is overloaded to load up the SpatiaLite initialization SQL prior to calling the `syncdb` command. """ + # Don't import django.core.management if it isn't needed. + from django.core.management import call_command + + test_database_name = self._get_test_db_name() + if verbosity >= 1: - print "Creating test database '%s'..." % self.connection.alias + test_db_repr = '' + if verbosity >= 2: + test_db_repr = " ('%s')" % test_database_name + print "Creating test database for alias '%s'%s..." % (self.connection.alias, test_db_repr) - test_database_name = self._create_test_db(verbosity, autoclobber) + self._create_test_db(verbosity, autoclobber) self.connection.close() - self.connection.settings_dict["NAME"] = test_database_name + # Confirm the feature set of the test database self.connection.features.confirm() + # Need to load the SpatiaLite initialization SQL before running `syncdb`. self.load_spatialite_sql() - call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias) + # Report syncdb messages at one level lower than that requested. + # This ensures we don't get flooded with messages during testing + # (unless you really ask to be flooded) + call_command('syncdb', + verbosity=max(verbosity - 1, 0), + interactive=False, + database=self.connection.alias, + load_initial_data=False) + + # We need to then do a flush to ensure that any data installed by + # custom SQL has been removed. The only test data should come from + # test fixtures, or autogenerated from post_syncdb triggers. + # This has the side effect of loading initial data (which was + # intentionally skipped in the syncdb). + call_command('flush', + verbosity=max(verbosity - 1, 0), + interactive=False, + database=self.connection.alias) + + # One effect of calling syncdb followed by flush is that the id of the + # default site may or may not be 1, depending on how the sequence was + # reset. If the sites app is loaded, then we coerce it. + from django.db.models import get_model + Site = get_model('sites', 'Site') + if Site is not None and Site.objects.using(self.connection.alias).count() == 1: + Site.objects.using(self.connection.alias).update(id=settings.SITE_ID) + + from django.core.cache import get_cache + from django.core.cache.backends.db import BaseDatabaseCache for cache_alias in settings.CACHES: cache = get_cache(cache_alias) if isinstance(cache, BaseDatabaseCache): from django.db import router if router.allow_syncdb(self.connection.alias, cache.cache_model_class): call_command('createcachetable', cache._table, database=self.connection.alias) + # Get a cursor (even though we don't need one yet). This has # the side effect of initializing the test database. cursor = self.connection.cursor() diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index e6f8409fdb2b..1dc612c97b86 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -48,7 +48,7 @@ def get_dist_ops(operator): return (SpatiaLiteDistance(operator),) class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): - compiler_module = 'django.contrib.gis.db.models.sql.compiler' + compiler_module = 'django.contrib.gis.db.backends.spatialite.compiler' name = 'spatialite' spatialite = True version_regex = re.compile(r'^(?P\d)\.(?P\d)\.(?P\d+)') diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index dea0fd3e8300..782ce7836845 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -202,7 +202,7 @@ def resolve_columns(self, row, fields=()): #### Routines unique to GeoQuery #### def get_extra_select_format(self, alias): sel_fmt = '%s' - if alias in self.query.custom_select: + if hasattr(self.query, 'custom_select') and alias in self.query.custom_select: sel_fmt = sel_fmt % self.query.custom_select[alias] return sel_fmt diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py index 89027eedfbc9..1ac830953a76 100644 --- a/django/contrib/gis/tests/geoapp/models.py +++ b/django/contrib/gis/tests/geoapp/models.py @@ -19,6 +19,7 @@ def __unicode__(self): return self.name # This is an inherited model from City class PennsylvaniaCity(City): county = models.CharField(max_length=30) + founded = models.DateTimeField(null=True) objects = models.GeoManager() # TODO: This should be implicitly inherited. class State(models.Model): diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index 029552643ea8..e13bc7204a97 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -1,9 +1,10 @@ -import os, unittest +from datetime import datetime from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite from django.contrib.gis.shortcuts import render_to_kmz -from models import City +from django.test import TestCase +from models import City, PennsylvaniaCity -class GeoRegressionTests(unittest.TestCase): +class GeoRegressionTests(TestCase): def test01_update(self): "Testing GeoQuerySet.update(), see #10411." @@ -35,3 +36,10 @@ def test03_extent(self): extent = City.objects.filter(name='Pueblo').extent() for ref_val, val in zip(ref_ext, extent): self.assertAlmostEqual(ref_val, val, 4) + + def test04_unicode_date(self): + "Testing dates are converted properly, even on SpatiaLite, see #16408." + founded = datetime(1857, 5, 23) + mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)', + founded=founded) + self.assertEqual(founded, PennsylvaniaCity.objects.dates('founded', 'day')[0]) From afe47636f7dbbd18a79148c3c5d14e897a7d66dc Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Fri, 9 Sep 2011 23:27:31 +0000 Subject: [PATCH 109/225] [1.3.X] Fixed #16782 -- Corrected a broken cross-reference to the database engine setting in the tutorial. Thanks to mjumbewu for the report and patch. Backport of r16754 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16755 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 23c05ba3a4b8..2f2e049a1644 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -41,7 +41,7 @@ code, then run the following command: .. code-block:: bash django-admin.py startproject mysite - + This will create a ``mysite`` directory in your current directory. .. admonition:: Script name may differ in distribution packages @@ -173,11 +173,11 @@ module-level variables representing Django settings. Change the following keys in the :setting:`DATABASES` ``'default'`` item to match your databases connection settings. - * :setting:`ENGINE` -- Either + * :setting:`ENGINE ` -- Either ``'django.db.backends.postgresql_psycopg2'``, ``'django.db.backends.mysql'`` or ``'django.db.backends.sqlite3'``. Other backends are - :setting:`also available `. + :setting:`also available `. * :setting:`NAME` -- The name of your database. If you're using SQLite, the database will be a file on your computer; in that @@ -692,7 +692,7 @@ Save these changes and start a new Python interactive shell by running For more information on model relations, see :doc:`Accessing related objects `. For more on how to use double underscores to perform -field lookups via the API, see `Field lookups`__. For full details on the +field lookups via the API, see `Field lookups`__. For full details on the database API, see our :doc:`Database API reference `. __ http://docs.djangoproject.com/en/1.2/topics/db/queries/#field-lookups From 2f7fadc38efa58ac0a8f93f936b82332a199f396 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 10 Sep 2011 01:07:50 +0000 Subject: [PATCH 110/225] [1.3.X] Added protection against spoofing of X_FORWARDED_HOST headers. A security announcement will be made shortly. Backport of r16758 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16761 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 2 + django/http/__init__.py | 3 +- docs/ref/request-response.txt | 9 +-- docs/ref/settings.txt | 13 ++++ tests/regressiontests/requests/tests.py | 90 +++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 5 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 97b7ac670f07..5ef6886ac50d 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -399,6 +399,8 @@ DEFAULT_TABLESPACE = '' DEFAULT_INDEX_TABLESPACE = '' +USE_X_FORWARDED_HOST = False + ############## # MIDDLEWARE # ############## diff --git a/django/http/__init__.py b/django/http/__init__.py index 6fccd24da07f..68ac45d6e318 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -153,7 +153,8 @@ def __repr__(self): def get_host(self): """Returns the HTTP host using the environment or request headers.""" # We try three options, in order of decreasing preference. - if 'HTTP_X_FORWARDED_HOST' in self.META: + if settings.USE_X_FORWARDED_HOST and ( + 'HTTP_X_FORWARDED_HOST' in self.META): host = self.META['HTTP_X_FORWARDED_HOST'] elif 'HTTP_HOST' in self.META: host = self.META['HTTP_HOST'] diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index eb34f1e1475e..d15a07cdaeed 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -191,10 +191,11 @@ Methods .. method:: HttpRequest.get_host() - Returns the originating host of the request using information from the - ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If - they don't provide a value, the method uses a combination of - ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. + Returns the originating host of the request using information from + the ``HTTP_X_FORWARDED_HOST`` (if enabled in the settings) and ``HTTP_HOST`` + headers (in that order). If they don't provide a value, the method + uses a combination of ``SERVER_NAME`` and ``SERVER_PORT`` as + detailed in :pep:`3333`. .. _PEP 333: http://www.python.org/dev/peps/pep-0333/ diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 6d69a085d9ff..175e50818c75 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1960,6 +1960,19 @@ in order to format numbers. See also :setting:`THOUSAND_SEPARATOR` and :setting:`NUMBER_GROUPING`. +.. setting:: USE_X_FORWARDED_HOST + +USE_X_FORWARDED_HOST +-------------------- + +.. versionadded:: 1.3.1 + +Default: ``False`` + +A boolean that specifies whether to use the X-Forwarded-Host header in +preference to the Host header. This should only be enabled if a proxy +which sets this header is in use. + .. setting:: YEAR_MONTH_FORMAT YEAR_MONTH_FORMAT diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index 154c1fa07dde..cd488e2fed7d 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -2,12 +2,14 @@ from datetime import datetime, timedelta from StringIO import StringIO +from django.conf import settings from django.core.handlers.modpython import ModPythonRequest from django.core.handlers.wsgi import WSGIRequest, LimitedStream from django.http import HttpRequest, HttpResponse, parse_cookie from django.utils import unittest from django.utils.http import cookie_date + class RequestsTests(unittest.TestCase): def test_httprequest(self): request = HttpRequest() @@ -58,6 +60,94 @@ def test_httprequest_location(self): self.assertEqual(request.build_absolute_uri(location="/path/with:colons"), 'http://www.example.com/path/with:colons') + def test_http_get_host(self): + old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST + try: + settings.USE_X_FORWARDED_HOST = False + + # Check if X_FORWARDED_HOST is provided. + request = HttpRequest() + request.META = { + u'HTTP_X_FORWARDED_HOST': u'forward.com', + u'HTTP_HOST': u'example.com', + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 80, + } + # X_FORWARDED_HOST is ignored. + self.assertEqual(request.get_host(), 'example.com') + + # Check if X_FORWARDED_HOST isn't provided. + request = HttpRequest() + request.META = { + u'HTTP_HOST': u'example.com', + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'example.com') + + # Check if HTTP_HOST isn't provided. + request = HttpRequest() + request.META = { + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'internal.com') + + # Check if HTTP_HOST isn't provided, and we're on a nonstandard port + request = HttpRequest() + request.META = { + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 8042, + } + self.assertEqual(request.get_host(), 'internal.com:8042') + + finally: + settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST + + def test_http_get_host_with_x_forwarded_host(self): + old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST + try: + settings.USE_X_FORWARDED_HOST = True + + # Check if X_FORWARDED_HOST is provided. + request = HttpRequest() + request.META = { + u'HTTP_X_FORWARDED_HOST': u'forward.com', + u'HTTP_HOST': u'example.com', + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 80, + } + # X_FORWARDED_HOST is obeyed. + self.assertEqual(request.get_host(), 'forward.com') + + # Check if X_FORWARDED_HOST isn't provided. + request = HttpRequest() + request.META = { + u'HTTP_HOST': u'example.com', + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'example.com') + + # Check if HTTP_HOST isn't provided. + request = HttpRequest() + request.META = { + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'internal.com') + + # Check if HTTP_HOST isn't provided, and we're on a nonstandard port + request = HttpRequest() + request.META = { + u'SERVER_NAME': u'internal.com', + u'SERVER_PORT': 8042, + } + self.assertEqual(request.get_host(), 'internal.com:8042') + + finally: + settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST + def test_near_expiration(self): "Cookie will expire when an near expiration time is provided" response = HttpResponse() From fbe2eead2fa9d808658ca582241bcacb02618840 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 10 Sep 2011 01:08:02 +0000 Subject: [PATCH 111/225] [1.3.X] Corrected an issue which could allow attackers to manipulate session data using the cache. A security announcement will be made shortly. Backport of r16759 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16762 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sessions/backends/cache.py | 10 ++++++---- django/contrib/sessions/backends/cached_db.py | 14 +++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index ab0716dcb446..b326b8b0d42b 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -1,6 +1,8 @@ from django.contrib.sessions.backends.base import SessionBase, CreateError from django.core.cache import cache +KEY_PREFIX = "django.contrib.sessions.cache" + class SessionStore(SessionBase): """ A cache-based session store. @@ -10,7 +12,7 @@ def __init__(self, session_key=None): super(SessionStore, self).__init__(session_key) def load(self): - session_data = self._cache.get(self.session_key) + session_data = self._cache.get(KEY_PREFIX + self.session_key) if session_data is not None: return session_data self.create() @@ -37,13 +39,13 @@ def save(self, must_create=False): func = self._cache.add else: func = self._cache.set - result = func(self.session_key, self._get_session(no_load=must_create), + result = func(KEY_PREFIX + self.session_key, self._get_session(no_load=must_create), self.get_expiry_age()) if must_create and not result: raise CreateError def exists(self, session_key): - if self._cache.has_key(session_key): + if self._cache.has_key(KEY_PREFIX + session_key): return True return False @@ -52,5 +54,5 @@ def delete(self, session_key=None): if self._session_key is None: return session_key = self._session_key - self._cache.delete(session_key) + self._cache.delete(KEY_PREFIX + session_key) diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py index 9e22c69228f0..465472d29c48 100644 --- a/django/contrib/sessions/backends/cached_db.py +++ b/django/contrib/sessions/backends/cached_db.py @@ -6,6 +6,8 @@ from django.contrib.sessions.backends.db import SessionStore as DBStore from django.core.cache import cache +KEY_PREFIX = "django.contrib.sessions.cached_db" + class SessionStore(DBStore): """ Implements cached, database backed sessions. @@ -15,10 +17,11 @@ def __init__(self, session_key=None): super(SessionStore, self).__init__(session_key) def load(self): - data = cache.get(self.session_key, None) + data = cache.get(KEY_PREFIX + self.session_key, None) if data is None: data = super(SessionStore, self).load() - cache.set(self.session_key, data, settings.SESSION_COOKIE_AGE) + cache.set(KEY_PREFIX + self.session_key, data, + settings.SESSION_COOKIE_AGE) return data def exists(self, session_key): @@ -26,11 +29,12 @@ def exists(self, session_key): def save(self, must_create=False): super(SessionStore, self).save(must_create) - cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE) + cache.set(KEY_PREFIX + self.session_key, self._session, + settings.SESSION_COOKIE_AGE) def delete(self, session_key=None): super(SessionStore, self).delete(session_key) - cache.delete(session_key or self.session_key) + cache.delete(KEY_PREFIX + (session_key or self.session_key)) def flush(self): """ @@ -39,4 +43,4 @@ def flush(self): """ self.clear() self.delete(self.session_key) - self.create() \ No newline at end of file + self.create() From 1a76dbefdfc60e2d5954c0ba614c3d054ba9c3f0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 10 Sep 2011 01:08:24 +0000 Subject: [PATCH 112/225] [1.3.X] Altered the behavior of URLField to avoid a potential DOS vector, and to avoid potential leakage of local filesystem data. A security announcement will be made shortly. Backport of r16760 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16763 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/validators.py | 50 +++++++++++++-------- django/db/models/fields/__init__.py | 2 +- docs/internals/deprecation.txt | 6 +++ docs/ref/forms/fields.txt | 5 +++ docs/ref/models/fields.txt | 13 ++++-- docs/ref/settings.txt | 26 ++++++----- tests/modeltests/validation/__init__.py | 4 +- tests/modeltests/validation/models.py | 1 + tests/modeltests/validation/tests.py | 23 +++++----- tests/regressiontests/forms/tests/fields.py | 14 ++---- 10 files changed, 87 insertions(+), 57 deletions(-) diff --git a/django/core/validators.py b/django/core/validators.py index a40af0c8ddb0..a93c6ac97568 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -1,3 +1,4 @@ +import platform import re import urllib2 import urlparse @@ -39,10 +40,6 @@ def __call__(self, value): if not self.regex.search(smart_unicode(value)): raise ValidationError(self.message, code=self.code) -class HeadRequest(urllib2.Request): - def get_method(self): - return "HEAD" - class URLValidator(RegexValidator): regex = re.compile( r'^(?:http|ftp)s?://' # http:// or https:// @@ -52,7 +49,8 @@ class URLValidator(RegexValidator): r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) - def __init__(self, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT): + def __init__(self, verify_exists=False, + validator_user_agent=URL_VALIDATOR_USER_AGENT): super(URLValidator, self).__init__() self.verify_exists = verify_exists self.user_agent = validator_user_agent @@ -76,6 +74,7 @@ def __call__(self, value): else: url = value + #This is deprecated and will be removed in a future release. if self.verify_exists: headers = { "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", @@ -88,21 +87,36 @@ def __call__(self, value): broken_error = ValidationError( _(u'This URL appears to be a broken link.'), code='invalid_link') try: - req = HeadRequest(url, None, headers) - u = urllib2.urlopen(req) + req = urllib2.Request(url, None, headers) + req.get_method = lambda: 'HEAD' + #Create an opener that does not support local file access + opener = urllib2.OpenerDirector() + + #Don't follow redirects, but don't treat them as errors either + error_nop = lambda *args, **kwargs: True + http_error_processor = urllib2.HTTPErrorProcessor() + http_error_processor.http_error_301 = error_nop + http_error_processor.http_error_302 = error_nop + http_error_processor.http_error_307 = error_nop + + handlers = [urllib2.UnknownHandler(), + urllib2.HTTPHandler(), + urllib2.HTTPDefaultErrorHandler(), + urllib2.FTPHandler(), + http_error_processor] + try: + import ssl + handlers.append(urllib2.HTTPSHandler()) + except: + #Python isn't compiled with SSL support + pass + map(opener.add_handler, handlers) + if platform.python_version_tuple() >= (2, 6): + opener.open(req, timeout=10) + else: + opener.open(req) except ValueError: raise ValidationError(_(u'Enter a valid URL.'), code='invalid') - except urllib2.HTTPError, e: - if e.code in (405, 501): - # Try a GET request (HEAD refused) - # See also: http://www.w3.org/Protocols/rfc2616/rfc2616.html - try: - req = urllib2.Request(url, None, headers) - u = urllib2.urlopen(req) - except: - raise broken_error - else: - raise broken_error except: # urllib2.URLError, httplib.InvalidURL, etc. raise broken_error diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 8081cf3954fb..dfacb667c787 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1119,7 +1119,7 @@ def formfield(self, **kwargs): class URLField(CharField): description = _("URL") - def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): + def __init__(self, verbose_name=None, name=None, verify_exists=False, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 200) CharField.__init__(self, verbose_name, name, **kwargs) self.validators.append(validators.URLValidator(verify_exists=verify_exists)) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index c7f8bcbcc0f6..3f0f998b4a10 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -108,6 +108,12 @@ their deprecation, as per the :ref:`Django deprecation policy beyond that of a simple ``TextField`` since the removal of oldforms. All uses of ``XMLField`` can be replaced with ``TextField``. + * ``django.db.models.fields.URLField.verify_exists`` has been + deprecated due to intractable security and performance + issues. Validation behavior has been removed in 1.4, and the + argument will be removed in 1.5. + + * 1.5 * The ``mod_python`` request handler has been deprecated since the 1.3 release. The ``mod_wsgi`` handler should be used instead. diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 59a6df82d062..11647b33b285 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -756,6 +756,11 @@ Takes the following optional arguments: If ``True``, the validator will attempt to load the given URL, raising ``ValidationError`` if the page gives a 404. Defaults to ``False``. +.. deprecated:: 1.3.1 + + ``verify_exists`` was deprecated for security reasons and will be + removed in 1.4. This deprecation also removes ``validator_user_agent``. + .. attribute:: URLField.validator_user_agent String used as the user-agent used when checking for a URL's existence. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 2fb5d494b15a..36e2b109b8e1 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -831,14 +831,21 @@ shortcuts. ``URLField`` ------------ -.. class:: URLField([verify_exists=True, max_length=200, **options]) +.. class:: URLField([verify_exists=False, max_length=200, **options]) A :class:`CharField` for a URL. Has one extra optional argument: +.. deprecated:: 1.3.1 + + ``verify_exists`` is deprecated for security reasons as of 1.3.1 + and will be removed in 1.4. Prior to 1.3.1, the default value was + ``True``. + .. attribute:: URLField.verify_exists - If ``True`` (the default), the URL given will be checked for existence - (i.e., the URL actually loads and doesn't give a 404 response). + If ``True``, the URL given will be checked for existence (i.e., + the URL actually loads and doesn't give a 404 response) using a + ``HEAD`` request. Redirects are allowed, but will not be followed. Note that when you're using the single-threaded development server, validating a URL being served by the same server will hang. This should not diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 175e50818c75..18155f19fc1a 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1892,16 +1892,6 @@ to ensure your processes are running in the correct environment. .. _See available choices: http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE -.. setting:: URL_VALIDATOR_USER_AGENT - -URL_VALIDATOR_USER_AGENT ------------------------- - -Default: ``Django/ (http://www.djangoproject.com/)`` - -The string to use as the ``User-Agent`` header when checking to see if URLs -exist (see the ``verify_exists`` option on :class:`~django.db.models.URLField`). - .. setting:: USE_ETAGS USE_ETAGS @@ -2095,3 +2085,19 @@ TEST_DATABASE_NAME This setting has been replaced by :setting:`TEST_NAME` in :setting:`DATABASES`. + + +URL_VALIDATOR_USER_AGENT +------------------------ + +.. deprecated:: 1.3.1 + This setting has been removed due to intractable performance and + security problems. + +Default: ``Django/ (http://www.djangoproject.com/)`` + +The string to use as the ``User-Agent`` header when checking to see if +URLs exist (see the ``verify_exists`` option on +:class:`~django.db.models.URLField`). This setting was deprecated in +1.3.1 along with ``verify_exists`` and will be removed in 1.4. + diff --git a/tests/modeltests/validation/__init__.py b/tests/modeltests/validation/__init__.py index c8a89cd36f32..31c5000ba69f 100644 --- a/tests/modeltests/validation/__init__.py +++ b/tests/modeltests/validation/__init__.py @@ -1,8 +1,8 @@ -from django.utils import unittest +from django.test import TestCase from django.core.exceptions import ValidationError -class ValidationTestCase(unittest.TestCase): +class ValidationTestCase(TestCase): def assertFailsValidation(self, clean, failed_fields): self.assertRaises(ValidationError, clean) try: diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py index 861d1440fe78..91ad87203da9 100644 --- a/tests/modeltests/validation/models.py +++ b/tests/modeltests/validation/models.py @@ -15,6 +15,7 @@ class ModelToValidate(models.Model): parent = models.ForeignKey('self', blank=True, null=True, limit_choices_to={'number': 10}) email = models.EmailField(blank=True) url = models.URLField(blank=True) + url_verify = models.URLField(blank=True, verify_exists=True) f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe]) def clean(self): diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py index 4236d8e7c401..eaf130be29ad 100644 --- a/tests/modeltests/validation/tests.py +++ b/tests/modeltests/validation/tests.py @@ -53,25 +53,22 @@ def test_wrong_url_value_raises_error(self): mtv = ModelToValidate(number=10, name='Some Name', url='not a url') self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'Enter a valid value.']) + #The tests below which use url_verify are deprecated def test_correct_url_but_nonexisting_gives_404(self): - mtv = ModelToValidate(number=10, name='Some Name', url='http://google.com/we-love-microsoft.html') - self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.']) + mtv = ModelToValidate(number=10, name='Some Name', url_verify='http://qa-dev.w3.org/link-testsuite/http.php?code=404') + self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url_verify', [u'This URL appears to be a broken link.']) def test_correct_url_value_passes(self): - mtv = ModelToValidate(number=10, name='Some Name', url='http://www.example.com/') + mtv = ModelToValidate(number=10, name='Some Name', url_verify='http://www.google.com/') self.assertEqual(None, mtv.full_clean()) # This will fail if there's no Internet connection - def test_correct_https_url_but_nonexisting(self): - mtv = ModelToValidate(number=10, name='Some Name', url='https://www.example.com/') - self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.']) - - def test_correct_ftp_url_but_nonexisting(self): - mtv = ModelToValidate(number=10, name='Some Name', url='ftp://ftp.google.com/we-love-microsoft.html') - self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.']) + def test_correct_url_with_redirect(self): + mtv = ModelToValidate(number=10, name='Some Name', url_verify='http://qa-dev.w3.org/link-testsuite/http.php?code=301') #example.com is a redirect to iana.org now + self.assertEqual(None, mtv.full_clean()) # This will fail if there's no Internet connection - def test_correct_ftps_url_but_nonexisting(self): - mtv = ModelToValidate(number=10, name='Some Name', url='ftps://ftp.google.com/we-love-microsoft.html') - self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.']) + def test_correct_https_url_but_nonexisting(self): + mtv = ModelToValidate(number=10, name='Some Name', url_verify='https://www.example.com/') + self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url_verify', [u'This URL appears to be a broken link.']) def test_text_greater_that_charfields_max_length_raises_erros(self): mtv = ModelToValidate(number=10, name='Some Name'*100) diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index f76e7327eb8c..e963cf2f1b0d 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -567,7 +567,7 @@ def test_urlfield_3(self): f.clean('http://www.broken.djangoproject.com') # bad domain except ValidationError, e: self.assertEqual("[u'This URL appears to be a broken link.']", str(e)) - self.assertRaises(ValidationError, f.clean, 'http://google.com/we-love-microsoft.html') # good domain, bad page + self.assertRaises(ValidationError, f.clean, 'http://qa-dev.w3.org/link-testsuite/http.php?code=400') # good domain, bad page try: f.clean('http://google.com/we-love-microsoft.html') # good domain, bad page except ValidationError, e: @@ -626,16 +626,10 @@ def test_urlfield_9(self): self.assertEqual("[u'This URL appears to be a broken link.']", str(e)) def test_urlfield_10(self): - # UTF-8 char in path, enclosed by a monkey-patch to make sure - # the encoding is passed to urllib2.urlopen + # UTF-8 in the domain. f = URLField(verify_exists=True) - try: - _orig_urlopen = urllib2.urlopen - urllib2.urlopen = lambda req: True - url = u'http://t\xfcr.djangoproject.com/' - self.assertEqual(url, f.clean(url)) - finally: - urllib2.urlopen = _orig_urlopen + url = u'http://\u03b5\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac.idn.icann.org/\u0391\u03c1\u03c7\u03b9\u03ba\u03ae_\u03c3\u03b5\u03bb\u03af\u03b4\u03b1' + self.assertEqual(url, f.clean(url)) #This will fail without internet. # BooleanField ################################################################ From 722d4068a7e798f65b01d0d60676f7e8eff8703b Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sat, 10 Sep 2011 01:32:14 +0000 Subject: [PATCH 113/225] [1.3.X] Bump to 1.3.1 for security release. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16767 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index faa34cd4d2d6..287452848983 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 0, 'final', 0) +VERSION = (1, 3, 1, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/setup.py b/setup.py index 77aaca7d03b8..1d8732fa242b 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'http://media.djangoproject.com/releases/1.3/Django-1.3.tar.gz', + download_url = 'http://media.djangoproject.com/releases/1.3/Django-1.3.1.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From 5e451b9e6f201d6e29256555c148be0847a42b3d Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sat, 10 Sep 2011 01:34:55 +0000 Subject: [PATCH 114/225] [1.3.X] Bump docs version number, too. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16768 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 3d88ffeddeed..7165b92f73d3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ # The short X.Y version. version = '1.3' # The full version, including alpha/beta/rc tags. -release = '1.3' +release = '1.3.1' # The next version to be released django_next_version = '1.4' From 2954c36ff7631a0e0180a6f8aa112fe782294d2e Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sun, 11 Sep 2011 04:06:29 +0000 Subject: [PATCH 115/225] [1.3.X] Fixed #16079: Clarified (for real this time) how handler404 and handler500 work, and that they only work in a root URLconf. Backport of [16804] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16805 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial03.txt | 18 ++++++++-------- docs/topics/http/urls.txt | 42 ++++++++++++++++++++++++++------------ docs/topics/http/views.txt | 2 ++ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 41a62a72d724..867855406023 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -357,11 +357,13 @@ the list is empty. Write a 404 (page not found) view ================================= -When you raise :exc:`~django.http.Http404` from within a view, Django will load -a special view devoted to handling 404 errors. It finds it by looking for the -variable ``handler404``, which is a string in Python dotted syntax -- the same -format the normal URLconf callbacks use. A 404 view itself has nothing special: -It's just a normal view. +When you raise :exc:`~django.http.Http404` from within a view, Django +will load a special view devoted to handling 404 errors. It finds it +by looking for the variable ``handler404`` in your root URLconf (and +only in your root URLconf; setting ``handler404`` anywhere else will +have no effect), which is a string in Python dotted syntax -- the same +format the normal URLconf callbacks use. A 404 view itself has nothing +special: It's just a normal view. You normally won't have to bother with writing 404 views. By default, URLconfs have the following line up top:: @@ -393,9 +395,9 @@ Four more things to note about 404 views: Write a 500 (server error) view =============================== -Similarly, URLconfs may define a ``handler500``, which points to a view to call -in case of server errors. Server errors happen when you have runtime errors in -view code. +Similarly, your root URLconf may define a ``handler500``, which points +to a view to call in case of server errors. Server errors happen when +you have runtime errors in view code. Use the template system ======================= diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 1caa801c5d76..08dd94d8382f 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -54,6 +54,10 @@ algorithm the system follows to determine which Python code to execute: :class:`~django.http.HttpRequest` as its first argument and any values captured in the regex as remaining arguments. + 5. If no regex matches, or if an exception is raised during any + point in this process, Django invokes an appropriate + error-handling view. See `Error handling`_ below. + Example ======= @@ -246,6 +250,31 @@ The ``prefix`` parameter has the same meaning as the first argument to ``patterns()`` and is only relevant when you're passing a string as the ``view`` parameter. +include +------- + +.. function:: include() + +A function that takes a full Python import path to another URLconf module that +should be "included" in this place. + +:func:`include` also accepts as an argument an iterable that returns URL +patterns. + +See `Including other URLconfs`_ below. + +Error handling +============== + +When Django can't find a regex matching the requested URL, or when an +exception is raised, Django will invoke an error-handling view. The +views to use for these cases are specified by two variables which can +be set in your root URLconf. Setting these variables in any other +URLconf will have no effect. + +See the documentation on :ref:`customizing error views +` for more details. + handler404 ---------- @@ -275,19 +304,6 @@ value should suffice. .. versionchanged:: 1.2 Previous versions of Django only accepted strings representing import paths. -include -------- - -.. function:: include() - -A function that takes a full Python import path to another URLconf module that -should be "included" in this place. - -:func:`include` also accepts as an argument an iterable that returns URL -patterns. - -See `Including other URLconfs`_ below. - Notes on capturing text in URLs =============================== diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index 99359ab4ad3e..83a52cbba402 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -122,6 +122,8 @@ In order to use the ``Http404`` exception to its fullest, you should create a template that is displayed when a 404 error is raised. This template should be called ``404.html`` and located in the top level of your template tree. +.. _customizing-error-views: + Customizing error views ======================= From 0326e2e61138f8495a4b863d6960e7666c054944 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sun, 11 Sep 2011 05:32:14 +0000 Subject: [PATCH 116/225] [1.3.X] Fixed #16552: Noted that contrib.sessions is a requirement for the admin. Backport of [16806] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16807 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index beff94ee7bbb..d5b16e1e9984 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -19,8 +19,10 @@ There are six steps in activating the Django admin site: 1. Add ``'django.contrib.admin'`` to your :setting:`INSTALLED_APPS` setting. - 2. Admin has two dependencies - :mod:`django.contrib.auth` and - :mod:`django.contrib.contenttypes`. If these applications are not + 2. The admin has four dependencies - :mod:`django.contrib.auth`, + :mod:`django.contrib.contenttypes`, + :mod:`django.contrib.messages` and + :mod:`django.contrib.sessions`. If these applications are not in your :setting:`INSTALLED_APPS` list, add them. 3. Determine which of your application's models should be editable in the From f242c02fb97254715cf929d011851e4469d1fcfe Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sun, 11 Sep 2011 05:38:50 +0000 Subject: [PATCH 117/225] [1.3.X] Fixed #16293: Document a way to return dicts with column names from a DB cursor. Backport of [16808] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16809 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/sql.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index fe7173609c50..d9b8b1a4d3b5 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -236,6 +236,30 @@ alias:: # Your code here... transaction.commit_unless_managed(using='my_db_alias') +By default, the Python DB API will return results without their field +names, which means you end up with a ``list`` of values, rather than a +``dict``. At a small performance cost, you can return results as a +``dict`` by using something like this:: + + def dictfetchall(cursor): + "Returns all rows from a cursor as a dict" + desc = cursor.description + return [ + dict(zip([col[0] for col in desc], row)) + for row in cursor.fetchall() + ] + +Here is an example of the difference between the two:: + + >>> cursor.execute("SELECT id, parent_id from test LIMIT 2"); + >>> cursor.fetchall() + ((54360982L, None), (54360880L, None)) + + >>> cursor.execute("SELECT id, parent_id from test LIMIT 2"); + >>> dictfetchall(cursor) + [{'parent_id': None, 'id': 54360982L}, {'parent_id': None, 'id': 54360880L}] + + .. _transactions-and-raw-sql: Transactions and raw SQL From 16787c690396d5ce4d24070bd910e23ba6559c84 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sun, 11 Sep 2011 05:45:26 +0000 Subject: [PATCH 118/225] [1.3.X] Fixed #16109: Corrected an inconsistency in URLconf examples for matching a numeric month. Backport of [16811] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16812 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/instances.txt | 6 +++--- docs/topics/http/urls.txt | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 3728a098048a..52c0869146b7 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -470,7 +470,7 @@ the URL. For example, if your URLconf contained a line such as:: Similarly, if you had a URLconf entry that looked like:: - (r'/archive/(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/$', archive_view) + (r'/archive/(?P\d{4})/(?P\d{2})/(?P\d{2})/$', archive_view) ...you could reference this using ``permalink()`` as follows:: @@ -478,8 +478,8 @@ Similarly, if you had a URLconf entry that looked like:: def get_absolute_url(self): return ('archive_view', (), { 'year': self.created.year, - 'month': self.created.month, - 'day': self.created.day}) + 'month': self.created.strftime('%m'), + 'day': self.created.strftime('%d')}) Notice that we specify an empty sequence for the second parameter in this case, because we only want to pass keyword parameters, not positional ones. diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 08dd94d8382f..a5cf21bb658f 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -103,8 +103,8 @@ Example requests: * ``/articles/2003`` would not match any of these patterns, because each pattern requires that the URL end with a slash. - * ``/articles/2003/03/3/`` would match the final pattern. Django would call - the function ``news.views.article_detail(request, '2003', '03', '3')``. + * ``/articles/2003/03/03/`` would match the final pattern. Django would call + the function ``news.views.article_detail(request, '2003', '03', '03')``. .. _Dive Into Python's explanation: http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3 @@ -127,7 +127,7 @@ Here's the above example URLconf, rewritten to use named groups:: (r'^articles/2003/$', 'news.views.special_case_2003'), (r'^articles/(?P\d{4})/$', 'news.views.year_archive'), (r'^articles/(?P\d{4})/(?P\d{2})/$', 'news.views.month_archive'), - (r'^articles/(?P\d{4})/(?P\d{2})/(?P\d+)/$', 'news.views.article_detail'), + (r'^articles/(?P\d{4})/(?P\d{2})/(?P\d{2})/$', 'news.views.article_detail'), ) This accomplishes exactly the same thing as the previous example, with one @@ -138,8 +138,8 @@ arguments rather than positional arguments. For example: ``news.views.month_archive(request, year='2005', month='03')``, instead of ``news.views.month_archive(request, '2005', '03')``. - * A request to ``/articles/2003/03/3/`` would call the function - ``news.views.article_detail(request, year='2003', month='03', day='3')``. + * A request to ``/articles/2003/03/03/`` would call the function + ``news.views.article_detail(request, year='2003', month='03', day='03')``. In practice, this means your URLconfs are slightly more explicit and less prone to argument-order bugs -- and you can reorder the arguments in your views' From 460150975c2ef7ff8ab087682bc30ac462a7e935 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sun, 11 Sep 2011 05:48:39 +0000 Subject: [PATCH 119/225] [1.3.X] Fixed #16094: Added clear example of how to refer to custom permissions. Backport of [16813] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16814 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 635f18f8b2bf..14bff48193aa 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1251,16 +1251,19 @@ can or cannot do with Task instances, specific to your application:: ... class Meta: permissions = ( - ("can_view", "Can see available tasks"), - ("can_change_status", "Can change the status of tasks"), - ("can_close", "Can remove a task by setting its status as closed"), + ("view_task", "Can see available tasks"), + ("change_task_status", "Can change the status of tasks"), + ("close_task", "Can remove a task by setting its status as closed"), ) The only thing this does is create those extra permissions when you run :djadmin:`manage.py syncdb `. Your code is in charge of checking the value of these permissions when an user is trying to access the functionality provided by the application (viewing tasks, changing the status of tasks, -closing tasks.) +closing tasks.) Continuing the above example, the following checks if a user may +view tasks: + + user.has_perm('app.view_task') API reference ------------- From 2c8e45e1f4a85691cb6349b37d4340ab8e33b023 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sun, 11 Sep 2011 05:58:34 +0000 Subject: [PATCH 120/225] [1.3.X] Fixed #16334: Make it quite clear that cache_page's 'cache' argument refers to the name of a cache in the CACHES setting. Backport of [16815] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16816 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/cache.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 8ef4ea2f3086..fe50160ccc5e 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -526,9 +526,10 @@ you may expect. But once a particular URL (e.g., ``/foo/23/``) has been requested, subsequent requests to that URL will use the cache. ``cache_page`` can also take an optional keyword argument, ``cache``, -which directs the decorator to use a specific cache alias when caching view -results. By default, the ``default`` alias will be used, but you can specify -any cache alias you want:: +which directs the decorator to use a specific cache (from your +:setting:`CACHES` setting) when caching view results. By default, the +``default`` cache will be used, but you can specify any cache you +want:: @cache_page(60 * 15, cache="special_cache") def my_view(request): From 70a69017751cc26c33f4a1de295fbd24e7545e3c Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 14 Sep 2011 20:33:42 +0000 Subject: [PATCH 121/225] [1.3.X] Refs #16839 - Added basic release notes for 1.2.6 and 1.3.1. Backport of r16777 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16827 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/releases/1.2.6.txt | 16 ++++++++++++++++ docs/releases/1.3.1.txt | 16 ++++++++++++++++ docs/releases/index.txt | 2 ++ 3 files changed, 34 insertions(+) create mode 100644 docs/releases/1.2.6.txt create mode 100644 docs/releases/1.3.1.txt diff --git a/docs/releases/1.2.6.txt b/docs/releases/1.2.6.txt new file mode 100644 index 000000000000..cfd1d9c0d4ba --- /dev/null +++ b/docs/releases/1.2.6.txt @@ -0,0 +1,16 @@ +========================== +Django 1.2.6 release notes +========================== + +*September 9, 2011* + +Welcome to Django 1.2.6! + +This is the sixth bugfix/security release in the Django 1.2 series, fixing +several security issues present in Django 1.2.5. Django 1.2.6 is a +recommended upgrade for all users of any Django release in the 1.2.X series. + +For a full list of issues addressed in this release, see the `security +advisory`_. + +.. _security advisory: https://www.djangoproject.com/weblog/2011/sep/09/security-releases-issued/ diff --git a/docs/releases/1.3.1.txt b/docs/releases/1.3.1.txt new file mode 100644 index 000000000000..4c289161e1aa --- /dev/null +++ b/docs/releases/1.3.1.txt @@ -0,0 +1,16 @@ +========================== +Django 1.3.1 release notes +========================== + +*September 9, 2011* + +Welcome to Django 1.3.1! + +This is the first security release in the Django 1.3 series, fixing several +security issues in Django 1.3. Django 1.3.1 is a recommended upgrade for +all users of Django 1.3. + +For a full list of issues addressed in this release, see the `security +advisory`_. + +.. _security advisory: https://www.djangoproject.com/weblog/2011/sep/09/security-releases-issued/ diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 8d23c28fc8fb..9fc3dc8f61cc 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -19,6 +19,7 @@ Final releases .. toctree:: :maxdepth: 1 + 1.3.1 1.3 1.2 release @@ -26,6 +27,7 @@ Final releases .. toctree:: :maxdepth: 1 + 1.2.6 1.2.5 1.2.4 1.2.2 From ee23919a8af3f046b7579cc3f477cad48510acb9 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 14 Sep 2011 20:33:55 +0000 Subject: [PATCH 122/225] [1.3.X] Fixed #16839 - Added basic release notes for 1.2.7. Thanks claudep for the report. Backport of r16803 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16828 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/releases/1.2.7.txt | 16 ++++++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 17 insertions(+) create mode 100644 docs/releases/1.2.7.txt diff --git a/docs/releases/1.2.7.txt b/docs/releases/1.2.7.txt new file mode 100644 index 000000000000..c0cf6980f25c --- /dev/null +++ b/docs/releases/1.2.7.txt @@ -0,0 +1,16 @@ +========================== +Django 1.2.7 release notes +========================== + +*September 10, 2011* + +Welcome to Django 1.2.7! + +This is the seventh bugfix/security release in the Django 1.2 series. It +replaces Django 1.2.6 due to problems with the 1.2.6 release tarball. +Django 1.2.7 is a recommended upgrade for all users of any Django release in +the 1.2.X series. + +For more information, see the `release advisory`_. + +.. _release advisory: https://www.djangoproject.com/weblog/2011/sep/10/127/ diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 9fc3dc8f61cc..40fe5b0e02b4 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -27,6 +27,7 @@ Final releases .. toctree:: :maxdepth: 1 + 1.2.7 1.2.6 1.2.5 1.2.4 From 27c8d612800e7b6adadc7b112bb1619934cd799e Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Fri, 16 Sep 2011 06:26:22 +0000 Subject: [PATCH 123/225] [1.3.X] Reverted the change in r16684, which, while fixing an alignment issue in IE7 with the admin's "Save and continue editing" and "Save and add another" buttons, caused the swapping of those buttons' order. Thanks to jocmeh for the report. Refs #16852. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16834 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/css/forms.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css index 1cedf24b5b45..35d0ed796dfd 100644 --- a/django/contrib/admin/media/css/forms.css +++ b/django/contrib/admin/media/css/forms.css @@ -352,9 +352,3 @@ fieldset.monospace textarea { .empty-form { display: none; } - -/* IE7 specific bug fixes */ - -.submit-row input { - float: right; -} \ No newline at end of file From c9676d035fa885734abc7f539c1392b64db5fc9b Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 16 Sep 2011 10:57:53 +0000 Subject: [PATCH 124/225] [1.3.X] Fixed #16094 -- Added missing colon in custom permissions docs. Backport of r16836 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16837 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 14bff48193aa..5a2608a3a799 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1261,7 +1261,7 @@ The only thing this does is create those extra permissions when you run value of these permissions when an user is trying to access the functionality provided by the application (viewing tasks, changing the status of tasks, closing tasks.) Continuing the above example, the following checks if a user may -view tasks: +view tasks:: user.has_perm('app.view_task') From ae51b46d1904ac14c3c67e440db9ae3506f300ec Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 17 Sep 2011 18:08:28 +0000 Subject: [PATCH 125/225] [1.3.X] Fixed #14648 -- Fixed annotated date querysets when `GeoManager` is used. Thanks, codysoyland, for the bug report. Backport of r16796 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16844 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../gis/db/backends/spatialite/compiler.py | 32 ------------------ .../gis/db/backends/spatialite/operations.py | 2 +- django/contrib/gis/db/models/sql/compiler.py | 28 ++++++++++++--- .../relatedapp/fixtures/initial_data.json.gz | Bin 528 -> 526 bytes django/contrib/gis/tests/relatedapp/models.py | 1 + django/contrib/gis/tests/relatedapp/tests.py | 8 +++++ 6 files changed, 34 insertions(+), 37 deletions(-) delete mode 100644 django/contrib/gis/db/backends/spatialite/compiler.py diff --git a/django/contrib/gis/db/backends/spatialite/compiler.py b/django/contrib/gis/db/backends/spatialite/compiler.py deleted file mode 100644 index 3f81ae68a13d..000000000000 --- a/django/contrib/gis/db/backends/spatialite/compiler.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.db.backends.util import typecast_timestamp -from django.db.models.sql import compiler -from django.db.models.sql.constants import MULTI -from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler - -SQLCompiler = compiler.SQLCompiler - -class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler): - pass - -class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler): - pass - -class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler): - pass - -class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler): - pass - -class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): - pass - -class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): - """ - This is overridden for GeoDjango to properly cast date columns, see #16757. - """ - def results_iter(self): - offset = len(self.query.extra_select) - for rows in self.execute_sql(MULTI): - for row in rows: - date = typecast_timestamp(str(row[offset])) - yield date diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 1dc612c97b86..e6f8409fdb2b 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -48,7 +48,7 @@ def get_dist_ops(operator): return (SpatiaLiteDistance(operator),) class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): - compiler_module = 'django.contrib.gis.db.backends.spatialite.compiler' + compiler_module = 'django.contrib.gis.db.models.sql.compiler' name = 'spatialite' spatialite = True version_regex = re.compile(r'^(?P\d)\.(?P\d)\.(?P\d+)') diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index 782ce7836845..405a000f4239 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -1,7 +1,7 @@ from itertools import izip -from django.db.backends.util import truncate_name +from django.db.backends.util import truncate_name, typecast_timestamp from django.db.models.sql import compiler -from django.db.models.sql.constants import TABLE_NAME +from django.db.models.sql.constants import TABLE_NAME, MULTI from django.db.models.sql.query import get_proxied_model SQLCompiler = compiler.SQLCompiler @@ -194,7 +194,7 @@ def resolve_columns(self, row, fields=()): # We resolve the rest of the columns if we're on Oracle or if # the `geo_values` attribute is defined. for value, field in map(None, row[index_start:], fields): - values.append(self.query.convert_values(value, field, connection=self.connection)) + values.append(self.query.convert_values(value, field, self.connection)) else: values.extend(row[index_start:]) return tuple(values) @@ -275,4 +275,24 @@ class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): pass class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): - pass + """ + This is overridden for GeoDjango to properly cast date columns, since + `GeoQuery.resolve_columns` is used for spatial values. + See #14648, #16757. + """ + def results_iter(self): + if self.connection.ops.oracle: + from django.db.models.fields import DateTimeField + fields = [DateTimeField()] + else: + needs_string_cast = self.connection.features.needs_datetime_string_cast + + offset = len(self.query.extra_select) + for rows in self.execute_sql(MULTI): + for row in rows: + date = row[offset] + if self.connection.ops.oracle: + date = self.resolve_columns(row, fields)[offset] + elif needs_string_cast: + date = typecast_timestamp(str(date)) + yield date diff --git a/django/contrib/gis/tests/relatedapp/fixtures/initial_data.json.gz b/django/contrib/gis/tests/relatedapp/fixtures/initial_data.json.gz index 68bf54c1b0d196a4e3b35d1b9045a6d8bf3f416e..893763724b819f615c09dd01e1b5705f5352d4c6 100644 GIT binary patch literal 526 zcmV+p0`dJHiwFSQ&TCEp1GUsoZ<{a_0PuT1#S^DZDhS&c$fg~VMpL6HUDP6_)wF{X zyny0hWK1_S<-5-$T^dz{5vCllK0Cc^tY-G%ZsauK*rg_Zr z+!1ogbSg9JWz3J1i?}d@_6yWY$TZXStcUx__>)Jl|8e$pau>lndk{Dj`3NB(#Bpgz z2zhVq=50;6y?*1smvSK2b0`j7>emnw1{mWu2)Ps_7&xBq2E+r;bx`O9ejCHP*t<}N z_ykb|c;Ey+A%WNCpcixaPUzMF0t7ojh+P`AIojfMnd<7aA!8d}PZ*X;D)utYv=`dU zuREg0*T%z<%#QtnwHxMM7C*TV4Vt@YqXzej*lwCHe7PffM9@n@-E z##VwKT~RC-buStOo=t5`tW$=zKdGx>o(hq&jX`KtH5jOCBO9G0v|YnU%Jpf0PPN$I zf_afrafsNj^cBGN{-aEC&gnaDJ%JlpiWnl3P2fqEFe)`f3D0(w`L6GlJ#)|!797UB z&`MT3hD8Naeh#)R&2+?$1k2X&kV{>F%s?j`rYt`P(Uw~}B6eX?Z+|26N~NnrHvkUl Qc6;^x4;-0u?=uMi0Q%YZ6#xJL literal 528 zcmV+r0`L7FiwFo=x%Nx|18Ht)bZKF1Uu0o)VJ>QOZ*Bm!lwE7vFcgOG`zsE<+MpA* zB54>6BVA|>SsA-fVl6gNEF;U^Ov!(roOEd^j;46zLIQmpz0Z+eo$nU_Pr3m( z<=VLd?{Yubh?H?Xb|m9ev6Pl2;$lfvE(+&)&(ECkw9=dQ2ee0syije_{oQ2z$tO7c zIQu%eP2k;!L4*P?^gIs;L2eKeLf$*OxUKHDmv1=mxi~!6M*)uAAZ$TM9AS)2Amm&i z(E$0O8xbFT7kRNCg(eLDZ0`~v5)v=)01r?U5)%0)j^Jz@A&TABAV4sRV(bQyiNoa6 zWv-gz3?>TCO^HX2VV%($BhfJY&i^t?ZvF=;vQMm>W@7>R@sU8vid%3-g*K{BM@eLMoW+ zF6(hQZ*fdtYFMjG$bSPRl06CuGrHoeaE>XN{)?^BJm);88!e?|b5zNc(@2Q*aUGQ_ zzE4SCZ!H-l^mqPpp>&V=sw|w6q^0x(u0_pLNOWm{2T{YQR*+<@*bU~pwHx}#KnYkd z7_&-A(XbR24NTc7u%?<}2|MDnSi^lOR0W~{l`)vo@)QVDp|FJ5xuKQ67G)#zRi-)s S4z+W8@#7D)DyrK62><{}zVY(_ diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py index 2e9a62b61f26..aec4e1574973 100644 --- a/django/contrib/gis/tests/relatedapp/models.py +++ b/django/contrib/gis/tests/relatedapp/models.py @@ -36,6 +36,7 @@ def __unicode__(self): return self.name # These use the GeoManager but do not have any geographic fields. class Author(models.Model): name = models.CharField(max_length=100) + dob = models.DateField() objects = models.GeoManager() class Article(models.Model): diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 250783b2de14..1a6197cb6dbf 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -1,3 +1,4 @@ +from datetime import date from django.test import TestCase from django.contrib.gis.geos import GEOSGeometry, Point, MultiPoint @@ -281,4 +282,11 @@ def test15_invalid_select_related(self): # evaluated as list generation swallows TypeError in CPython. sql = str(qs.query) + def test16_annotated_date_queryset(self): + "Ensure annotated date querysets work if spatial backend is used. See #14648." + birth_years = [dt.year for dt in + list(Author.objects.annotate(num_books=Count('books')).dates('dob', 'year'))] + birth_years.sort() + self.assertEqual([1950, 1974], birth_years) + # TODO: Related tests for KML, GML, and distance lookups. From add0628528d12e63cca0e9603bbcd93315ee142f Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Wed, 21 Sep 2011 01:37:17 +0000 Subject: [PATCH 126/225] [1.3.X] Fixed #16886 -- Memcached socket file documentation. Thanks ddbeck for the report and patch. Backport of [16858] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16859 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/cache.txt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index fe50160ccc5e..9b683532bd06 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -99,8 +99,9 @@ To use Memcached with Django: on your chosen memcached binding) * Set :setting:`LOCATION ` to ``ip:port`` values, - where ``ip`` is the IP address of the Memcached daemon and - ``port`` is the port on which Memcached is running. + where ``ip`` is the IP address of the Memcached daemon and ``port`` is the + port on which Memcached is running, or to a ``unix:path`` value, where + ``path`` is the path to a Memcached Unix socket file. In this example, Memcached is running on localhost (127.0.0.1) port 11211, using the ``python-memcached`` binding:: @@ -112,6 +113,16 @@ the ``python-memcached`` binding:: } } +In this example, Memcached is available through a local Unix socket file +:file:`/tmp/memcached.sock` using the ``python-memcached`` binding:: + + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': 'unix:/tmp/memcached.sock', + } + } + One excellent feature of Memcached is its ability to share cache over multiple servers. This means you can run Memcached daemons on multiple machines, and the program will treat the group of machines as a *single* cache, without the need From 3606f1f7b2eb422a49944824b1d8dbc227dc879f Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Wed, 21 Sep 2011 22:45:25 +0000 Subject: [PATCH 127/225] [1.3.X] Fixed #16904 -- Additional clarification regarding contrib.messages iteration. Thanks murphyke for the report and patch. Backport of [16866] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16867 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/messages.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index ca3212df1fd7..ee7e01c61fc0 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -210,6 +210,10 @@ If you're using the context processor, your template should be rendered with a ``RequestContext``. Otherwise, ensure ``messages`` is available to the template context. +Even if you know there is only just one message, you should still iterate over +the ``messages`` sequence, because otherwise the message storage will not be cleared +for the next request. + Creating custom message levels ------------------------------ From 65942eb31f448f6ac1cc16b8ff29b18253fa103d Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 21 Sep 2011 22:58:15 +0000 Subject: [PATCH 128/225] [1.3.X] Fixed #16353 -- don't try to create Site objects on all databases. Refs #15573, #15346. Thanks Aymeric Augustin for the report and the patch. Backport of r16868 in trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16869 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/db/backends/spatialite/creation.py | 8 -------- django/contrib/sites/management.py | 11 +++++++++-- django/db/backends/creation.py | 9 --------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py index c107d96e72da..ee5f9db33616 100644 --- a/django/contrib/gis/db/backends/spatialite/creation.py +++ b/django/contrib/gis/db/backends/spatialite/creation.py @@ -56,14 +56,6 @@ def create_test_db(self, verbosity=1, autoclobber=False): interactive=False, database=self.connection.alias) - # One effect of calling syncdb followed by flush is that the id of the - # default site may or may not be 1, depending on how the sequence was - # reset. If the sites app is loaded, then we coerce it. - from django.db.models import get_model - Site = get_model('sites', 'Site') - if Site is not None and Site.objects.using(self.connection.alias).count() == 1: - Site.objects.using(self.connection.alias).update(id=settings.SITE_ID) - from django.core.cache import get_cache from django.core.cache.backends.db import BaseDatabaseCache for cache_alias in settings.CACHES: diff --git a/django/contrib/sites/management.py b/django/contrib/sites/management.py index 19872740eeb2..daec3d9eec2e 100644 --- a/django/contrib/sites/management.py +++ b/django/contrib/sites/management.py @@ -3,14 +3,21 @@ """ from django.db.models import signals +from django.db import router from django.contrib.sites.models import Site from django.contrib.sites import models as site_app def create_default_site(app, created_models, verbosity, db, **kwargs): - if Site in created_models: + # Only create the default sites in databases where Django created the table + if Site in created_models and router.allow_syncdb(db, Site) : if verbosity >= 2: print "Creating example.com Site object" - s = Site(domain="example.com", name="example.com") + # The default settings set SITE_ID = 1, and some tests in Django's test + # suite rely on this value. However, if database sequences are reused + # (e.g. in the test suite after flush/syncdb), it isn't guaranteed that + # the next id will be 1, so we coerce it. See #15573 and #16353. This + # can also crop up outside of tests - see #15346. + s = Site(pk=1, domain="example.com", name="example.com") s.save(using=db) Site.objects.clear_cache() diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 57e3f7762dd8..ef594b7bfc5c 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -374,15 +374,6 @@ def create_test_db(self, verbosity=1, autoclobber=False): verbosity=max(verbosity - 1, 0), interactive=False, database=self.connection.alias) - - # One effect of calling syncdb followed by flush is that the id of the - # default site may or may not be 1, depending on how the sequence was - # reset. If the sites app is loaded, then we coerce it. - from django.db.models import get_model - if 'django.contrib.sites' in settings.INSTALLED_APPS: - Site = get_model('sites', 'Site') - if Site is not None and Site.objects.using(self.connection.alias).count() == 1: - Site.objects.using(self.connection.alias).update(id=settings.SITE_ID) from django.core.cache import get_cache from django.core.cache.backends.db import BaseDatabaseCache From 5978d7a341aaa7d2adb8579e65c5028de0271f26 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 22 Sep 2011 00:58:42 +0000 Subject: [PATCH 129/225] [1.3.X] backport minor docs fix for GeoIP git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16870 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/gis/geoip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/gis/geoip.txt b/docs/ref/contrib/gis/geoip.txt index 6503be7c3fdb..f5c45e133be9 100644 --- a/docs/ref/contrib/gis/geoip.txt +++ b/docs/ref/contrib/gis/geoip.txt @@ -144,7 +144,7 @@ parameters. Returns a dictionary of city information for the given query. Some of the values in the dictionary may be undefined (``None``). -.. method:: GeoIPcountry(query) +.. method:: GeoIP.country(query) Returns a dictionary with the country code and country for the given query. From 2a4aa8bcf7be28bac9d5ed6a029c2c3f52f389e7 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 22 Sep 2011 05:36:57 +0000 Subject: [PATCH 130/225] [1.3.X] Fixed #16837 -- Improved error messages for admin login. Thanks Wim Feijen for the patch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16878 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/forms.py | 4 ++-- tests/regressiontests/admin_views/tests.py | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/django/contrib/admin/forms.py b/django/contrib/admin/forms.py index f26c10014d49..e790e2e3b681 100644 --- a/django/contrib/admin/forms.py +++ b/django/contrib/admin/forms.py @@ -6,8 +6,8 @@ from django.utils.translation import ugettext_lazy, ugettext as _ -ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. " - "Note that both fields are case-sensitive.") +ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password " + "for a staff account. Note that both fields are case-sensitive.") class AdminAuthenticationForm(AuthenticationForm): """ diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 57bd87f8e673..7f4b05fab60f 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -38,6 +38,9 @@ Question, Answer, Inquisition, Actor, FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory) +ERROR_MESSAGE = "Please enter the correct username and password \ +for a staff account. Note that both fields are case-sensitive." + class AdminViewBasicTest(TestCase): fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', @@ -638,12 +641,12 @@ def testLogin(self): self.assertContains(login, "Your e-mail address is not your username") # only correct passwords get a username hint login = self.client.post('/test_admin/admin/', self.super_email_bad_login) - self.assertContains(login, "Please enter a correct username and password.") + self.assertContains(login, ERROR_MESSAGE) new_user = User(username='jondoe', password='secret', email='super@example.com') new_user.save() # check to ensure if there are multiple e-mail addresses a user doesn't get a 500 login = self.client.post('/test_admin/admin/', self.super_email_login) - self.assertContains(login, "Please enter a correct username and password.") + self.assertContains(login, ERROR_MESSAGE) # Add User request = self.client.get('/test_admin/admin/') @@ -674,7 +677,7 @@ def testLogin(self): self.assertEqual(request.status_code, 200) login = self.client.post('/test_admin/admin/', self.joepublic_login) self.assertEqual(login.status_code, 200) - self.assertContains(login, "Please enter a correct username and password.") + self.assertContains(login, ERROR_MESSAGE) # Requests without username should not return 500 errors. request = self.client.get('/test_admin/admin/') @@ -1234,12 +1237,12 @@ def test_staff_member_required_decorator_works_as_per_admin_login(self): self.assertContains(login, "Your e-mail address is not your username") # only correct passwords get a username hint login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login) - self.assertContains(login, "Please enter a correct username and password.") + self.assertContains(login, ERROR_MESSAGE) new_user = User(username='jondoe', password='secret', email='super@example.com') new_user.save() # check to ensure if there are multiple e-mail addresses a user doesn't get a 500 login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login) - self.assertContains(login, "Please enter a correct username and password.") + self.assertContains(login, ERROR_MESSAGE) # Add User request = self.client.get('/test_admin/admin/secure-view/') @@ -1271,7 +1274,7 @@ def test_staff_member_required_decorator_works_as_per_admin_login(self): login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login) self.assertEqual(login.status_code, 200) # Login.context is a list of context dicts we just need to check the first one. - self.assertContains(login, "Please enter a correct username and password.") + self.assertContains(login, ERROR_MESSAGE) # 8509 - if a normal user is already logged in, it is possible # to change user into the superuser without error From 35e807c4a5fb5bb4d72ede54c92d7359a19c3370 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 22 Sep 2011 05:39:07 +0000 Subject: [PATCH 131/225] [1.3.X] Fixed #15633 -- Improved docs for post_syncdb signal. Thanks Justin Lilly for the patch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16879 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/signals.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index e83142e79700..f3f22992edf1 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -352,12 +352,16 @@ post_syncdb .. data:: django.db.models.signals.post_syncdb :module: -Sent by :djadmin:`syncdb` after it installs an application. +Sent by :djadmin:`syncdb` command after it installs an application, and +:djadmin:`flush` command. Any handlers that listen to this signal need to be written in a particular place: a ``management`` module in one of your :setting:`INSTALLED_APPS`. If handlers are registered anywhere else they may not be loaded by -:djadmin:`syncdb`. +:djadmin:`syncdb`. It is important that handlers of this signal perform +idempodent changes (e.g. no database alterations) as this may cause the +:djadmin:`flush` management command to fail if it also ran during the +:djadmin:`syncdb` command. Arguments sent with this signal: From 4443c6fd72ce9504d6736d03fd68948106deabbc Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 22 Sep 2011 05:40:54 +0000 Subject: [PATCH 132/225] [1.3.X] Spelling fix for r16879. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16880 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/signals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index f3f22992edf1..ad25dce368d8 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -359,7 +359,7 @@ Any handlers that listen to this signal need to be written in a particular place: a ``management`` module in one of your :setting:`INSTALLED_APPS`. If handlers are registered anywhere else they may not be loaded by :djadmin:`syncdb`. It is important that handlers of this signal perform -idempodent changes (e.g. no database alterations) as this may cause the +idempotent changes (e.g. no database alterations) as this may cause the :djadmin:`flush` management command to fail if it also ran during the :djadmin:`syncdb` command. From ed156a44caed412d5852912bfc7e93357e9452b9 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 22 Sep 2011 05:48:42 +0000 Subject: [PATCH 133/225] [1.3.X] Fixed #11674 -- Clarified docs on excluded fields of ModelForms. Thanks PieterSwinkels for the patch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16881 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/forms/modelforms.txt | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 07bc5e300165..24e000ece0fe 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -332,13 +332,17 @@ Since the Author model has only 3 fields, 'name', 'title', and .. note:: If you specify ``fields`` or ``exclude`` when creating a form with - ``ModelForm``, then the fields that are not in the resulting form will not - be set by the form's ``save()`` method. Django will prevent any attempt to - save an incomplete model, so if the model does not allow the missing fields - to be empty, and does not provide a default value for the missing fields, - any attempt to ``save()`` a ``ModelForm`` with missing fields will fail. - To avoid this failure, you must instantiate your model with initial values - for the missing, but required fields:: + ``ModelForm``, then the fields that are not in the resulting form + will not be set by the form's ``save()`` method. Also, if you + manually add the excluded fields back to the form, they will not + be initialized from the model instance. + + Django will prevent any attempt to save an incomplete model, so if + the model does not allow the missing fields to be empty, and does + not provide a default value for the missing fields, any attempt to + ``save()`` a ``ModelForm`` with missing fields will fail. To + avoid this failure, you must instantiate your model with initial + values for the missing, but required fields:: author = Author(title='Mr') form = PartialAuthorForm(request.POST, instance=author) @@ -633,6 +637,12 @@ database. If a given instance's data didn't change in the bound data, the instance won't be saved to the database and won't be included in the return value (``instances``, in the above example). +When fields are missing from the form (for example because they have +been excluded), these fields will not be set by the ``save()`` +method. You can find more information about this restriction, which +also holds for regular ``ModelForms``, in `Using a subset of fields on +the form`_. + Pass ``commit=False`` to return the unsaved model instances:: # don't save to the database From e3bc2590816b684bc44ce8516b3786a5027cc384 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 22 Sep 2011 22:55:47 +0000 Subject: [PATCH 134/225] [1.3.X] Reverting r16878 (improved admin error message) per advice from jezdez. refs #16837 git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16891 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/forms.py | 4 ++-- tests/regressiontests/admin_views/tests.py | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/django/contrib/admin/forms.py b/django/contrib/admin/forms.py index e790e2e3b681..f26c10014d49 100644 --- a/django/contrib/admin/forms.py +++ b/django/contrib/admin/forms.py @@ -6,8 +6,8 @@ from django.utils.translation import ugettext_lazy, ugettext as _ -ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password " - "for a staff account. Note that both fields are case-sensitive.") +ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. " + "Note that both fields are case-sensitive.") class AdminAuthenticationForm(AuthenticationForm): """ diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 7f4b05fab60f..57bd87f8e673 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -38,9 +38,6 @@ Question, Answer, Inquisition, Actor, FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory) -ERROR_MESSAGE = "Please enter the correct username and password \ -for a staff account. Note that both fields are case-sensitive." - class AdminViewBasicTest(TestCase): fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', @@ -641,12 +638,12 @@ def testLogin(self): self.assertContains(login, "Your e-mail address is not your username") # only correct passwords get a username hint login = self.client.post('/test_admin/admin/', self.super_email_bad_login) - self.assertContains(login, ERROR_MESSAGE) + self.assertContains(login, "Please enter a correct username and password.") new_user = User(username='jondoe', password='secret', email='super@example.com') new_user.save() # check to ensure if there are multiple e-mail addresses a user doesn't get a 500 login = self.client.post('/test_admin/admin/', self.super_email_login) - self.assertContains(login, ERROR_MESSAGE) + self.assertContains(login, "Please enter a correct username and password.") # Add User request = self.client.get('/test_admin/admin/') @@ -677,7 +674,7 @@ def testLogin(self): self.assertEqual(request.status_code, 200) login = self.client.post('/test_admin/admin/', self.joepublic_login) self.assertEqual(login.status_code, 200) - self.assertContains(login, ERROR_MESSAGE) + self.assertContains(login, "Please enter a correct username and password.") # Requests without username should not return 500 errors. request = self.client.get('/test_admin/admin/') @@ -1237,12 +1234,12 @@ def test_staff_member_required_decorator_works_as_per_admin_login(self): self.assertContains(login, "Your e-mail address is not your username") # only correct passwords get a username hint login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login) - self.assertContains(login, ERROR_MESSAGE) + self.assertContains(login, "Please enter a correct username and password.") new_user = User(username='jondoe', password='secret', email='super@example.com') new_user.save() # check to ensure if there are multiple e-mail addresses a user doesn't get a 500 login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login) - self.assertContains(login, ERROR_MESSAGE) + self.assertContains(login, "Please enter a correct username and password.") # Add User request = self.client.get('/test_admin/admin/secure-view/') @@ -1274,7 +1271,7 @@ def test_staff_member_required_decorator_works_as_per_admin_login(self): login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login) self.assertEqual(login.status_code, 200) # Login.context is a list of context dicts we just need to check the first one. - self.assertContains(login, ERROR_MESSAGE) + self.assertContains(login, "Please enter a correct username and password.") # 8509 - if a normal user is already logged in, it is possible # to change user into the superuser without error From a084a07ebe8357c5a05ad03c478c89c2e358a398 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 22 Sep 2011 23:02:57 +0000 Subject: [PATCH 135/225] [1.3.X] Fixed #16910 -- Misleading urlpatterns docs regex example. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16892 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/urls.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index a5cf21bb658f..e72c8dd4d8fe 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -436,8 +436,8 @@ directly the pattern list as returned by `patterns`_ instead. For example:: from django.conf.urls.defaults import * extra_patterns = patterns('', - url(r'reports/(?P\d+)/$', 'credit.views.report', name='credit-reports'), - url(r'charge/$', 'credit.views.charge', name='credit-charge'), + url(r'^reports/(?P\d+)/$', 'credit.views.report', name='credit-reports'), + url(r'^charge/$', 'credit.views.charge', name='credit-charge'), ) urlpatterns = patterns('', From 07c404be88c658698b89258e88d61b9af0366eaf Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 18 Oct 2011 18:09:23 +0000 Subject: [PATCH 136/225] [1.3.X] Refs #16072 -- Corrected blocktrans multiple-argument syntax example in the docs. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17011 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 5d50fa71f63c..a83299c1e835 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -477,7 +477,7 @@ for use within the translation block. Examples:: You can use multiple expressions inside a single ``blocktrans`` tag:: - {% blocktrans with book_t=book|title and author_t=author|title %} + {% blocktrans with book_t=book|title author_t=author|title %} This is {{ book_t }} by {{ author_t }} {% endblocktrans %} From 6372368b563404ad2011ef12bf65ec9fce6a59dc Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 27 Oct 2011 16:10:08 +0000 Subject: [PATCH 137/225] [1.3.X] Fixed #17123 -- corrected the path to admin static assets in 1.3 docs. Thanks glarrain for the report. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17044 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/deployment/modpython.txt | 2 +- docs/howto/deployment/modwsgi.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/deployment/modpython.txt b/docs/howto/deployment/modpython.txt index f5030e9c750a..75bdcd51cf76 100644 --- a/docs/howto/deployment/modpython.txt +++ b/docs/howto/deployment/modpython.txt @@ -293,7 +293,7 @@ of the admin app, but this is not the case when you use any other server arrangement. You're responsible for setting up Apache, or whichever media server you're using, to serve the admin files. -The admin files live in (:file:`django/contrib/admin/static/admin`) of the +The admin files live in (:file:`django/contrib/admin/media/admin`) of the Django distribution. We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle diff --git a/docs/howto/deployment/modwsgi.txt b/docs/howto/deployment/modwsgi.txt index de3a5b63aa1b..99cdbd1f2845 100644 --- a/docs/howto/deployment/modwsgi.txt +++ b/docs/howto/deployment/modwsgi.txt @@ -127,7 +127,7 @@ of the admin app, but this is not the case when you use any other server arrangement. You're responsible for setting up Apache, or whichever media server you're using, to serve the admin files. -The admin files live in (:file:`django/contrib/admin/static/admin`) of the +The admin files live in (:file:`django/contrib/admin/media/admin`) of the Django distribution. We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle From eabbb361d2ff4f40898a2891a74ff8a84c03d712 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sun, 6 Nov 2011 16:59:26 +0000 Subject: [PATCH 138/225] [1.3.X] Fixed #17171 -- Updated tutorial urls.py code snippets to match startproject template and recommended practice. (No 'import *', use 'urls()' function.). Thanks haimunt for report. Parts of this are a backport of r17073 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17074 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial02.txt | 14 +++++----- docs/intro/tutorial03.txt | 56 +++++++++++++++++++-------------------- docs/intro/tutorial04.txt | 16 +++++------ 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 4bd31fb99a90..1e837e6c27e8 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -40,22 +40,22 @@ activate the admin site for your installation, do these three things: .. parsed-literal:: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, include, url # Uncomment the next two lines to enable the admin: **from django.contrib import admin** **admin.autodiscover()** urlpatterns = patterns('', - # Example: - # (r'^mysite/', include('mysite.foo.urls')), + # Examples: + # url(r'^$', 'mysite.views.home', name='home'), + # url(r'^mysite/', include('mysite.foo.urls')), - # Uncomment the admin/doc line below and add 'django.contrib.admindocs' - # to INSTALLED_APPS to enable admin documentation: - # (r'^admin/doc/', include('django.contrib.admindocs.urls')), + # Uncomment the admin/doc line below to enable admin documentation: + # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: - **(r'^admin/', include(admin.site.urls)),** + **url(r'^admin/', include(admin.site.urls)),** ) (The bold lines are the ones that needed to be uncommented.) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 867855406023..ee2df3416497 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -78,17 +78,17 @@ point at that file:: Time for an example. Edit ``mysite/urls.py`` so it looks like this:: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', - (r'^polls/$', 'polls.views.index'), - (r'^polls/(?P\d+)/$', 'polls.views.detail'), - (r'^polls/(?P\d+)/results/$', 'polls.views.results'), - (r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), - (r'^admin/', include(admin.site.urls)), + url(r'^polls/$', 'polls.views.index'), + url(r'^polls/(?P\d+)/$', 'polls.views.detail'), + url(r'^polls/(?P\d+)/results/$', 'polls.views.results'), + url(r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), + url(r'^admin/', include(admin.site.urls)), ) This is worth a review. When somebody requests a page from your Web site -- say, @@ -112,7 +112,7 @@ what you can do with them. And there's no need to add URL cruft such as ``.php`` -- unless you have a sick sense of humor, in which case you can do something like this:: - (r'^polls/latest\.php$', 'polls.views.index'), + url(r'^polls/latest\.php$', 'polls.views.index'), But, don't do that. It's silly. @@ -434,10 +434,10 @@ Take some time to play around with the views and template system. As you edit the URLconf, you may notice there's a fair bit of redundancy in it:: urlpatterns = patterns('', - (r'^polls/$', 'polls.views.index'), - (r'^polls/(?P\d+)/$', 'polls.views.detail'), - (r'^polls/(?P\d+)/results/$', 'polls.views.results'), - (r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), + url(r'^polls/$', 'polls.views.index'), + url(r'^polls/(?P\d+)/$', 'polls.views.detail'), + url(r'^polls/(?P\d+)/results/$', 'polls.views.results'), + url(r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), ) Namely, ``polls.views`` is in every callback. @@ -447,10 +447,10 @@ common prefixes. You can factor out the common prefixes and add them as the first argument to :func:`~django.conf.urls.defaults.patterns`, like so:: urlpatterns = patterns('polls.views', - (r'^polls/$', 'index'), - (r'^polls/(?P\d+)/$', 'detail'), - (r'^polls/(?P\d+)/results/$', 'results'), - (r'^polls/(?P\d+)/vote/$', 'vote'), + url(r'^polls/$', 'index'), + url(r'^polls/(?P\d+)/$', 'detail'), + url(r'^polls/(?P\d+)/results/$', 'results'), + url(r'^polls/(?P\d+)/vote/$', 'vote'), ) This is functionally identical to the previous formatting. It's just a bit @@ -461,20 +461,20 @@ callback in your URLconf, you can concatenate multiple :func:`~django.conf.urls.defaults.patterns`. Your full ``mysite/urls.py`` might now look like this:: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('polls.views', - (r'^polls/$', 'index'), - (r'^polls/(?P\d+)/$', 'detail'), - (r'^polls/(?P\d+)/results/$', 'results'), - (r'^polls/(?P\d+)/vote/$', 'vote'), + url(r'^polls/$', 'index'), + url(r'^polls/(?P\d+)/$', 'detail'), + url(r'^polls/(?P\d+)/results/$', 'results'), + url(r'^polls/(?P\d+)/vote/$', 'vote'), ) urlpatterns += patterns('', - (r'^admin/', include(admin.site.urls)), + url(r'^admin/', include(admin.site.urls)), ) Decoupling the URLconfs @@ -504,8 +504,8 @@ Copy the file ``mysite/urls.py`` to ``polls/urls.py``. Then, change admin.autodiscover() urlpatterns = patterns('', - (r'^polls/', include('polls.urls')), - (r'^admin/', include(admin.site.urls)), + url(r'^polls/', include('polls.urls')), + url(r'^admin/', include(admin.site.urls)), ) :func:`~django.conf.urls.defaults.include` simply references another URLconf. @@ -528,13 +528,13 @@ URLconf by removing the leading "polls/" from each line, and removing the lines registering the admin site. Your ``polls/urls.py`` file should now look like this:: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, include, url urlpatterns = patterns('polls.views', - (r'^$', 'index'), - (r'^(?P\d+)/$', 'detail'), - (r'^(?P\d+)/results/$', 'results'), - (r'^(?P\d+)/vote/$', 'vote'), + url(r'^$', 'index'), + url(r'^(?P\d+)/$', 'detail'), + url(r'^(?P\d+)/results/$', 'results'), + url(r'^(?P\d+)/vote/$', 'vote'), ) The idea behind :func:`~django.conf.urls.defaults.include` and URLconf diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index ded5cb2199df..4c2f2d4d5e1a 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -218,13 +218,13 @@ Read on for details. First, open the ``polls/urls.py`` URLconf. It looks like this, according to the tutorial so far:: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, include, url urlpatterns = patterns('polls.views', - (r'^$', 'index'), - (r'^(?P\d+)/$', 'detail'), - (r'^(?P\d+)/results/$', 'results'), - (r'^(?P\d+)/vote/$', 'vote'), + url(r'^$', 'index'), + url(r'^(?P\d+)/$', 'detail'), + url(r'^(?P\d+)/results/$', 'results'), + url(r'^(?P\d+)/vote/$', 'vote'), ) Change it like so:: @@ -234,12 +234,12 @@ Change it like so:: from polls.models import Poll urlpatterns = patterns('', - (r'^$', + url(r'^$', ListView.as_view( queryset=Poll.objects.order_by('-pub_date')[:5], context_object_name='latest_poll_list', template_name='polls/index.html')), - (r'^(?P\d+)/$', + url(r'^(?P\d+)/$', DetailView.as_view( model=Poll, template_name='polls/detail.html')), @@ -248,7 +248,7 @@ Change it like so:: model=Poll, template_name='polls/results.html'), name='poll_results'), - (r'^(?P\d+)/vote/$', 'polls.views.vote'), + url(r'^(?P\d+)/vote/$', 'polls.views.vote'), ) We're using two generic views here: From bf5fdf1397c48e3a56819d4d8bf20b54d1cb377b Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 19 Nov 2011 22:57:47 +0000 Subject: [PATCH 139/225] [1.3.X] Fixed #17028 - Changed diveintopython.org -> diveintopython.net Backport of r17115 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17116 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/index.txt | 2 +- docs/ref/django-admin.txt | 2 +- docs/ref/templates/builtins.txt | 2 +- docs/topics/http/urls.txt | 2 +- docs/topics/settings.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/intro/index.txt b/docs/intro/index.txt index bc61be778a25..19290a53c61a 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -31,6 +31,6 @@ place: read this material to quickly get up and running. .. _python: http://python.org/ .. _list of Python resources for non-programmers: http://wiki.python.org/moin/BeginnersGuide/NonProgrammers - .. _dive into python: http://diveintopython.org/ + .. _dive into python: http://diveintopython.net/ .. _dead-tree version: http://www.amazon.com/exec/obidos/ASIN/1590593561/ref=nosim/jacobian20 .. _books about Python: http://wiki.python.org/moin/PythonBooks \ No newline at end of file diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 89bc43ff73f6..58b86ead14b6 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1156,7 +1156,7 @@ variable. Note that this option is unnecessary in ``manage.py``, because it takes care of setting the Python path for you. -.. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html +.. _import search path: http://diveintopython.net/getting_to_know_python/everything_is_an_object.html .. django-admin-option:: --settings diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 7d24c1dc008e..6c7057ab6004 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1868,7 +1868,7 @@ slice Returns a slice of the list. Uses the same syntax as Python's list slicing. See -http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice +http://diveintopython.net/native_data_types/lists.html#odbchelper.list.slice for an introduction. Example:: diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index e72c8dd4d8fe..7a0e7243e8da 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -106,7 +106,7 @@ Example requests: * ``/articles/2003/03/03/`` would match the final pattern. Django would call the function ``news.views.article_detail(request, '2003', '03', '03')``. -.. _Dive Into Python's explanation: http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3 +.. _Dive Into Python's explanation: http://diveintopython.net/regular_expressions/street_addresses.html#re.matching.2.3 Named groups ============ diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index 61ddf8cf32eb..bda51f2a137f 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -39,7 +39,7 @@ The value of ``DJANGO_SETTINGS_MODULE`` should be in Python path syntax, e.g. ``mysite.settings``. Note that the settings module should be on the Python `import search path`_. -.. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html +.. _import search path: http://diveintopython.net/getting_to_know_python/everything_is_an_object.html The django-admin.py utility --------------------------- From 68f37a908162ac54cf90c50da6e4f4515ad7bf93 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 3 Dec 2011 21:17:41 +0000 Subject: [PATCH 140/225] =?UTF-8?q?[1.3.X]=20Backported=20the=20fix=20for?= =?UTF-8?q?=20#15852=20--=20Modified=20cookie=20parsing=20so=20it=20can=20?= =?UTF-8?q?handle=20duplicate=20invalid=20cookie=20names.=20Thanks=20goes?= =?UTF-8?q?=20to=20Fredrik=20St=C3=A5lnacke=20for=20the=20report=20and=20t?= =?UTF-8?q?o=20vung=20for=20the=20patch.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17168 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/http/__init__.py | 6 +++--- tests/regressiontests/httpwrappers/tests.py | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 68ac45d6e318..07e5a4679776 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -92,7 +92,7 @@ def value_encode(self, val): if not _cookie_allows_colon_in_names: def load(self, rawdata, ignore_parse_errors=False): if ignore_parse_errors: - self.bad_cookies = [] + self.bad_cookies = set() self._BaseCookie__set = self._loose_set super(SimpleCookie, self).load(rawdata) if ignore_parse_errors: @@ -106,8 +106,8 @@ def _loose_set(self, key, real_value, coded_value): try: self._strict_set(key, real_value, coded_value) except Cookie.CookieError: - self.bad_cookies.append(key) - dict.__setitem__(self, key, None) + self.bad_cookies.add(key) + dict.__setitem__(self, key, Cookie.Morsel()) class CompatCookie(SimpleCookie): diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 2e2932f0cf6a..6aabfe655e0c 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -281,3 +281,9 @@ def test_nonstandard_keys(self): Test that a single non-standard cookie name doesn't affect all cookies. Ticket #13007. """ self.assertTrue('good_cookie' in parse_cookie('good_cookie=yes;bad:cookie=yes').keys()) + + def test_repeated_nonstandard_keys(self): + """ + Test that a repeated non-standard name doesn't affect all cookies. Ticket #15852 + """ + self.assertTrue('good_cookie' in parse_cookie('a,=b; a,=c; good_cookie=yes').keys()) From b5853cf043fe22277e5aff7648b5b1a74b778255 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 11 Dec 2011 10:09:15 +0000 Subject: [PATCH 141/225] [1.3.X] Fixed #16632 -- Crash on responses without Content-Type with IE. Backport of r17196. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17198 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/http/utils.py | 3 +- tests/regressiontests/utils/http.py | 48 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/django/http/utils.py b/django/http/utils.py index 5eea23907b0e..01808648ba41 100644 --- a/django/http/utils.py +++ b/django/http/utils.py @@ -76,7 +76,8 @@ def fix_IE_for_vary(request, response): # The first part of the Content-Type field will be the MIME type, # everything after ';', such as character-set, can be ignored. - if response['Content-Type'].split(';')[0] not in safe_mime_types: + mime_type = response.get('Content-Type', '').partition(';')[0] + if mime_type not in safe_mime_types: try: del response['Vary'] except KeyError: diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py index 83a4a7f54d7e..666d04f672fa 100644 --- a/tests/regressiontests/utils/http.py +++ b/tests/regressiontests/utils/http.py @@ -1,5 +1,7 @@ from django.utils import http from django.utils import unittest +from django.http import HttpResponse, utils +from django.test import RequestFactory class TestUtilsHttp(unittest.TestCase): @@ -21,3 +23,49 @@ def test_same_origin_false(self): self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com.evil.com')) # Different port self.assertFalse(http.same_origin('http://foo.com:8000', 'http://foo.com:8001')) + + def test_fix_IE_for_vary(self): + """ + Regression for #16632. + + `fix_IE_for_vary` shouldn't crash when there's no Content-Type header. + """ + + # functions to generate responses + def response_with_unsafe_content_type(): + r = HttpResponse(content_type="text/unsafe") + r['Vary'] = 'Cookie' + return r + + def no_content_response_with_unsafe_content_type(): + # 'Content-Type' always defaulted, so delete it + r = response_with_unsafe_content_type() + del r['Content-Type'] + return r + + # request with & without IE user agent + rf = RequestFactory() + request = rf.get('/') + ie_request = rf.get('/', HTTP_USER_AGENT='MSIE') + + # not IE, unsafe_content_type + response = response_with_unsafe_content_type() + utils.fix_IE_for_vary(request, response) + self.assertTrue('Vary' in response) + + # IE, unsafe_content_type + response = response_with_unsafe_content_type() + utils.fix_IE_for_vary(ie_request, response) + self.assertFalse('Vary' in response) + + # not IE, no_content + response = no_content_response_with_unsafe_content_type() + utils.fix_IE_for_vary(request, response) + self.assertTrue('Vary' in response) + + # IE, no_content + response = no_content_response_with_unsafe_content_type() + utils.fix_IE_for_vary(ie_request, response) + self.assertFalse('Vary' in response) + + From c9ab2bfb39c03883623dbd858791b39690391900 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Fri, 30 Dec 2011 12:29:47 +0000 Subject: [PATCH 142/225] [1.3.X] Fixed #17470 - Broken links in 0.95/0.96 release notes; thanks fastinetserver for the report; aaugustin for the patch. Backport of r17290 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17291 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/releases/0.95.txt | 11 +++++------ docs/releases/0.96.txt | 28 ++++++++++------------------ 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/docs/releases/0.95.txt b/docs/releases/0.95.txt index 7409bff1c02e..3632c314609a 100644 --- a/docs/releases/0.95.txt +++ b/docs/releases/0.95.txt @@ -92,15 +92,15 @@ changes is described in the `Removing The Magic`_ wiki page. There is also an easy checklist_ for reference when undertaking the porting operation. .. _Removing The Magic: http://code.djangoproject.com/wiki/RemovingTheMagic -.. _checklist: http://code.djangoproject.com/wiki/MagicRemovalCheatSheet1 +.. _checklist: http://code.djangoproject.com/wiki/MagicRemovalCheatSheet Problem reports and getting help ================================ -Need help resolving a problem with Django? The documentation in the distribution -is also available online_ at the `Django Web site`_. The :doc:`FAQ ` -document is especially recommended, as it contains a number of issues that come -up time and again. +Need help resolving a problem with Django? The documentation in the +distribution is also available :doc:`online ` at the `Django Web +site`_. The :doc:`FAQ ` document is especially recommended, as it +contains a number of issues that come up time and again. For more personalized help, the `django-users`_ mailing list is a very active list, with more than 2,000 subscribers who can help you solve any sort of @@ -113,7 +113,6 @@ there's a #django channel on irc.freenode.net that is regularly populated by Django users and developers from around the world. Friendly people are usually available at any hour of the day -- to help, or just to chat. -.. _online: http://www.djangoproject.com/documentation/0.95/ .. _Django Web site: http://www.djangoproject.com/ .. _django-users: http://groups.google.com/group/django-users diff --git a/docs/releases/0.96.txt b/docs/releases/0.96.txt index 1224360e3f50..8874ccb20e6c 100644 --- a/docs/releases/0.96.txt +++ b/docs/releases/0.96.txt @@ -50,12 +50,10 @@ aside from any necessary security fixes, it will not be actively maintained, and it will be removed in a future release of Django. Also, note that some features, like the new :setting:`DATABASE_OPTIONS` -setting (see the `databases documentation`_ for details), are only -available on the "mysql" backend, and will not be made available for +setting (see the :doc:`databases documentation ` for details), +are only available on the "mysql" backend, and will not be made available for "mysql_old". -.. _databases documentation: http://www.djangoproject.com/documentation/0.96/databases/ - Database constraint names changed --------------------------------- @@ -164,10 +162,8 @@ Although the ``newforms`` library will continue to evolve, it's ready for use for most common cases. We recommend that anyone new to form handling skip the old forms system and start with the new. -For more information about ``django.newforms``, read the `newforms -documentation`_. - -.. _newforms documentation: http://www.djangoproject.com/documentation/0.96/newforms/ +For more information about ``django.newforms``, read the :doc:`newforms +documentation `. URLconf improvements -------------------- @@ -216,19 +212,15 @@ The test framework ------------------ Django now includes a test framework so you can start transmuting fear into -boredom (with apologies to Kent Beck). You can write tests based on doctest_ -or unittest_ and test your views with a simple test client. +boredom (with apologies to Kent Beck). You can write tests based on +:mod:`doctest` or :mod:`unittest` and test your views with a simple test client. There is also new support for "fixtures" -- initial data, stored in any of the -supported `serialization formats`_, that will be loaded into your database at the -start of your tests. This makes testing with real data much easier. - -See `the testing documentation`_ for the full details. +supported :doc:`serialization formats `, that will be +loaded into your database at the start of your tests. This makes testing with +real data much easier. -.. _doctest: http://docs.python.org/library/doctest.html -.. _unittest: http://docs.python.org/library/unittest.html -.. _the testing documentation: http://www.djangoproject.com/documentation/0.96/testing/ -.. _serialization formats: http://www.djangoproject.com/documentation/0.96/serialization/ +See :doc:`the testing documentation ` for the full details. Improvements to the admin interface ----------------------------------- From 7b9016e5a229c2269d758959e4d07a0dbe4e710f Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Fri, 30 Dec 2011 15:31:09 +0000 Subject: [PATCH 143/225] [1.3.X] Fixed #17068 - Documented that documentation fixes will be more freely backported. Backport of r17300 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17301 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/release-process.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/internals/release-process.txt b/docs/internals/release-process.txt index 2a56f0be9238..799a59eda5ac 100644 --- a/docs/internals/release-process.txt +++ b/docs/internals/release-process.txt @@ -99,6 +99,13 @@ varying levels: * Security fixes will be applied to the current trunk and the previous two minor releases. +* Documentation fixes will generally be more freely backported to the last + release branch (at the discretion of the committer), and don't need to meet + the "critical fixes only" bar as it's highly advantageous to have the docs + for the last release be up-to-date and correct, and the downside of + backporting (risk of introducing regressions) is much less of a concern + with doc fixes. + As a concrete example, consider a moment in time halfway between the release of Django 1.3 and 1.4. At this point in time: @@ -111,6 +118,9 @@ Django 1.3 and 1.4. At this point in time: ``1.2.X`` branch. Security fixes will trigger the release of ``1.3.1``, ``1.2.1``, etc. +* Documentation fixes will be applied to trunk, and if easily backported, to + the ``1.3.X`` branch. + .. _release-process: Release process From 580389c588ade9709f54c1494c5af6747011f043 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 31 Dec 2011 00:43:31 +0000 Subject: [PATCH 144/225] [1.3.X] Fixed #702 - Documented that ManyToMany fields can't be in unique_together; thanks poirier for the patch. Backport of r17314 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17315 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/options.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 45304392b4a6..aae90d0b6a00 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -238,6 +238,12 @@ Django quotes column and table names behind the scenes. unique_together = ("driver", "restaurant") + A :class:`~django.db.models.ManyToManyField` cannot be included in + unique_together (it's not even clear what that would mean). If you + need to validate uniqueness related to a + :class:`~django.db.models.ManyToManyField`, look at signals or + using an explicit :attr:`through ` model. + ``verbose_name`` ---------------- From 7bee6b451afd80f98aa8c720ec2d61177f0674b5 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 31 Dec 2011 15:30:41 +0000 Subject: [PATCH 145/225] [1.3.X] Fixed #640 - Documented that changing order_with_respect_to requires a schema change; thanks fcurella and poirier for the draft patches. Backport of r17316 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17317 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/options.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index aae90d0b6a00..c1beab03ec3d 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -166,6 +166,13 @@ Django quotes column and table names behind the scenes. >>> answer.get_previous_in_order() +.. admonition:: Changing order_with_respect_to + + ``order_with_respect_to`` adds an additional field/database column + named ``_order``, so be sure to handle that as you would any other + change to your models if you add or change ``order_with_respect_to`` + after your initial :djadmin:`syncdb`. + ``ordering`` ------------ From 79248a7e0a3918c1ed934601f6c295521c346b03 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 31 Dec 2011 15:35:04 +0000 Subject: [PATCH 146/225] [1.3.X] Fixed #11986 - Added sudo to Mac OS permissions note in tutorial. Backport of r17318 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17319 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 2f2e049a1644..7b8fccbd5890 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -59,7 +59,7 @@ This will create a ``mysite`` directory in your current directory. can be run as a program. To do this, open Terminal.app and navigate (using the ``cd`` command) to the directory where :doc:`django-admin.py ` is installed, then run the command - ``chmod +x django-admin.py``. + ``sudo chmod +x django-admin.py``. .. note:: From ca141051286c116cbbced71eb26a323d30c8a0ca Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 4 Jan 2012 18:35:45 +0000 Subject: [PATCH 147/225] [1.3.X] Clarified deployment docs to avoid giving users the impression that staticfiles should be used to actually serve files in production. Backport of r17338 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17339 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/deployment/modpython.txt | 7 +++++-- docs/howto/deployment/modwsgi.txt | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/howto/deployment/modpython.txt b/docs/howto/deployment/modpython.txt index 75bdcd51cf76..693f942b08e8 100644 --- a/docs/howto/deployment/modpython.txt +++ b/docs/howto/deployment/modpython.txt @@ -296,8 +296,11 @@ server you're using, to serve the admin files. The admin files live in (:file:`django/contrib/admin/media/admin`) of the Django distribution. -We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle -the admin files, but here are two other approaches: +We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle the +admin files (this means using the :djadmin:`collectstatic` management command +to collect the static files in :setting:`STATIC_ROOT`, and then configuring +your webserver to serve :setting:`STATIC_ROOT` at :setting:`STATIC_URL`), but +here are two other approaches: 1. Create a symbolic link to the admin static files from within your document root. diff --git a/docs/howto/deployment/modwsgi.txt b/docs/howto/deployment/modwsgi.txt index 99cdbd1f2845..fdf9d27d2072 100644 --- a/docs/howto/deployment/modwsgi.txt +++ b/docs/howto/deployment/modwsgi.txt @@ -130,8 +130,11 @@ server you're using, to serve the admin files. The admin files live in (:file:`django/contrib/admin/media/admin`) of the Django distribution. -We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle -the admin files, but here are two other approaches: +We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle the +admin files (this means using the :djadmin:`collectstatic` management command +to collect the static files in :setting:`STATIC_ROOT`, and then configuring +your webserver to serve :setting:`STATIC_ROOT` at :setting:`STATIC_URL`), but +here are two other approaches: 1. Create a symbolic link to the admin static files from within your document root. From a9789c0d444869dd4cfe0ba7e17b5d404b81ac6b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jan 2012 09:05:36 +0000 Subject: [PATCH 148/225] [1.3.X] Fixed #17415 -- Reset database sequence for Site's pk after creating the default site with an explicit pk. Backport of r17343 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17344 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sites/management.py | 20 ++++++++++++++++---- django/contrib/sites/tests.py | 6 ++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/django/contrib/sites/management.py b/django/contrib/sites/management.py index daec3d9eec2e..9bf5a149f476 100644 --- a/django/contrib/sites/management.py +++ b/django/contrib/sites/management.py @@ -3,22 +3,34 @@ """ from django.db.models import signals +from django.db import connections from django.db import router from django.contrib.sites.models import Site from django.contrib.sites import models as site_app +from django.core.management.color import no_style def create_default_site(app, created_models, verbosity, db, **kwargs): # Only create the default sites in databases where Django created the table if Site in created_models and router.allow_syncdb(db, Site) : - if verbosity >= 2: - print "Creating example.com Site object" # The default settings set SITE_ID = 1, and some tests in Django's test # suite rely on this value. However, if database sequences are reused # (e.g. in the test suite after flush/syncdb), it isn't guaranteed that # the next id will be 1, so we coerce it. See #15573 and #16353. This # can also crop up outside of tests - see #15346. - s = Site(pk=1, domain="example.com", name="example.com") - s.save(using=db) + if verbosity >= 2: + print "Creating example.com Site object" + Site(pk=1, domain="example.com", name="example.com").save(using=db) + + # We set an explicit pk instead of relying on auto-incrementation, + # so we need to reset the database sequence. + sequence_sql = connections[db].ops.sequence_reset_sql(no_style(), [Site]) + if sequence_sql: + if verbosity >= 2: + print "Resetting sequence" + cursor = connections[db].cursor() + for command in sequence_sql: + cursor.execute(command) + Site.objects.clear_cache() signals.post_syncdb.connect(create_default_site, sender=site_app) diff --git a/django/contrib/sites/tests.py b/django/contrib/sites/tests.py index 17ab1f2a7c7c..828badb38674 100644 --- a/django/contrib/sites/tests.py +++ b/django/contrib/sites/tests.py @@ -15,6 +15,12 @@ def setUp(self): def tearDown(self): Site._meta.installed = self.old_Site_meta_installed + def test_save_another(self): + # Regression for #17415 + # On some backends the sequence needs reset after save with explicit ID. + # Test that there is no sequence collisions by saving another site. + Site(domain="example2.com", name="example2.com").save() + def test_site_manager(self): # Make sure that get_current() does not return a deleted Site object. s = Site.objects.get_current() From 9e124926167e2565ab64976bd20e131aca330ebd Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jan 2012 18:48:12 +0000 Subject: [PATCH 149/225] [1.3.X] Fixed #17100 -- Typo in the regex for EmailValidator. Backport of r17349 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17350 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/validators.py | 3 ++- tests/modeltests/validators/tests.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django/core/validators.py b/django/core/validators.py index a93c6ac97568..9dcc2bc3ab21 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -147,7 +147,8 @@ def __call__(self, value): email_re = re.compile( r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom - r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string + # quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5 + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid') diff --git a/tests/modeltests/validators/tests.py b/tests/modeltests/validators/tests.py index e58526285b37..4bd5827f0b11 100644 --- a/tests/modeltests/validators/tests.py +++ b/tests/modeltests/validators/tests.py @@ -28,6 +28,9 @@ (validate_email, 'abc', ValidationError), (validate_email, 'a @x.cz', ValidationError), (validate_email, 'something@@somewhere.com', ValidationError), + # Quoted-string format (CR not allowed) + (validate_email, '"\\\011"@here.com', None), + (validate_email, '"\\\012"@here.com', ValidationError), (validate_slug, 'slug-ok', None), (validate_slug, 'longer-slug-still-ok', None), From 9bc6119daf03d5f33095f52b5eac1aed1359c775 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 12 Jan 2012 22:17:22 +0000 Subject: [PATCH 150/225] Fixed #17538 -- corrected the section in tutorial 3 about the handler404 default. Thanks matt at brozowski dot com for the report. Backport of r17369 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17370 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial03.txt | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index ee2df3416497..566ba553325a 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -365,16 +365,15 @@ have no effect), which is a string in Python dotted syntax -- the same format the normal URLconf callbacks use. A 404 view itself has nothing special: It's just a normal view. -You normally won't have to bother with writing 404 views. By default, URLconfs -have the following line up top:: +You normally won't have to bother with writing 404 views. If you don't set +``handler404``, the built-in view :func:`django.views.defaults.page_not_found` +is used by default. In this case, you still have one obligation: To create a +``404.html`` template in the root of your template directory. The default 404 +view will use that template for all 404 errors. If :setting:`DEBUG` is set to +``False`` (in your settings module) and if you didn't create a ``404.html`` +file, an ``Http500`` is raised instead. So remember to create a ``404.html``. - from django.conf.urls.defaults import * - -That takes care of setting ``handler404`` in the current module. As you can see -in ``django/conf/urls/defaults.py``, ``handler404`` is set to -:func:`django.views.defaults.page_not_found` by default. - -Four more things to note about 404 views: +A couple more things to note about 404 views: * If :setting:`DEBUG` is set to ``True`` (in your settings module) then your 404 view will never be used (and thus the ``404.html`` template will never @@ -383,15 +382,6 @@ Four more things to note about 404 views: * The 404 view is also called if Django doesn't find a match after checking every regular expression in the URLconf. - * If you don't define your own 404 view -- and simply use the default, which - is recommended -- you still have one obligation: To create a ``404.html`` - template in the root of your template directory. The default 404 view will - use that template for all 404 errors. - - * If :setting:`DEBUG` is set to ``False`` (in your settings module) and if - you didn't create a ``404.html`` file, an ``Http500`` is raised instead. - So remember to create a ``404.html``. - Write a 500 (server error) view =============================== From 2b793b1a3580a318ba8a505f8a70942bf849de25 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Thu, 19 Jan 2012 17:39:59 +0000 Subject: [PATCH 151/225] [1.3.X] Fixed #17078: properly invoke IPython 0.12. Backport of r17379. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17380 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/commands/shell.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index e4ce462c205a..039ebb8d5d09 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -13,9 +13,8 @@ class Command(NoArgsCommand): def ipython(self): try: - from IPython.frontend.terminal.embed import TerminalInteractiveShell - shell = TerminalInteractiveShell() - shell.mainloop() + from IPython import embed + embed() except ImportError: # IPython < 0.11 # Explicitly pass an empty list as arguments, because otherwise From 405a0ba9de87a506a84a6f52590f6914518e0cce Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 21 Jan 2012 15:48:08 +0000 Subject: [PATCH 152/225] [1.3.X] Fixed the link to the IRC logs in the README and docs. Refs #17453, #16277. Backport of [17149] and [17385]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17386 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- README | 2 +- docs/index.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index c7d225c1e2f9..47bfd33c754c 100644 --- a/README +++ b/README @@ -28,7 +28,7 @@ http://code.djangoproject.com/newticket To get more help: * Join the #django channel on irc.freenode.net. Lots of helpful people - hang out there. Read the archives at http://botland.oebfare.com/logger/django/. + hang out there. Read the archives at http://django-irc-logs.com/. * Join the django-users mailing list, or read the archives, at http://groups.google.com/group/django-users. diff --git a/docs/index.txt b/docs/index.txt index 0cf066ee01ca..20a7cecc0f33 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -28,7 +28,7 @@ Having trouble? We'd like to help! .. _archives of the django-users mailing list: http://groups.google.com/group/django-users/ .. _post a question: http://groups.google.com/group/django-users/ .. _#django IRC channel: irc://irc.freenode.net/django -.. _IRC logs: http://botland.oebfare.com/logger/django/ +.. _IRC logs: http://django-irc-logs.com/ .. _ticket tracker: http://code.djangoproject.com/ First steps From 0d7431774f9b4b7db2062911515d531e6b0f0093 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 22 Jan 2012 18:53:41 +0000 Subject: [PATCH 153/225] [1.3.X] Fixed #17575 -- Typo in an example of ModelAdmin.list_filter. Thanks apelisse AT gmail com for the report. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17388 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- 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 d5b16e1e9984..7394c3aec290 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -544,7 +544,7 @@ subclass:: Fields in ``list_filter`` can also span relations using the ``__`` lookup:: class UserAdminWithLookup(UserAdmin): - list_filter = ('groups__name') + list_filter = ('groups__name',) .. attribute:: ModelAdmin.list_per_page From 723f995658c4c661a1a2660af48097f1a7dd88ad Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Wed, 25 Jan 2012 14:33:15 +0000 Subject: [PATCH 154/225] [1.3.X] Added note about deprecation of project-level translations to the deprecation timeline document. Thanks Jannis for the report. Fixes #17588. Backport of [17394] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17395 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/deprecation.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 3f0f998b4a10..ca29d15dcc06 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -177,6 +177,12 @@ their deprecation, as per the :ref:`Django deprecation policy required to end with a trailing slash to ensure there is a consistent way to combine paths in templates. + * Translations located under the so-called *project path* will be + ignored during the translation building process performed at runtime. + The :setting:`LOCALE_PATHS` setting can be used for the same task by + including the filesystem path to a ``locale`` directory containing + non-app-specific translations in its value. + * 2.0 * ``django.views.defaults.shortcut()``. This function has been moved to ``django.contrib.contenttypes.views.shortcut()`` as part of the From 33f9ba7ba023359db035c7008703bf0723ef2f44 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Thu, 26 Jan 2012 15:00:57 +0000 Subject: [PATCH 155/225] [1.3.X] Fixed #17240 -- Replaced links to the online version of the docs by internal references. Backport of [17100] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17397 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 7 +++---- docs/releases/1.0.1.txt | 15 +++++++-------- docs/topics/db/models.txt | 5 ++--- docs/topics/templates.txt | 2 ++ 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 7b8fccbd5890..4dc1e5c21e5c 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -692,10 +692,9 @@ Save these changes and start a new Python interactive shell by running For more information on model relations, see :doc:`Accessing related objects `. For more on how to use double underscores to perform -field lookups via the API, see `Field lookups`__. For full details on the -database API, see our :doc:`Database API reference `. - -__ http://docs.djangoproject.com/en/1.2/topics/db/queries/#field-lookups +field lookups via the API, see :ref:`Field lookups `. For +full details on the database API, see our :doc:`Database API reference +`. When you're comfortable with the API, read :doc:`part 2 of this tutorial ` to get Django's automatic admin working. diff --git a/docs/releases/1.0.1.txt b/docs/releases/1.0.1.txt index 780dc53c1f94..3550e7c58446 100644 --- a/docs/releases/1.0.1.txt +++ b/docs/releases/1.0.1.txt @@ -6,10 +6,10 @@ Welcome to Django 1.0.1! This is the first "bugfix" release in the Django 1.0 series, improving the stability and performance of the Django 1.0 codebase. As such, -Django 1.0.1 contains no new features (and, pursuant to `our -compatibility policy`_, maintains backwards compatibility with Django -1.0), but does contain a number of fixes and other -improvements. Django 1.0.1 is a recommended upgrade for any +Django 1.0.1 contains no new features (and, pursuant to :doc:`our +compatibility policy `, maintains backwards +compatibility with Django 1.0), but does contain a number of fixes +and other improvements. Django 1.0.1 is a recommended upgrade for any development or deployment currently using or targeting Django 1.0. @@ -46,8 +46,9 @@ highlights: * A fix to the application of autoescaping for literal strings passed to the ``join`` template filter. Previously, literal strings passed - to ``join`` were automatically escaped, contrary to `the documented - behavior for autoescaping and literal strings`_. Literal strings + to ``join`` were automatically escaped, contrary to :ref:`the + documented behavior for autoescaping and literal strings + `. Literal strings passed to ``join`` are no longer automatically escaped, meaning you must now manually escape them; this is an incompatibility if you were relying on this bug, but not if you were relying on escaping @@ -60,6 +61,4 @@ highlights: documentation, including both corrections to existing documents and expanded and new documentation. -.. _our compatibility policy: http://docs.djangoproject.com/en/dev/misc/api-stability/ .. _the Subversion log of the 1.0.X branch: http://code.djangoproject.com/log/django/branches/releases/1.0.X -.. _the documented behavior for autoescaping and literal strings: http://docs.djangoproject.com/en/dev/topics/templates/#string-literals-and-automatic-escaping diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 0e18205e5dd6..af7634439c9d 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -324,11 +324,10 @@ whatever you want. For example:: should work; all are optional. For details on accessing backwards-related objects, see the - `Following relationships backward example`_. - + :ref:`Following relationships backward example `. + For sample code, see the `Many-to-one relationship model tests`_. - .. _Following relationships backward example: http://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects .. _Many-to-one relationship model tests: http://code.djangoproject.com/browser/django/trunk/tests/modeltests/many_to_one Many-to-many relationships diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 83269aeb8ed1..fc2cd3f77528 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -555,6 +555,8 @@ variable that needs escaping. When auto-escaping is on, there's no danger of the ``escape`` filter *double-escaping* data -- the ``escape`` filter does not affect auto-escaped variables. +.. _string-literals-and-automatic-escaping: + String literals and automatic escaping -------------------------------------- From 46c08c8f951d2a5c662cc38f5a61bd22fa417a05 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 5 Feb 2012 15:41:53 +0000 Subject: [PATCH 156/225] [1.3.X] Fixed #17510 - Typo in docs/topics/class-based-views.txt; thanks andrew and noria. Backport of r17457 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17458 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/class-based-views.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index 38310462c239..4da48ec2885b 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -380,7 +380,7 @@ Next, we'll write the ``PublisherBookListView`` view itself:: class PublisherBookListView(ListView): context_object_name = "book_list" - template_name = "books/books_by_publisher.html", + template_name = "books/books_by_publisher.html" def get_queryset(self): publisher = get_object_or_404(Publisher, name__iexact=self.args[0]) @@ -396,7 +396,7 @@ use it in the template:: class PublisherBookListView(ListView): context_object_name = "book_list" - template_name = "books/books_by_publisher.html", + template_name = "books/books_by_publisher.html" def get_queryset(self): self.publisher = get_object_or_404(Publisher, name__iexact=self.args[0]) From a7a703dbdcf5778e97126db9a6ddba1f855c8230 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 5 Feb 2012 15:54:01 +0000 Subject: [PATCH 157/225] [1.3.X] Fixed #17571 - Fixed documentation of skipUnlessDBFeature; thanks EnTeQuAk for the report. Backport of r17459 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17460 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 0a86f0d1b9f4..8262b576fdd8 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1586,7 +1586,7 @@ skipUnlessDBFeature Skip the decorated test if the named database feature is *not* supported. -For example, the following test will not be executed if the database +For example, the following test will only be executed if the database supports transactions (e.g., it would run under PostgreSQL, but *not* under MySQL with MyISAM tables):: From 2646df45374d7c9054adda998e64f3beb6ae6f62 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 11 Feb 2012 02:07:40 +0000 Subject: [PATCH 158/225] [1.3.X] Fixed link to SpatiaLite 2.x initial SQL files. Backport of [17491] and [17496] from trunk. Refs #17554. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17498 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/gis/install.txt | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 3c2dcdc333d9..c5b2dcffbe3f 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -572,15 +572,30 @@ Creating a Spatial Database for SpatiaLite ------------------------------------------- After the SpatiaLite library and tools have been installed, it is now possible -to create spatial database for use with GeoDjango. In order to do this, download -the spatial database initialization SQL from the `SpatiaLite Resources`__ page:: +to create a spatial database for use with GeoDjango. - $ wget http://www.gaia-gis.it/spatialite/init_spatialite-2.3.sql.gz +For this, a number of spatial metadata tables must be created in the database +before any spatial query is performed against it. You need to download a +database initialization file and execute the SQL queries it contains against +your database. + +First, get it from the appropiate SpatiaLite Resources page (i.e. +http://www.gaia-gis.it/spatialite-2.3.1/resources.html for 2.3 or +http://www.gaia-gis.it/spatialite-2.4.0/ for 2.4):: + + $ wget http://www.gaia-gis.it/spatialite-2.3.1/init_spatialite-2.3.sql.gz $ gunzip init_spatialite-2.3.sql.gz +(Or, if you are using SpatiaLite 2.4 then do:: + + $ wget http://www.gaia-gis.it/spatialite-2.4.0/init_spatialite-2.4.sql.gz + $ gunzip init_spatialite-2.4.sql.gz + +) + Now, the ``spatialite`` command can be used to initialize a spatial database:: - $ spatialite geodjango.db < init_spatialite-2.3.sql + $ spatialite geodjango.db < init_spatialite-2.X.sql .. note:: @@ -588,10 +603,6 @@ Now, the ``spatialite`` command can be used to initialize a spatial database:: you want to use. Use the same in the :setting:`DATABASE_NAME` inside your ``settings.py``. - -__ http://www.gaia-gis.it/spatialite/resources.html - - Add ``django.contrib.gis`` to :setting:`INSTALLED_APPS` ------------------------------------------------------- @@ -1110,7 +1121,7 @@ Python ^^^^^^ First, download the latest `Python 2.7 installer`__ from the Python Web site. -Next, run the installer and keep the defaults -- for example, keep +Next, run the installer and keep the defaults -- for example, keep 'Install for all users' checked and the installation path set as ``C:\Python27``. @@ -1165,7 +1176,7 @@ tree and select :menuselection:`PostGIS 1.5 for PostgreSQL 9.0`. After clicking next, you will be prompted to select your mirror, PostGIS will be downloaded, and the PostGIS installer will begin. Select only the -default options during install (e.g., do not uncheck the option to create a +default options during install (e.g., do not uncheck the option to create a default PostGIS database). .. note:: @@ -1216,7 +1227,7 @@ executable with ``cmd.exe``, will set this up:: reg ADD "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PROJ_LIB /t REG_EXPAND_SZ /f /d "%PROJ_LIB%" For your convenience, these commands are available in the execuatble batch -script, :download:`geodjango_setup.bat`. +script, :download:`geodjango_setup.bat`. .. note:: From f202387e6c807878b4fc80e0efa83a488e157b7a Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 11 Feb 2012 12:47:58 +0000 Subject: [PATCH 159/225] [1.3.X] Fixed #17618 - Documented that variable names in template must not start with an underscore; thanks guillemette and krzysiumed. Backport of r17504 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17505 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/templates/api.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 4cfa4da2ac8c..7f04f20f1be5 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -112,7 +112,7 @@ template:: "My name is Dolores." Variable names must consist of any letter (A-Z), any digit (0-9), an underscore -or a dot. +(but they must not start with an underscore) or a dot. Dots have a special meaning in template rendering. A dot in a variable name signifies a **lookup**. Specifically, when the template system encounters a From ce9916a2c80b52b327b010c55edb9ae28ccb6b8a Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sun, 12 Feb 2012 17:16:37 +0000 Subject: [PATCH 160/225] Fixed #16653 - Added example of kwargs support for resolve(); thanks krzysiumed for the patch. Backport of r17517 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17518 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/urls.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 7a0e7243e8da..8be72d243b30 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -818,6 +818,13 @@ This ``current_app`` argument is used as a hint to resolve application namespaces into URLs on specific application instances, according to the :ref:`namespaced URL resolution strategy `. +You can use ``kwargs`` instead of ``args``, for example:: + + >>> reverse('admin:app_list', kwargs={'app_label': 'auth'}) + '/admin/auth/' + +``args`` and ``kwargs`` cannot be passed to ``reverse()`` at the same time. + .. admonition:: Make sure your views are all correct. As part of working out which URL names map to which patterns, the From c63a454bb684bee99aa7c13ec441040e5f091c71 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Tue, 14 Feb 2012 23:05:23 +0000 Subject: [PATCH 161/225] [1.3.X] Updated link to Django Debug Toolbar homepage. Thanks to rowynm AT gmail DOT com for the report and to Claude Paroz for the patch. Fixes #17543. Backport of r17376 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17523 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/optimization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 265ef55faec8..78bebac16eba 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -29,7 +29,7 @@ readability of your code. **All** of the suggestions below come with the caveat that in your circumstances the general principle might not apply, or might even be reversed. -.. _django-debug-toolbar: http://robhudson.github.com/django-debug-toolbar/ +.. _django-debug-toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar/ Use standard DB optimization techniques ======================================= From 813dc01cd88e13b4d125cb24061ebb86c7ce3b09 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 18 Feb 2012 10:11:17 +0000 Subject: [PATCH 162/225] [1.3.x] Fixed #15496 -- Corrected handling of base64 file upload encoding. Backport of r16176 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17546 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/http/multipartparser.py | 2 ++ tests/regressiontests/file_uploads/tests.py | 25 +++++++++++++++++++++ tests/regressiontests/file_uploads/urls.py | 1 + tests/regressiontests/file_uploads/views.py | 7 ++++++ 4 files changed, 35 insertions(+) diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index e3a03ff89701..63203a4581ac 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -150,6 +150,8 @@ def parse(self): continue transfer_encoding = meta_data.get('content-transfer-encoding') + if transfer_encoding is not None: + transfer_encoding = transfer_encoding[0].strip() field_name = force_unicode(field_name, encoding, errors='replace') if item_type == FIELD: diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py index 6dba4731fd0c..9b14e3e85f0c 100644 --- a/tests/regressiontests/file_uploads/tests.py +++ b/tests/regressiontests/file_uploads/tests.py @@ -1,5 +1,6 @@ #! -*- coding: utf-8 -*- import errno +import base64 import os import shutil from StringIO import StringIO @@ -55,6 +56,30 @@ def test_large_upload(self): self.assertEqual(response.status_code, 200) + def test_base64_upload(self): + test_string = "This data will be transmitted base64-encoded." + payload = "\r\n".join([ + '--' + client.BOUNDARY, + 'Content-Disposition: form-data; name="file"; filename="test.txt"', + 'Content-Type: application/octet-stream', + 'Content-Transfer-Encoding: base64', + '', + base64.b64encode(test_string), + '--' + client.BOUNDARY + '--', + '', + ]) + r = { + 'CONTENT_LENGTH': len(payload), + 'CONTENT_TYPE': client.MULTIPART_CONTENT, + 'PATH_INFO': "/file_uploads/echo_content/", + 'REQUEST_METHOD': 'POST', + 'wsgi.input': client.FakePayload(payload), + } + response = self.client.request(**r) + received = simplejson.loads(response.content) + + self.assertEqual(received['file'], test_string) + def test_unicode_file_name(self): tdir = tempfile.gettempdir() diff --git a/tests/regressiontests/file_uploads/urls.py b/tests/regressiontests/file_uploads/urls.py index 413080eb4f48..9f814c4d4c1e 100644 --- a/tests/regressiontests/file_uploads/urls.py +++ b/tests/regressiontests/file_uploads/urls.py @@ -6,6 +6,7 @@ (r'^verify/$', views.file_upload_view_verify), (r'^unicode_name/$', views.file_upload_unicode_name), (r'^echo/$', views.file_upload_echo), + (r'^echo_content/$', views.file_upload_echo_content), (r'^quota/$', views.file_upload_quota), (r'^quota/broken/$', views.file_upload_quota_broken), (r'^getlist_count/$', views.file_upload_getlist_count), diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py index 9f4ce69b5fe4..0de8148da9d3 100644 --- a/tests/regressiontests/file_uploads/views.py +++ b/tests/regressiontests/file_uploads/views.py @@ -85,6 +85,13 @@ def file_upload_echo(request): r = dict([(k, f.name) for k, f in request.FILES.items()]) return HttpResponse(simplejson.dumps(r)) +def file_upload_echo_content(request): + """ + Simple view to echo back the content of uploaded files for tests. + """ + r = dict([(k, f.read()) for k, f in request.FILES.items()]) + return HttpResponse(simplejson.dumps(r)) + def file_upload_quota(request): """ Dynamically add in an upload handler. From 5144f72be25b401cd6b37d842b58514cca6a948a Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 18 Feb 2012 21:40:26 +0000 Subject: [PATCH 163/225] [1.3.X] Fixed #17685 - Typo in BaseDateListView.get_dated_items(); thanks ejb. Backport of r17548 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17549 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/class-based-views.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/class-based-views.txt b/docs/ref/class-based-views.txt index b1dbd822095a..84f1b1eca34b 100644 --- a/docs/ref/class-based-views.txt +++ b/docs/ref/class-based-views.txt @@ -781,11 +781,11 @@ BaseDateListView .. method:: get_dated_items(): - Returns a 3-tuple containing (``date_list``, ``latest``, + Returns a 3-tuple containing (``date_list``, ``object_list``, ``extra_context``). ``date_list`` is the list of dates for which data is available. - ``object_list`` is the list of objects ``extra_context`` is a + ``object_list`` is the list of objects. ``extra_context`` is a dictionary of context data that will be added to any context data provided by the :class:`~django.views.generic.list.MultipleObjectMixin`. From 9729ad7466b25af4ae396d751c1a86541434a221 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 18 Feb 2012 21:51:18 +0000 Subject: [PATCH 164/225] [1.3.X] Fixed #17706 - Improved short description example in Tutorial 2; thanks xbito and claudep. Backport of r17550 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17551 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial02.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 1e837e6c27e8..7d1a3e977d74 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -343,9 +343,11 @@ an arbitrary method is not supported. Also note that the column header for underscores replaced with spaces). But you can change that by giving that method (in ``models.py``) a ``short_description`` attribute:: - def was_published_today(self): - return self.pub_date.date() == datetime.date.today() - was_published_today.short_description = 'Published today?' + class Poll(models.Model): + # ... + def was_published_today(self): + return self.pub_date.date() == datetime.date.today() + was_published_today.short_description = 'Published today?' Edit your admin.py file again and add an improvement to the Poll change list page: Filters. Add the following line to ``PollAdmin``:: From 1addaafa0ad848142b676cd2669db5ddb21735b0 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 19 Feb 2012 07:42:02 +0000 Subject: [PATCH 165/225] [1.3.X] Fixed #17573 -- Documented MySQL's switch to InnoDB as default storage engine. Backport of r17552 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17553 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/databases.txt | 14 ++++++++------ docs/ref/django-admin.txt | 14 +++++--------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 5da23366272c..70bb67bfe67a 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -147,12 +147,14 @@ Storage engines MySQL has several `storage engines`_ (previously called table types). You can change the default storage engine in the server configuration. -The default engine is MyISAM_ [#]_. The main drawback of MyISAM is that it -doesn't currently support transactions or foreign keys. On the plus side, it's -currently the only engine that supports full-text indexing and searching. - -The InnoDB_ engine is fully transactional and supports foreign key references -and is probably the best choice at this point in time. +Until MySQL 5.5.4, the default engine was MyISAM_ [#]_. The main drawbacks of +MyISAM are that it doesn't support transactions or enforce foreign keys +constraints. On the plus side, it's currently the only engine that supports +full-text indexing and searching. + +Since MySQL 5.5.5, the default storage engine is InnoDB_. This engine is fully +transactional and supports foreign key references. It's probably the best +choice at this point in time. .. _storage engines: http://dev.mysql.com/doc/refman/5.5/en/storage-engines.html .. _MyISAM: http://dev.mysql.com/doc/refman/5.5/en/myisam-storage-engine.html diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 58b86ead14b6..6e5e550897da 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -371,15 +371,11 @@ fixture type are discovered (for example, if ``mydata.json`` and installation will be aborted, and any data installed in the call to ``loaddata`` will be removed from the database. -.. admonition:: MySQL and Fixtures - - Unfortunately, MySQL isn't capable of completely supporting all the - features of Django fixtures. If you use MyISAM tables, MySQL doesn't - support transactions or constraints, so you won't get a rollback if - multiple transaction files are found, or validation of fixture data. - If you use InnoDB tables, you won't be able to have any forward - references in your data files - MySQL doesn't provide a mechanism to - defer checking of row constraints until a transaction is committed. +.. admonition:: MySQL with MyISAM and fixtures + + The MyISAM storage engine of MySQL doesn't support transactions or + constraints, so you won't get a rollback if multiple transaction files are + found, or validation of fixture data, if you use MyISAM tables. Database-specific fixtures ~~~~~~~~~~~~~~~~~~~~~~~~~~ From 2a5a0b8097a35e355b0742cb2c912913581d259a Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 19 Feb 2012 08:00:59 +0000 Subject: [PATCH 166/225] [1.3.X] Fixed #16452 -- Clarified that the DATE/DATETIME/TIME_INPUT_FORMATS settings have no effect when USE_L10N is True. Backport of r17554 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17555 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 18155f19fc1a..8dc5fdbe0d2d 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -619,11 +619,13 @@ Default:: '%b %d, %Y', '%d %b %Y', '%d %b, %Y', '%B %d %Y', '%B %d, %Y', '%d %B %Y', '%d %B, %Y') -A tuple of formats that will be accepted when inputting data on a date -field. Formats will be tried in order, using the first valid. -Note that these format strings are specified in Python's datetime_ module -syntax, that is different from the one used by Django for formatting dates -to be displayed. +A tuple of formats that will be accepted when inputting data on a date field. +Formats will be tried in order, using the first valid. Note that these format +strings are specified in Python's datetime_ module syntax, that is different +from the one used by Django for formatting dates to be displayed. + +When :setting:`USE_L10N` is ``True``, the locale-dictated format has higher +precedence and will be applied instead. See also :setting:`DATETIME_INPUT_FORMATS` and :setting:`TIME_INPUT_FORMATS`. @@ -660,10 +662,12 @@ Default:: '%m/%d/%y %H:%M:%S', '%m/%d/%y %H:%M', '%m/%d/%y') A tuple of formats that will be accepted when inputting data on a datetime -field. Formats will be tried in order, using the first valid. -Note that these format strings are specified in Python's datetime_ module -syntax, that is different from the one used by Django for formatting dates -to be displayed. +field. Formats will be tried in order, using the first valid. Note that these +format strings are specified in Python's datetime_ module syntax, that is +different from the one used by Django for formatting dates to be displayed. + +When :setting:`USE_L10N` is ``True``, the locale-dictated format has higher +precedence and will be applied instead. See also :setting:`DATE_INPUT_FORMATS` and :setting:`TIME_INPUT_FORMATS`. @@ -1836,11 +1840,13 @@ TIME_INPUT_FORMATS Default: ``('%H:%M:%S', '%H:%M')`` -A tuple of formats that will be accepted when inputting data on a time -field. Formats will be tried in order, using the first valid. -Note that these format strings are specified in Python's datetime_ module -syntax, that is different from the one used by Django for formatting dates -to be displayed. +A tuple of formats that will be accepted when inputting data on a time field. +Formats will be tried in order, using the first valid. Note that these format +strings are specified in Python's datetime_ module syntax, that is different +from the one used by Django for formatting dates to be displayed. + +When :setting:`USE_L10N` is ``True``, the locale-dictated format has higher +precedence and will be applied instead. See also :setting:`DATE_INPUT_FORMATS` and :setting:`DATETIME_INPUT_FORMATS`. From 7baee7a03b8f9ec4840942eecb80fcbe1c75b231 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 19 Feb 2012 08:10:20 +0000 Subject: [PATCH 167/225] [1.3.X] Fixed #17316 -- Mentionned that the MultipleProxyMiddleware provided as an example must run rather early. Backport of r17556 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17557 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/request-response.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index d15a07cdaeed..e6a16a916d25 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -223,6 +223,10 @@ Methods parts = request.META[field].split(',') request.META[field] = parts[-1].strip() + This middleware should be positionned before any other middleware that + relies on the value of :meth:`~HttpRequest.get_host()`, for instance + :class:`~django.middleware.common.CommonMiddleware` or + :class:`~django.middleware.csrf.CsrfViewMiddleware`. .. method:: HttpRequest.get_full_path() From 25b5da2abdebef867f1e38c95f5622df3741d0db Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 19 Feb 2012 09:04:19 +0000 Subject: [PATCH 168/225] [1.3.X] Fixed #17166 -- Documented how FIXTURE_DIRS works in the inital data how-to, and edited related bits in the settings reference. Backport of r17558 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17559 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/initial-data.txt | 25 +++++++++++++++++++------ docs/ref/settings.txt | 15 ++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/docs/howto/initial-data.txt b/docs/howto/initial-data.txt index dd6a099b9d7f..69e728ef1624 100644 --- a/docs/howto/initial-data.txt +++ b/docs/howto/initial-data.txt @@ -13,6 +13,8 @@ but initial SQL is also quite a bit more flexible. .. _initial data as sql: `providing initial sql data`_ .. _initial data via fixtures: `providing initial data with fixtures`_ +.. _initial-data-via-fixtures: + Providing initial data with fixtures ==================================== @@ -65,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 fixturename -`, where *fixturename* is the name of the fixture file you've created. -Every time you run :djadmin:`loaddata` the data will be read from the fixture -and re-loaded into the database. Note that 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. Every time you run :djadmin:`loaddata` the data will be read from the +fixture and re-loaded into the database. Note that 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 ------------------------------------------- @@ -80,6 +82,17 @@ be loaded every time you run :djadmin:`syncdb`. This is extremely convenient, but be careful: remember that the data will be refreshed *every time* you run :djadmin:`syncdb`. So don't use ``initial_data`` for data you'll want to edit. +Where Django finds fixture files +-------------------------------- + +By default, Django looks in the ``fixtures`` directory inside each app for +fixtures. You can set the :setting:`FIXTURE_DIRS` setting to a list of +additional directories where Django should look. + +When running :djadmin:`manage.py loaddata `, you can also +specify an absolute path to a fixture file, which overrides searching +the usual directories. + .. seealso:: Fixtures are also used by the :ref:`testing framework diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 8dc5fdbe0d2d..1685379edf05 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -982,9 +982,12 @@ FIXTURE_DIRS Default: ``()`` (Empty tuple) -List of locations of the fixture data files, in search order. Note that -these paths should use Unix-style forward slashes, even on Windows. See -:doc:`/topics/testing`. +List of directories searched for fixture files, in addition to the +``fixtures`` directory of each application, in search order. + +Note that these paths should use Unix-style forward slashes, even on Windows. + +See :ref:`initial-data-via-fixtures` and :ref:`topics-testing-fixtures`. FORCE_SCRIPT_NAME ------------------ @@ -1746,8 +1749,10 @@ TEMPLATE_DIRS Default: ``()`` (Empty tuple) -List of locations of the template source files, in search order. Note that -these paths should use Unix-style forward slashes, even on Windows. +List of locations of the template source files searched by +:class:`django.template.loaders.filesystem.Loader`, in search order. + +Note that these paths should use Unix-style forward slashes, even on Windows. See :doc:`/topics/templates`. From 4d959686e66feac9871304be80945270ccc3742d Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 19 Feb 2012 09:22:18 +0000 Subject: [PATCH 169/225] [1.3.X] Fixed #17319 -- Made the example for set_language less error-prone. Backport of r17560 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17561 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index a83299c1e835..807c6c53eaaa 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -763,7 +763,8 @@ The ``set_language`` redirect view .. function:: set_language(request) As a convenience, Django comes with a view, :func:`django.views.i18n.set_language`, -that sets a user's language preference and redirects back to the previous page. +that sets a user's language preference and redirects to a given URL or, by default, +back to the previous page. Activate this view by adding the following line to your URLconf:: @@ -792,7 +793,7 @@ Here's example HTML template code:
{% csrf_token %} - +
+ +In this example, Django looks up the URL of the page to which the user will be +redirected in the ``redirect_to`` context variable. From 4f6c36435cb3a083c25c040c278ef89a70810f16 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Mon, 20 Feb 2012 18:58:34 +0000 Subject: [PATCH 170/225] [1.3.X] Fixed #17390 - Added a note to topics/auth.txt regarding how to decorate class-based generic views; thanks zsiciarz for the patch. Backport of r17564 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17565 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 13 ++++++++++--- docs/topics/class-based-views.txt | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 5a2608a3a799..bb7bf4b07a0b 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1174,9 +1174,16 @@ The permission_required decorator Limiting access to generic views -------------------------------- -To limit access to a :doc:`generic view `, write a thin -wrapper around the view, and point your URLconf to your wrapper instead of the -generic view itself. For example:: +Controlling access to a :doc:`class-based generic view ` +is done by decorating the :meth:`View.dispatch ` +method on the class. See :ref:`decorating-class-based-views` for the details. + +Function-based generic views +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To limit access to a :doc:`function-based generic view `, +write a thin wrapper around the view, and point your URLconf to your wrapper +instead of the generic view itself. For example:: from django.views.generic.date_based import object_detail diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index 4da48ec2885b..1faa98023b9c 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -584,6 +584,8 @@ This approach applies the decorator on a per-instance basis. If you want every instance of a view to be decorated, you need to take a different approach. +.. _decorating-class-based-views: + Decorating the class -------------------- From 0af93e108e3dc5e0216ab45cae2dec86ee750771 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Mon, 20 Feb 2012 19:08:56 +0000 Subject: [PATCH 171/225] [1.3.X] Fixed #16758 - Added a warning regarding overriding default settings; thanks cyclops for the suggestion & Aymeric Augustin for the patch. Backport of r17566 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17567 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 1685379edf05..24ccb5f949d7 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -6,6 +6,13 @@ Settings :local: :depth: 1 +.. warning:: + + Be careful when you override settings, especially when the default value + is a non-empty tuple or dict, like :setting:`MIDDLEWARE_CLASSES` and + :setting:`TEMPLATE_CONTEXT_PROCESSORS`. Make sure you keep the components + required by the features of Django you wish to use. + Available settings ================== From b45fbc66670a375b0d6d42ecd160b7c9e9e00dc6 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Wed, 22 Feb 2012 00:52:19 +0000 Subject: [PATCH 172/225] [1.3.X] Don't let ALLOWED_INCLUDE_ROOTS be accidentally set to a string rather than a tuple. Backport of r17571 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17572 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/__init__.py | 3 +++ tests/regressiontests/settings_tests/tests.py | 7 +++++++ tests/regressiontests/templates/tests.py | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index f2012a64005f..3f707b0897e5 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -70,6 +70,9 @@ def __setattr__(self, name, value): if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'): warnings.warn('If set, %s must end with a slash' % name, PendingDeprecationWarning) + elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, basestring): + raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set " + "to a tuple, not a string.") object.__setattr__(self, name, value) diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py index dc7fde4f2cda..37ffd6d5777a 100644 --- a/tests/regressiontests/settings_tests/tests.py +++ b/tests/regressiontests/settings_tests/tests.py @@ -18,6 +18,13 @@ def test_settings_delete(self): def test_settings_delete_wrapped(self): self.assertRaises(TypeError, delattr, settings, '_wrapped') + def test_allowed_include_roots_string(self): + """ + ALLOWED_INCLUDE_ROOTS is not allowed to be incorrectly set to a string + rather than a tuple. + """ + self.assertRaises(ValueError, setattr, settings, + 'ALLOWED_INCLUDE_ROOTS', '/var/www/ssi/') class TrailingSlashURLTests(unittest.TestCase): settings_module = settings diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 074852c1c3f2..d17068821c96 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -412,7 +412,9 @@ def test_template_loader(template_name, template_dirs=None): #Set ALLOWED_INCLUDE_ROOTS so that ssi works. old_allowed_include_roots = settings.ALLOWED_INCLUDE_ROOTS - settings.ALLOWED_INCLUDE_ROOTS = os.path.dirname(os.path.abspath(__file__)) + settings.ALLOWED_INCLUDE_ROOTS = ( + os.path.dirname(os.path.abspath(__file__)), + ) # Warm the URL reversing cache. This ensures we don't pay the cost # warming the cache during one of the tests. From 38715d8af83ba73db6f80c832fe04089f2cc6644 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Fri, 24 Feb 2012 22:50:58 +0000 Subject: [PATCH 173/225] [1.3.X] Fixed #17749 - Documented better way of overriding ModelAdmin; thanks chrisdpratt and claudep. Backport of r17582 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17583 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 7394c3aec290..3f0bf625bba5 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1045,11 +1045,10 @@ provided some extra mapping data that would not otherwise be available:: pass def change_view(self, request, object_id, extra_context=None): - my_context = { - 'osm_data': self.get_osm_info(), - } + extra_context = extra_context or {} + extra_context['osm_data'] = self.get_osm_info() return super(MyModelAdmin, self).change_view(request, object_id, - extra_context=my_context) + extra_context=extra_context) ``ModelAdmin`` media definitions -------------------------------- From c0258f1da754309e25236cded6affb4d2a402238 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Fri, 24 Feb 2012 22:54:00 +0000 Subject: [PATCH 174/225] [1.3.X] Fixed #17757 - Typo in docs/intro/overview.txt; thanks kaushik1618. Backport of r17584 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17585 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/overview.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 34572a6c806e..cc0f7477d319 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -230,7 +230,7 @@ templates. In your Django settings, you specify a list of directories to check for templates. If a template doesn't exist in the first directory, it checks the second, and so on. -Let's say the ``news/article_detail.html`` template was found. Here's what that +Let's say the ``news/year_archive.html`` template was found. Here's what that might look like: .. code-block:: html+django From 41cd3b2ab121c1a9bf8797d221fd2a7bf0475a17 Mon Sep 17 00:00:00 2001 From: Timo Graham Date: Sat, 25 Feb 2012 12:55:41 +0000 Subject: [PATCH 175/225] [1.3.X] Fixed #17743 - Typo in topics/i18n/index.txt git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17587 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/i18n/index.txt b/docs/topics/i18n/index.txt index 9c2519250418..f650ed13b093 100644 --- a/docs/topics/i18n/index.txt +++ b/docs/topics/i18n/index.txt @@ -45,7 +45,7 @@ and the `Wikipedia article`_. Glossary ======== -First lets define some terms that will help us to handle a common language: +First let's define some terms that will help us to handle a common language: .. glossary:: From dad3e552349a708f32f492ca0cbc398b7b075de7 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 1 Mar 2012 19:34:23 +0000 Subject: [PATCH 176/225] [1.3.X] Fixed broken link to python-markdown in contrib.markup docs. Backport of r17608 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17609 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/markup.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/markup.txt b/docs/ref/contrib/markup.txt index d5f07f57efb3..7f4381067444 100644 --- a/docs/ref/contrib/markup.txt +++ b/docs/ref/contrib/markup.txt @@ -35,7 +35,7 @@ For more documentation, read the source code in .. _Markdown: http://en.wikipedia.org/wiki/Markdown .. _reST (reStructured Text): http://en.wikipedia.org/wiki/ReStructuredText .. _PyTextile: http://loopcore.com/python-textile/ -.. _Python-markdown: http://www.freewisdom.org/projects/python-markdown +.. _Python-markdown: http://pypi.python.org/pypi/Markdown .. _doc-utils: http://docutils.sf.net/ reStructured Text From 523d6167d6075eb3b9cb3aef71feb940ea0be5a3 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 1 Mar 2012 23:03:46 +0000 Subject: [PATCH 177/225] [1.3.X] Fixed #17737 -- Stopped the collectstatic management command from copying the wrong file in repeated runs. Thanks, pigletto. Backport from trunk (r17612). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17613 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../management/commands/collectstatic.py | 10 ++-- .../staticfiles_tests/tests.py | 52 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 24b29e45562d..d8aed4038310 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -76,6 +76,7 @@ def handle_noargs(self, **options): if confirm != 'yes': raise CommandError("Collecting static files cancelled.") + processed_files = [] for finder in finders.get_finders(): for path, storage in finder.list(ignore_patterns): # Prefix the relative path if the source storage contains it @@ -83,10 +84,13 @@ def handle_noargs(self, **options): prefixed_path = os.path.join(storage.prefix, path) else: prefixed_path = path + if prefixed_path in processed_files: + continue if symlink: self.link_file(path, prefixed_path, storage, **options) else: self.copy_file(path, prefixed_path, storage, **options) + processed_files.append(prefixed_path) actual_count = len(self.copied_files) + len(self.symlinked_files) unmodified_count = len(self.unmodified_files) @@ -196,9 +200,7 @@ def copy_file(self, path, prefixed_path, source_storage, **options): os.makedirs(os.path.dirname(full_path)) except OSError: pass - shutil.copy2(source_path, full_path) - else: - source_file = source_storage.open(path) - self.storage.save(prefixed_path, source_file) + source_file = source_storage.open(path) + self.storage.save(prefixed_path, source_file) if not prefixed_path in self.copied_files: self.copied_files.append(prefixed_path) diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index b8a7aa6d339a..4c03f6a5a9fe 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -245,6 +245,58 @@ def test_no_common_ignore_patterns(self): self.assertFileContains('test/CVS', 'should be ignored') +class TestCollectionFilesOverride(BuildStaticTestCase): + """ + Test overriding duplicated files by ``collectstatic`` management command. + Check for proper handling of apps order in INSTALLED_APPS even if file + modification dates are in different order: + + 'regressiontests.staticfiles_tests.apps.test', + 'regressiontests.staticfiles_tests.apps.no_label', + """ + def setUp(self): + self.orig_path = os.path.join(TEST_ROOT, 'apps', 'no_label', 'static', 'file2.txt') + # get modification and access times for no_label/static/file2.txt + self.orig_mtime = os.path.getmtime(self.orig_path) + self.orig_atime = os.path.getatime(self.orig_path) + + # prepare duplicate of file2.txt from no_label app + # this file will have modification time older than no_label/static/file2.txt + # anyway it should be taken to STATIC_ROOT because 'test' app is before + # 'no_label' app in INSTALLED_APPS + self.testfile_path = os.path.join(TEST_ROOT, 'apps', 'test', 'static', 'file2.txt') + f = open(self.testfile_path, 'w+') + f.write('duplicate of file2.txt') + f.close() + os.utime(self.testfile_path, (self.orig_atime - 1, self.orig_mtime - 1)) + super(TestCollectionFilesOverride, self).setUp() + + def tearDown(self): + if os.path.exists(self.testfile_path): + os.unlink(self.testfile_path) + # set back original modification time + os.utime(self.orig_path, (self.orig_atime, self.orig_mtime)) + + def test_override(self): + self.assertFileContains('file2.txt', 'duplicate of file2.txt') + + # run collectstatic again + self.run_collectstatic() + + self.assertFileContains('file2.txt', 'duplicate of file2.txt') + + # and now change modification time of no_label/static/file2.txt + # test app is first in INSTALLED_APPS so file2.txt should remain unmodified + mtime = os.path.getmtime(self.testfile_path) + atime = os.path.getatime(self.testfile_path) + os.utime(self.orig_path, (mtime + 1, atime + 1)) + + # run collectstatic again + self.run_collectstatic() + + self.assertFileContains('file2.txt', 'duplicate of file2.txt') + + class TestNoFilesCreated(object): def test_no_files_created(self): From 695187902367882e59136fd3ef6fa9e0363869fa Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 12 Mar 2012 22:13:34 +0000 Subject: [PATCH 178/225] [1.3.X] Fixed the localization docs a little to point to the correct Transifex URL. Also reworded it a bit to follow the site's new UI. Backport from trunk (r17690). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17691 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/contributing.txt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 220ed5db6111..22facfe022f1 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -454,9 +454,8 @@ infrastructure available to Django applications described in the These translations are contributed by Django users worldwide. If you find an incorrect translation or want to discuss specific translations, go to the -`translation team`_ page for that language. If you would like to help out -with translating or add a language that isn't yet translated, here's what -to do: +`Django project page`_ on `Transifex`_. If you would like to help out with +translating or add a language that isn't yet translated, here's what to do: * Join the `Django i18n mailing list`_ and introduce yourself. @@ -464,12 +463,12 @@ to do: * Signup at `Transifex`_ and visit the `Django project page`_. - * On the "`Translation Teams`_" page, choose the language team you want - to work with, **or** -- in case the language team doesn't exist yet -- - request a new team by clicking on the "Request a new team" button - and select the appropriate language. + * On the `Django project page`_ page, choose the language you want + to work on, **or** -- in case the language doesn't exist yet -- + request a new language team by clicking on the "Request language" + link and select the appropriate language. - * Then, click the "Join this Team" button to become a member of this team. + * Then, click the "Join Team" button to become a member of this team. Every team has at least one coordinator who is responsible to review your membership request. You can of course also contact the team coordinator to clarify procedural problems and handle the actual @@ -499,8 +498,6 @@ to do: .. _Django i18n mailing list: http://groups.google.com/group/django-i18n/ .. _Transifex: http://www.transifex.net/ .. _Django project page: http://www.transifex.net/projects/p/django/ -.. _translation teams: http://www.transifex.net/projects/p/django/teams/ -.. _translation team: http://www.transifex.net/projects/p/django/teams/ .. _Transifex Help: http://help.transifex.net/ Submitting javascript patches From ddfa89b959a03417fc3eb24738b0cb1049940e5d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 13 Mar 2012 22:25:10 +0000 Subject: [PATCH 179/225] Fixed #17584 -- Updated create_template_postgis-debian.sh script for PostgreSQL 9.1 installs. Thanks akaihola for the initial patch. Backport of r17706 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17707 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../gis/create_template_postgis-debian.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/gis/create_template_postgis-debian.sh b/docs/ref/contrib/gis/create_template_postgis-debian.sh index 61bbef40a10c..ac9641d16845 100755 --- a/docs/ref/contrib/gis/create_template_postgis-debian.sh +++ b/docs/ref/contrib/gis/create_template_postgis-debian.sh @@ -1,5 +1,8 @@ #!/bin/bash +GEOGRAPHY=0 +POSTGIS_SQL=postgis.sql + # For Ubuntu 8.x and 9.x releases. if [ -d "/usr/share/postgresql-8.3-postgis" ] then @@ -11,21 +14,24 @@ fi if [ -d "/usr/share/postgresql/8.4/contrib" ] then POSTGIS_SQL_PATH=/usr/share/postgresql/8.4/contrib - POSTGIS_SQL=postgis.sql fi # For Ubuntu 10.10 (with PostGIS 1.5) if [ -d "/usr/share/postgresql/8.4/contrib/postgis-1.5" ] then POSTGIS_SQL_PATH=/usr/share/postgresql/8.4/contrib/postgis-1.5 - POSTGIS_SQL=postgis.sql GEOGRAPHY=1 -else - GEOGRAPHY=0 +fi + +# For Ubuntu 11.10 / Linux Mint 12 (with PostGIS 1.5) +if [ -d "/usr/share/postgresql/9.1/contrib/postgis-1.5" ] +then + POSTGIS_SQL_PATH=/usr/share/postgresql/9.1/contrib/postgis-1.5 + GEOGRAPHY=1 fi createdb -E UTF8 template_postgis && \ -createlang -d template_postgis plpgsql && \ +( createlang -d template_postgis -l | grep plpgsql || createlang -d template_postgis plpgsql ) && \ psql -d postgres -c "UPDATE pg_database SET datistemplate='true' WHERE datname='template_postgis';" && \ psql -d template_postgis -f $POSTGIS_SQL_PATH/$POSTGIS_SQL && \ psql -d template_postgis -f $POSTGIS_SQL_PATH/spatial_ref_sys.sql && \ From d498033818d2e1b395e303003536462c8690e96e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 14 Mar 2012 07:48:03 +0000 Subject: [PATCH 180/225] [1.3.X] Updated some outdated external URLs in docs. Backport of r17710 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17711 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/static-files.txt | 4 ++-- docs/internals/contributing.txt | 4 ++-- docs/ref/contrib/gis/db-api.txt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index d6e269770939..d21995abec5d 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -457,8 +457,8 @@ For details on how you'd write one of these backends, storage backends for many common file storage APIs (including `S3`__). __ http://s3.amazonaws.com/ -__ http://code.welldev.org/django-storages/ -__ http://code.welldev.org/django-storages/wiki/S3Storage +__ http://code.larlet.fr/django-storages/ +__ http://django-storages.readthedocs.org/en/latest/backends/amazon-S3.html Upgrading from ``django-staticfiles`` ===================================== diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 22facfe022f1..b5e5194aa36a 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -536,7 +536,7 @@ binary, so relinking that command may be necessary as well. Please don't forget to run ``compress.py`` and include the ``diff`` of the minified scripts when submitting patches for Django's javascript. -.. _Closure Compiler: http://code.google.com/closure/compiler/ +.. _Closure Compiler: https://developers.google.com/closure/compiler/ Django conventions ================== @@ -869,7 +869,7 @@ repository: For the curious: We're using a `Trac post-commit hook`_ for this. - .. _Trac post-commit hook: http://trac.edgewall.org/browser/trunk/contrib/trac-post-commit-hook + .. _Trac post-commit hook: http://trac.edgewall.org/browser/trunk/contrib/trac-svn-post-commit-hook.cmd * If your commit references a ticket in the Django `ticket tracker`_ but does *not* close the ticket, include the phrase "Refs #abc", where "abc" diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index fbced8e6e1eb..30371c81af71 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -54,7 +54,7 @@ MySQL Spatial Limitations MySQL's spatial extensions only support bounding box operations (what MySQL calls minimum bounding rectangles, or MBR). Specifically, -`MySQL does not conform to the OGC standard `_: +`MySQL does not conform to the OGC standard `_: Currently, MySQL does not implement these functions [``Contains``, ``Crosses``, ``Disjoint``, ``Intersects``, ``Overlaps``, From 1f924cf72d76466a359371267c448d19c4e1352a Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Wed, 14 Mar 2012 18:51:20 +0000 Subject: [PATCH 181/225] [1.3.X] Fixed #17837. Improved markdown safety. Markdown enable_attributes is now False when safe_mode is enabled. Documented the markdown "safe" argument. Added warnings when the safe argument is passed to versions of markdown which cannot be made safe. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17734 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/markup/templatetags/markup.py | 17 +++++++++++++++-- django/contrib/markup/tests.py | 14 ++++++++++++++ docs/ref/contrib/markup.txt | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 7cdc04c65383..66cb12b008ca 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -11,6 +11,8 @@ * reStructuredText, which requires docutils from http://docutils.sf.net/ """ +import warnings + from django import template from django.conf import settings from django.utils.encoding import smart_str, force_unicode @@ -65,10 +67,21 @@ def markdown(value, arg=''): # Unicode support only in markdown v1.7 or above. Version_info # exist only in markdown v1.6.2rc-2 or above. - if getattr(markdown, "version_info", None) < (1,7): + markdown_vers = getattr(markdown, "version_info", None) + if markdown_vers < (1,7): return mark_safe(force_unicode(markdown.markdown(smart_str(value), extensions, safe_mode=safe_mode))) else: - return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode)) + if markdown_vers >= (2,1): + if safe_mode: + return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode, enable_attributes=False)) + else: + return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode)) + else: + warnings.warn("Versions of markdown prior to 2.1 do not " + "support disabling of attributes, no " + "attributes have been removed and the result " + "is insecure.") + return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode)) else: return mark_safe(force_unicode(markdown.markdown(smart_str(value)))) markdown.is_safe = True diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py index e97a7def3b23..6903dd7d85d6 100644 --- a/django/contrib/markup/tests.py +++ b/django/contrib/markup/tests.py @@ -60,6 +60,20 @@ def test_markdown(self): pattern = re.compile("""

Paragraph 1\s*

\s*

\s*An h2

""") self.assertTrue(pattern.match(rendered)) + @unittest.skipUnless(markdown, 'markdown no installed') + def test_markdown_attribute_disable(self): + t = Template("{% load markup %}{{ markdown_content|markdown:'safe' }}") + markdown_content = "{@onclick=alert('hi')}some paragraph" + rendered = t.render(Context({'markdown_content':markdown_content})).strip() + self.assertTrue('@' in rendered) + + @unittest.skipUnless(markdown, 'markdown no installed') + def test_markdown_attribute_enable(self): + t = Template("{% load markup %}{{ markdown_content|markdown }}") + markdown_content = "{@onclick=alert('hi')}some paragraph" + rendered = t.render(Context({'markdown_content':markdown_content})).strip() + self.assertFalse('@' in rendered) + @unittest.skipIf(markdown, 'markdown is installed') def test_no_markdown(self): t = Template("{{ markdown_content|markdown }}") diff --git a/docs/ref/contrib/markup.txt b/docs/ref/contrib/markup.txt index 7f4381067444..09cb08022137 100644 --- a/docs/ref/contrib/markup.txt +++ b/docs/ref/contrib/markup.txt @@ -47,3 +47,19 @@ override the default writer settings. See the `restructuredtext writer settings`_ for details on what these settings are. .. _restructuredtext writer settings: http://docutils.sourceforge.net/docs/user/config.html#html4css1-writer + +Markdown +-------- + +The Python Markdown library supports options named "safe_mode" and +"enable_attributes". Both relate to the security of the output. To enable both +options in tandem, the markdown filter supports the "safe" argument. + + {{ markdown_content_var|markdown:"safe" }} + +.. warning:: + + Versions of the Python-Markdown library prior to 2.1 do not support the + optional disabling of attributes and by default they will be included in + any output from the markdown filter - a warning is issued if this is the + case. From 2acf028b4b92c4085c04695b83b503d25797a637 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 15 Mar 2012 07:58:19 +0000 Subject: [PATCH 182/225] [1.3.X] Fixed #17900 -- StreamHandler output defaults to stderr. Thanks c4m3lo for the report. Backport of r17741 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17742 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/logging.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 64becd1799f9..d570218b7cad 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -324,7 +324,7 @@ This logging configuration does the following things: higher message to ``/dev/null``. * ``console``, a StreamHandler, which will print any ``DEBUG`` - message to stdout. This handler uses the `simple` output + message to stderr. This handler uses the `simple` output format. * ``mail_admins``, an AdminEmailHandler, which will e-mail any From 838adb231254dfec5b1fd4b7588c2cd9593debc7 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Fri, 16 Mar 2012 00:32:42 +0000 Subject: [PATCH 183/225] [1.3.X] Ensured that some staticfiles tests get properly cleaned up on teardown. Thanks to Claude Paroz for the patch. Backport of r17747 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17748 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/staticfiles_tests/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 4c03f6a5a9fe..b9b9c59790d9 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -276,6 +276,7 @@ def tearDown(self): os.unlink(self.testfile_path) # set back original modification time os.utime(self.orig_path, (self.orig_atime, self.orig_mtime)) + super(TestCollectionFilesOverride, self).tearDown() def test_override(self): self.assertFileContains('file2.txt', 'duplicate of file2.txt') From 2f6b8482f6a1117ac51efec675a7362f9b450349 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Fri, 16 Mar 2012 00:38:18 +0000 Subject: [PATCH 184/225] [1.3.X] Fixed #17908 -- Made some `contrib.markup` tests be skipped so they don't fail on old versions of Markdown. Thanks to Preston Holmes for the patch. Backport of r17749 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17750 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/markup/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py index 6903dd7d85d6..f970c286e2f0 100644 --- a/django/contrib/markup/tests.py +++ b/django/contrib/markup/tests.py @@ -14,6 +14,7 @@ try: import markdown + markdown_version = getattr(markdown, "version_info", 0) except ImportError: markdown = None @@ -38,7 +39,6 @@ class Templates(unittest.TestCase): .. _link: http://www.example.com/""" - @unittest.skipUnless(textile, 'texttile not installed') def test_textile(self): t = Template("{{ textile_content|textile }}") @@ -60,14 +60,14 @@ def test_markdown(self): pattern = re.compile("""

Paragraph 1\s*

\s*

\s*An h2

""") self.assertTrue(pattern.match(rendered)) - @unittest.skipUnless(markdown, 'markdown no installed') + @unittest.skipUnless(markdown and markdown_version >= (2,1), 'markdown >= 2.1 not installed') def test_markdown_attribute_disable(self): t = Template("{% load markup %}{{ markdown_content|markdown:'safe' }}") markdown_content = "{@onclick=alert('hi')}some paragraph" rendered = t.render(Context({'markdown_content':markdown_content})).strip() self.assertTrue('@' in rendered) - @unittest.skipUnless(markdown, 'markdown no installed') + @unittest.skipUnless(markdown and markdown_version >= (2,1), 'markdown >= 2.1 not installed') def test_markdown_attribute_enable(self): t = Template("{% load markup %}{{ markdown_content|markdown }}") markdown_content = "{@onclick=alert('hi')}some paragraph" From 1dd8848bebfcf98ae688984c7686250f7956acb6 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 16 Mar 2012 19:32:13 +0000 Subject: [PATCH 185/225] [1.3.X] Fixed #17841 -- Clarified caching note about authentication backends. Thanks auzigog for the proposal and lukegb for the patch. Backport of r17752 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17753 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index bb7bf4b07a0b..e34ab9529587 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1518,8 +1518,9 @@ processing at the first positive match. Once a user has authenticated, Django stores which backend was used to authenticate the user in the user's session, and re-uses the same backend - for subsequent authentication attempts for that user. This effectively means - that authentication sources are cached, so if you change + for the duration of that session whenever access to the currently + authenticated user is needed. This effectively means that authentication + sources are cached on a per-session basis, so if you change :setting:`AUTHENTICATION_BACKENDS`, you'll need to clear out session data if you need to force users to re-authenticate using different methods. A simple way to do that is simply to execute ``Session.objects.all().delete()``. From 92929d5ef4ba555e2fedf683e8223ba748963538 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 17 Mar 2012 12:58:16 +0000 Subject: [PATCH 186/225] [1.3.X] Fixed #17488 -- This test passed in 2011 only because 2012-01-01 is a Sunday. Thanks Florian Apolloner for the report and patch. Fixes #17912. Thanks Julien for the report. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17759 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/generic_views/dates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/regressiontests/generic_views/dates.py b/tests/regressiontests/generic_views/dates.py index 5b784f68102c..83658ca3951e 100644 --- a/tests/regressiontests/generic_views/dates.py +++ b/tests/regressiontests/generic_views/dates.py @@ -255,7 +255,8 @@ def test_week_view_allow_empty(self): self.assertEqual(list(res.context['book_list']), []) def test_week_view_allow_future(self): - future = datetime.date(datetime.date.today().year + 1, 1, 1) + # January 7th always falls in week 1, given Python's definition of week numbers + future = datetime.date(datetime.date.today().year + 1, 1, 7) b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future) res = self.client.get('/dates/books/%s/week/1/' % future.year) From 651c0414a8be83785f64db7efd88837f2a0f0ce1 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 24 Mar 2012 07:36:23 +0000 Subject: [PATCH 187/225] [1.3.X] Fixed #16812 -- Percent-encode URLs in verify_exists, to fix test failures on Python 2.5 and 2.6. Backport of r16838 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17803 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/validators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django/core/validators.py b/django/core/validators.py index 9dcc2bc3ab21..b75550935282 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -1,5 +1,6 @@ import platform import re +import urllib import urllib2 import urlparse @@ -84,6 +85,8 @@ def __call__(self, value): "User-Agent": self.user_agent, } url = url.encode('utf-8') + # Quote characters from the unreserved set, refs #16812 + url = urllib.quote(url, "!*'();:@&=+$,/?#[]") broken_error = ValidationError( _(u'This URL appears to be a broken link.'), code='invalid_link') try: From fd2efb35fbb4c031fb52d4b969b0d8118df6a8e2 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 24 Mar 2012 07:43:24 +0000 Subject: [PATCH 188/225] [1.3.X] Fixed #16677 -- Fixed the future version of the ssi template tag to work with template file names that contain spaces. Backport of r16687 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17804 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/templatetags/future.py | 2 +- .../templates/ssi include with spaces.html | 1 + tests/regressiontests/templates/tests.py | 40 ++++++++++++++----- 3 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 tests/regressiontests/templates/templates/ssi include with spaces.html diff --git a/django/templatetags/future.py b/django/templatetags/future.py index cd29dc276acf..21a67dad5836 100644 --- a/django/templatetags/future.py +++ b/django/templatetags/future.py @@ -22,7 +22,7 @@ def ssi(parser, token): {% ssi "/home/html/ljworld.com/includes/right_generic.html" parsed %} """ - bits = token.contents.split() + bits = token.split_contents() parsed = False if len(bits) not in (2, 3): raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" diff --git a/tests/regressiontests/templates/templates/ssi include with spaces.html b/tests/regressiontests/templates/templates/ssi include with spaces.html new file mode 100644 index 000000000000..1c856488610c --- /dev/null +++ b/tests/regressiontests/templates/templates/ssi include with spaces.html @@ -0,0 +1 @@ +This is for testing an ssi include with spaces in its name. {{ test }} diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index d17068821c96..d09660e686cb 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -507,7 +507,8 @@ def render(self, test_template, vals): def get_template_tests(self): # SYNTAX -- # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class) - return { + basedir = os.path.dirname(os.path.abspath(__file__)) + tests = { ### BASIC SYNTAX ################################################ # Plain text should go through the template parser untouched @@ -1349,27 +1350,34 @@ def get_template_tests(self): ### SSI TAG ######################################################## # Test normal behavior - 'old-ssi01': ('{%% ssi %s %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {}, 'This is for testing an ssi include. {{ test }}\n'), - 'old-ssi02': ('{%% ssi %s %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {}, ''), + 'old-ssi01': ('{%% ssi %s %%}' % os.path.join(basedir, 'templates', 'ssi_include.html'), {}, 'This is for testing an ssi include. {{ test }}\n'), + 'old-ssi02': ('{%% ssi %s %%}' % os.path.join(basedir, 'not_here'), {}, ''), # Test parsed output - 'old-ssi06': ('{%% ssi %s parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {'test': 'Look ma! It parsed!'}, 'This is for testing an ssi include. Look ma! It parsed!\n'), - 'old-ssi07': ('{%% ssi %s parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {'test': 'Look ma! It parsed!'}, ''), + 'old-ssi06': ('{%% ssi %s parsed %%}' % os.path.join(basedir, 'templates', 'ssi_include.html'), {'test': 'Look ma! It parsed!'}, 'This is for testing an ssi include. Look ma! It parsed!\n'), + 'old-ssi07': ('{%% ssi %s parsed %%}' % os.path.join(basedir, 'not_here'), {'test': 'Look ma! It parsed!'}, ''), + + # Test space in file name + 'old-ssi08': ('{%% ssi %s %%}' % os.path.join(basedir, 'templates', 'ssi include with spaces.html'), {}, template.TemplateSyntaxError), + 'old-ssi09': ('{%% ssi %s parsed %%}' % os.path.join(basedir, 'templates', 'ssi include with spaces.html'), {'test': 'Look ma! It parsed!'}, template.TemplateSyntaxError), # Future compatibility # Test normal behavior - 'ssi01': ('{%% load ssi from future %%}{%% ssi "%s" %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {}, 'This is for testing an ssi include. {{ test }}\n'), - 'ssi02': ('{%% load ssi from future %%}{%% ssi "%s" %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {}, ''), - 'ssi03': ("{%% load ssi from future %%}{%% ssi '%s' %%}" % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {}, ''), + 'ssi01': ('{%% load ssi from future %%}{%% ssi "%s" %%}' % os.path.join(basedir, 'templates', 'ssi_include.html'), {}, 'This is for testing an ssi include. {{ test }}\n'), + 'ssi02': ('{%% load ssi from future %%}{%% ssi "%s" %%}' % os.path.join(basedir, 'not_here'), {}, ''), + 'ssi03': ("{%% load ssi from future %%}{%% ssi '%s' %%}" % os.path.join(basedir, 'not_here'), {}, ''), # Test passing as a variable - 'ssi04': ('{% load ssi from future %}{% ssi ssi_file %}', {'ssi_file': os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html')}, 'This is for testing an ssi include. {{ test }}\n'), + 'ssi04': ('{% load ssi from future %}{% ssi ssi_file %}', {'ssi_file': os.path.join(basedir, 'templates', 'ssi_include.html')}, 'This is for testing an ssi include. {{ test }}\n'), 'ssi05': ('{% load ssi from future %}{% ssi ssi_file %}', {'ssi_file': 'no_file'}, ''), # Test parsed output - 'ssi06': ('{%% load ssi from future %%}{%% ssi "%s" parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {'test': 'Look ma! It parsed!'}, 'This is for testing an ssi include. Look ma! It parsed!\n'), - 'ssi07': ('{%% load ssi from future %%}{%% ssi "%s" parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {'test': 'Look ma! It parsed!'}, ''), + 'ssi06': ('{%% load ssi from future %%}{%% ssi "%s" parsed %%}' % os.path.join(basedir, 'templates', 'ssi_include.html'), {'test': 'Look ma! It parsed!'}, 'This is for testing an ssi include. Look ma! It parsed!\n'), + 'ssi07': ('{%% load ssi from future %%}{%% ssi "%s" parsed %%}' % os.path.join(basedir, 'not_here'), {'test': 'Look ma! It parsed!'}, ''), + # Test space in file name + 'ssi08': ('{%% load ssi from future %%}{%% ssi "%s" %%}' % os.path.join(basedir, 'templates', 'ssi include with spaces.html'), {}, 'This is for testing an ssi include with spaces in its name. {{ test }}\n'), + 'ssi09': ('{%% load ssi from future %%}{%% ssi "%s" parsed %%}' % os.path.join(basedir, 'templates', 'ssi include with spaces.html'), {'test': 'Look ma! It parsed!'}, 'This is for testing an ssi include with spaces in its name. Look ma! It parsed!\n'), ### TEMPLATETAG TAG ####################################################### 'templatetag01': ('{% templatetag openblock %}', {}, '{%'), @@ -1601,6 +1609,16 @@ def get_template_tests(self): 'static-prefixtag03': ('{% load static %}{% get_media_prefix %}', {}, settings.MEDIA_URL), 'static-prefixtag04': ('{% load static %}{% get_media_prefix as media_prefix %}{{ media_prefix }}', {}, settings.MEDIA_URL), } + # Until Django 1.5, the ssi tag takes an unquoted constant in argument, + # and there's no way to escape spaces. As a consequence it's impossible + # to include a file if its absolute path contains a space, as + # demonstrated by tests old-ssi08 and old-ssi09. + # If the patch to the Django chekout contains a space, the following + # tests will raise an exception too. + if ' ' in basedir: + for test_name in 'old-ssi01', 'old-ssi02', 'old-ssi06', 'old-ssi07': + tests[test_name] = tests[test_name][:-1] + (template.TemplateSyntaxError,) + return tests class TemplateTagLoading(unittest.TestCase): From 8e73302070d69b49c65390d7a8c3c3a367c0b3d9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 24 Mar 2012 12:26:46 +0000 Subject: [PATCH 189/225] [1.3.x] Fixed #16481 -- Adapted one raw SQL query in cull implementation of the database-based cache backend so it works with Oracle. Backport of r16635 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17805 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/cache/backends/db.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 495812a48de5..4007de6ecdab 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -132,7 +132,13 @@ def _cull(self, db, cursor, now): cursor.execute("SELECT COUNT(*) FROM %s" % table) num = cursor.fetchone()[0] if num > self._max_entries: - cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % table, [num / self._cull_frequency]) + cull_num = num / self._cull_frequency + if connections[db].vendor == 'oracle': + # Special case for Oracle because it 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("DELETE FROM %s WHERE cache_key < %%s" % table, [cursor.fetchone()[0]]) def clear(self): From 15fb61c62ce403bc2ba7f63c5f519a988ce2c5d2 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 24 Mar 2012 13:56:48 +0000 Subject: [PATCH 190/225] [1.3.X] Avoided a test failure if the settings module used to run the test suite is called "test_settings". The globbing feature and this test were removed in 1.4. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17806 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/app_loading/tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/regressiontests/app_loading/tests.py b/tests/regressiontests/app_loading/tests.py index 78e6519e354f..420560ab658b 100644 --- a/tests/regressiontests/app_loading/tests.py +++ b/tests/regressiontests/app_loading/tests.py @@ -5,7 +5,7 @@ from django.conf import Settings from django.db.models.loading import cache, load_app -from django.utils.unittest import TestCase +from django.utils.unittest import TestCase, skipIf class InstalledAppsGlobbingTest(TestCase): @@ -14,6 +14,8 @@ def setUp(self): sys.path.append(os.path.dirname(os.path.abspath(__file__))) self.OLD_TZ = os.environ.get("TZ") + @skipIf('test_settings' in sys.modules, + 'A toplevel module named test_settings already exists') def test_globbing(self): settings = Settings('test_settings') self.assertEqual(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2']) From 0bbe7379ee3e2cb290f9c9e841bb2f1457e14b16 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 25 Mar 2012 06:53:47 +0000 Subject: [PATCH 191/225] [1.3.X] Fixed #17634 -- Optimized the performance of MultiValueDict by using append instead of copy and by minimizing the number of dict lookups. Backport of r17464 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17807 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/datastructures.py | 15 +++++++++------ tests/regressiontests/utils/datastructures.py | 6 ++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 7425ea2ce210..33cc2450c29c 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -319,17 +319,20 @@ def setlist(self, key, list_): def setdefault(self, key, default=None): if key not in self: self[key] = default + return default return self[key] - def setlistdefault(self, key, default_list=()): + def setlistdefault(self, key, default_list=None): if key not in self: + if default_list is None: + default_list = [] self.setlist(key, default_list) + return default_list return self.getlist(key) def appendlist(self, key, value): """Appends an item to the internal list associated with key.""" - self.setlistdefault(key, []) - super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value]) + self.setlistdefault(key).append(value) def items(self): """ @@ -378,15 +381,15 @@ def update(self, *args, **kwargs): other_dict = args[0] if isinstance(other_dict, MultiValueDict): for key, value_list in other_dict.lists(): - self.setlistdefault(key, []).extend(value_list) + self.setlistdefault(key).extend(value_list) else: try: for key, value in other_dict.items(): - self.setlistdefault(key, []).append(value) + self.setlistdefault(key).append(value) except TypeError: raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") for key, value in kwargs.iteritems(): - self.setlistdefault(key, []).append(value) + self.setlistdefault(key).append(value) class DotExpandedDict(dict): """ diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index 3d01ab8b6fe6..1f8b59ec17a7 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -212,6 +212,12 @@ def test_multivaluedict(self): self.assertEqual(list(d.itervalues()), ['Developer', 'Simon', 'Willison']) + def test_appendlist(self): + d = MultiValueDict() + d.appendlist('name', 'Adrian') + d.appendlist('name', 'Simon') + self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) + def test_copy(self): for copy_func in [copy, lambda d: d.copy()]: d1 = MultiValueDict({ From e293d82c36133d722cf6b5e71c56d92853ceaf00 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 31 Mar 2012 18:42:38 +0000 Subject: [PATCH 192/225] [1.3.X] Fixed #17972 -- Ensured that admin filters on a foreign key respect the to_field attribute. This fixes a regression introduced in [14674] and Django 1.3. Thanks to graveyboat and Karen Tracey for the report. Backport of r17854 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@17857 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/filterspecs.py | 9 ++- django/contrib/admin/options.py | 10 +-- .../admin_filterspecs/models.py | 15 ++++ .../admin_filterspecs/tests.py | 68 ++++++++++++++++++- 4 files changed, 92 insertions(+), 10 deletions(-) diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 965b32bb08a0..05f6b1ad5d38 100644 --- a/django/contrib/admin/filterspecs.py +++ b/django/contrib/admin/filterspecs.py @@ -74,9 +74,12 @@ def __init__(self, f, request, params, model, model_admin, self.lookup_title = other_model._meta.verbose_name else: self.lookup_title = f.verbose_name # use field name - rel_name = other_model._meta.pk.name + if hasattr(f, 'rel'): + rel_name = f.rel.get_related_field().name + else: + rel_name = other_model._meta.pk.name self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name) - self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path) + self.lookup_kwarg_isnull = '%s__isnull' % self.field_path self.lookup_val = request.GET.get(self.lookup_kwarg, None) self.lookup_val_isnull = request.GET.get( self.lookup_kwarg_isnull, None) @@ -176,7 +179,7 @@ def choices(self, cl): class DateFieldFilterSpec(FilterSpec): def __init__(self, f, request, params, model, model_admin, - field_path=None): + field_path=None): super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin, field_path=field_path) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 43c5503f9907..3d06050c5455 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -225,18 +225,18 @@ def lookup_allowed(self, lookup, value): # if foo has been specificially included in the lookup list; so # drop __id if it is the last part. However, first we need to find # the pk attribute name. - pk_attr_name = None + rel_name = None for part in parts[:-1]: field, _, _, _ = model._meta.get_field_by_name(part) if hasattr(field, 'rel'): model = field.rel.to - pk_attr_name = model._meta.pk.name + rel_name = field.rel.get_related_field().name elif isinstance(field, RelatedObject): model = field.model - pk_attr_name = model._meta.pk.name + rel_name = model._meta.pk.name else: - pk_attr_name = None - if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name: + rel_name = None + if rel_name and len(parts) > 1 and parts[-1] == rel_name: parts.pop() try: diff --git a/tests/regressiontests/admin_filterspecs/models.py b/tests/regressiontests/admin_filterspecs/models.py index 5b284c77991b..55239d940d3d 100644 --- a/tests/regressiontests/admin_filterspecs/models.py +++ b/tests/regressiontests/admin_filterspecs/models.py @@ -21,3 +21,18 @@ class BoolTest(models.Model): default=NO, choices=YES_NO_CHOICES ) + + +class Department(models.Model): + code = models.CharField(max_length=4, unique=True) + description = models.CharField(max_length=50, blank=True, null=True) + + def __unicode__(self): + return self.description + +class Employee(models.Model): + department = models.ForeignKey(Department, to_field="code") + name = models.CharField(max_length=100) + + def __unicode__(self): + return self.name diff --git a/tests/regressiontests/admin_filterspecs/tests.py b/tests/regressiontests/admin_filterspecs/tests.py index 8b9e7343138a..c73e53e4288c 100644 --- a/tests/regressiontests/admin_filterspecs/tests.py +++ b/tests/regressiontests/admin_filterspecs/tests.py @@ -6,7 +6,7 @@ from django.contrib.admin.views.main import ChangeList from django.utils.encoding import force_unicode -from models import Book, BoolTest +from models import Book, BoolTest, Employee, Department def select_by(dictlist, key, value): return [x for x in dictlist if x[key] == value][0] @@ -32,7 +32,6 @@ def setUp(self): self.request_factory = RequestFactory() - def get_changelist(self, request, model, modeladmin): return ChangeList(request, model, modeladmin.list_display, modeladmin.list_display_links, modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields, @@ -200,6 +199,67 @@ def test_BooleanFilterSpec(self): self.assertEqual(choice['selected'], True) self.assertEqual(choice['query_string'], '?completed__exact=1') + def test_fk_with_to_field(self): + """ + Ensure that a filter on a FK respects the FK's to_field attribute. + Refs #17972. + """ + modeladmin = EmployeeAdmin(Employee, admin.site) + + dev = Department.objects.create(code='DEV', description='Development') + design = Department.objects.create(code='DSN', description='Design') + john = Employee.objects.create(name='John Blue', department=dev) + jack = Employee.objects.create(name='Jack Red', department=design) + + request = self.request_factory.get('/', {}) + changelist = self.get_changelist(request, Employee, modeladmin) + + # Make sure the correct queryset is returned + queryset = changelist.get_query_set() + self.assertEqual(list(queryset), [jack, john]) + + filterspec = changelist.get_filters(request)[0][-1] + self.assertEqual(force_unicode(filterspec.title()), u'department') + choices = list(filterspec.choices(changelist)) + + self.assertEqual(choices[0]['display'], u'All') + self.assertEqual(choices[0]['selected'], True) + self.assertEqual(choices[0]['query_string'], '?') + + self.assertEqual(choices[1]['display'], u'Development') + self.assertEqual(choices[1]['selected'], False) + self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV') + + self.assertEqual(choices[2]['display'], u'Design') + self.assertEqual(choices[2]['selected'], False) + self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN') + + # Filter by Department=='Development' -------------------------------- + + request = self.request_factory.get('/', {'department__code__exact': 'DEV'}) + changelist = self.get_changelist(request, Employee, modeladmin) + + # Make sure the correct queryset is returned + queryset = changelist.get_query_set() + self.assertEqual(list(queryset), [john]) + + filterspec = changelist.get_filters(request)[0][-1] + self.assertEqual(force_unicode(filterspec.title()), u'department') + choices = list(filterspec.choices(changelist)) + + self.assertEqual(choices[0]['display'], u'All') + self.assertEqual(choices[0]['selected'], False) + self.assertEqual(choices[0]['query_string'], '?') + + self.assertEqual(choices[1]['display'], u'Development') + self.assertEqual(choices[1]['selected'], True) + self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV') + + self.assertEqual(choices[2]['display'], u'Design') + self.assertEqual(choices[2]['selected'], False) + self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN') + + class CustomUserAdmin(UserAdmin): list_filter = ('books_authored', 'books_contributed') @@ -209,3 +269,7 @@ class BookAdmin(admin.ModelAdmin): class BoolTestAdmin(admin.ModelAdmin): list_filter = ('completed',) + +class EmployeeAdmin(admin.ModelAdmin): + list_display = ['name', 'department'] + list_filter = ['department'] \ No newline at end of file From a15d3b58d8c4cbb6137f0458544ec03f3394bb08 Mon Sep 17 00:00:00 2001 From: Michael Newman Date: Sun, 27 May 2012 21:51:03 +0300 Subject: [PATCH 193/225] [1.3.x] Fixed #18135 -- Close connection used for db version checking On MySQL when checking the server version, a new connection could be created but never closed. This could result in open connections on server startup. Backport of 4423757c0c50afbe2470434778c8d5e5b4a70925. --- django/db/backends/mysql/base.py | 15 ++++++++++++--- tests/regressiontests/backends/tests.py | 8 ++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 98233c73291d..55f3c3d71c71 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -334,10 +334,19 @@ def _rollback(self): def get_server_version(self): if not self.server_version: + new_connection = False if not self._valid_connection(): - self.cursor() - m = server_version_re.match(self.connection.get_server_info()) + # Ensure we have a connection with the DB by using a temporary + # cursor + new_connection = True + self.cursor().close() + server_info = self.connection.get_server_info() + if new_connection: + # Make sure we close the connection + self.connection.close() + self.connection = None + m = server_version_re.match(server_info) if not m: - raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info()) + raise Exception('Unable to determine MySQL version from version string %r' % server_info) self.server_version = tuple([int(x) for x in m.groups()]) return self.server_version diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index fc2a973ab196..ee2e15bbdd31 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -56,6 +56,14 @@ def test_client_encoding(self): self.assertEqual(connection.connection.encoding, "UTF-8") self.assertEqual(connection.connection.nencoding, "UTF-8") +class MySQLTests(TestCase): + @unittest.skipUnless(connection.vendor == 'mysql', + "Test valid only for MySQL") + def test_server_version_connections(self): + connection.close() + connection.get_server_version() + self.assertTrue(connection.connection is None) + class DateQuotingTest(TestCase): def test_django_date_trunc(self): From 7ca10b1dac758924f9bbd219880cc17a537c5e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Mon, 28 May 2012 20:41:39 +0300 Subject: [PATCH 194/225] Reverted "[1.3.x] Fixed #18135 -- Close connection used for db version checking" This reverts commit a15d3b58d8c4cbb6137f0458544ec03f3394bb08. Django 1.3.x is in security fixes only state, and this wasn't a security issue. --- django/db/backends/mysql/base.py | 15 +++------------ tests/regressiontests/backends/tests.py | 8 -------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 55f3c3d71c71..98233c73291d 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -334,19 +334,10 @@ def _rollback(self): def get_server_version(self): if not self.server_version: - new_connection = False if not self._valid_connection(): - # Ensure we have a connection with the DB by using a temporary - # cursor - new_connection = True - self.cursor().close() - server_info = self.connection.get_server_info() - if new_connection: - # Make sure we close the connection - self.connection.close() - self.connection = None - m = server_version_re.match(server_info) + self.cursor() + m = server_version_re.match(self.connection.get_server_info()) if not m: - raise Exception('Unable to determine MySQL version from version string %r' % server_info) + raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info()) self.server_version = tuple([int(x) for x in m.groups()]) return self.server_version diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index ee2e15bbdd31..fc2a973ab196 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -56,14 +56,6 @@ def test_client_encoding(self): self.assertEqual(connection.connection.encoding, "UTF-8") self.assertEqual(connection.connection.nencoding, "UTF-8") -class MySQLTests(TestCase): - @unittest.skipUnless(connection.vendor == 'mysql', - "Test valid only for MySQL") - def test_server_version_connections(self): - connection.close() - connection.get_server_version() - self.assertTrue(connection.connection is None) - class DateQuotingTest(TestCase): def test_django_date_trunc(self): From 9ca0ff6268eeff92d0d0ac2c315d4b6a8e229155 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 30 Jul 2012 21:55:23 +0200 Subject: [PATCH 195/225] [1.3.x] Fixed a security issue in image uploading. Disclosure and release forthcoming. Backport of dd16b17099b7d86f27773df048c5014cf439b282 from master. --- django/core/files/images.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/django/core/files/images.py b/django/core/files/images.py index 228a7118c51a..7d7eac65db93 100644 --- a/django/core/files/images.py +++ b/django/core/files/images.py @@ -47,13 +47,18 @@ def get_image_dimensions(file_or_path, close=False): file = open(file_or_path, 'rb') close = True try: + # Most of the time PIL only needs a small chunk to parse the image and + # get the dimensions, but with some TIFF files PIL needs to parse the + # whole file. + chunk_size = 1024 while 1: - data = file.read(1024) + data = file.read(chunk_size) if not data: break p.feed(data) if p.image: return p.image.size + chunk_size = chunk_size*2 return None finally: if close: From b2eb4787a0fff9c9993b78be5c698e85108f3446 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 30 Jul 2012 21:58:22 +0200 Subject: [PATCH 196/225] [1.3.x] Fixed second security issue in image uploading. Disclosure and release forthcoming. Backport of b1d463468694f2e91fde67221b7996e9c52a9720 from master. --- django/forms/fields.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/django/forms/fields.py b/django/forms/fields.py index ae6d8a41bc63..67ab4c5026d6 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -538,20 +538,10 @@ def to_python(self, data): file = StringIO(data['content']) try: - # load() is the only method that can spot a truncated JPEG, - # but it cannot be called sanely after verify() - trial_image = Image.open(file) - trial_image.load() - - # Since we're about to use the file again we have to reset the - # file object if possible. - if hasattr(file, 'reset'): - file.reset() - - # verify() is the only method that can spot a corrupt PNG, - # but it must be called immediately after the constructor - trial_image = Image.open(file) - trial_image.verify() + # load() could spot a truncated JPEG, but it loads the entire + # image in memory, which is a DoS vector. See #3848 and #18520. + # verify() must be called immediately after the constructor. + Image.open(file).verify() except ImportError: # Under PyPy, it is possible to import PIL. However, the underlying # _imaging C module isn't available, so an ImportError will be From 4dea4883e6c50d75f215a6b9bcbd95273f57c72d Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 30 Jul 2012 22:03:46 +0200 Subject: [PATCH 197/225] [1.3.x] Fixed a security issue in http redirects. Disclosure and new release forthcoming. Backport of 4129201c3e0fa057c198bdefcb34686a23b4a93c from master. --- django/http/__init__.py | 21 ++++++++++++--------- tests/regressiontests/httpwrappers/tests.py | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 07e5a4679776..74113b080a17 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -4,7 +4,7 @@ import time from pprint import pformat from urllib import urlencode, quote -from urlparse import urljoin +from urlparse import urljoin, urlparse try: from cStringIO import StringIO except ImportError: @@ -117,6 +117,7 @@ def __init__(self, *args, **kwargs): warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.", PendingDeprecationWarning) +from django.core.exceptions import SuspiciousOperation 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 @@ -635,19 +636,21 @@ def tell(self): raise Exception("This %s instance cannot tell its position" % self.__class__) return sum([len(chunk) for chunk in self._container]) -class HttpResponseRedirect(HttpResponse): - status_code = 302 +class HttpResponseRedirectBase(HttpResponse): + allowed_schemes = ['http', 'https', 'ftp'] def __init__(self, redirect_to): - super(HttpResponseRedirect, self).__init__() + super(HttpResponseRedirectBase, self).__init__() + parsed = urlparse(redirect_to) + if parsed.scheme and parsed.scheme not in self.allowed_schemes: + raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed.scheme) self['Location'] = iri_to_uri(redirect_to) -class HttpResponsePermanentRedirect(HttpResponse): - status_code = 301 +class HttpResponseRedirect(HttpResponseRedirectBase): + status_code = 302 - def __init__(self, redirect_to): - super(HttpResponsePermanentRedirect, self).__init__() - self['Location'] = iri_to_uri(redirect_to) +class HttpResponsePermanentRedirect(HttpResponseRedirectBase): + status_code = 301 class HttpResponseNotModified(HttpResponse): status_code = 304 diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 6aabfe655e0c..9a9b73edeea5 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -1,8 +1,11 @@ import copy import pickle -from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError, - parse_cookie) +from django.core.exceptions import SuspiciousOperation +from django.http import (QueryDict, HttpResponse, HttpResponseRedirect, + HttpResponsePermanentRedirect, + SimpleCookie, BadHeaderError, + parse_cookie) from django.utils import unittest class QueryDictTests(unittest.TestCase): @@ -243,6 +246,18 @@ def test_newlines_in_headers(self): self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', 'test') self.assertRaises(BadHeaderError, r.__setitem__, 'test\nstr', 'test') + def test_unsafe_redirects(self): + bad_urls = [ + 'data:text/html,', + 'mailto:test@example.com', + 'file:///etc/passwd', + ] + for url in bad_urls: + self.assertRaises(SuspiciousOperation, + HttpResponseRedirect, url) + self.assertRaises(SuspiciousOperation, + HttpResponsePermanentRedirect, url) + class CookieTests(unittest.TestCase): def test_encode(self): """ From 0b0c51a09501823433d2051e9b7ac3829d01871e Mon Sep 17 00:00:00 2001 From: James Bennett Date: Mon, 30 Jul 2012 15:54:15 -0500 Subject: [PATCH 198/225] [1.3.x] Bump version numbers for security releases. --- django/__init__.py | 2 +- docs/conf.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index 287452848983..a39e0e70fd36 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 1, 'final', 0) +VERSION = (1, 3, 2, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/docs/conf.py b/docs/conf.py index 7165b92f73d3..91eeef30409f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ # The short X.Y version. version = '1.3' # The full version, including alpha/beta/rc tags. -release = '1.3.1' +release = '1.3.2' # The next version to be released django_next_version = '1.4' diff --git a/setup.py b/setup.py index 1d8732fa242b..dc361e22d99b 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'http://media.djangoproject.com/releases/1.3/Django-1.3.1.tar.gz', + download_url = 'http://media.djangoproject.com/releases/1.3/Django-1.3.2.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From e2ac91735f8acb9d1b792f510ed38e4afcb744b5 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Mon, 30 Jul 2012 16:00:55 -0500 Subject: [PATCH 199/225] [1.3.x] Use correct download URL. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index dc361e22d99b..e14e65ad72f3 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'http://media.djangoproject.com/releases/1.3/Django-1.3.2.tar.gz', + download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.2.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From d0d5dc6cd76f01c8a71b677357ad2f702cb54416 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 1 Aug 2012 10:56:35 +0200 Subject: [PATCH 200/225] [1.3.x] Fixed #18692 -- Restored python 2.4 compatibility. Thanks to chipx86 for the report. --- django/http/__init__.py | 4 ++-- django/http/utils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 74113b080a17..2dfe12ee4bee 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -642,8 +642,8 @@ class HttpResponseRedirectBase(HttpResponse): def __init__(self, redirect_to): super(HttpResponseRedirectBase, self).__init__() parsed = urlparse(redirect_to) - if parsed.scheme and parsed.scheme not in self.allowed_schemes: - raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed.scheme) + if parsed[0] and parsed[0] not in self.allowed_schemes: + raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed[0]) self['Location'] = iri_to_uri(redirect_to) class HttpResponseRedirect(HttpResponseRedirectBase): diff --git a/django/http/utils.py b/django/http/utils.py index 01808648ba41..3fdebf0474ff 100644 --- a/django/http/utils.py +++ b/django/http/utils.py @@ -76,7 +76,7 @@ def fix_IE_for_vary(request, response): # The first part of the Content-Type field will be the MIME type, # everything after ';', such as character-set, can be ignored. - mime_type = response.get('Content-Type', '').partition(';')[0] + mime_type = response.get('Content-Type', '').split(';', 1)[0] if mime_type not in safe_mime_types: try: del response['Vary'] From c718b4a0369bb2b7333d862037d30662d5f4c19d Mon Sep 17 00:00:00 2001 From: James Bennett Date: Wed, 1 Aug 2012 15:06:44 -0500 Subject: [PATCH 201/225] [1.3.x] Bump version numbers for bugfix release. --- django/__init__.py | 2 +- docs/conf.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index a39e0e70fd36..6ff1588a870d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 2, 'final', 0) +VERSION = (1, 3, 3, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/docs/conf.py b/docs/conf.py index 91eeef30409f..7cc33e8e1513 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ # The short X.Y version. version = '1.3' # The full version, including alpha/beta/rc tags. -release = '1.3.2' +release = '1.3.3' # The next version to be released django_next_version = '1.4' diff --git a/setup.py b/setup.py index e14e65ad72f3..7ec6a03d0aa8 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.2.tar.gz', + download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.3.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From b45c377f8f488955e0c7069cad3f3dd21910b071 Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Wed, 17 Oct 2012 14:43:08 -0700 Subject: [PATCH 202/225] Fixed a security issue related to password resets Full disclosure and new release are forthcoming backport from master --- django/contrib/auth/tests/urls.py | 1 + django/contrib/auth/tests/views.py | 39 ++++++++++++++++++++++++++++++ django/contrib/auth/views.py | 2 +- django/http/__init__.py | 5 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/django/contrib/auth/tests/urls.py b/django/contrib/auth/tests/urls.py index 3d76a4e44394..c01964f83324 100644 --- a/django/contrib/auth/tests/urls.py +++ b/django/contrib/auth/tests/urls.py @@ -19,6 +19,7 @@ def remote_user_auth_view(request): (r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')), (r'^remote_user/$', remote_user_auth_view), (r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')), + (r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)), (r'^login_required/$', login_required(password_reset)), (r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')), ) diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index b03489c7d205..046d00da0be9 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -9,6 +9,7 @@ from django.contrib.auth.models import User from django.test import TestCase from django.core import mail +from django.core.exceptions import SuspiciousOperation from django.core.urlresolvers import reverse from django.http import QueryDict @@ -69,6 +70,44 @@ def test_email_found_custom_from(self): self.assertEqual(len(mail.outbox), 1) self.assertEqual("staffmember@example.com", mail.outbox[0].from_email) + def test_admin_reset(self): + "If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override." + response = self.client.post('/admin_password_reset/', + {'email': 'staffmember@example.com'}, + HTTP_HOST='adminsite.com' + ) + self.assertEqual(response.status_code, 302) + self.assertEqual(len(mail.outbox), 1) + self.assertTrue("http://adminsite.com" in mail.outbox[0].body) + self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) + + def test_poisoned_http_host(self): + "Poisoned HTTP_HOST headers can't be used for reset emails" + # This attack is based on the way browsers handle URLs. The colon + # should be used to separate the port, but if the URL contains an @, + # the colon is interpreted as part of a username for login purposes, + # making 'evil.com' the request domain. Since HTTP_HOST is used to + # produce a meaningful reset URL, we need to be certain that the + # HTTP_HOST header isn't poisoned. This is done as a check when get_host() + # is invoked, but we check here as a practical consequence. + def test_host_poisoning(): + self.client.post('/password_reset/', + {'email': 'staffmember@example.com'}, + HTTP_HOST='www.example:dr.frankenstein@evil.tld' + ) + self.assertRaises(SuspiciousOperation, test_host_poisoning) + self.assertEqual(len(mail.outbox), 0) + + def test_poisoned_http_host_admin_site(self): + "Poisoned HTTP_HOST headers can't be used for reset emails on admin views" + def test_host_poisoning(): + self.client.post('/admin_password_reset/', + {'email': 'staffmember@example.com'}, + HTTP_HOST='www.example:dr.frankenstein@evil.tld' + ) + self.assertRaises(SuspiciousOperation, test_host_poisoning) + self.assertEqual(len(mail.outbox), 0) + def _test_confirm_start(self): # Start by creating the email response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index eba83a269e52..727e9162bd5c 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -151,7 +151,7 @@ def password_reset(request, is_admin_site=False, 'request': request, } if is_admin_site: - opts = dict(opts, domain_override=request.META['HTTP_HOST']) + opts = dict(opts, domain_override=request.get_host()) form.save(**opts) return HttpResponseRedirect(post_reset_redirect) else: diff --git a/django/http/__init__.py b/django/http/__init__.py index 2dfe12ee4bee..dddd9a89c472 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -165,6 +165,11 @@ def get_host(self): server_port = str(self.META['SERVER_PORT']) if server_port != (self.is_secure() and '443' or '80'): host = '%s:%s' % (host, server_port) + + # Disallow potentially poisoned hostnames. + if set(';/?@&=+$,').intersection(host): + raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host) + return host def get_full_path(self): From 25d23d9846f945a7a0bbd562302e6ccb9ca5a5c1 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Wed, 17 Oct 2012 17:25:52 -0500 Subject: [PATCH 203/225] [1.3.x] Bump version numbers for security release. --- django/__init__.py | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index 6ff1588a870d..80dc39e5e0ea 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 3, 'final', 0) +VERSION = (1, 3, 4, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/docs/conf.py b/docs/conf.py index 7cc33e8e1513..5141dde48581 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '1.3' +version = '1.3.4' # The full version, including alpha/beta/rc tags. -release = '1.3.3' +release = '1.3.4' # The next version to be released django_next_version = '1.4' diff --git a/setup.py b/setup.py index 7ec6a03d0aa8..11de0c185b98 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.3.tar.gz', + download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.4.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From 6383d2358c1077b16b13eb6e6975d7a200ed7285 Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Thu, 18 Oct 2012 11:21:54 -0700 Subject: [PATCH 204/225] Added missed poisoned host header test material --- tests/regressiontests/requests/tests.py | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index cd488e2fed7d..19713b6e265d 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -4,6 +4,7 @@ from django.conf import settings from django.core.handlers.modpython import ModPythonRequest +from django.core.exceptions import SuspiciousOperation from django.core.handlers.wsgi import WSGIRequest, LimitedStream from django.http import HttpRequest, HttpResponse, parse_cookie from django.utils import unittest @@ -101,6 +102,39 @@ def test_http_get_host(self): } self.assertEqual(request.get_host(), 'internal.com:8042') + # Poisoned host headers are rejected as suspicious + legit_hosts = [ + 'example.com', + 'example.com:80', + '12.34.56.78', + '12.34.56.78:443', + '[2001:19f0:feee::dead:beef:cafe]', + '[2001:19f0:feee::dead:beef:cafe]:8080', + ] + + poisoned_hosts = [ + 'example.com@evil.tld', + 'example.com:dr.frankenstein@evil.tld', + 'example.com:someone@somestie.com:80', + 'example.com:80/badpath' + ] + + for host in legit_hosts: + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + + for host in poisoned_hosts: + def test_host_poisoning(): + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + self.assertRaises(SuspiciousOperation, test_host_poisoning) + finally: settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST @@ -145,6 +179,39 @@ def test_http_get_host_with_x_forwarded_host(self): } self.assertEqual(request.get_host(), 'internal.com:8042') + # Poisoned host headers are rejected as suspicious + legit_hosts = [ + 'example.com', + 'example.com:80', + '12.34.56.78', + '12.34.56.78:443', + '[2001:19f0:feee::dead:beef:cafe]', + '[2001:19f0:feee::dead:beef:cafe]:8080', + ] + + poisoned_hosts = [ + 'example.com@evil.tld', + 'example.com:dr.frankenstein@evil.tld', + 'example.com:dr.frankenstein@evil.tld:80', + 'example.com:80/badpath' + ] + + for host in legit_hosts: + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + + for host in poisoned_hosts: + def test_host_poisoning(): + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() + self.assertRaises(SuspiciousOperation, test_host_poisoning) + finally: settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST From 1515eb46daa0897ba5ad5f0a2db8969255f1b343 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sat, 17 Nov 2012 22:00:53 +0100 Subject: [PATCH 205/225] [1.3.X] Fixed #18856 -- Ensured that redirects can't be poisoned by malicious users. --- django/contrib/auth/views.py | 50 +++++------ django/contrib/comments/views/comments.py | 11 +-- django/contrib/comments/views/moderation.py | 7 +- django/contrib/comments/views/utils.py | 10 ++- django/utils/http.py | 12 +++ django/views/i18n.py | 12 +-- .../comment_tests/tests/comment_view_tests.py | 7 ++ .../tests/moderation_view_tests.py | 89 ++++++++++++++++++- tests/regressiontests/views/tests/i18n.py | 17 +++- 9 files changed, 163 insertions(+), 52 deletions(-) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 727e9162bd5c..c5dcda91f434 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -5,7 +5,7 @@ from django.http import HttpResponseRedirect, QueryDict from django.shortcuts import render_to_response from django.template import RequestContext -from django.utils.http import base36_to_int +from django.utils.http import base36_to_int, is_safe_url from django.utils.translation import ugettext as _ from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect @@ -33,18 +33,11 @@ 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] - - # Use default setting if redirect_to is empty - if not redirect_to: - redirect_to = settings.LOGIN_REDIRECT_URL - - # Security check -- don't allow redirection to a different - # host. - elif netloc and netloc != request.get_host(): + # Ensure the user-originating redirection url is safe. + if not is_safe_url(url=redirect_to, host=request.get_host()): redirect_to = settings.LOGIN_REDIRECT_URL - # Okay, security checks complete. Log the user in. + # Okay, security check complete. Log the user in. auth_login(request, form.get_user()) if request.session.test_cookie_worked(): @@ -76,26 +69,27 @@ def logout(request, next_page=None, Logs out the user and displays 'You are logged out' message. """ auth_logout(request) - redirect_to = request.REQUEST.get(redirect_field_name, '') - if redirect_to: - netloc = urlparse.urlparse(redirect_to)[1] + + if redirect_field_name in request.REQUEST: + next_page = request.REQUEST[redirect_field_name] # Security check -- don't allow redirection to a different host. - if not (netloc and netloc != request.get_host()): - return HttpResponseRedirect(redirect_to) + if not is_safe_url(url=next_page, host=request.get_host()): + next_page = request.path - if next_page is None: - current_site = get_current_site(request) - context = { - 'site': current_site, - 'site_name': current_site.name, - 'title': _('Logged out') - } - context.update(extra_context or {}) - return render_to_response(template_name, context, - context_instance=RequestContext(request, current_app=current_app)) - else: + if next_page: # Redirect to this page until the session has been cleared. - return HttpResponseRedirect(next_page or request.path) + return HttpResponseRedirect(next_page) + + current_site = get_current_site(request) + context = { + 'site': current_site, + 'site_name': current_site.name, + 'title': _('Logged out') + } + if extra_context is not None: + context.update(extra_context) + return render_to_response(template_name, context, + context_instance=RequestContext(request, current_app=current_app)) def logout_then_login(request, login_url=None, current_app=None, extra_context=None): """ diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index c2b553fe0db9..37ec9673ce7c 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -40,9 +40,6 @@ def post_comment(request, next=None, using=None): if not data.get('email', ''): data["email"] = request.user.email - # Check to see if the POST data overrides the view's next argument. - next = data.get("next", next) - # Look up the object we're trying to comment about ctype = data.get("content_type") object_pk = data.get("object_pk") @@ -94,9 +91,9 @@ def post_comment(request, next=None, using=None): ] return render_to_response( template_list, { - "comment" : form.data.get("comment", ""), - "form" : form, - "next": next, + "comment": form.data.get("comment", ""), + "form": form, + "next": data.get("next", next), }, RequestContext(request, {}) ) @@ -127,7 +124,7 @@ def post_comment(request, next=None, using=None): request = request ) - return next_redirect(data, next, comment_done, c=comment._get_pk_val()) + return next_redirect(request, next, comment_done, c=comment._get_pk_val()) comment_done = confirmation_view( template = "comments/posted.html", diff --git a/django/contrib/comments/views/moderation.py b/django/contrib/comments/views/moderation.py index 73304ba4164c..20ebe5ef7882 100644 --- a/django/contrib/comments/views/moderation.py +++ b/django/contrib/comments/views/moderation.py @@ -7,6 +7,7 @@ from django.contrib.comments import signals from django.views.decorators.csrf import csrf_protect + @csrf_protect @login_required def flag(request, comment_id, next=None): @@ -23,7 +24,7 @@ def flag(request, comment_id, next=None): # Flag on POST if request.method == 'POST': perform_flag(request, comment) - return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk) + return next_redirect(request, next, flag_done, c=comment.pk) # Render a form on GET else: @@ -50,7 +51,7 @@ def delete(request, comment_id, next=None): if request.method == 'POST': # Flag the comment as deleted instead of actually deleting it. perform_delete(request, comment) - return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk) + return next_redirect(request, next, delete_done, c=comment.pk) # Render a form on GET else: @@ -77,7 +78,7 @@ def approve(request, comment_id, next=None): if request.method == 'POST': # Flag the comment as approved. perform_approve(request, comment) - return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk) + return next_redirect(request, next, approve_done, c=comment.pk) # Render a form on GET else: diff --git a/django/contrib/comments/views/utils.py b/django/contrib/comments/views/utils.py index cc985e52d203..94ee2470d526 100644 --- a/django/contrib/comments/views/utils.py +++ b/django/contrib/comments/views/utils.py @@ -4,14 +4,15 @@ import urllib import textwrap -from django.http import HttpResponseRedirect from django.core import urlresolvers +from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext from django.core.exceptions import ObjectDoesNotExist from django.contrib import comments +from django.utils.http import is_safe_url -def next_redirect(data, default, default_view, **get_kwargs): +def next_redirect(request, default, default_view, **get_kwargs): """ Handle the "where should I go next?" part of comment views. @@ -21,9 +22,10 @@ def next_redirect(data, default, default_view, **get_kwargs): Returns an ``HttpResponseRedirect``. """ - next = data.get("next", default) - if next is None: + next = request.POST.get('next', default) + if not is_safe_url(url=next, host=request.get_host()): next = urlresolvers.reverse(default_view) + if get_kwargs: if '#' in next: tmp = next.rsplit('#', 1) diff --git a/django/utils/http.py b/django/utils/http.py index c93a338c3081..5b4dbc5aa5d0 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -204,3 +204,15 @@ def same_origin(url1, url2): """ p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2) return p1[0:2] == p2[0:2] + +def is_safe_url(url, host=None): + """ + Return ``True`` if the url is a safe redirection (i.e. it doesn't point to + a different host). + + Always returns ``False`` on an empty url. + """ + if not url: + return False + netloc = urlparse.urlparse(url)[1] + return not netloc or netloc == host diff --git a/django/views/i18n.py b/django/views/i18n.py index 140dc543e3d5..e288d227f749 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -8,6 +8,8 @@ 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.http import is_safe_url + def set_language(request): """ @@ -20,11 +22,11 @@ def set_language(request): redirect to the page in the request (the 'next' parameter) without changing any state. """ - next = request.REQUEST.get('next', None) - if not next: - next = request.META.get('HTTP_REFERER', None) - if not next: - next = '/' + next = request.REQUEST.get('next') + if not is_safe_url(url=next, host=request.get_host()): + next = request.META.get('HTTP_REFERER') + if not is_safe_url(url=next, host=request.get_host()): + next = '/' response = http.HttpResponseRedirect(next) if request.method == 'POST': lang_code = request.POST.get('language', None) diff --git a/tests/regressiontests/comment_tests/tests/comment_view_tests.py b/tests/regressiontests/comment_tests/tests/comment_view_tests.py index e3f3bbeff74a..25c250612fd7 100644 --- a/tests/regressiontests/comment_tests/tests/comment_view_tests.py +++ b/tests/regressiontests/comment_tests/tests/comment_view_tests.py @@ -217,6 +217,13 @@ def testCommentNext(self): match = re.search(r"^http://testserver/somewhere/else/\?c=\d+$", location) self.assertTrue(match != None, "Unexpected redirect location: %s" % location) + data["next"] = "http://badserver/somewhere/else/" + data["comment"] = "This is another comment with an unsafe next url" + response = self.client.post("/post/", data) + location = response["Location"] + match = post_redirect_re.match(location) + self.assertTrue(match != None, "Unsafe redirection to: %s" % location) + def testCommentDoneView(self): a = Article.objects.get(pk=1) data = self.getValidData(a) diff --git a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py index c9be06a3403c..59cd34a818a8 100644 --- a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py +++ b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py @@ -27,6 +27,30 @@ def testFlagPost(self): self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1) return c + def testFlagPostNext(self): + """ + POST the flag view, explicitly providing a next url. + """ + comments = self.createSomeComments() + pk = comments[0].pk + self.client.login(username="normaluser", password="normaluser") + response = self.client.post("/flag/%d/" % pk, {'next': "/go/here/"}) + self.assertEqual(response["Location"], + "http://testserver/go/here/?c=1") + + def testFlagPostUnsafeNext(self): + """ + POSTing to the flag view with an unsafe next url will ignore the + provided url when redirecting. + """ + comments = self.createSomeComments() + pk = comments[0].pk + self.client.login(username="normaluser", password="normaluser") + response = self.client.post("/flag/%d/" % pk, + {'next': "http://elsewhere/bad"}) + self.assertEqual(response["Location"], + "http://testserver/flagged/?c=%d" % pk) + def testFlagPostTwice(self): """Users don't get to flag comments more than once.""" c = self.testFlagPost() @@ -46,7 +70,7 @@ def testFlagAnon(self): def testFlaggedView(self): comments = self.createSomeComments() pk = comments[0].pk - response = self.client.get("/flagged/", data={"c":pk}) + response = self.client.get("/flagged/", data={"c": pk}) self.assertTemplateUsed(response, "comments/flagged.html") def testFlagSignals(self): @@ -98,6 +122,33 @@ def testDeletePost(self): self.assertTrue(c.is_removed) self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_DELETION, user__username="normaluser").count(), 1) + def testDeletePostNext(self): + """ + POSTing the delete view will redirect to an explicitly provided a next + url. + """ + comments = self.createSomeComments() + pk = comments[0].pk + makeModerator("normaluser") + self.client.login(username="normaluser", password="normaluser") + response = self.client.post("/delete/%d/" % pk, {'next': "/go/here/"}) + self.assertEqual(response["Location"], + "http://testserver/go/here/?c=1") + + def testDeletePostUnsafeNext(self): + """ + POSTing to the delete view with an unsafe next url will ignore the + provided url when redirecting. + """ + comments = self.createSomeComments() + pk = comments[0].pk + makeModerator("normaluser") + self.client.login(username="normaluser", password="normaluser") + response = self.client.post("/delete/%d/" % pk, + {'next': "http://elsewhere/bad"}) + self.assertEqual(response["Location"], + "http://testserver/deleted/?c=%d" % pk) + def testDeleteSignals(self): def receive(sender, **kwargs): received_signals.append(kwargs.get('signal')) @@ -113,13 +164,13 @@ def receive(sender, **kwargs): def testDeletedView(self): comments = self.createSomeComments() pk = comments[0].pk - response = self.client.get("/deleted/", data={"c":pk}) + response = self.client.get("/deleted/", data={"c": pk}) self.assertTemplateUsed(response, "comments/deleted.html") class ApproveViewTests(CommentTestCase): def testApprovePermissions(self): - """The delete view should only be accessible to 'moderators'""" + """The approve view should only be accessible to 'moderators'""" comments = self.createSomeComments() pk = comments[0].pk self.client.login(username="normaluser", password="normaluser") @@ -131,7 +182,7 @@ def testApprovePermissions(self): self.assertEqual(response.status_code, 200) def testApprovePost(self): - """POSTing the delete view should mark the comment as removed""" + """POSTing the approve view should mark the comment as removed""" c1, c2, c3, c4 = self.createSomeComments() c1.is_public = False; c1.save() @@ -143,6 +194,36 @@ def testApprovePost(self): self.assertTrue(c.is_public) self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_APPROVAL, user__username="normaluser").count(), 1) + def testApprovePostNext(self): + """ + POSTing the approve view will redirect to an explicitly provided a next + url. + """ + c1, c2, c3, c4 = self.createSomeComments() + c1.is_public = False; c1.save() + + makeModerator("normaluser") + self.client.login(username="normaluser", password="normaluser") + response = self.client.post("/approve/%d/" % c1.pk, + {'next': "/go/here/"}) + self.assertEqual(response["Location"], + "http://testserver/go/here/?c=1") + + def testApprovePostUnsafeNext(self): + """ + POSTing to the approve view with an unsafe next url will ignore the + provided url when redirecting. + """ + c1, c2, c3, c4 = self.createSomeComments() + c1.is_public = False; c1.save() + + makeModerator("normaluser") + self.client.login(username="normaluser", password="normaluser") + response = self.client.post("/approve/%d/" % c1.pk, + {'next': "http://elsewhere/bad"}) + self.assertEqual(response["Location"], + "http://testserver/approved/?c=%d" % c1.pk) + def testApproveSignals(self): def receive(sender, **kwargs): received_signals.append(kwargs.get('signal')) diff --git a/tests/regressiontests/views/tests/i18n.py b/tests/regressiontests/views/tests/i18n.py index 97a0a5b5112e..22d73469204c 100644 --- a/tests/regressiontests/views/tests/i18n.py +++ b/tests/regressiontests/views/tests/i18n.py @@ -13,13 +13,28 @@ class I18NTests(TestCase): """ Tests django views in django/views/i18n.py """ def test_setlang(self): - """The set_language view can be used to change the session language""" + """ + The set_language view can be used to change the session language. + + The user is redirected to the 'next' argument if provided. + """ for lang_code, lang_name in settings.LANGUAGES: post_data = dict(language=lang_code, next='/views/') response = self.client.post('/views/i18n/setlang/', data=post_data) self.assertRedirects(response, 'http://testserver/views/') self.assertEqual(self.client.session['django_language'], lang_code) + def test_setlang_unsafe_next(self): + """ + The set_language view only redirects to the 'next' argument if it is + "safe". + """ + lang_code, lang_name = settings.LANGUAGES[0] + post_data = dict(language=lang_code, next='//unsafe/redirection/') + response = self.client.post('/views/i18n/setlang/', data=post_data) + self.assertEqual(response['Location'], 'http://testserver/') + self.assertEqual(self.client.session['django_language'], lang_code) + def test_jsi18n(self): """The javascript_catalog can be deployed with language settings""" for lang_code in ['es', 'fr', 'ru']: From 2da4ace0bc1bc1d79bf43b368cb857f6f0cd6b1b Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Tue, 27 Nov 2012 22:27:14 +0100 Subject: [PATCH 206/225] [1.3.X] Fixed a security issue in get_host. Full disclosure and new release forthcoming. --- django/http/__init__.py | 4 +++- tests/regressiontests/requests/tests.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index dddd9a89c472..a80750b57c65 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -129,6 +129,8 @@ def __init__(self, *args, **kwargs): RESERVED_CHARS="!*'();:@&=+$,/?%#[]" absolute_http_url_re = re.compile(r"^https?://", re.I) +host_validation_re = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9:]+\])(:\d+)?$") + class Http404(Exception): pass @@ -167,7 +169,7 @@ def get_host(self): host = '%s:%s' % (host, server_port) # Disallow potentially poisoned hostnames. - if set(';/?@&=+$,').intersection(host): + if not host_validation_re.match(host.lower()): raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host) return host diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index 19713b6e265d..bbd2280c432b 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import time from datetime import datetime, timedelta from StringIO import StringIO @@ -110,13 +111,15 @@ def test_http_get_host(self): '12.34.56.78:443', '[2001:19f0:feee::dead:beef:cafe]', '[2001:19f0:feee::dead:beef:cafe]:8080', + 'xn--4ca9at.com', # Punnycode for öäü.com ] poisoned_hosts = [ 'example.com@evil.tld', 'example.com:dr.frankenstein@evil.tld', - 'example.com:someone@somestie.com:80', - 'example.com:80/badpath' + 'example.com:dr.frankenstein@evil.tld:80', + 'example.com:80/badpath', + 'example.com: recovermypassword.com', ] for host in legit_hosts: @@ -187,13 +190,15 @@ def test_http_get_host_with_x_forwarded_host(self): '12.34.56.78:443', '[2001:19f0:feee::dead:beef:cafe]', '[2001:19f0:feee::dead:beef:cafe]:8080', + 'xn--4ca9at.com', # Punnycode for öäü.com ] poisoned_hosts = [ 'example.com@evil.tld', 'example.com:dr.frankenstein@evil.tld', 'example.com:dr.frankenstein@evil.tld:80', - 'example.com:80/badpath' + 'example.com:80/badpath', + 'example.com: recovermypassword.com', ] for host in legit_hosts: From 59a3e26425cfabdb39295085ca6f3d5922bf1ec6 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Mon, 10 Dec 2012 15:38:03 -0600 Subject: [PATCH 207/225] [1.3.x] Bump version numbers for security release. --- django/__init__.py | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index 80dc39e5e0ea..d3869d6fda9d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 4, 'final', 0) +VERSION = (1, 3, 5, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/docs/conf.py b/docs/conf.py index 5141dde48581..df09ac5e0e41 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '1.3.4' +version = '1.3.5' # The full version, including alpha/beta/rc tags. -release = '1.3.4' +release = '1.3.5' # The next version to be released django_next_version = '1.4' diff --git a/setup.py b/setup.py index 11de0c185b98..54a6ce57f323 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.4.tar.gz', + download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.5.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From 6e70f67470d6d4baf87728702886f89ac075b73c Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 10 Dec 2012 23:34:51 +0100 Subject: [PATCH 208/225] [1.3.X] Fixed a test failure in the comment tests. Backport of 1eb0da1c5ba3096f218d1df13d02a2b8e1ac7a36 from master. --- .../comment_tests/tests/moderation_view_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py index 59cd34a818a8..320924d61094 100644 --- a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py +++ b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py @@ -36,7 +36,7 @@ def testFlagPostNext(self): self.client.login(username="normaluser", password="normaluser") response = self.client.post("/flag/%d/" % pk, {'next': "/go/here/"}) self.assertEqual(response["Location"], - "http://testserver/go/here/?c=1") + "http://testserver/go/here/?c=%d" % pk) def testFlagPostUnsafeNext(self): """ @@ -133,7 +133,7 @@ def testDeletePostNext(self): self.client.login(username="normaluser", password="normaluser") response = self.client.post("/delete/%d/" % pk, {'next': "/go/here/"}) self.assertEqual(response["Location"], - "http://testserver/go/here/?c=1") + "http://testserver/go/here/?c=%d" % pk) def testDeletePostUnsafeNext(self): """ @@ -207,7 +207,7 @@ def testApprovePostNext(self): response = self.client.post("/approve/%d/" % c1.pk, {'next': "/go/here/"}) self.assertEqual(response["Location"], - "http://testserver/go/here/?c=1") + "http://testserver/go/here/?c=%d" % c1.pk) def testApprovePostUnsafeNext(self): """ From 27cd872e6e36a81d0bb6f5b8765a1705fecfc253 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 9 Feb 2013 12:25:52 -0700 Subject: [PATCH 209/225] [1.3.x] Added ALLOWED_HOSTS setting for HTTP host header validation. This is a security fix; disclosure and advisory coming shortly. --- django/conf/global_settings.py | 4 ++ django/conf/project_template/settings.py | 4 ++ django/http/__init__.py | 54 ++++++++++++-- django/test/utils.py | 6 ++ docs/ref/settings.txt | 36 ++++++++++ docs/releases/1.3.6.txt | 31 ++++++++ docs/releases/index.txt | 1 + tests/regressiontests/requests/tests.py | 91 ++++++++++++++++-------- 8 files changed, 191 insertions(+), 36 deletions(-) create mode 100644 docs/releases/1.3.6.txt diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 5ef6886ac50d..ec9ca437dac1 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -29,6 +29,10 @@ # * Receive x-headers INTERNAL_IPS = () +# Hosts/domain names that are valid for this site. +# "*" matches anything, ".example.com" matches example.com and all subdomains +ALLOWED_HOSTS = ['*'] + # Local time zone for this installation. All choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all # systems may support all possibilities). diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index 9d05ac2c8fe4..839039de9d58 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -20,6 +20,10 @@ } } +# Hosts/domain names that are valid for this site; required if DEBUG is False +# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts +ALLOWED_HOSTS = [] + # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. diff --git a/django/http/__init__.py b/django/http/__init__.py index a80750b57c65..b3af71870895 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -168,11 +168,15 @@ def get_host(self): if server_port != (self.is_secure() and '443' or '80'): host = '%s:%s' % (host, server_port) - # Disallow potentially poisoned hostnames. - if not host_validation_re.match(host.lower()): - raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host) - - return host + if settings.DEBUG: + allowed_hosts = ['*'] + else: + allowed_hosts = settings.ALLOWED_HOSTS + if validate_host(host, allowed_hosts): + return host + else: + raise SuspiciousOperation( + "Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): %s" % host) def get_full_path(self): # RFC 3986 requires query string arguments to be in the ASCII range. @@ -704,3 +708,43 @@ def str_to_unicode(s, encoding): else: return s +def validate_host(host, allowed_hosts): + """ + Validate the given host header value for this site. + + Check that the host looks valid and matches a host or host pattern in the + given list of ``allowed_hosts``. Any pattern beginning with a period + matches a domain and all its subdomains (e.g. ``.example.com`` matches + ``example.com`` and any subdomain), ``*`` matches anything, and anything + else must match exactly. + + Return ``True`` for a valid host, ``False`` otherwise. + + """ + # All validation is case-insensitive + host = host.lower() + + # Basic sanity check + if not host_validation_re.match(host): + return False + + # Validate only the domain part. + if host[-1] == ']': + # It's an IPv6 address without a port. + domain = host + else: + domain = host.rsplit(':', 1)[0] + + for pattern in allowed_hosts: + pattern = pattern.lower() + match = ( + pattern == '*' or + pattern.startswith('.') and ( + domain.endswith(pattern) or domain == pattern[1:] + ) or + pattern == domain + ) + if match: + return True + + return False diff --git a/django/test/utils.py b/django/test/utils.py index 6a41c1b70f5b..7be169962caa 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -76,6 +76,9 @@ def setup_test_environment(): mail.original_email_backend = settings.EMAIL_BACKEND settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' + settings._original_allowed_hosts = settings.ALLOWED_HOSTS + settings.ALLOWED_HOSTS = ['*'] + mail.outbox = [] deactivate() @@ -97,6 +100,9 @@ def teardown_test_environment(): settings.EMAIL_BACKEND = mail.original_email_backend del mail.original_email_backend + settings.ALLOWED_HOSTS = settings._original_allowed_hosts + del settings._original_allowed_hosts + del mail.outbox diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 24ccb5f949d7..68869f166c72 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -82,6 +82,42 @@ of (Full name, e-mail address). Example:: Note that Django will e-mail *all* of these people whenever an error happens. See :doc:`/howto/error-reporting` for more information. +.. setting:: ALLOWED_HOSTS + +ALLOWED_HOSTS +------------- + +Default: ``['*']`` + +A list of strings representing the host/domain names that this Django site can +serve. This is a security measure to prevent an attacker from poisoning caches +and password reset emails with links to malicious hosts by submitting requests +with a fake HTTP ``Host`` header, which is possible even under many +seemingly-safe webserver configurations. + +Values in this list can be fully qualified names (e.g. ``'www.example.com'``), +in which case they will be matched against the request's ``Host`` header +exactly (case-insensitive, not including port). A value beginning with a period +can be used as a subdomain wildcard: ``'.example.com'`` will match +``example.com``, ``www.example.com``, and any other subdomain of +``example.com``. A value of ``'*'`` will match anything; in this case you are +responsible to provide your own validation of the ``Host`` header (perhaps in a +middleware; if so this middleware must be listed first in +:setting:`MIDDLEWARE_CLASSES`). + +If the ``Host`` header (or ``X-Forwarded-Host`` if +:setting:`USE_X_FORWARDED_HOST` is enabled) does not match any value in this +list, the :meth:`django.http.HttpRequest.get_host()` method will raise +:exc:`~django.core.exceptions.SuspiciousOperation`. + +When :setting:`DEBUG` is ``True`` or when running tests, host validation is +disabled; any host will be accepted. Thus it's usually only necessary to set it +in production. + +This validation only applies via :meth:`~django.http.HttpRequest.get_host()`; +if your code accesses the ``Host`` header directly from ``request.META`` you +are bypassing this security protection. + .. setting:: ALLOWED_INCLUDE_ROOTS ALLOWED_INCLUDE_ROOTS diff --git a/docs/releases/1.3.6.txt b/docs/releases/1.3.6.txt new file mode 100644 index 000000000000..c1e4bed2c70b --- /dev/null +++ b/docs/releases/1.3.6.txt @@ -0,0 +1,31 @@ +========================== +Django 1.3.6 release notes +========================== + +*February 19, 2013* + +This is the sixth bugfix/security release in the Django 1.3 series. + +Host header poisoning +--------------------- + +Some parts of Django -- independent of end-user-written applications -- make +use of full URLs, including domain name, which are generated from the HTTP Host +header. Django's documentation has for some time contained notes advising users +on how to configure webservers to ensure that only valid Host headers can reach +the Django application. However, it has been reported to us that even with the +recommended webserver configurations there are still techniques available for +tricking many common webservers into supplying the application with an +incorrect and possibly malicious Host header. + +For this reason, Django 1.3.6 adds a new setting, ``ALLOWED_HOSTS``, which +should contain an explicit list of valid host/domain names for this site. A +request with a Host header not matching an entry in this list will raise +``SuspiciousOperation`` if ``request.get_host()`` is called. For full details +see the documentation for the :setting:`ALLOWED_HOSTS` setting. + +The default value for this setting in Django 1.3.6 is `['*']` (matching any +host), for backwards-compatibility, but we strongly encourage all sites to set +a more restrictive value. + +This host validation is disabled when ``DEBUG`` is ``True`` or when running tests. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 40fe5b0e02b4..3f14936fea7d 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -19,6 +19,7 @@ Final releases .. toctree:: :maxdepth: 1 + 1.3.6 1.3.1 1.3 diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index bbd2280c432b..72c9bc256b5e 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -63,17 +63,23 @@ def test_httprequest_location(self): 'http://www.example.com/path/with:colons') def test_http_get_host(self): - old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST + _old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST + _old_ALLOWED_HOSTS = settings.ALLOWED_HOSTS try: settings.USE_X_FORWARDED_HOST = False + settings.ALLOWED_HOSTS = [ + 'forward.com', 'example.com', 'internal.com', '12.34.56.78', + '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com', + '.multitenant.com', 'INSENSITIVE.com', + ] # Check if X_FORWARDED_HOST is provided. request = HttpRequest() request.META = { - u'HTTP_X_FORWARDED_HOST': u'forward.com', - u'HTTP_HOST': u'example.com', - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 80, + 'HTTP_X_FORWARDED_HOST': 'forward.com', + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, } # X_FORWARDED_HOST is ignored. self.assertEqual(request.get_host(), 'example.com') @@ -81,25 +87,25 @@ def test_http_get_host(self): # Check if X_FORWARDED_HOST isn't provided. request = HttpRequest() request.META = { - u'HTTP_HOST': u'example.com', - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 80, + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, } self.assertEqual(request.get_host(), 'example.com') # Check if HTTP_HOST isn't provided. request = HttpRequest() request.META = { - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 80, + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, } self.assertEqual(request.get_host(), 'internal.com') # Check if HTTP_HOST isn't provided, and we're on a nonstandard port request = HttpRequest() request.META = { - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 8042, + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 8042, } self.assertEqual(request.get_host(), 'internal.com:8042') @@ -112,6 +118,9 @@ def test_http_get_host(self): '[2001:19f0:feee::dead:beef:cafe]', '[2001:19f0:feee::dead:beef:cafe]:8080', 'xn--4ca9at.com', # Punnycode for öäü.com + 'anything.multitenant.com', + 'multitenant.com', + 'insensitive.com', ] poisoned_hosts = [ @@ -120,6 +129,7 @@ def test_http_get_host(self): 'example.com:dr.frankenstein@evil.tld:80', 'example.com:80/badpath', 'example.com: recovermypassword.com', + 'other.com', # not in ALLOWED_HOSTS ] for host in legit_hosts: @@ -130,29 +140,31 @@ def test_http_get_host(self): request.get_host() for host in poisoned_hosts: - def test_host_poisoning(): + def _test(): request = HttpRequest() request.META = { 'HTTP_HOST': host, } request.get_host() - self.assertRaises(SuspiciousOperation, test_host_poisoning) - + self.assertRaises(SuspiciousOperation, _test) finally: - settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST + settings.ALLOWED_HOSTS = _old_ALLOWED_HOSTS + settings.USE_X_FORWARDED_HOST = _old_USE_X_FORWARDED_HOST def test_http_get_host_with_x_forwarded_host(self): - old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST + _old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST + _old_ALLOWED_HOSTS = settings.ALLOWED_HOSTS try: settings.USE_X_FORWARDED_HOST = True + settings.ALLOWED_HOSTS = ['*'] # Check if X_FORWARDED_HOST is provided. request = HttpRequest() request.META = { - u'HTTP_X_FORWARDED_HOST': u'forward.com', - u'HTTP_HOST': u'example.com', - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 80, + 'HTTP_X_FORWARDED_HOST': 'forward.com', + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, } # X_FORWARDED_HOST is obeyed. self.assertEqual(request.get_host(), 'forward.com') @@ -160,25 +172,25 @@ def test_http_get_host_with_x_forwarded_host(self): # Check if X_FORWARDED_HOST isn't provided. request = HttpRequest() request.META = { - u'HTTP_HOST': u'example.com', - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 80, + 'HTTP_HOST': 'example.com', + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, } self.assertEqual(request.get_host(), 'example.com') # Check if HTTP_HOST isn't provided. request = HttpRequest() request.META = { - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 80, + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 80, } self.assertEqual(request.get_host(), 'internal.com') # Check if HTTP_HOST isn't provided, and we're on a nonstandard port request = HttpRequest() request.META = { - u'SERVER_NAME': u'internal.com', - u'SERVER_PORT': 8042, + 'SERVER_NAME': 'internal.com', + 'SERVER_PORT': 8042, } self.assertEqual(request.get_host(), 'internal.com:8042') @@ -209,16 +221,33 @@ def test_http_get_host_with_x_forwarded_host(self): request.get_host() for host in poisoned_hosts: - def test_host_poisoning(): + def _test(): request = HttpRequest() request.META = { 'HTTP_HOST': host, } request.get_host() - self.assertRaises(SuspiciousOperation, test_host_poisoning) + self.assertRaises(SuspiciousOperation, _test) + finally: + settings.ALLOWED_HOSTS = _old_ALLOWED_HOSTS + settings.USE_X_FORWARDED_HOST = _old_USE_X_FORWARDED_HOST + + def test_host_validation_disabled_in_debug_mode(self): + """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass.""" + _old_DEBUG = settings.DEBUG + _old_ALLOWED_HOSTS = settings.ALLOWED_HOSTS + try: + settings.DEBUG = True + settings.ALLOWED_HOSTS = [] + request = HttpRequest() + request.META = { + 'HTTP_HOST': 'example.com', + } + self.assertEqual(request.get_host(), 'example.com') finally: - settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST + settings.DEBUG = _old_DEBUG + settings.ALLOWED_HOSTS = _old_ALLOWED_HOSTS def test_near_expiration(self): "Cookie will expire when an near expiration time is provided" From d19a27066b2247102e65412aa66917aff0091112 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Mon, 11 Feb 2013 21:54:53 -0700 Subject: [PATCH 210/225] [1.3.x] Restrict the XML deserializer to prevent network and entity-expansion DoS attacks. This is a security fix. Disclosure and advisory coming shortly. --- django/core/serializers/xml_serializer.py | 94 ++++++++++++++++++- .../serializers_regress/tests.py | 15 +++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index bcf5631e00f1..547e6e0e9bd0 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -8,6 +8,8 @@ from django.utils.xmlutils import SimplerXMLGenerator from django.utils.encoding import smart_unicode from xml.dom import pulldom +from xml.sax import handler +from xml.sax.expatreader import ExpatParser as _ExpatParser class Serializer(base.Serializer): """ @@ -154,9 +156,13 @@ class Deserializer(base.Deserializer): def __init__(self, stream_or_string, **options): super(Deserializer, self).__init__(stream_or_string, **options) - self.event_stream = pulldom.parse(self.stream) + self.event_stream = pulldom.parse(self.stream, self._make_parser()) self.db = options.pop('using', DEFAULT_DB_ALIAS) + def _make_parser(self): + """Create a hardened XML parser (no custom/external entities).""" + return DefusedExpatParser() + def next(self): for event, node in self.event_stream: if event == "START_ELEMENT" and node.nodeName == "object": @@ -295,3 +301,89 @@ def getInnerText(node): else: pass return u"".join(inner_text) + + +# Below code based on Christian Heimes' defusedxml + + +class DefusedExpatParser(_ExpatParser): + """ + An expat parser hardened against XML bomb attacks. + + Forbids DTDs, external entity references + + """ + def __init__(self, *args, **kwargs): + _ExpatParser.__init__(self, *args, **kwargs) + self.setFeature(handler.feature_external_ges, False) + self.setFeature(handler.feature_external_pes, False) + + def start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def entity_decl(self, name, is_parameter_entity, value, base, + sysid, pubid, notation_name): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) + + def external_entity_ref_handler(self, context, base, sysid, pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + def reset(self): + _ExpatParser.reset(self) + parser = self._parser + parser.StartDoctypeDeclHandler = self.start_doctype_decl + parser.EntityDeclHandler = self.entity_decl + parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl + parser.ExternalEntityRefHandler = self.external_entity_ref_handler + + +class DefusedXmlException(ValueError): + """Base exception.""" + def __repr__(self): + return str(self) + + +class DTDForbidden(DefusedXmlException): + """Document type definition is forbidden.""" + def __init__(self, name, sysid, pubid): + self.name = name + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class EntitiesForbidden(DefusedXmlException): + """Entity definition is forbidden.""" + def __init__(self, name, value, base, sysid, pubid, notation_name): + super(EntitiesForbidden, self).__init__() + self.name = name + self.value = value + self.base = base + self.sysid = sysid + self.pubid = pubid + self.notation_name = notation_name + + def __str__(self): + tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class ExternalReferenceForbidden(DefusedXmlException): + """Resolving an external reference is forbidden.""" + def __init__(self, context, base, sysid, pubid): + super(ExternalReferenceForbidden, self).__init__() + self.context = context + self.base = base + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})" + return tpl.format(self.sysid, self.pubid) diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index de57a6f0de7f..a1b85d6df777 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -14,6 +14,7 @@ from cStringIO import StringIO except ImportError: from StringIO import StringIO +from django.core.serializers.xml_serializer import DTDForbidden from django.conf import settings from django.core import serializers, management @@ -416,3 +417,17 @@ def streamTest(format, self): setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format)) if format != 'python': setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) + + +class XmlDeserializerSecurityTests(TestCase): + + def test_no_dtd(self): + """ + The XML deserializer shouldn't allow a DTD. + + This is the most straightforward way to prevent all entity definitions + and avoid both external entities and entity-expansion attacks. + + """ + xml = '' + self.assertRaises(DTDForbidden, serializers.deserialize('xml', xml).next) From d3a45e10c8ac8268899999129daa27652ec0da35 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Mon, 4 Feb 2013 16:57:59 -0700 Subject: [PATCH 211/225] [1.3.x] Checked object permissions on admin history view. This is a security fix. Disclosure and advisory coming shortly. Patch by Russell Keith-Magee. --- django/contrib/admin/options.py | 10 ++++-- tests/regressiontests/admin_views/tests.py | 40 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3d06050c5455..c30394edb646 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1242,15 +1242,21 @@ def delete_view(self, request, object_id, extra_context=None): def history_view(self, request, object_id, extra_context=None): "The 'history' admin view for this model." from django.contrib.admin.models import LogEntry + # First check if the user can see this history. model = self.model + obj = get_object_or_404(model, pk=unquote(object_id)) + + if not self.has_change_permission(request, obj): + raise PermissionDenied + + # Then get the history for this object. opts = model._meta app_label = opts.app_label action_list = LogEntry.objects.filter( object_id = 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. - obj = get_object_or_404(model, pk=unquote(object_id)) + context = { 'title': _('Change history: %s') % force_unicode(obj), 'action_list': action_list, diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 57bd87f8e673..db67db27f403 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -827,6 +827,46 @@ def testChangeView(self): self.assertContains(request, 'login-form') self.client.get('/test_admin/admin/logout/') + def testHistoryView(self): + """History view should restrict access.""" + + # add user shoud not be able to view the list of article or change any of them + self.client.get('/test_admin/admin/') + self.client.post('/test_admin/admin/', self.adduser_login) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.status_code, 403) + self.client.get('/test_admin/admin/logout/') + + # change user can view all items and edit them + self.client.get('/test_admin/admin/') + self.client.post('/test_admin/admin/', self.changeuser_login) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.status_code, 200) + + # Test redirection when using row-level change permissions. Refs #11513. + RowLevelChangePermissionModel.objects.create(id=1, name="odd id") + RowLevelChangePermissionModel.objects.create(id=2, name="even id") + for login_dict in [self.super_login, self.changeuser_login, self.adduser_login, self.deleteuser_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 403) + + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + + self.client.get('/test_admin/admin/logout/') + + for login_dict in [self.joepublic_login, self.no_username_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + + self.client.get('/test_admin/admin/logout/') + def testConditionallyShowAddSectionLink(self): """ The foreign key widget should only show the "add related" button if the From d7094bbce8cb838f3b40f504f198c098ff1cf727 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Tue, 12 Feb 2013 11:22:41 +0100 Subject: [PATCH 212/225] [1.3.x] Added a default limit to the maximum number of forms in a formset. This is a security fix. Disclosure and advisory coming shortly. --- django/forms/formsets.py | 12 ++- docs/topics/forms/formsets.txt | 6 +- docs/topics/forms/modelforms.txt | 4 +- tests/regressiontests/forms/tests/formsets.py | 85 ++++++++++++++++--- 4 files changed, 88 insertions(+), 19 deletions(-) diff --git a/django/forms/formsets.py b/django/forms/formsets.py index da92fbd408c6..da1ce7fe9277 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -16,6 +16,9 @@ ORDERING_FIELD_NAME = 'ORDER' DELETION_FIELD_NAME = 'DELETE' +# default maximum number of forms in a formset, to prevent memory exhaustion +DEFAULT_MAX_NUM = 1000 + class ManagementForm(Form): """ ``ManagementForm`` is used to keep track of how many form instances @@ -104,7 +107,7 @@ def initial_form_count(self): def _construct_forms(self): # instantiate all the forms and put them in self.forms self.forms = [] - for i in xrange(self.total_form_count()): + for i in xrange(min(self.total_form_count(), self.absolute_max)): self.forms.append(self._construct_form(i)) def _construct_form(self, i, **kwargs): @@ -348,9 +351,14 @@ def as_ul(self): def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None): """Return a FormSet for the given form class.""" + if max_num is None: + max_num = DEFAULT_MAX_NUM + # hard limit on forms instantiated, to prevent memory-exhaustion attacks + # limit defaults to DEFAULT_MAX_NUM, but developer can increase it via max_num + absolute_max = max(DEFAULT_MAX_NUM, max_num) attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete, - 'max_num': max_num} + 'max_num': max_num, 'absolute_max': absolute_max} return type(form.__name__ + 'FormSet', (formset,), attrs) def all_valid(formsets): diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index a3721fdcdff2..078597e14d7d 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -102,8 +102,10 @@ If the value of ``max_num`` is greater than the number of existing objects, up to ``extra`` additional blank forms will be added to the formset, so long as the total number of forms does not exceed ``max_num``. -A ``max_num`` value of ``None`` (the default) puts no limit on the number of -forms displayed. Please note that the default value of ``max_num`` was changed +A ``max_num`` value of ``None`` (the default) puts a high limit on the number +of forms displayed (1000). In practice this is equivalent to no limit. + +Please note that the default value of ``max_num`` was changed from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value. Formset validation diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 24e000ece0fe..04b0d76b64dc 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -691,8 +691,8 @@ so long as the total number of forms does not exceed ``max_num``:: .. versionchanged:: 1.2 -A ``max_num`` value of ``None`` (the default) puts no limit on the number of -forms displayed. +A ``max_num`` value of ``None`` (the default) puts a high limit on the number +of forms displayed (1000). In practice this is equivalent to no limit. Using a model formset in a view ------------------------------- diff --git a/tests/regressiontests/forms/tests/formsets.py b/tests/regressiontests/forms/tests/formsets.py index 4451fc76b17b..f50c2b28c75a 100644 --- a/tests/regressiontests/forms/tests/formsets.py +++ b/tests/regressiontests/forms/tests/formsets.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.forms import Form, CharField, IntegerField, ValidationError, DateField +from django.forms import Form, CharField, IntegerField, ValidationError, DateField, formsets from django.forms.formsets import formset_factory, BaseFormSet from django.utils.unittest import TestCase @@ -47,7 +47,7 @@ def test_basic_formset(self): # for adding data. By default, it displays 1 blank form. It can display more, # but we'll look at how to do so later. formset = ChoiceFormSet(auto_id=False, prefix='choices') - self.assertEqual(str(formset), """ + self.assertEqual(str(formset), """ Choice: Votes:""") @@ -623,8 +623,8 @@ def test_limiting_max_forms(self): # Limiting the maximum number of forms ######################################## # Base case for max_num. - # When not passed, max_num will take its default value of None, i.e. unlimited - # number of forms, only controlled by the value of the extra parameter. + # When not passed, max_num will take a high default value, leaving the + # number of forms only controlled by the value of the extra parameter. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3) formset = LimitedFavoriteDrinkFormSet() @@ -671,8 +671,8 @@ def test_limiting_max_forms(self): def test_max_num_with_initial_data(self): # max_num with initial data - # When not passed, max_num will take its default value of None, i.e. unlimited - # number of forms, only controlled by the values of the initial and extra + # When not passed, max_num will take a high default value, leaving the + # number of forms only controlled by the value of the initial and extra # parameters. initial = [ @@ -805,6 +805,65 @@ def __iter__(self): self.assertEqual(str(reverse_formset[1]), str(forms[-2])) self.assertEqual(len(reverse_formset), len(forms)) + def test_hard_limit_on_instantiated_forms(self): + """A formset has a hard limit on the number of forms instantiated.""" + # reduce the default limit of 1000 temporarily for testing + _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM + try: + formsets.DEFAULT_MAX_NUM = 3 + ChoiceFormSet = formset_factory(Choice) + # someone fiddles with the mgmt form data... + formset = ChoiceFormSet( + { + 'choices-TOTAL_FORMS': '4', + 'choices-INITIAL_FORMS': '0', + 'choices-MAX_NUM_FORMS': '4', + 'choices-0-choice': 'Zero', + 'choices-0-votes': '0', + 'choices-1-choice': 'One', + 'choices-1-votes': '1', + 'choices-2-choice': 'Two', + 'choices-2-votes': '2', + 'choices-3-choice': 'Three', + 'choices-3-votes': '3', + }, + prefix='choices', + ) + # But we still only instantiate 3 forms + self.assertEqual(len(formset.forms), 3) + finally: + formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM + + def test_increase_hard_limit(self): + """Can increase the built-in forms limit via a higher max_num.""" + # reduce the default limit of 1000 temporarily for testing + _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM + try: + formsets.DEFAULT_MAX_NUM = 3 + # for this form, we want a limit of 4 + ChoiceFormSet = formset_factory(Choice, max_num=4) + formset = ChoiceFormSet( + { + 'choices-TOTAL_FORMS': '4', + 'choices-INITIAL_FORMS': '0', + 'choices-MAX_NUM_FORMS': '4', + 'choices-0-choice': 'Zero', + 'choices-0-votes': '0', + 'choices-1-choice': 'One', + 'choices-1-votes': '1', + 'choices-2-choice': 'Two', + 'choices-2-votes': '2', + 'choices-3-choice': 'Three', + 'choices-3-votes': '3', + }, + prefix='choices', + ) + # This time four forms are instantiated + self.assertEqual(len(formset.forms), 4) + finally: + formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM + + data = { 'choices-TOTAL_FORMS': '1', # the number of forms rendered 'choices-INITIAL_FORMS': '0', # the number of forms with initial data @@ -900,12 +959,12 @@ def test_empty_forms_are_unbound(self): # The empty forms should be equal. self.assertEqual(empty_forms[0].as_p(), empty_forms[1].as_p()) -class TestEmptyFormSet(TestCase): +class TestEmptyFormSet(TestCase): "Test that an empty formset still calls clean()" - def test_empty_formset_is_valid(self): - EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate) - formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form") - formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form") - self.assertFalse(formset.is_valid()) - self.assertFalse(formset2.is_valid()) + def test_empty_formset_is_valid(self): + EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate) + formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form") + formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form") + self.assertFalse(formset.is_valid()) + self.assertFalse(formset2.is_valid()) From f6f6f87a9832f9bd441f6510a6b233e72771e4f5 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 12 Feb 2013 15:48:37 -0700 Subject: [PATCH 213/225] [1.3.x] Update 1.3.6 release notes for all security fixes. --- docs/releases/1.3.6.txt | 50 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/releases/1.3.6.txt b/docs/releases/1.3.6.txt index c1e4bed2c70b..af17ad3bb059 100644 --- a/docs/releases/1.3.6.txt +++ b/docs/releases/1.3.6.txt @@ -4,8 +4,12 @@ Django 1.3.6 release notes *February 19, 2013* +Django 1.3.6 fixes four security issues present in previous Django releases in +the 1.3 series. + This is the sixth bugfix/security release in the Django 1.3 series. + Host header poisoning --------------------- @@ -24,8 +28,52 @@ request with a Host header not matching an entry in this list will raise ``SuspiciousOperation`` if ``request.get_host()`` is called. For full details see the documentation for the :setting:`ALLOWED_HOSTS` setting. -The default value for this setting in Django 1.3.6 is `['*']` (matching any +The default value for this setting in Django 1.3.6 is ``['*']`` (matching any host), for backwards-compatibility, but we strongly encourage all sites to set a more restrictive value. This host validation is disabled when ``DEBUG`` is ``True`` or when running tests. + + +XML deserialization +------------------- + +The XML parser in the Python standard library is vulnerable to a number of +denial-of-service attacks via external entities and entity expansion. Django +uses this parser for deserializing XML-formatted database fixtures. The fixture +deserializer is not intended for use with untrusted data, but in order to err +on the side of safety in Django 1.3.6 the XML deserializer refuses to parse an +XML document with a DTD (DOCTYPE definition), which closes off these attack +avenues. + +These issues in the Python standard library are CVE-2013-1664 and +CVE-2013-1665. More information available `from the Python security team`_. + +Django's XML serializer does not create documents with a DTD, so this should +not cause any issues with the typical round-trip from ``dumpdata`` to +``loaddata``, but if you feed your own XML documents to the ``loaddata`` +management command, you will need to ensure they do not contain a DTD. + +.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html + + +Formset memory exhaustion +------------------------- + +Previous versions of Django did not validate or limit the form-count data +provided by the client in a formset's management form, making it possible to +exhaust a server's available memory by forcing it to create very large numbers +of forms. + +In Django 1.3.6, all formsets have a strictly-enforced maximum number of forms +(1000 by default, though it can be set higher via the ``max_num`` formset +factory argument). + + +Admin history view information leakage +-------------------------------------- + +In previous versions of Django, an admin user without change permission on a +model could still view the unicode representation of instances via their admin +history log. Django 1.3.6 now limits the admin history log view for an object +to users with change permission for that model. From 747d3f0d0390d1eeae28cb74c310c08c8b4fb58c Mon Sep 17 00:00:00 2001 From: James Bennett Date: Tue, 19 Feb 2013 14:18:32 -0600 Subject: [PATCH 214/225] [1.3.x] Bump version numbers for security release. --- django/__init__.py | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index d3869d6fda9d..f5abd46a57bf 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 5, 'final', 0) +VERSION = (1, 3, 6, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/docs/conf.py b/docs/conf.py index df09ac5e0e41..f2e3173d65ef 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '1.3.5' +version = '1.3.6' # The full version, including alpha/beta/rc tags. -release = '1.3.5' +release = '1.3.6' # The next version to be released django_next_version = '1.4' diff --git a/setup.py b/setup.py index 54a6ce57f323..368ecde7352f 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.5.tar.gz', + download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.6.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From 2378c31430a2b441d23efe98c0af32fe4af0fb53 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 19 Feb 2013 18:23:25 -0700 Subject: [PATCH 215/225] [1.3.x] Don't characterize XML vulnerabilities as DoS-only. --- docs/releases/1.3.6.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/releases/1.3.6.txt b/docs/releases/1.3.6.txt index af17ad3bb059..d55199a88240 100644 --- a/docs/releases/1.3.6.txt +++ b/docs/releases/1.3.6.txt @@ -39,12 +39,11 @@ XML deserialization ------------------- The XML parser in the Python standard library is vulnerable to a number of -denial-of-service attacks via external entities and entity expansion. Django -uses this parser for deserializing XML-formatted database fixtures. The fixture -deserializer is not intended for use with untrusted data, but in order to err -on the side of safety in Django 1.3.6 the XML deserializer refuses to parse an -XML document with a DTD (DOCTYPE definition), which closes off these attack -avenues. +attacks via external entities and entity expansion. Django uses this parser for +deserializing XML-formatted database fixtures. The fixture deserializer is not +intended for use with untrusted data, but in order to err on the side of safety +in Django 1.3.6 the XML deserializer refuses to parse an XML document with a +DTD (DOCTYPE definition), which closes off these attack avenues. These issues in the Python standard library are CVE-2013-1664 and CVE-2013-1665. More information available `from the Python security team`_. From a6927d821941fa5c25f277479e84e3c32fe005cd Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 19 Feb 2013 18:38:58 -0700 Subject: [PATCH 216/225] [1.3.x] Fixed #19857 -- Fixed broken docs link in project template. Backport of 4cdfb24c98 from 1.4.x. --- django/conf/project_template/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index 839039de9d58..45e421130633 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -21,7 +21,7 @@ } # Hosts/domain names that are valid for this site; required if DEBUG is False -# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts +# See https://docs.djangoproject.com/en/1.3/ref/settings/#allowed-hosts ALLOWED_HOSTS = [] # Local time zone for this installation. Choices can be found here: From a57743c9ff904e8c8a90499d9f92bdbd52ff113a Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 20 Feb 2013 12:26:54 -0700 Subject: [PATCH 217/225] [1.4.x] Note that ALLOWED_HOSTS default changes in Django 1.5. --- docs/ref/settings.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 68869f166c72..7711240a65eb 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -118,6 +118,11 @@ This validation only applies via :meth:`~django.http.HttpRequest.get_host()`; if your code accesses the ``Host`` header directly from ``request.META`` you are bypassing this security protection. +The default value of this setting in Django 1.3.6+ is ``['*']`` (accept any +host) in order to avoid breaking backwards-compatibility in a security update, +but in Django 1.5+ the default is ``[]`` and explicitly configuring this +setting is required. + .. setting:: ALLOWED_INCLUDE_ROOTS ALLOWED_INCLUDE_ROOTS From 304a5e06287290392f2d654446b97b4f6c685b1c Mon Sep 17 00:00:00 2001 From: James Bennett Date: Wed, 20 Feb 2013 13:52:28 -0600 Subject: [PATCH 218/225] [1.3.x] Bump version numbers to roll a clean package. --- django/__init__.py | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index f5abd46a57bf..815646a095c3 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 6, 'final', 0) +VERSION = (1, 3, 7, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/docs/conf.py b/docs/conf.py index f2e3173d65ef..e62e760cc38e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '1.3.6' +version = '1.3.7' # The full version, including alpha/beta/rc tags. -release = '1.3.6' +release = '1.3.7' # The next version to be released django_next_version = '1.4' diff --git a/setup.py b/setup.py index 368ecde7352f..064d68ad5e80 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.6.tar.gz', + download_url = 'https://www.djangoproject.com/m/releases/1.3/Django-1.3.7.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From 956b755d7e62a799ba691d00e02e82065ca6bb6b Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 28 Mar 2013 15:12:13 -0600 Subject: [PATCH 219/225] [1.3.x] Bump version to no longer claim to be 1.3.7 final. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 815646a095c3..9c517c5561f8 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 3, 7, 'final', 0) +VERSION = (1, 3, 8, 'alpha', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) From 7dd37edf6292ad0d24059e7039cabefceca32caa Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 12 Aug 2013 13:20:58 -0400 Subject: [PATCH 220/225] [1.3.x] Added missing release notes for older versions of Django Backport of 3f6cc33cff from master --- docs/releases/1.3.2.txt | 14 ++++++++++ docs/releases/1.3.3.txt | 11 ++++++++ docs/releases/1.3.4.txt | 37 +++++++++++++++++++++++++ docs/releases/1.3.5.txt | 60 +++++++++++++++++++++++++++++++++++++++++ docs/releases/1.3.7.txt | 13 +++++++++ docs/releases/index.txt | 5 ++++ 6 files changed, 140 insertions(+) create mode 100644 docs/releases/1.3.2.txt create mode 100644 docs/releases/1.3.3.txt create mode 100644 docs/releases/1.3.4.txt create mode 100644 docs/releases/1.3.5.txt create mode 100644 docs/releases/1.3.7.txt diff --git a/docs/releases/1.3.2.txt b/docs/releases/1.3.2.txt new file mode 100644 index 000000000000..16e00f973269 --- /dev/null +++ b/docs/releases/1.3.2.txt @@ -0,0 +1,14 @@ +========================== +Django 1.3.2 release notes +========================== + +*July 30, 2012* + +This is the second security release in the Django 1.3 series, fixing several +security issues in Django 1.3. Django 1.3.2 is a recommended upgrade for +all users of Django 1.3. + +For a full list of issues addressed in this release, see the `security +advisory`_. + +.. _security advisory: https://www.djangoproject.com/weblog/2012/jul/30/security-releases-issued/ diff --git a/docs/releases/1.3.3.txt b/docs/releases/1.3.3.txt new file mode 100644 index 000000000000..437cbfb41258 --- /dev/null +++ b/docs/releases/1.3.3.txt @@ -0,0 +1,11 @@ +========================== +Django 1.3.3 release notes +========================== + +*August 1, 2012* + +Following Monday's security release of :doc:`Django 1.3.2 `, +we began receiving reports that one of the fixes applied was breaking Python +2.4 compatibility for Django 1.3. Since Python 2.4 is a supported Python +version for that release series, this release fixes compatibility with +Python 2.4. diff --git a/docs/releases/1.3.4.txt b/docs/releases/1.3.4.txt new file mode 100644 index 000000000000..3a174b3d448e --- /dev/null +++ b/docs/releases/1.3.4.txt @@ -0,0 +1,37 @@ +========================== +Django 1.3.4 release notes +========================== + +*October 17, 2012* + +This is the fourth release in the Django 1.3 series. + +Host header poisoning +--------------------- + +Some parts of Django -- independent of end-user-written applications -- make +use of full URLs, including domain name, which are generated from the HTTP Host +header. Some attacks against this are beyond Django's ability to control, and +require the web server to be properly configured; Django's documentation has +for some time contained notes advising users on such configuration. + +Django's own built-in parsing of the Host header is, however, still vulnerable, +as was reported to us recently. The Host header parsing in Django 1.3.3 and +Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was +incorrectly handling username/password information in the header. Thus, for +example, the following Host header would be accepted by Django when running on +"validsite.com":: + + Host: validsite.com:random@evilsite.com + +Using this, an attacker can cause parts of Django -- particularly the +password-reset mechanism -- to generate and display arbitrary URLs to users. + +To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified; +Host headers which contain potentially dangerous content (such as +username/password pairs) now raise the exception +:exc:`django.core.exceptions.SuspiciousOperation`. + +Details of this issue were initially posted online as a `security advisory`_. + +.. _security advisory: https://www.djangoproject.com/weblog/2012/oct/17/security/ diff --git a/docs/releases/1.3.5.txt b/docs/releases/1.3.5.txt new file mode 100644 index 000000000000..65c403209d13 --- /dev/null +++ b/docs/releases/1.3.5.txt @@ -0,0 +1,60 @@ +========================== +Django 1.3.5 release notes +========================== + +*December 10, 2012* + +Django 1.3.5 addresses two security issues present in previous Django releases +in the 1.3 series. + +Please be aware that this security release is slightly different from previous +ones. Both issues addressed here have been dealt with in prior security updates +to Django. In one case, we have received ongoing reports of problems, and in +the other we've chosen to take further steps to tighten up Django's code in +response to independent discovery of potential problems from multiple sources. + +Host header poisoning +--------------------- + +Several earlier Django security releases focused on the issue of poisoning the +HTTP Host header, causing Django to generate URLs pointing to arbitrary, +potentially-malicious domains. + +In response to further input received and reports of continuing issues +following the previous release, we're taking additional steps to tighten Host +header validation. Rather than attempt to accommodate all features HTTP +supports here, Django's Host header validation attempts to support a smaller, +but far more common, subset: + +* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot + ('.'). +* IP addresses -- both IPv4 and IPv6 -- are permitted. +* Port, if specified, is numeric. + +Any deviation from this will now be rejected, raising the exception +:exc:`django.core.exceptions.SuspiciousOperation`. + +Redirect poisoning +------------------ + +Also following up on a previous issue: in July of this year, we made changes to +Django's HTTP redirect classes, performing additional validation of the scheme +of the URL to redirect to (since, both within Django's own supplied +applications and many third-party applications, accepting a user-supplied +redirect target is a common pattern). + +Since then, two independent audits of the code turned up further potential +problems. So, similar to the Host-header issue, we are taking steps to provide +tighter validation in response to reported problems (primarily with third-party +applications, but to a certain extent also within Django itself). This comes in +two parts: + +1. A new utility function, ``django.utils.http.is_safe_url``, is added; this +function takes a URL and a hostname, and checks that the URL is either +relative, or if absolute matches the supplied hostname. This function is +intended for use whenever user-supplied redirect targets are accepted, to +ensure that such redirects cannot lead to arbitrary third-party sites. + +2. All of Django's own built-in views -- primarily in the authentication system +-- which allow user-supplied redirect targets now use ``is_safe_url`` to +validate the supplied URL. diff --git a/docs/releases/1.3.7.txt b/docs/releases/1.3.7.txt new file mode 100644 index 000000000000..3cccfcfb1c5e --- /dev/null +++ b/docs/releases/1.3.7.txt @@ -0,0 +1,13 @@ +========================== +Django 1.3.7 release notes +========================== + +*February 20, 2013* + +Django 1.3.7 corrects a packaging problem with yesterday's :doc:`1.3.6 release +`. + +The release contained stray ``.pyc`` files that caused "bad magic number" +errors when running with some versions of Python. This releases corrects this, +and also fixes a bad documentation link in the project template ``settings.py`` +file generated by ``manage.py startproject``. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 3f14936fea7d..7356901d87a8 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -19,7 +19,12 @@ Final releases .. toctree:: :maxdepth: 1 + 1.3.7 1.3.6 + 1.3.5 + 1.3.4 + 1.3.3 + 1.3.2 1.3.1 1.3 From 1989df601402125da1420b896eb7a6bab5a8c11f Mon Sep 17 00:00:00 2001 From: John Heasly Date: Fri, 31 Jan 2014 17:15:13 -0800 Subject: [PATCH 221/225] Fixed bad link --- docs/topics/db/models.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index af7634439c9d..3ee15f0b78c3 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -328,7 +328,7 @@ whatever you want. For example:: For sample code, see the `Many-to-one relationship model tests`_. - .. _Many-to-one relationship model tests: http://code.djangoproject.com/browser/django/trunk/tests/modeltests/many_to_one + .. _Many-to-one relationship model tests: https://github.com/django/django/tree/stable/1.3.x/tests/modeltests/many_to_one Many-to-many relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~ From 8dc1b2e03f2402911dd68172ecc0b0bb22fd3e18 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sat, 14 May 2011 02:58:58 +0000 Subject: [PATCH 222/225] [1.3.x] Fixed our Sphinx extension to work with latest Sphinx This is pretty hacky, but there doesn't seem to be a nice way to fix it, since we can't call the base method - we are deliberately overriding it in order to not call the base method, which adds an unwanted 'border=1' to the HTML. git-svn-id: http://code.djangoproject.com/svn/django/trunk@16223 bcc190cf-cafb-0310-a4f2-bffc1f526a37 Backport of e127e17b49 from master --- docs/_ext/djangodocs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 7710786d0fcd..eec0983307c7 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -127,6 +127,7 @@ class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): # Don't use border=1, which docutils does by default. def visit_table(self, node): + self._table_row_index = 0 # Needed by Sphinx self.body.append(self.starttag(node, 'table', CLASS='docutils')) # ? Really? From 8892b0c2c36e67ea8470de302a51f12654df668a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 31 Jul 2013 09:24:29 -0400 Subject: [PATCH 223/225] [1.3.x] Added a bugfix in docutils 0.11 -- docs will now build properly. Backport of a3a59a3197 from master --- docs/_ext/djangodocs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index eec0983307c7..ee960f1a2aed 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -127,9 +127,15 @@ class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): # Don't use border=1, which docutils does by default. def visit_table(self, node): + self.context.append(self.compact_p) + self.compact_p = True self._table_row_index = 0 # Needed by Sphinx self.body.append(self.starttag(node, 'table', CLASS='docutils')) + def depart_table(self, node): + self.compact_p = self.context.pop() + self.body.append('\n') + # ? Really? def visit_desc_parameterlist(self, node): self.body.append('(') From e982cbd4a13a17e8c9246860fec8be8716519a66 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sun, 15 May 2011 23:02:36 +0000 Subject: [PATCH 224/225] [1.3.x] Fixed djangodocs Sphinx extension to work with latest Sphinx git-svn-id: http://code.djangoproject.com/svn/django/trunk@16231 bcc190cf-cafb-0310-a4f2-bffc1f526a37 Backport of 66fd824ee0 from master --- docs/_ext/djangodocs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index ee960f1a2aed..003e4ab588a5 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -140,6 +140,7 @@ def depart_table(self, node): def visit_desc_parameterlist(self, node): self.body.append('(') self.first_param = 1 + self.param_separator = node.child_text_separator def depart_desc_parameterlist(self, node): self.body.append(')') From 6726d750979a7c29e0dd866b4ea367eef7c8a420 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 24 Jan 2014 08:52:43 -0500 Subject: [PATCH 225/225] [1.3.x] Fixed #21869 -- Fixed docs building with Sphinx 1.2.1. Thanks tragiclifestories for the report. Backport of e1d18b9d2e from master --- docs/_ext/djangodocs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 003e4ab588a5..825ea931e968 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -136,11 +136,13 @@ def depart_table(self, node): self.compact_p = self.context.pop() self.body.append('\n') - # ? Really? def visit_desc_parameterlist(self, node): - self.body.append('(') + self.body.append('(') # by default sphinx puts around the "(" self.first_param = 1 + self.optional_param_level = 0 self.param_separator = node.child_text_separator + self.required_params_left = sum([isinstance(c, addnodes.desc_parameter) + for c in node.children]) def depart_desc_parameterlist(self, node): self.body.append(')')