From b719cbb901026eb86cbf691f126f8cf92b717ca4 Mon Sep 17 00:00:00 2001 From: Marc Neuwirth Date: Sun, 29 Apr 2012 22:48:30 -0300 Subject: [PATCH 01/95] Removed 'return false' in favor of preventDefault. Moved preventDefault to the top --- .../contrib/admin/static/admin/js/inlines.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index bddd6f75ba..668e1157c8 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -31,11 +31,11 @@ } }; var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); - var nextIndex = parseInt(totalForms.val()); + var nextIndex = parseInt(totalForms.val(), 10); var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); // only show the add button if we are allowed to add more items, // note that max_num = None translates to a blank string. - var showAddButton = maxForms.val() == '' || (maxForms.val()-totalForms.val()) > 0; + var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; $(this).each(function(i) { $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); }); @@ -52,13 +52,14 @@ $(this).filter(":last").after('
' + options.addText + "
"); addButton = $(this).filter(":last").next().find("a"); } - addButton.click(function() { + addButton.click(function(e) { + e.preventDefault(); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); var template = $("#" + options.prefix + "-empty"); var row = template.clone(true); row.removeClass(options.emptyCssClass) - .addClass(options.formCssClass) - .attr("id", options.prefix + "-" + nextIndex); + .addClass(options.formCssClass) + .attr("id", options.prefix + "-" + nextIndex); if (row.is("tr")) { // If the forms are laid out in table rows, insert // the remove button into the last table cell: @@ -78,14 +79,15 @@ // Insert the new form when it has been fully edited row.insertBefore($(template)); // Update number of total forms - $(totalForms).val(parseInt(totalForms.val()) + 1); + $(totalForms).val(parseInt(totalForms.val(), 10) + 1); nextIndex += 1; // Hide add button in case we've hit the max, except we want to add infinitely - if ((maxForms.val() != '') && (maxForms.val()-totalForms.val()) <= 0) { + if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { addButton.parent().hide(); } // The delete button of each row triggers a bunch of other things - row.find("a." + options.deleteCssClass).click(function() { + row.find("a." + options.deleteCssClass).click(function(e) { + e.preventDefault(); // Remove the parent form containing this button: var row = $(this).parents("." + options.formCssClass); row.remove(); @@ -98,7 +100,7 @@ var forms = $("." + options.formCssClass); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); // Show add button again once we drop below max - if ((maxForms.val() == '') || (maxForms.val()-forms.length) > 0) { + if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { addButton.parent().show(); } // Also, update names and ids for all remaining form controls @@ -110,17 +112,15 @@ updateElementIndex(this, options.prefix, i); }); } - return false; }); // If a post-add callback was supplied, call it with the added form: if (options.added) { options.added(row); } - return false; }); } return this; - } + }; /* Setup plugin defaults */ $.fn.formset.defaults = { prefix: "form", // The form prefix for your django formset @@ -132,5 +132,5 @@ formCssClass: "dynamic-form", // CSS class applied to each form in a formset added: null, // Function called each time a new form is added removed: null // Function called each time a form is deleted - } + }; })(django.jQuery); From 5f75ac91df2ef1c19946f65e08aa50165f061cd4 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 16 May 2012 13:21:46 +0200 Subject: [PATCH 02/95] Fixed #17896 -- Added file_hash method to CachedStaticFilesStorage to be able to customize the way the hashed name of a file is created. Thanks to mkai for the initial patch. --- django/contrib/staticfiles/storage.py | 23 +++++++---- docs/ref/contrib/staticfiles.txt | 9 +++++ .../staticfiles_tests/storage.py | 7 ++++ .../staticfiles_tests/tests.py | 40 ++++++++++++++++++- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index 7b5866a699..fd8f9efb02 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -64,6 +64,17 @@ class CachedFilesMixin(object): compiled = re.compile(pattern) self._patterns.setdefault(extension, []).append(compiled) + def file_hash(self, name, content=None): + """ + Retuns a hash of the file with the given name and optional content. + """ + if content is None: + return None + md5 = hashlib.md5() + for chunk in content.chunks(): + md5.update(chunk) + return md5.hexdigest()[:12] + def hashed_name(self, name, content=None): parsed_name = urlsplit(unquote(name)) clean_name = parsed_name.path.strip() @@ -78,13 +89,11 @@ class CachedFilesMixin(object): return name path, filename = os.path.split(clean_name) root, ext = os.path.splitext(filename) - # Get the MD5 hash of the file - md5 = hashlib.md5() - for chunk in content.chunks(): - md5.update(chunk) - md5sum = md5.hexdigest()[:12] - hashed_name = os.path.join(path, u"%s.%s%s" % - (root, md5sum, ext)) + file_hash = self.file_hash(clean_name, content) + if file_hash is not None: + file_hash = u".%s" % file_hash + hashed_name = os.path.join(path, u"%s%s%s" % + (root, file_hash, ext)) unparsed_name = list(parsed_name) unparsed_name[2] = hashed_name # Special casing for a @font-face hack, like url(myfont.eot?#iefix") diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 1dbd00b299..2bdf825316 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -348,6 +348,15 @@ CachedStaticFilesStorage :setting:`CACHES` setting named ``'staticfiles'``. It falls back to using the ``'default'`` cache backend. + .. method:: file_hash(name, content=None) + + .. versionadded:: 1.5 + + The method that is used when creating the hashed name of a file. + Needs to return a hash for the given file name and content. + By default it calculates a MD5 hash from the content's chunks as + mentioned above. + .. _`far future Expires headers`: http://developer.yahoo.com/performance/rules.html#expires .. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import .. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri diff --git a/tests/regressiontests/staticfiles_tests/storage.py b/tests/regressiontests/staticfiles_tests/storage.py index 8358f833c8..4d49e6fbb2 100644 --- a/tests/regressiontests/staticfiles_tests/storage.py +++ b/tests/regressiontests/staticfiles_tests/storage.py @@ -1,5 +1,6 @@ from datetime import datetime from django.core.files import storage +from django.contrib.staticfiles.storage import CachedStaticFilesStorage class DummyStorage(storage.Storage): """ @@ -17,3 +18,9 @@ class DummyStorage(storage.Storage): def modified_time(self, name): return datetime.date(1970, 1, 1) + + +class SimpleCachedStaticFilesStorage(CachedStaticFilesStorage): + + def file_hash(self, name, content=None): + return 'deploy12345' diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 7f30cb987a..0c247e4a17 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -10,7 +10,7 @@ from StringIO import StringIO from django.template import loader, Context from django.conf import settings -from django.core.cache.backends.base import BaseCache, CacheKeyWarning +from django.core.cache.backends.base import BaseCache from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage from django.core.management import call_command @@ -515,6 +515,44 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, self.assertEqual(cache_key, 'staticfiles:e95bbc36387084582df2a70750d7b351') +# we set DEBUG to False here since the template tag wouldn't work otherwise +@override_settings(**dict(TEST_SETTINGS, + STATICFILES_STORAGE='regressiontests.staticfiles_tests.storage.SimpleCachedStaticFilesStorage', + DEBUG=False, +)) +class TestCollectionSimpleCachedStorage(BaseCollectionTestCase, + BaseStaticFilesTestCase, TestCase): + """ + Tests for the Cache busting storage + """ + def cached_file_path(self, path): + fullpath = self.render_template(self.static_template_snippet(path)) + return fullpath.replace(settings.STATIC_URL, '') + + def test_template_tag_return(self): + """ + Test the CachedStaticFilesStorage backend. + """ + self.assertStaticRaises(ValueError, + "does/not/exist.png", + "/static/does/not/exist.png") + self.assertStaticRenders("test/file.txt", + "/static/test/file.deploy12345.txt") + self.assertStaticRenders("cached/styles.css", + "/static/cached/styles.deploy12345.css") + self.assertStaticRenders("path/", + "/static/path/") + self.assertStaticRenders("path/?query", + "/static/path/?query") + + def test_template_tag_simple_content(self): + relpath = self.cached_file_path("cached/styles.css") + self.assertEqual(relpath, "cached/styles.deploy12345.css") + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + self.assertNotIn("cached/other.css", content) + self.assertIn("other.deploy12345.css", content) + if sys.platform != 'win32': class TestCollectionLinks(CollectionTestCase, TestDefaults): From 59d2b8aa16126d8d0a453f07d5c3497048bcd6b0 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Thu, 3 May 2012 21:52:56 +0300 Subject: [PATCH 03/95] Fix issue #18267 - document `settings.configured` property. --- docs/topics/settings.txt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index 52cc06da56..88fa7b6864 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -220,7 +220,7 @@ In this example, default settings are taken from ``myapp_defaults``, and the The following example, which uses ``myapp_defaults`` as a positional argument, is equivalent:: - settings.configure(myapp_defaults, DEBUG = True) + settings.configure(myapp_defaults, DEBUG=True) Normally, you will not need to override the defaults in this fashion. The Django defaults are sufficiently tame that you can safely use them. Be aware @@ -242,7 +242,16 @@ is accessed. If you set ``DJANGO_SETTINGS_MODULE``, access settings values somehow, *then* call ``configure()``, Django will raise a ``RuntimeError`` indicating -that settings have already been configured. +that settings have already been configured. There is a property just for this +purpose: + +.. attribute: django.conf.settings.configured + +For example:: + + from django.conf import settings + if not settings.configured: + settings.configure(myapp_defaults, DEBUG=True) Also, it's an error to call ``configure()`` more than once, or to call ``configure()`` after any setting has been accessed. From e656ca9ab87efa040c694221ac9d6143f42c9887 Mon Sep 17 00:00:00 2001 From: Boo Date: Wed, 16 May 2012 18:47:25 +0300 Subject: [PATCH 04/95] Fixing my e-mail duplicates. --- AUTHORS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 7e771c2c4d..c81f2fbcb8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -93,7 +93,7 @@ answer newbie questions, and generally made Django that much better: Simon Blanchard David Blewett Matt Boersma - boobsd@gmail.com + Artem Gnilov Matías Bordese Nate Bragg Sean Brant @@ -211,7 +211,6 @@ answer newbie questions, and generally made Django that much better: Dimitris Glezos glin@seznam.cz martin.glueck@gmail.com - Artyom Gnilov Ben Godfrey GomoX Guilherme Mesquita Gondim From c647065b490667a6691a1c358d471def9ad72b60 Mon Sep 17 00:00:00 2001 From: Travis Swicegood Date: Wed, 16 May 2012 13:32:53 -0500 Subject: [PATCH 05/95] Make sure these functions don't bleed into the global scope This makes sure that all of these functions are assigned to variables assigned to the current scope, rather than the global scope. It also adds a trailing semi-colon to make sure various linters are happy. --- django/contrib/admin/static/admin/js/actions.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js index 94aa6db934..8e46eb187c 100644 --- a/django/contrib/admin/static/admin/js/actions.js +++ b/django/contrib/admin/static/admin/js/actions.js @@ -3,7 +3,7 @@ var options = $.extend({}, $.fn.actions.defaults, opts); var actionCheckboxes = $(this); var list_editable_changed = false; - checker = function(checked) { + var checker = function(checked) { if (checked) { showQuestion(); } else { @@ -11,7 +11,7 @@ } $(actionCheckboxes).attr("checked", checked) .parent().parent().toggleClass(options.selectedClass, checked); - } + }, updateCounter = function() { var sel = $(actionCheckboxes).filter(":checked").length; $(options.counterContainer).html(interpolate( @@ -29,30 +29,30 @@ } return value; }); - } + }, showQuestion = function() { $(options.acrossClears).hide(); $(options.acrossQuestions).show(); $(options.allContainer).hide(); - } + }, showClear = function() { $(options.acrossClears).show(); $(options.acrossQuestions).hide(); $(options.actionContainer).toggleClass(options.selectedClass); $(options.allContainer).show(); $(options.counterContainer).hide(); - } + }, reset = function() { $(options.acrossClears).hide(); $(options.acrossQuestions).hide(); $(options.allContainer).hide(); $(options.counterContainer).show(); - } + }, clearAcross = function() { reset(); $(options.acrossInput).val(0); $(options.actionContainer).removeClass(options.selectedClass); - } + }; // Show counter by default $(options.counterContainer).show(); // Check state of checkboxes and reinit state if needed From 67fd30e9abdf0ad0fc95fd4d2d5bd7ba35a74038 Mon Sep 17 00:00:00 2001 From: Travis Swicegood Date: Wed, 16 May 2012 13:38:02 -0500 Subject: [PATCH 06/95] Remove `var` declaration---this variable is already delcared as an argument --- django/contrib/admin/static/admin/js/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js index 8e46eb187c..1c34a8ee70 100644 --- a/django/contrib/admin/static/admin/js/actions.js +++ b/django/contrib/admin/static/admin/js/actions.js @@ -81,7 +81,7 @@ }); lastChecked = null; $(actionCheckboxes).click(function(event) { - if (!event) { var event = window.event; } + if (!event) { event = window.event; } var target = event.target ? event.target : event.srcElement; if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) { var inrange = false; From 6e3b9962cce022a611fb84fbde11ad274d29a341 Mon Sep 17 00:00:00 2001 From: Travis Swicegood Date: Wed, 16 May 2012 13:38:23 -0500 Subject: [PATCH 07/95] Add missing semi-colons --- django/contrib/admin/static/admin/js/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js index 1c34a8ee70..91baa1d822 100644 --- a/django/contrib/admin/static/admin/js/actions.js +++ b/django/contrib/admin/static/admin/js/actions.js @@ -124,7 +124,7 @@ } } }); - } + }; /* Setup plugin defaults */ $.fn.actions.defaults = { actionContainer: "div.actions", @@ -135,5 +135,5 @@ acrossClears: "div.actions span.clear", allToggle: "#action-toggle", selectedClass: "selected" - } + }; })(django.jQuery); From f92c7c5df2f6f09e3c5d4112d950c60738dea7f8 Mon Sep 17 00:00:00 2001 From: Travis Swicegood Date: Wed, 16 May 2012 13:40:24 -0500 Subject: [PATCH 08/95] Swap out to === for the true comparison to avoid possible coercion issues --- django/contrib/admin/static/admin/js/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js index 91baa1d822..8494970aa1 100644 --- a/django/contrib/admin/static/admin/js/actions.js +++ b/django/contrib/admin/static/admin/js/actions.js @@ -83,7 +83,7 @@ $(actionCheckboxes).click(function(event) { if (!event) { event = window.event; } var target = event.target ? event.target : event.srcElement; - if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) { + if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) { var inrange = false; $(lastChecked).attr("checked", target.checked) .parent().parent().toggleClass(options.selectedClass, target.checked); From eb0140bddca4d25be2bf02144f43b1b12b0cbed3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 16 May 2012 17:56:56 +0200 Subject: [PATCH 09/95] Hidden __pycache__ dirs for FilePathField. Refs #17393. This will be tested as soon as tests will run under Python 3. Patch taken from Vinay Sajip's Python 3 branch. --- django/forms/fields.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/django/forms/fields.py b/django/forms/fields.py index 61137f55c1..53250cc8a9 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -932,12 +932,16 @@ class FilePathField(ChoiceField): self.choices.append((f, f.replace(path, "", 1))) if self.allow_folders: for f in dirs: + if f == '__pycache__': + continue if self.match is None or self.match_re.search(f): f = os.path.join(root, f) self.choices.append((f, f.replace(path, "", 1))) else: try: for f in sorted(os.listdir(self.path)): + if f == '__pycache__': + continue full_file = os.path.join(self.path, f) if (((self.allow_files and os.path.isfile(full_file)) or (self.allow_folders and os.path.isdir(full_file))) and From 006c2b8fc166e53d4ffccda9c42ef9fc109e1c8d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 17 May 2012 11:05:38 +0200 Subject: [PATCH 10/95] Fixed #18326 -- Stripped ending chars in LiveServerViews tests. Ending chars might be different depending on git crlf setting. Thanks Michael Manfre for the report and the patch. --- AUTHORS | 1 + tests/regressiontests/servers/tests.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 7e771c2c4d..c52ca0b2eb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -347,6 +347,7 @@ answer newbie questions, and generally made Django that much better: Frantisek Malina Mike Malone Martin Maney + Michael Manfre masonsimon+django@gmail.com Manuzhai Petr Marhoun diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py index 5d287a195b..9537e1feb3 100644 --- a/tests/regressiontests/servers/tests.py +++ b/tests/regressiontests/servers/tests.py @@ -113,7 +113,7 @@ class LiveServerViews(LiveServerBase): Refs #2879. """ f = self.urlopen('/example_view/') - self.assertEqual(f.read(), 'example view') + self.assertEqual(f.read(), b'example view') def test_static_files(self): """ @@ -121,7 +121,7 @@ class LiveServerViews(LiveServerBase): Refs #2879. """ f = self.urlopen('/static/example_static_file.txt') - self.assertEqual(f.read(), 'example static file\n') + self.assertEqual(f.read().rstrip(b'\r\n'), b'example static file') def test_media_files(self): """ @@ -129,7 +129,7 @@ class LiveServerViews(LiveServerBase): Refs #2879. """ f = self.urlopen('/media/example_media_file.txt') - self.assertEqual(f.read(), 'example media file\n') + self.assertEqual(f.read().rstrip(b'\r\n'), b'example media file') class LiveServerDatabase(LiveServerBase): From 009e237cf0c25ce13e73d58feb54ef4f86e47e4e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 17 May 2012 13:29:52 +0200 Subject: [PATCH 11/95] Fixed #17535 -- Optimized list generic views. When allow_empty is False, prevented the view from loading the entire queryset in memory when pagination is enabled. --- django/views/generic/dates.py | 2 +- django/views/generic/list.py | 18 ++++++++++++++---- tests/regressiontests/generic_views/list.py | 10 ++++++++++ tests/regressiontests/generic_views/urls.py | 2 ++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 6964624516..00eadb71d5 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -273,7 +273,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): if not allow_empty: # When pagination is enabled, it's better to do a cheap query # than to load the unpaginated queryset in memory. - is_empty = not bool(qs) if paginate_by is None else not qs.exists() + is_empty = len(qs) == 0 if paginate_by is None else not qs.exists() if is_empty: raise Http404(_(u"No %(verbose_name_plural)s available") % { 'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural) diff --git a/django/views/generic/list.py b/django/views/generic/list.py index d4664c34ef..50127066a1 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -16,7 +16,7 @@ class MultipleObjectMixin(ContextMixin): def get_queryset(self): """ - Get the list of items for this view. This must be an interable, and may + Get the list of items for this view. This must be an iterable, and may be a queryset (in which qs-specific behavior will be enabled). """ if self.queryset is not None: @@ -113,9 +113,19 @@ class BaseListView(MultipleObjectMixin, View): def get(self, request, *args, **kwargs): self.object_list = self.get_queryset() allow_empty = self.get_allow_empty() - if not allow_empty and len(self.object_list) == 0: - raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.") - % {'class_name': self.__class__.__name__}) + + if not allow_empty: + # When pagination is enabled and object_list is a queryset, + # it's better to do a cheap query than to load the unpaginated + # queryset in memory. + if (self.get_paginate_by(self.object_list) is not None + and hasattr(self.object_list, 'exists')): + is_empty = not self.object_list.exists() + else: + is_empty = len(self.object_list) == 0 + if is_empty: + raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.") + % {'class_name': self.__class__.__name__}) context = self.get_context_data(object_list=self.object_list) return self.render_to_response(context) diff --git a/tests/regressiontests/generic_views/list.py b/tests/regressiontests/generic_views/list.py index 9ad00edc3b..b925758524 100644 --- a/tests/regressiontests/generic_views/list.py +++ b/tests/regressiontests/generic_views/list.py @@ -159,6 +159,16 @@ class ListViewTests(TestCase): def test_missing_items(self): self.assertRaises(ImproperlyConfigured, self.client.get, '/list/authors/invalid/') + def test_paginated_list_view_does_not_load_entire_table(self): + # Regression test for #17535 + self._make_authors(3) + # 1 query for authors + with self.assertNumQueries(1): + self.client.get('/list/authors/notempty/') + # same as above + 1 query to test if authors exist + 1 query for pagination + with self.assertNumQueries(3): + self.client.get('/list/authors/notempty/paginated/') + def _make_authors(self, n): Author.objects.all().delete() for i in range(n): diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py index 9c47ab8d82..20432f9b6a 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -128,6 +128,8 @@ urlpatterns = patterns('', views.AuthorList.as_view(paginate_by=30)), (r'^list/authors/notempty/$', views.AuthorList.as_view(allow_empty=False)), + (r'^list/authors/notempty/paginated/$', + views.AuthorList.as_view(allow_empty=False, paginate_by=2)), (r'^list/authors/template_name/$', views.AuthorList.as_view(template_name='generic_views/list.html')), (r'^list/authors/template_name_suffix/$', From c4996df16c58b46844d2e046bca5a3d41dfcc122 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 17 May 2012 13:54:51 +0200 Subject: [PATCH 12/95] Fixed #17449 -- Added OPTIONS to generic views. Thanks estebistec for the report and patch. --- django/views/generic/base.py | 12 ++++- tests/regressiontests/generic_views/base.py | 52 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/django/views/generic/base.py b/django/views/generic/base.py index c32a58a349..7d3649cbff 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -79,14 +79,22 @@ class View(object): return handler(request, *args, **kwargs) def http_method_not_allowed(self, request, *args, **kwargs): - allowed_methods = [m for m in self.http_method_names if hasattr(self, m)] logger.warning('Method Not Allowed (%s): %s', request.method, request.path, extra={ 'status_code': 405, 'request': self.request } ) - return http.HttpResponseNotAllowed(allowed_methods) + return http.HttpResponseNotAllowed(self._allowed_methods()) + + def options(self, request, *args, **kwargs): + response = http.HttpResponse() + response['Allow'] = ', '.join(self._allowed_methods()) + response['Content-Length'] = 0 + return response + + def _allowed_methods(self): + return [m.upper() for m in self.http_method_names if hasattr(self, m)] class TemplateResponseMixin(object): diff --git a/tests/regressiontests/generic_views/base.py b/tests/regressiontests/generic_views/base.py index e18ed2a2a7..fc3dc05fd7 100644 --- a/tests/regressiontests/generic_views/base.py +++ b/tests/regressiontests/generic_views/base.py @@ -173,6 +173,58 @@ class ViewTest(unittest.TestCase): """ self.assertTrue(DecoratedDispatchView.as_view().is_decorated) + def test_head_no_get(self): + """ + Test that a view class with no get responds to a HEAD request with HTTP + 405. + """ + request = self.rf.head('/') + view = PostOnlyView.as_view() + self.assertEqual(405, view(request).status_code) + + def test_options(self): + """ + Test that views respond to HTTP OPTIONS requests with an Allow header + appropriate for the methods implemented by the view class. + """ + request = self.rf.options('/') + view = SimpleView.as_view() + response = view(request) + self.assertEqual(200, response.status_code) + self.assertTrue(response['Allow']) + + def test_options_for_get_view(self): + """ + Test that a view implementing GET allows GET and HEAD. + """ + request = self.rf.options('/') + view = SimpleView.as_view() + response = view(request) + self._assert_allows(response, 'GET', 'HEAD') + + def test_options_for_get_and_post_view(self): + """ + Test that a view implementing GET and POST allows GET, HEAD, and POST. + """ + request = self.rf.options('/') + view = SimplePostView.as_view() + response = view(request) + self._assert_allows(response, 'GET', 'HEAD', 'POST') + + def test_options_for_post_view(self): + """ + Test that a view implementing POST allows POST. + """ + request = self.rf.options('/') + view = PostOnlyView.as_view() + response = view(request) + self._assert_allows(response, 'POST') + + def _assert_allows(self, response, *expected_methods): + "Assert allowed HTTP methods reported in the Allow response header" + response_allows = set(response['Allow'].split(', ')) + self.assertEqual(set(expected_methods + ('OPTIONS',)), response_allows) + class TemplateViewTest(TestCase): urls = 'regressiontests.generic_views.urls' From 82a76ef67d944b1a4507cac81476bebba0c90e4a Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 17 May 2012 14:03:19 +0200 Subject: [PATCH 13/95] Mentioned the previous commit in the release notes --- docs/releases/1.5.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index be06fe58d8..51e64bd202 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -55,6 +55,8 @@ Django 1.5 also includes several smaller improvements worth noting: * :mod:`django.utils.timezone` provides a helper for converting aware datetimes between time zones. See :func:`~django.utils.timezone.localtime`. +* The generic views support OPTIONS requests. + * In the localflavor for Canada, "pq" was added to the acceptable codes for Quebec. It's an old abbreviation. From dcd4383107d96c18bcb53312ca4de070374b334c Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 17 May 2012 16:02:05 +0200 Subject: [PATCH 14/95] Fixed #9893 -- Validated the length of file names after the full file name is generated by the storage class. Thanks Refefer for the report, carsongee for the patch, and everyone else involved in the discussion. --- django/db/models/fields/files.py | 21 +++++++++++++++++++++ tests/regressiontests/model_fields/tests.py | 12 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 6f1f183d4f..34cf84dbc0 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -3,6 +3,7 @@ import os from django import forms from django.db.models.fields import Field +from django.core.exceptions import ValidationError from django.core.files.base import File from django.core.files.storage import default_storage from django.core.files.images import ImageFile @@ -204,6 +205,11 @@ class FileDescriptor(object): instance.__dict__[self.field.name] = value class FileField(Field): + + default_error_messages = { + 'max_length': _(u'Filename is %(extra)d characters too long.') + } + # The class to wrap instance attributes in. Accessing the file object off # the instance will always return an instance of attr_class. attr_class = FieldFile @@ -226,6 +232,21 @@ class FileField(Field): kwargs['max_length'] = kwargs.get('max_length', 100) super(FileField, self).__init__(verbose_name, name, **kwargs) + def validate(self, value, model_instance): + """ + Validates that the generated file name still fits within max_length. + """ + # The generated file name stored in the database is generally longer + # than the uploaded file name. Using the length of generated name in + # the error message would be confusing. However, in the common case + # (ie. upload_to='path/to/upload/dir'), the length of the generated + # name equals the length of the uploaded name plus a constant. Thus + # we can tell the user how much shorter the name should be (roughly). + length = len(self.generate_filename(model_instance, value.name)) + if self.max_length and length > self.max_length: + error_values = {'extra': length - self.max_length} + raise ValidationError(self.error_messages['max_length'] % error_values) + def get_internal_type(self): return "FileField" diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index ea1e1c7d99..4b48616e99 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -365,3 +365,15 @@ class FileFieldTests(unittest.TestCase): field = d._meta.get_field('myfile') field.save_form_data(d, 'else.txt') self.assertEqual(d.myfile, 'else.txt') + + def test_max_length(self): + """ + Test that FileField validates the length of the generated file name + that will be stored in the database. Regression for #9893. + + """ + # upload_to = 'unused', so file names are saved as 'unused/xxxxx'. + # max_length = 100, so names longer than 93 characters are rejected. + Document(myfile=93 * 'x').full_clean() + with self.assertRaises(ValidationError): + Document(myfile=94 * 'x').full_clean() From 20e697368219603599649bc67aea5e087caedeae Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 17 May 2012 17:41:37 +0200 Subject: [PATCH 15/95] Fixed #18323 -- Refactored date arithmetic in date based generic views, in order to deal properly with both DateFields and DateTimeFields. --- django/views/generic/dates.py | 196 +++++++++++++++++++++++----------- 1 file changed, 136 insertions(+), 60 deletions(-) diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 00eadb71d5..a634d170c2 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -23,7 +23,9 @@ class YearMixin(object): return self.year_format def get_year(self): - "Return the year for which this view should display data" + """ + Return the year for which this view should display data. + """ year = self.year if year is None: try: @@ -35,6 +37,20 @@ class YearMixin(object): raise Http404(_(u"No year specified")) return year + def _get_next_year(self, date): + """ + Return the start date of the next interval. + + The interval is defined by start date <= item date < next start date. + """ + return date.replace(year=date.year + 1, month=1, day=1) + + def _get_current_year(self, date): + """ + Return the start date of the current interval. + """ + return date.replace(month=1, day=1) + class MonthMixin(object): month_format = '%b' @@ -48,7 +64,9 @@ class MonthMixin(object): return self.month_format def get_month(self): - "Return the month for which this view should display data" + """ + Return the month for which this view should display data. + """ month = self.month if month is None: try: @@ -64,20 +82,30 @@ class MonthMixin(object): """ Get the next valid month. """ - # next must be the first day of the next month. - if date.month == 12: - next = date.replace(year=date.year + 1, month=1, day=1) - else: - next = date.replace(month=date.month + 1, day=1) - return _get_next_prev(self, next, is_previous=False, period='month') + return _get_next_prev(self, date, is_previous=False, period='month') def get_previous_month(self, date): """ Get the previous valid month. """ - # prev must be the last day of the previous month. - prev = date.replace(day=1) - datetime.timedelta(days=1) - return _get_next_prev(self, prev, is_previous=True, period='month') + return _get_next_prev(self, date, is_previous=True, period='month') + + def _get_next_month(self, date): + """ + Return the start date of the next interval. + + The interval is defined by start date <= item date < next start date. + """ + if date.month == 12: + return date.replace(year=date.year + 1, month=1, day=1) + else: + return date.replace(month=date.month + 1, day=1) + + def _get_current_month(self, date): + """ + Return the start date of the previous interval. + """ + return date.replace(day=1) class DayMixin(object): @@ -92,7 +120,9 @@ class DayMixin(object): return self.day_format def get_day(self): - "Return the day for which this view should display data" + """ + Return the day for which this view should display data. + """ day = self.day if day is None: try: @@ -108,15 +138,27 @@ class DayMixin(object): """ Get the next valid day. """ - next = date + datetime.timedelta(days=1) - return _get_next_prev(self, next, is_previous=False, period='day') + return _get_next_prev(self, date, is_previous=False, period='day') def get_previous_day(self, date): """ Get the previous valid day. """ - prev = date - datetime.timedelta(days=1) - return _get_next_prev(self, prev, is_previous=True, period='day') + return _get_next_prev(self, date, is_previous=True, period='day') + + def _get_next_day(self, date): + """ + Return the start date of the next interval. + + The interval is defined by start date <= item date < next start date. + """ + return date + datetime.timedelta(days=1) + + def _get_current_day(self, date): + """ + Return the start date of the current interval. + """ + return date class WeekMixin(object): @@ -131,7 +173,9 @@ class WeekMixin(object): return self.week_format def get_week(self): - "Return the week for which this view should display data" + """ + Return the week for which this view should display data + """ week = self.week if week is None: try: @@ -147,19 +191,34 @@ class WeekMixin(object): """ Get the next valid week. """ - # next must be the first day of the next week. - next = date + datetime.timedelta(days=7 - self._get_weekday(date)) - return _get_next_prev(self, next, is_previous=False, period='week') + return _get_next_prev(self, date, is_previous=False, period='week') def get_previous_week(self, date): """ Get the previous valid week. """ - # prev must be the last day of the previous week. - prev = date - datetime.timedelta(days=self._get_weekday(date) + 1) - return _get_next_prev(self, prev, is_previous=True, period='week') + return _get_next_prev(self, date, is_previous=True, period='week') + + def _get_next_week(self, date): + """ + Return the start date of the next interval. + + The interval is defined by start date <= item date < next start date. + """ + return date + datetime.timedelta(days=7 - self._get_weekday(date)) + + def _get_current_week(self, date): + """ + Return the start date of the current interval. + """ + return date - datetime.timedelta(self._get_weekday(date)) def _get_weekday(self, date): + """ + Return the weekday for a given date. + + The first day according to the week format is 0 and the last day is 6. + """ week_format = self.get_week_format() if week_format == '%W': # week starts on Monday return date.weekday() @@ -168,6 +227,7 @@ class WeekMixin(object): else: raise ValueError("unknown week format: %s" % week_format) + class DateMixin(object): """ Mixin class for views manipulating date-based data. @@ -267,7 +327,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): paginate_by = self.get_paginate_by(qs) if not allow_future: - now = timezone.now() if self.uses_datetime_field else datetime.date.today() + now = timezone.now() if self.uses_datetime_field else timezone_today() qs = qs.filter(**{'%s__lte' % date_field: now}) if not allow_empty: @@ -344,7 +404,7 @@ class BaseYearArchiveView(YearMixin, BaseDateListView): date = _date_from_string(year, self.get_year_format()) since = self._make_date_lookup_arg(date) - until = self._make_date_lookup_arg(datetime.date(date.year + 1, 1, 1)) + until = self._make_date_lookup_arg(self._get_next_year(date)) lookup_kwargs = { '%s__gte' % date_field: since, '%s__lt' % date_field: until, @@ -392,12 +452,8 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView): date = _date_from_string(year, self.get_year_format(), month, self.get_month_format()) - # Construct a date-range lookup. since = self._make_date_lookup_arg(date) - if date.month == 12: - until = self._make_date_lookup_arg(datetime.date(date.year + 1, 1, 1)) - else: - until = self._make_date_lookup_arg(datetime.date(date.year, date.month + 1, 1)) + until = self._make_date_lookup_arg(self._get_next_month(date)) lookup_kwargs = { '%s__gte' % date_field: since, '%s__lt' % date_field: until, @@ -442,9 +498,8 @@ class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView): week_start, '%w', week, week_format) - # Construct a date-range lookup. since = self._make_date_lookup_arg(date) - until = self._make_date_lookup_arg(date + datetime.timedelta(days=7)) + until = self._make_date_lookup_arg(self._get_next_week(date)) lookup_kwargs = { '%s__gte' % date_field: since, '%s__lt' % date_field: until, @@ -585,22 +640,22 @@ def _date_from_string(year, year_format, month='', month_format='', day='', day_ }) -def _get_next_prev(generic_view, naive_result, is_previous, period): +def _get_next_prev(generic_view, date, is_previous, period): """ Helper: Get the next or the previous valid date. The idea is to allow links on month/day views to never be 404s by never providing a date that'll be invalid for the given view. - This is a bit complicated since it handles both next and previous months - and days (for MonthArchiveView and DayArchiveView); hence the coupling to generic_view. + This is a bit complicated since it handles different intervals of time, + hence the coupling to generic_view. However in essence the logic comes down to: * If allow_empty and allow_future are both true, this is easy: just - return the naive result (just the next/previous day or month, + return the naive result (just the next/previous day/week/month, reguardless of object existence.) - * If allow_empty is true, allow_future is false, and the naive month + * If allow_empty is true, allow_future is false, and the naive result isn't in the future, then return it; otherwise return None. * If allow_empty is false and allow_future is true, return the next @@ -616,9 +671,23 @@ def _get_next_prev(generic_view, naive_result, is_previous, period): allow_empty = generic_view.get_allow_empty() allow_future = generic_view.get_allow_future() - # If allow_empty is True the naive value will be valid + get_current = getattr(generic_view, '_get_current_%s' % period) + get_next = getattr(generic_view, '_get_next_%s' % period) + + # Bounds of the current interval + start, end = get_current(date), get_next(date) + + # If allow_empty is True, the naive result will be valid if allow_empty: - result = naive_result + if is_previous: + result = get_current(start - datetime.timedelta(days=1)) + else: + result = end + + if allow_future or result <= timezone_today(): + return result + else: + return None # Otherwise, we'll need to go to the database to look for an object # whose date_field is at least (greater than/less than) the given @@ -627,12 +696,22 @@ def _get_next_prev(generic_view, naive_result, is_previous, period): # Construct a lookup and an ordering depending on whether we're doing # a previous date or a next date lookup. if is_previous: - lookup = {'%s__lte' % date_field: generic_view._make_date_lookup_arg(naive_result)} + lookup = {'%s__lt' % date_field: generic_view._make_date_lookup_arg(start)} ordering = '-%s' % date_field else: - lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(naive_result)} + lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(end)} ordering = date_field + # Filter out objects in the future if appropriate. + if not allow_future: + # Fortunately, to match the implementation of allow_future, + # we need __lte, which doesn't conflict with __lt above. + if generic_view.uses_datetime_field: + now = timezone.now() + else: + now = timezone_today() + lookup['%s__lte' % date_field] = now + qs = generic_view.get_queryset().filter(**lookup).order_by(ordering) # Snag the first object from the queryset; if it doesn't exist that @@ -640,26 +719,23 @@ def _get_next_prev(generic_view, naive_result, is_previous, period): try: result = getattr(qs[0], date_field) except IndexError: - result = None + return None - # Convert datetimes to a dates - if result and generic_view.uses_datetime_field: - if settings.USE_TZ: - result = timezone.localtime(result) - result = result.date() + # Convert datetimes to dates in the current time zone. + if generic_view.uses_datetime_field: + if settings.USE_TZ: + result = timezone.localtime(result) + result = result.date() - if result: - if period == 'month': - # first day of the month - result = result.replace(day=1) - elif period == 'week': - # monday of the week - result = result - datetime.timedelta(days=generic_view._get_weekday(result)) - elif period != 'day': - raise ValueError('invalid period: %s' % period) + # Return the first day of the period. + return get_current(result) - # Check against future dates. - if result and (allow_future or result < datetime.date.today()): - return result + +def timezone_today(): + """ + Return the current date in the current time zone. + """ + if settings.USE_TZ: + return timezone.localtime(timezone.now()).date() else: - return None + return datetime.date.today() From ab268e18486b244f6633f2e013c12b6664a3661a Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 17 May 2012 17:53:19 +0200 Subject: [PATCH 16/95] Added a test for DayArchiveView. Refs #17192. --- tests/regressiontests/generic_views/dates.py | 7 +++++++ tests/regressiontests/generic_views/urls.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/tests/regressiontests/generic_views/dates.py b/tests/regressiontests/generic_views/dates.py index 98c089f2b4..59833a780c 100644 --- a/tests/regressiontests/generic_views/dates.py +++ b/tests/regressiontests/generic_views/dates.py @@ -446,6 +446,13 @@ class DayArchiveViewTests(TestCase): self.assertEqual(res.context['next_day'], future) self.assertEqual(res.context['previous_day'], datetime.date(2006, 5, 1)) + # allow_future for yesterday, next_day is today (#17192) + today = datetime.date.today() + yesterday = today - datetime.timedelta(days=1) + res = self.client.get('/dates/books/%s/allow_empty_and_future/' + % yesterday.strftime('%Y/%b/%d').lower()) + self.assertEqual(res.context['next_day'], today) + def test_day_view_paginated(self): res = self.client.get('/dates/books/2008/oct/1/') self.assertEqual(res.status_code, 200) diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py index 20432f9b6a..96d77e7ed1 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -204,6 +204,8 @@ urlpatterns = patterns('', 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})/allow_empty_and_future/$', + views.BookDayArchive.as_view(allow_empty=True, 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/$', From 4774875896dec4098dba2981d562bd74e75c463f Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 17 May 2012 22:04:47 +0200 Subject: [PATCH 17/95] Fixed #6916 -- Wrong spelling of Spanish province. --- django/contrib/localflavor/es/es_provinces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/localflavor/es/es_provinces.py b/django/contrib/localflavor/es/es_provinces.py index 9f5e12680b..51f681f4fc 100644 --- a/django/contrib/localflavor/es/es_provinces.py +++ b/django/contrib/localflavor/es/es_provinces.py @@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy as _ PROVINCE_CHOICES = ( - ('01', _('Arava')), + ('01', _('Araba')), ('02', _('Albacete')), ('03', _('Alacant')), ('04', _('Almeria')), From c23d306df83d226b4f3b5b852ec9fb22be12c61e Mon Sep 17 00:00:00 2001 From: Jeremy Cowgar Date: Thu, 17 May 2012 18:53:57 -0400 Subject: [PATCH 18/95] Added load i18n code to the base wizard form template documentation as it uses the trans tag. --- docs/ref/contrib/formtools/form-wizard.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index a3d9673db9..7aafbe89f3 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -186,6 +186,7 @@ Here's a full example template: .. code-block:: html+django {% extends "base.html" %} + {% load i18n %} {% block head %} {{ wizard.form.media }} From 7549de841c6e0d18822af047763f5e3211539be8 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 18 May 2012 12:08:36 +0200 Subject: [PATCH 19/95] Fixed #18334 -- Fixed detection of supports_stddev backend feature. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to Michael Manfre for the report and Anssi Kääriäinen for the review. --- django/db/backends/__init__.py | 12 +++++++----- tests/regressiontests/backends/tests.py | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 27623505e5..bf622b9a0f 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -414,10 +414,11 @@ class BaseDatabaseFeatures(object): def confirm(self): "Perform manual checks of any database features that might vary between installs" - self._confirmed = True - self.supports_transactions = self._supports_transactions() - self.supports_stddev = self._supports_stddev() - self.can_introspect_foreign_keys = self._can_introspect_foreign_keys() + if not self._confirmed: + self._confirmed = True + self.supports_transactions = self._supports_transactions() + self.supports_stddev = self._supports_stddev() + self.can_introspect_foreign_keys = self._can_introspect_foreign_keys() def _supports_transactions(self): "Confirm support for transactions" @@ -439,8 +440,9 @@ class BaseDatabaseFeatures(object): try: self.connection.ops.check_aggregate_support(StdDevPop()) + return True except NotImplementedError: - self.supports_stddev = False + return False def _can_introspect_foreign_keys(self): "Confirm support for introspected foreign keys" diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index eec817c5a2..10e69b65c1 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -397,6 +397,12 @@ class BackendTestCase(TestCase): self.assertTrue(hasattr(connection.ops, 'connection')) self.assertEqual(connection, connection.ops.connection) + def test_supports_needed_confirm(self): + connection.features.confirm() + self.assertIn(connection.features.supports_transactions, (True, False)) + self.assertIn(connection.features.supports_stddev, (True, False)) + self.assertIn(connection.features.can_introspect_foreign_keys, (True, False)) + def test_duplicate_table_error(self): """ Test that creating an existing table returns a DatabaseError """ cursor = connection.cursor() From 5d80d30a237725109d3d822d99e82a34e36f1a22 Mon Sep 17 00:00:00 2001 From: Stratos Moros Date: Fri, 18 May 2012 13:25:15 +0300 Subject: [PATCH 20/95] remove mention of djangoproject.com from cbv topic The class based views topic mentions that the djangoproject.com weblog is built using the date-based generic views, but looking at the code, it actually uses the deprecated pre-1.3 function based generic views. --- docs/topics/class-based-views.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index abac22000f..13c2b994e4 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -35,9 +35,6 @@ Django ships with generic views to do the following: * Present date-based objects in year/month/day archive pages, associated detail, and "latest" pages. - `The Django Weblog `_'s - year, month, and day archives are built with these, as would be a typical - newspaper's archives. * Allow users to create, update, and delete objects -- with or without authorization. From 45f55a9fccaac3240826aa993aba4247310be331 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 18 May 2012 13:45:42 +0200 Subject: [PATCH 21/95] Fixed broken ES localflavor test after 4774875. --- tests/regressiontests/localflavor/es/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressiontests/localflavor/es/tests.py b/tests/regressiontests/localflavor/es/tests.py index c989e3e9ca..c13eac6d61 100644 --- a/tests/regressiontests/localflavor/es/tests.py +++ b/tests/regressiontests/localflavor/es/tests.py @@ -31,7 +31,7 @@ class ESLocalFlavorTests(SimpleTestCase): def test_ESProvinceSelect(self): f = ESProvinceSelect() out = u'''', ) diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 780718b487..cbba7c19a3 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -822,7 +822,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): def test_second_call_doesnt_crash(self): err = StringIO.StringIO() management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False, stderr=err) - self.assertTrue("Cache table 'test cache table' could not be created" in err.getvalue()) + self.assertTrue(b"Cache table 'test cache table' could not be created" in err.getvalue()) @override_settings(USE_TZ=True) diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index d403951ae2..2d9b4f755f 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -47,7 +47,7 @@ class TestingHttpRequest(HttpRequest): class CsrfViewMiddlewareTest(TestCase): # The csrf token is potentially from an untrusted source, so could have # characters that need dealing with. - _csrf_id_cookie = "<1>\xc2\xa1" + _csrf_id_cookie = b"<1>\xc2\xa1" _csrf_id = "1" def _get_GET_no_csrf_cookie_request(self): diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index ce64c5de23..87035d96d7 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -127,7 +127,7 @@ class FileStorageTests(unittest.TestCase): """ self.assertFalse(self.storage.exists('test.file')) - f = ContentFile('custom contents') + f = ContentFile(b'custom contents') f_name = self.storage.save('test.file', f) atime = self.storage.accessed_time(f_name) @@ -143,7 +143,7 @@ class FileStorageTests(unittest.TestCase): """ self.assertFalse(self.storage.exists('test.file')) - f = ContentFile('custom contents') + f = ContentFile(b'custom contents') f_name = self.storage.save('test.file', f) ctime = self.storage.created_time(f_name) @@ -160,7 +160,7 @@ class FileStorageTests(unittest.TestCase): """ self.assertFalse(self.storage.exists('test.file')) - f = ContentFile('custom contents') + f = ContentFile(b'custom contents') f_name = self.storage.save('test.file', f) mtime = self.storage.modified_time(f_name) @@ -177,7 +177,7 @@ class FileStorageTests(unittest.TestCase): """ self.assertFalse(self.storage.exists('test.file')) - f = ContentFile('custom contents') + f = ContentFile(b'custom contents') f.name = 'test.file' storage_f_name = self.storage.save(None, f) @@ -194,11 +194,11 @@ class FileStorageTests(unittest.TestCase): """ self.assertFalse(self.storage.exists('path/to')) self.storage.save('path/to/test.file', - ContentFile('file saved with path')) + ContentFile(b'file saved with path')) self.assertTrue(self.storage.exists('path/to')) self.assertEqual(self.storage.open('path/to/test.file').read(), - 'file saved with path') + b'file saved with path') self.assertTrue(os.path.exists( os.path.join(self.temp_dir, 'path', 'to', 'test.file'))) @@ -211,7 +211,7 @@ class FileStorageTests(unittest.TestCase): """ self.assertFalse(self.storage.exists('test.file')) - f = ContentFile('custom contents') + f = ContentFile(b'custom contents') f_name = self.storage.save('test.file', f) self.assertEqual(self.storage.path(f_name), @@ -246,8 +246,8 @@ class FileStorageTests(unittest.TestCase): self.assertFalse(self.storage.exists('storage_test_2')) self.assertFalse(self.storage.exists('storage_dir_1')) - f = self.storage.save('storage_test_1', ContentFile('custom content')) - f = self.storage.save('storage_test_2', ContentFile('custom content')) + f = self.storage.save('storage_test_1', ContentFile(b'custom content')) + f = self.storage.save('storage_test_2', ContentFile(b'custom content')) os.mkdir(os.path.join(self.temp_dir, 'storage_dir_1')) dirs, files = self.storage.listdir('') @@ -304,18 +304,18 @@ class FileStorageTests(unittest.TestCase): os.makedirs = fake_makedirs self.storage.save('normal/test.file', - ContentFile('saved normally')) + ContentFile(b'saved normally')) self.assertEqual(self.storage.open('normal/test.file').read(), - 'saved normally') + b'saved normally') self.storage.save('raced/test.file', - ContentFile('saved with race')) + ContentFile(b'saved with race')) self.assertEqual(self.storage.open('raced/test.file').read(), - 'saved with race') + b'saved with race') # Check that OSErrors aside from EEXIST are still raised. self.assertRaises(OSError, - self.storage.save, 'error/test.file', ContentFile('not saved')) + self.storage.save, 'error/test.file', ContentFile(b'not saved')) finally: os.makedirs = real_makedirs @@ -341,16 +341,16 @@ class FileStorageTests(unittest.TestCase): try: os.remove = fake_remove - self.storage.save('normal.file', ContentFile('delete normally')) + self.storage.save('normal.file', ContentFile(b'delete normally')) self.storage.delete('normal.file') self.assertFalse(self.storage.exists('normal.file')) - self.storage.save('raced.file', ContentFile('delete with race')) + self.storage.save('raced.file', ContentFile(b'delete with race')) self.storage.delete('raced.file') self.assertFalse(self.storage.exists('normal.file')) # Check that OSErrors aside from ENOENT are still raised. - self.storage.save('error.file', ContentFile('delete with error')) + self.storage.save('error.file', ContentFile(b'delete with error')) self.assertRaises(OSError, self.storage.delete, 'error.file') finally: os.remove = real_remove @@ -374,9 +374,9 @@ class CustomStorageTests(FileStorageTests): storage_class = CustomStorage def test_custom_get_available_name(self): - first = self.storage.save('custom_storage', ContentFile('custom contents')) + first = self.storage.save('custom_storage', ContentFile(b'custom contents')) self.assertEqual(first, 'custom_storage') - second = self.storage.save('custom_storage', ContentFile('more contents')) + second = self.storage.save('custom_storage', ContentFile(b'more contents')) self.assertEqual(second, 'custom_storage.2') self.storage.delete(first) self.storage.delete(second) @@ -410,7 +410,7 @@ class FileSaveRaceConditionTest(unittest.TestCase): shutil.rmtree(self.storage_dir) def save_file(self, name): - name = self.storage.save(name, SlowFile("Data")) + name = self.storage.save(name, SlowFile(b"Data")) def test_race_condition(self): self.thread.start() @@ -433,7 +433,7 @@ class FileStoragePermissions(unittest.TestCase): shutil.rmtree(self.storage_dir) def test_file_upload_permissions(self): - name = self.storage.save("the_file", ContentFile("data")) + name = self.storage.save("the_file", ContentFile(b"data")) actual_mode = os.stat(self.storage.path(name))[0] & 0777 self.assertEqual(actual_mode, 0666) @@ -453,8 +453,8 @@ class FileStoragePathParsing(unittest.TestCase): sure we still mangle the file name instead of the directory name. """ - self.storage.save('dotted.path/test', ContentFile("1")) - self.storage.save('dotted.path/test', ContentFile("2")) + self.storage.save('dotted.path/test', ContentFile(b"1")) + self.storage.save('dotted.path/test', ContentFile(b"2")) self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test'))) @@ -465,8 +465,8 @@ class FileStoragePathParsing(unittest.TestCase): File names with a dot as their first character don't have an extension, and the underscore should get added to the end. """ - self.storage.save('dotted.path/.test', ContentFile("1")) - self.storage.save('dotted.path/.test', ContentFile("2")) + self.storage.save('dotted.path/.test', ContentFile(b"1")) + self.storage.save('dotted.path/.test', ContentFile(b"2")) self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test'))) self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1'))) @@ -542,11 +542,11 @@ class ContentFileTestCase(unittest.TestCase): Test that the constructor of ContentFile accepts 'name' (#16590). """ def test_content_file_default_name(self): - self.assertEqual(ContentFile("content").name, None) + self.assertEqual(ContentFile(b"content").name, None) def test_content_file_custom_name(self): name = "I can have a name too!" - self.assertEqual(ContentFile("content", name=name).name, name) + self.assertEqual(ContentFile(b"content", name=name).name, name) class NoNameFileTestCase(unittest.TestCase): """ diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py index de552d88ec..c8de8035f9 100644 --- a/tests/regressiontests/file_uploads/tests.py +++ b/tests/regressiontests/file_uploads/tests.py @@ -24,7 +24,7 @@ UNICODE_FILENAME = u'test-0123456789_中文_Orléans.jpg' class FileUploadTests(TestCase): def test_simple_upload(self): - with open(__file__) as fp: + with open(__file__, 'rb') as fp: post_data = { 'name': 'Ringo', 'file_field': fp, @@ -36,11 +36,11 @@ class FileUploadTests(TestCase): tdir = tempfile.gettempdir() file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir) - file1.write('a' * (2 ** 21)) + file1.write(b'a' * (2 ** 21)) file1.seek(0) file2 = tempfile.NamedTemporaryFile(suffix=".file2", dir=tdir) - file2.write('a' * (10 * 2 ** 20)) + file2.write(b'a' * (10 * 2 ** 20)) file2.seek(0) post_data = { @@ -89,7 +89,7 @@ class FileUploadTests(TestCase): # This file contains chinese symbols and an accented char in the name. with open(os.path.join(tdir, UNICODE_FILENAME.encode('utf-8')), 'w+b') as file1: - file1.write('b' * (2 ** 10)) + file1.write(b'b' * (2 ** 10)) file1.seek(0) post_data = { @@ -214,7 +214,7 @@ class FileUploadTests(TestCase): 'CONTENT_TYPE': client.MULTIPART_CONTENT, 'PATH_INFO': '/file_uploads/echo/', 'REQUEST_METHOD': 'POST', - 'wsgi.input': client.FakePayload(''), + 'wsgi.input': client.FakePayload(b''), } got = json.loads(self.client.request(**r).content) self.assertEqual(got, {}) @@ -222,12 +222,12 @@ class FileUploadTests(TestCase): def test_custom_upload_handler(self): # A small file (under the 5M quota) smallfile = tempfile.NamedTemporaryFile() - smallfile.write('a' * (2 ** 21)) + smallfile.write(b'a' * (2 ** 21)) smallfile.seek(0) # A big file (over the quota) bigfile = tempfile.NamedTemporaryFile() - bigfile.write('a' * (10 * 2 ** 20)) + bigfile.write(b'a' * (10 * 2 ** 20)) bigfile.seek(0) # Small file posting should work. @@ -242,7 +242,7 @@ class FileUploadTests(TestCase): def test_broken_custom_upload_handler(self): f = tempfile.NamedTemporaryFile() - f.write('a' * (2 ** 21)) + f.write(b'a' * (2 ** 21)) f.seek(0) # AttributeError: You cannot alter upload handlers after the upload has been processed. @@ -255,15 +255,15 @@ class FileUploadTests(TestCase): def test_fileupload_getlist(self): file1 = tempfile.NamedTemporaryFile() - file1.write('a' * (2 ** 23)) + file1.write(b'a' * (2 ** 23)) file1.seek(0) file2 = tempfile.NamedTemporaryFile() - file2.write('a' * (2 * 2 ** 18)) + file2.write(b'a' * (2 * 2 ** 18)) file2.seek(0) file2a = tempfile.NamedTemporaryFile() - file2a.write('a' * (5 * 2 ** 20)) + file2a.write(b'a' * (5 * 2 ** 20)) file2a.seek(0) response = self.client.post('/file_uploads/getlist_count/', { @@ -299,14 +299,14 @@ class FileUploadTests(TestCase): # this test would fail. So we need to know exactly what kind of error # it raises when there is an attempt to read more than the available bytes: try: - client.FakePayload('a').read(2) + client.FakePayload(b'a').read(2) except Exception as reference_error: pass # install the custom handler that tries to access request.POST self.client.handler = POSTAccessingHandler() - with open(__file__) as fp: + with open(__file__, 'rb') as fp: post_data = { 'name': 'Ringo', 'file_field': fp, @@ -374,7 +374,7 @@ class DirectoryCreationTests(unittest.TestCase): """Permission errors are not swallowed""" os.chmod(temp_storage.location, 0500) try: - self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', 'x')) + self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', b'x')) except OSError as err: self.assertEqual(err.errno, errno.EACCES) except Exception: @@ -383,9 +383,9 @@ class DirectoryCreationTests(unittest.TestCase): def test_not_a_directory(self): """The correct IOError is raised when the upload directory name exists but isn't a directory""" # Create a file with the upload directory name - open(UPLOAD_TO, 'w').close() + open(UPLOAD_TO, 'wb').close() try: - self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', 'x')) + self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', b'x')) except IOError as err: # The test needs to be done on a specific string as IOError # is raised even without the patch (just not early enough) diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index a7d98ec7d4..d66ee2c084 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -540,27 +540,27 @@ class FieldsTests(SimpleTestCase): self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None) self.assertRaisesMessage(ValidationError, "[u'This field is required.']", f.clean, None, '') self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) - self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', '')) - self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', ''), '') + self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', b'')) + self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', b''), '') self.assertEqual('files/test3.pdf', f.clean(None, 'files/test3.pdf')) self.assertRaisesMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, 'some content that is not a file') self.assertRaisesMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', None)) - self.assertRaisesMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', '')) - self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content')))) - self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')))) - self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))) + self.assertRaisesMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', b'')) + self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content')))) + self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', u'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))))) + self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content'), 'files/test4.pdf'))) def test_filefield_2(self): f = FileField(max_length = 5) - self.assertRaisesMessage(ValidationError, "[u'Ensure this filename has at most 5 characters (it has 18).']", f.clean, SimpleUploadedFile('test_maxlength.txt', 'hello world')) + self.assertRaisesMessage(ValidationError, "[u'Ensure this filename has at most 5 characters (it has 18).']", f.clean, SimpleUploadedFile('test_maxlength.txt', b'hello world')) self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf')) self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) - self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content')))) + self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content')))) def test_filefield_3(self): f = FileField(allow_empty_file=True) self.assertEqual(SimpleUploadedFile, - type(f.clean(SimpleUploadedFile('name', '')))) + type(f.clean(SimpleUploadedFile('name', b'')))) # URLField ################################################################## diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py index 3f529f23db..16cf46fc29 100644 --- a/tests/regressiontests/forms/tests/forms.py +++ b/tests/regressiontests/forms/tests/forms.py @@ -1463,17 +1463,17 @@ class FormsTestCase(TestCase): f = FileForm(data={}, files={}, auto_id=False) self.assertHTMLEqual(f.as_table(), 'File1:
  • This field is required.
') - f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False) + f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', b'')}, auto_id=False) self.assertHTMLEqual(f.as_table(), 'File1:
  • The submitted file is empty.
') f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False) self.assertHTMLEqual(f.as_table(), 'File1:
  • No file was submitted. Check the encoding type on the form.
') - f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False) + f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', b'some content')}, auto_id=False) self.assertHTMLEqual(f.as_table(), 'File1:') self.assertTrue(f.is_valid()) - f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False) + f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', u'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False) self.assertHTMLEqual(f.as_table(), 'File1:') def test_basic_processing_in_view(self): diff --git a/tests/regressiontests/forms/tests/models.py b/tests/regressiontests/forms/tests/models.py index 64cd18bde6..7f569651c7 100644 --- a/tests/regressiontests/forms/tests/models.py +++ b/tests/regressiontests/forms/tests/models.py @@ -106,7 +106,7 @@ class ModelFormCallableModelDefault(TestCase): class FormsModelTestCase(TestCase): def test_unicode_filename(self): # FileModel with unicode filename and data ######################### - f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False) + f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', u'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False) self.assertTrue(f.is_valid()) self.assertTrue('file1' in f.cleaned_data) m = FileModel.objects.create(file=f.cleaned_data['file1']) @@ -177,7 +177,7 @@ class RelatedModelFormTests(TestCase): class Meta: model=A - self.assertRaises(ValueError, ModelFormMetaclass, 'Form', (ModelForm,), {'Meta': Meta}) + self.assertRaises(ValueError, ModelFormMetaclass, b'Form', (ModelForm,), {'Meta': Meta}) class B(models.Model): pass @@ -195,4 +195,4 @@ class RelatedModelFormTests(TestCase): class Meta: model=A - self.assertTrue(issubclass(ModelFormMetaclass('Form', (ModelForm,), {'Meta': Meta}), ModelForm)) + self.assertTrue(issubclass(ModelFormMetaclass(b'Form', (ModelForm,), {'Meta': Meta}), ModelForm)) diff --git a/tests/regressiontests/forms/tests/regressions.py b/tests/regressiontests/forms/tests/regressions.py index 267ef300e6..20c11f99a8 100644 --- a/tests/regressiontests/forms/tests/regressions.py +++ b/tests/regressiontests/forms/tests/regressions.py @@ -54,10 +54,11 @@ class FormsRegressionsTestCase(TestCase): # Testing choice validation with UTF-8 bytestrings as input (these are the # Russian abbreviations "мес." and "шт.". - UNITS = (('\xd0\xbc\xd0\xb5\xd1\x81.', '\xd0\xbc\xd0\xb5\xd1\x81.'), ('\xd1\x88\xd1\x82.', '\xd1\x88\xd1\x82.')) + UNITS = ((b'\xd0\xbc\xd0\xb5\xd1\x81.', b'\xd0\xbc\xd0\xb5\xd1\x81.'), + (b'\xd1\x88\xd1\x82.', b'\xd1\x88\xd1\x82.')) f = ChoiceField(choices=UNITS) self.assertEqual(f.clean(u'\u0448\u0442.'), u'\u0448\u0442.') - self.assertEqual(f.clean('\xd1\x88\xd1\x82.'), u'\u0448\u0442.') + self.assertEqual(f.clean(b'\xd1\x88\xd1\x82.'), u'\u0448\u0442.') # Translated error messages used to be buggy. with override('ru'): diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/regressiontests/forms/tests/widgets.py index 2499b7a350..8630b45666 100644 --- a/tests/regressiontests/forms/tests/widgets.py +++ b/tests/regressiontests/forms/tests/widgets.py @@ -1182,7 +1182,7 @@ class ClearableFileInputTests(TestCase): """ widget = ClearableFileInput() widget.is_required = True - f = SimpleUploadedFile('something.txt', 'content') + f = SimpleUploadedFile('something.txt', b'content') self.assertEqual(widget.value_from_datadict( data={'myfile-clear': True}, files={'myfile': f}, diff --git a/tests/regressiontests/handlers/tests.py b/tests/regressiontests/handlers/tests.py index 59d3d84528..ae2062c756 100644 --- a/tests/regressiontests/handlers/tests.py +++ b/tests/regressiontests/handlers/tests.py @@ -28,7 +28,7 @@ class HandlerTests(unittest.TestCase): def test_bad_path_info(self): """Tests for bug #15672 ('request' referenced before assignment)""" environ = RequestFactory().get('/').environ - environ['PATH_INFO'] = '\xed' + environ['PATH_INFO'] = b'\xed' handler = WSGIHandler() response = handler(environ, lambda *a, **k: None) self.assertEqual(response.status_code, 400) diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 6dd2128afd..218c5b5b3b 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -174,7 +174,7 @@ class QueryDictTests(unittest.TestCase): QueryDicts must be able to handle invalid input encoding (in this case, bad UTF-8 encoding). """ - q = QueryDict('foo=bar&foo=\xff') + q = QueryDict(b'foo=bar&foo=\xff') self.assertEqual(q['foo'], u'\ufffd') self.assertEqual(q.getlist('foo'), [u'bar', u'\ufffd']) @@ -198,7 +198,7 @@ class QueryDictTests(unittest.TestCase): def test_non_default_encoding(self): """#13572 - QueryDict with a non-default encoding""" - q = QueryDict('sbb=one', encoding='rot_13') + q = QueryDict(b'sbb=one', encoding='rot_13') self.assertEqual(q.encoding , 'rot_13' ) self.assertEqual(q.items() , [(u'foo', u'bar')] ) self.assertEqual(q.urlencode() , 'sbb=one' ) @@ -285,8 +285,8 @@ class HttpResponseTests(unittest.TestCase): except StopIteration: break #'\xde\x9e' == unichr(1950).encode('utf-8') - self.assertEqual(result, ['1', '2', '3', '\xde\x9e']) - self.assertEqual(r.content, '123\xde\x9e') + self.assertEqual(result, ['1', '2', '3', b'\xde\x9e']) + self.assertEqual(r.content, b'123\xde\x9e') #with Content-Encoding header r = HttpResponse([1,1,2,4,8]) @@ -321,7 +321,7 @@ class CookieTests(unittest.TestCase): Test that we haven't broken normal encoding """ c = SimpleCookie() - c['test'] = "\xf0" + c['test'] = b"\xf0" c2 = SimpleCookie() c2.load(c.output()) self.assertEqual(c['test'].value, c2['test'].value) diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index faed4e55c7..5e95ee36ec 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -230,7 +230,7 @@ class TranslationTests(TestCase): """ Translating a string requiring no auto-escaping shouldn't change the "safe" status. """ - s = mark_safe('Password') + s = mark_safe(b'Password') self.assertEqual(SafeString, type(s)) with translation.override('de', deactivate=True): self.assertEqual(SafeUnicode, type(ugettext(s))) diff --git a/tests/regressiontests/localflavor/mk/tests.py b/tests/regressiontests/localflavor/mk/tests.py index f78dbdd440..3a8add6e34 100644 --- a/tests/regressiontests/localflavor/mk/tests.py +++ b/tests/regressiontests/localflavor/mk/tests.py @@ -92,7 +92,7 @@ class MKLocalFlavorTests(SimpleTestCase): """ Test that the empty option is there. """ - municipality_select_html = """\ + municipality_select_html = b"""\ -
+
{% endfor %} @@ -168,7 +168,7 @@ Now, create a ``results.html`` template:
    {% for choice in poll.choice_set.all %} -
  • {{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
  • +
  • {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
  • {% endfor %}
From c2139bbcefb2249bdf1012f843f7daa6a5806922 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 May 2012 00:02:15 +0200 Subject: [PATCH 58/95] Updated WMS URL in geoadmin test. --- django/contrib/gis/tests/geoadmin/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py index 1f104b22a1..53c00c6588 100644 --- a/django/contrib/gis/tests/geoadmin/tests.py +++ b/django/contrib/gis/tests/geoadmin/tests.py @@ -31,5 +31,5 @@ class GeoAdminTest(TestCase): result = geoadmin.get_map_widget(City._meta.get_field('point'))( ).render('point', Point(-79.460734, 40.18476)) self.assertIn( - """geodjango_point.layers.base = new OpenLayers.Layer.WMS("OpenLayers WMS", "http://labs.metacarta.com/wms/vmap0", {layers: \'basic\', format: 'image/jpeg'});""", + """geodjango_point.layers.base = new OpenLayers.Layer.WMS("OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: \'basic\', format: 'image/jpeg'});""", result) From 3b5083bee5e96539dec599106aece9889e70ce05 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 May 2012 11:43:37 +0200 Subject: [PATCH 59/95] Fixed #5423 -- Made dumpdata output one row at a time. This should prevent storing all rows in memory when big sets of data are dumped. See ticket for heroic contributors. --- django/core/management/base.py | 14 +++++----- django/core/management/commands/dumpdata.py | 30 ++++++++++++--------- django/core/serializers/base.py | 3 +++ django/core/serializers/json.py | 29 ++++++++++++++++++-- django/core/serializers/python.py | 13 +++++---- tests/modeltests/fixtures/tests.py | 26 +++++++++--------- 6 files changed, 75 insertions(+), 40 deletions(-) diff --git a/django/core/management/base.py b/django/core/management/base.py index 16feb91f49..6e06991bf0 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -49,23 +49,23 @@ class OutputWrapper(object): """ Wrapper around stdout/stderr """ - def __init__(self, out, style_func=None): + def __init__(self, out, style_func=None, ending='\n'): self._out = out self.style_func = None if hasattr(out, 'isatty') and out.isatty(): self.style_func = style_func + self.ending = ending def __getattr__(self, name): return getattr(self._out, name) - def write(self, msg, style_func=None, ending='\n'): + def write(self, msg, style_func=None, ending=None): + ending = ending is None and self.ending or ending if ending and not msg.endswith(ending): msg += ending - if style_func is not None: - msg = style_func(msg) - elif self.style_func is not None: - msg = self.style_func(msg) - self._out.write(smart_str(msg)) + style_func = [f for f in (style_func, self.style_func, lambda x:x) + if f is not None][0] + self._out.write(smart_str(style_func(msg))) class BaseCommand(object): diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 87b100b44d..b1a46cec3c 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -4,6 +4,7 @@ from django.core import serializers from django.db import router, DEFAULT_DB_ALIAS from django.utils.datastructures import SortedDict +import sys from optparse import make_option class Command(BaseCommand): @@ -97,21 +98,24 @@ class Command(BaseCommand): except KeyError: raise CommandError("Unknown serialization format: %s" % format) - # Now collate the objects to be serialized. - objects = [] - for model in sort_dependencies(app_list.items()): - if model in excluded_models: - continue - if not model._meta.proxy and router.allow_syncdb(using, model): - if use_base_manager: - objects.extend(model._base_manager.using(using).all()) - else: - objects.extend(model._default_manager.using(using).all()) + def get_objects(): + # Collate the objects to be serialized. + for model in sort_dependencies(app_list.items()): + if model in excluded_models: + continue + if not model._meta.proxy and router.allow_syncdb(using, model): + if use_base_manager: + objects = model._base_manager + else: + objects = model._default_manager + for obj in objects.using(using).\ + order_by(model._meta.pk.name).iterator(): + yield obj try: - self.stdout.write(serializers.serialize(format, objects, - indent=indent, use_natural_keys=use_natural_keys), - ending='') + self.stdout.ending = None + serializers.serialize(format, get_objects(), indent=indent, + use_natural_keys=use_natural_keys, stream=self.stdout) except Exception as e: if show_traceback: raise diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 2e6a0656b1..04053c1f8f 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -39,6 +39,7 @@ class Serializer(object): self.use_natural_keys = options.pop("use_natural_keys", False) self.start_serialization() + self.first = True for obj in queryset: self.start_object(obj) # Use the concrete parent class' _meta instead of the object's _meta @@ -57,6 +58,8 @@ class Serializer(object): if self.selected_fields is None or field.attname in self.selected_fields: self.handle_m2m_field(obj, field) self.end_object(obj) + if self.first: + self.first = False self.end_serialization() return self.getvalue() diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index 4da8581f68..fce00600f4 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -21,13 +21,38 @@ class Serializer(PythonSerializer): """ internal_use_only = False - def end_serialization(self): + def start_serialization(self): if json.__version__.split('.') >= ['2', '1', '3']: # Use JS strings to represent Python Decimal instances (ticket #16850) self.options.update({'use_decimal': False}) - json.dump(self.objects, self.stream, cls=DjangoJSONEncoder, **self.options) + self._current = None + self.json_kwargs = self.options.copy() + self.json_kwargs.pop('stream', None) + self.json_kwargs.pop('fields', None) + self.stream.write("[") + + def end_serialization(self): + if self.options.get("indent"): + self.stream.write("\n") + self.stream.write("]") + if self.options.get("indent"): + self.stream.write("\n") + + def end_object(self, obj): + # self._current has the field data + indent = self.options.get("indent") + if not self.first: + self.stream.write(",") + if not indent: + self.stream.write(" ") + if indent: + self.stream.write("\n") + json.dump(self.get_dump_object(obj), self.stream, + cls=DjangoJSONEncoder, **self.json_kwargs) + self._current = None def getvalue(self): + # overwrite PythonSerializer.getvalue() with base Serializer.getvalue() if callable(getattr(self.stream, 'getvalue', None)): return self.stream.getvalue() diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 195bf11d24..49120434eb 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -27,13 +27,16 @@ class Serializer(base.Serializer): self._current = {} def end_object(self, obj): - self.objects.append({ - "model" : smart_unicode(obj._meta), - "pk" : smart_unicode(obj._get_pk_val(), strings_only=True), - "fields" : self._current - }) + self.objects.append(self.get_dump_object(obj)) self._current = None + def get_dump_object(self, obj): + return { + "pk": smart_unicode(obj._get_pk_val(), strings_only=True), + "model": smart_unicode(obj._meta), + "fields": self._current + } + def handle_field(self, obj, field): value = field._get_val_from_obj(obj) # Protected types (i.e., primitives like None, numbers, dates, diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index d22010d7a0..48a5fe7f16 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -54,25 +54,25 @@ class FixtureLoadingTests(TestCase): ]) # Dump the current contents of the database as a JSON fixture - self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') # Try just dumping the contents of fixtures.Category self._dumpdata_assert(['fixtures.Category'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}]') # ...and just fixtures.Article - self._dumpdata_assert(['fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}]') + self._dumpdata_assert(['fixtures.Article'], '[{"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}]') # ...and both - self._dumpdata_assert(['fixtures.Category', 'fixtures.Article'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}]') + self._dumpdata_assert(['fixtures.Category', 'fixtures.Article'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}]') # Specify a specific model twice - self._dumpdata_assert(['fixtures.Article', 'fixtures.Article'], '[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}]') + self._dumpdata_assert(['fixtures.Article', 'fixtures.Article'], '[{"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}]') # Specify a dump that specifies Article both explicitly and implicitly - self._dumpdata_assert(['fixtures.Article', 'fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') + self._dumpdata_assert(['fixtures.Article', 'fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') # Same again, but specify in the reverse order - self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') # Specify one model from one application, and an entire other application. self._dumpdata_assert(['fixtures.Category', 'sites'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}]') @@ -143,17 +143,17 @@ class FixtureLoadingTests(TestCase): ]) # By default, you get raw keys on dumpdata - self._dumpdata_assert(['fixtures.book'], '[{"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [3, 1]}}]') + self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [3, 1]}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') # But you can get natural keys if you ask for them and they are available - self._dumpdata_assert(['fixtures.book'], '[{"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True) + self._dumpdata_assert(['fixtures.book'], '[{"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]', natural_keys=True) # Dump the current contents of the database as a JSON fixture - self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16T16:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16T15:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16T11:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_keys=True) + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker on TV is great!", "pub_date": "2006-06-16T11:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}, {"pk": 4, "model": "fixtures.article", "fields": {"headline": "Django conquers world!", "pub_date": "2006-06-16T15:00:00"}}, {"pk": 5, "model": "fixtures.article", "fields": {"headline": "XML identified as leading cause of cancer", "pub_date": "2006-06-16T16:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "legal", "tagged_id": 3}}, {"pk": 3, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "django", "tagged_id": 4}}, {"pk": 4, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "world domination", "tagged_id": 4}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Artist formerly known as \\"Prince\\""}}, {"pk": 1, "model": "fixtures.visa", "fields": {"person": ["Django Reinhardt"], "permissions": [["add_user", "auth", "user"], ["change_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 2, "model": "fixtures.visa", "fields": {"person": ["Stephane Grappelli"], "permissions": [["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, {"pk": 3, "model": "fixtures.visa", "fields": {"person": ["Artist formerly known as \\"Prince\\""], "permissions": [["change_user", "auth", "user"]]}}, {"pk": 1, "model": "fixtures.book", "fields": {"name": "Music for all ages", "authors": [["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]', natural_keys=True) # Dump the current contents of the database as an XML fixture self._dumpdata_assert(['fixtures'], """ -News StoriesLatest news storiesXML identified as leading cause of cancer2006-06-16T16:00:00Django conquers world!2006-06-16T15:00:00Copyright is fine the way it is2006-06-16T14:00:00Poker on TV is great!2006-06-16T11:00:00copyrightfixturesarticle3legalfixturesarticle3djangofixturesarticle4world dominationfixturesarticle4Artist formerly known as "Prince"Django ReinhardtStephane GrappelliDjango Reinhardtadd_userauthuserchange_userauthuserdelete_userauthuserStephane Grappelliadd_userauthuserdelete_userauthuserArtist formerly known as "Prince"change_userauthuserAchieving self-awareness of Python programsMusic for all agesArtist formerly known as "Prince"Django Reinhardt""", format='xml', natural_keys=True) +News StoriesLatest news storiesPoker on TV is great!2006-06-16T11:00:00Copyright is fine the way it is2006-06-16T14:00:00Django conquers world!2006-06-16T15:00:00XML identified as leading cause of cancer2006-06-16T16:00:00copyrightfixturesarticle3legalfixturesarticle3djangofixturesarticle4world dominationfixturesarticle4Django ReinhardtStephane GrappelliArtist formerly known as "Prince"Django Reinhardtadd_userauthuserchange_userauthuserdelete_userauthuserStephane Grappelliadd_userauthuserdelete_userauthuserArtist formerly known as "Prince"change_userauthuserMusic for all agesArtist formerly known as "Prince"Django ReinhardtAchieving self-awareness of Python programs""", format='xml', natural_keys=True) def test_dumpdata_with_excludes(self): # Load fixture1 which has a site, two articles, and a category @@ -292,11 +292,11 @@ class FixtureLoadingTests(TestCase): ]) # Dump the current contents of the database as a JSON fixture - self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]', natural_keys=True) + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]', natural_keys=True) # Dump the current contents of the database as an XML fixture self._dumpdata_assert(['fixtures'], """ -News StoriesLatest news storiesTime to reform copyright2006-06-16T13:00:00Poker has no place on ESPN2006-06-16T12:00:00copyrightfixturesarticle3lawfixturesarticle3Django ReinhardtPrinceStephane GrappelliAchieving self-awareness of Python programs""", format='xml', natural_keys=True) +News StoriesLatest news storiesPoker has no place on ESPN2006-06-16T12:00:00Time to reform copyright2006-06-16T13:00:00copyrightfixturesarticle3lawfixturesarticle3Django ReinhardtStephane GrappelliPrinceAchieving self-awareness of Python programs""", format='xml', natural_keys=True) class FixtureTransactionTests(TransactionTestCase): def _dumpdata_assert(self, args, output, format='json'): @@ -329,7 +329,7 @@ class FixtureTransactionTests(TransactionTestCase): ]) # Dump the current contents of the database as a JSON fixture - self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') + self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]') # Load fixture 4 (compressed), using format discovery management.call_command('loaddata', 'fixture4', verbosity=0, commit=False) From 12f4bd74fcc4b6907241ca460bc406665430569d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 May 2012 11:53:33 +0200 Subject: [PATCH 60/95] Removed unneeded sys import added in previous commit --- django/core/management/commands/dumpdata.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index b1a46cec3c..9059625dec 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -4,7 +4,6 @@ from django.core import serializers from django.db import router, DEFAULT_DB_ALIAS from django.utils.datastructures import SortedDict -import sys from optparse import make_option class Command(BaseCommand): From fb871f66a8a9cbf2a908725360290914ea3f59da Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 May 2012 16:15:45 +0200 Subject: [PATCH 61/95] Added entry in 1.5 release notes about dumpdata improvements. --- docs/releases/1.5.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 851fed69f7..0fec53ab6d 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -75,6 +75,9 @@ Django 1.5 also includes several smaller improvements worth noting: * The generic views support OPTIONS requests. +* The dumpdata management command outputs one row at a time, preventing + out-of-memory errors when dumping large datasets. + * In the localflavor for Canada, "pq" was added to the acceptable codes for Quebec. It's an old abbreviation. From 7a4233b69c3a4f4ff023bb58ed30f9f7307d7cd2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 May 2012 16:42:28 +0200 Subject: [PATCH 62/95] Removed a duplicate test in fixtures_regress. test_abort_loaddata_on_error was exactly the same test as test_empty (error is tested in test_error_message). --- .../regressiontests/fixtures_regress/tests.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index 5c6f758b66..0e2cc3a9b2 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -185,25 +185,6 @@ class TestFixtures(TestCase): "No fixture data found for 'empty'. (File format may be invalid.)\n" ) - def test_abort_loaddata_on_error(self): - """ - Test for ticket #4371 -- If any of the fixtures contain an error, - loading is aborted. - Validate that error conditions are caught correctly - """ - stderr = BytesIO() - management.call_command( - 'loaddata', - 'empty', - verbosity=0, - commit=False, - stderr=stderr, - ) - self.assertEqual( - stderr.getvalue(), - "No fixture data found for 'empty'. (File format may be invalid.)\n" - ) - def test_error_message(self): """ (Regression for #9011 - error message is correct) From a8a81aae20a81e012fddc24f3ede556501af64a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Sun, 27 May 2012 02:08:44 +0300 Subject: [PATCH 63/95] Fixed #18343 -- Cleaned up deferred model implementation Generic cleanup and dead code removal in deferred model field loading and model.__reduce__(). Also fixed an issue where if an inherited model with a parent field chain parent_ptr_id -> id would be deferred loaded, then accessing the id field caused caused a database query, even if the id field's value is already loaded in the parent_ptr_id field. --- django/db/models/base.py | 8 +--- django/db/models/query_utils.py | 49 ++++++++++++++------- tests/modeltests/defer/tests.py | 15 +++++++ tests/modeltests/field_subclassing/tests.py | 4 ++ 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 08bfc73a87..13238fc9dc 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -404,7 +404,6 @@ class Model(object): # and as a result, the super call will cause an infinite recursion. # See #10547 and #12121. defers = [] - pk_val = None if self._deferred: from django.db.models.query_utils import deferred_class_factory factory = deferred_class_factory @@ -412,12 +411,7 @@ class Model(object): if isinstance(self.__class__.__dict__.get(field.attname), DeferredAttribute): defers.append(field.attname) - if pk_val is None: - # The pk_val and model values are the same for all - # DeferredAttribute classes, so we only need to do this - # once. - obj = self.__class__.__dict__[field.attname] - model = obj.model_ref() + model = self._meta.proxy_for_model else: factory = simple_class_factory return (model_unpickle, (model, defers, factory), data) diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index a56ab5ca80..5676fdce9a 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -6,8 +6,6 @@ large and/or so that they can be used by other modules without getting into circular import difficulties. """ -import weakref - from django.db.backends import util from django.utils import tree @@ -70,8 +68,6 @@ class DeferredAttribute(object): """ def __init__(self, field_name, model): self.field_name = field_name - self.model_ref = weakref.ref(model) - self.loaded = False def __get__(self, instance, owner): """ @@ -79,27 +75,32 @@ class DeferredAttribute(object): Returns the cached value. """ from django.db.models.fields import FieldDoesNotExist + non_deferred_model = instance._meta.proxy_for_model + opts = non_deferred_model._meta assert instance is not None - cls = self.model_ref() data = instance.__dict__ if data.get(self.field_name, self) is self: # self.field_name is the attname of the field, but only() takes the # actual name, so we need to translate it here. try: - cls._meta.get_field_by_name(self.field_name) - name = self.field_name + f = opts.get_field_by_name(self.field_name)[0] except FieldDoesNotExist: - name = [f.name for f in cls._meta.fields - if f.attname == self.field_name][0] - # We use only() instead of values() here because we want the - # various data coersion methods (to_python(), etc.) to be called - # here. - val = getattr( - cls._base_manager.filter(pk=instance.pk).only(name).using( - instance._state.db).get(), - self.field_name - ) + f = [f for f in opts.fields + if f.attname == self.field_name][0] + name = f.name + # Lets see if the field is part of the parent chain. If so we + # might be able to reuse the already loaded value. Refs #18343. + val = self._check_parent_chain(instance, name) + if val is None: + # We use only() instead of values() here because we want the + # various data coersion methods (to_python(), etc.) to be + # called here. + val = getattr( + non_deferred_model._base_manager.only(name).using( + instance._state.db).get(pk=instance.pk), + self.field_name + ) data[self.field_name] = val return data[self.field_name] @@ -110,6 +111,20 @@ class DeferredAttribute(object): """ instance.__dict__[self.field_name] = value + def _check_parent_chain(self, instance, name): + """ + Check if the field value can be fetched from a parent field already + loaded in the instance. This can be done if the to-be fetched + field is a primary key field. + """ + opts = instance._meta + f = opts.get_field_by_name(name)[0] + link_field = opts.get_ancestor_link(f.model) + if f.primary_key and f != link_field: + return getattr(instance, link_field.attname) + return None + + def select_related_descend(field, restricted, requested, reverse=False): """ Returns True if this field should be used to descend deeper for diff --git a/tests/modeltests/defer/tests.py b/tests/modeltests/defer/tests.py index 09138293af..eb09162b01 100644 --- a/tests/modeltests/defer/tests.py +++ b/tests/modeltests/defer/tests.py @@ -158,3 +158,18 @@ class DeferTests(TestCase): self.assert_delayed(child, 1) self.assertEqual(child.name, 'p1') self.assertEqual(child.value, 'xx') + + def test_defer_inheritance_pk_chaining(self): + """ + When an inherited model is fetched from the DB, its PK is also fetched. + When getting the PK of the parent model it is useful to use the already + fetched parent model PK if it happens to be available. Tests that this + is done. + """ + s1 = Secondary.objects.create(first="x1", second="y1") + bc = BigChild.objects.create(name="b1", value="foo", related=s1, + other="bar") + bc_deferred = BigChild.objects.only('name').get(pk=bc.pk) + with self.assertNumQueries(0): + bc_deferred.id + self.assertEqual(bc_deferred.pk, bc_deferred.id) diff --git a/tests/modeltests/field_subclassing/tests.py b/tests/modeltests/field_subclassing/tests.py index c6a2335f37..48755123f2 100644 --- a/tests/modeltests/field_subclassing/tests.py +++ b/tests/modeltests/field_subclassing/tests.py @@ -17,6 +17,10 @@ class CustomField(TestCase): self.assertTrue(isinstance(d.data, list)) self.assertEqual(d.data, [1, 2, 3]) + d = DataModel.objects.defer("data").get(pk=d.pk) + self.assertTrue(isinstance(d.data, list)) + self.assertEqual(d.data, [1, 2, 3]) + # Refetch for save d = DataModel.objects.defer("data").get(pk=d.pk) d.save() From 4423757c0c50afbe2470434778c8d5e5b4a70925 Mon Sep 17 00:00:00 2001 From: Michael Newman Date: Sun, 27 May 2012 18:24:35 +0300 Subject: [PATCH 64/95] 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. --- django/db/backends/mysql/base.py | 13 +++++++++++-- tests/regressiontests/backends/tests.py | 6 ++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index ecbb02f31d..1df487bd92 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -418,11 +418,20 @@ class DatabaseWrapper(BaseDatabaseWrapper): @cached_property def mysql_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() - m = server_version_re.match(self.connection.get_server_info()) + 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 10e69b65c1..109e1a5bcc 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -89,6 +89,12 @@ class MySQLTests(TestCase): else: self.assertFalse(found_reset) + @unittest.skipUnless(connection.vendor == 'mysql', + "Test valid only for MySQL") + def test_server_version_connections(self): + connection.close() + connection.mysql_version + self.assertTrue(connection.connection is None) class DateQuotingTest(TestCase): From f2b6763ad7cb281ca8699a9c3d532a82f965be4f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 May 2012 20:50:44 +0200 Subject: [PATCH 65/95] Fixed #18387 -- Do not call sys.exit during call_command. Moved sys.exit(1) so as failing management commands reach it only when running from command line. --- django/contrib/auth/tests/management.py | 13 ++---- django/core/management/base.py | 44 +++++++------------ docs/howto/custom-management-commands.txt | 10 +++-- docs/releases/1.5.txt | 4 ++ tests/modeltests/fixtures/tests.py | 4 +- .../management/commands/dance.py | 8 ++-- tests/modeltests/user_commands/tests.py | 17 +++++++ 7 files changed, 56 insertions(+), 44 deletions(-) diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py index 5d31bd70a4..81ab0aa052 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -2,6 +2,7 @@ from StringIO import StringIO from django.contrib.auth import models, management from django.contrib.auth.management.commands import changepassword +from django.core.management.base import CommandError from django.test import TestCase @@ -56,16 +57,10 @@ class ChangepasswordManagementCommandTestCase(TestCase): def test_that_max_tries_exits_1(self): """ A CommandError should be thrown by handle() if the user enters in - mismatched passwords three times. This should be caught by execute() and - converted to a SystemExit + mismatched passwords three times. """ command = changepassword.Command() command._get_pass = lambda *args: args or 'foo' - self.assertRaises( - SystemExit, - command.execute, - "joe", - stdout=self.stdout, - stderr=self.stderr - ) + with self.assertRaises(CommandError): + command.execute("joe", stdout=self.stdout, stderr=self.stderr) diff --git a/django/core/management/base.py b/django/core/management/base.py index 6e06991bf0..a204f6f0bc 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -97,8 +97,9 @@ class BaseCommand(object): output and, if the command is intended to produce a block of SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``. - 4. If ``handle()`` raised a ``CommandError``, ``execute()`` will - instead print an error message to ``stderr``. + 4. If ``handle()`` or ``execute()`` raised any exception (e.g. + ``CommandError``), ``run_from_argv()`` will instead print an error + message to ``stderr``. Thus, the ``handle()`` method is typically the starting point for subclasses; many built-in commands and command types either place @@ -210,23 +211,27 @@ class BaseCommand(object): def run_from_argv(self, argv): """ Set up any environment changes requested (e.g., Python path - and Django settings), then run this command. - + and Django settings), then run this command. If the + command raises a ``CommandError``, intercept it and print it sensibly + to stderr. """ parser = self.create_parser(argv[0], argv[1]) options, args = parser.parse_args(argv[2:]) handle_default_options(options) - self.execute(*args, **options.__dict__) + try: + self.execute(*args, **options.__dict__) + except Exception as e: + if options.traceback: + self.stderr.write(traceback.format_exc()) + self.stderr.write('%s: %s' % (e.__class__.__name__, e)) + sys.exit(1) def execute(self, *args, **options): """ Try to execute this command, performing model validation if needed (as controlled by the attribute - ``self.requires_model_validation``, except if force-skipped). If the - command raises a ``CommandError``, intercept it and print it sensibly - to stderr. + ``self.requires_model_validation``, except if force-skipped). """ - show_traceback = options.get('traceback', False) # Switch to English, because django-admin.py creates database content # like permissions, and those shouldn't contain any translations. @@ -237,18 +242,9 @@ class BaseCommand(object): self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR) if self.can_import_settings: - try: - from django.utils import translation - saved_lang = translation.get_language() - translation.activate('en-us') - except ImportError as e: - # If settings should be available, but aren't, - # raise the error and quit. - if show_traceback: - traceback.print_exc() - else: - self.stderr.write('Error: %s' % e) - sys.exit(1) + from django.utils import translation + saved_lang = translation.get_language() + translation.activate('en-us') try: if self.requires_model_validation and not options.get('skip_validation'): @@ -265,12 +261,6 @@ class BaseCommand(object): self.stdout.write(output) if self.output_transaction: self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;")) - except CommandError as e: - if show_traceback: - traceback.print_exc() - else: - self.stderr.write('Error: %s' % e) - sys.exit(1) finally: if saved_lang is not None: translation.activate(saved_lang) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 2b34d35de7..4a27bdf7a9 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -317,8 +317,12 @@ Exception class indicating a problem while executing a management command. If this exception is raised during the execution of a management -command, it will be caught and turned into a nicely-printed error -message to the appropriate output stream (i.e., stderr); as a -result, raising this exception (with a sensible description of the +command from a command line console, it will be caught and turned into a +nicely-printed error message to the appropriate output stream (i.e., stderr); +as a result, raising this exception (with a sensible description of the error) is the preferred way to indicate that something has gone wrong in the execution of a command. + +If a management command is called from code through +:ref:`call_command `, it's up to you to catch the exception +when needed. diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 0fec53ab6d..0d86a52670 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -75,6 +75,10 @@ Django 1.5 also includes several smaller improvements worth noting: * The generic views support OPTIONS requests. +* Management commands do not raise ``SystemExit`` any more when called by code + from :ref:`call_command `. Any exception raised by the command + (mostly :ref:`CommandError `) is propagated. + * The dumpdata management command outputs one row at a time, preventing out-of-memory errors when dumping large datasets. diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index 48a5fe7f16..478bbe9129 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -185,14 +185,14 @@ class FixtureLoadingTests(TestCase): exclude_list=['fixtures.Article', 'fixtures.Book', 'sites']) # Excluding a bogus app should throw an error - self.assertRaises(SystemExit, + self.assertRaises(management.CommandError, self._dumpdata_assert, ['fixtures', 'sites'], '', exclude_list=['foo_app']) # Excluding a bogus model should throw an error - self.assertRaises(SystemExit, + self.assertRaises(management.CommandError, self._dumpdata_assert, ['fixtures', 'sites'], '', diff --git a/tests/modeltests/user_commands/management/commands/dance.py b/tests/modeltests/user_commands/management/commands/dance.py index 4ad5579e1e..911530d223 100644 --- a/tests/modeltests/user_commands/management/commands/dance.py +++ b/tests/modeltests/user_commands/management/commands/dance.py @@ -1,6 +1,6 @@ from optparse import make_option -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError class Command(BaseCommand): @@ -8,11 +8,13 @@ class Command(BaseCommand): args = '' requires_model_validation = True - option_list =[ + option_list = BaseCommand.option_list + ( make_option("-s", "--style", default="Rock'n'Roll"), make_option("-x", "--example") - ] + ) def handle(self, *args, **options): example = options["example"] + if example == "raise": + raise CommandError() self.stdout.write("I don't feel like dancing %s." % options["style"]) diff --git a/tests/modeltests/user_commands/tests.py b/tests/modeltests/user_commands/tests.py index c1e2bf9a74..509f13f62f 100644 --- a/tests/modeltests/user_commands/tests.py +++ b/tests/modeltests/user_commands/tests.py @@ -1,3 +1,4 @@ +import sys from StringIO import StringIO from django.core import management @@ -26,4 +27,20 @@ class CommandTests(TestCase): self.assertEqual(translation.get_language(), 'fr') def test_explode(self): + """ Test that an unknown command raises CommandError """ self.assertRaises(CommandError, management.call_command, ('explode',)) + + def test_system_exit(self): + """ Exception raised in a command should raise CommandError with + call_command, but SystemExit when run from command line + """ + with self.assertRaises(CommandError): + management.call_command('dance', example="raise") + old_stderr = sys.stderr + sys.stderr = err = StringIO() + try: + with self.assertRaises(SystemExit): + management.ManagementUtility(['manage.py', 'dance', '--example=raise']).execute() + finally: + sys.stderr = old_stderr + self.assertIn("CommandError", err.getvalue()) From cc4b4d9fd34d9b601de0bafaa3c1f249729fa49a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 27 May 2012 23:03:21 +0200 Subject: [PATCH 66/95] Used CommandError in createcachetable command. Raising CommandError whenever a management command meets an error condition is the standard way to handle errors in commands. --- django/core/management/commands/createcachetable.py | 13 ++++++------- tests/regressiontests/cache/tests.py | 12 ++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index fec3aff67c..bcc47e17c8 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -1,7 +1,7 @@ from optparse import make_option from django.core.cache.backends.db import BaseDatabaseCache -from django.core.management.base import LabelCommand +from django.core.management.base import LabelCommand, CommandError from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.db.utils import DatabaseError @@ -55,11 +55,10 @@ class Command(LabelCommand): try: curs.execute("\n".join(full_statement)) except DatabaseError as e: - self.stderr.write( + transaction.rollback_unless_managed(using=db) + raise CommandError( "Cache table '%s' could not be created.\nThe error was: %s." % (tablename, e)) - transaction.rollback_unless_managed(using=db) - else: - for statement in index_output: - curs.execute(statement) - transaction.commit_unless_managed(using=db) + for statement in index_output: + curs.execute(statement) + transaction.commit_unless_managed(using=db) diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index cbba7c19a3..88fe5471cf 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -7,7 +7,6 @@ from __future__ import absolute_import import hashlib import os import re -import StringIO import tempfile import time import warnings @@ -820,9 +819,14 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): self.perform_cull_test(50, 18) def test_second_call_doesnt_crash(self): - err = StringIO.StringIO() - management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False, stderr=err) - self.assertTrue(b"Cache table 'test cache table' could not be created" in err.getvalue()) + with self.assertRaisesRegexp(management.CommandError, + "Cache table 'test cache table' could not be created"): + management.call_command( + 'createcachetable', + self._table_name, + verbosity=0, + interactive=False + ) @override_settings(USE_TZ=True) From c3b56c7cdde661464da4aaa72cafb11c9260d754 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 27 May 2012 23:11:27 +0200 Subject: [PATCH 67/95] Used call_command in i18n compilation tests. Now that call_command does not raise SystemExit any more, we can use call_command again for testing compilemessages. --- .../i18n/commands/compilation.py | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/tests/regressiontests/i18n/commands/compilation.py b/tests/regressiontests/i18n/commands/compilation.py index 039e463d0b..d88e1feef6 100644 --- a/tests/regressiontests/i18n/commands/compilation.py +++ b/tests/regressiontests/i18n/commands/compilation.py @@ -1,8 +1,7 @@ import os from io import BytesIO -from django.core.management import CommandError -from django.core.management.commands.compilemessages import compile_messages +from django.core.management import call_command, CommandError from django.test import TestCase from django.test.utils import override_settings from django.utils import translation @@ -25,11 +24,9 @@ class PoFileTests(MessageCompilationTests): def test_bom_rejection(self): os.chdir(test_dir) - # We don't use the django.core.management infrastructure (call_command() - # et al) because CommandError's cause exit(1) there. We test the - # underlying compile_messages function instead - out = BytesIO() - self.assertRaises(CommandError, compile_messages, out, locale=self.LOCALE) + with self.assertRaisesRegexp(CommandError, + "file has a BOM \(Byte Order Mark\)"): + call_command('compilemessages', locale=self.LOCALE, stderr=BytesIO()) self.assertFalse(os.path.exists(self.MO_FILE)) @@ -45,11 +42,7 @@ class PoFileContentsTests(MessageCompilationTests): def test_percent_symbol_in_po_file(self): os.chdir(test_dir) - # We don't use the django.core.management infrastructure (call_command() - # et al) because CommandError's cause exit(1) there. We test the - # underlying compile_messages function instead - out = BytesIO() - compile_messages(out, locale=self.LOCALE) + call_command('compilemessages', locale=self.LOCALE, stderr=BytesIO()) self.assertTrue(os.path.exists(self.MO_FILE)) @@ -64,11 +57,7 @@ class PercentRenderingTests(MessageCompilationTests): def test_percent_symbol_escaping(self): from django.template import Template, Context os.chdir(test_dir) - # We don't use the django.core.management infrastructure (call_command() - # et al) because CommandError's cause exit(1) there. We test the - # underlying compile_messages function instead - out = BytesIO() - compile_messages(out, locale=self.LOCALE) + call_command('compilemessages', locale=self.LOCALE, stderr=BytesIO()) with translation.override(self.LOCALE): t = Template('{% load i18n %}{% trans "Looks like a str fmt spec %% o but shouldn\'t be interpreted as such" %}') rendered = t.render(Context({})) From a768b1d94a708a06be67619e72149d5bb6d286e5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 28 May 2012 11:15:31 +0200 Subject: [PATCH 68/95] Removed numbering of GEOS tests. --- django/contrib/gis/geos/tests/test_geos.py | 111 +++++++++++---------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 4a04d15ad9..9946495f54 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -1,18 +1,22 @@ import ctypes import random import unittest -from django.contrib.gis.geos import * + +from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, + GeometryCollection, Point, MultiPoint, Polygon, MultiPolygon, LinearRing, + LineString, MultiLineString, fromfile, fromstr, geos_version_info) from django.contrib.gis.geos.base import gdal, numpy, GEOSBase from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geometry.test_data import TestDataMixin + class GEOSTest(unittest.TestCase, TestDataMixin): @property def null_srid(self): """ Returns the proper null SRID depending on the GEOS version. - See the comments in `test15_srid` for more details. + See the comments in `test_srid` for more details. """ info = geos_version_info() if info['version'] == '3.0.0' and info['release_candidate']: @@ -20,7 +24,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): else: return None - def test00_base(self): + def test_base(self): "Tests out the GEOSBase class." # Testing out GEOSBase class, which provides a `ptr` property # that abstracts out access to underlying C pointers. @@ -62,19 +66,19 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertRaises(TypeError, fg1._set_ptr, bad_ptr) self.assertRaises(TypeError, fg2._set_ptr, bad_ptr) - def test01a_wkt(self): + def test_wkt(self): "Testing WKT output." for g in self.geometries.wkt_out: geom = fromstr(g.wkt) self.assertEqual(g.ewkt, geom.wkt) - def test01b_hex(self): + def test_hex(self): "Testing HEX output." for g in self.geometries.hex_wkt: geom = fromstr(g.wkt) self.assertEqual(g.hex, geom.hex) - def test01b_hexewkb(self): + def test_hexewkb(self): "Testing (HEX)EWKB output." from binascii import a2b_hex @@ -124,14 +128,14 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # Redundant sanity check. self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid) - def test01c_kml(self): + def test_kml(self): "Testing KML output." for tg in self.geometries.wkt_out: geom = fromstr(tg.wkt) kml = getattr(tg, 'kml', False) if kml: self.assertEqual(kml, geom.kml) - def test01d_errors(self): + def test_errors(self): "Testing the Error handlers." # string-based print("\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n") @@ -154,7 +158,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # None self.assertRaises(TypeError, GEOSGeometry, None) - def test01e_wkb(self): + def test_wkb(self): "Testing WKB output." from binascii import b2a_hex for g in self.geometries.hex_wkt: @@ -162,7 +166,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): wkb = geom.wkb self.assertEqual(b2a_hex(wkb).upper(), g.hex) - def test01f_create_hex(self): + def test_create_hex(self): "Testing creation from HEX." for g in self.geometries.hex_wkt: geom_h = GEOSGeometry(g.hex) @@ -170,7 +174,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): geom_t = fromstr(g.wkt) self.assertEqual(geom_t.wkt, geom_h.wkt) - def test01g_create_wkb(self): + def test_create_wkb(self): "Testing creation from WKB." from binascii import a2b_hex for g in self.geometries.hex_wkt: @@ -180,7 +184,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): geom_t = fromstr(g.wkt) self.assertEqual(geom_t.wkt, geom_h.wkt) - def test01h_ewkt(self): + def test_ewkt(self): "Testing EWKT." srids = (-1, 32140) for srid in srids: @@ -191,7 +195,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export - def test01i_json(self): + def test_json(self): "Testing GeoJSON input/output (via GDAL)." if not gdal or not gdal.GEOJSON: return for g in self.geometries.json_geoms: @@ -201,7 +205,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(g.json, geom.geojson) self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json)) - def test01k_fromfile(self): + def test_fromfile(self): "Testing the fromfile() factory." from io import BytesIO ref_pnt = GEOSGeometry('POINT(5 23)') @@ -218,7 +222,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): pnt = fromfile(fh) self.assertEqual(ref_pnt, pnt) - def test01k_eq(self): + def test_eq(self): "Testing equivalence." p = fromstr('POINT(5 23)') self.assertEqual(p, p.wkt) @@ -233,7 +237,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertNotEqual(g, {'foo' : 'bar'}) self.assertNotEqual(g, False) - def test02a_points(self): + def test_points(self): "Testing Point objects." prev = fromstr('POINT(0 0)') for p in self.geometries.points: @@ -288,7 +292,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): prev = pnt # setting the previous geometry - def test02b_multipoints(self): + def test_multipoints(self): "Testing MultiPoint objects." for mp in self.geometries.multipoints: mpnt = fromstr(mp.wkt) @@ -307,7 +311,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(p.empty, False) self.assertEqual(p.valid, True) - def test03a_linestring(self): + def test_linestring(self): "Testing LineString objects." prev = fromstr('POINT(0 0)') for l in self.geometries.linestrings: @@ -333,7 +337,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments if numpy: self.assertEqual(ls, LineString(numpy.array(ls.tuple))) # as numpy array - def test03b_multilinestring(self): + def test_multilinestring(self): "Testing MultiLineString objects." prev = fromstr('POINT(0 0)') for l in self.geometries.multilinestrings: @@ -357,7 +361,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt) self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml))) - def test04_linearring(self): + def test_linearring(self): "Testing LinearRing objects." for rr in self.geometries.linearrings: lr = fromstr(rr.wkt) @@ -373,14 +377,15 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple])) if numpy: self.assertEqual(lr, LinearRing(numpy.array(lr.tuple))) - def test05a_polygons(self): - "Testing Polygon objects." - - # Testing `from_bbox` class method + def test_polygons_from_bbox(self): + "Testing `from_bbox` class method." bbox = (-180, -90, 180, 90) - p = Polygon.from_bbox( bbox ) + p = Polygon.from_bbox(bbox) self.assertEqual(bbox, p.extent) + def test_polygons(self): + "Testing Polygon objects." + prev = fromstr('POINT(0 0)') for p in self.geometries.polygons: # Creating the Polygon, testing its properties. @@ -437,7 +442,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt) self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt) - def test05b_multipolygons(self): + def test_multipolygons(self): "Testing MultiPolygon objects." print("\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n") prev = fromstr('POINT (0 0)') @@ -460,7 +465,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n") - def test06a_memory_hijinks(self): + def test_memory_hijinks(self): "Testing Geometry __del__() on rings and polygons." #### Memory issues with rings and polygons @@ -483,7 +488,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # Access to these rings is OK since they are clones. s1, s2 = str(ring1), str(ring2) - def test08_coord_seq(self): + def test_coord_seq(self): "Testing Coordinate Sequence objects." for p in self.geometries.polygons: if p.ext_ring_cs: @@ -510,7 +515,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): cs[i] = tset self.assertEqual(tset[j], cs[i][j]) - def test09_relate_pattern(self): + def test_relate_pattern(self): "Testing relate() and relate_pattern()." g = fromstr('POINT (0 0)') self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo') @@ -520,7 +525,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern)) self.assertEqual(rg.pattern, a.relate(b)) - def test10_intersection(self): + def test_intersection(self): "Testing intersects() and intersection()." for i in xrange(len(self.geometries.topology_geoms)): a = fromstr(self.geometries.topology_geoms[i].wkt_a) @@ -533,7 +538,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): a &= b # testing __iand__ self.assertEqual(i1, a) - def test11_union(self): + def test_union(self): "Testing union()." for i in xrange(len(self.geometries.topology_geoms)): a = fromstr(self.geometries.topology_geoms[i].wkt_a) @@ -545,7 +550,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): a |= b # testing __ior__ self.assertEqual(u1, a) - def test12_difference(self): + def test_difference(self): "Testing difference()." for i in xrange(len(self.geometries.topology_geoms)): a = fromstr(self.geometries.topology_geoms[i].wkt_a) @@ -557,7 +562,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): a -= b # testing __isub__ self.assertEqual(d1, a) - def test13_symdifference(self): + def test_symdifference(self): "Testing sym_difference()." for i in xrange(len(self.geometries.topology_geoms)): a = fromstr(self.geometries.topology_geoms[i].wkt_a) @@ -569,7 +574,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): a ^= b # testing __ixor__ self.assertEqual(d1, a) - def test14_buffer(self): + def test_buffer(self): "Testing buffer()." for bg in self.geometries.buffer_geoms: g = fromstr(bg.wkt) @@ -597,7 +602,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9) self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9) - def test15_srid(self): + def test_srid(self): "Testing the SRID property and keyword." # Testing SRID keyword on Point pnt = Point(5, 23, srid=4326) @@ -635,7 +640,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): p3 = fromstr(p1.hex, srid=-1) # -1 is intended. self.assertEqual(-1, p3.srid) - def test16_mutable_geometries(self): + def test_mutable_geometries(self): "Testing the mutability of Polygons and Geometry Collections." ### Testing the mutability of Polygons ### for p in self.geometries.polygons: @@ -699,7 +704,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): #self.assertEqual((3.14, 2.71), mpoly[0].shell[0]) #del mpoly - def test17_threed(self): + def test_threed(self): "Testing three-dimensional geometries." # Testing a 3D Point pnt = Point(2, 3, 8) @@ -715,7 +720,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): ls[0] = (1.,2.,3.) self.assertEqual((1.,2.,3.), ls[0]) - def test18_distance(self): + def test_distance(self): "Testing the distance() function." # Distance to self should be 0. pnt = Point(0, 0) @@ -733,7 +738,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): ls2 = LineString((5, 2), (6, 1), (7, 0)) self.assertEqual(3, ls1.distance(ls2)) - def test19_length(self): + def test_length(self): "Testing the length property." # Points have 0 length. pnt = Point(0, 0) @@ -751,7 +756,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): mpoly = MultiPolygon(poly.clone(), poly) self.assertEqual(8.0, mpoly.length) - def test20a_emptyCollections(self): + def test_emptyCollections(self): "Testing empty geometries and collections." gc1 = GeometryCollection([]) gc2 = fromstr('GEOMETRYCOLLECTION EMPTY') @@ -789,7 +794,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): else: self.assertRaises(GEOSIndexError, g.__getitem__, 0) - def test20b_collections_of_collections(self): + def test_collections_of_collections(self): "Testing GeometryCollection handling of other collections." # Creating a GeometryCollection WKT string composed of other # collections and polygons. @@ -808,7 +813,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # And, they should be equal. self.assertEqual(gc1, gc2) - def test21_test_gdal(self): + def test_gdal(self): "Testing `ogr` and `srs` properties." if not gdal.HAS_GDAL: return g1 = fromstr('POINT(5 23)') @@ -821,7 +826,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(g2.hex, g2.ogr.hex) self.assertEqual('WGS 84', g2.srs.name) - def test22_copy(self): + def test_copy(self): "Testing use with the Python `copy` module." import copy poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 23, 23 0, 0 0), (5 5, 5 10, 10 10, 10 5, 5 5))') @@ -830,7 +835,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertNotEqual(poly._ptr, cpy1._ptr) self.assertNotEqual(poly._ptr, cpy2._ptr) - def test23_transform(self): + def test_transform(self): "Testing `transform` method." if not gdal.HAS_GDAL: return orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) @@ -855,7 +860,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertAlmostEqual(trans.x, p.x, prec) self.assertAlmostEqual(trans.y, p.y, prec) - def test23_transform_noop(self): + def test_transform_noop(self): """ Testing `transform` method (SRID match) """ # transform() should no-op if source & dest SRIDs match, # regardless of whether GDAL is available. @@ -890,7 +895,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): finally: gdal.HAS_GDAL = old_has_gdal - def test23_transform_nosrid(self): + def test_transform_nosrid(self): """ Testing `transform` method (no SRID or negative SRID) """ g = GEOSGeometry('POINT (-104.609 38.255)', srid=None) @@ -905,7 +910,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1) self.assertRaises(GEOSException, g.transform, 2774, clone=True) - def test23_transform_nogdal(self): + def test_transform_nogdal(self): """ Testing `transform` method (GDAL not available) """ old_has_gdal = gdal.HAS_GDAL try: @@ -919,7 +924,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): finally: gdal.HAS_GDAL = old_has_gdal - def test24_extent(self): + def test_extent(self): "Testing `extent` method." # The xmin, ymin, xmax, ymax of the MultiPoint should be returned. mp = MultiPoint(Point(5, 23), Point(0, 0), Point(10, 50)) @@ -935,7 +940,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): xmax, ymax = max(x), max(y) self.assertEqual((xmin, ymin, xmax, ymax), poly.extent) - def test25_pickle(self): + def test_pickle(self): "Testing pickling and unpickling support." # Using both pickle and cPickle -- just 'cause. import pickle, cPickle @@ -958,7 +963,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(geom, tmpg) if not no_srid: self.assertEqual(geom.srid, tmpg.srid) - def test26_prepared(self): + def test_prepared(self): "Testing PreparedGeometry support." if not GEOS_PREPARE: return # Creating a simple multipolygon and getting a prepared version. @@ -974,7 +979,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt)) self.assertEqual(c, prep.covers(pnt)) - def test26_line_merge(self): + def test_line_merge(self): "Testing line merge support" ref_geoms = (fromstr('LINESTRING(1 1, 1 1, 3 3)'), fromstr('MULTILINESTRING((1 1, 3 3), (3 3, 4 2))'), @@ -985,7 +990,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): for geom, merged in zip(ref_geoms, ref_merged): self.assertEqual(merged, geom.merged) - def test27_valid_reason(self): + def test_valid_reason(self): "Testing IsValidReason support" # Skipping tests if GEOS < v3.1. if not GEOS_PREPARE: return @@ -1005,7 +1010,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n") - def test28_geos_version(self): + def test_geos_version(self): "Testing the GEOS version regular expression." from django.contrib.gis.geos.libgeos import version_regex versions = [ ('3.0.0rc4-CAPI-1.3.3', '3.0.0'), From a535040bfac30766cb475054dac5a6359073e48b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 28 May 2012 11:55:38 +0200 Subject: [PATCH 69/95] Used call_command stdout parameter to capture output in staticfiles tests. --- .../staticfiles_tests/tests.py | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 6565a4d786..3711d92448 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -6,7 +6,7 @@ import posixpath import shutil import sys import tempfile -from StringIO import StringIO +from io import BytesIO from django.template import loader, Context from django.conf import settings @@ -187,30 +187,22 @@ class TestFindStatic(CollectionTestCase, TestDefaults): Test ``findstatic`` management command. """ def _get_file(self, filepath): - _stdout = sys.stdout - sys.stdout = StringIO() - try: - call_command('findstatic', filepath, all=False, verbosity='0') - sys.stdout.seek(0) - lines = [l.strip() for l in sys.stdout.readlines()] - contents = codecs.open( - smart_unicode(lines[1].strip()), "r", "utf-8").read() - finally: - sys.stdout = _stdout + out = BytesIO() + call_command('findstatic', filepath, all=False, verbosity=0, stdout=out) + out.seek(0) + lines = [l.strip() for l in out.readlines()] + contents = codecs.open( + smart_unicode(lines[1].strip()), "r", "utf-8").read() return contents def test_all_files(self): """ Test that findstatic returns all candidate files if run without --first. """ - _stdout = sys.stdout - sys.stdout = StringIO() - try: - call_command('findstatic', 'test/file.txt', verbosity='0') - sys.stdout.seek(0) - lines = [l.strip() for l in sys.stdout.readlines()] - finally: - sys.stdout = _stdout + out = BytesIO() + call_command('findstatic', 'test/file.txt', verbosity=0, stdout=out) + out.seek(0) + lines = [l.strip() for l in out.readlines()] self.assertEqual(len(lines), 3) # three because there is also the "Found here" line self.assertIn('project', lines[1]) self.assertIn('apps', lines[2]) From 432339a72c20f78abf1999892a39c71b5d853174 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Mon, 28 May 2012 11:03:34 -0700 Subject: [PATCH 70/95] Fixed #18393 -- Prevented blocktrans to crash when a variable name is badly formatted. --- django/templatetags/i18n.py | 2 +- .../i18n/other/locale/fr/LC_MESSAGES/django.mo | Bin 454 -> 528 bytes .../i18n/other/locale/fr/LC_MESSAGES/django.po | 6 ++++++ tests/regressiontests/i18n/tests.py | 16 +++++++++++++++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index ca79f41edc..e090781080 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -148,7 +148,7 @@ class BlockTranslateNode(Node): context.pop() try: result = result % data - except KeyError: + except (KeyError, ValueError): with translation.override(None): result = self.render(context) return result diff --git a/tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.mo index f0a21797676abbe377b4637c57fe40fce594a5b2..478338bc886a2445f9a1c7004b49f4bac719d5fb 100644 GIT binary patch delta 144 zcmX@cJb@+lo)F7a1|VPtVi_Pd0b*7l_5orLNC09nAWj5gkbDIYJ2NscfHX-0@l7Bb zB>ovl0}%rg5W@iTL| Date: Mon, 28 May 2012 21:13:09 +0200 Subject: [PATCH 71/95] Removed unneeded smart_str in cache utils. --- django/utils/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/utils/cache.py b/django/utils/cache.py index 7ef3680db2..8b81a2ffa2 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -23,7 +23,7 @@ import time from django.conf import settings from django.core.cache import get_cache -from django.utils.encoding import smart_str, iri_to_uri, force_unicode +from django.utils.encoding import iri_to_uri, force_unicode from django.utils.http import http_date from django.utils.timezone import get_current_timezone_name from django.utils.translation import get_language @@ -53,7 +53,7 @@ def patch_cache_control(response, **kwargs): if t[1] is True: return t[0] else: - return t[0] + '=' + smart_str(t[1]) + return '%s=%s' % (t[0], t[1]) if response.has_header('Cache-Control'): cc = cc_delim_re.split(response['Cache-Control']) From 2626ea4a74d09642a50c3fb30944513b68dda289 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 31 May 2012 09:59:58 +0200 Subject: [PATCH 72/95] Fixed #14681 -- Do not set mode to None on file-like objects. gzip.GzipFile does not support files with mode set to None. --- django/core/files/base.py | 3 ++- tests/modeltests/files/tests.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/django/core/files/base.py b/django/core/files/base.py index ad0922b88b..a2d703c963 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -12,7 +12,8 @@ class File(FileProxyMixin): if name is None: name = getattr(file, 'name', None) self.name = name - self.mode = getattr(file, 'mode', None) + if hasattr(file, 'mode'): + self.mode = file.mode def __str__(self): return smart_str(self.name or '') diff --git a/tests/modeltests/files/tests.py b/tests/modeltests/files/tests.py index 3bfe193610..837723a049 100644 --- a/tests/modeltests/files/tests.py +++ b/tests/modeltests/files/tests.py @@ -1,5 +1,6 @@ from __future__ import absolute_import +import gzip import shutil import tempfile @@ -8,11 +9,12 @@ from django.core.files import File from django.core.files.base import ContentFile from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase +from django.utils import unittest from .models import Storage, temp_storage, temp_storage_location -class FileTests(TestCase): +class FileStorageTests(TestCase): def tearDown(self): shutil.rmtree(temp_storage_location) @@ -106,6 +108,8 @@ class FileTests(TestCase): obj3.default.delete() obj4.random.delete() + +class FileTests(unittest.TestCase): def test_context_manager(self): orig_file = tempfile.TemporaryFile() base_file = File(orig_file) @@ -114,3 +118,10 @@ class FileTests(TestCase): self.assertFalse(f.closed) self.assertTrue(f.closed) self.assertTrue(orig_file.closed) + + def test_file_mode(self): + # Should not set mode to None if it is not present. + # See #14681, stdlib gzip module crashes if mode is set to None + file = SimpleUploadedFile("mode_test.txt", "content") + self.assertFalse(hasattr(file, 'mode')) + g = gzip.GzipFile(fileobj=file) From 473c272246dcb7cb2ea528e6405a465f16e82764 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 31 May 2012 16:24:37 +0200 Subject: [PATCH 73/95] Rewrote test_error_messages with helper test utility. --- .../validation/test_error_messages.py | 149 ++++++------------ 1 file changed, 48 insertions(+), 101 deletions(-) diff --git a/tests/modeltests/validation/test_error_messages.py b/tests/modeltests/validation/test_error_messages.py index 04ad7aadf7..214fa2c4d5 100644 --- a/tests/modeltests/validation/test_error_messages.py +++ b/tests/modeltests/validation/test_error_messages.py @@ -5,142 +5,89 @@ from django.utils.unittest import TestCase class ValidationMessagesTest(TestCase): + def _test_validation_messages(self, field, value, expected): + with self.assertRaises(ValidationError) as cm: + field.clean(value, None) + self.assertEqual(cm.exception.messages, expected) + def test_autofield_field_raises_error_message(self): f = models.AutoField(primary_key=True) - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, [u"'foo' value must be an integer."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value must be an integer."]) # primary_key must be True. Refs #12467. - self.assertRaises(AssertionError, models.AutoField, 'primary_key', False) - try: + with self.assertRaisesRegexp(AssertionError, + "AutoFields must have primary_key=True."): models.AutoField(primary_key=False) - except AssertionError as e: - self.assertEqual(str(e), "AutoFields must have primary_key=True.") def test_integer_field_raises_error_message(self): f = models.IntegerField() - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, [u"'foo' value must be an integer."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value must be an integer."]) def test_boolean_field_raises_error_message(self): f = models.BooleanField() - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, - [u"'foo' value must be either True or False."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value must be either True or False."]) def test_float_field_raises_error_message(self): f = models.FloatField() - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, [u"'foo' value must be a float."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value must be a float."]) def test_decimal_field_raises_error_message(self): f = models.DecimalField() - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, - [u"'foo' value must be a decimal number."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value must be a decimal number."]) def test_null_boolean_field_raises_error_message(self): f = models.NullBooleanField() - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, - [u"'foo' value must be either None, True or False."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value must be either None, True or False."]) def test_date_field_raises_error_message(self): f = models.DateField() - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'foo' value has an invalid date format. " - u"It must be in YYYY-MM-DD format."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value has an invalid date format. " + u"It must be in YYYY-MM-DD format."]) - self.assertRaises(ValidationError, f.clean, 'aaaa-10-10', None) - try: - f.clean('aaaa-10-10', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'aaaa-10-10' value has an invalid date format. " - u"It must be in YYYY-MM-DD format."]) + self._test_validation_messages(f, 'aaaa-10-10', + [u"'aaaa-10-10' value has an invalid date format. " + u"It must be in YYYY-MM-DD format."]) - self.assertRaises(ValidationError, f.clean, '2011-13-10', None) - try: - f.clean('2011-13-10', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'2011-13-10' value has the correct format (YYYY-MM-DD) " - u"but it is an invalid date."]) + self._test_validation_messages(f, '2011-13-10', + [u"'2011-13-10' value has the correct format (YYYY-MM-DD) " + u"but it is an invalid date."]) - self.assertRaises(ValidationError, f.clean, '2011-10-32', None) - try: - f.clean('2011-10-32', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'2011-10-32' value has the correct format (YYYY-MM-DD) " - u"but it is an invalid date."]) + self._test_validation_messages(f, '2011-10-32', + [u"'2011-10-32' value has the correct format (YYYY-MM-DD) " + u"but it is an invalid date."]) def test_datetime_field_raises_error_message(self): f = models.DateTimeField() # Wrong format - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'foo' value has an invalid format. It must be " - u"in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value has an invalid format. It must be " + u"in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."]) # Correct format but invalid date - self.assertRaises(ValidationError, f.clean, '2011-10-32', None) - try: - f.clean('2011-10-32', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'2011-10-32' value has the correct format " - u"(YYYY-MM-DD) but it is an invalid date."]) + self._test_validation_messages(f, '2011-10-32', + [u"'2011-10-32' value has the correct format " + u"(YYYY-MM-DD) but it is an invalid date."]) # Correct format but invalid date/time - self.assertRaises(ValidationError, f.clean, '2011-10-32 10:10', None) - try: - f.clean('2011-10-32 10:10', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'2011-10-32 10:10' value has the correct format " - u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " - u"but it is an invalid date/time."]) + self._test_validation_messages(f, '2011-10-32 10:10', + [u"'2011-10-32 10:10' value has the correct format " + u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " + u"but it is an invalid date/time."]) def test_time_field_raises_error_message(self): f = models.TimeField() # Wrong format - self.assertRaises(ValidationError, f.clean, 'foo', None) - try: - f.clean('foo', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'foo' value has an invalid format. It must be in " - u"HH:MM[:ss[.uuuuuu]] format."]) + self._test_validation_messages(f, 'foo', + [u"'foo' value has an invalid format. It must be in " + u"HH:MM[:ss[.uuuuuu]] format."]) + # Correct format but invalid time - self.assertRaises(ValidationError, f.clean, '25:50', None) - try: - f.clean('25:50', None) - except ValidationError as e: - self.assertEqual(e.messages, [ - u"'25:50' value has the correct format " - u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."]) + self._test_validation_messages(f, '25:50', + [u"'25:50' value has the correct format " + u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."]) From 5c59e43aef288d2f7adcaac4c0ae7692d6c01ba3 Mon Sep 17 00:00:00 2001 From: Daniel Roseman Date: Thu, 31 May 2012 15:33:45 +0100 Subject: [PATCH 74/95] Use render shortcut in form example. --- docs/topics/forms/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index 18e55f5eb6..4870ffc094 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -99,7 +99,7 @@ The standard pattern for processing a form in a view looks like this: else: form = ContactForm() # An unbound form - return render_to_response('contact.html', { + return render(request, 'contact.html', { 'form': form, }) From 0dc904979d5b6df78662653d498c91f4d54f36c2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 31 May 2012 16:46:07 +0200 Subject: [PATCH 75/95] Fixed #18407 -- Made model field's to_python methods fully accept unicode. When generating error message in to_python, any unicode string containing non-ascii characters triggered a UnicodeEncodeError for most field types. --- django/db/models/fields/__init__.py | 20 ++++------ .../validation/test_error_messages.py | 37 ++++++++++--------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 50976db339..8a317e79c8 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -17,7 +17,7 @@ from django.utils.functional import curry, total_ordering from django.utils.text import capfirst from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, force_unicode, smart_str +from django.utils.encoding import smart_unicode, force_unicode from django.utils.ipv6 import clean_ipv6_address class NOT_PROVIDED: @@ -530,7 +530,7 @@ class AutoField(Field): try: return int(value) except (TypeError, ValueError): - msg = self.error_messages['invalid'] % str(value) + msg = self.error_messages['invalid'] % value raise exceptions.ValidationError(msg) def validate(self, value, model_instance): @@ -582,7 +582,7 @@ class BooleanField(Field): return True if value in ('f', 'False', '0'): return False - msg = self.error_messages['invalid'] % str(value) + msg = self.error_messages['invalid'] % value raise exceptions.ValidationError(msg) def get_prep_lookup(self, lookup_type, value): @@ -686,8 +686,6 @@ class DateField(Field): if isinstance(value, datetime.date): return value - value = smart_str(value) - try: parsed = parse_date(value) if parsed is not None: @@ -779,8 +777,6 @@ class DateTimeField(DateField): value = timezone.make_aware(value, default_timezone) return value - value = smart_str(value) - try: parsed = parse_datetime(value) if parsed is not None: @@ -862,7 +858,7 @@ class DecimalField(Field): try: return decimal.Decimal(value) except decimal.InvalidOperation: - msg = self.error_messages['invalid'] % str(value) + msg = self.error_messages['invalid'] % value raise exceptions.ValidationError(msg) def _format(self, value): @@ -967,7 +963,7 @@ class FloatField(Field): try: return float(value) except (TypeError, ValueError): - msg = self.error_messages['invalid'] % str(value) + msg = self.error_messages['invalid'] % value raise exceptions.ValidationError(msg) def formfield(self, **kwargs): @@ -1002,7 +998,7 @@ class IntegerField(Field): try: return int(value) except (TypeError, ValueError): - msg = self.error_messages['invalid'] % str(value) + msg = self.error_messages['invalid'] % value raise exceptions.ValidationError(msg) def formfield(self, **kwargs): @@ -1107,7 +1103,7 @@ class NullBooleanField(Field): return True if value in ('f', 'False', '0'): return False - msg = self.error_messages['invalid'] % str(value) + msg = self.error_messages['invalid'] % value raise exceptions.ValidationError(msg) def get_prep_lookup(self, lookup_type, value): @@ -1228,8 +1224,6 @@ class TimeField(Field): # database backend (e.g. Oracle), so we'll be accommodating. return value.time() - value = smart_str(value) - try: parsed = parse_time(value) if parsed is not None: diff --git a/tests/modeltests/validation/test_error_messages.py b/tests/modeltests/validation/test_error_messages.py index 214fa2c4d5..4aea3d52de 100644 --- a/tests/modeltests/validation/test_error_messages.py +++ b/tests/modeltests/validation/test_error_messages.py @@ -1,3 +1,4 @@ +# -*- encoding: utf-8 -*- from django.core.exceptions import ValidationError from django.db import models from django.utils.unittest import TestCase @@ -12,8 +13,8 @@ class ValidationMessagesTest(TestCase): def test_autofield_field_raises_error_message(self): f = models.AutoField(primary_key=True) - self._test_validation_messages(f, 'foo', - [u"'foo' value must be an integer."]) + self._test_validation_messages(f, u'fõo', + [u"'fõo' value must be an integer."]) # primary_key must be True. Refs #12467. with self.assertRaisesRegexp(AssertionError, "AutoFields must have primary_key=True."): @@ -21,33 +22,33 @@ class ValidationMessagesTest(TestCase): def test_integer_field_raises_error_message(self): f = models.IntegerField() - self._test_validation_messages(f, 'foo', - [u"'foo' value must be an integer."]) + self._test_validation_messages(f, u'fõo', + [u"'fõo' value must be an integer."]) def test_boolean_field_raises_error_message(self): f = models.BooleanField() - self._test_validation_messages(f, 'foo', - [u"'foo' value must be either True or False."]) + self._test_validation_messages(f, u'fõo', + [u"'fõo' value must be either True or False."]) def test_float_field_raises_error_message(self): f = models.FloatField() - self._test_validation_messages(f, 'foo', - [u"'foo' value must be a float."]) + self._test_validation_messages(f, u'fõo', + [u"'fõo' value must be a float."]) def test_decimal_field_raises_error_message(self): f = models.DecimalField() - self._test_validation_messages(f, 'foo', - [u"'foo' value must be a decimal number."]) + self._test_validation_messages(f, u'fõo', + [u"'fõo' value must be a decimal number."]) def test_null_boolean_field_raises_error_message(self): f = models.NullBooleanField() - self._test_validation_messages(f, 'foo', - [u"'foo' value must be either None, True or False."]) + self._test_validation_messages(f, u'fõo', + [u"'fõo' value must be either None, True or False."]) def test_date_field_raises_error_message(self): f = models.DateField() - self._test_validation_messages(f, 'foo', - [u"'foo' value has an invalid date format. " + self._test_validation_messages(f, u'fõo', + [u"'fõo' value has an invalid date format. " u"It must be in YYYY-MM-DD format."]) self._test_validation_messages(f, 'aaaa-10-10', @@ -65,8 +66,8 @@ class ValidationMessagesTest(TestCase): def test_datetime_field_raises_error_message(self): f = models.DateTimeField() # Wrong format - self._test_validation_messages(f, 'foo', - [u"'foo' value has an invalid format. It must be " + self._test_validation_messages(f, u'fõo', + [u"'fõo' value has an invalid format. It must be " u"in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."]) # Correct format but invalid date @@ -83,8 +84,8 @@ class ValidationMessagesTest(TestCase): def test_time_field_raises_error_message(self): f = models.TimeField() # Wrong format - self._test_validation_messages(f, 'foo', - [u"'foo' value has an invalid format. It must be in " + self._test_validation_messages(f, u'fõo', + [u"'fõo' value has an invalid format. It must be in " u"HH:MM[:ss[.uuuuuu]] format."]) # Correct format but invalid time From 4553f511557052d6f18811807ae6136f81fa86a3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 31 May 2012 17:21:13 +0200 Subject: [PATCH 76/95] Moved test_client_regress tests from models.py to tests.py --- .../test_client_regress/models.py | 1021 ----------------- .../test_client_regress/tests.py | 1021 +++++++++++++++++ 2 files changed, 1021 insertions(+), 1021 deletions(-) create mode 100644 tests/regressiontests/test_client_regress/tests.py diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index ca55bd156b..e69de29bb2 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -1,1021 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Regression tests for the Test Client, especially the customized assertions. -""" -import os - -from django.conf import settings -from django.core.exceptions import SuspiciousOperation -from django.core.urlresolvers import reverse -from django.template import (TemplateDoesNotExist, TemplateSyntaxError, - Context, Template, loader) -import django.template.context -from django.test import Client, TestCase -from django.test.client import encode_file, RequestFactory -from django.test.utils import ContextList, override_settings -from django.template.response import SimpleTemplateResponse -from django.http import HttpResponse - - -class AssertContainsTests(TestCase): - def setUp(self): - self.old_templates = settings.TEMPLATE_DIRS - settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),) - - def tearDown(self): - settings.TEMPLATE_DIRS = self.old_templates - - def test_contains(self): - "Responses can be inspected for content, including counting repeated substrings" - response = self.client.get('/test_client_regress/no_template_view/') - - self.assertNotContains(response, 'never') - self.assertContains(response, 'never', 0) - self.assertContains(response, 'once') - self.assertContains(response, 'once', 1) - self.assertContains(response, 'twice') - self.assertContains(response, 'twice', 2) - - try: - self.assertContains(response, 'text', status_code=999) - except AssertionError as e: - self.assertIn("Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) - try: - self.assertContains(response, 'text', status_code=999, msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) - - try: - self.assertNotContains(response, 'text', status_code=999) - except AssertionError as e: - self.assertIn("Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) - try: - self.assertNotContains(response, 'text', status_code=999, msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) - - try: - self.assertNotContains(response, 'once') - except AssertionError as e: - self.assertIn("Response should not contain 'once'", str(e)) - try: - self.assertNotContains(response, 'once', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Response should not contain 'once'", str(e)) - - try: - self.assertContains(response, 'never', 1) - except AssertionError as e: - self.assertIn("Found 0 instances of 'never' in response (expected 1)", str(e)) - try: - self.assertContains(response, 'never', 1, msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Found 0 instances of 'never' in response (expected 1)", str(e)) - - try: - self.assertContains(response, 'once', 0) - except AssertionError as e: - self.assertIn("Found 1 instances of 'once' in response (expected 0)", str(e)) - try: - self.assertContains(response, 'once', 0, msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Found 1 instances of 'once' in response (expected 0)", str(e)) - - try: - self.assertContains(response, 'once', 2) - except AssertionError as e: - self.assertIn("Found 1 instances of 'once' in response (expected 2)", str(e)) - try: - self.assertContains(response, 'once', 2, msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Found 1 instances of 'once' in response (expected 2)", str(e)) - - try: - self.assertContains(response, 'twice', 1) - except AssertionError as e: - self.assertIn("Found 2 instances of 'twice' in response (expected 1)", str(e)) - try: - self.assertContains(response, 'twice', 1, msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Found 2 instances of 'twice' in response (expected 1)", str(e)) - - try: - self.assertContains(response, 'thrice') - except AssertionError as e: - self.assertIn("Couldn't find 'thrice' in response", str(e)) - try: - self.assertContains(response, 'thrice', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Couldn't find 'thrice' in response", str(e)) - - try: - self.assertContains(response, 'thrice', 3) - except AssertionError as e: - self.assertIn("Found 0 instances of 'thrice' in response (expected 3)", str(e)) - try: - self.assertContains(response, 'thrice', 3, msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Found 0 instances of 'thrice' in response (expected 3)", str(e)) - - def test_unicode_contains(self): - "Unicode characters can be found in template context" - #Regression test for #10183 - r = self.client.get('/test_client_regress/check_unicode/') - self.assertContains(r, u'さかき') - self.assertContains(r, b'\xe5\xb3\xa0'.decode('utf-8')) - - def test_unicode_not_contains(self): - "Unicode characters can be searched for, and not found in template context" - #Regression test for #10183 - r = self.client.get('/test_client_regress/check_unicode/') - self.assertNotContains(r, u'はたけ') - self.assertNotContains(r, b'\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode('utf-8')) - - def test_assert_contains_renders_template_response(self): - """ Test that we can pass in an unrendered SimpleTemplateReponse - without throwing an error. - Refs #15826. - """ - response = SimpleTemplateResponse(Template('Hello'), status=200) - self.assertContains(response, 'Hello') - - def test_assert_contains_using_non_template_response(self): - """ Test that auto-rendering does not affect responses that aren't - instances (or subclasses) of SimpleTemplateResponse. - Refs #15826. - """ - response = HttpResponse('Hello') - self.assertContains(response, 'Hello') - - def test_assert_not_contains_renders_template_response(self): - """ Test that we can pass in an unrendered SimpleTemplateReponse - without throwing an error. - Refs #15826. - """ - response = SimpleTemplateResponse(Template('Hello'), status=200) - self.assertNotContains(response, 'Bye') - - def test_assert_not_contains_using_non_template_response(self): - """ Test that auto-rendering does not affect responses that aren't - instances (or subclasses) of SimpleTemplateResponse. - Refs #15826. - """ - response = HttpResponse('Hello') - self.assertNotContains(response, 'Bye') - -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class AssertTemplateUsedTests(TestCase): - fixtures = ['testdata.json'] - - def test_no_context(self): - "Template usage assertions work then templates aren't in use" - response = self.client.get('/test_client_regress/no_template_view/') - - # Check that the no template case doesn't mess with the template assertions - self.assertTemplateNotUsed(response, 'GET Template') - - try: - self.assertTemplateUsed(response, 'GET Template') - except AssertionError as e: - self.assertIn("No templates used to render the response", str(e)) - - try: - self.assertTemplateUsed(response, 'GET Template', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: No templates used to render the response", str(e)) - - def test_single_context(self): - "Template assertions work when there is a single context" - response = self.client.get('/test_client/post_view/', {}) - - try: - self.assertTemplateNotUsed(response, 'Empty GET Template') - except AssertionError as e: - self.assertIn("Template 'Empty GET Template' was used unexpectedly in rendering the response", str(e)) - - try: - self.assertTemplateNotUsed(response, 'Empty GET Template', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Template 'Empty GET Template' was used unexpectedly in rendering the response", str(e)) - - try: - self.assertTemplateUsed(response, 'Empty POST Template') - except AssertionError as e: - self.assertIn("Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template", str(e)) - - try: - self.assertTemplateUsed(response, 'Empty POST Template', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template", str(e)) - - def test_multiple_context(self): - "Template assertions work when there are multiple contexts" - post_data = { - 'text': 'Hello World', - 'email': 'foo@example.com', - 'value': 37, - 'single': 'b', - 'multi': ('b','c','e') - } - response = self.client.post('/test_client/form_view_with_template/', post_data) - self.assertContains(response, 'POST data OK') - try: - self.assertTemplateNotUsed(response, "form_view.html") - except AssertionError as e: - self.assertIn("Template 'form_view.html' was used unexpectedly in rendering the response", str(e)) - - try: - self.assertTemplateNotUsed(response, 'base.html') - except AssertionError as e: - self.assertIn("Template 'base.html' was used unexpectedly in rendering the response", str(e)) - - try: - self.assertTemplateUsed(response, "Valid POST Template") - except AssertionError as e: - self.assertIn("Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html", str(e)) - -class AssertRedirectsTests(TestCase): - def test_redirect_page(self): - "An assertion is raised if the original page couldn't be retrieved as expected" - # This page will redirect with code 301, not 302 - response = self.client.get('/test_client/permanent_redirect_view/') - try: - self.assertRedirects(response, '/test_client/get_view/') - except AssertionError as e: - self.assertIn("Response didn't redirect as expected: Response code was 301 (expected 302)", str(e)) - - try: - self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Response didn't redirect as expected: Response code was 301 (expected 302)", str(e)) - - def test_lost_query(self): - "An assertion is raised if the redirect location doesn't preserve GET parameters" - response = self.client.get('/test_client/redirect_view/', {'var': 'value'}) - try: - self.assertRedirects(response, '/test_client/get_view/') - except AssertionError as e: - self.assertIn("Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'", str(e)) - - try: - self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'", str(e)) - - def test_incorrect_target(self): - "An assertion is raised if the response redirects to another target" - response = self.client.get('/test_client/permanent_redirect_view/') - try: - # Should redirect to get_view - self.assertRedirects(response, '/test_client/some_view/') - except AssertionError as e: - self.assertIn("Response didn't redirect as expected: Response code was 301 (expected 302)", str(e)) - - def test_target_page(self): - "An assertion is raised if the response redirect target cannot be retrieved as expected" - response = self.client.get('/test_client/double_redirect_view/') - try: - # The redirect target responds with a 301 code, not 200 - self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/') - except AssertionError as e: - self.assertIn("Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)", str(e)) - - try: - # The redirect target responds with a 301 code, not 200 - self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)", str(e)) - - def test_redirect_chain(self): - "You can follow a redirect chain of multiple redirects" - response = self.client.get('/test_client_regress/redirects/further/more/', {}, follow=True) - self.assertRedirects(response, '/test_client_regress/no_template_view/', - status_code=301, target_status_code=200) - - self.assertEqual(len(response.redirect_chain), 1) - self.assertEqual(response.redirect_chain[0], ('http://testserver/test_client_regress/no_template_view/', 301)) - - def test_multiple_redirect_chain(self): - "You can follow a redirect chain of multiple redirects" - response = self.client.get('/test_client_regress/redirects/', {}, follow=True) - self.assertRedirects(response, '/test_client_regress/no_template_view/', - status_code=301, target_status_code=200) - - self.assertEqual(len(response.redirect_chain), 3) - self.assertEqual(response.redirect_chain[0], ('http://testserver/test_client_regress/redirects/further/', 301)) - self.assertEqual(response.redirect_chain[1], ('http://testserver/test_client_regress/redirects/further/more/', 301)) - self.assertEqual(response.redirect_chain[2], ('http://testserver/test_client_regress/no_template_view/', 301)) - - def test_redirect_chain_to_non_existent(self): - "You can follow a chain to a non-existent view" - response = self.client.get('/test_client_regress/redirect_to_non_existent_view2/', {}, follow=True) - self.assertRedirects(response, '/test_client_regress/non_existent_view/', - status_code=301, target_status_code=404) - - def test_redirect_chain_to_self(self): - "Redirections to self are caught and escaped" - response = self.client.get('/test_client_regress/redirect_to_self/', {}, follow=True) - # The chain of redirects stops once the cycle is detected. - self.assertRedirects(response, '/test_client_regress/redirect_to_self/', - status_code=301, target_status_code=301) - self.assertEqual(len(response.redirect_chain), 2) - - def test_circular_redirect(self): - "Circular redirect chains are caught and escaped" - response = self.client.get('/test_client_regress/circular_redirect_1/', {}, follow=True) - # The chain of redirects will get back to the starting point, but stop there. - self.assertRedirects(response, '/test_client_regress/circular_redirect_2/', - status_code=301, target_status_code=301) - self.assertEqual(len(response.redirect_chain), 4) - - def test_redirect_chain_post(self): - "A redirect chain will be followed from an initial POST post" - response = self.client.post('/test_client_regress/redirects/', - {'nothing': 'to_send'}, follow=True) - self.assertRedirects(response, - '/test_client_regress/no_template_view/', 301, 200) - self.assertEqual(len(response.redirect_chain), 3) - - def test_redirect_chain_head(self): - "A redirect chain will be followed from an initial HEAD request" - response = self.client.head('/test_client_regress/redirects/', - {'nothing': 'to_send'}, follow=True) - self.assertRedirects(response, - '/test_client_regress/no_template_view/', 301, 200) - self.assertEqual(len(response.redirect_chain), 3) - - def test_redirect_chain_options(self): - "A redirect chain will be followed from an initial OPTIONS request" - response = self.client.options('/test_client_regress/redirects/', - follow=True) - self.assertRedirects(response, - '/test_client_regress/no_template_view/', 301, 200) - self.assertEqual(len(response.redirect_chain), 3) - - def test_redirect_chain_put(self): - "A redirect chain will be followed from an initial PUT request" - response = self.client.put('/test_client_regress/redirects/', - follow=True) - self.assertRedirects(response, - '/test_client_regress/no_template_view/', 301, 200) - self.assertEqual(len(response.redirect_chain), 3) - - def test_redirect_chain_delete(self): - "A redirect chain will be followed from an initial DELETE request" - response = self.client.delete('/test_client_regress/redirects/', - follow=True) - self.assertRedirects(response, - '/test_client_regress/no_template_view/', 301, 200) - self.assertEqual(len(response.redirect_chain), 3) - - def test_redirect_to_different_host(self): - "The test client will preserve scheme, host and port changes" - response = self.client.get('/test_client_regress/redirect_other_host/', follow=True) - self.assertRedirects(response, - 'https://otherserver:8443/test_client_regress/no_template_view/', - status_code=301, target_status_code=200) - # We can't use is_secure() or get_host() - # because response.request is a dictionary, not an HttpRequest - self.assertEqual(response.request.get('wsgi.url_scheme'), 'https') - self.assertEqual(response.request.get('SERVER_NAME'), 'otherserver') - self.assertEqual(response.request.get('SERVER_PORT'), '8443') - - def test_redirect_chain_on_non_redirect_page(self): - "An assertion is raised if the original page couldn't be retrieved as expected" - # This page will redirect with code 301, not 302 - response = self.client.get('/test_client/get_view/', follow=True) - try: - self.assertRedirects(response, '/test_client/get_view/') - except AssertionError as e: - self.assertIn("Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) - - try: - self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) - - def test_redirect_on_non_redirect_page(self): - "An assertion is raised if the original page couldn't be retrieved as expected" - # This page will redirect with code 301, not 302 - response = self.client.get('/test_client/get_view/') - try: - self.assertRedirects(response, '/test_client/get_view/') - except AssertionError as e: - self.assertIn("Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) - - try: - self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) - - -class AssertFormErrorTests(TestCase): - def test_unknown_form(self): - "An assertion is raised if the form name is unknown" - post_data = { - 'text': 'Hello World', - 'email': 'not an email address', - 'value': 37, - 'single': 'b', - 'multi': ('b','c','e') - } - response = self.client.post('/test_client/form_view/', post_data) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "Invalid POST Template") - - try: - self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.') - except AssertionError as e: - self.assertIn("The form 'wrong_form' was not used to render the response", str(e)) - try: - self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: The form 'wrong_form' was not used to render the response", str(e)) - - def test_unknown_field(self): - "An assertion is raised if the field name is unknown" - post_data = { - 'text': 'Hello World', - 'email': 'not an email address', - 'value': 37, - 'single': 'b', - 'multi': ('b','c','e') - } - response = self.client.post('/test_client/form_view/', post_data) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "Invalid POST Template") - - try: - self.assertFormError(response, 'form', 'some_field', 'Some error.') - except AssertionError as e: - self.assertIn("The form 'form' in context 0 does not contain the field 'some_field'", str(e)) - try: - self.assertFormError(response, 'form', 'some_field', 'Some error.', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: The form 'form' in context 0 does not contain the field 'some_field'", str(e)) - - def test_noerror_field(self): - "An assertion is raised if the field doesn't have any errors" - post_data = { - 'text': 'Hello World', - 'email': 'not an email address', - 'value': 37, - 'single': 'b', - 'multi': ('b','c','e') - } - response = self.client.post('/test_client/form_view/', post_data) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "Invalid POST Template") - - try: - self.assertFormError(response, 'form', 'value', 'Some error.') - except AssertionError as e: - self.assertIn("The field 'value' on form 'form' in context 0 contains no errors", str(e)) - try: - self.assertFormError(response, 'form', 'value', 'Some error.', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: The field 'value' on form 'form' in context 0 contains no errors", str(e)) - - def test_unknown_error(self): - "An assertion is raised if the field doesn't contain the provided error" - post_data = { - 'text': 'Hello World', - 'email': 'not an email address', - 'value': 37, - 'single': 'b', - 'multi': ('b','c','e') - } - response = self.client.post('/test_client/form_view/', post_data) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "Invalid POST Template") - - try: - self.assertFormError(response, 'form', 'email', 'Some error.') - except AssertionError as e: - self.assertIn("The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])", str(e)) - try: - self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])", str(e)) - - def test_unknown_nonfield_error(self): - """ - Checks that an assertion is raised if the form's non field errors - doesn't contain the provided error. - """ - post_data = { - 'text': 'Hello World', - 'email': 'not an email address', - 'value': 37, - 'single': 'b', - 'multi': ('b','c','e') - } - response = self.client.post('/test_client/form_view/', post_data) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "Invalid POST Template") - - try: - self.assertFormError(response, 'form', None, 'Some error.') - except AssertionError as e: - self.assertIn("The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e)) - try: - self.assertFormError(response, 'form', None, 'Some error.', msg_prefix='abc') - except AssertionError as e: - self.assertIn("abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e)) - -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class LoginTests(TestCase): - fixtures = ['testdata'] - - def test_login_different_client(self): - "Check that using a different test client doesn't violate authentication" - - # Create a second client, and log in. - c = Client() - login = c.login(username='testclient', password='password') - self.assertTrue(login, 'Could not log in') - - # Get a redirection page with the second client. - response = c.get("/test_client_regress/login_protected_redirect_view/") - - # At this points, the self.client isn't logged in. - # Check that assertRedirects uses the original client, not the - # default client. - self.assertRedirects(response, "http://testserver/test_client_regress/get_view/") - - -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class SessionEngineTests(TestCase): - fixtures = ['testdata'] - - def setUp(self): - self.old_SESSION_ENGINE = settings.SESSION_ENGINE - settings.SESSION_ENGINE = 'regressiontests.test_client_regress.session' - - def tearDown(self): - settings.SESSION_ENGINE = self.old_SESSION_ENGINE - - def test_login(self): - "A session engine that modifies the session key can be used to log in" - login = self.client.login(username='testclient', password='password') - self.assertTrue(login, 'Could not log in') - - # Try to access a login protected page. - response = self.client.get("/test_client/login_protected_view/") - self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['user'].username, 'testclient') - - -class URLEscapingTests(TestCase): - def test_simple_argument_get(self): - "Get a view that has a simple string argument" - response = self.client.get(reverse('arg_view', args=['Slartibartfast'])) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'Howdy, Slartibartfast') - - def test_argument_with_space_get(self): - "Get a view that has a string argument that requires escaping" - response = self.client.get(reverse('arg_view', args=['Arthur Dent'])) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'Hi, Arthur') - - def test_simple_argument_post(self): - "Post for a view that has a simple string argument" - response = self.client.post(reverse('arg_view', args=['Slartibartfast'])) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'Howdy, Slartibartfast') - - def test_argument_with_space_post(self): - "Post for a view that has a string argument that requires escaping" - response = self.client.post(reverse('arg_view', args=['Arthur Dent'])) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'Hi, Arthur') - -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class ExceptionTests(TestCase): - fixtures = ['testdata.json'] - - def test_exception_cleared(self): - "#5836 - A stale user exception isn't re-raised by the test client." - - login = self.client.login(username='testclient',password='password') - self.assertTrue(login, 'Could not log in') - try: - response = self.client.get("/test_client_regress/staff_only/") - self.fail("General users should not be able to visit this page") - except SuspiciousOperation: - pass - - # At this point, an exception has been raised, and should be cleared. - - # This next operation should be successful; if it isn't we have a problem. - login = self.client.login(username='staff', password='password') - self.assertTrue(login, 'Could not log in') - try: - self.client.get("/test_client_regress/staff_only/") - except SuspiciousOperation: - self.fail("Staff should be able to visit this page") - -class TemplateExceptionTests(TestCase): - def setUp(self): - # Reset the loaders so they don't try to render cached templates. - if loader.template_source_loaders is not None: - for template_loader in loader.template_source_loaders: - if hasattr(template_loader, 'reset'): - template_loader.reset() - self.old_templates = settings.TEMPLATE_DIRS - settings.TEMPLATE_DIRS = () - - def tearDown(self): - settings.TEMPLATE_DIRS = self.old_templates - - def test_no_404_template(self): - "Missing templates are correctly reported by test client" - try: - response = self.client.get("/no_such_view/") - self.fail("Should get error about missing template") - except TemplateDoesNotExist: - pass - - def test_bad_404_template(self): - "Errors found when rendering 404 error templates are re-raised" - settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'bad_templates'),) - try: - response = self.client.get("/no_such_view/") - self.fail("Should get error about syntax error in template") - except TemplateSyntaxError: - pass - -# We need two different tests to check URLconf substitution - one to check -# it was changed, and another one (without self.urls) to check it was reverted on -# teardown. This pair of tests relies upon the alphabetical ordering of test execution. -class UrlconfSubstitutionTests(TestCase): - urls = 'regressiontests.test_client_regress.urls' - - def test_urlconf_was_changed(self): - "TestCase can enforce a custom URLconf on a per-test basis" - url = reverse('arg_view', args=['somename']) - self.assertEqual(url, '/arg_view/somename/') - -# This test needs to run *after* UrlconfSubstitutionTests; the zz prefix in the -# name is to ensure alphabetical ordering. -class zzUrlconfSubstitutionTests(TestCase): - def test_urlconf_was_reverted(self): - "URLconf is reverted to original value after modification in a TestCase" - url = reverse('arg_view', args=['somename']) - self.assertEqual(url, '/test_client_regress/arg_view/somename/') - -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class ContextTests(TestCase): - fixtures = ['testdata'] - - def test_single_context(self): - "Context variables can be retrieved from a single context" - response = self.client.get("/test_client_regress/request_data/", data={'foo':'whiz'}) - self.assertEqual(response.context.__class__, Context) - self.assertTrue('get-foo' in response.context) - self.assertEqual(response.context['get-foo'], 'whiz') - self.assertEqual(response.context['request-foo'], 'whiz') - self.assertEqual(response.context['data'], 'sausage') - - try: - response.context['does-not-exist'] - self.fail('Should not be able to retrieve non-existent key') - except KeyError as e: - self.assertEqual(e.args[0], 'does-not-exist') - - def test_inherited_context(self): - "Context variables can be retrieved from a list of contexts" - response = self.client.get("/test_client_regress/request_data_extended/", data={'foo':'whiz'}) - self.assertEqual(response.context.__class__, ContextList) - self.assertEqual(len(response.context), 2) - self.assertTrue('get-foo' in response.context) - self.assertEqual(response.context['get-foo'], 'whiz') - self.assertEqual(response.context['request-foo'], 'whiz') - self.assertEqual(response.context['data'], 'bacon') - - try: - response.context['does-not-exist'] - self.fail('Should not be able to retrieve non-existent key') - except KeyError as e: - self.assertEqual(e.args[0], 'does-not-exist') - - def test_15368(self): - # Need to insert a context processor that assumes certain things about - # the request instance. This triggers a bug caused by some ways of - # copying RequestContext. - try: - django.template.context._standard_context_processors = (lambda request: {'path': request.special_path},) - response = self.client.get("/test_client_regress/request_context_view/") - self.assertContains(response, 'Path: /test_client_regress/request_context_view/') - finally: - django.template.context._standard_context_processors = None - - -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class SessionTests(TestCase): - fixtures = ['testdata.json'] - - def test_session(self): - "The session isn't lost if a user logs in" - # The session doesn't exist to start. - response = self.client.get('/test_client_regress/check_session/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'NO') - - # This request sets a session variable. - response = self.client.get('/test_client_regress/set_session/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'set_session') - - # Check that the session has been modified - response = self.client.get('/test_client_regress/check_session/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'YES') - - # Log in - login = self.client.login(username='testclient',password='password') - self.assertTrue(login, 'Could not log in') - - # Session should still contain the modified value - response = self.client.get('/test_client_regress/check_session/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'YES') - - def test_logout(self): - """Logout should work whether the user is logged in or not (#9978).""" - self.client.logout() - login = self.client.login(username='testclient',password='password') - self.assertTrue(login, 'Could not log in') - self.client.logout() - self.client.logout() - -class RequestMethodTests(TestCase): - def test_get(self): - "Request a view via request method GET" - response = self.client.get('/test_client_regress/request_methods/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'request method: GET') - - def test_post(self): - "Request a view via request method POST" - response = self.client.post('/test_client_regress/request_methods/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'request method: POST') - - def test_head(self): - "Request a view via request method HEAD" - response = self.client.head('/test_client_regress/request_methods/') - self.assertEqual(response.status_code, 200) - # A HEAD request doesn't return any content. - self.assertNotEqual(response.content, b'request method: HEAD') - self.assertEqual(response.content, b'') - - def test_options(self): - "Request a view via request method OPTIONS" - response = self.client.options('/test_client_regress/request_methods/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'request method: OPTIONS') - - def test_put(self): - "Request a view via request method PUT" - response = self.client.put('/test_client_regress/request_methods/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'request method: PUT') - - def test_delete(self): - "Request a view via request method DELETE" - response = self.client.delete('/test_client_regress/request_methods/') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'request method: DELETE') - -class RequestMethodStringDataTests(TestCase): - def test_post(self): - "Request a view with string data via request method POST" - # Regression test for #11371 - data = u'{"test": "json"}' - response = self.client.post('/test_client_regress/request_methods/', data=data, content_type='application/json') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'request method: POST') - - def test_put(self): - "Request a view with string data via request method PUT" - # Regression test for #11371 - data = u'{"test": "json"}' - response = self.client.put('/test_client_regress/request_methods/', data=data, content_type='application/json') - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'request method: PUT') - -class QueryStringTests(TestCase): - def test_get_like_requests(self): - # See: https://code.djangoproject.com/ticket/10571. - for method_name in ('get', 'head'): - # 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'}) - self.assertEqual(response.context['get-foo'], 'whiz') - self.assertEqual(response.context['request-foo'], 'whiz') - - # A GET-like request can pass a query string as part of the URL - response = method("/test_client_regress/request_data/?foo=whiz") - self.assertEqual(response.context['get-foo'], 'whiz') - self.assertEqual(response.context['request-foo'], 'whiz') - - # Data provided in the URL to a GET-like request is overridden by actual form data - response = method("/test_client_regress/request_data/?foo=whiz", data={'foo':'bang'}) - self.assertEqual(response.context['get-foo'], 'bang') - self.assertEqual(response.context['request-foo'], 'bang') - - response = method("/test_client_regress/request_data/?foo=whiz", data={'bar':'bang'}) - self.assertEqual(response.context['get-foo'], None) - self.assertEqual(response.context['get-bar'], 'bang') - self.assertEqual(response.context['request-foo'], None) - self.assertEqual(response.context['request-bar'], 'bang') - - def test_post_like_requests(self): - # A POST-like request can pass a query string as data - response = self.client.post("/test_client_regress/request_data/", data={'foo':'whiz'}) - self.assertEqual(response.context['get-foo'], None) - self.assertEqual(response.context['post-foo'], 'whiz') - - # A POST-like request can pass a query string as part of the URL - response = self.client.post("/test_client_regress/request_data/?foo=whiz") - self.assertEqual(response.context['get-foo'], 'whiz') - self.assertEqual(response.context['post-foo'], None) - self.assertEqual(response.context['request-foo'], 'whiz') - - # POST data provided in the URL augments actual form data - response = self.client.post("/test_client_regress/request_data/?foo=whiz", data={'foo':'bang'}) - self.assertEqual(response.context['get-foo'], 'whiz') - self.assertEqual(response.context['post-foo'], 'bang') - self.assertEqual(response.context['request-foo'], 'bang') - - response = self.client.post("/test_client_regress/request_data/?foo=whiz", data={'bar':'bang'}) - self.assertEqual(response.context['get-foo'], 'whiz') - self.assertEqual(response.context['get-bar'], None) - self.assertEqual(response.context['post-foo'], None) - self.assertEqual(response.context['post-bar'], 'bang') - self.assertEqual(response.context['request-foo'], 'whiz') - self.assertEqual(response.context['request-bar'], 'bang') - -class UnicodePayloadTests(TestCase): - def test_simple_unicode_payload(self): - "A simple ASCII-only unicode JSON document can be POSTed" - # Regression test for #10571 - json = u'{"english": "mountain pass"}' - response = self.client.post("/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" - # Regression test for #10571 - json = u'{"dog": "собака"}' - 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')) - - def test_unicode_payload_utf16(self): - "A non-ASCII unicode data encoded as UTF-16 can be POSTed" - # Regression test for #10571 - json = u'{"dog": "собака"}' - 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')) - - def test_unicode_payload_non_utf(self): - "A non-ASCII unicode data as a non-UTF based encoding can be POSTed" - #Regression test for #10571 - json = u'{"dog": "собака"}' - 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')) - -class DummyFile(object): - def __init__(self, filename): - self.name = filename - def read(self): - return 'TEST_FILE_CONTENT' - -class UploadedFileEncodingTest(TestCase): - def test_file_encoding(self): - encoded_file = encode_file('TEST_BOUNDARY', 'TEST_KEY', DummyFile('test_name.bin')) - self.assertEqual(b'--TEST_BOUNDARY', encoded_file[0]) - self.assertEqual(b'Content-Disposition: form-data; name="TEST_KEY"; filename="test_name.bin"', encoded_file[1]) - self.assertEqual(b'TEST_FILE_CONTENT', encoded_file[-1]) - - def test_guesses_content_type_on_file_encoding(self): - self.assertEqual(b'Content-Type: application/octet-stream', - encode_file('IGNORE', 'IGNORE', DummyFile("file.bin"))[2]) - self.assertEqual(b'Content-Type: text/plain', - encode_file('IGNORE', 'IGNORE', DummyFile("file.txt"))[2]) - self.assertIn(encode_file('IGNORE', 'IGNORE', DummyFile("file.zip"))[2], ( - b'Content-Type: application/x-compress', - b'Content-Type: application/x-zip', - b'Content-Type: application/x-zip-compressed', - b'Content-Type: application/zip',)) - self.assertEqual(b'Content-Type: application/octet-stream', - encode_file('IGNORE', 'IGNORE', DummyFile("file.unknown"))[2]) - -class RequestHeadersTest(TestCase): - def test_client_headers(self): - "A test client can receive custom headers" - response = self.client.get("/test_client_regress/check_headers/", HTTP_X_ARG_CHECK='Testing 123') - self.assertEqual(response.content, b"HTTP_X_ARG_CHECK: Testing 123") - self.assertEqual(response.status_code, 200) - - def test_client_headers_redirect(self): - "Test client headers are preserved through redirects" - response = self.client.get("/test_client_regress/check_headers_redirect/", follow=True, HTTP_X_ARG_CHECK='Testing 123') - self.assertEqual(response.content, b"HTTP_X_ARG_CHECK: Testing 123") - self.assertRedirects(response, '/test_client_regress/check_headers/', - status_code=301, target_status_code=200) - - -class ReadLimitedStreamTest(TestCase): - """ - Tests that ensure that HttpRequest.body, HttpRequest.read() and - HttpRequest.read(BUFFER) have proper LimitedStream behavior. - - Refs #14753, #15785 - """ - - def test_body_from_empty_request(self): - """HttpRequest.body on a test client GET request should return - the empty string.""" - self.assertEqual(self.client.get("/test_client_regress/body/").content, b'') - - def test_read_from_empty_request(self): - """HttpRequest.read() on a test client GET request should return the - empty string.""" - self.assertEqual(self.client.get("/test_client_regress/read_all/").content, b'') - - def test_read_numbytes_from_empty_request(self): - """HttpRequest.read(LARGE_BUFFER) on a test client GET request should - return the empty string.""" - self.assertEqual(self.client.get("/test_client_regress/read_buffer/").content, b'') - - def test_read_from_nonempty_request(self): - """HttpRequest.read() on a test client PUT request with some payload - should return that payload.""" - payload = b'foobar' - self.assertEqual(self.client.put("/test_client_regress/read_all/", - data=payload, - content_type='text/plain').content, payload) - - def test_read_numbytes_from_nonempty_request(self): - """HttpRequest.read(LARGE_BUFFER) on a test client PUT request with - some payload should return that payload.""" - payload = b'foobar' - self.assertEqual(self.client.put("/test_client_regress/read_buffer/", - data=payload, - content_type='text/plain').content, payload) - - -class RequestFactoryStateTest(TestCase): - """Regression tests for #15929.""" - # These tests are checking that certain middleware don't change certain - # global state. Alternatively, from the point of view of a test, they are - # ensuring test isolation behavior. So, unusually, it doesn't make sense to - # run the tests individually, and if any are failing it is confusing to run - # them with any other set of tests. - - def setUp(self): - self.factory = RequestFactory() - - def common_test_that_should_always_pass(self): - request = self.factory.get('/') - request.session = {} - self.assertFalse(hasattr(request, 'user')) - - def test_request(self): - self.common_test_that_should_always_pass() - - def test_request_after_client(self): - # apart from the next line the three tests are identical - self.client.get('/') - self.common_test_that_should_always_pass() - - def test_request_after_client_2(self): - # This test is executed after the previous one - self.common_test_that_should_always_pass() - - -class RequestFactoryEnvironmentTests(TestCase): - """ - Regression tests for #8551 and #17067: ensure that environment variables - are set correctly in RequestFactory. - """ - - def setUp(self): - self.factory = RequestFactory() - - def test_should_set_correct_env_variables(self): - request = self.factory.get('/path/') - - self.assertEqual(request.META.get('REMOTE_ADDR'), '127.0.0.1') - self.assertEqual(request.META.get('SERVER_NAME'), 'testserver') - self.assertEqual(request.META.get('SERVER_PORT'), '80') - self.assertEqual(request.META.get('SERVER_PROTOCOL'), 'HTTP/1.1') - self.assertEqual(request.META.get('SCRIPT_NAME') + - request.META.get('PATH_INFO'), '/path/') diff --git a/tests/regressiontests/test_client_regress/tests.py b/tests/regressiontests/test_client_regress/tests.py new file mode 100644 index 0000000000..ca55bd156b --- /dev/null +++ b/tests/regressiontests/test_client_regress/tests.py @@ -0,0 +1,1021 @@ +# -*- coding: utf-8 -*- +""" +Regression tests for the Test Client, especially the customized assertions. +""" +import os + +from django.conf import settings +from django.core.exceptions import SuspiciousOperation +from django.core.urlresolvers import reverse +from django.template import (TemplateDoesNotExist, TemplateSyntaxError, + Context, Template, loader) +import django.template.context +from django.test import Client, TestCase +from django.test.client import encode_file, RequestFactory +from django.test.utils import ContextList, override_settings +from django.template.response import SimpleTemplateResponse +from django.http import HttpResponse + + +class AssertContainsTests(TestCase): + def setUp(self): + self.old_templates = settings.TEMPLATE_DIRS + settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),) + + def tearDown(self): + settings.TEMPLATE_DIRS = self.old_templates + + def test_contains(self): + "Responses can be inspected for content, including counting repeated substrings" + response = self.client.get('/test_client_regress/no_template_view/') + + self.assertNotContains(response, 'never') + self.assertContains(response, 'never', 0) + self.assertContains(response, 'once') + self.assertContains(response, 'once', 1) + self.assertContains(response, 'twice') + self.assertContains(response, 'twice', 2) + + try: + self.assertContains(response, 'text', status_code=999) + except AssertionError as e: + self.assertIn("Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) + try: + self.assertContains(response, 'text', status_code=999, msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) + + try: + self.assertNotContains(response, 'text', status_code=999) + except AssertionError as e: + self.assertIn("Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) + try: + self.assertNotContains(response, 'text', status_code=999, msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Couldn't retrieve content: Response code was 200 (expected 999)", str(e)) + + try: + self.assertNotContains(response, 'once') + except AssertionError as e: + self.assertIn("Response should not contain 'once'", str(e)) + try: + self.assertNotContains(response, 'once', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Response should not contain 'once'", str(e)) + + try: + self.assertContains(response, 'never', 1) + except AssertionError as e: + self.assertIn("Found 0 instances of 'never' in response (expected 1)", str(e)) + try: + self.assertContains(response, 'never', 1, msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Found 0 instances of 'never' in response (expected 1)", str(e)) + + try: + self.assertContains(response, 'once', 0) + except AssertionError as e: + self.assertIn("Found 1 instances of 'once' in response (expected 0)", str(e)) + try: + self.assertContains(response, 'once', 0, msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Found 1 instances of 'once' in response (expected 0)", str(e)) + + try: + self.assertContains(response, 'once', 2) + except AssertionError as e: + self.assertIn("Found 1 instances of 'once' in response (expected 2)", str(e)) + try: + self.assertContains(response, 'once', 2, msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Found 1 instances of 'once' in response (expected 2)", str(e)) + + try: + self.assertContains(response, 'twice', 1) + except AssertionError as e: + self.assertIn("Found 2 instances of 'twice' in response (expected 1)", str(e)) + try: + self.assertContains(response, 'twice', 1, msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Found 2 instances of 'twice' in response (expected 1)", str(e)) + + try: + self.assertContains(response, 'thrice') + except AssertionError as e: + self.assertIn("Couldn't find 'thrice' in response", str(e)) + try: + self.assertContains(response, 'thrice', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Couldn't find 'thrice' in response", str(e)) + + try: + self.assertContains(response, 'thrice', 3) + except AssertionError as e: + self.assertIn("Found 0 instances of 'thrice' in response (expected 3)", str(e)) + try: + self.assertContains(response, 'thrice', 3, msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Found 0 instances of 'thrice' in response (expected 3)", str(e)) + + def test_unicode_contains(self): + "Unicode characters can be found in template context" + #Regression test for #10183 + r = self.client.get('/test_client_regress/check_unicode/') + self.assertContains(r, u'さかき') + self.assertContains(r, b'\xe5\xb3\xa0'.decode('utf-8')) + + def test_unicode_not_contains(self): + "Unicode characters can be searched for, and not found in template context" + #Regression test for #10183 + r = self.client.get('/test_client_regress/check_unicode/') + self.assertNotContains(r, u'はたけ') + self.assertNotContains(r, b'\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode('utf-8')) + + def test_assert_contains_renders_template_response(self): + """ Test that we can pass in an unrendered SimpleTemplateReponse + without throwing an error. + Refs #15826. + """ + response = SimpleTemplateResponse(Template('Hello'), status=200) + self.assertContains(response, 'Hello') + + def test_assert_contains_using_non_template_response(self): + """ Test that auto-rendering does not affect responses that aren't + instances (or subclasses) of SimpleTemplateResponse. + Refs #15826. + """ + response = HttpResponse('Hello') + self.assertContains(response, 'Hello') + + def test_assert_not_contains_renders_template_response(self): + """ Test that we can pass in an unrendered SimpleTemplateReponse + without throwing an error. + Refs #15826. + """ + response = SimpleTemplateResponse(Template('Hello'), status=200) + self.assertNotContains(response, 'Bye') + + def test_assert_not_contains_using_non_template_response(self): + """ Test that auto-rendering does not affect responses that aren't + instances (or subclasses) of SimpleTemplateResponse. + Refs #15826. + """ + response = HttpResponse('Hello') + self.assertNotContains(response, 'Bye') + +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class AssertTemplateUsedTests(TestCase): + fixtures = ['testdata.json'] + + def test_no_context(self): + "Template usage assertions work then templates aren't in use" + response = self.client.get('/test_client_regress/no_template_view/') + + # Check that the no template case doesn't mess with the template assertions + self.assertTemplateNotUsed(response, 'GET Template') + + try: + self.assertTemplateUsed(response, 'GET Template') + except AssertionError as e: + self.assertIn("No templates used to render the response", str(e)) + + try: + self.assertTemplateUsed(response, 'GET Template', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: No templates used to render the response", str(e)) + + def test_single_context(self): + "Template assertions work when there is a single context" + response = self.client.get('/test_client/post_view/', {}) + + try: + self.assertTemplateNotUsed(response, 'Empty GET Template') + except AssertionError as e: + self.assertIn("Template 'Empty GET Template' was used unexpectedly in rendering the response", str(e)) + + try: + self.assertTemplateNotUsed(response, 'Empty GET Template', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Template 'Empty GET Template' was used unexpectedly in rendering the response", str(e)) + + try: + self.assertTemplateUsed(response, 'Empty POST Template') + except AssertionError as e: + self.assertIn("Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template", str(e)) + + try: + self.assertTemplateUsed(response, 'Empty POST Template', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template", str(e)) + + def test_multiple_context(self): + "Template assertions work when there are multiple contexts" + post_data = { + 'text': 'Hello World', + 'email': 'foo@example.com', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data OK') + try: + self.assertTemplateNotUsed(response, "form_view.html") + except AssertionError as e: + self.assertIn("Template 'form_view.html' was used unexpectedly in rendering the response", str(e)) + + try: + self.assertTemplateNotUsed(response, 'base.html') + except AssertionError as e: + self.assertIn("Template 'base.html' was used unexpectedly in rendering the response", str(e)) + + try: + self.assertTemplateUsed(response, "Valid POST Template") + except AssertionError as e: + self.assertIn("Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html", str(e)) + +class AssertRedirectsTests(TestCase): + def test_redirect_page(self): + "An assertion is raised if the original page couldn't be retrieved as expected" + # This page will redirect with code 301, not 302 + response = self.client.get('/test_client/permanent_redirect_view/') + try: + self.assertRedirects(response, '/test_client/get_view/') + except AssertionError as e: + self.assertIn("Response didn't redirect as expected: Response code was 301 (expected 302)", str(e)) + + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Response didn't redirect as expected: Response code was 301 (expected 302)", str(e)) + + def test_lost_query(self): + "An assertion is raised if the redirect location doesn't preserve GET parameters" + response = self.client.get('/test_client/redirect_view/', {'var': 'value'}) + try: + self.assertRedirects(response, '/test_client/get_view/') + except AssertionError as e: + self.assertIn("Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'", str(e)) + + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'", str(e)) + + def test_incorrect_target(self): + "An assertion is raised if the response redirects to another target" + response = self.client.get('/test_client/permanent_redirect_view/') + try: + # Should redirect to get_view + self.assertRedirects(response, '/test_client/some_view/') + except AssertionError as e: + self.assertIn("Response didn't redirect as expected: Response code was 301 (expected 302)", str(e)) + + def test_target_page(self): + "An assertion is raised if the response redirect target cannot be retrieved as expected" + response = self.client.get('/test_client/double_redirect_view/') + try: + # The redirect target responds with a 301 code, not 200 + self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/') + except AssertionError as e: + self.assertIn("Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)", str(e)) + + try: + # The redirect target responds with a 301 code, not 200 + self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)", str(e)) + + def test_redirect_chain(self): + "You can follow a redirect chain of multiple redirects" + response = self.client.get('/test_client_regress/redirects/further/more/', {}, follow=True) + self.assertRedirects(response, '/test_client_regress/no_template_view/', + status_code=301, target_status_code=200) + + self.assertEqual(len(response.redirect_chain), 1) + self.assertEqual(response.redirect_chain[0], ('http://testserver/test_client_regress/no_template_view/', 301)) + + def test_multiple_redirect_chain(self): + "You can follow a redirect chain of multiple redirects" + response = self.client.get('/test_client_regress/redirects/', {}, follow=True) + self.assertRedirects(response, '/test_client_regress/no_template_view/', + status_code=301, target_status_code=200) + + self.assertEqual(len(response.redirect_chain), 3) + self.assertEqual(response.redirect_chain[0], ('http://testserver/test_client_regress/redirects/further/', 301)) + self.assertEqual(response.redirect_chain[1], ('http://testserver/test_client_regress/redirects/further/more/', 301)) + self.assertEqual(response.redirect_chain[2], ('http://testserver/test_client_regress/no_template_view/', 301)) + + def test_redirect_chain_to_non_existent(self): + "You can follow a chain to a non-existent view" + response = self.client.get('/test_client_regress/redirect_to_non_existent_view2/', {}, follow=True) + self.assertRedirects(response, '/test_client_regress/non_existent_view/', + status_code=301, target_status_code=404) + + def test_redirect_chain_to_self(self): + "Redirections to self are caught and escaped" + response = self.client.get('/test_client_regress/redirect_to_self/', {}, follow=True) + # The chain of redirects stops once the cycle is detected. + self.assertRedirects(response, '/test_client_regress/redirect_to_self/', + status_code=301, target_status_code=301) + self.assertEqual(len(response.redirect_chain), 2) + + def test_circular_redirect(self): + "Circular redirect chains are caught and escaped" + response = self.client.get('/test_client_regress/circular_redirect_1/', {}, follow=True) + # The chain of redirects will get back to the starting point, but stop there. + self.assertRedirects(response, '/test_client_regress/circular_redirect_2/', + status_code=301, target_status_code=301) + self.assertEqual(len(response.redirect_chain), 4) + + def test_redirect_chain_post(self): + "A redirect chain will be followed from an initial POST post" + response = self.client.post('/test_client_regress/redirects/', + {'nothing': 'to_send'}, follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEqual(len(response.redirect_chain), 3) + + def test_redirect_chain_head(self): + "A redirect chain will be followed from an initial HEAD request" + response = self.client.head('/test_client_regress/redirects/', + {'nothing': 'to_send'}, follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEqual(len(response.redirect_chain), 3) + + def test_redirect_chain_options(self): + "A redirect chain will be followed from an initial OPTIONS request" + response = self.client.options('/test_client_regress/redirects/', + follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEqual(len(response.redirect_chain), 3) + + def test_redirect_chain_put(self): + "A redirect chain will be followed from an initial PUT request" + response = self.client.put('/test_client_regress/redirects/', + follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEqual(len(response.redirect_chain), 3) + + def test_redirect_chain_delete(self): + "A redirect chain will be followed from an initial DELETE request" + response = self.client.delete('/test_client_regress/redirects/', + follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEqual(len(response.redirect_chain), 3) + + def test_redirect_to_different_host(self): + "The test client will preserve scheme, host and port changes" + response = self.client.get('/test_client_regress/redirect_other_host/', follow=True) + self.assertRedirects(response, + 'https://otherserver:8443/test_client_regress/no_template_view/', + status_code=301, target_status_code=200) + # We can't use is_secure() or get_host() + # because response.request is a dictionary, not an HttpRequest + self.assertEqual(response.request.get('wsgi.url_scheme'), 'https') + self.assertEqual(response.request.get('SERVER_NAME'), 'otherserver') + self.assertEqual(response.request.get('SERVER_PORT'), '8443') + + def test_redirect_chain_on_non_redirect_page(self): + "An assertion is raised if the original page couldn't be retrieved as expected" + # This page will redirect with code 301, not 302 + response = self.client.get('/test_client/get_view/', follow=True) + try: + self.assertRedirects(response, '/test_client/get_view/') + except AssertionError as e: + self.assertIn("Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) + + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) + + def test_redirect_on_non_redirect_page(self): + "An assertion is raised if the original page couldn't be retrieved as expected" + # This page will redirect with code 301, not 302 + response = self.client.get('/test_client/get_view/') + try: + self.assertRedirects(response, '/test_client/get_view/') + except AssertionError as e: + self.assertIn("Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) + + try: + self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: Response didn't redirect as expected: Response code was 200 (expected 302)", str(e)) + + +class AssertFormErrorTests(TestCase): + def test_unknown_form(self): + "An assertion is raised if the form name is unknown" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.') + except AssertionError as e: + self.assertIn("The form 'wrong_form' was not used to render the response", str(e)) + try: + self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: The form 'wrong_form' was not used to render the response", str(e)) + + def test_unknown_field(self): + "An assertion is raised if the field name is unknown" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'form', 'some_field', 'Some error.') + except AssertionError as e: + self.assertIn("The form 'form' in context 0 does not contain the field 'some_field'", str(e)) + try: + self.assertFormError(response, 'form', 'some_field', 'Some error.', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: The form 'form' in context 0 does not contain the field 'some_field'", str(e)) + + def test_noerror_field(self): + "An assertion is raised if the field doesn't have any errors" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'form', 'value', 'Some error.') + except AssertionError as e: + self.assertIn("The field 'value' on form 'form' in context 0 contains no errors", str(e)) + try: + self.assertFormError(response, 'form', 'value', 'Some error.', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: The field 'value' on form 'form' in context 0 contains no errors", str(e)) + + def test_unknown_error(self): + "An assertion is raised if the field doesn't contain the provided error" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'form', 'email', 'Some error.') + except AssertionError as e: + self.assertIn("The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])", str(e)) + try: + self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])", str(e)) + + def test_unknown_nonfield_error(self): + """ + Checks that an assertion is raised if the form's non field errors + doesn't contain the provided error. + """ + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'form', None, 'Some error.') + except AssertionError as e: + self.assertIn("The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e)) + try: + self.assertFormError(response, 'form', None, 'Some error.', msg_prefix='abc') + except AssertionError as e: + self.assertIn("abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e)) + +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class LoginTests(TestCase): + fixtures = ['testdata'] + + def test_login_different_client(self): + "Check that using a different test client doesn't violate authentication" + + # Create a second client, and log in. + c = Client() + login = c.login(username='testclient', password='password') + self.assertTrue(login, 'Could not log in') + + # Get a redirection page with the second client. + response = c.get("/test_client_regress/login_protected_redirect_view/") + + # At this points, the self.client isn't logged in. + # Check that assertRedirects uses the original client, not the + # default client. + self.assertRedirects(response, "http://testserver/test_client_regress/get_view/") + + +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class SessionEngineTests(TestCase): + fixtures = ['testdata'] + + def setUp(self): + self.old_SESSION_ENGINE = settings.SESSION_ENGINE + settings.SESSION_ENGINE = 'regressiontests.test_client_regress.session' + + def tearDown(self): + settings.SESSION_ENGINE = self.old_SESSION_ENGINE + + def test_login(self): + "A session engine that modifies the session key can be used to log in" + login = self.client.login(username='testclient', password='password') + self.assertTrue(login, 'Could not log in') + + # Try to access a login protected page. + response = self.client.get("/test_client/login_protected_view/") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['user'].username, 'testclient') + + +class URLEscapingTests(TestCase): + def test_simple_argument_get(self): + "Get a view that has a simple string argument" + response = self.client.get(reverse('arg_view', args=['Slartibartfast'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'Howdy, Slartibartfast') + + def test_argument_with_space_get(self): + "Get a view that has a string argument that requires escaping" + response = self.client.get(reverse('arg_view', args=['Arthur Dent'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'Hi, Arthur') + + def test_simple_argument_post(self): + "Post for a view that has a simple string argument" + response = self.client.post(reverse('arg_view', args=['Slartibartfast'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'Howdy, Slartibartfast') + + def test_argument_with_space_post(self): + "Post for a view that has a string argument that requires escaping" + response = self.client.post(reverse('arg_view', args=['Arthur Dent'])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'Hi, Arthur') + +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class ExceptionTests(TestCase): + fixtures = ['testdata.json'] + + def test_exception_cleared(self): + "#5836 - A stale user exception isn't re-raised by the test client." + + login = self.client.login(username='testclient',password='password') + self.assertTrue(login, 'Could not log in') + try: + response = self.client.get("/test_client_regress/staff_only/") + self.fail("General users should not be able to visit this page") + except SuspiciousOperation: + pass + + # At this point, an exception has been raised, and should be cleared. + + # This next operation should be successful; if it isn't we have a problem. + login = self.client.login(username='staff', password='password') + self.assertTrue(login, 'Could not log in') + try: + self.client.get("/test_client_regress/staff_only/") + except SuspiciousOperation: + self.fail("Staff should be able to visit this page") + +class TemplateExceptionTests(TestCase): + def setUp(self): + # Reset the loaders so they don't try to render cached templates. + if loader.template_source_loaders is not None: + for template_loader in loader.template_source_loaders: + if hasattr(template_loader, 'reset'): + template_loader.reset() + self.old_templates = settings.TEMPLATE_DIRS + settings.TEMPLATE_DIRS = () + + def tearDown(self): + settings.TEMPLATE_DIRS = self.old_templates + + def test_no_404_template(self): + "Missing templates are correctly reported by test client" + try: + response = self.client.get("/no_such_view/") + self.fail("Should get error about missing template") + except TemplateDoesNotExist: + pass + + def test_bad_404_template(self): + "Errors found when rendering 404 error templates are re-raised" + settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'bad_templates'),) + try: + response = self.client.get("/no_such_view/") + self.fail("Should get error about syntax error in template") + except TemplateSyntaxError: + pass + +# We need two different tests to check URLconf substitution - one to check +# it was changed, and another one (without self.urls) to check it was reverted on +# teardown. This pair of tests relies upon the alphabetical ordering of test execution. +class UrlconfSubstitutionTests(TestCase): + urls = 'regressiontests.test_client_regress.urls' + + def test_urlconf_was_changed(self): + "TestCase can enforce a custom URLconf on a per-test basis" + url = reverse('arg_view', args=['somename']) + self.assertEqual(url, '/arg_view/somename/') + +# This test needs to run *after* UrlconfSubstitutionTests; the zz prefix in the +# name is to ensure alphabetical ordering. +class zzUrlconfSubstitutionTests(TestCase): + def test_urlconf_was_reverted(self): + "URLconf is reverted to original value after modification in a TestCase" + url = reverse('arg_view', args=['somename']) + self.assertEqual(url, '/test_client_regress/arg_view/somename/') + +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class ContextTests(TestCase): + fixtures = ['testdata'] + + def test_single_context(self): + "Context variables can be retrieved from a single context" + response = self.client.get("/test_client_regress/request_data/", data={'foo':'whiz'}) + self.assertEqual(response.context.__class__, Context) + self.assertTrue('get-foo' in response.context) + self.assertEqual(response.context['get-foo'], 'whiz') + self.assertEqual(response.context['request-foo'], 'whiz') + self.assertEqual(response.context['data'], 'sausage') + + try: + response.context['does-not-exist'] + self.fail('Should not be able to retrieve non-existent key') + except KeyError as e: + self.assertEqual(e.args[0], 'does-not-exist') + + def test_inherited_context(self): + "Context variables can be retrieved from a list of contexts" + response = self.client.get("/test_client_regress/request_data_extended/", data={'foo':'whiz'}) + self.assertEqual(response.context.__class__, ContextList) + self.assertEqual(len(response.context), 2) + self.assertTrue('get-foo' in response.context) + self.assertEqual(response.context['get-foo'], 'whiz') + self.assertEqual(response.context['request-foo'], 'whiz') + self.assertEqual(response.context['data'], 'bacon') + + try: + response.context['does-not-exist'] + self.fail('Should not be able to retrieve non-existent key') + except KeyError as e: + self.assertEqual(e.args[0], 'does-not-exist') + + def test_15368(self): + # Need to insert a context processor that assumes certain things about + # the request instance. This triggers a bug caused by some ways of + # copying RequestContext. + try: + django.template.context._standard_context_processors = (lambda request: {'path': request.special_path},) + response = self.client.get("/test_client_regress/request_context_view/") + self.assertContains(response, 'Path: /test_client_regress/request_context_view/') + finally: + django.template.context._standard_context_processors = None + + +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class SessionTests(TestCase): + fixtures = ['testdata.json'] + + def test_session(self): + "The session isn't lost if a user logs in" + # The session doesn't exist to start. + response = self.client.get('/test_client_regress/check_session/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'NO') + + # This request sets a session variable. + response = self.client.get('/test_client_regress/set_session/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'set_session') + + # Check that the session has been modified + response = self.client.get('/test_client_regress/check_session/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'YES') + + # Log in + login = self.client.login(username='testclient',password='password') + self.assertTrue(login, 'Could not log in') + + # Session should still contain the modified value + response = self.client.get('/test_client_regress/check_session/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'YES') + + def test_logout(self): + """Logout should work whether the user is logged in or not (#9978).""" + self.client.logout() + login = self.client.login(username='testclient',password='password') + self.assertTrue(login, 'Could not log in') + self.client.logout() + self.client.logout() + +class RequestMethodTests(TestCase): + def test_get(self): + "Request a view via request method GET" + response = self.client.get('/test_client_regress/request_methods/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: GET') + + def test_post(self): + "Request a view via request method POST" + response = self.client.post('/test_client_regress/request_methods/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: POST') + + def test_head(self): + "Request a view via request method HEAD" + response = self.client.head('/test_client_regress/request_methods/') + self.assertEqual(response.status_code, 200) + # A HEAD request doesn't return any content. + self.assertNotEqual(response.content, b'request method: HEAD') + self.assertEqual(response.content, b'') + + def test_options(self): + "Request a view via request method OPTIONS" + response = self.client.options('/test_client_regress/request_methods/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: OPTIONS') + + def test_put(self): + "Request a view via request method PUT" + response = self.client.put('/test_client_regress/request_methods/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: PUT') + + def test_delete(self): + "Request a view via request method DELETE" + response = self.client.delete('/test_client_regress/request_methods/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: DELETE') + +class RequestMethodStringDataTests(TestCase): + def test_post(self): + "Request a view with string data via request method POST" + # Regression test for #11371 + data = u'{"test": "json"}' + response = self.client.post('/test_client_regress/request_methods/', data=data, content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: POST') + + def test_put(self): + "Request a view with string data via request method PUT" + # Regression test for #11371 + data = u'{"test": "json"}' + response = self.client.put('/test_client_regress/request_methods/', data=data, content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: PUT') + +class QueryStringTests(TestCase): + def test_get_like_requests(self): + # See: https://code.djangoproject.com/ticket/10571. + for method_name in ('get', 'head'): + # 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'}) + self.assertEqual(response.context['get-foo'], 'whiz') + self.assertEqual(response.context['request-foo'], 'whiz') + + # A GET-like request can pass a query string as part of the URL + response = method("/test_client_regress/request_data/?foo=whiz") + self.assertEqual(response.context['get-foo'], 'whiz') + self.assertEqual(response.context['request-foo'], 'whiz') + + # Data provided in the URL to a GET-like request is overridden by actual form data + response = method("/test_client_regress/request_data/?foo=whiz", data={'foo':'bang'}) + self.assertEqual(response.context['get-foo'], 'bang') + self.assertEqual(response.context['request-foo'], 'bang') + + response = method("/test_client_regress/request_data/?foo=whiz", data={'bar':'bang'}) + self.assertEqual(response.context['get-foo'], None) + self.assertEqual(response.context['get-bar'], 'bang') + self.assertEqual(response.context['request-foo'], None) + self.assertEqual(response.context['request-bar'], 'bang') + + def test_post_like_requests(self): + # A POST-like request can pass a query string as data + response = self.client.post("/test_client_regress/request_data/", data={'foo':'whiz'}) + self.assertEqual(response.context['get-foo'], None) + self.assertEqual(response.context['post-foo'], 'whiz') + + # A POST-like request can pass a query string as part of the URL + response = self.client.post("/test_client_regress/request_data/?foo=whiz") + self.assertEqual(response.context['get-foo'], 'whiz') + self.assertEqual(response.context['post-foo'], None) + self.assertEqual(response.context['request-foo'], 'whiz') + + # POST data provided in the URL augments actual form data + response = self.client.post("/test_client_regress/request_data/?foo=whiz", data={'foo':'bang'}) + self.assertEqual(response.context['get-foo'], 'whiz') + self.assertEqual(response.context['post-foo'], 'bang') + self.assertEqual(response.context['request-foo'], 'bang') + + response = self.client.post("/test_client_regress/request_data/?foo=whiz", data={'bar':'bang'}) + self.assertEqual(response.context['get-foo'], 'whiz') + self.assertEqual(response.context['get-bar'], None) + self.assertEqual(response.context['post-foo'], None) + self.assertEqual(response.context['post-bar'], 'bang') + self.assertEqual(response.context['request-foo'], 'whiz') + self.assertEqual(response.context['request-bar'], 'bang') + +class UnicodePayloadTests(TestCase): + def test_simple_unicode_payload(self): + "A simple ASCII-only unicode JSON document can be POSTed" + # Regression test for #10571 + json = u'{"english": "mountain pass"}' + response = self.client.post("/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" + # Regression test for #10571 + json = u'{"dog": "собака"}' + 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')) + + def test_unicode_payload_utf16(self): + "A non-ASCII unicode data encoded as UTF-16 can be POSTed" + # Regression test for #10571 + json = u'{"dog": "собака"}' + 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')) + + def test_unicode_payload_non_utf(self): + "A non-ASCII unicode data as a non-UTF based encoding can be POSTed" + #Regression test for #10571 + json = u'{"dog": "собака"}' + 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')) + +class DummyFile(object): + def __init__(self, filename): + self.name = filename + def read(self): + return 'TEST_FILE_CONTENT' + +class UploadedFileEncodingTest(TestCase): + def test_file_encoding(self): + encoded_file = encode_file('TEST_BOUNDARY', 'TEST_KEY', DummyFile('test_name.bin')) + self.assertEqual(b'--TEST_BOUNDARY', encoded_file[0]) + self.assertEqual(b'Content-Disposition: form-data; name="TEST_KEY"; filename="test_name.bin"', encoded_file[1]) + self.assertEqual(b'TEST_FILE_CONTENT', encoded_file[-1]) + + def test_guesses_content_type_on_file_encoding(self): + self.assertEqual(b'Content-Type: application/octet-stream', + encode_file('IGNORE', 'IGNORE', DummyFile("file.bin"))[2]) + self.assertEqual(b'Content-Type: text/plain', + encode_file('IGNORE', 'IGNORE', DummyFile("file.txt"))[2]) + self.assertIn(encode_file('IGNORE', 'IGNORE', DummyFile("file.zip"))[2], ( + b'Content-Type: application/x-compress', + b'Content-Type: application/x-zip', + b'Content-Type: application/x-zip-compressed', + b'Content-Type: application/zip',)) + self.assertEqual(b'Content-Type: application/octet-stream', + encode_file('IGNORE', 'IGNORE', DummyFile("file.unknown"))[2]) + +class RequestHeadersTest(TestCase): + def test_client_headers(self): + "A test client can receive custom headers" + response = self.client.get("/test_client_regress/check_headers/", HTTP_X_ARG_CHECK='Testing 123') + self.assertEqual(response.content, b"HTTP_X_ARG_CHECK: Testing 123") + self.assertEqual(response.status_code, 200) + + def test_client_headers_redirect(self): + "Test client headers are preserved through redirects" + response = self.client.get("/test_client_regress/check_headers_redirect/", follow=True, HTTP_X_ARG_CHECK='Testing 123') + self.assertEqual(response.content, b"HTTP_X_ARG_CHECK: Testing 123") + self.assertRedirects(response, '/test_client_regress/check_headers/', + status_code=301, target_status_code=200) + + +class ReadLimitedStreamTest(TestCase): + """ + Tests that ensure that HttpRequest.body, HttpRequest.read() and + HttpRequest.read(BUFFER) have proper LimitedStream behavior. + + Refs #14753, #15785 + """ + + def test_body_from_empty_request(self): + """HttpRequest.body on a test client GET request should return + the empty string.""" + self.assertEqual(self.client.get("/test_client_regress/body/").content, b'') + + def test_read_from_empty_request(self): + """HttpRequest.read() on a test client GET request should return the + empty string.""" + self.assertEqual(self.client.get("/test_client_regress/read_all/").content, b'') + + def test_read_numbytes_from_empty_request(self): + """HttpRequest.read(LARGE_BUFFER) on a test client GET request should + return the empty string.""" + self.assertEqual(self.client.get("/test_client_regress/read_buffer/").content, b'') + + def test_read_from_nonempty_request(self): + """HttpRequest.read() on a test client PUT request with some payload + should return that payload.""" + payload = b'foobar' + self.assertEqual(self.client.put("/test_client_regress/read_all/", + data=payload, + content_type='text/plain').content, payload) + + def test_read_numbytes_from_nonempty_request(self): + """HttpRequest.read(LARGE_BUFFER) on a test client PUT request with + some payload should return that payload.""" + payload = b'foobar' + self.assertEqual(self.client.put("/test_client_regress/read_buffer/", + data=payload, + content_type='text/plain').content, payload) + + +class RequestFactoryStateTest(TestCase): + """Regression tests for #15929.""" + # These tests are checking that certain middleware don't change certain + # global state. Alternatively, from the point of view of a test, they are + # ensuring test isolation behavior. So, unusually, it doesn't make sense to + # run the tests individually, and if any are failing it is confusing to run + # them with any other set of tests. + + def setUp(self): + self.factory = RequestFactory() + + def common_test_that_should_always_pass(self): + request = self.factory.get('/') + request.session = {} + self.assertFalse(hasattr(request, 'user')) + + def test_request(self): + self.common_test_that_should_always_pass() + + def test_request_after_client(self): + # apart from the next line the three tests are identical + self.client.get('/') + self.common_test_that_should_always_pass() + + def test_request_after_client_2(self): + # This test is executed after the previous one + self.common_test_that_should_always_pass() + + +class RequestFactoryEnvironmentTests(TestCase): + """ + Regression tests for #8551 and #17067: ensure that environment variables + are set correctly in RequestFactory. + """ + + def setUp(self): + self.factory = RequestFactory() + + def test_should_set_correct_env_variables(self): + request = self.factory.get('/path/') + + self.assertEqual(request.META.get('REMOTE_ADDR'), '127.0.0.1') + self.assertEqual(request.META.get('SERVER_NAME'), 'testserver') + self.assertEqual(request.META.get('SERVER_PORT'), '80') + self.assertEqual(request.META.get('SERVER_PROTOCOL'), 'HTTP/1.1') + self.assertEqual(request.META.get('SCRIPT_NAME') + + request.META.get('PATH_INFO'), '/path/') From ea4e0aad9efd691aa200bf7bbabd282267234b3a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 31 May 2012 17:41:31 +0200 Subject: [PATCH 77/95] Cleaned up test_client_regress tests --- .../test_client_regress/tests.py | 39 ++++++------------- .../test_client_regress/views.py | 5 +-- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/tests/regressiontests/test_client_regress/tests.py b/tests/regressiontests/test_client_regress/tests.py index ca55bd156b..293c0ff557 100644 --- a/tests/regressiontests/test_client_regress/tests.py +++ b/tests/regressiontests/test_client_regress/tests.py @@ -17,14 +17,10 @@ from django.template.response import SimpleTemplateResponse from django.http import HttpResponse +@override_settings( + TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),) +) class AssertContainsTests(TestCase): - def setUp(self): - self.old_templates = settings.TEMPLATE_DIRS - settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),) - - def tearDown(self): - settings.TEMPLATE_DIRS = self.old_templates - def test_contains(self): "Responses can be inspected for content, including counting repeated substrings" response = self.client.get('/test_client_regress/no_template_view/') @@ -544,17 +540,13 @@ class LoginTests(TestCase): self.assertRedirects(response, "http://testserver/test_client_regress/get_view/") -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +@override_settings( + PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',), + SESSION_ENGINE='regressiontests.test_client_regress.session' +) class SessionEngineTests(TestCase): fixtures = ['testdata'] - def setUp(self): - self.old_SESSION_ENGINE = settings.SESSION_ENGINE - settings.SESSION_ENGINE = 'regressiontests.test_client_regress.session' - - def tearDown(self): - settings.SESSION_ENGINE = self.old_SESSION_ENGINE - def test_login(self): "A session engine that modifies the session key can be used to log in" login = self.client.login(username='testclient', password='password') @@ -616,6 +608,8 @@ class ExceptionTests(TestCase): except SuspiciousOperation: self.fail("Staff should be able to visit this page") + +@override_settings(TEMPLATE_DIRS=()) class TemplateExceptionTests(TestCase): def setUp(self): # Reset the loaders so they don't try to render cached templates. @@ -623,11 +617,6 @@ class TemplateExceptionTests(TestCase): for template_loader in loader.template_source_loaders: if hasattr(template_loader, 'reset'): template_loader.reset() - self.old_templates = settings.TEMPLATE_DIRS - settings.TEMPLATE_DIRS = () - - def tearDown(self): - settings.TEMPLATE_DIRS = self.old_templates def test_no_404_template(self): "Missing templates are correctly reported by test client" @@ -980,11 +969,8 @@ class RequestFactoryStateTest(TestCase): # run the tests individually, and if any are failing it is confusing to run # them with any other set of tests. - def setUp(self): - self.factory = RequestFactory() - def common_test_that_should_always_pass(self): - request = self.factory.get('/') + request = RequestFactory().get('/') request.session = {} self.assertFalse(hasattr(request, 'user')) @@ -1007,11 +993,8 @@ class RequestFactoryEnvironmentTests(TestCase): are set correctly in RequestFactory. """ - def setUp(self): - self.factory = RequestFactory() - def test_should_set_correct_env_variables(self): - request = self.factory.get('/path/') + request = RequestFactory().get('/path/') self.assertEqual(request.META.get('REMOTE_ADDR'), '127.0.0.1') self.assertEqual(request.META.get('SERVER_NAME'), 'testserver') diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index ab625a90dc..3a934ea047 100644 --- a/tests/regressiontests/test_client_regress/views.py +++ b/tests/regressiontests/test_client_regress/views.py @@ -5,7 +5,6 @@ from django.contrib.auth.decorators import login_required from django.http import HttpResponse, HttpResponseRedirect from django.core.exceptions import SuspiciousOperation from django.shortcuts import render_to_response -from django.utils.encoding import smart_str from django.core.serializers.json import DjangoJSONEncoder from django.test.client import CONTENT_TYPE_RE from django.template import RequestContext @@ -84,8 +83,8 @@ def return_json_file(request): obj_json = json.dumps(obj_dict, encoding=charset, cls=DjangoJSONEncoder, ensure_ascii=False) - response = HttpResponse(smart_str(obj_json, encoding=charset), status=200, - mimetype='application/json; charset=' + charset) + response = HttpResponse(obj_json.encode(charset), status=200, + mimetype='application/json; charset=%s' % charset) response['Content-Disposition'] = 'attachment; filename=testfile.json' return response From ba10be70322027074e5f9defc1423eb0cc77473c Mon Sep 17 00:00:00 2001 From: Jens Page Date: Thu, 10 May 2012 17:09:29 -0400 Subject: [PATCH 78/95] Fixed #18408 -- Isolated flatpages tests from existing sites. Resolves Flatpages test issues by: - Creating an example_site fixture - Overriding project SITE_ID setting to 1 - Normalizing the use of the hardcoded (1) site_id to settings.SITE_ID --- AUTHORS | 1 + django/contrib/flatpages/fixtures/example_site.json | 11 +++++++++++ django/contrib/flatpages/tests/csrf.py | 3 ++- django/contrib/flatpages/tests/forms.py | 5 ++++- django/contrib/flatpages/tests/middleware.py | 12 +++++++----- django/contrib/flatpages/tests/templatetags.py | 1 + django/contrib/flatpages/tests/views.py | 8 +++++--- 7 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 django/contrib/flatpages/fixtures/example_site.json diff --git a/AUTHORS b/AUTHORS index 0e29608f75..45a0544e65 100644 --- a/AUTHORS +++ b/AUTHORS @@ -401,6 +401,7 @@ answer newbie questions, and generally made Django that much better: Christian Oudard oggie rob oggy + Jens Page Jay Parlar Carlos Eduardo de Paula John Paulett diff --git a/django/contrib/flatpages/fixtures/example_site.json b/django/contrib/flatpages/fixtures/example_site.json new file mode 100644 index 0000000000..71aa84de12 --- /dev/null +++ b/django/contrib/flatpages/fixtures/example_site.json @@ -0,0 +1,11 @@ +[ + { + "pk": 1, + "model": "sites.site", + "fields": { + "domain": "example.com", + "name": "example.com" + } + } +] + diff --git a/django/contrib/flatpages/tests/csrf.py b/django/contrib/flatpages/tests/csrf.py index a8b9c2804c..e64c4bb102 100644 --- a/django/contrib/flatpages/tests/csrf.py +++ b/django/contrib/flatpages/tests/csrf.py @@ -18,9 +18,10 @@ from django.test.utils import override_settings TEMPLATE_DIRS=( os.path.join(os.path.dirname(__file__), 'templates'), ), + SITE_ID=1, ) class FlatpageCSRFTests(TestCase): - fixtures = ['sample_flatpages'] + fixtures = ['sample_flatpages', 'example_site'] urls = 'django.contrib.flatpages.tests.urls' def setUp(self): diff --git a/django/contrib/flatpages/tests/forms.py b/django/contrib/flatpages/tests/forms.py index eb37e9ec29..0e54176aa2 100644 --- a/django/contrib/flatpages/tests/forms.py +++ b/django/contrib/flatpages/tests/forms.py @@ -5,7 +5,10 @@ from django.test import TestCase from django.test.utils import override_settings from django.utils import translation +@override_settings(SITE_ID=1) class FlatpageAdminFormTests(TestCase): + fixtures = ['example_site'] + def setUp(self): self.form_data = { 'title': "A test page", @@ -89,5 +92,5 @@ class FlatpageAdminFormTests(TestCase): self.assertEqual( f.errors, - {'sites': [u'This field is required.']}) + {'sites': [translation.ugettext(u'This field is required.')]}) diff --git a/django/contrib/flatpages/tests/middleware.py b/django/contrib/flatpages/tests/middleware.py index fa9d735c0c..4afa4ff982 100644 --- a/django/contrib/flatpages/tests/middleware.py +++ b/django/contrib/flatpages/tests/middleware.py @@ -19,9 +19,10 @@ from django.test.utils import override_settings TEMPLATE_DIRS=( os.path.join(os.path.dirname(__file__), 'templates'), ), + SITE_ID=1, ) class FlatpageMiddlewareTests(TestCase): - fixtures = ['sample_flatpages'] + fixtures = ['sample_flatpages', 'example_site'] urls = 'django.contrib.flatpages.tests.urls' def test_view_flatpage(self): @@ -75,7 +76,7 @@ class FlatpageMiddlewareTests(TestCase): enable_comments=False, registration_required=False, ) - fp.sites.add(1) + fp.sites.add(settings.SITE_ID) response = self.client.get('/some.very_special~chars-here/') self.assertEqual(response.status_code, 200) @@ -96,9 +97,10 @@ class FlatpageMiddlewareTests(TestCase): TEMPLATE_DIRS=( os.path.join(os.path.dirname(__file__), 'templates'), ), + SITE_ID=1, ) class FlatpageMiddlewareAppendSlashTests(TestCase): - fixtures = ['sample_flatpages'] + fixtures = ['sample_flatpages', 'example_site'] urls = 'django.contrib.flatpages.tests.urls' def test_redirect_view_flatpage(self): @@ -130,7 +132,7 @@ class FlatpageMiddlewareAppendSlashTests(TestCase): enable_comments=False, registration_required=False, ) - fp.sites.add(1) + fp.sites.add(settings.SITE_ID) response = self.client.get('/some.very_special~chars-here') self.assertRedirects(response, '/some.very_special~chars-here/', status_code=301) @@ -144,7 +146,7 @@ class FlatpageMiddlewareAppendSlashTests(TestCase): enable_comments=False, registration_required=False, ) - fp.sites.add(1) + fp.sites.add(settings.SITE_ID) response = self.client.get('/') self.assertEqual(response.status_code, 200) diff --git a/django/contrib/flatpages/tests/templatetags.py b/django/contrib/flatpages/tests/templatetags.py index de6a0a90fb..aebc62277e 100644 --- a/django/contrib/flatpages/tests/templatetags.py +++ b/django/contrib/flatpages/tests/templatetags.py @@ -18,6 +18,7 @@ from django.test.utils import override_settings TEMPLATE_DIRS=( os.path.join(os.path.dirname(__file__), 'templates'), ), + SITE_ID=1, ) class FlatpageTemplateTagTests(TestCase): fixtures = ['sample_flatpages'] diff --git a/django/contrib/flatpages/tests/views.py b/django/contrib/flatpages/tests/views.py index 997bfabec8..b69bd6abb4 100644 --- a/django/contrib/flatpages/tests/views.py +++ b/django/contrib/flatpages/tests/views.py @@ -19,9 +19,10 @@ from django.test.utils import override_settings TEMPLATE_DIRS=( os.path.join(os.path.dirname(__file__), 'templates'), ), + SITE_ID=1, ) class FlatpageViewTests(TestCase): - fixtures = ['sample_flatpages'] + fixtures = ['sample_flatpages', 'example_site'] urls = 'django.contrib.flatpages.tests.urls' def test_view_flatpage(self): @@ -85,9 +86,10 @@ class FlatpageViewTests(TestCase): TEMPLATE_DIRS=( os.path.join(os.path.dirname(__file__), 'templates'), ), + SITE_ID=1, ) class FlatpageViewAppendSlashTests(TestCase): - fixtures = ['sample_flatpages'] + fixtures = ['sample_flatpages', 'example_site'] urls = 'django.contrib.flatpages.tests.urls' def test_redirect_view_flatpage(self): @@ -119,7 +121,7 @@ class FlatpageViewAppendSlashTests(TestCase): enable_comments=False, registration_required=False, ) - fp.sites.add(1) + fp.sites.add(settings.SITE_ID) response = self.client.get('/flatpage_root/some.very_special~chars-here') self.assertRedirects(response, '/flatpage_root/some.very_special~chars-here/', status_code=301) From f6fc83c97514ced9de48ca9ea442a9b166d3e211 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Thu, 31 May 2012 11:45:35 -0700 Subject: [PATCH 79/95] Fixed #18409 -- Made RegexField work with unicode characters. --- django/forms/fields.py | 2 +- tests/regressiontests/forms/tests/fields.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/django/forms/fields.py b/django/forms/fields.py index 53250cc8a9..3811510326 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -446,7 +446,7 @@ class RegexField(CharField): def _set_regex(self, regex): if isinstance(regex, basestring): - regex = re.compile(regex) + regex = re.compile(regex, re.UNICODE) self._regex = regex if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: self.validators.remove(self._regex_validator) diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index d66ee2c084..c4a8a9a3fa 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -478,6 +478,14 @@ class FieldsTests(SimpleTestCase): self.assertRaisesMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '12345678901') self.assertRaisesMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '12345a') + def test_regexfield_6(self): + """ + Ensure that it works with unicode characters. + Refs #. + """ + f = RegexField('^\w+$') + self.assertEqual(u'éèøçÎÎ你好', f.clean(u'éèøçÎÎ你好')) + def test_change_regex_after_init(self): f = RegexField('^[a-z]+$') f.regex = '^\d+$' From 939af5a6548bbbae3838773217aecdee910539e8 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 1 Jun 2012 08:09:58 +0200 Subject: [PATCH 80/95] Fixed a typo in a comment. Refs #17742. --- django/db/models/fields/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 8a317e79c8..d572cce28f 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -678,7 +678,7 @@ class DateField(Field): return value if isinstance(value, datetime.datetime): if settings.USE_TZ and timezone.is_aware(value): - # Convert aware datetimes to the current time zone + # Convert aware datetimes to the default time zone # before casting them to dates (#17742). default_timezone = timezone.get_default_timezone() value = timezone.make_naive(value, default_timezone) From 72130385bfe503bd440e52605c44a10a0480a25b Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Fri, 1 Jun 2012 20:58:53 -0300 Subject: [PATCH 81/95] Made inspectdb tests deal with a smaller generated models.py file. Implemented this by adding a stealth table_name_filter option for the command. --- django/core/management/commands/inspectdb.py | 5 ++++ tests/regressiontests/inspectdb/tests.py | 29 ++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index 10fd9bd463..a524e64f65 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -26,6 +26,8 @@ class Command(NoArgsCommand): def handle_inspection(self, options): connection = connections[options.get('database')] + # 'table_name_filter' is a stealth option + table_name_filter = options.get('table_name_filter') table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') @@ -43,6 +45,9 @@ class Command(NoArgsCommand): yield '' known_models = [] for table_name in connection.introspection.table_names(cursor): + if table_name_filter is not None and callable(table_name_filter): + if not table_name_filter(table_name): + continue yield 'class %s(models.Model):' % table2model(table_name) known_models.append(table2model(table_name)) try: diff --git a/tests/regressiontests/inspectdb/tests.py b/tests/regressiontests/inspectdb/tests.py index 6896bf9126..8d1222c545 100644 --- a/tests/regressiontests/inspectdb/tests.py +++ b/tests/regressiontests/inspectdb/tests.py @@ -6,10 +6,31 @@ from django.test import TestCase, skipUnlessDBFeature class InspectDBTestCase(TestCase): + def test_stealth_table_name_filter_option(self): + out = StringIO() + # Lets limit the introspection to tables created for models of this + # application + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_'), + stdout=out) + error_message = "inspectdb has examined a table that should have been filtered out." + # contrib.contenttypes is one of the apps always installed when running + # the Django test suite, check that one of its tables hasn't been + # inspected + self.assertNotIn("class DjangoContentType(models.Model):", out.getvalue(), msg=error_message) + out.close() + @skipUnlessDBFeature('can_introspect_foreign_keys') def test_attribute_name_not_python_keyword(self): out = StringIO() - call_command('inspectdb', stdout=out) + # Lets limit the introspection to tables created for models of this + # application + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_'), + stdout=out) + f = open('/home/ramiro/models2.py', 'w') + f.write(out.getvalue()) + f.close() error_message = "inspectdb generated an attribute name which is a python keyword" self.assertNotIn("from = models.ForeignKey(InspectdbPeople)", out.getvalue(), msg=error_message) # As InspectdbPeople model is defined after InspectdbMessage, it should be quoted @@ -23,7 +44,11 @@ class InspectDBTestCase(TestCase): def test_digits_column_name_introspection(self): """Introspection of column names consist/start with digits (#16536/#17676)""" out = StringIO() - call_command('inspectdb', stdout=out) + # Lets limit the introspection to tables created for models of this + # application + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_'), + stdout=out) error_message = "inspectdb generated a model field name which is a number" self.assertNotIn(" 123 = models.CharField", out.getvalue(), msg=error_message) self.assertIn("number_123 = models.CharField", out.getvalue()) From d4648a34467bb2e90af4038e3424077afa63ceba Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Fri, 1 Jun 2012 21:50:06 -0300 Subject: [PATCH 82/95] Removed debugging code added in last commit. --- tests/regressiontests/inspectdb/tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/regressiontests/inspectdb/tests.py b/tests/regressiontests/inspectdb/tests.py index 8d1222c545..29435b8375 100644 --- a/tests/regressiontests/inspectdb/tests.py +++ b/tests/regressiontests/inspectdb/tests.py @@ -28,9 +28,6 @@ class InspectDBTestCase(TestCase): call_command('inspectdb', table_name_filter=lambda tn:tn.startswith('inspectdb_'), stdout=out) - f = open('/home/ramiro/models2.py', 'w') - f.write(out.getvalue()) - f.close() error_message = "inspectdb generated an attribute name which is a python keyword" self.assertNotIn("from = models.ForeignKey(InspectdbPeople)", out.getvalue(), msg=error_message) # As InspectdbPeople model is defined after InspectdbMessage, it should be quoted From f65e412abd289f78aff278a782e29eff1749c6e5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 2 Jun 2012 11:35:36 +0200 Subject: [PATCH 83/95] Fixed #18259 -- Specified that ROOT_URLCONF might be needed. When using the template system in standalone mode and if the url template tag is used, ROOT_URLCONF has to be defined. Thanks techtonik for the report. --- docs/ref/templates/api.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index aece572e07..e945e0d4ca 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -790,9 +790,10 @@ templating functions, call :func:`django.conf.settings.configure()` with any settings you wish to specify. You might want to consider setting at least :setting:`TEMPLATE_DIRS` (if you're going to use template loaders), :setting:`DEFAULT_CHARSET` (although the default of ``utf-8`` is probably fine) -and :setting:`TEMPLATE_DEBUG`. All available settings are described in the -:doc:`settings documentation `, and any setting starting with -``TEMPLATE_`` is of obvious interest. +and :setting:`TEMPLATE_DEBUG`. If you plan to use the :ttag:`url` template tag, +you will also need to set the :setting:`ROOT_URLCONF` setting. All available +settings are described in the :doc:`settings documentation `, +and any setting starting with ``TEMPLATE_`` is of obvious interest. .. _topic-template-alternate-language: From ade44b8d402c425ca687f254bbaaf161522e5c04 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 2 Jun 2012 19:44:06 +0200 Subject: [PATCH 84/95] Fixed settings override in mail regression tests self.settings_override from test subclasses were overwriting parent attribute. --- tests/regressiontests/mail/tests.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index 99d52a701c..85e73c197f 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -500,14 +500,14 @@ class FileBackendTests(BaseEmailBackendTests, TestCase): email_backend = 'django.core.mail.backends.filebased.EmailBackend' def setUp(self): + super(FileBackendTests, self).setUp() self.tmp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tmp_dir) - self.settings_override = override_settings(EMAIL_FILE_PATH=self.tmp_dir) - self.settings_override.enable() - super(FileBackendTests, self).setUp() + self._settings_override = override_settings(EMAIL_FILE_PATH=self.tmp_dir) + self._settings_override.enable() def tearDown(self): - self.settings_override.disable() + self._settings_override.disable() super(FileBackendTests, self).tearDown() def flush_mailbox(self): @@ -644,15 +644,15 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase): @classmethod def setUpClass(cls): cls.server = FakeSMTPServer(('127.0.0.1', 0), None) - cls.settings_override = override_settings( + cls._settings_override = override_settings( EMAIL_HOST="127.0.0.1", EMAIL_PORT=cls.server.socket.getsockname()[1]) - cls.settings_override.enable() + cls._settings_override.enable() cls.server.start() @classmethod def tearDownClass(cls): - cls.settings_override.disable() + cls._settings_override.disable() cls.server.stop() def setUp(self): From 566ac30eb92574f698a848739fa5008d5e08a2ee Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 2 Jun 2012 19:50:24 +0200 Subject: [PATCH 85/95] Fixed override_settings usage in test_client_regress --- tests/regressiontests/test_client_regress/tests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/test_client_regress/tests.py b/tests/regressiontests/test_client_regress/tests.py index 293c0ff557..ece487a211 100644 --- a/tests/regressiontests/test_client_regress/tests.py +++ b/tests/regressiontests/test_client_regress/tests.py @@ -609,7 +609,6 @@ class ExceptionTests(TestCase): self.fail("Staff should be able to visit this page") -@override_settings(TEMPLATE_DIRS=()) class TemplateExceptionTests(TestCase): def setUp(self): # Reset the loaders so they don't try to render cached templates. @@ -618,6 +617,7 @@ class TemplateExceptionTests(TestCase): if hasattr(template_loader, 'reset'): template_loader.reset() + @override_settings(TEMPLATE_DIRS=(),) def test_no_404_template(self): "Missing templates are correctly reported by test client" try: @@ -626,9 +626,11 @@ class TemplateExceptionTests(TestCase): except TemplateDoesNotExist: pass + @override_settings( + TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'bad_templates'),) + ) def test_bad_404_template(self): "Errors found when rendering 404 error templates are re-raised" - settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'bad_templates'),) try: response = self.client.get("/no_such_view/") self.fail("Should get error about syntax error in template") From 7676d6e764c47c9d33a755c3b861034b32de77ac Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 2 Jun 2012 21:24:18 +0200 Subject: [PATCH 86/95] Made sitemaps tests use override_settings. Refs #14478 --- django/contrib/sitemaps/tests/base.py | 10 ---------- django/contrib/sitemaps/tests/http.py | 13 +++++++++++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/django/contrib/sitemaps/tests/base.py b/django/contrib/sitemaps/tests/base.py index f1d6753b3a..224277e2a0 100644 --- a/django/contrib/sitemaps/tests/base.py +++ b/django/contrib/sitemaps/tests/base.py @@ -1,6 +1,3 @@ -import os - -from django.conf import settings from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.test import TestCase @@ -13,16 +10,9 @@ class SitemapTestsBase(TestCase): def setUp(self): self.base_url = '%s://%s' % (self.protocol, self.domain) - self.old_USE_L10N = settings.USE_L10N - self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS - settings.TEMPLATE_DIRS = ( - os.path.join(os.path.dirname(__file__), 'templates'), - ) self.old_Site_meta_installed = Site._meta.installed # Create a user that will double as sitemap content User.objects.create_user('testuser', 'test@example.com', 's3krit') def tearDown(self): - settings.USE_L10N = self.old_USE_L10N - settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS Site._meta.installed = self.old_Site_meta_installed diff --git a/django/contrib/sitemaps/tests/http.py b/django/contrib/sitemaps/tests/http.py index 3b56aa8d53..5786aa48d5 100644 --- a/django/contrib/sitemaps/tests/http.py +++ b/django/contrib/sitemaps/tests/http.py @@ -1,15 +1,19 @@ +import os from datetime import date + from django.conf import settings from django.contrib.auth.models import User from django.contrib.sitemaps import Sitemap, GenericSitemap from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured +from django.test.utils import override_settings from django.utils.unittest import skipUnless from django.utils.formats import localize from django.utils.translation import activate, deactivate from .base import SitemapTestsBase + class HTTPSitemapTests(SitemapTestsBase): def test_simple_sitemap_index(self): @@ -21,6 +25,9 @@ class HTTPSitemapTests(SitemapTestsBase): """ % self.base_url) + @override_settings( + TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),) + ) def test_simple_sitemap_custom_index(self): "A simple sitemap index can be rendered with a custom template" response = self.client.get('/simple/custom-index.xml') @@ -49,6 +56,9 @@ class HTTPSitemapTests(SitemapTestsBase): """ % (self.base_url, date.today())) + @override_settings( + TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),) + ) def test_simple_custom_sitemap(self): "A simple sitemap can be rendered with a custom template" response = self.client.get('/simple/custom-sitemap.xml') @@ -60,10 +70,9 @@ class HTTPSitemapTests(SitemapTestsBase): """ % (self.base_url, date.today())) @skipUnless(settings.USE_I18N, "Internationalization is not enabled") + @override_settings(USE_L10N=True) def test_localized_priority(self): "The priority value should not be localized (Refs #14164)" - # Localization should be active - settings.USE_L10N = True activate('fr') self.assertEqual(u'0,3', localize(0.3)) From 6522283a712af96a98e99d675b69ca15aadbace8 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 2 Jun 2012 21:28:18 +0200 Subject: [PATCH 87/95] Fixed #14478 -- Isolated messages tests from custom TEMPLATE_CONTEXT_PROCESSORS --- django/contrib/messages/tests/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py index 4a00324a3d..1f64c61ecb 100644 --- a/django/contrib/messages/tests/base.py +++ b/django/contrib/messages/tests/base.py @@ -1,5 +1,5 @@ from django import http -from django.conf import settings +from django.conf import settings, global_settings from django.contrib.messages import constants, utils, get_level, set_level from django.contrib.messages.api import MessageFailure from django.contrib.messages.storage import default_storage, base @@ -57,6 +57,7 @@ class BaseTest(TestCase): def setUp(self): self.settings_override = override_settings_tags( TEMPLATE_DIRS = (), + TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS, MESSAGE_TAGS = '', MESSAGE_STORAGE = '%s.%s' % (self.storage_class.__module__, self.storage_class.__name__), From 43a46e90048884b3e8457331c86b96c0ac6a9415 Mon Sep 17 00:00:00 2001 From: Samuel Sutch Date: Sun, 3 Jun 2012 12:44:37 -0700 Subject: [PATCH 88/95] Remove the summary attribute of the table In the discussion here: https://code.djangoproject.com/ticket/17138 it was decided that using the caption for this previously non-visible part of the table element was not semantic, so in this patch is moves that summary to the `title` attribute of the `a` tag which when overed over, on most browsers, will show the text. --- django/contrib/admin/templates/admin/index.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index a8ced39121..91ea0844b1 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -15,8 +15,12 @@ {% if app_list %} {% for app in app_list %}
- - +
{% blocktrans with name=app.name %}{{ name }}{% endblocktrans %}
+ {% for model in app.models %} {% if model.admin_url %} From f823ae3b3e835dd4abdb987ea0fcc22573bb308d Mon Sep 17 00:00:00 2001 From: Honza Kral Date: Sun, 3 Jun 2012 21:46:14 +0200 Subject: [PATCH 89/95] Fixed incorrect assert in test_filepathfield_folders --- tests/regressiontests/forms/tests/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index c4a8a9a3fa..590fb01682 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -1017,7 +1017,7 @@ class FieldsTests(SimpleTestCase): ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) - self.assertEqual(exp[1], got[1]) + self.assert_(got[0].endswith(exp[0])) # SplitDateTimeField ########################################################## From 71d9a2a7fe8cfadec8a46d208a218786aff09107 Mon Sep 17 00:00:00 2001 From: Honza Kral Date: Sun, 3 Jun 2012 22:31:49 +0200 Subject: [PATCH 90/95] Unittest2 style assertTrue instead of assert_ --- tests/regressiontests/forms/tests/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index 590fb01682..9800cfb879 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -994,7 +994,7 @@ class FieldsTests(SimpleTestCase): ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) - self.assert_(got[0].endswith(exp[0])) + self.assertTrue(got[0].endswith(exp[0])) f = FilePathField(path=path, allow_folders=True, allow_files=True) f.choices.sort() @@ -1017,7 +1017,7 @@ class FieldsTests(SimpleTestCase): ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) - self.assert_(got[0].endswith(exp[0])) + self.assertTrue(got[0].endswith(exp[0])) # SplitDateTimeField ########################################################## From a89034a2d8c0ca84184d260090d87645cfdc0e7d Mon Sep 17 00:00:00 2001 From: Honza Kral Date: Sun, 3 Jun 2012 22:54:34 +0200 Subject: [PATCH 91/95] Changed FilePathField tests to use it's own directory with test files. In previous version it used /django/forms/ which was fragile - for some users .pyc files were not generated and this tests failed. --- tests/regressiontests/forms/tests/fields.py | 29 +++++++------------ .../forms/tests/filepath_test_files/.dot-file | 0 .../tests/filepath_test_files/directory/.keep | 0 .../tests/filepath_test_files/fake-image.jpg | 0 .../filepath_test_files/real-text-file.txt | 0 5 files changed, 10 insertions(+), 19 deletions(-) create mode 100644 tests/regressiontests/forms/tests/filepath_test_files/.dot-file create mode 100644 tests/regressiontests/forms/tests/filepath_test_files/directory/.keep create mode 100644 tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg create mode 100644 tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index 9800cfb879..4d442de382 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -985,12 +985,11 @@ class FieldsTests(SimpleTestCase): self.assertTrue(got[0].endswith(exp[0])) def test_filepathfield_folders(self): - path = forms.__file__ - path = os.path.dirname(path) + '/' + path = os.path.dirname(__file__) + '/filepath_test_files/' f = FilePathField(path=path, allow_folders=True, allow_files=False) f.choices.sort() expected = [ - ('/django/forms/extras', 'extras'), + ('/tests/regressiontests/forms/tests/filepath_test_files/directory', 'directory'), ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) @@ -999,23 +998,15 @@ class FieldsTests(SimpleTestCase): f = FilePathField(path=path, allow_folders=True, allow_files=True) f.choices.sort() expected = [ - ('/django/forms/__init__.py', '__init__.py'), - ('/django/forms/__init__.pyc', '__init__.pyc'), - ('/django/forms/extras', 'extras'), - ('/django/forms/fields.py', 'fields.py'), - ('/django/forms/fields.pyc', 'fields.pyc'), - ('/django/forms/forms.py', 'forms.py'), - ('/django/forms/forms.pyc', 'forms.pyc'), - ('/django/forms/formsets.py', 'formsets.py'), - ('/django/forms/formsets.pyc', 'formsets.pyc'), - ('/django/forms/models.py', 'models.py'), - ('/django/forms/models.pyc', 'models.pyc'), - ('/django/forms/util.py', 'util.py'), - ('/django/forms/util.pyc', 'util.pyc'), - ('/django/forms/widgets.py', 'widgets.py'), - ('/django/forms/widgets.pyc', 'widgets.pyc') + ('/tests/regressiontests/forms/tests/filepath_test_files/.dot-file', '.dot-file'), + ('/tests/regressiontests/forms/tests/filepath_test_files/directory', 'directory'), + ('/tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg', 'fake-image.jpg'), + ('/tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt', 'real-text-file.txt'), ] - for exp, got in zip(expected, fix_os_paths(f.choices)): + + actual = fix_os_paths(f.choices) + self.assertEqual(len(expected), len(actual)) + for exp, got in zip(expected, actual): self.assertEqual(exp[1], got[1]) self.assertTrue(got[0].endswith(exp[0])) diff --git a/tests/regressiontests/forms/tests/filepath_test_files/.dot-file b/tests/regressiontests/forms/tests/filepath_test_files/.dot-file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/forms/tests/filepath_test_files/directory/.keep b/tests/regressiontests/forms/tests/filepath_test_files/directory/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg b/tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt b/tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt new file mode 100644 index 0000000000..e69de29bb2 From f699641161a4ec8b6cbee938fd3a4379e7889ff2 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sun, 3 Jun 2012 17:33:09 -0700 Subject: [PATCH 92/95] Fixed #17138 -- Made the sensitive_variables decorator work with object methods. --- django/views/debug.py | 17 +++- django/views/decorators/debug.py | 12 +-- tests/regressiontests/views/tests/debug.py | 104 +++++++++++++-------- tests/regressiontests/views/views.py | 23 ++++- 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/django/views/debug.py b/django/views/debug.py index 7bdf0d25ee..d95cd62017 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter): Replaces the values of variables marked as sensitive with stars (*********). """ - func_name = tb_frame.f_code.co_name - func = tb_frame.f_globals.get(func_name) - sensitive_variables = getattr(func, 'sensitive_variables', []) + # Loop through the frame's callers to see if the sensitive_variables + # decorator was used. + current_frame = tb_frame.f_back + sensitive_variables = None + while current_frame is not None: + if (current_frame.f_code.co_name == 'sensitive_variables_wrapper' + and 'sensitive_variables_wrapper' in current_frame.f_locals): + # The sensitive_variables decorator was used, so we take note + # of the sensitive variables' names. + wrapper = current_frame.f_locals['sensitive_variables_wrapper'] + sensitive_variables = getattr(wrapper, 'sensitive_variables', None) + break + current_frame = current_frame.f_back + cleansed = [] if self.is_active(request) and sensitive_variables: if sensitive_variables == '__ALL__': diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py index d04967ef09..5c222963d3 100644 --- a/django/views/decorators/debug.py +++ b/django/views/decorators/debug.py @@ -26,13 +26,13 @@ def sensitive_variables(*variables): """ def decorator(func): @functools.wraps(func) - def wrapper(*args, **kwargs): + def sensitive_variables_wrapper(*args, **kwargs): if variables: - wrapper.sensitive_variables = variables + sensitive_variables_wrapper.sensitive_variables = variables else: - wrapper.sensitive_variables = '__ALL__' + sensitive_variables_wrapper.sensitive_variables = '__ALL__' return func(*args, **kwargs) - return wrapper + return sensitive_variables_wrapper return decorator @@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters): """ def decorator(view): @functools.wraps(view) - def wrapper(request, *args, **kwargs): + def sensitive_post_parameters_wrapper(request, *args, **kwargs): if parameters: request.sensitive_post_parameters = parameters else: request.sensitive_post_parameters = '__ALL__' return view(request, *args, **kwargs) - return wrapper + return sensitive_post_parameters_wrapper return decorator diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py index c8358d334f..e8a7d49e79 100644 --- a/tests/regressiontests/views/tests/debug.py +++ b/tests/regressiontests/views/tests/debug.py @@ -10,13 +10,12 @@ from django.test import TestCase, RequestFactory from django.test.utils import (setup_test_template_loader, restore_template_loaders) from django.core.urlresolvers import reverse -from django.template import TemplateSyntaxError from django.views.debug import ExceptionReporter from django.core import mail from .. import BrokenException, except_args from ..views import (sensitive_view, non_sensitive_view, paranoid_view, - custom_exception_reporter_filter_view) + custom_exception_reporter_filter_view, sensitive_method_view) class DebugViewTests(TestCase): @@ -238,7 +237,8 @@ class ExceptionReportTestMixin(object): 'hash-brown-key': 'hash-brown-value', 'bacon-key': 'bacon-value',} - def verify_unsafe_response(self, view, check_for_vars=True): + def verify_unsafe_response(self, view, check_for_vars=True, + check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the response. """ @@ -250,13 +250,14 @@ class ExceptionReportTestMixin(object): self.assertContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertContains(response, 'worcestershire', status_code=500) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters are shown. + self.assertContains(response, k, status_code=500) + self.assertContains(response, v, status_code=500) - for k, v in self.breakfast_data.items(): - # All POST parameters are shown. - self.assertContains(response, k, status_code=500) - self.assertContains(response, v, status_code=500) - - def verify_safe_response(self, view, check_for_vars=True): + def verify_safe_response(self, view, check_for_vars=True, + check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the response. """ @@ -269,18 +270,19 @@ class ExceptionReportTestMixin(object): # Sensitive variable's name is shown but not its value. self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters' names are shown. + self.assertContains(response, k, status_code=500) + # Non-sensitive POST parameters' values are shown. + self.assertContains(response, 'baked-beans-value', status_code=500) + self.assertContains(response, 'hash-brown-value', status_code=500) + # Sensitive POST parameters' values are not shown. + self.assertNotContains(response, 'sausage-value', status_code=500) + self.assertNotContains(response, 'bacon-value', status_code=500) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertContains(response, k, status_code=500) - # Non-sensitive POST parameters' values are shown. - self.assertContains(response, 'baked-beans-value', status_code=500) - self.assertContains(response, 'hash-brown-value', status_code=500) - # Sensitive POST parameters' values are not shown. - self.assertNotContains(response, 'sausage-value', status_code=500) - self.assertNotContains(response, 'bacon-value', status_code=500) - - def verify_paranoid_response(self, view, check_for_vars=True): + def verify_paranoid_response(self, view, check_for_vars=True, + check_for_POST_params=True): """ Asserts that no variables or POST parameters are displayed in the response. """ @@ -292,14 +294,14 @@ class ExceptionReportTestMixin(object): self.assertNotContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters' names are shown. + self.assertContains(response, k, status_code=500) + # No POST parameters' values are shown. + self.assertNotContains(response, v, status_code=500) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertContains(response, k, status_code=500) - # No POST parameters' values are shown. - self.assertNotContains(response, v, status_code=500) - - def verify_unsafe_email(self, view): + def verify_unsafe_email(self, view, check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the email report. """ @@ -314,12 +316,13 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) - for k, v in self.breakfast_data.items(): - # All POST parameters are shown. - self.assertIn(k, email.body) - self.assertIn(v, email.body) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters are shown. + self.assertIn(k, email.body) + self.assertIn(v, email.body) - def verify_safe_email(self, view): + def verify_safe_email(self, view, check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the email report. """ @@ -334,15 +337,16 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertIn(k, email.body) - # Non-sensitive POST parameters' values are shown. - self.assertIn('baked-beans-value', email.body) - self.assertIn('hash-brown-value', email.body) - # Sensitive POST parameters' values are not shown. - self.assertNotIn('sausage-value', email.body) - self.assertNotIn('bacon-value', email.body) + if check_for_POST_params: + for k, v in self.breakfast_data.items(): + # All POST parameters' names are shown. + self.assertIn(k, email.body) + # Non-sensitive POST parameters' values are shown. + self.assertIn('baked-beans-value', email.body) + self.assertIn('hash-brown-value', email.body) + # Sensitive POST parameters' values are not shown. + self.assertNotIn('sausage-value', email.body) + self.assertNotIn('bacon-value', email.body) def verify_paranoid_email(self, view): """ @@ -425,6 +429,24 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): self.verify_unsafe_response(custom_exception_reporter_filter_view) self.verify_unsafe_email(custom_exception_reporter_filter_view) + def test_sensitive_method(self): + """ + Ensure that the sensitive_variables decorator works with object + methods. + Refs #18379. + """ + with self.settings(DEBUG=True): + self.verify_unsafe_response(sensitive_method_view, + check_for_POST_params=False) + self.verify_unsafe_email(sensitive_method_view, + check_for_POST_params=False) + + with self.settings(DEBUG=False): + self.verify_safe_response(sensitive_method_view, + check_for_POST_params=False) + self.verify_safe_email(sensitive_method_view, + check_for_POST_params=False) + class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin): """ diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py index 8e530cd2d8..2836d1bdde 100644 --- a/tests/regressiontests/views/views.py +++ b/tests/regressiontests/views/views.py @@ -2,7 +2,6 @@ from __future__ import absolute_import import sys -from django import forms from django.core.exceptions import PermissionDenied from django.core.urlresolvers import get_resolver from django.http import HttpResponse, HttpResponseRedirect @@ -14,7 +13,7 @@ from django.views.decorators.debug import (sensitive_post_parameters, from django.utils.log import getLogger from . import BrokenException, except_args -from .models import Article + def index_page(request): @@ -209,3 +208,23 @@ def custom_exception_reporter_filter_view(request): exc_info = sys.exc_info() send_log(request, exc_info) return technical_500_response(request, *exc_info) + + +class Klass(object): + + @sensitive_variables('sauce') + def method(self, request): + # Do not just use plain strings for the variables' values in the code + # so that the tests don't return false positives when the function's + # source is displayed in the exception report. + cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) + sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']) + try: + raise Exception + except Exception: + exc_info = sys.exc_info() + send_log(request, exc_info) + return technical_500_response(request, *exc_info) + +def sensitive_method_view(request): + return Klass().method(request) \ No newline at end of file From 5ef599c7b32a238bfda6f56cd149bacb61b85c77 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 4 Jun 2012 20:39:54 +0200 Subject: [PATCH 93/95] Used skipUnless decorator to skip tests in geos tests. --- django/contrib/gis/geos/tests/test_geos.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 9946495f54..f435cdaff1 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -1,6 +1,5 @@ import ctypes import random -import unittest from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, GeometryCollection, Point, MultiPoint, Polygon, MultiPolygon, LinearRing, @@ -9,6 +8,8 @@ from django.contrib.gis.geos.base import gdal, numpy, GEOSBase from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geometry.test_data import TestDataMixin +from django.utils import unittest + class GEOSTest(unittest.TestCase, TestDataMixin): @@ -195,9 +196,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export + @unittest.skipUnless(gdal.HAS_GDAL and gdal.GEOJSON, "gdal >= 1.5 is required") def test_json(self): "Testing GeoJSON input/output (via GDAL)." - if not gdal or not gdal.GEOJSON: return for g in self.geometries.json_geoms: geom = GEOSGeometry(g.wkt) if not hasattr(g, 'not_equal'): @@ -813,9 +814,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # And, they should be equal. self.assertEqual(gc1, gc2) + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_gdal(self): "Testing `ogr` and `srs` properties." - if not gdal.HAS_GDAL: return g1 = fromstr('POINT(5 23)') self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry)) self.assertEqual(g1.srs, None) @@ -835,9 +836,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertNotEqual(poly._ptr, cpy1._ptr) self.assertNotEqual(poly._ptr, cpy2._ptr) + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_transform(self): "Testing `transform` method." - if not gdal.HAS_GDAL: return orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774) @@ -963,9 +964,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(geom, tmpg) if not no_srid: self.assertEqual(geom.srid, tmpg.srid) + @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") def test_prepared(self): "Testing PreparedGeometry support." - if not GEOS_PREPARE: return # Creating a simple multipolygon and getting a prepared version. mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))') prep = mpoly.prepared @@ -990,10 +991,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): for geom, merged in zip(ref_geoms, ref_merged): self.assertEqual(merged, geom.merged) + @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") def test_valid_reason(self): "Testing IsValidReason support" - # Skipping tests if GEOS < v3.1. - if not GEOS_PREPARE: return g = GEOSGeometry("POINT(0 0)") self.assertTrue(g.valid) From 0199bdc0b411d4aba0173053417bbeea8224b26b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 4 Jun 2012 20:31:23 +0100 Subject: [PATCH 94/95] Rewrote security.txt SSL docs, noting SECURE_PROXY_SSL_HEADER. --- docs/topics/security.txt | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 193d0029a4..151853d4ac 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -122,22 +122,19 @@ transferred between client and server, and in some cases -- **active** network attackers -- to alter data that is sent in either direction. If you want the protection that HTTPS provides, and have enabled it on your -server, there are some additional steps to consider to ensure that sensitive -information is not leaked: +server, there are some additional steps you may need: + +* If necessary, set :setting:`SECURE_PROXY_SSL_HEADER`, ensuring that you have + understood the warnings there thoroughly. Failure to do this can result + in CSRF vulnerabilities, and failure to do it correctly can also be + dangerous! * Set up redirection so that requests over HTTP are redirected to HTTPS. - It is possible to do this with a piece of Django middleware. However, this has - problems for the common case of a Django app running behind a reverse - proxy. Often, reverse proxies are configured to set the ``X-Forwarded-SSL`` - header (or equivalent) if the incoming connection was HTTPS, and the absence - of this header could be used to detect a request that was not HTTPS. However, - this method usually cannot be relied on, as a client, or a malicious active - network attacker, could also set this header. - - So, for the case of a reverse proxy, it is recommended that the main Web - server should be configured to do the redirect to HTTPS, or configured to send - HTTP requests to an app that unconditionally redirects to HTTPS. + This could be done using a custom middleware. Please note the caveats under + :setting:`SECURE_PROXY_SSL_HEADER`. For the case of a reverse proxy, it may be + easier or more secure to configure the main Web server to do the redirect to + HTTPS. * Use 'secure' cookies. From 840ffd80baad85c05670d6b642f654cffaa93cc3 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 4 Jun 2012 20:39:57 +0100 Subject: [PATCH 95/95] Noted that SECURE_PROXY_SSL_HEADER is needed by CSRF protection. Both false positives and false negatives of HttpRequest.is_secure can be dangerous. --- docs/ref/settings.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 398c90661b..a1b76f65e1 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1605,7 +1605,8 @@ method. This takes some explanation. By default, ``is_secure()`` is able to determine whether a request is secure by looking at whether the requested URL uses -"https://". +"https://". This is important for Django's CSRF protection, and may be used +by your own code or third-party apps. If your Django app is behind a proxy, though, the proxy may be "swallowing" the fact that a request is HTTPS, using a non-HTTPS connection between the proxy @@ -1635,7 +1636,7 @@ available in ``request.META``.) .. warning:: - **You will probably open security holes in your site if you set this without knowing what you're doing. Seriously.** + **You will probably open security holes in your site if you set this without knowing what you're doing. And if you fail to set it when you should. Seriously.** Make sure ALL of the following are true before setting this (assuming the values from the example above):
+ + {% blocktrans with name=app.name %}{{ name }}{% endblocktrans %} + +