From aee9eecb920cf281e8339a5f7edadc6f2dd04fea Mon Sep 17 00:00:00 2001 From: Daniel Hepper Date: Fri, 8 Jun 2012 12:32:16 +0200 Subject: Fixed #18444 -- Replace hard coded "View on Site" URLs --- docs/ref/contrib/admin/index.txt | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 3ef9abe6da..7aca0981c3 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1948,16 +1948,17 @@ accessible using Django's :ref:`URL reversing system `. The :class:`AdminSite` provides the following named URL patterns: -====================== ======================== ============= -Page URL name Parameters -====================== ======================== ============= -Index ``index`` -Logout ``logout`` -Password change ``password_change`` -Password change done ``password_change_done`` -i18n javascript ``jsi18n`` -Application index page ``app_list`` ``app_label`` -====================== ======================== ============= +========================= ======================== ================================== +Page URL name Parameters +========================= ======================== ================================== +Index ``index`` +Logout ``logout`` +Password change ``password_change`` +Password change done ``password_change_done`` +i18n javascript ``jsi18n`` +Application index page ``app_list`` ``app_label`` +Redirect to object's page ``view_on_site`` ``content_type_id``, ``object_id`` +========================= ======================== ================================== Each :class:`ModelAdmin` instance provides an additional set of named URLs: -- cgit v1.3 From ffa6d95f65363b7f4f9047ab11561880be29049a Mon Sep 17 00:00:00 2001 From: Gabe Jackson Date: Thu, 7 Jun 2012 14:08:46 +0200 Subject: Fixed #18154 -- Documentation on closing File objects and best practices --- docs/topics/files.txt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'docs') diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 9ab8d5c496..c9b4327941 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -75,6 +75,29 @@ using a Python built-in ``file`` object:: Now you can use any of the documented attributes and methods of the :class:`~django.core.files.File` class. +Be aware that files created in this way are not automatically closed. +The following approach may be used to close files automatically:: + + >>> from django.core.files import File + + # Create a Python file object using open() and the with statement + >>> with open('/tmp/hello.world', 'w') as f: + >>> myfile = File(f) + >>> for line in myfile: + >>> print line + >>> myfile.closed + True + >>> f.closed + True + +Closing files is especially important when accessing file fields in a loop +over a large number of objects:: If files are not manually closed after +accessing them, the risk of running out of file descriptors may arise. This +may lead to the following error: + + IOError: [Errno 24] Too many open files + + File storage ============ -- cgit v1.3 From c57ba673312cb5774d544353044e2182b6223040 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 19 Jun 2012 10:49:30 +1200 Subject: Fixed #14502 again -- saner verbatim closing token Previously, the closing token for the verbatim tag was specified as the first argument of the opening token. As pointed out by Jannis, this is a rather major departure from the core tag standard. The new method reflects how you can give a specific closing name to {% block %} tags. --- django/template/base.py | 9 ++------- django/template/defaulttags.py | 14 +++++--------- docs/ref/templates/builtins.txt | 10 +++++----- tests/regressiontests/templates/tests.py | 2 +- 4 files changed, 13 insertions(+), 22 deletions(-) (limited to 'docs') diff --git a/django/template/base.py b/django/template/base.py index 5a91bfda99..89bc90971f 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -216,13 +216,8 @@ class Lexer(object): if token_string.startswith(VARIABLE_TAG_START): token = Token(TOKEN_VAR, token_string[2:-2].strip()) elif token_string.startswith(BLOCK_TAG_START): - if block_content.startswith('verbatim'): - bits = block_content.split(' ', 1) - if bits[0] == 'verbatim': - if len(bits) > 1: - self.verbatim = bits[1] - else: - self.verbatim = 'endverbatim' + if block_content[:9] in ('verbatim', 'verbatim '): + self.verbatim = 'end%s' % block_content token = Token(TOKEN_BLOCK, block_content) elif token_string.startswith(COMMENT_TAG_START): content = '' diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 0de5d9e3db..83b72e120b 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -1291,18 +1291,14 @@ def verbatim(parser, token): {% don't process this %} {% endverbatim %} - You can also specify an alternate closing tag:: + You can also designate a specific closing tag block (allowing the + unrendered use of ``{% endverbatim %}``):: - {% verbatim -- %} + {% verbatim myblock %} ... - {% -- %} + {% endverbatim myblock %} """ - bits = token.contents.split(' ', 1) - if len(bits) > 1: - closing_tag = bits[1] - else: - closing_tag = 'endverbatim' - nodelist = parser.parse((closing_tag,)) + nodelist = parser.parse(('endverbatim',)) parser.delete_first_token() return VerbatimNode(nodelist.render(Context())) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 6f341e9f97..cf228d72f6 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1047,12 +1047,12 @@ Django's syntax. For example:: {{if dying}}Still alive.{{/if}} {% endverbatim %} -You can also specify an alternate closing tag:: +You can also designate a specific closing tag, allowing the use of +``{% endverbatim %}`` as part of the unrendered contents:: - {% verbatim finished %} - The verbatim tag looks like this: - {% verbatim %}{% endverbatim %} - {% finished %} + {% verbatim myblock %} + Avoid template rendering via the {% verbatim %}{% endverbatim %} block. + {% endverbatim myblock %} .. templatetag:: widthratio diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 989fd72d94..35d01221ab 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1623,7 +1623,7 @@ class Templates(unittest.TestCase): 'verbatim-tag03': ("{% verbatim %}It's the {% verbatim %} tag{% endverbatim %}", {}, "It's the {% verbatim %} tag"), 'verbatim-tag04': ('{% verbatim %}{% verbatim %}{% endverbatim %}{% endverbatim %}', {}, template.TemplateSyntaxError), 'verbatim-tag05': ('{% verbatim %}{% endverbatim %}{% verbatim %}{% endverbatim %}', {}, ''), - 'verbatim-tag06': ("{% verbatim -- %}Don't {% endverbatim %} just yet{% -- %}", {}, "Don't {% endverbatim %} just yet"), + 'verbatim-tag06': ("{% verbatim special %}Don't {% endverbatim %} just yet{% endverbatim special %}", {}, "Don't {% endverbatim %} just yet"), } return tests -- cgit v1.3 From f8ef93a6576837ef3598066d6ed715171a91cd04 Mon Sep 17 00:00:00 2001 From: Tim Saylor Date: Wed, 13 Jun 2012 13:42:18 -0500 Subject: Fixed a documentation typo on the widget page. --- docs/ref/forms/widgets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 88d0d706cd..fb7657349a 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -75,7 +75,7 @@ changing :attr:`ChoiceField.choices` will update :attr:`Select.choices`. For example:: >>> from django import forms - >>> CHOICES = (('1', 'First',), ('2', 'Second',))) + >>> CHOICES = (('1', 'First',), ('2', 'Second',)) >>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES) >>> choice_field.choices [('1', 'First'), ('2', 'Second')] -- cgit v1.3 From 7f225880e4b7f846a1e910f6be0bce11f9a8d5ec Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 22 Jun 2012 15:43:57 +0200 Subject: Corrected the `instance_dict` description for form wizards. --- django/contrib/formtools/wizard/views.py | 5 +++-- docs/ref/contrib/formtools/form-wizard.txt | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index 4372c8aa6a..6222d1ddab 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -133,8 +133,9 @@ class WizardView(TemplateView): The key should be equal to the `step_name` in the `form_list` (or the str of the zero based counter - if no step_names added in the `form_list`) - * `instance_dict` - contains a dictionary of instance objects. This - is only used when `ModelForm`s are used. The key should be equal to + * `instance_dict` - contains a dictionary whose values are model + instances if the step is based on a ``ModelForm`` and querysets if + the step is based on a ``ModelFormSet``. The key should be equal to the `step_name` in the `form_list`. Same rules as for `initial_dict` apply. * `condition_dict` - contains a dictionary of boolean values or diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 7aafbe89f3..7d229a5d66 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -554,9 +554,8 @@ How to work with ModelForm and ModelFormSet WizardView supports :doc:`ModelForms ` and :ref:`ModelFormSets `. Additionally to :attr:`~WizardView.initial_dict`, the :meth:`~WizardView.as_view` method takes -an ``instance_dict`` argument that should contain instances of ``ModelForm`` and -``ModelFormSet``. Similarly to :attr:`~WizardView.initial_dict`, these -dictionary key values should be equal to the step number in the form list. +an ``instance_dict`` argument that should contain model instances for steps +based on ``ModelForm`` and querysets for steps based on ``ModelFormSet``. Usage of ``NamedUrlWizardView`` =============================== -- cgit v1.3 From ea9536b17fe16b2be45aa4a3552f919682c93e3e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Jun 2012 19:00:40 -0700 Subject: Note that Jython has an alpha with 2.7 support. --- docs/releases/1.5.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 3e274b5d98..719433f8fe 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -27,8 +27,9 @@ Django 1.4 until you can upgrade your Python version. Per :doc:`our support poli `, Django 1.4 will continue to receive security support until the release of Django 1.6. -Django 1.5 does not run on Jython, because Jython doesn't currently offer any -version compatible with Python 2.6. +Django 1.5 does not run on a Jython final release, because Jython's latest release +doesn't currently support Python 2.6. However, Jython currently does offer an alpha +release featuring 2.7 support. What's new in Django 1.5 ======================== -- cgit v1.3 From c864b36ba12f195cb1d3f9b1ad27ad71d5d8b5ea Mon Sep 17 00:00:00 2001 From: jnns Date: Thu, 21 Jun 2012 12:37:12 +0300 Subject: Updated TEMPLATE_CONTEXT_PROCESSORS defaults in the docs. django.core.context_processors.tz was missing from default TEMPLATE_CONTEXT_PROCESSORS in the template api documentation. --- docs/ref/templates/api.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 6142dd7017..7816e1d07d 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -370,6 +370,7 @@ and return a dictionary of items to be merged into the context. By default, "django.core.context_processors.i18n", "django.core.context_processors.media", "django.core.context_processors.static", + "django.core.context_processors.tz", "django.contrib.messages.context_processors.messages") In addition to these, ``RequestContext`` always uses -- cgit v1.3 From 946d3d9f84ea7979a4abf0857e4aa7ee33576303 Mon Sep 17 00:00:00 2001 From: Bojan Mihelac Date: Tue, 19 Jun 2012 14:42:02 +0300 Subject: Fixed url translation docs. ``include`` calls shouldn't have a $ sign at the end of the url pattern. --- docs/topics/i18n/translation.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 896ff87744..ec6159d538 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -890,7 +890,7 @@ prepend the current active language code to all url patterns defined within urlpatterns += i18n_patterns('', url(r'^about/$', 'about.view', name='about'), - url(r'^news/$', include(news_patterns, namespace='news')), + url(r'^news/', include(news_patterns, namespace='news')), ) @@ -945,7 +945,7 @@ URL patterns can also be marked translatable using the urlpatterns += i18n_patterns('', url(_(r'^about/$'), 'about.view', name='about'), - url(_(r'^news/$'), include(news_patterns, namespace='news')), + url(_(r'^news/'), include(news_patterns, namespace='news')), ) -- cgit v1.3 From d4da08375b634544b95859d4d4667b8f05e3a29a Mon Sep 17 00:00:00 2001 From: Dmitry Medvinsky Date: Fri, 8 Jun 2012 14:00:51 +0400 Subject: Fixed #18454 -- Added ability to pass a list of signals to `receiver`. Added ability to use receiver decorator in the following way: @receiver([post_save, post_delete], sender=MyModel) def signals_receiver(sender, **kwargs): ... --- django/dispatch/dispatcher.py | 11 ++++++-- docs/releases/1.5.txt | 3 +++ docs/topics/signals.txt | 13 +++++++--- tests/regressiontests/dispatch/tests/__init__.py | 2 +- .../dispatch/tests/test_dispatcher.py | 30 +++++++++++++++++++++- 5 files changed, 52 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index 54e71c01cc..8f57b185c3 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -257,14 +257,21 @@ class Signal(object): def receiver(signal, **kwargs): """ A decorator for connecting receivers to signals. Used by passing in the - signal and keyword arguments to connect:: + signal (or list of signals) and keyword arguments to connect:: @receiver(post_save, sender=MyModel) def signal_receiver(sender, **kwargs): ... + @receiver([post_save, post_delete], sender=MyModel) + def signals_receiver(sender, **kwargs): + ... + """ def _decorator(func): - signal.connect(func, **kwargs) + if isinstance(signal, (list, tuple)): + [s.connect(func, **kwargs) for s in signal] + else: + signal.connect(func, **kwargs) return func return _decorator diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 719433f8fe..2f20f5f9f9 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -103,6 +103,9 @@ Django 1.5 also includes several smaller improvements worth noting: * In the localflavor for Canada, "pq" was added to the acceptable codes for Quebec. It's an old abbreviation. +* The :ref:`receiver ` decorator is now able to + connect to more than one signal by supplying a list of signals. + Backwards incompatible changes in 1.5 ===================================== diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index 3ef68316a9..fa668cc8c7 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -52,10 +52,10 @@ called when the signal is sent by using the :meth:`.Signal.connect` method: .. method:: Signal.connect(receiver, [sender=None, weak=True, dispatch_uid=None]) - + :param receiver: The callback function which will be connected to this signal. See :ref:`receiver-functions` for more information. - + :param sender: Specifies a particular sender to receive signals from. See :ref:`connecting-to-specific-signals` for more information. @@ -129,10 +129,17 @@ receiver: Now, our ``my_callback`` function will be called each time a request finishes. +Note that ``receiver`` can also take a list of signals to connect a function +to. + .. versionadded:: 1.3 The ``receiver`` decorator was added in Django 1.3. +.. versionchanged:: 1.5 + +The ability to pass a list of signals was added. + .. admonition:: Where should this code live? You can put signal handling and registration code anywhere you like. @@ -182,7 +189,7 @@ Preventing duplicate signals In some circumstances, the module in which you are connecting signals may be imported multiple times. This can cause your receiver function to be registered more than once, and thus called multiples times for a single signal -event. +event. If this behavior is problematic (such as when using signals to send an email whenever a model is saved), pass a unique identifier as diff --git a/tests/regressiontests/dispatch/tests/__init__.py b/tests/regressiontests/dispatch/tests/__init__.py index 447975ab85..b6d26217e1 100644 --- a/tests/regressiontests/dispatch/tests/__init__.py +++ b/tests/regressiontests/dispatch/tests/__init__.py @@ -4,5 +4,5 @@ Unit-tests for the dispatch project from __future__ import absolute_import -from .test_dispatcher import DispatcherTests +from .test_dispatcher import DispatcherTests, ReceiverTestCase from .test_saferef import SaferefTests diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py index 319d6553a0..5f7094d5fa 100644 --- a/tests/regressiontests/dispatch/tests/test_dispatcher.py +++ b/tests/regressiontests/dispatch/tests/test_dispatcher.py @@ -2,7 +2,7 @@ import gc import sys import time -from django.dispatch import Signal +from django.dispatch import Signal, receiver from django.utils import unittest @@ -33,6 +33,8 @@ class Callable(object): return val a_signal = Signal(providing_args=["val"]) +b_signal = Signal(providing_args=["val"]) +c_signal = Signal(providing_args=["val"]) class DispatcherTests(unittest.TestCase): """Test suite for dispatcher (barely started)""" @@ -123,3 +125,29 @@ class DispatcherTests(unittest.TestCase): garbage_collect() a_signal.disconnect(receiver_3) self._testIsClean(a_signal) + + +class ReceiverTestCase(unittest.TestCase): + """ + Test suite for receiver. + + """ + def testReceiverSingleSignal(self): + @receiver(a_signal) + def f(val, **kwargs): + self.state = val + self.state = False + a_signal.send(sender=self, val=True) + self.assertTrue(self.state) + + def testReceiverSignalList(self): + @receiver([a_signal, b_signal, c_signal]) + def f(val, **kwargs): + self.state.append(val) + self.state = [] + a_signal.send(sender=self, val='a') + c_signal.send(sender=self, val='c') + b_signal.send(sender=self, val='b') + self.assertIn('a', self.state) + self.assertIn('b', self.state) + self.assertIn('c', self.state) -- cgit v1.3 From 19a810b18cacea12f201b5a235d1af9218d9c2e9 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 23 Jun 2012 18:44:40 +0200 Subject: Fixed #14917 -- Hinted that view should redirect after form post success --- docs/topics/forms/modelforms.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index eb53b177c5..4cfde400a7 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -877,7 +877,8 @@ of a model. Here's how you can do that:: formset = BookInlineFormSet(request.POST, request.FILES, instance=author) if formset.is_valid(): formset.save() - # Do something. + # Do something. Should generally end with a redirect. For example: + return HttpResponseRedirect(author.get_absolute_url()) else: formset = BookInlineFormSet(instance=author) return render_to_response("manage_books.html", { -- cgit v1.3 From d69f1d71c4ee382542d09e80b18ef01feac5884c Mon Sep 17 00:00:00 2001 From: Gabriel Grant Date: Sat, 23 Jun 2012 19:55:23 -0700 Subject: Fixed typo in JSONResponseMixin example. --- docs/topics/class-based-views/index.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index bdf649da48..6c2848944c 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -93,13 +93,13 @@ conversion to JSON once. For example, a simple JSON mixin might look something like this:: import json - from django import http + from django.http import HttpResponse class JSONResponseMixin(object): """ A mixin that can be used to render a JSON response. """ - reponse_class = HTTPResponse + response_class = HttpResponse def render_to_response(self, context, **response_kwargs): """ -- cgit v1.3 From b6c356b7bb97f3d6d4831b31e67868313bbbc090 Mon Sep 17 00:00:00 2001 From: Anssi Kääriäinen Date: Tue, 26 Jun 2012 18:08:42 +0300 Subject: Fixed #17485 -- Made defer work with select_related This commit tackles a couple of issues. First, in certain cases there were some mixups if field.attname or field.name should be deferred. Field.attname is now always used. Another issue tackled is a case where field is both deferred by .only(), and selected by select_related. This case is now an error. A lot of thanks to koniiiik (Michal Petrucha) for the patch, and to Andrei Antoukh for review. --- django/db/models/query.py | 7 +++--- django/db/models/query_utils.py | 13 +++++++++-- django/db/models/sql/compiler.py | 7 ++++-- django/db/models/sql/query.py | 12 +++++++--- docs/ref/models/querysets.txt | 11 ++++++--- tests/modeltests/defer/tests.py | 20 ++++++++++++----- tests/regressiontests/defer_regress/models.py | 4 ++++ tests/regressiontests/defer_regress/tests.py | 26 ++++++++++++++++++++-- .../select_related_regress/tests.py | 2 +- 9 files changed, 81 insertions(+), 21 deletions(-) (limited to 'docs') diff --git a/django/db/models/query.py b/django/db/models/query.py index 755820c3b0..0f1d87c642 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1296,7 +1296,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, # Build the list of fields that *haven't* been requested for field, model in klass._meta.get_fields_with_model(): if field.name not in load_fields: - skip.add(field.name) + skip.add(field.attname) elif local_only and model is not None: continue else: @@ -1327,7 +1327,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, related_fields = [] for f in klass._meta.fields: - if select_related_descend(f, restricted, requested): + if select_related_descend(f, restricted, requested, load_fields): if restricted: next = requested[f.name] else: @@ -1339,7 +1339,8 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, reverse_related_fields = [] if restricted: for o in klass._meta.get_all_related_objects(): - if o.field.unique and select_related_descend(o.field, restricted, requested, reverse=True): + if o.field.unique and select_related_descend(o.field, restricted, requested, + only_load.get(o.model), reverse=True): next = requested[o.field.related_query_name()] klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth+1, requested=next, only_load=only_load, local_only=True) diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index a7c176fd8f..60bdb2bcb4 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -126,18 +126,19 @@ class DeferredAttribute(object): return None -def select_related_descend(field, restricted, requested, reverse=False): +def select_related_descend(field, restricted, requested, load_fields, reverse=False): """ Returns True if this field should be used to descend deeper for select_related() purposes. Used by both the query construction code (sql.query.fill_related_selections()) and the model instance creation code - (query.get_cached_row()). + (query.get_klass_info()). Arguments: * field - the field to be checked * restricted - a boolean field, indicating if the field list has been manually restricted using a requested clause) * requested - The select_related() dictionary. + * load_fields - the set of fields to be loaded on this model * reverse - boolean, True if we are checking a reverse select related """ if not field.rel: @@ -151,6 +152,14 @@ def select_related_descend(field, restricted, requested, reverse=False): return False if not restricted and field.null: return False + if load_fields: + if field.name not in load_fields: + if restricted and field.name in requested: + raise InvalidQuery("Field %s.%s cannot be both deferred" + " and traversed using select_related" + " at the same time." % + (field.model._meta.object_name, field.name)) + return False return True # This function is needed because data descriptors must be defined on a class diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 5801b2f428..d44cdfe4a4 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -596,6 +596,7 @@ class SQLCompiler(object): if avoid_set is None: avoid_set = set() orig_dupe_set = dupe_set + only_load = self.query.get_loaded_field_names() # Setup for the case when only particular related fields should be # included in the related selection. @@ -607,7 +608,8 @@ class SQLCompiler(object): restricted = False for f, model in opts.get_fields_with_model(): - if not select_related_descend(f, restricted, requested): + if not select_related_descend(f, restricted, requested, + only_load.get(model or self.query.model)): continue # The "avoid" set is aliases we want to avoid just for this # particular branch of the recursion. They aren't permanently @@ -680,7 +682,8 @@ class SQLCompiler(object): if o.field.unique ] for f, model in related_fields: - if not select_related_descend(f, restricted, requested, reverse=True): + if not select_related_descend(f, restricted, requested, + only_load.get(model), reverse=True): continue # The "avoid" set is aliases we want to avoid just for this # particular branch of the recursion. They aren't permanently diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 7f331bfe7f..8fbba3dbc9 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1845,9 +1845,15 @@ class Query(object): If no fields are marked for deferral, returns an empty dictionary. """ - collection = {} - self.deferred_to_data(collection, self.get_loaded_field_names_cb) - return collection + # We cache this because we call this function multiple times + # (compiler.fill_related_selections, query.iterator) + try: + return self._loaded_field_names_cache + except AttributeError: + collection = {} + self.deferred_to_data(collection, self.get_loaded_field_names_cb) + self._loaded_field_names_cache = collection + return collection def get_loaded_field_names_cb(self, target, model, fields): """ diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index eef22728ab..2876f1474d 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1081,11 +1081,13 @@ to ``defer()``:: # Load all fields immediately. my_queryset.defer(None) +.. versionchanged:: 1.5 + Some fields in a model won't be deferred, even if you ask for them. You can never defer the loading of the primary key. If you are using :meth:`select_related()` to retrieve related models, you shouldn't defer the -loading of the field that connects from the primary model to the related one -(at the moment, that doesn't raise an error, but it will eventually). +loading of the field that connects from the primary model to the related +one, doing so will result in an error. .. note:: @@ -1145,9 +1147,12 @@ logically:: # existing set of fields). Entry.objects.defer("body").only("headline", "body") +.. versionchanged:: 1.5 + All of the cautions in the note for the :meth:`defer` documentation apply to ``only()`` as well. Use it cautiously and only after exhausting your other -options. +options. Also note that using :meth:`only` and omitting a field requested +using :meth:`select_related` is an error as well. using ~~~~~ diff --git a/tests/modeltests/defer/tests.py b/tests/modeltests/defer/tests.py index eb09162b01..50db5a76b4 100644 --- a/tests/modeltests/defer/tests.py +++ b/tests/modeltests/defer/tests.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -from django.db.models.query_utils import DeferredAttribute +from django.db.models.query_utils import DeferredAttribute, InvalidQuery from django.test import TestCase from .models import Secondary, Primary, Child, BigChild, ChildProxy @@ -73,9 +73,19 @@ class DeferTests(TestCase): self.assert_delayed(qs.defer("name").get(pk=p1.pk), 1) self.assert_delayed(qs.only("name").get(pk=p1.pk), 2) - # DOES THIS WORK? - self.assert_delayed(qs.only("name").select_related("related")[0], 1) - self.assert_delayed(qs.defer("related").select_related("related")[0], 0) + # When we defer a field and also select_related it, the query is + # invalid and raises an exception. + with self.assertRaises(InvalidQuery): + qs.only("name").select_related("related")[0] + with self.assertRaises(InvalidQuery): + qs.defer("related").select_related("related")[0] + + # With a depth-based select_related, all deferred ForeignKeys are + # deferred instead of traversed. + with self.assertNumQueries(3): + obj = qs.defer("related").select_related()[0] + self.assert_delayed(obj, 1) + self.assertEqual(obj.related.id, s1.pk) # Saving models with deferred fields is possible (but inefficient, # since every field has to be retrieved first). @@ -155,7 +165,7 @@ class DeferTests(TestCase): children = ChildProxy.objects.all().select_related().only('id', 'name') self.assertEqual(len(children), 1) child = children[0] - self.assert_delayed(child, 1) + self.assert_delayed(child, 2) self.assertEqual(child.name, 'p1') self.assertEqual(child.value, 'xx') diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index 812d2da206..bd4f845f27 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -47,3 +47,7 @@ class SimpleItem(models.Model): class Feature(models.Model): item = models.ForeignKey(SimpleItem) + +class ItemAndSimpleItem(models.Model): + item = models.ForeignKey(Item) + simple = models.ForeignKey(SimpleItem) diff --git a/tests/regressiontests/defer_regress/tests.py b/tests/regressiontests/defer_regress/tests.py index 1f07d4c9a8..53bb59f5b3 100644 --- a/tests/regressiontests/defer_regress/tests.py +++ b/tests/regressiontests/defer_regress/tests.py @@ -9,7 +9,7 @@ from django.db.models.loading import cache from django.test import TestCase from .models import (ResolveThis, Item, RelatedItem, Child, Leaf, Proxy, - SimpleItem, Feature) + SimpleItem, Feature, ItemAndSimpleItem) class DeferRegressionTest(TestCase): @@ -109,6 +109,7 @@ class DeferRegressionTest(TestCase): Child, Feature, Item, + ItemAndSimpleItem, Leaf, Proxy, RelatedItem, @@ -125,12 +126,16 @@ class DeferRegressionTest(TestCase): ), ) ) + # FIXME: This is dependent on the order in which tests are run -- + # this test case has to be the first, otherwise a LOT more classes + # appear. self.assertEqual( klasses, [ "Child", "Child_Deferred_value", "Feature", "Item", + "ItemAndSimpleItem", "Item_Deferred_name", "Item_Deferred_name_other_value_text", "Item_Deferred_name_other_value_value", @@ -139,7 +144,7 @@ class DeferRegressionTest(TestCase): "Leaf", "Leaf_Deferred_child_id_second_child_id_value", "Leaf_Deferred_name_value", - "Leaf_Deferred_second_child_value", + "Leaf_Deferred_second_child_id_value", "Leaf_Deferred_value", "Proxy", "RelatedItem", @@ -175,6 +180,23 @@ class DeferRegressionTest(TestCase): self.assertEqual(1, qs.count()) self.assertEqual('Foobar', qs[0].name) + def test_defer_with_select_related(self): + item1 = Item.objects.create(name="first", value=47) + item2 = Item.objects.create(name="second", value=42) + simple = SimpleItem.objects.create(name="simple", value="23") + related = ItemAndSimpleItem.objects.create(item=item1, simple=simple) + + obj = ItemAndSimpleItem.objects.defer('item').select_related('simple').get() + self.assertEqual(obj.item, item1) + self.assertEqual(obj.item_id, item1.id) + + obj.item = item2 + obj.save() + + obj = ItemAndSimpleItem.objects.defer('item').select_related('simple').get() + self.assertEqual(obj.item, item2) + self.assertEqual(obj.item_id, item2.id) + def test_deferred_class_factory(self): from django.db.models.query_utils import deferred_class_factory new_class = deferred_class_factory(Item, diff --git a/tests/regressiontests/select_related_regress/tests.py b/tests/regressiontests/select_related_regress/tests.py index e35157dbaf..73b0a8a875 100644 --- a/tests/regressiontests/select_related_regress/tests.py +++ b/tests/regressiontests/select_related_regress/tests.py @@ -133,7 +133,7 @@ class SelectRelatedRegressTests(TestCase): self.assertEqual(troy.state.name, 'Western Australia') # Also works if you use only, rather than defer - troy = SpecialClient.objects.select_related('state').only('name').get(name='Troy Buswell') + troy = SpecialClient.objects.select_related('state').only('name', 'state').get(name='Troy Buswell') self.assertEqual(troy.name, 'Troy Buswell') self.assertEqual(troy.value, 42) -- cgit v1.3 From ada961b0d20cc31c6d414b556ba2dafb4e73c406 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 27 Jun 2012 18:11:45 +0200 Subject: Fixed #18527 -- Removed superfluous backslash in CBV docs Thanks ramilzay at gmail.com for the report. --- docs/topics/class-based-views/generic-display.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt index 39fb41df04..4c2f95ce17 100644 --- a/docs/topics/class-based-views/generic-display.txt +++ b/docs/topics/class-based-views/generic-display.txt @@ -397,7 +397,7 @@ custom view:: urlpatterns = patterns('', #... - url(r'^authors/(?P\\d+)/$', AuthorDetailView.as_view(), name='author-detail'), + url(r'^authors/(?P\d+)/$', AuthorDetailView.as_view(), name='author-detail'), ) Then we'd write our new view -- ``get_object`` is the method that retrieves the -- cgit v1.3 From 1cf8287e3a6f252b79e9b9a8f945f11a92e17cd5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 20 Jun 2012 19:13:35 -0400 Subject: Fixed #18369 - Fixed argument name in render() function; thanks qsolo825@ for the report. --- docs/topics/http/shortcuts.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 2185d5497f..10be353e80 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -15,7 +15,7 @@ introduce controlled coupling for convenience's sake. ``render`` ========== -.. function:: render(request, template[, dictionary][, context_instance][, content_type][, status][, current_app]) +.. function:: render(request, template_name[, dictionary][, context_instance][, content_type][, status][, current_app]) .. versionadded:: 1.3 @@ -32,7 +32,7 @@ Required arguments ``request`` The request object used to generate this response. -``template`` +``template_name`` The full name of a template to use or sequence of template names. Optional arguments -- cgit v1.3 From c8928b91b515c0712bdb633c1d1b2656b80baff8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 20 Jun 2012 20:03:06 -0400 Subject: Fixed #17511 - Removed reference to deprecated "reset" management command in FAQ; thanks voxpuibr@ for the report. --- docs/faq/models.txt | 11 ++++------- docs/ref/django-admin.txt | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) (limited to 'docs') diff --git a/docs/faq/models.txt b/docs/faq/models.txt index d34a26a82e..4a83aa9f2c 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -40,18 +40,15 @@ Yes. See :doc:`Integrating with a legacy database `. If I make changes to a model, how do I update the database? ----------------------------------------------------------- -If you don't mind clearing data, your project's ``manage.py`` utility has an -option to reset the SQL for a particular application:: - - manage.py reset appname - -This drops any tables associated with ``appname`` and recreates them. +If you don't mind clearing data, your project's ``manage.py`` utility has a +:djadmin:`flush` option to reset the database to the state it was in +immediately after :djadmin:`syncdb` was executed. If you do care about deleting data, you'll have to execute the ``ALTER TABLE`` statements manually in your database. There are `external projects which handle schema updates -`_, of which the current +`_, of which the current defacto standard is `south `_. Do Django models support multiple-column primary keys? diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 360c0ae4d3..8fea699c1b 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -224,8 +224,8 @@ flush .. django-admin:: flush -Returns the database to the state it was in immediately after syncdb was -executed. This means that all data will be removed from the database, any +Returns the database to the state it was in immediately after :djadmin:`syncdb` +was executed. This means that all data will be removed from the database, any post-synchronization handlers will be re-executed, and the ``initial_data`` fixture will be re-installed. -- cgit v1.3 From 8a5d1a6b93e05546c5fdbfc497d7fb3a3377cf85 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 28 Jun 2012 10:49:07 +0200 Subject: Updated obsolete links in the documentation --- docs/howto/jython.txt | 2 +- docs/internals/committers.txt | 4 ++-- docs/internals/contributing/localizing.txt | 6 +++--- docs/internals/contributing/writing-code/unit-tests.txt | 2 +- docs/internals/contributing/writing-documentation.txt | 2 +- docs/intro/whatsnext.txt | 6 +++--- docs/ref/contrib/gis/geoquerysets.txt | 6 +++--- docs/ref/contrib/gis/geos.txt | 4 ++-- docs/ref/contrib/gis/install.txt | 16 ++++++++-------- docs/ref/contrib/gis/model-api.txt | 4 ++-- docs/ref/contrib/gis/sitemaps.txt | 9 ++++----- docs/ref/contrib/gis/tutorial.txt | 2 +- docs/ref/contrib/localflavor.txt | 6 +++--- docs/ref/contrib/sitemaps.txt | 2 +- docs/ref/databases.txt | 2 +- docs/ref/request-response.txt | 2 +- docs/ref/settings.txt | 2 +- docs/ref/templates/api.txt | 2 +- docs/ref/utils.txt | 6 +++--- docs/releases/1.0-beta-2.txt | 2 +- docs/releases/1.0.1.txt | 4 ++-- docs/releases/1.0.txt | 2 +- docs/releases/1.1.txt | 4 ++-- docs/releases/1.3.txt | 2 +- docs/topics/email.txt | 2 +- docs/topics/http/sessions.txt | 2 +- docs/topics/install.txt | 2 +- docs/topics/testing.txt | 2 +- 28 files changed, 53 insertions(+), 54 deletions(-) (limited to 'docs') diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 2cee4e6daa..762250212a 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -36,7 +36,7 @@ such as `Apache Tomcat`_. Full JavaEE applications servers such as `GlassFish`_ or `JBoss`_ are also OK, if you need the extra features they include. .. _`Apache Tomcat`: http://tomcat.apache.org/ -.. _GlassFish: https://glassfish.dev.java.net/ +.. _GlassFish: http://glassfish.java.net/ .. _JBoss: http://www.jboss.org/ Installing Django diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 972e506155..b0eed95571 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -146,7 +146,7 @@ Joseph Kocherhans Brian lives in Denver, Colorado, USA. -.. _brian rosner: http://oebfare.com/ +.. _brian rosner: http://brosner.com/ .. _eldarion: http://eldarion.com/ .. _django dose: http://djangodose.com/ @@ -162,7 +162,7 @@ Joseph Kocherhans Gary lives in Austin, Texas, USA. -.. _Gary Wilson: http://gdub.wordpress.com/ +.. _Gary Wilson: http://thegarywilson.com/ .. _The University of Texas: http://www.utexas.edu/ Justin Bronn diff --git a/docs/internals/contributing/localizing.txt b/docs/internals/contributing/localizing.txt index ff957c44a4..263087b5fa 100644 --- a/docs/internals/contributing/localizing.txt +++ b/docs/internals/contributing/localizing.txt @@ -60,7 +60,7 @@ Django source tree, as for any code change: * Open a ticket in Django's ticket system, set its ``Component`` field to ``Translations``, and attach the patch to it. -.. _Transifex: https://www.transifex.net/ +.. _Transifex: https://www.transifex.com/ .. _Django i18n mailing list: http://groups.google.com/group/django-i18n/ -.. _Django project page: https://www.transifex.net/projects/p/django/ -.. _Transifex User Guide: http://help.transifex.net/ +.. _Django project page: https://www.transifex.com/projects/p/django/ +.. _Transifex User Guide: http://help.transifex.com/ diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 9d6e76e453..4de506a654 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -159,7 +159,7 @@ associated tests will be skipped. .. _Textile: http://pypi.python.org/pypi/textile .. _docutils: http://pypi.python.org/pypi/docutils/0.4 .. _setuptools: http://pypi.python.org/pypi/setuptools/ -.. _memcached: http://www.danga.com/memcached/ +.. _memcached: http://memcached.org/ .. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html .. _selenium: http://pypi.python.org/pypi/selenium diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index e91d1291e9..c8d7039a68 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -22,7 +22,7 @@ Getting the raw documentation ----------------------------- Though Django's documentation is intended to be read as HTML at -http://docs.djangoproject.com/, we edit it as a collection of text files for +https://docs.djangoproject.com/, we edit it as a collection of text files for maximum flexibility. These files live in the top-level ``docs/`` directory of a Django release. diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index 6e0d97fefd..cc793c8129 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -108,7 +108,7 @@ On the Web ---------- The most recent version of the Django documentation lives at -http://docs.djangoproject.com/en/dev/. These HTML pages are generated +https://docs.djangoproject.com/en/dev/. These HTML pages are generated automatically from the text files in source control. That means they reflect the "latest and greatest" in Django -- they include the very latest corrections and additions, and they discuss the latest Django features, which may only be @@ -124,7 +124,7 @@ rather than asking broad tech-support questions. If you need help with your particular Django setup, try the `django-users mailing list`_ or the `#django IRC channel`_ instead. -.. _ticket system: https://code.djangoproject.com/simpleticket?component=Documentation +.. _ticket system: https://code.djangoproject.com/newticket?component=Documentation .. _django-users mailing list: http://groups.google.com/group/django-users .. _#django IRC channel: irc://irc.freenode.net/django @@ -228,4 +228,4 @@ We follow this policy: * The `main documentation Web page`_ includes links to documentation for all previous versions. -.. _main documentation Web page: http://docs.djangoproject.com/en/dev/ +.. _main documentation Web page: https://docs.djangoproject.com/en/dev/ diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index da00aa97f8..eeec2e2133 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -1026,7 +1026,7 @@ Keyword Argument Description representation -- the default value is 8. ===================== ===================================================== -__ http://code.google.com/apis/kml/documentation/ +__ https://developers.google.com/kml/documentation/ ``svg`` ~~~~~~~ @@ -1185,7 +1185,7 @@ Keyword Argument Description details. ===================== ===================================================== -__ http://download.oracle.com/docs/html/B14255_01/sdo_intro.htm#sthref150 +__ http://docs.oracle.com/html/B14255_01/sdo_intro.htm#sthref150 Aggregate Functions ------------------- @@ -1232,6 +1232,6 @@ Returns the same as the :meth:`GeoQuerySet.union` aggregate method. .. rubric:: Footnotes .. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL `_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model). -.. [#fnsdorelate] *See* `SDO_RELATE documentation `_, from Ch. 11 of the Oracle Spatial User's Guide and Manual. +.. [#fnsdorelate] *See* `SDO_RELATE documentation `_, from Ch. 11 of the Oracle Spatial User's Guide and Manual. .. [#fncovers] For an explanation of this routine, read `Quirks of the "Contains" Spatial Predicate `_ by Martin Davis (a PostGIS developer). .. [#fncontainsproperly] Refer to the PostGIS ``ST_ContainsProperly`` `documentation `_ for more details. diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 1b32265e55..eda9617381 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -324,7 +324,7 @@ and Z values that are a part of this geometry. Returns the Well-Known Text of the geometry (an OGC standard). -__ http://code.google.com/apis/kml/documentation/ +__ https://developers.google.com/kml/documentation/ Spatial Predicate Methods ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -891,7 +891,7 @@ Returns the WKT of the given geometry. Example:: .. rubric:: Footnotes -.. [#fnogc] *See* `PostGIS EWKB, EWKT and Canonical Forms `_, PostGIS documentation at Ch. 4.1.2. +.. [#fnogc] *See* `PostGIS EWKB, EWKT and Canonical Forms `_, PostGIS documentation at Ch. 4.1.2. .. [#fncascadedunion] For more information, read Paul Ramsey's blog post about `(Much) Faster Unions in PostGIS 1.4 `_ and Martin Davis' blog post on `Fast polygon merging in JTS using Cascaded Union `_. Settings diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 4b7ee89a52..00f8f8a370 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -102,7 +102,7 @@ Program Description Required .. _PROJ.4: http://trac.osgeo.org/proj/ __ http://postgis.refractions.net/ -__ http://www.gaia-gis.it/spatialite/index.html +__ http://www.gaia-gis.it/gaia-sins/ .. _build_from_source: @@ -233,7 +233,7 @@ installed prior to building PostGIS. The `psycopg2`_ module is required for use as the database adaptor when using GeoDjango with PostGIS. -.. _psycopg2: http://initd.org/projects/psycopg2 +.. _psycopg2: http://initd.org/psycopg/ First download the source archive, and extract:: @@ -376,7 +376,7 @@ SpatiaLite. After installation is complete, don't forget to read the post-installation docs on :ref:`create_spatialite_db`. -__ http://www.gaia-gis.it/spatialite/index.html +__ http://www.gaia-gis.it/gaia-sins/ .. _sqlite: @@ -457,7 +457,7 @@ Finally, do the same for the SpatiaLite tools:: $ ./configure --target=macosx -__ http://www.gaia-gis.it/spatialite/sources.html +__ http://www.gaia-gis.it/gaia-sins/libspatialite-sources/ .. _pysqlite2: @@ -664,7 +664,7 @@ community! You can: and specify the component as "GIS". __ http://groups.google.com/group/geodjango -__ https://code.djangoproject.com/simpleticket +__ https://code.djangoproject.com/newticket .. _libsettings: @@ -903,7 +903,7 @@ add the following to your ``settings.py``: SPATIALITE_LIBRARY_PATH='/Library/Frameworks/SQLite3.framework/SQLite3' -__ http://www.gaia-gis.it/spatialite/binaries.html +__ http://www.gaia-gis.it/spatialite-2.3.1/binaries.html .. _fink: @@ -1314,8 +1314,8 @@ may be executed from the SQL Shell as the ``postgres`` user:: .. rubric:: Footnotes .. [#] The datum shifting files are needed for converting data to and from certain projections. - For example, the PROJ.4 string for the `Google projection (900913) - `_ requires the + For example, the PROJ.4 string for the `Google projection (900913 or 3857) + `_ requires the ``null`` grid file only included in the extra datum shifting files. It is easier to install the shifting files now, then to have debug a problem caused by their absence later. diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index 462df50d64..8c5274e6d3 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -142,7 +142,7 @@ __ http://en.wikipedia.org/wiki/Geodesy __ http://en.wikipedia.org/wiki/Great_circle __ http://www.spatialreference.org/ref/epsg/2796/ __ http://spatialreference.org/ -__ http://welcome.warnercnr.colostate.edu/class_info/nr502/lg3/datums_coordinates/spcs.html +__ http://web.archive.org/web/20080302095452/http://welcome.warnercnr.colostate.edu/class_info/nr502/lg3/datums_coordinates/spcs.html ``spatial_index`` ----------------- @@ -252,7 +252,7 @@ for example:: qs = Address.objects.filter(zipcode__poly__contains='POINT(-104.590948 38.319914)') .. rubric:: Footnotes -.. [#fnogc] OpenGIS Consortium, Inc., `Simple Feature Specification For SQL `_, Document 99-049 (May 5, 1999). +.. [#fnogc] OpenGIS Consortium, Inc., `Simple Feature Specification For SQL `_. .. [#fnogcsrid] *See id.* at Ch. 2.3.8, p. 39 (Geometry Values and Spatial Reference Systems). .. [#fnsrid] Typically, SRID integer corresponds to an EPSG (`European Petroleum Survey Group `_) identifier. However, it may also be associated with custom projections defined in spatial database's spatial reference systems table. .. [#fnharvard] Harvard Graduate School of Design, `An Overview of Geodesy and Geographic Referencing Systems `_. This is an excellent resource for an overview of principles relating to geographic and Cartesian coordinate systems. diff --git a/docs/ref/contrib/gis/sitemaps.txt b/docs/ref/contrib/gis/sitemaps.txt index 75bddd3b86..0ab8f75825 100644 --- a/docs/ref/contrib/gis/sitemaps.txt +++ b/docs/ref/contrib/gis/sitemaps.txt @@ -2,10 +2,10 @@ Geographic Sitemaps =================== -Google's sitemap protocol has been recently extended to support geospatial -content. [#]_ This includes the addition of the ```` child element +Google's sitemap protocol used to include geospatial content support. [#]_ +This included the addition of the ```` child element ````, which tells Google that the content located at the URL is -geographic in nature. [#]_ +geographic in nature. This is now obsolete. Example ======= @@ -23,5 +23,4 @@ Reference ----------------- .. rubric:: Footnotes -.. [#] Google, Inc., `What is a Geo Sitemap? `_. -.. [#] Google, Inc., `Submit Your Geo Content to Google `_. +.. [#] Google, Inc., `What is a Geo Sitemap? `_. diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 395eac1821..3a63493137 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -784,4 +784,4 @@ option class in your ``admin.py`` file:: .. [#] Special thanks to Bjørn Sandvik of `thematicmapping.org `_ for providing and maintaining this data set. .. [#] GeoDjango basic apps was written by Dane Springmeyer, Josh Livni, and Christopher Schmidt. .. [#] Here the point is for the `University of Houston Law Center `_. -.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_, Document 99-049. +.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_. diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 61c8c7ae47..4595f51d9e 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -93,7 +93,7 @@ Here's an example of how to use them:: class MyForm(forms.Form): my_date_field = generic.forms.DateField() -.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm +.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes.htm .. _Argentina: `Argentina (ar)`_ .. _Australia: `Australia (au)`_ .. _Austria: `Austria (at)`_ @@ -158,7 +158,7 @@ any code you'd like to contribute. One thing we ask is that you please use Unicode objects (``u'mystring'``) for strings, rather than setting the encoding in the file. See any of the existing flavors for examples. -.. _create a ticket: https://code.djangoproject.com/simpleticket +.. _create a ticket: https://code.djangoproject.com/newticket Localflavor and backwards compatibility ======================================= @@ -713,7 +713,7 @@ Italy (``it``) A form field that validates input as an Italian social security number (`codice fiscale`_). -.. _codice fiscale: http://www.agenziaentrate.it/ilwwcm/connect/Nsi/Servizi/Codice+fiscale+-+tessera+sanitaria/NSI+Informazioni+sulla+codificazione+delle+persone+fisiche +.. _codice fiscale: http://www.agenziaentrate.gov.it/wps/content/Nsilib/Nsi/Home/CosaDeviFare/Richiedere/Codice+fiscale+e+tessera+sanitaria/Richiesta+TS_CF/SchedaI/Informazioni+codificazione+pf/ .. class:: it.forms.ITVatNumberField diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index d775092eae..2393a4a9a3 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -410,7 +410,7 @@ generate a Google News compatible sitemap: {% endspaceless %} -.. _`Google news sitemaps`: http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=74288 +.. _`Google news sitemaps`: http://support.google.com/webmasters/bin/answer.py?hl=en&answer=74288 Pinging Google ============== diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 600de8ed3a..ff7a349eb5 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -31,7 +31,7 @@ attempt to use the ``StdDev(sample=False)`` or ``Variance(sample=False)`` aggregate with a database backend that falls within the affected release range. .. _known to be faulty: http://archives.postgresql.org/pgsql-bugs/2007-07/msg00046.php -.. _Release 8.2.5: http://developer.postgresql.org/pgdocs/postgres/release-8-2-5.html +.. _Release 8.2.5: http://www.postgresql.org/docs/devel/static/release-8-2-5.html Optimizing PostgreSQL's configuration ------------------------------------- diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index d2b6f35b84..a29ddc63cc 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -684,7 +684,7 @@ Methods risk of client side script accessing the protected cookie data. - .. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly + .. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. method:: HttpResponse.set_signed_cookie(key, value='', salt='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=True) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 627aa5007f..72d60453c3 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1675,7 +1675,7 @@ consistently by all browsers. However, when it is honored, it can be a useful way to mitigate the risk of client side script accessing the protected cookie data. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. versionchanged:: 1.4 The default value of the setting was changed from ``False`` to ``True``. diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 7816e1d07d..ec01fe2faa 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -774,7 +774,7 @@ Using an alternative template language The Django ``Template`` and ``Loader`` classes implement a simple API for loading and rendering templates. By providing some simple wrapper classes that implement this API we can use third party template systems like `Jinja2 -`_ or `Cheetah `_. This +`_ or `Cheetah `_. This allows us to use third-party template libraries without giving up useful Django features like the Django ``Context`` object and handy shortcuts like :func:`~django.shortcuts.render_to_response()`. diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index b323f0629f..0974409453 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -246,13 +246,13 @@ For simplifying the selection of a generator use ``feedgenerator.DefaultFeed`` which is currently ``Rss201rev2Feed`` For definitions of the different versions of RSS, see: -http://diveintomark.org/archives/2004/02/04/incompatible-rss +http://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/02/04/incompatible-rss .. function:: get_tag_uri(url, date) Creates a TagURI. - See http://diveintomark.org/archives/2004/05/28/howto-atom-id + See http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id SyndicationFeed --------------- @@ -330,7 +330,7 @@ Rss201rev2Feed .. class:: Rss201rev2Feed(RssFeed) - Spec: http://blogs.law.harvard.edu/tech/rss + Spec: http://cyber.law.harvard.edu/rss/rss.html RssUserland091Feed ------------------ diff --git a/docs/releases/1.0-beta-2.txt b/docs/releases/1.0-beta-2.txt index 2a2554432e..288ac8fbc1 100644 --- a/docs/releases/1.0-beta-2.txt +++ b/docs/releases/1.0-beta-2.txt @@ -46,7 +46,7 @@ Refactored documentation documentation files bundled with Django. .. _Sphinx: http://sphinx.pocoo.org/ -.. _online: http://docs.djangoproject.com/en/dev/ +.. _online: https://docs.djangoproject.com/ Along with these new features, the Django team has also been hard at work polishing Django's codebase for the final 1.0 release; this beta diff --git a/docs/releases/1.0.1.txt b/docs/releases/1.0.1.txt index 7901b8e219..d6d21afc01 100644 --- a/docs/releases/1.0.1.txt +++ b/docs/releases/1.0.1.txt @@ -18,7 +18,7 @@ Fixes and improvements in Django 1.0.1 Django 1.0.1 contains over two hundred fixes to the original Django 1.0 codebase; full details of every fix are available in `the -Subversion log of the 1.0.X branch`_, but here are some of the +history of the 1.0.X branch`_, but here are some of the highlights: * Several fixes in ``django.contrib.comments``, pertaining to RSS @@ -61,4 +61,4 @@ highlights: documentation, including both corrections to existing documents and expanded and new documentation. -.. _the Subversion log of the 1.0.X branch: https://code.djangoproject.com/log/django/branches/releases/1.0.X +.. _the history of the 1.0.X branch: https://github.com/django/django/commits/stable/1.0.x diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index e752b02e1b..1e44f57de8 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -57,7 +57,7 @@ there. In fact, new documentation is one of our favorite features of Django 1.0, so we might as well start there. First, there's a new documentation site: -* http://docs.djangoproject.com/ +* https://docs.djangoproject.com/ The documentation has been greatly improved, cleaned up, and generally made awesome. There's now dedicated search, indexes, and more. diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 12940114ed..852644dee4 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -101,7 +101,7 @@ If you've been relying on this middleware, the easiest upgrade path is: * Introduce your modified version of ``SetRemoteAddrFromForwardedFor`` as a piece of middleware in your own project. -__ https://code.djangoproject.com/browser/django/trunk/django/middleware/http.py?rev=11000#L33 +__ https://github.com/django/django/blob/91f18400cc0fb37659e2dbaab5484ff2081f1f30/django/middleware/http.py#L33 Names of uploaded files are available later ------------------------------------------- @@ -366,7 +366,7 @@ features: For more details, see the `GeoDjango documentation`_. .. _geodjango: http://geodjango.org/ -.. _spatialite: http://www.gaia-gis.it/spatialite/ +.. _spatialite: http://www.gaia-gis.it/gaia-sins/ .. _geodjango documentation: http://geodjango.org/docs/ Other improvements diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index a2b5447476..772dbdb2e7 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -329,7 +329,7 @@ requests. These include: * Support for combining :ref:`F() expressions ` with timedelta values when retrieving or updating database values. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. _backwards-incompatible-changes-1.3: diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 3a78a83ce5..0cc476e02c 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -192,7 +192,7 @@ from the request's POST data, sends that to admin@example.com and redirects to # to get proper validation errors. return HttpResponse('Make sure all fields are entered and valid.') -.. _Header injection: http://www.nyphp.org/phundamentals/email_header_injection.php +.. _Header injection: http://www.nyphp.org/phundamentals/8_Preventing-Email-Header-Injection .. _emailmessage-and-smtpconnection: diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 20dc61a222..a32d9b54dd 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -521,7 +521,7 @@ consistently by all browsers. However, when it is honored, it can be a useful way to mitigate the risk of client side script accessing the protected cookie data. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly SESSION_COOKIE_NAME ------------------- diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 0cdb8a49e7..4b0aca3548 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -70,7 +70,7 @@ server platforms. See the `server-arrangements wiki page`_ for specific installation instructions for each platform. .. _Apache: http://httpd.apache.org/ -.. _nginx: http://nginx.net/ +.. _nginx: http://nginx.org/ .. _mod_wsgi: http://code.google.com/p/modwsgi/ .. _server-arrangements wiki page: https://code.djangoproject.com/wiki/ServerArrangements diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index f7aadd68f3..aa274d83c9 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1940,7 +1940,7 @@ out the `full reference`_ for more details. .. _Selenium: http://seleniumhq.org/ .. _selenium package: http://pypi.python.org/pypi/selenium -.. _full reference: http://readthedocs.org/docs/selenium-python/en/latest/api.html +.. _full reference: http://selenium-python.readthedocs.org/en/latest/api.html .. _Firefox: http://www.mozilla.com/firefox/ .. note:: -- cgit v1.3 From 4f53e77f0720bd4d74b1b08857db8f7d32c65008 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 28 Jun 2012 13:42:36 +0200 Subject: Fixed #18306 -- Removed most of GeoDjango-specific deployment docs --- docs/ref/contrib/gis/deployment.txt | 59 ++++--------------------------------- 1 file changed, 6 insertions(+), 53 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/gis/deployment.txt b/docs/ref/contrib/gis/deployment.txt index d98fc51837..f90c9c2e91 100644 --- a/docs/ref/contrib/gis/deployment.txt +++ b/docs/ref/contrib/gis/deployment.txt @@ -2,6 +2,10 @@ Deploying GeoDjango =================== +Basically, the deployment of a GeoDjango application is not different from +the deployment of a normal Django application. Please consult Django's +:doc:`deployment documentation `. + .. warning:: GeoDjango uses the GDAL geospatial library which is @@ -10,58 +14,7 @@ Deploying GeoDjango appropriate configuration of Apache or the prefork method when using FastCGI through another Web server. -Apache -====== -In this section there are some example ``VirtualHost`` directives for -when deploying using ``mod_wsgi``, which is now officially the recommended -way to deploy Django applications with Apache. -As long as ``mod_wsgi`` is configured correctly, it does not -matter whether the version of Apache is prefork or worker. - -.. note:: - - The ``Alias`` and ``Directory`` configurations in the examples - below use an example path to a system-wide installation folder of Django. - Substitute in an appropriate location, if necessary, as it may be - different than the path on your system. - -``mod_wsgi`` ------------- - -Example:: - - - WSGIDaemonProcess geodjango user=geo group=geo processes=5 threads=1 - WSGIProcessGroup geodjango - WSGIScriptAlias / /home/geo/geodjango/world.wsgi - - Alias /media/ "/usr/lib/python2.6/site-packages/django/contrib/admin/media/" - - Order allow,deny - Options Indexes - Allow from all - IndexOptions FancyIndexing - - - - -.. warning:: - - If the ``WSGIDaemonProcess`` attribute ``threads`` is not set to ``1``, then + For example, when configuring your application with ``mod_wsgi``, + set the ``WSGIDaemonProcess`` attribute ``threads`` to ``1``, unless Apache may crash when running your GeoDjango application. Increase the number of ``processes`` instead. - -For more information, please consult Django's -:doc:`mod_wsgi documentation `. - -Lighttpd -======== - -FastCGI -------- - -Nginx -===== - -FastCGI -------- -- cgit v1.3 From 54b1519dfd90ea042105fc42aa439763c8df93d4 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 28 Jun 2012 17:18:50 +0200 Subject: Fixed a formatting issue in the CBV docs. --- docs/ref/class-based-views/generic-display.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'docs') diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index 8ec66a8cc1..b90cbf95b2 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -76,11 +76,11 @@ many projects they are typically the most commonly used views. **Method Flowchart** - 1. :meth:`dispatch():` - 2. :meth:`http_method_not_allowed():` - 3. :meth:`get_template_names():` - 4. :meth:`get_queryset():` - 5. :meth:`get_objects():` - 6. :meth:`get_context_data():` - 7. :meth:`get():` - 8. :meth:`render_to_response():` + 1. :meth:`dispatch()` + 2. :meth:`http_method_not_allowed()` + 3. :meth:`get_template_names()` + 4. :meth:`get_queryset()` + 5. :meth:`get_objects()` + 6. :meth:`get_context_data()` + 7. :meth:`get()` + 8. :meth:`render_to_response()` -- cgit v1.3 From b9ecbedb31375a887d100ccf550a091c8d5d4fd7 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 29 Jun 2012 15:08:30 +0200 Subject: Fixed #18528 -- Fixed custom field value_to_string example Thanks anuraguniyal for the report. --- docs/howto/custom-model-fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 1377a62c89..99c5904fe9 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -666,7 +666,7 @@ data storage anyway, we can reuse some existing conversion code:: def value_to_string(self, obj): value = self._get_val_from_obj(obj) - return self.get_db_prep_value(value) + return self.get_prep_value(value) Some general advice -------------------- -- cgit v1.3 From 596e15293c9f522a2b001d49a0c40005711682a6 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 14:30:32 +0200 Subject: Fixed #11162 -- Mentioned ValidationError in custom model field docs --- docs/howto/custom-model-fields.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 99c5904fe9..8b4c2303ab 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -434,10 +434,14 @@ database, so we need to be able to process strings and ``Hand`` instances in p1 = re.compile('.{26}') p2 = re.compile('..') args = [p2.findall(x) for x in p1.findall(value)] + if len(args) != 4: + raise ValidationError("Invalid input for a Hand instance") return Hand(*args) Notice that we always return a ``Hand`` instance from this method. That's the -Python object type we want to store in the model's attribute. +Python object type we want to store in the model's attribute. If anything is +going wrong during value conversion, you should return a +:exc:`~django.core.exceptions.ValidationError` exception. **Remember:** If your custom field needs the :meth:`to_python` method to be called when it is created, you should be using `The SubfieldBase metaclass`_ -- cgit v1.3 From db87016b1a92b790c4250a2b5c16ceadd737a00e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 15:06:42 +0200 Subject: Fixed #12493 -- Deprecated auto-correction of TEMPLATE_DIRS --- django/conf/__init__.py | 3 +++ docs/internals/deprecation.txt | 4 ++++ 2 files changed, 7 insertions(+) (limited to 'docs') diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 531eed8658..5e4b412f32 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -103,6 +103,9 @@ class Settings(BaseSettings): setting_value = getattr(mod, setting) if setting in tuple_settings and \ isinstance(setting_value, basestring): + warnings.warn("The %s setting must be a tuple. Please fix your " + "settings, as auto-correction is now deprecated." % setting, + PendingDeprecationWarning) setting_value = (setting_value,) # In case the user forgot the comma. setattr(self, setting, setting_value) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 342ef5266a..f338adac33 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -276,6 +276,10 @@ these changes. * The function ``django.utils.itercompat.product`` will be removed. The Python builtin version should be used instead. +* Auto-correction of INSTALLED_APPS and TEMPLATE_DIRS settings when they are + specified as a plain string instead of a tuple will be removed and raise an + exception. + 2.0 --- -- cgit v1.3 From 5d81ad1af14ee3127b53f923687ed5c4e00329ee Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 30 Jun 2012 10:25:51 -0400 Subject: Fixed #17168 - Noted TransactionMiddleware only works with "default" database alias. Thanks codeinthehole for the draft patch. --- docs/topics/db/transactions.txt | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'docs') diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 76b65b99e0..1f15949000 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -56,6 +56,13 @@ database cursor (which is mapped to its own database connection internally). .. _transaction-management-functions: +.. note:: + + The ``TransactionMiddleware`` only affects the database aliased + as "default" within your :setting:`DATABASES` setting. If you are using + multiple databases and want transaction control over databases other than + "default", you will need to write your own transaction middleware. + Controlling transaction management in views =========================================== -- cgit v1.3 From c446bdee84efe42f5c0bbfee16986a80c83ec9a2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 20:50:24 +0200 Subject: Fixed #17024 -- Added import statements in tutorial code sample --- docs/intro/tutorial02.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 0d95f6ff37..dd315ed8a7 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -113,8 +113,8 @@ Just one thing to do: We need to tell the admin that ``Poll`` objects have an admin interface. To do this, create a file called ``admin.py`` in your ``polls`` directory, and edit it to look like this:: - from polls.models import Poll from django.contrib import admin + from polls.models import Poll admin.site.register(Poll) @@ -283,6 +283,9 @@ It'd be better if you could add a bunch of Choices directly when you create the Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Poll`` registration code to read:: + from django.contrib import admin + from polls.models import Poll + class ChoiceInline(admin.StackedInline): model = Choice extra = 3 -- cgit v1.3 From da200c5e35cdbb617572977bcbf97fae33920b2e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 21:25:16 +0200 Subject: Fixed #16519 -- Deprecated mimetype kwarg of HttpResponse __init__ This keyword was already deprecated in the code (supported for backwards compatibility only), but never formally deprecated. Thanks Paul McMillan for the report and yasar11732 for the initial patch. --- django/http/__init__.py | 8 +++++--- django/template/response.py | 14 +++++++------- docs/internals/deprecation.txt | 3 +++ docs/ref/request-response.txt | 21 ++++++++++----------- 4 files changed, 25 insertions(+), 21 deletions(-) (limited to 'docs') diff --git a/django/http/__init__.py b/django/http/__init__.py index 30d7e5dc2f..51e6ca2304 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -524,14 +524,16 @@ class HttpResponse(object): status_code = 200 - def __init__(self, content='', mimetype=None, status=None, - content_type=None): + def __init__(self, content='', content_type=None, status=None, + mimetype=None): # _headers is a mapping of the lower-case name to the original case of # the header (required for working with legacy systems) and the header # value. Both the name of the header and its value are ASCII strings. self._headers = {} self._charset = settings.DEFAULT_CHARSET - if mimetype: # For backwards compatibility. + if mimetype: + warnings.warn("Using mimetype keyword argument is deprecated, use" + " content_type instead", PendingDeprecationWarning) content_type = mimetype if not content_type: content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, diff --git a/django/template/response.py b/django/template/response.py index bb0b9cbf9d..286417ccdf 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -9,8 +9,8 @@ class ContentNotRenderedError(Exception): class SimpleTemplateResponse(HttpResponse): rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks'] - def __init__(self, template, context=None, mimetype=None, status=None, - content_type=None): + def __init__(self, template, context=None, content_type=None, status=None, + mimetype=None): # It would seem obvious to call these next two members 'template' and # 'context', but those names are reserved as part of the test Client # API. To avoid the name collision, we use tricky-to-debug problems @@ -22,8 +22,8 @@ class SimpleTemplateResponse(HttpResponse): # content argument doesn't make sense here because it will be replaced # with rendered template so we always pass empty string in order to # prevent errors and provide shorter signature. - super(SimpleTemplateResponse, self).__init__('', mimetype, status, - content_type) + super(SimpleTemplateResponse, self).__init__('', content_type, status, + mimetype) # _is_rendered tracks whether the template and context has been baked # into a final response. @@ -137,8 +137,8 @@ class TemplateResponse(SimpleTemplateResponse): rendering_attrs = SimpleTemplateResponse.rendering_attrs + \ ['_request', '_current_app'] - def __init__(self, request, template, context=None, mimetype=None, - status=None, content_type=None, current_app=None): + def __init__(self, request, template, context=None, content_type=None, + status=None, mimetype=None, current_app=None): # self.request gets over-written by django.test.client.Client - and # unlike context_data and template_name the _request should not # be considered part of the public API. @@ -147,7 +147,7 @@ class TemplateResponse(SimpleTemplateResponse): # having to avoid needing to create the RequestContext directly self._current_app = current_app super(TemplateResponse, self).__init__( - template, context, mimetype, status, content_type) + template, context, content_type, status, mimetype) def resolve_context(self, context): """Convert context data into a full RequestContext object diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index f338adac33..9359c82e46 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -280,6 +280,9 @@ these changes. specified as a plain string instead of a tuple will be removed and raise an exception. +* The ``mimetype`` argument to :class:`~django.http.HttpResponse` ``__init__`` + will be removed (``content_type`` should be used instead). + 2.0 --- diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index a29ddc63cc..551ee00c6b 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -606,11 +606,10 @@ Attributes Methods ------- -.. method:: HttpResponse.__init__(content='', mimetype=None, status=200, content_type=DEFAULT_CONTENT_TYPE) +.. method:: HttpResponse.__init__(content='', content_type=None, status=200) - Instantiates an ``HttpResponse`` object with the given page content (a - string) and MIME type. The :setting:`DEFAULT_CONTENT_TYPE` is - ``'text/html'``. + Instantiates an ``HttpResponse`` object with the given page content and + content type. ``content`` should be an iterator or a string. If it's an iterator, it should return strings, and those strings will be @@ -618,15 +617,15 @@ Methods an iterator or a string, it will be converted to a string when accessed. + ``content_type`` is the MIME type optionally completed by a character set + encoding and is used to fill the HTTP ``Content-Type`` header. If not + specified, it is formed by the :setting:`DEFAULT_CONTENT_TYPE` and + :setting:`DEFAULT_CHARSET` settings, by default: "`text/html; charset=utf-8`". + + Historically, this parameter was called ``mimetype`` (now deprecated). + ``status`` is the `HTTP Status code`_ for the response. - ``content_type`` is an alias for ``mimetype``. Historically, this parameter - was only called ``mimetype``, but since this is actually the value included - in the HTTP ``Content-Type`` header, it can also include the character set - encoding, which makes it more than just a MIME type specification. - If ``mimetype`` is specified (not ``None``), that value is used. - Otherwise, ``content_type`` is used. If neither is given, the - :setting:`DEFAULT_CONTENT_TYPE` setting is used. .. method:: HttpResponse.__setitem__(header, value) -- cgit v1.3 From 03896eb5df74a47237ec3ed9a73aadc925e90dbb Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 30 Jun 2012 22:21:23 +0200 Subject: Fixed #17026 -- Improved wording of contrib.messages' storage backends section --- docs/ref/contrib/messages.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index da6336e832..6929a3b0d0 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -54,7 +54,8 @@ Storage backends ---------------- The messages framework can use different backends to store temporary messages. -To change which backend is being used, add a `MESSAGE_STORAGE`_ to your +If the default FallbackStorage isn't suitable to your needs, you can change +which backend is being used by adding a `MESSAGE_STORAGE`_ to your settings, referencing the module and class of the storage class. For example:: @@ -62,7 +63,7 @@ example:: The value should be the full path of the desired storage class. -Four storage classes are included: +Three storage classes are available: ``'django.contrib.messages.storage.session.SessionStorage'`` This class stores all messages inside of the request's session. It @@ -74,6 +75,8 @@ Four storage classes are included: messages are dropped if the cookie data size would exceed 4096 bytes. ``'django.contrib.messages.storage.fallback.FallbackStorage'`` + This is the default storage class. + This class first uses CookieStorage for all messages, falling back to using SessionStorage for the messages that could not fit in a single cookie. -- cgit v1.3 From 55ffcf8e7b414a39e2dfc9c9eb4c5d3fa548e78e Mon Sep 17 00:00:00 2001 From: Raúl Cumplido Date: Thu, 7 Jun 2012 13:46:06 +0200 Subject: Fixed #18145 -- Improved documentation of unique_together type fields --- docs/ref/models/options.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 6ca3d3b2d0..9d076f6274 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -244,12 +244,12 @@ Django quotes column and table names behind the scenes. unique_together = (("driver", "restaurant"),) - This is a list of lists of fields that must be unique when considered together. + This is a tuple of tuples that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate ``UNIQUE`` statements are included in the ``CREATE TABLE`` statement). - For convenience, unique_together can be a single list when dealing with a single + For convenience, unique_together can be a single tuple when dealing with a single set of fields:: unique_together = ("driver", "restaurant") -- cgit v1.3 From c5fb8299ef9658a58221d4894dadd080cc25962b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 30 Jun 2012 18:20:34 -0400 Subject: Fixed #17705 - Updated TabularInline image and doc in tutorial 2. Thanks xbito for the draft patch. --- docs/intro/_images/admin12t.png | Bin 0 -> 17414 bytes docs/intro/tutorial02.txt | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/intro/_images/admin12t.png (limited to 'docs') diff --git a/docs/intro/_images/admin12t.png b/docs/intro/_images/admin12t.png new file mode 100644 index 0000000000..14c5c31a4f Binary files /dev/null and b/docs/intro/_images/admin12t.png differ diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index dd315ed8a7..27dc82aa96 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -322,9 +322,12 @@ the ``ChoiceInline`` declaration to read:: With that ``TabularInline`` (instead of ``StackedInline``), the related objects are displayed in a more compact, table-based format: -.. image:: _images/admin12.png +.. image:: _images/admin12t.png :alt: Add poll page now has more compact choices +Note that there is an extra "Delete?" column that allows removing rows added +using the "Add Another Choice" button and rows that have already been saved. + Customize the admin change list =============================== -- cgit v1.3 From c68f4c514c7b1102772b6ea11e9e59c7c87f7fae Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Jul 2012 06:55:46 -0400 Subject: Fixed #18493 - Added instructions to locate the Django source files to the t Thanks Claude Paroz for the draft patch. --- docs/intro/tutorial02.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'docs') diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 27dc82aa96..16682c67c3 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -448,6 +448,19 @@ above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to ``/home/my_username/mytemplates/admin/base_site.html``. Don't forget that ``admin`` subdirectory. +.. admonition:: Where are the Django source files? + + If you have difficulty finding where the Django source files are located + on your system, run the following command: + + .. code-block:: bash + + python -c " + import sys + sys.path = sys.path[1:] + import django + print(django.__path__)" + Then, just edit the file and replace the generic Django text with your own site's name as you see fit. -- cgit v1.3 From 9974069620b0f0e6ac2f1e9bd64819ae0a0e623b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Jul 2012 07:25:24 -0400 Subject: Fixed #16882 - Clarified why one should not use 'init_command' after initial database creation. --- docs/ref/databases.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index ff7a349eb5..1f4d09f6cb 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -335,7 +335,9 @@ storage engine, you have a couple of options. } This sets the default storage engine upon connecting to the database. - After your tables have been created, you should remove this option. + After your tables have been created, you should remove this option as it + adds a query that is only needed during table creation to each database + connection. * Another method for changing the storage engine is described in AlterModelOnSyncDB_. -- cgit v1.3 From f572ee0c65ec5eac9edb0cb3e35c96ec86d89ffb Mon Sep 17 00:00:00 2001 From: Anssi Kääriäinen Date: Sat, 26 May 2012 23:19:13 +0300 Subject: Fixed #16047 -- Restore autocommit state correctly on psycopg2 When the postgresql_psycopg2 backend was used with DB-level autocommit mode enabled, after entering transaction management and then leaving it, the isolation level was never set back to autocommit mode. Thanks brodie for report and working on this issue. --- django/db/backends/__init__.py | 14 +++-- docs/releases/1.5.txt | 9 ++++ .../regressiontests/transactions_regress/tests.py | 60 +++++++++++++++++++++- 3 files changed, 76 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index dab9b7e213..05ef7bf62a 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -109,16 +109,18 @@ class BaseDatabaseWrapper(object): over to the surrounding block, as a commit will commit all changes, even those from outside. (Commits are on connection level.) """ - self._leave_transaction_management(self.is_managed()) if self.transaction_state: del self.transaction_state[-1] else: - raise TransactionManagementError("This code isn't under transaction " - "management") + raise TransactionManagementError( + "This code isn't under transaction management") + # We will pass the next status (after leaving the previous state + # behind) to subclass hook. + self._leave_transaction_management(self.is_managed()) if self._dirty: self.rollback() - raise TransactionManagementError("Transaction managed block ended with " - "pending COMMIT/ROLLBACK") + raise TransactionManagementError( + "Transaction managed block ended with pending COMMIT/ROLLBACK") self._dirty = False def validate_thread_sharing(self): @@ -176,6 +178,8 @@ class BaseDatabaseWrapper(object): """ if self.transaction_state: return self.transaction_state[-1] + # Note that this setting isn't documented, and is only used here, and + # in enter_transaction_management() return settings.TRANSACTIONS_MANAGED def managed(self, flag=True): diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 2f20f5f9f9..4544be0eac 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -168,6 +168,15 @@ number was inside the existing page range. It does check it now and raises an :exc:`InvalidPage` exception when the number is either too low or too high. +Behavior of autocommit database option on PostgreSQL changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PostgreSQL's autocommit option didn't work as advertised previously. It did +work for single transaction block, but after the first block was left the +autocommit behavior was never restored. This bug is now fixed in 1.5. While +this is only a bug fix, it is worth checking your applications behavior if +you are using PostgreSQL together with the autocommit option. + Features deprecated in 1.5 ========================== diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/regressiontests/transactions_regress/tests.py index abd7a4ceaa..fb26138eed 100644 --- a/tests/regressiontests/transactions_regress/tests.py +++ b/tests/regressiontests/transactions_regress/tests.py @@ -1,11 +1,11 @@ from __future__ import absolute_import from django.core.exceptions import ImproperlyConfigured -from django.db import connection, transaction +from django.db import connection, connections, transaction, DEFAULT_DB_ALIAS from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError from django.test import TransactionTestCase, skipUnlessDBFeature from django.test.utils import override_settings -from django.utils.unittest import skipIf +from django.utils.unittest import skipIf, skipUnless from .models import Mod, M2mA, M2mB @@ -175,6 +175,62 @@ class TestTransactionClosing(TransactionTestCase): self.test_failing_query_transaction_closed() +class TestPostgresAutocommit(TransactionTestCase): + """ + Tests to make sure psycopg2's autocommit mode is restored after entering + and leaving transaction management. Refs #16047. + """ + def setUp(self): + from psycopg2.extensions import (ISOLATION_LEVEL_AUTOCOMMIT, + ISOLATION_LEVEL_READ_COMMITTED) + self._autocommit = ISOLATION_LEVEL_AUTOCOMMIT + self._read_committed = ISOLATION_LEVEL_READ_COMMITTED + + # We want a clean backend with autocommit = True, so + # first we need to do a bit of work to have that. + self._old_backend = connections[DEFAULT_DB_ALIAS] + settings = self._old_backend.settings_dict.copy() + opts = settings['OPTIONS'].copy() + opts['autocommit'] = True + settings['OPTIONS'] = opts + new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS) + connections[DEFAULT_DB_ALIAS] = new_backend + + def test_initial_autocommit_state(self): + self.assertTrue(connection.features.uses_autocommit) + self.assertEqual(connection.isolation_level, self._autocommit) + + def test_transaction_management(self): + transaction.enter_transaction_management() + transaction.managed(True) + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.leave_transaction_management() + self.assertEqual(connection.isolation_level, self._autocommit) + + def test_transaction_stacking(self): + transaction.enter_transaction_management() + transaction.managed(True) + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.enter_transaction_management() + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.leave_transaction_management() + self.assertEqual(connection.isolation_level, self._read_committed) + + transaction.leave_transaction_management() + self.assertEqual(connection.isolation_level, self._autocommit) + + def tearDown(self): + connections[DEFAULT_DB_ALIAS] = self._old_backend + +TestPostgresAutocommit = skipUnless(connection.vendor == 'postgresql', + "This test only valid for PostgreSQL")(TestPostgresAutocommit) +TestPostgresAutoCommit = skipUnlessDBFeature('supports_transactions')( + TestPostgresAutocommit) + + class TestManyToManyAddTransaction(TransactionTestCase): def test_manyrelated_add_commit(self): "Test for https://code.djangoproject.com/ticket/16818" -- cgit v1.3 From 7313468f85c3ade1275fc7fef193a4429f4af4bf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Jul 2012 18:04:16 -0400 Subject: Fixed #17436 - Added warning about overriding Model.__init__() Thanks zsiciarz for the draft patch. --- docs/ref/models/instances.txt | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 3ae994bc5b..509ea9d30e 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -25,6 +25,41 @@ The keyword arguments are simply the names of the fields you've defined on your model. Note that instantiating a model in no way touches your database; for that, you need to :meth:`~Model.save()`. +.. note:: + + You may be tempted to customize the model by overriding the ``__init__`` + method. If you do so, however, take care not to change the calling + signature as any change may prevent the model instance from being saved. + Rather than overriding ``__init__``, try using one of these approaches: + + 1. Add a classmethod on the model class:: + + class Book(models.Model): + title = models.CharField(max_length=100) + + @classmethod + def create(cls, title): + book = cls(title=title) + # do something with the book + return book + + book = Book.create("Pride and Prejudice") + + 2. Add a method on a custom manager (usually preferred):: + + class BookManager(models.Manager): + def create_book(title): + book = self.create(title=title) + # do something with the book + return book + + class Book(models.Model): + title = models.CharField(max_length=100) + + objects = BookManager() + + book = Book.objects.create_book("Pride and Prejudice") + .. _validating-objects: Validating objects @@ -604,4 +639,3 @@ described in :ref:`Field lookups `. Note that in the case of identical date values, these methods will use the primary key as a tie-breaker. This guarantees that no records are skipped or duplicated. That also means you cannot use those methods on unsaved objects. - -- cgit v1.3 From 784d0c261c76535dc760bc8d76793d92f35c1513 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 2 Jul 2012 10:16:42 +0200 Subject: Replaced 'return' by 'raise' in custom model field docs Thanks Simon Charette for noticing it. Refs #11162. --- docs/howto/custom-model-fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 8b4c2303ab..706cc25129 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -440,7 +440,7 @@ database, so we need to be able to process strings and ``Hand`` instances in Notice that we always return a ``Hand`` instance from this method. That's the Python object type we want to store in the model's attribute. If anything is -going wrong during value conversion, you should return a +going wrong during value conversion, you should raise a :exc:`~django.core.exceptions.ValidationError` exception. **Remember:** If your custom field needs the :meth:`to_python` method to be -- cgit v1.3 From f33e15036907d6e4bda6116dc84097e9e590d2c8 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sat, 30 Jun 2012 16:41:51 +0100 Subject: Documented utils.html.escape and conditional_escape --- django/utils/html.py | 14 +++++++------- docs/ref/utils.txt | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/django/utils/html.py b/django/utils/html.py index 014d837bbb..fe2e6b7a29 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -31,11 +31,11 @@ hard_coded_bullets_re = re.compile(r'((?:

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

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

(?: |\s|
)*?

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

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

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

    and
    s.""" value = normalize_newlines(value) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 549812296b..c74392df36 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -410,6 +410,45 @@ escaping HTML. Similar to ``escape()``, except that it doesn't operate on pre-escaped strings, so it will not double escape. +.. function:: format_html(format_string, *args, **kwargs) + + This is similar to `str.format`_, except that it is appropriate for + building up HTML fragments. All args and kwargs are passed through + :func:`conditional_escape` before being passed to ``str.format``. + + For the case of building up small HTML fragments, this function is to be + preferred over string interpolation using ``%`` or ``str.format`` directly, + because it applies escaping to all arguments - just like the Template system + applies escaping by default. + + So, instead of writing: + + .. code-block:: python + + mark_safe(u"%s %s %s" % (some_html, + escape(some_text), + escape(some_other_text), + )) + + you should instead use: + + .. code-block:: python + + format_html(u"%{0} {1} {2}", + mark_safe(some_html), some_text, some_other_text) + + This has the advantage that you don't need to apply :func:`escape` to each + argument and risk a bug and an XSS vulnerability if you forget one. + + Note that although this function uses ``str.format`` to do the + interpolation, some of the formatting options provided by `str.format`_ + (e.g. number formatting) will not work, since all arguments are passed + through :func:`conditional_escape` which (ultimately) calls + :func:`~django.utils.encoding.force_unicode` on the values. + + +.. _str.format: http://docs.python.org/library/stdtypes.html#str.format + ``django.utils.http`` ===================== diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py index 434873b9e0..389ae8ec75 100644 --- a/tests/regressiontests/utils/html.py +++ b/tests/regressiontests/utils/html.py @@ -34,6 +34,17 @@ class TestUtilsHtml(unittest.TestCase): # Verify it doesn't double replace &. self.check_output(f, '<&', '<&') + def test_format_html(self): + self.assertEqual( + html.format_html(u"{0} {1} {third} {fourth}", + u"< Dangerous >", + html.mark_safe(u"safe"), + third="< dangerous again", + fourth=html.mark_safe(u"safe again") + ), + u"< Dangerous > safe < dangerous again safe again" + ) + def test_linebreaks(self): f = html.linebreaks items = ( -- cgit v1.3 From 3caf53524c5285f1a057e00fc6549cde0ca67dda Mon Sep 17 00:00:00 2001 From: Renato Pedigoni Date: Wed, 4 Jul 2012 18:34:55 -0300 Subject: fixed typo --- docs/releases/1.5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 4544be0eac..944f19f03b 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -70,7 +70,7 @@ To make it easier to deal with javascript templates which collide with Django's syntax, you can now use the :ttag:`verbatim` block tag to avoid parsing the tag's content. -Retreival of ``ContentType`` instances associated with proxy models +Retrieval of ``ContentType`` instances associated with proxy models ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The methods :meth:`ContentTypeManager.get_for_model() ` -- cgit v1.3 From e4a1407a9cf35e6449136c22647a58cb14451e7a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 5 Jul 2012 08:39:05 -0400 Subject: Fixed #17997 - Documented that the debug server is now multithreaded by default. Thanks trey.smith@ for the report and vanessagomes for the patch. --- docs/ref/django-admin.txt | 1 + docs/releases/1.4.txt | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 8fea699c1b..7ca1ee5ddd 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -668,6 +668,7 @@ Example usage:: .. versionadded:: 1.4 +Since version 1.4, the development server is multithreaded by default. Use the ``--nothreading`` option to disable the use of threading in the development server. diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 51766dc2f5..a091869645 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -1145,6 +1145,15 @@ field. This was something that should not have worked, and in 1.4 loading such incomplete fixtures will fail. Because fixtures are a raw import, they should explicitly specify all field values, regardless of field options on the model. +Development Server Multithreading +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The development server is now is multithreaded by default. Use the +:djadminopt:`--nothreading` option to disable the use of threading in the +development server:: + + django-admin.py runserver --nothreading + Attributes disabled in markdown when safe mode set ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1338,4 +1347,3 @@ Versions of Python-Markdown earlier than 2.1 do not support the option to disable attributes. As a security issue, earlier versions of this library will not be supported by the markup contrib app in 1.5 under an accelerated deprecation timeline. - -- cgit v1.3 From 83da36ebfbdad2ac6e714b5308c076a1fb64b0be Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Fri, 6 Jul 2012 10:10:27 +0100 Subject: restored a missing \ in uwsgi docs --- docs/howto/deployment/wsgi/uwsgi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt index 3ac2203544..b5d438450e 100644 --- a/docs/howto/deployment/wsgi/uwsgi.txt +++ b/docs/howto/deployment/wsgi/uwsgi.txt @@ -46,7 +46,7 @@ uWSGI supports multiple ways to configure the process. See uWSGI's Here's an example command to start a uWSGI server:: - uwsgi --chdir=/path/to/your/project + uwsgi --chdir=/path/to/your/project \ --module=mysite.wsgi:application \ --env DJANGO_SETTINGS_MODULE=mysite.settings \ --master --pidfile=/tmp/project-master.pid \ -- cgit v1.3 From 4c417cc9ebd65595128b81cdddc9ea8293cbcbbe Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 6 Jul 2012 13:57:14 +0200 Subject: Fixed #18576 -- Added missing import in tutorial02 Thanks jaaruiz at yahoo.com for the report. --- docs/intro/tutorial02.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 16682c67c3..84da36be86 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -284,7 +284,7 @@ Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Poll`` registration code to read:: from django.contrib import admin - from polls.models import Poll + from polls.models import Choice, Poll class ChoiceInline(admin.StackedInline): model = Choice -- cgit v1.3 From 0a68a2994be17ed2669accead331881cb0be41dd Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sat, 7 Jul 2012 15:30:25 +0200 Subject: Fixed #18254 -- Added ability to the static template tags to store the result in a contextt variable. Many thanks to Andrei Antoukh for the initial patch. --- .../staticfiles/templatetags/staticfiles.py | 30 +++++++++-- django/templatetags/static.py | 63 ++++++++++++++++++++-- docs/ref/contrib/staticfiles.txt | 11 ++++ docs/ref/templates/builtins.txt | 11 ++++ tests/regressiontests/staticfiles_tests/tests.py | 12 +++-- tests/regressiontests/templates/tests.py | 2 + 6 files changed, 118 insertions(+), 11 deletions(-) (limited to 'docs') diff --git a/django/contrib/staticfiles/templatetags/staticfiles.py b/django/contrib/staticfiles/templatetags/staticfiles.py index 788f06ec16..71339ea8cd 100644 --- a/django/contrib/staticfiles/templatetags/staticfiles.py +++ b/django/contrib/staticfiles/templatetags/staticfiles.py @@ -1,13 +1,37 @@ from django import template +from django.templatetags.static import StaticNode from django.contrib.staticfiles.storage import staticfiles_storage register = template.Library() -@register.simple_tag -def static(path): +class StaticFilesNode(StaticNode): + + def url(self, context): + path = self.path.resolve(context) + return staticfiles_storage.url(path) + + +@register.tag('static') +def do_static(parser, token): """ A template tag that returns the URL to a file using staticfiles' storage backend + + Usage:: + + {% static path [as varname] %} + + Examples:: + + {% static "myapp/css/base.css" %} + {% static variable_with_path %} + {% static "myapp/css/base.css" as admin_base_css %} + {% static variable_with_path as varname %} + """ - return staticfiles_storage.url(path) + return StaticFilesNode.handle_token(parser, token) + + +def static(path): + return StaticNode.handle_simple(path) diff --git a/django/templatetags/static.py b/django/templatetags/static.py index cba44378c3..4b2d66d521 100644 --- a/django/templatetags/static.py +++ b/django/templatetags/static.py @@ -1,9 +1,11 @@ from urlparse import urljoin from django import template +from django.template.base import Node from django.utils.encoding import iri_to_uri register = template.Library() + class PrefixNode(template.Node): def __repr__(self): @@ -48,6 +50,7 @@ class PrefixNode(template.Node): context[self.varname] = prefix return '' + @register.tag def get_static_prefix(parser, token): """ @@ -66,6 +69,7 @@ def get_static_prefix(parser, token): """ return PrefixNode.handle_token(parser, token, "STATIC_URL") + @register.tag def get_media_prefix(parser, token): """ @@ -84,19 +88,70 @@ def get_media_prefix(parser, token): """ return PrefixNode.handle_token(parser, token, "MEDIA_URL") -@register.simple_tag -def static(path): + +class StaticNode(Node): + def __init__(self, varname=None, path=None): + if path is None: + raise template.TemplateSyntaxError( + "Static template nodes must be given a path to return.") + self.path = path + self.varname = varname + + def url(self, context): + path = self.path.resolve(context) + return self.handle_simple(path) + + def render(self, context): + url = self.url(context) + if self.varname is None: + return url + context[self.varname] = url + return '' + + @classmethod + def handle_simple(cls, path): + return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) + + @classmethod + def handle_token(cls, parser, token): + """ + Class method to parse prefix node and return a Node. + """ + bits = token.split_contents() + + if len(bits) < 2: + raise template.TemplateSyntaxError( + "'%s' takes at least one argument (path to file)" % bits[0]) + + path = parser.compile_filter(bits[1]) + + if len(bits) >= 2 and bits[-2] == 'as': + varname = bits[3] + else: + varname = None + + return cls(varname, path) + + +@register.tag('static') +def do_static(parser, token): """ Joins the given path with the STATIC_URL setting. Usage:: - {% static path %} + {% static path [as varname] %} Examples:: {% static "myapp/css/base.css" %} {% static variable_with_path %} + {% static "myapp/css/base.css" as admin_base_css %} + {% static variable_with_path as varname %} """ - return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) + return StaticNode.handle_token(parser, token) + + +def static(path): + return StaticNode.handle_simple(path) diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 126bcdd4e6..f5557dff91 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -387,6 +387,17 @@ The previous example is equal to calling the ``url`` method of an instance of useful when using a non-local storage backend to deploy files as documented in :ref:`staticfiles-from-cdn`. +.. versionadded:: 1.5 + +If you'd like to retrieve a static URL without displaying it, you can use a +slightly different call:: + +.. code-block:: html+django + + {% load static from staticfiles %} + {% static "images/hi.jpg" as myphoto %} + Hi! + Other Helpers ============= diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index cf228d72f6..71f57acdbf 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2354,6 +2354,17 @@ It is also able to consume standard context variables, e.g. assuming a {% load static %} +If you'd like to retrieve a static URL without displaying it, you can use a +slightly different call:: + +.. versionadded:: 1.5 + +.. code-block:: html+django + + {% load static %} + {% static "images/hi.jpg" as myphoto %} + + .. note:: The :mod:`staticfiles` contrib app also ships diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 8321fc2365..7678d7f100 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -87,14 +87,16 @@ class BaseStaticFilesTestCase(object): template = loader.get_template_from_string(template) return template.render(Context(kwargs)).strip() - def static_template_snippet(self, path): + def static_template_snippet(self, path, asvar=False): + if asvar: + return "{%% load static from staticfiles %%}{%% static '%s' as var %%}{{ var }}" % path return "{%% load static from staticfiles %%}{%% static '%s' %%}" % path - def assertStaticRenders(self, path, result, **kwargs): - template = self.static_template_snippet(path) + def assertStaticRenders(self, path, result, asvar=False, **kwargs): + template = self.static_template_snippet(path, asvar) self.assertEqual(self.render_template(template, **kwargs), result) - def assertStaticRaises(self, exc, path, result, **kwargs): + def assertStaticRaises(self, exc, path, result, asvar=False, **kwargs): self.assertRaises(exc, self.assertStaticRenders, path, result, **kwargs) @@ -368,6 +370,8 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, "/static/does/not/exist.png") self.assertStaticRenders("test/file.txt", "/static/test/file.dad0999e4f8f.txt") + self.assertStaticRenders("test/file.txt", + "/static/test/file.dad0999e4f8f.txt", asvar=True) self.assertStaticRenders("cached/styles.css", "/static/cached/styles.93b1147e8552.css") self.assertStaticRenders("path/", diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 35d01221ab..4aa71f9709 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1616,6 +1616,8 @@ class Templates(unittest.TestCase): 'static-prefixtag04': ('{% load static %}{% get_media_prefix as media_prefix %}{{ media_prefix }}', {}, settings.MEDIA_URL), 'static-statictag01': ('{% load static %}{% static "admin/base.css" %}', {}, urljoin(settings.STATIC_URL, 'admin/base.css')), 'static-statictag02': ('{% load static %}{% static base_css %}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')), + 'static-statictag03': ('{% load static %}{% static "admin/base.css" as foo %}{{ foo }}', {}, urljoin(settings.STATIC_URL, 'admin/base.css')), + 'static-statictag04': ('{% load static %}{% static base_css as foo %}{{ foo }}', {'base_css': 'admin/base.css'}, urljoin(settings.STATIC_URL, 'admin/base.css')), # Verbatim template tag outputs contents without rendering. 'verbatim-tag01': ('{% verbatim %}{{bare }}{% endverbatim %}', {}, '{{bare }}'), -- cgit v1.3 From 29ca3d3c4b3d330337cce8713196ef7eea956dc9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jul 2012 16:00:03 +0200 Subject: Fixed #18587 -- Typo in management command example Thanks Frank Wiles. --- docs/howto/custom-management-commands.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 4a27bdf7a9..12e8ec2494 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -129,7 +129,7 @@ default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`. class Command(BaseCommand): ... - self.can_import_settings = True + can_import_settings = True def handle(self, *args, **options): -- cgit v1.3 From bbc590697a8cde2faf067b124d08fe9488db4905 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jul 2012 16:44:55 +0200 Subject: Removed Django 1.0-specific sections. --- docs/ref/contrib/gis/install.txt | 11 ----------- docs/ref/models/querysets.txt | 16 ---------------- 2 files changed, 27 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 00f8f8a370..805772fa88 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -838,17 +838,6 @@ your ``.profile`` to be able to run the package programs from the command-line:: __ http://www.kyngchaos.com/software/frameworks __ http://www.kyngchaos.com/software/postgres -.. note:: - - Use of these binaries requires Django 1.0.3 and above. If you are - using a previous version of Django (like 1.0.2), then you will have - to add the following in your settings: - - .. code-block:: python - - GEOS_LIBRARY_PATH='/Library/Frameworks/GEOS.framework/GEOS' - GDAL_LIBRARY_PATH='/Library/Frameworks/GDAL.framework/GDAL' - .. _psycopg2_kyngchaos: psycopg2 diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 2876f1474d..0a9005ad26 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1768,22 +1768,6 @@ This queryset will be evaluated as subselect statement:: SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%') -The above code fragment could also be written as follows:: - - inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query - entries = Entry.objects.filter(blog__in=inner_q) - -.. warning:: - - This ``query`` attribute should be considered an opaque internal attribute. - It's fine to use it like above, but its API may change between Django - versions. - -This second form is a bit less readable and unnatural to write, since it -accesses the internal ``query`` attribute and requires a ``ValuesQuerySet``. -If your code doesn't require compatibility with Django 1.0, use the first -form, passing in a queryset directly. - If you pass in a ``ValuesQuerySet`` or ``ValuesListQuerySet`` (the result of calling ``values()`` or ``values_list()`` on a queryset) as the value to an ``__in`` lookup, you need to ensure you are only extracting one field in the -- cgit v1.3 From d94cfdcfae92fd4409cd1b5e14eebc75033266fd Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Jul 2012 17:42:04 +0200 Subject: Fixed #18589 -- Typo in generic CBV docs. Thanks cpthomas for the report. --- docs/topics/class-based-views/generic-display.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt index 4c2f95ce17..0d4cb6244d 100644 --- a/docs/topics/class-based-views/generic-display.txt +++ b/docs/topics/class-based-views/generic-display.txt @@ -157,7 +157,7 @@ might look like the following:: That's really all there is to it. All the cool features of generic views come from changing the attributes set on the generic view. The :doc:`generic views reference` documents all the -generic views and their options in detail; the rest of this document will +generic views and their options in detail; the rest of this document will consider some of the common ways you might customize and extend generic views. @@ -220,7 +220,7 @@ more:: def get_context_data(self, **kwargs): # Call the base implementation first to get a context - context = super(PublisherDetailView, self).get_context_data(**kwargs) + context = super(PublisherDetail, self).get_context_data(**kwargs) # Add in a QuerySet of all the books context['book_list'] = Book.objects.all() return context @@ -284,7 +284,7 @@ technique:: from django.views.generic import ListView from books.models import Book - class AcmeBookListView(ListView): + class AcmeBookList(ListView): context_object_name = 'book_list' queryset = Book.objects.filter(publisher__name='Acme Publishing') @@ -361,7 +361,7 @@ use it in the template:: def get_context_data(self, **kwargs): # Call the base implementation first to get a context - context = super(PublisherBookListView, self).get_context_data(**kwargs) + context = super(PublisherBookList, self).get_context_data(**kwargs) # Add in the publisher context['publisher'] = self.publisher return context -- cgit v1.3 From 249c445446f0811d6396cfd3053aed33edf2e7b3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 7 Jul 2012 23:08:43 +0200 Subject: Fixed #18164 -- Precised startapp template context content --- docs/ref/django-admin.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 7ca1ee5ddd..31c46c7fa0 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -888,7 +888,8 @@ through the template engine: the files whose extensions match the with the ``--name`` option. The :class:`template context ` used is: -- Any option passed to the startapp command +- Any option passed to the startapp command (among the command's supported + options) - ``app_name`` -- the app name as passed to the command - ``app_directory`` -- the full path of the newly created app -- cgit v1.3 From 5e94ef293cb1cfe4dc43cbd5509653c0e0bf66cf Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 8 Jul 2012 11:53:45 +0200 Subject: Fixed #18374 -- Explained "corrupt image" error Thanks fabian and charettes. --- docs/ref/forms/fields.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 486d49d796..082ec17a35 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -591,7 +591,11 @@ For each field, we describe the default widget used if you don't specify * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``, ``invalid_image`` - Using an ImageField requires that the `Python Imaging Library`_ is installed. + Using an ``ImageField`` requires that the `Python Imaging Library`_ (PIL) + is installed and supports the image formats you use. If you encounter a + ``corrupt image`` error when you upload an image, it usually means PIL + doesn't understand its format. To fix this, install the appropriate + library and reinstall PIL. When you use an ``ImageField`` on a form, you must also remember to :ref:`bind the file data to the form `. -- cgit v1.3 From d44aa98184ced66d95ba95d0e4a255d09c15df0c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 8 Jul 2012 18:35:17 -0400 Subject: Fixed #18173 - Corrected ModelAdmin documentation for get_changelist. Thanks Keryn Knight for the report and vanessagomes for the patch. --- docs/ref/contrib/admin/index.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 7aca0981c3..ad4f6ba3cd 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -371,12 +371,6 @@ subclass:: because ``raw_id_fields`` and ``radio_fields`` imply custom widgets of their own. -.. attribute:: ModelAdmin.get_changelist - - Returns the Changelist class to be used for listing. By default, - ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this - class you can change the behavior of the listing. - .. attribute:: ModelAdmin.inlines See :class:`InlineModelAdmin` objects below. @@ -1168,6 +1162,12 @@ templates used by the :class:`ModelAdmin` views: kwargs['choices'] += (('ready', 'Ready for deployment'),) return super(MyModelAdmin, self).formfield_for_choice_field(db_field, request, **kwargs) +.. method:: ModelAdmin.get_changelist(self, request, **kwargs) + + Returns the Changelist class to be used for listing. By default, + ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this + class you can change the behavior of the listing. + .. method:: ModelAdmin.has_add_permission(self, request) Should return ``True`` if adding an object is permitted, ``False`` -- cgit v1.3 From 590de18add78945344de049c2d3e7021fd46ce53 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 8 Jul 2012 19:26:53 -0400 Subject: Fixed #18577 - Clarified middleware initialization. Thanks Lukasz Balcerzak for the patch. --- docs/topics/http/middleware.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index a768a3bbd8..fe92bc59a9 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -199,7 +199,8 @@ of caveats: define ``__init__`` as requiring any arguments. * Unlike the ``process_*`` methods which get called once per request, - ``__init__`` gets called only *once*, when the Web server starts up. + ``__init__`` gets called only *once*, when the Web server responds to the + first request. Marking middleware as unused ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3 From 5664338e22c1312e68293181a41b290434b2018a Mon Sep 17 00:00:00 2001 From: Stefan Kjartansson Date: Tue, 10 Jul 2012 15:27:50 +0000 Subject: typo in "django/docs/topics/python3.txt" --- docs/topics/python3.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 974ddb0e88..1aea252e3f 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -181,7 +181,7 @@ xrange range xrange =============================== ====================================== ====================== -Ouptut encoding now Unicode +Output encoding now Unicode =========================== If you want to catch stdout/stderr output, the output content is UTF-8 encoded -- cgit v1.3 From fe443b11def46828a140bdd5521807e6a6c27bf8 Mon Sep 17 00:00:00 2001 From: mitnk Date: Wed, 11 Jul 2012 10:57:26 +0800 Subject: fixed a typo in timezones docs. --- docs/topics/i18n/timezones.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index 1d9dd4b3c6..f3bb13ab03 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -509,7 +509,7 @@ Setup Finally, our calendar system contains interesting traps for computers:: >>> import datetime - >>> def substract_one_year(value): # DON'T DO THAT! + >>> def one_year_before(value): # DON'T DO THAT! ... return value.replace(year=value.year - 1) >>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0)) datetime.datetime(2011, 3, 1, 10, 0) -- cgit v1.3 From e806f047f3e54a0c3830cf669935a8f12854990b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 12 Jul 2012 18:49:32 +0200 Subject: Removed old gis install instructions for obsolete distros --- docs/ref/contrib/gis/install.txt | 50 ---------------------------------------- 1 file changed, 50 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 805772fa88..b6122b8289 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -1034,61 +1034,11 @@ Optional packages to consider: do not plan on doing any database transformation of geometries to the Google projection (900913). -.. _heron: - -8.04 and lower -~~~~~~~~~~~~~~ - -The 8.04 (and lower) versions of Ubuntu use GEOS v2.2.3 in their binary packages, -which is incompatible with GeoDjango. Thus, do *not* use the binary packages -for GEOS or PostGIS and build some prerequisites from source, per the instructions -in this document; however, it is okay to use the PostgreSQL binary packages. - -For more details, please see the Debian instructions for :ref:`etch` below. - .. _debian: Debian ------ -.. _etch: - -4.0 (Etch) -^^^^^^^^^^ - -The situation here is the same as that of Ubuntu :ref:`heron` -- in other words, -some packages must be built from source to work properly with GeoDjango. - -Binary packages -~~~~~~~~~~~~~~~ -The following command will install acceptable binary packages, as well as -the development tools necessary to build the rest of the requirements: - -.. code-block:: bash - - $ sudo apt-get install binutils bzip2 gcc g++ flex make postgresql-8.1 \ - postgresql-server-dev-8.1 python-ctypes python-psycopg2 python-setuptools - -Required package information: - -* ``binutils``: for ctypes to find libraries -* ``bzip2``: for decompressing the source packages -* ``gcc``, ``g++``, ``make``: GNU developer tools used to compile the libraries -* ``flex``: required to build PostGIS -* ``postgresql-8.1`` -* ``postgresql-server-dev-8.1``: for ``pg_config`` -* ``python-psycopg2`` - -Optional packages: - -* ``libgeoip``: for :ref:`GeoIP ` support - -Source packages -~~~~~~~~~~~~~~~ -You will still have to install :ref:`geosbuild`, :ref:`proj4`, -:ref:`postgis`, and :ref:`gdalbuild` from source. Please follow the -directions carefully. - .. _lenny: 5.0 (Lenny) -- cgit v1.3 From 8c670ee34714acffbc64e5cafd1e664fb8341a37 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 12 Jul 2012 23:02:58 +0200 Subject: Fixed #18617 -- Highlighted that the app_directories template loader depends on the order of INSTALLED_APPS. Thanks evildmp for the patch. --- docs/ref/templates/api.txt | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index ec01fe2faa..bd2b4c6e9d 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -649,14 +649,24 @@ class. Here are the template loaders that come with Django: INSTALLED_APPS = ('myproject.polls', 'myproject.music') - ...then ``get_template('foo.html')`` will look for templates in these + ...then ``get_template('foo.html')`` will look for ``foo.html`` in these directories, in this order: - * ``/path/to/myproject/polls/templates/foo.html`` - * ``/path/to/myproject/music/templates/foo.html`` + * ``/path/to/myproject/polls/templates/`` + * ``/path/to/myproject/music/templates/`` - Note that the loader performs an optimization when it is first imported: It - caches a list of which :setting:`INSTALLED_APPS` packages have a + ... and will use the one it finds first. + + The order of :setting:`INSTALLED_APPS` is significant! For example, if you + want to customize the Django admin, you might choose to override the + standard ``admin/base_site.html`` template, from ``django.contrib.admin``, + with your own ``admin/base_site.html`` in ``myproject.polls``. You must + then make sure that your ``myproject.polls`` comes *before* + ``django.contrib.admin`` in :setting:`INSTALLED_APPS`, otherwise + ``django.contrib.admin``'s will be loaded first and yours will be ignored. + + Note that the loader performs an optimization when it is first imported: + it caches a list of which :setting:`INSTALLED_APPS` packages have a ``templates`` subdirectory. This loader is enabled by default. -- cgit v1.3 From 18b9dc41543616ba5b15d0400564e665b76701d1 Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Fri, 13 Jul 2012 17:30:45 +0200 Subject: Fixed #18601 -- Specified that Python minimum version is 2.6.5 This is due to a bug in previous Python 2.6 versions related to unicode keyword arguments. --- docs/faq/install.txt | 6 +++--- docs/intro/install.txt | 2 +- docs/releases/1.5.txt | 2 +- docs/topics/install.txt | 2 +- docs/topics/logging.txt | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'docs') diff --git a/docs/faq/install.txt b/docs/faq/install.txt index e2ecfb4717..a14615e47c 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -16,8 +16,8 @@ How do I get started? What are Django's prerequisites? -------------------------------- -Django requires Python_, specifically Python 2.6 or 2.7. -No other Python libraries are required for basic Django usage. +Django requires Python_, specifically Python 2.6.5 - 2.7.x. No other Python +libraries are required for basic Django usage. For a development environment -- if you just want to experiment with Django -- you don't need to have a separate Web server installed; Django comes with its @@ -42,7 +42,7 @@ Do I lose anything by using Python 2.6 versus newer Python versions, such as Pyt ---------------------------------------------------------------------------------------- Not in the core framework. Currently, Django itself officially supports -Python 2.6 and 2.7. However, newer versions of +Python 2.6 (2.6.5 or higher) and 2.7. However, newer versions of Python are often faster, have more features, and are better supported. If you use a newer version of Python you will also have access to some APIs that aren't available under older versions of Python. diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 41339b5f11..7e8c7db7b3 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -10,7 +10,7 @@ Install Python -------------- Being a Python Web framework, Django requires Python. It works with any Python -version from 2.6 to 2.7 (due to backwards incompatibilities in Python 3.0, +version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with Python 3.0; see :doc:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition), these versions of Python include a lightweight database called diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 944f19f03b..33ea0cb4a2 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -16,7 +16,7 @@ features`_. Python compatibility ==================== -Django 1.5 has dropped support for Python 2.5. Python 2.6 is now the minimum +Django 1.5 has dropped support for Python 2.5. Python 2.6.5 is now the minimum required Python version. Django is tested and supported on Python 2.6 and 2.7. diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 4b0aca3548..291b22cb3e 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -9,7 +9,7 @@ Install Python Being a Python Web framework, Django requires Python. -It works with any Python version from 2.6 to 2.7 (due to backwards +It works with any Python version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with Python 3.0; see :doc:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition). diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index aa2afba760..a878d42266 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -209,8 +209,8 @@ By default, Django uses the `dictConfig format`_. ``logging.dictConfig`` is a builtin library in Python 2.7. In order to make this library available for users of earlier Python versions, Django includes a copy as part of ``django.utils.log``. - If you have Python 2.7, the system native library will be used; if - you have Python 2.6 or earlier, Django's copy will be used. + If you have Python 2.7 or later, the system native library will be used; if + you have Python 2.6, Django's copy will be used. In order to configure logging, you use :setting:`LOGGING` to define a dictionary of logging settings. These settings describes the loggers, -- cgit v1.3 From b6215f68100884600fb23bc2a16b9a8c27dd1d6b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 13 Jul 2012 17:49:50 +0200 Subject: Updated links for MacOSX python installers --- docs/ref/contrib/gis/install.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index b6122b8289..d7deb17d49 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -762,13 +762,13 @@ Python ^^^^^^ Although OS X comes with Python installed, users can use framework -installers (`2.5`__ and `2.6`__ are available) provided by +installers (`2.6`__ and `2.7`__ are available) provided by the Python Software Foundation. An advantage to using the installer is that OS X's Python will remain "pristine" for internal operating system use. -__ http://python.org/ftp/python/2.5.4/python-2.5.4-macosx.dmg -__ http://python.org/ftp/python/2.6.2/python-2.6.2-macosx2009-04-16.dmg +__ http://python.org/ftp/python/2.6.6/python-2.6.6-macosx10.3.dmg +__ http://python.org/ftp/python/2.7.3/ .. note:: -- cgit v1.3 From c13a98968e25c84b418c779004b3c8b0cdc81355 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 14 Jul 2012 12:31:34 +0200 Subject: Fixed a misplaced Sphinx reference. --- docs/topics/db/transactions.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 1f15949000..9928354664 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -54,8 +54,6 @@ The various cache middlewares are an exception: Even when using database caching, Django's cache backend uses its own database cursor (which is mapped to its own database connection internally). -.. _transaction-management-functions: - .. note:: The ``TransactionMiddleware`` only affects the database aliased @@ -63,6 +61,8 @@ database cursor (which is mapped to its own database connection internally). multiple databases and want transaction control over databases other than "default", you will need to write your own transaction middleware. +.. _transaction-management-functions: + Controlling transaction management in views =========================================== -- cgit v1.3 From 8b3c2f2c51183bde47bd1e98057b77866f35dd6e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Jul 2012 16:08:42 -0700 Subject: Deprecate two methods (which I seriously doubt anyone ever used, but they were documented so...) because they cannot be implemented efficiently on top of collections.SortedDict in Python 2.7 and up. --- django/utils/datastructures.py | 9 +++++++++ docs/ref/utils.txt | 4 ++++ 2 files changed, 13 insertions(+) (limited to 'docs') diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 9a41b4311c..832b16c3d4 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,6 +1,8 @@ import copy +import warning from types import GeneratorType + class MergeDict(object): """ A simple class for creating new "virtual" dictionaries that actually look @@ -191,10 +193,17 @@ class SortedDict(dict): def value_for_index(self, index): """Returns the value of the item at the given zero-based index.""" + # This, and insert() are deprecated because they cannot be implemented + # using collections.OrderedDict (Python 2.7 and up), which we'll + # eventually switch to + warning.warn(PendingDeprecationWarning, + "SortedDict.value_for_index is deprecated", stacklevel=2) return self[self.keyOrder[index]] def insert(self, index, key, value): """Inserts the key, value pair before the item with the given index.""" + warning.warn(PendingDeprecationWarning, + "SortedDict.insert is deprecated", stacklevel=2) if key in self.keyOrder: n = self.keyOrder.index(key) del self.keyOrder[n] diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index c74392df36..c2f2025bc3 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -112,10 +112,14 @@ to distinguish caches by the ``Accept-language`` header. .. method:: insert(index, key, value) + .. deprecated:: 1.5 + Inserts the key, value pair before the item with the given index. .. method:: value_for_index(index) + .. deprecated:: 1.5 + Returns the value of the item at the given zero-based index. Creating a new SortedDict -- cgit v1.3 From fb46f243b4c48ab42ed2f33a2636f7c33f5861a9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 15 Jul 2012 11:19:00 +0200 Subject: Fixed #18625 -- Removed old-style use of url tag from the documentation. --- docs/ref/contrib/comments/index.txt | 2 +- docs/topics/http/urls.txt | 4 ++-- docs/topics/i18n/translation.txt | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 40b1b662b7..af937e036e 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -250,7 +250,7 @@ Redirecting after the comment post To specify the URL you want to redirect to after the comment has been posted, you can include a hidden form input called ``next`` in your comment form. For example:: - + .. _notes-on-the-comment-form: diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 2310fac413..4e75dfe55f 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -704,8 +704,8 @@ target each pattern individually by using its name: .. code-block:: html+django - {% url arch-summary 1945 %} - {% url full-archive 2007 %} + {% url 'arch-summary' 1945 %} + {% url 'full-archive' 2007 %} Even though both URL patterns refer to the ``archive`` view here, using the ``name`` parameter to ``url()`` allows you to tell them apart in templates. diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index ec6159d538..c912bf9575 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -596,7 +596,7 @@ apply. Reverse URL lookups cannot be carried out within the ``blocktrans`` and should be retrieved (and stored) beforehand:: - {% url path.to.view arg arg2 as the_url %} + {% url 'path.to.view' arg arg2 as the_url %} {% blocktrans %} This is a URL: {{ the_url }} {% endblocktrans %} @@ -790,7 +790,7 @@ To use the catalog, just pull in the dynamically generated script like this: .. code-block:: html+django - + This uses reverse URL lookup to find the URL of the JavaScript catalog view. When the catalog is loaded, your JavaScript code can use the standard @@ -992,7 +992,7 @@ template tag. It enables the given language in the enclosed template section: {% trans "View this category in:" %} {% for lang_code, lang_name in languages %} {% language lang_code %} - {{ lang_name }} + {{ lang_name }} {% endlanguage %} {% endfor %} -- cgit v1.3 From bf9d5eff4cd74ffc8fcd1f610587e5ad00dc7f3f Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 15 Jul 2012 11:25:13 +0200 Subject: Fixed #18626 -- rst syntax collision. --- docs/howto/initial-data.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/docs/howto/initial-data.txt b/docs/howto/initial-data.txt index e5a4957cb2..eca2e2c4f9 100644 --- a/docs/howto/initial-data.txt +++ b/docs/howto/initial-data.txt @@ -67,12 +67,12 @@ And here's that same fixture as YAML: You'll store this data in a ``fixtures`` directory inside your app. -Loading data is easy: just call :djadmin:`manage.py loaddata -`, where ```` is the name of the fixture file you've -created. Each time you run :djadmin:`loaddata`, the data will be read from the -fixture and re-loaded into the database. Note this means that if you change one -of the rows created by a fixture and then run :djadmin:`loaddata` again, you'll -wipe out any changes you've made. +Loading data is easy: just call :djadmin:`manage.py loaddata ` +````, where ```` is the name of the fixture file +you've created. Each time you run :djadmin:`loaddata`, the data will be read +from the fixture and re-loaded into the database. Note this means that if you +change one of the rows created by a fixture and then run :djadmin:`loaddata` +again, you'll wipe out any changes you've made. Automatically loading initial data fixtures ------------------------------------------- -- cgit v1.3 From cdcdd131da950741fa74debc21bef8632fd3c684 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 15 Jul 2012 21:07:02 +0200 Subject: Dropped support for GDAL < 1.5 GDAL 1.5 has been released in December 2007. --- django/contrib/gis/gdal/__init__.py | 4 ++-- django/contrib/gis/gdal/geometries.py | 20 +++++--------------- django/contrib/gis/gdal/libgdal.py | 9 +-------- django/contrib/gis/gdal/prototypes/geom.py | 15 +++++---------- django/contrib/gis/gdal/tests/test_geom.py | 5 ----- django/contrib/gis/geos/base.py | 1 - django/contrib/gis/geos/geometry.py | 9 ++++----- django/contrib/gis/geos/tests/test_geos.py | 2 +- django/contrib/gis/tests/test_geoforms.py | 1 + docs/ref/contrib/gis/gdal.txt | 2 +- docs/ref/contrib/gis/install.txt | 8 ++++---- docs/releases/1.5.txt | 5 +++++ 12 files changed, 29 insertions(+), 52 deletions(-) (limited to 'docs') diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index 5c336f3210..adff96b47a 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -37,12 +37,12 @@ try: from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.datasource import DataSource - from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GEOJSON, GDAL_VERSION + from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GDAL_VERSION from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform from django.contrib.gis.gdal.geometries import OGRGeometry HAS_GDAL = True except: - HAS_GDAL, GEOJSON = False, False + HAS_GDAL = False try: from django.contrib.gis.gdal.envelope import Envelope diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index b4d4ad1646..3feb18a923 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -48,7 +48,7 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.geomtype import OGRGeomType -from django.contrib.gis.gdal.libgdal import GEOJSON, GDAL_VERSION +from django.contrib.gis.gdal.libgdal import GDAL_VERSION from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform # Getting the ctypes prototype functions that interface w/the GDAL C library. @@ -97,10 +97,7 @@ class OGRGeometry(GDALBase): else: g = capi.from_wkt(byref(c_char_p(wkt_m.group('wkt'))), None, byref(c_void_p())) elif json_m: - if GEOJSON: - g = capi.from_json(geom_input) - else: - raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') + g = capi.from_json(geom_input) else: # Seeing if the input is a valid short-hand string # (e.g., 'Point', 'POLYGON'). @@ -328,22 +325,15 @@ class OGRGeometry(GDALBase): @property def json(self): """ - Returns the GeoJSON representation of this Geometry (requires - GDAL 1.5+). + Returns the GeoJSON representation of this Geometry. """ - if GEOJSON: - return capi.to_json(self.ptr) - else: - raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.') + return capi.to_json(self.ptr) geojson = json @property def kml(self): "Returns the KML representation of the Geometry." - if GEOJSON: - return capi.to_kml(self.ptr, None) - else: - raise NotImplementedError('KML output only supported on GDAL 1.5+.') + return capi.to_kml(self.ptr, None) @property def wkb_size(self): diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 69643371d2..27a5b8172e 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -19,7 +19,7 @@ elif os.name == 'nt': elif os.name == 'posix': # *NIX library names. lib_names = ['gdal', 'GDAL', 'gdal1.9.0', 'gdal1.8.0', 'gdal1.7.0', - 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] + 'gdal1.6.0', 'gdal1.5.0'] else: raise OGRException('Unsupported OS "%s"' % os.name) @@ -97,10 +97,3 @@ GDAL_MINOR_VERSION = int(_verinfo['minor']) GDAL_SUBMINOR_VERSION = _verinfo['subminor'] and int(_verinfo['subminor']) GDAL_VERSION = (GDAL_MAJOR_VERSION, GDAL_MINOR_VERSION, GDAL_SUBMINOR_VERSION) del _verinfo - -# GeoJSON support is available only in GDAL 1.5+. -if GDAL_VERSION >= (1, 5): - GEOJSON = True -else: - GEOJSON = False - diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index 7fa83910c7..f2c833d576 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -1,6 +1,6 @@ from ctypes import c_char_p, c_double, c_int, c_void_p, POINTER from django.contrib.gis.gdal.envelope import OGREnvelope -from django.contrib.gis.gdal.libgdal import lgdal, GEOJSON +from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.prototypes.errcheck import check_bool, check_envelope from django.contrib.gis.gdal.prototypes.generation import (const_string_output, double_output, geom_output, int_output, srs_output, string_output, void_output) @@ -25,15 +25,10 @@ def topology_func(f): ### OGR_G ctypes function prototypes ### -# GeoJSON routines, if supported. -if GEOJSON: - from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) - to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) - to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True) -else: - from_json = False - to_json = False - to_kml = False +# GeoJSON routines. +from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) +to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) +to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True) # GetX, GetY, GetZ all return doubles. getx = pnt_func(lgdal.OGR_G_GetX) diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index b68aa41b0a..20e25946b0 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -6,7 +6,6 @@ except ImportError: from django.contrib.gis.gdal import (OGRGeometry, OGRGeomType, OGRException, OGRIndexError, SpatialReference, CoordTransform, GDAL_VERSION) -from django.contrib.gis.gdal.prototypes.geom import GEOJSON from django.contrib.gis.geometry.test_data import TestDataMixin from django.utils import unittest @@ -108,7 +107,6 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): def test01e_json(self): "Testing GeoJSON input/output." - if not GEOJSON: return for g in self.geometries.json_geoms: geom = OGRGeometry(g.wkt) if not hasattr(g, 'not_equal'): @@ -244,9 +242,6 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): self.fail('Should have raised an OGRException!') print("\nEND - expecting IllegalArgumentException; safe to ignore.\n") - # Closing the rings -- doesn't work on GDAL versions 1.4.1 and below: - # http://trac.osgeo.org/gdal/ticket/1673 - if GDAL_VERSION <= (1, 4, 1): return poly.close_rings() self.assertEqual(10, poly.point_count) # Two closing points should've been added self.assertEqual(OGRGeometry('POINT(2.5 2.5)'), poly.centroid) diff --git a/django/contrib/gis/geos/base.py b/django/contrib/gis/geos/base.py index 754bcce7ad..fd2693ed59 100644 --- a/django/contrib/gis/geos/base.py +++ b/django/contrib/gis/geos/base.py @@ -10,7 +10,6 @@ except ImportError: # A 'dummy' gdal module. class GDALInfo(object): HAS_GDAL = False - GEOJSON = False gdal = GDALInfo() # NumPy supported? diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index f411d5a353..2d8ac53dbd 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -65,7 +65,7 @@ class GEOSGeometry(GEOSBase, ListMixin): elif hex_regex.match(geo_input): # Handling HEXEWKB input. g = wkb_r().read(geo_input) - elif gdal.GEOJSON and json_regex.match(geo_input): + elif gdal.HAS_GDAL and json_regex.match(geo_input): # Handling GeoJSON input. g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb) else: @@ -409,13 +409,12 @@ class GEOSGeometry(GEOSBase, ListMixin): @property def json(self): """ - Returns GeoJSON representation of this Geometry if GDAL 1.5+ - is installed. + Returns GeoJSON representation of this Geometry if GDAL is installed. """ - if gdal.GEOJSON: + if gdal.HAS_GDAL: return self.ogr.json else: - raise GEOSException('GeoJSON output only supported on GDAL 1.5+.') + raise GEOSException('GeoJSON output only supported when GDAL is installed.') geojson = json @property diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index ed38283dfc..18f1fb65be 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -196,7 +196,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export - @unittest.skipUnless(gdal.HAS_GDAL and gdal.GEOJSON, "gdal >= 1.5 is required") + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_json(self): "Testing GeoJSON input/output (via GDAL)." for g in self.geometries.json_geoms: diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py index ed851df0d2..79a3ae85a2 100644 --- a/django/contrib/gis/tests/test_geoforms.py +++ b/django/contrib/gis/tests/test_geoforms.py @@ -71,6 +71,7 @@ class GeometryFieldTest(unittest.TestCase): for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'): self.assertEqual(GEOSGeometry(wkt), fld.to_python(wkt)) # but raises a ValidationError for any other string + import pdb; pdb.set_trace() for wkt in ('POINT(5)', 'MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'BLAH(0 0, 1 1)'): self.assertRaises(forms.ValidationError, fld.to_python, wkt) diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index 619f23fba2..c4b29bead7 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -447,7 +447,7 @@ systems and coordinate transformation:: This object is a wrapper for the `OGR Geometry`__ class. These objects are instantiated directly from the given ``geom_input`` - parameter, which may be a string containing WKT or HEX, a ``buffer`` + parameter, which may be a string containing WKT, HEX, GeoJSON, a ``buffer`` containing WKB data, or an :class:`OGRGeomType` object. These objects are also returned from the :class:`Feature.geom` attribute, when reading vector data from :class:`Layer` (which is in turn a part of diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index d7deb17d49..5ee6d5153d 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -81,7 +81,7 @@ Program Description Required ======================== ==================================== ================================ ========================== :ref:`GEOS ` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0 `PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.7, 4.6, 4.5, 4.4 -:ref:`GDAL ` Geospatial Data Abstraction Library No (but, required for SQLite) 1.8, 1.7, 1.6, 1.5, 1.4 +:ref:`GDAL ` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6, 1.5 :ref:`GeoIP ` IP-based geolocation library No 1.4 `PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 1.5, 1.4, 1.3 `SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 3.0, 2.4, 2.3 @@ -270,9 +270,9 @@ supports :ref:`GDAL's vector data ` capabilities [#]_. First download the latest GDAL release version and untar the archive:: - $ wget http://download.osgeo.org/gdal/gdal-1.8.1.tar.gz - $ tar xzf gdal-1.8.1.tar.gz - $ cd gdal-1.8.1 + $ wget http://download.osgeo.org/gdal/gdal-1.9.1.tar.gz + $ tar xzf gdal-1.9.1.tar.gz + $ cd gdal-1.9.1 Configure, make and install:: diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 33ea0cb4a2..4275fbae52 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -177,6 +177,11 @@ autocommit behavior was never restored. This bug is now fixed in 1.5. While this is only a bug fix, it is worth checking your applications behavior if you are using PostgreSQL together with the autocommit option. +Miscellaneous +~~~~~~~~~~~~~ + +* GeoDjango dropped support for GDAL < 1.5 + Features deprecated in 1.5 ========================== -- cgit v1.3 From aeda55e6bffc3cfbf53698af398a19c1a0f02d46 Mon Sep 17 00:00:00 2001 From: Anssi Kääriäinen Date: Thu, 5 Jul 2012 18:09:48 +0300 Subject: Fixed #3881 -- skip saving session when response status is 500 Saving session data is somewhat likely to lead into error when the status code is 500. It is guaranteed to lead into error if the reason for the 500 code is query error on PostgreSQL. --- django/contrib/sessions/middleware.py | 16 +++++++++------- django/contrib/sessions/tests.py | 16 ++++++++++++++++ docs/releases/1.5.txt | 6 ++++++ docs/topics/http/sessions.txt | 3 +++ 4 files changed, 34 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 68cb77f7e1..9f65255f47 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -33,11 +33,13 @@ class SessionMiddleware(object): expires_time = time.time() + max_age expires = cookie_date(expires_time) # Save the session data and refresh the client cookie. - request.session.save() - response.set_cookie(settings.SESSION_COOKIE_NAME, - request.session.session_key, max_age=max_age, - expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, - path=settings.SESSION_COOKIE_PATH, - secure=settings.SESSION_COOKIE_SECURE or None, - httponly=settings.SESSION_COOKIE_HTTPONLY or None) + # Skip session save for 500 responses, refs #3881. + if response.status_code != 500: + request.session.save() + response.set_cookie(settings.SESSION_COOKIE_NAME, + request.session.session_key, max_age=max_age, + expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, + path=settings.SESSION_COOKIE_PATH, + secure=settings.SESSION_COOKIE_SECURE or None, + httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 92ea6bbd91..328b085f1e 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -409,6 +409,22 @@ class SessionMiddlewareTests(unittest.TestCase): self.assertNotIn('httponly', str(response.cookies[settings.SESSION_COOKIE_NAME])) + def test_session_save_on_500(self): + request = RequestFactory().get('/') + response = HttpResponse('Horrible error') + response.status_code = 500 + middleware = SessionMiddleware() + + # Simulate a request the modifies the session + middleware.process_request(request) + request.session['hello'] = 'world' + + # Handle the response through the middleware + response = middleware.process_response(request, response) + + # Check that the value wasn't saved above. + self.assertNotIn('hello', request.session.load()) + class CookieSessionTests(SessionTestsMixin, TestCase): diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 4275fbae52..b863d102ec 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -177,6 +177,12 @@ autocommit behavior was never restored. This bug is now fixed in 1.5. While this is only a bug fix, it is worth checking your applications behavior if you are using PostgreSQL together with the autocommit option. +Session not saved on 500 responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Django's session middleware will skip saving the session data if the +response's status code is 500. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index a32d9b54dd..1f55293413 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -423,6 +423,9 @@ cookie will be sent on every request. Similarly, the ``expires`` part of a session cookie is updated each time the session cookie is sent. +.. versionchanged:: 1.5 + The session is not saved if the response's status code is 500. + Browser-length sessions vs. persistent sessions =============================================== -- cgit v1.3 From 29132ebdef0e0b9c09e456b05f0e6a22f1106a4f Mon Sep 17 00:00:00 2001 From: Anssi Kääriäinen Date: Sun, 29 Apr 2012 04:22:05 +0300 Subject: Fixed #17788 -- Added batch_size argument to qs.bulk_create() The qs.bulk_create() method did not work with large batches together with SQLite3. This commit adds a way to split the bulk into smaller batches. The default batch size is unlimited except for SQLite3 where the batch size is limited to 999 SQL parameters per batch. Thanks to everybody who participated in the discussions at Trac. --- django/db/backends/__init__.py | 30 ++++++++++++------- django/db/backends/sqlite3/base.py | 9 +++++- django/db/models/query.py | 29 ++++++++++++++---- docs/ref/models/querysets.txt | 20 ++++--------- docs/releases/1.5.txt | 5 ++++ tests/regressiontests/bulk_create/models.py | 6 +++- tests/regressiontests/bulk_create/tests.py | 46 +++++++++++++++++++++++++++-- tests/regressiontests/queries/tests.py | 3 +- 8 files changed, 110 insertions(+), 38 deletions(-) (limited to 'docs') diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 2da1f2be0f..3075bdb3e4 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -475,6 +475,14 @@ class BaseDatabaseOperations(object): """ return None + def bulk_batch_size(self, fields, objs): + """ + Returns the maximum allowed batch size for the backend. The fields + are the fields going to be inserted in the batch, the objs contains + all the objects to be inserted. + """ + return len(objs) + def cache_key_culling_sql(self): """ Returns a SQL query that retrieves the first cache key greater than the @@ -522,6 +530,17 @@ class BaseDatabaseOperations(object): """ return '' + def distinct_sql(self, fields): + """ + Returns an SQL DISTINCT clause which removes duplicate rows from the + result set. If any fields are given, only the given fields are being + checked for duplicates. + """ + if fields: + raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') + else: + return 'DISTINCT' + def drop_foreignkey_sql(self): """ Returns the SQL command that drops a foreign key. @@ -577,17 +596,6 @@ class BaseDatabaseOperations(object): """ raise NotImplementedError('Full-text search is not implemented for this database backend') - def distinct_sql(self, fields): - """ - Returns an SQL DISTINCT clause which removes duplicate rows from the - result set. If any fields are given, only the given fields are being - checked for duplicates. - """ - if fields: - raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') - else: - return 'DISTINCT' - def last_executed_query(self, cursor, sql, params): """ Returns a string of the query last executed by the given cursor, with diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index d7f51605d5..60723124c1 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -85,7 +85,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_1000_query_parameters = False supports_mixed_date_datetime_comparisons = False has_bulk_insert = True - can_combine_inserts_with_and_without_auto_increment_pk = True + can_combine_inserts_with_and_without_auto_increment_pk = False @cached_property def supports_stddev(self): @@ -107,6 +107,13 @@ class DatabaseFeatures(BaseDatabaseFeatures): return has_support class DatabaseOperations(BaseDatabaseOperations): + def bulk_batch_size(self, fields, objs): + """ + SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of + 999 variables per query. + """ + return (999 // len(fields)) if len(fields) > 0 else len(objs) + def date_extract_sql(self, lookup_type, field_name): # sqlite doesn't support extract, so we fake it with the user-defined # function django_extract that's registered in connect(). Note that diff --git a/django/db/models/query.py b/django/db/models/query.py index 0f1d87c642..ebe61a1226 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -388,7 +388,7 @@ class QuerySet(object): obj.save(force_insert=True, using=self.db) return obj - def bulk_create(self, objs): + def bulk_create(self, objs, batch_size=None): """ Inserts each of the instances into the database. This does *not* call save() on each of the instances, does not send any pre/post save @@ -401,8 +401,10 @@ class QuerySet(object): # this could be implemented if you didn't have an autoincrement pk, # and 2) you could do it by doing O(n) normal inserts into the parent # tables to get the primary keys back, and then doing a single bulk - # insert into the childmost table. We're punting on these for now - # because they are relatively rare cases. + # insert into the childmost table. Some databases might allow doing + # this by using RETURNING clause for the insert query. We're punting + # on these for now because they are relatively rare cases. + assert batch_size is None or batch_size > 0 if self.model._meta.parents: raise ValueError("Can't bulk create an inherited model") if not objs: @@ -418,13 +420,14 @@ class QuerySet(object): try: if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk and self.model._meta.has_auto_field): - self.model._base_manager._insert(objs, fields=fields, using=self.db) + self._batched_insert(objs, fields, batch_size) else: objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs) if objs_with_pk: - self.model._base_manager._insert(objs_with_pk, fields=fields, using=self.db) + self._batched_insert(objs_with_pk, fields, batch_size) if objs_without_pk: - self.model._base_manager._insert(objs_without_pk, fields=[f for f in fields if not isinstance(f, AutoField)], using=self.db) + fields= [f for f in fields if not isinstance(f, AutoField)] + self._batched_insert(objs_without_pk, fields, batch_size) if forced_managed: transaction.commit(using=self.db) else: @@ -860,6 +863,20 @@ class QuerySet(object): ################### # PRIVATE METHODS # ################### + def _batched_insert(self, objs, fields, batch_size): + """ + A little helper method for bulk_insert to insert the bulk one batch + at a time. Inserts recursively a batch from the front of the bulk and + then _batched_insert() the remaining objects again. + """ + if not objs: + return + ops = connections[self.db].ops + batch_size = (batch_size or max(ops.bulk_batch_size(fields, objs), 1)) + for batch in [objs[i:i+batch_size] + for i in range(0, len(objs), batch_size)]: + self.model._base_manager._insert(batch, fields=fields, + using=self.db) def _clone(self, klass=None, setup=False, **kwargs): if klass is None: diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 0a9005ad26..8c188c67c3 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1350,7 +1350,7 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec. bulk_create ~~~~~~~~~~~ -.. method:: bulk_create(objs) +.. method:: bulk_create(objs, batch_size=None) .. versionadded:: 1.4 @@ -1372,20 +1372,12 @@ This has a number of caveats though: * If the model's primary key is an :class:`~django.db.models.AutoField` it does not retrieve and set the primary key attribute, as ``save()`` does. -.. admonition:: Limits of SQLite +The ``batch_size`` parameter controls how many objects are created in single +query. The default is to create all objects in one batch, except for SQLite +where the default is such that at maximum 999 variables per query is used. - SQLite sets a limit on the number of parameters per SQL statement. The - maximum is defined by the SQLITE_MAX_VARIABLE_NUMBER_ compilation option, - which defaults to 999. For instance, if your model has 8 fields (including - the primary key), you cannot create more than 999 // 8 = 124 instances at - a time. If you exceed this limit, you'll get an exception:: - - django.db.utils.DatabaseError: too many SQL variables - - If your application's performance requirements exceed SQLite's limits, you - should switch to another database engine, such as PostgreSQL. - -.. _SQLITE_MAX_VARIABLE_NUMBER: http://sqlite.org/limits.html#max_variable_number +.. versionadded:: 1.5 + The ``batch_size`` parameter was added in version 1.5. count ~~~~~ diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index b863d102ec..fd9ae4f038 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -106,6 +106,11 @@ Django 1.5 also includes several smaller improvements worth noting: * The :ref:`receiver ` decorator is now able to connect to more than one signal by supplying a list of signals. +* :meth:`QuerySet.bulk_create() + ` has now a batch_size + argument. By default the batch_size is unlimited except for SQLite where + single batch is limited so that 999 parameters per query isn't exceeded. + Backwards incompatible changes in 1.5 ===================================== diff --git a/tests/regressiontests/bulk_create/models.py b/tests/regressiontests/bulk_create/models.py index a4c611d537..bc685bbbe4 100644 --- a/tests/regressiontests/bulk_create/models.py +++ b/tests/regressiontests/bulk_create/models.py @@ -18,4 +18,8 @@ class Pizzeria(Restaurant): pass class State(models.Model): - two_letter_code = models.CharField(max_length=2, primary_key=True) \ No newline at end of file + two_letter_code = models.CharField(max_length=2, primary_key=True) + +class TwoFields(models.Model): + f1 = models.IntegerField(unique=True) + f2 = models.IntegerField(unique=True) diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/regressiontests/bulk_create/tests.py index 0b55f637a4..33108ea9b0 100644 --- a/tests/regressiontests/bulk_create/tests.py +++ b/tests/regressiontests/bulk_create/tests.py @@ -2,9 +2,11 @@ from __future__ import absolute_import from operator import attrgetter -from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature +from django.db import connection +from django.test import TestCase, skipIfDBFeature +from django.test.utils import override_settings -from .models import Country, Restaurant, Pizzeria, State +from .models import Country, Restaurant, Pizzeria, State, TwoFields class BulkCreateTests(TestCase): @@ -27,7 +29,6 @@ class BulkCreateTests(TestCase): self.assertEqual(created, []) self.assertEqual(Country.objects.count(), 4) - @skipUnlessDBFeature("has_bulk_insert") def test_efficiency(self): with self.assertNumQueries(1): Country.objects.bulk_create(self.data) @@ -69,3 +70,42 @@ class BulkCreateTests(TestCase): invalid_country = Country(id=0, name='Poland', iso_two_letter='PL') with self.assertRaises(ValueError): Country.objects.bulk_create([valid_country, invalid_country]) + + def test_large_batch(self): + with override_settings(DEBUG=True): + connection.queries = [] + TwoFields.objects.bulk_create([ + TwoFields(f1=i, f2=i+1) for i in range(0, 1001) + ]) + self.assertTrue(len(connection.queries) < 10) + self.assertEqual(TwoFields.objects.count(), 1001) + self.assertEqual( + TwoFields.objects.filter(f1__gte=450, f1__lte=550).count(), + 101) + self.assertEqual(TwoFields.objects.filter(f2__gte=901).count(), 101) + + def test_large_batch_mixed(self): + """ + Test inserting a large batch with objects having primary key set + mixed together with objects without PK set. + """ + with override_settings(DEBUG=True): + connection.queries = [] + TwoFields.objects.bulk_create([ + TwoFields(id=i if i % 2 == 0 else None, f1=i, f2=i+1) + for i in range(100000, 101000)]) + self.assertTrue(len(connection.queries) < 10) + self.assertEqual(TwoFields.objects.count(), 1000) + # We can't assume much about the ID's created, except that the above + # created IDs must exist. + id_range = range(100000, 101000, 2) + self.assertEqual(TwoFields.objects.filter(id__in=id_range).count(), 500) + self.assertEqual(TwoFields.objects.exclude(id__in=id_range).count(), 500) + + def test_explicit_batch_size(self): + objs = [TwoFields(f1=i, f2=i) for i in range(0, 100)] + with self.assertNumQueries(2): + TwoFields.objects.bulk_create(objs, 50) + TwoFields.objects.all().delete() + with self.assertNumQueries(1): + TwoFields.objects.bulk_create(objs, len(objs)) diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 9bb3a29430..1582993dfc 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -1879,8 +1879,7 @@ class ConditionalTests(BaseQuerysetTest): # Test that the "in" lookup works with lists of 1000 items or more. Number.objects.all().delete() numbers = range(2500) - for num in numbers: - _ = Number.objects.create(num=num) + Number.objects.bulk_create(Number(num=num) for num in numbers) self.assertEqual( Number.objects.filter(num__in=numbers[:1000]).count(), 1000 -- cgit v1.3 From f2abfe1e4853df1848d178c3de369c80932292e8 Mon Sep 17 00:00:00 2001 From: Mike Grouchy Date: Mon, 16 Jul 2012 20:37:52 -0400 Subject: Adds interpreter option to shell command as per ticket #18639 Specify python interpreter interface ipython or bpython with the -i, --interface options argument. ex// python manage.py shell -i bpython ex// python manage.py shell --interface bpython Like all other options, defaults to default python interpreter when your selected choice isn't available. updated documentation where appropriate --- django/core/management/commands/shell.py | 20 +++++++++++++++----- docs/ref/django-admin.txt | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 075efa0bcd..4e7d1dbbf4 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -2,13 +2,19 @@ import os from django.core.management.base import NoArgsCommand from optparse import make_option + class Command(NoArgsCommand): + shells = ['ipython', 'bpython'] + option_list = NoArgsCommand.option_list + ( make_option('--plain', action='store_true', dest='plain', help='Tells Django to use plain Python, not IPython or bpython.'), + make_option('-i', '--interface', action='store', type='choice', choices=shells, + dest='interface', + help='Specify an interactive interpreter interface. Available options: "ipython" and "bpython"'), + ) help = "Runs a Python interactive interpreter. Tries to use IPython or bpython, if one of them is available." - shells = ['ipython', 'bpython'] requires_model_validation = False def ipython(self): @@ -31,8 +37,10 @@ class Command(NoArgsCommand): import bpython bpython.embed() - def run_shell(self): - for shell in self.shells: + def run_shell(self, shell=None): + available_shells = [shell] if shell else self.shells + + for shell in available_shells: try: return getattr(self, shell)() except ImportError: @@ -46,19 +54,21 @@ class Command(NoArgsCommand): get_models() use_plain = options.get('plain', False) + interface = options.get('interface', None) try: if use_plain: # Don't bother loading IPython, because the user wants plain Python. raise ImportError - self.run_shell() + + self.run_shell(shell=interface) except ImportError: import code # Set up a dictionary to serve as the environment for the shell, so # that tab completion works on objects that are imported at runtime. # See ticket 5082. imported_objects = {} - try: # Try activating rlcompleter, because it's handy. + try: # Try activating rlcompleter, because it's handy. import readline except ImportError: pass diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 31c46c7fa0..c4295c68d5 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -743,6 +743,24 @@ use the ``--plain`` option, like so:: django-admin.py shell --plain +.. versionchanged:: 1.5 + +If you would like to specify either IPython or bpython as your interpreter if +you have both installed you can specify an alternative interpreter interface +with the ``-i`` or ``--interface`` options like so:: + +IPython:: + + django-admin.py shell -i ipython + django-admin.py shell --interface ipython + + +bpython:: + + django-admin.py shell -i bpython + django-admin.py shell --interface bpython + + .. _IPython: http://ipython.scipy.org/ .. _bpython: http://bpython-interpreter.org/ -- cgit v1.3 From adad6c3afeea91422a22312939330ce8165084e3 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 18 Jul 2012 13:45:42 +0200 Subject: Added myself to internals/committers.txt. --- docs/internals/committers.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'docs') diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index b0eed95571..712b29ee25 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -365,6 +365,16 @@ Anssi Kääriäinen all related features. He's also a fan of benckmarking and he tries keep Django as fast as possible. +Florian Apolloner + Florian is currently studying Physics at the `Graz University of Technology`_. + Soon after he started using Django he joined the `Ubuntuusers webteam`_ to + work on *Inyoka*, the software powering the whole Ubuntusers site. + + For the time beeing he lives in Graz, Austria (not Australia ;)). + +.. _Graz University of Technology: http://tugraz.at/ +.. _Ubuntuusers webteam: http://wiki.ubuntuusers.de/ubuntuusers/Webteam + Specialists ----------- -- cgit v1.3 From 810fd236fad03e687e714c6a8e0ccd21229f89c1 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 18 Jul 2012 14:10:40 +0200 Subject: Updated my bio. --- docs/internals/committers.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 712b29ee25..79cbcb47d0 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -191,14 +191,19 @@ Karen Tracey `Jannis Leidel`_ Jannis graduated in media design from `Bauhaus-University Weimar`_, is the author of a number of pluggable Django apps and likes to - contribute to Open Source projects like Pinax_. He currently works as - a freelance Web developer and designer. + contribute to Open Source projects like virtualenv_ and pip_. + + He has worked on Django's auth, admin and staticfiles apps as well as + the form, core, internationalization and test systems. He currently works + as the lead engineer at Gidsy_. Jannis lives in Berlin, Germany. .. _Jannis Leidel: http://jezdez.com/ .. _Bauhaus-University Weimar: http://www.uni-weimar.de/ -.. _pinax: http://pinaxproject.com/ +.. _virtualenv: http://www.virtualenv.org/ +.. _pip: http://www.pip-installer.org/ +.. _Gidsy: http://gidsy.com/ `James Tauber`_ James is the lead developer of Pinax_ and the CEO and founder of -- cgit v1.3 From 6d0dbd88f649f5038732de8643e9c9ba50ebb567 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 18 Jul 2012 08:04:29 -0700 Subject: Update my bio. --- docs/internals/committers.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 79cbcb47d0..fb601a24c0 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -219,15 +219,15 @@ Karen Tracey .. _James Tauber: http://jtauber.com/ `Alex Gaynor`_ - Alex is a student at Rensselaer Polytechnic Institute, and is also an - independent contractor. He found Django in 2007 and has been addicted ever - since he found out you don't need to write out your forms by hand. He has - a small obsession with compilers. He's contributed to the ORM, forms, - admin, and other components of Django. + Alex is a software engineer working at Rdio_. He found Django in 2007 and + has been addicted ever since he found out you don't need to write out your + forms by hand. He has a small obsession with compilers. He's contributed + to the ORM, forms, admin, and other components of Django. - Alex lives in Chicago, IL, but spends most of his time in Troy, NY. + Alex lives in San Francisco, CA, USA. .. _Alex Gaynor: http://alexgaynor.net +.. _Rdio: http://rdio.com `Andrew Godwin`_ Andrew is a freelance Python developer and tinkerer, and has been -- cgit v1.3 From a7928dedc83bfe00736fc4ef3a905652684b55d4 Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Wed, 18 Jul 2012 14:34:08 -0400 Subject: Fix typo in staticfiles app documentation In the documentation for the `static` template tag, a `::` was used prior to a `code-block`. Doing so caused the `code-block` line to render as code. Changing the `::` to `:` corrects the display. --- docs/ref/contrib/staticfiles.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index f5557dff91..cbe8ad54b8 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -390,7 +390,7 @@ in :ref:`staticfiles-from-cdn`. .. versionadded:: 1.5 If you'd like to retrieve a static URL without displaying it, you can use a -slightly different call:: +slightly different call: .. code-block:: html+django -- cgit v1.3 From 8b0190984116873158ee627c7a033a7bd4c3a199 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 11:32:38 +0200 Subject: [py3] Bundled six for Python 3 compatibility. Refs #18363. --- django/utils/py3.py | 109 --------------- django/utils/six.py | 353 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/topics/python3.txt | 253 ++++------------------------------ 3 files changed, 378 insertions(+), 337 deletions(-) delete mode 100644 django/utils/py3.py create mode 100644 django/utils/six.py (limited to 'docs') diff --git a/django/utils/py3.py b/django/utils/py3.py deleted file mode 100644 index 8a5c37e976..0000000000 --- a/django/utils/py3.py +++ /dev/null @@ -1,109 +0,0 @@ -# Compatibility layer for running Django both in 2.x and 3.x - -import sys - -if sys.version_info[0] < 3: - PY3 = False - # Changed module locations - from urlparse import (urlparse, urlunparse, urljoin, urlsplit, urlunsplit, - urldefrag, parse_qsl) - from urllib import (quote, unquote, quote_plus, urlopen, urlencode, - url2pathname, urlretrieve, unquote_plus) - from urllib2 import (Request, OpenerDirector, UnknownHandler, HTTPHandler, - HTTPSHandler, HTTPDefaultErrorHandler, FTPHandler, - HTTPError, HTTPErrorProcessor) - import urllib2 - import Cookie as cookies - try: - import cPickle as pickle - except ImportError: - import pickle - try: - import thread - except ImportError: - import dummy_thread as thread - from htmlentitydefs import name2codepoint - import HTMLParser - from os import getcwdu - from itertools import izip as zip - unichr = unichr - xrange = xrange - maxsize = sys.maxint - - # Type aliases - string_types = basestring, - text_type = unicode - integer_types = int, long - long_type = long - - from io import BytesIO as OutputIO - - # Glue code for syntax differences - def reraise(tp, value, tb=None): - exec("raise tp, value, tb") - - def with_metaclass(meta, base=object): - class _DjangoBase(base): - __metaclass__ = meta - return _DjangoBase - - iteritems = lambda o: o.iteritems() - itervalues = lambda o: o.itervalues() - iterkeys = lambda o: o.iterkeys() - - # n() is useful when python3 needs a str (unicode), and python2 str (bytes) - n = lambda s: s.encode('utf-8') - -else: - PY3 = True - import builtins - - # Changed module locations - from urllib.parse import (urlparse, urlunparse, urlencode, urljoin, - urlsplit, urlunsplit, quote, unquote, - quote_plus, unquote_plus, parse_qsl, - urldefrag) - from urllib.request import (urlopen, url2pathname, Request, OpenerDirector, - UnknownHandler, HTTPHandler, HTTPSHandler, - HTTPDefaultErrorHandler, FTPHandler, - HTTPError, HTTPErrorProcessor, urlretrieve) - import urllib.request as urllib2 - import http.cookies as cookies - import pickle - try: - import _thread as thread - except ImportError: - import _dummy_thread as thread - from html.entities import name2codepoint - import html.parser as HTMLParser - from os import getcwd as getcwdu - zip = zip - unichr = chr - xrange = range - maxsize = sys.maxsize - - # Type aliases - string_types = str, - text_type = str - integer_types = int, - long_type = int - - from io import StringIO as OutputIO - - # Glue code for syntax differences - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - def with_metaclass(meta, base=object): - ns = dict(base=base, meta=meta) - exec("""class _DjangoBase(base, metaclass=meta): - pass""", ns) - return ns["_DjangoBase"] - - iteritems = lambda o: o.items() - itervalues = lambda o: o.values() - iterkeys = lambda o: o.keys() - - n = lambda s: s diff --git a/django/utils/six.py b/django/utils/six.py new file mode 100644 index 0000000000..6526d76cb1 --- /dev/null +++ b/django/utils/six.py @@ -0,0 +1,353 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.1.0" + + +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) + # This is a bit ugly, but it avoids running this again. + delattr(tp, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + + +class _MovedItems(types.ModuleType): + """Lazy loading of moved objects""" + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) +del attr + +moves = sys.modules["six.moves"] = _MovedItems("moves") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_code = "__code__" + _func_defaults = "__defaults__" + + _iterkeys = "keys" + _itervalues = "values" + _iteritems = "items" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_code = "func_code" + _func_defaults = "func_defaults" + + _iterkeys = "iterkeys" + _itervalues = "itervalues" + _iteritems = "iteritems" + + +if PY3: + def get_unbound_function(unbound): + return unbound + + + advance_iterator = next + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) +else: + def get_unbound_function(unbound): + return unbound.im_func + + + def advance_iterator(it): + return it.next() + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) + + +def iterkeys(d): + """Return an iterator over the keys of a dictionary.""" + return getattr(d, _iterkeys)() + +def itervalues(d): + """Return an iterator over the values of a dictionary.""" + return getattr(d, _itervalues)() + +def iteritems(d): + """Return an iterator over the (key, value) pairs of a dictionary.""" + return getattr(d, _iteritems)() + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + return unicode(s, "unicode_escape") + int2byte = chr + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + import builtins + exec_ = getattr(builtins, "exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = getattr(builtins, "print") + del builtins + +else: + def exec_(code, globs=None, locs=None): + """Execute code in a namespace.""" + if globs is None: + frame = sys._getframe(1) + globs = frame.f_globals + if locs is None: + locs = frame.f_locals + del frame + elif locs is None: + locs = globs + exec("""exec code in globs, locs""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +_add_doc(reraise, """Reraise an exception.""") + + +def with_metaclass(meta, base=object): + """Create a base class with a metaclass.""" + return meta("NewBase", (base,), {}) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 1aea252e3f..e4bfc1bd9c 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -2,251 +2,48 @@ Python 3 compatibility ====================== -Django 1.5 introduces a compatibility layer that allows the code to be run both -in Python 2 (2.6/2.7) and Python 3 (>= 3.2) (*work in progress*). +Django 1.5 is the first version of Django to support Python 3. -This document is not meant as a complete Python 2 to Python 3 migration guide. -There are many existing resources you can read. But we describe some utilities -and guidelines that we recommend you should use when you want to ensure your -code can be run with both Python 2 and 3. +The same code runs both on Python 2 (≥2.6.5) and Python 3 (≥3.2). To +achieve this: -* http://docs.python.org/py3k/howto/pyporting.html -* http://python3porting.com/ +- wherever possible, Django uses the six_ compatibility layer, +- all modules declare ``from __future__ import unicode_literals``. -django.utils.py3 -================ +.. _six: http://packages.python.org/six/ + +This document is not meant as a Python 2 to Python 3 migration guide. There +are many existing resources, including `Python's official porting guide`_. But +it describes guidelines that apply to Django's code and are recommended for +pluggable apps that run with both Python 2 and 3. -Whenever a symbol or module has different semantics or different locations on -Python 2 and Python 3, you can import it from ``django.utils.py3`` where it -will be automatically converted depending on your current Python version. +.. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html -PY3 ---- +.. module: django.utils.six -If you need to know anywhere in your code if you are running Python 3 or a -previous Python 2 version, you can check the ``PY3`` boolean variable:: +django.utils.six +================ - from django.utils.py3 import PY3 +Read the documentation of six_. It's the canonical compatibility library for +supporting Python 2 and 3 in a single codebase. - if PY3: - # Do stuff Python 3-wise - else: - # Do stuff Python 2-wise +``six`` is bundled with Django: you can import it as :mod:`django.utils.six`. -This should be considered as a last resort solution when it is not possible -to import a compatible name from django.utils.py3, as described in the sections -below. +.. _string-handling: String handling =============== -In Python 3, all strings are considered Unicode strings by default. Byte strings -have to be prefixed with the letter 'b'. To mimic the same behaviour in Python 2, -we recommend you import ``unicode_literals`` from the ``__future__`` library:: +In Python 3, all strings are considered Unicode strings by default. Byte +strings must be prefixed with the letter ``b``. In order to enable the same +behavior in Python 2, every module must import ``unicode_literals`` from +``__future__``:: from __future__ import unicode_literals my_string = "This is an unicode literal" my_bytestring = b"This is a bytestring" -Be cautious if you have to slice bytestrings. -See http://docs.python.org/py3k/howto/pyporting.html#bytes-literals - -Different expected strings --------------------------- - -Some method parameters have changed the expected string type of a parameter. -For example, ``strftime`` format parameter expects a bytestring on Python 2 but -a normal (Unicode) string on Python 3. For these cases, ``django.utils.py3`` -provides a ``n()`` function which encodes the string parameter only with -Python 2. - - >>> from __future__ import unicode_literals - >>> from datetime import datetime - - >>> print(datetime.date(2012, 5, 21).strftime(n("%m → %Y"))) - 05 → 2012 - -Renamed types -============= - -Several types are named differently in Python 2 and Python 3. In order to keep -compatibility while using those types, import their corresponding aliases from -``django.utils.py3``. - -=========== ========= ===================== -Python 2 Python 3 django.utils.py3 -=========== ========= ===================== -basestring, str, string_types (tuple) -unicode str text_type -int, long int, integer_types (tuple) -long int long_type -=========== ========= ===================== - -String aliases --------------- - -Code sample:: - - if isinstance(foo, basestring): - print("foo is a string") - - # I want to convert a number to a Unicode string - bar = 45 - bar_string = unicode(bar) - -Should be replaced by:: - - from django.utils.py3 import string_types, text_type - - if isinstance(foo, string_types): - print("foo is a string") - - # I want to convert a number to a Unicode string - bar = 45 - bar_string = text_type(bar) - -No more long type ------------------ - -``long`` and ``int`` types have been unified in Python 3, meaning that ``long`` -is no longer available. ``django.utils.py3`` provides both ``long_type`` and -``integer_types`` aliases. For example: - -.. code-block:: python - - # Old Python 2 code - my_var = long(333463247234623) - if isinstance(my_var, (int, long)): - # ... - -Should be replaced by: - -.. code-block:: python - - from django.utils.py3 import long_type, integer_types - - my_var = long_type(333463247234623) - if isinstance(my_var, integer_types): - # ... - - -Changed module locations -======================== - -The following modules have changed their location in Python 3. Therefore, it is -recommended to import them from the ``django.utils.py3`` compatibility layer: - -=============================== ====================================== ====================== -Python 2 Python3 django.utils.py3 -=============================== ====================================== ====================== -Cookie http.cookies cookies - -urlparse.urlparse urllib.parse.urlparse urlparse -urlparse.urlunparse urllib.parse.urlunparse urlunparse -urlparse.urljoin urllib.parse.urljoin urljoin -urlparse.urlsplit urllib.parse.urlsplit urlsplit -urlparse.urlunsplit urllib.parse.urlunsplit urlunsplit -urlparse.urldefrag urllib.parse.urldefrag urldefrag -urlparse.parse_qsl urllib.parse.parse_qsl parse_qsl -urllib.quote urllib.parse.quote quote -urllib.unquote urllib.parse.unquote unquote -urllib.quote_plus urllib.parse.quote_plus quote_plus -urllib.unquote_plus urllib.parse.unquote_plus unquote_plus -urllib.urlencode urllib.parse.urlencode urlencode -urllib.urlopen urllib.request.urlopen urlopen -urllib.url2pathname urllib.request.url2pathname url2pathname -urllib.urlretrieve urllib.request.urlretrieve urlretrieve -urllib2 urllib.request urllib2 -urllib2.Request urllib.request.Request Request -urllib2.OpenerDirector urllib.request.OpenerDirector OpenerDirector -urllib2.UnknownHandler urllib.request.UnknownHandler UnknownHandler -urllib2.HTTPHandler urllib.request.HTTPHandler HTTPHandler -urllib2.HTTPSHandler urllib.request.HTTPSHandler HTTPSHandler -urllib2.HTTPDefaultErrorHandler urllib.request.HTTPDefaultErrorHandler HTTPDefaultErrorHandler -urllib2.FTPHandler urllib.request.FTPHandler FTPHandler -urllib2.HTTPError urllib.request.HTTPError HTTPError -urllib2.HTTPErrorProcessor urllib.request.HTTPErrorProcessor HTTPErrorProcessor - -htmlentitydefs.name2codepoint html.entities.name2codepoint name2codepoint -HTMLParser html.parser HTMLParser -cPickle/pickle pickle pickle -thread/dummy_thread _thread/_dummy_thread thread - -os.getcwdu os.getcwd getcwdu -itertools.izip zip zip -sys.maxint sys.maxsize maxsize -unichr chr unichr -xrange range xrange -=============================== ====================================== ====================== - - -Output encoding now Unicode -=========================== - -If you want to catch stdout/stderr output, the output content is UTF-8 encoded -in Python 2, while it is Unicode strings in Python 3. You can use the OutputIO -stream to capture this output:: - - from django.utils.py3 import OutputIO - - try: - old_stdout = sys.stdout - out = OutputIO() - sys.stdout = out - # Do stuff which produces standard output - result = out.getvalue() - finally: - sys.stdout = old_stdout - -Dict iteritems/itervalues/iterkeys -================================== - -The iteritems(), itervalues() and iterkeys() methods of dictionaries do not -exist any more in Python 3, simply because they represent the default items() -values() and keys() behavior in Python 3. Therefore, to keep compatibility, -use similar functions from ``django.utils.py3``:: - - from django.utils.py3 import iteritems, itervalues, iterkeys - - my_dict = {'a': 21, 'b': 42} - for key, value in iteritems(my_dict): - # ... - for value in itervalues(my_dict): - # ... - for key in iterkeys(my_dict): - # ... - -Note that in Python 3, dict.keys(), dict.items() and dict.values() return -"views" instead of lists. Wrap them into list() if you really need their return -values to be in a list. - -http://docs.python.org/release/3.0.1/whatsnew/3.0.html#views-and-iterators-instead-of-lists - -Metaclass -========= - -The syntax for declaring metaclasses has changed in Python 3. -``django.utils.py3`` offers a compatible way to declare metaclasses:: - - from django.utils.py3 import with_metaclass - - class MyClass(with_metaclass(SubClass1, SubClass2,...)): - # ... - -Re-raising exceptions -===================== - -One of the syntaxes to raise exceptions (raise E, V, T) is gone in Python 3. -This is especially used in very specific cases where you want to re-raise a -different exception that the initial one, while keeping the original traceback. -So, instead of:: - - raise Exception, Exception(msg), traceback - -Use:: - - from django.utils.py3 import reraise - - reraise(Exception, Exception(msg), traceback) +Be cautious if you have to `slice bytestrings`_. +.. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals -- cgit v1.3 From 00ace01411b4cb558e71bfaf34cf42870e73092b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 22 Jul 2012 10:29:07 +0200 Subject: [py3] Documented coding guidelines for Python 3. --- docs/conf.py | 1 + docs/topics/python3.txt | 123 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 99 insertions(+), 25 deletions(-) (limited to 'docs') diff --git a/docs/conf.py b/docs/conf.py index 659115dfbd..39a280e464 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -94,6 +94,7 @@ pygments_style = 'trac' intersphinx_mapping = { 'python': ('http://docs.python.org/2.7', None), 'sphinx': ('http://sphinx.pocoo.org/', None), + 'six': ('http://packages.python.org/six/', None), } # Python's docs don't change every week. diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index e4bfc1bd9c..cfa38d9bec 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -2,42 +2,34 @@ Python 3 compatibility ====================== -Django 1.5 is the first version of Django to support Python 3. - -The same code runs both on Python 2 (≥2.6.5) and Python 3 (≥3.2). To -achieve this: - -- wherever possible, Django uses the six_ compatibility layer, -- all modules declare ``from __future__ import unicode_literals``. +Django 1.5 is the first version of Django to support Python 3. The same code +runs both on Python 2 (≥ 2.6.5) and Python 3 (≥ 3.2), thanks to the six_ +compatibility layer and ``unicode_literals``. .. _six: http://packages.python.org/six/ This document is not meant as a Python 2 to Python 3 migration guide. There -are many existing resources, including `Python's official porting guide`_. But -it describes guidelines that apply to Django's code and are recommended for -pluggable apps that run with both Python 2 and 3. +are many existing resources, including `Python's official porting guide`_. +Rather, it describes guidelines that apply to Django's code and are +recommended for pluggable apps that run with both Python 2 and 3. .. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html -.. module: django.utils.six - -django.utils.six -================ - -Read the documentation of six_. It's the canonical compatibility library for -supporting Python 2 and 3 in a single codebase. +Syntax requirements +=================== -``six`` is bundled with Django: you can import it as :mod:`django.utils.six`. +Unicode +------- -.. _string-handling: +In Python 3, all strings are considered Unicode by default. The ``unicode`` +type from Python 2 is called ``str`` in Python 3, and ``str`` becomes +``bytes``. -String handling -=============== +You mustn't use the ``u`` prefix before a unicode string literal because it's +a syntax error in Python 3.2. You must prefix byte strings with ``b``. -In Python 3, all strings are considered Unicode strings by default. Byte -strings must be prefixed with the letter ``b``. In order to enable the same -behavior in Python 2, every module must import ``unicode_literals`` from -``__future__``:: +In order to enable the same behavior in Python 2, every module must import +``unicode_literals`` from ``__future__``:: from __future__ import unicode_literals @@ -47,3 +39,84 @@ behavior in Python 2, every module must import ``unicode_literals`` from Be cautious if you have to `slice bytestrings`_. .. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals + +Exceptions +---------- + +When you capture exceptions, use the ``as`` keyword:: + + try: + ... + except MyException as exc: + ... + +This older syntax was removed in Python 3:: + + try: + ... + except MyException, exc: + ... + +The syntax to reraise an exception with a different traceback also changed. +Use :func:`six.reraise`. + + +.. module: django.utils.six + +Writing compatible code with six +================================ + +six is the canonical compatibility library for supporting Python 2 and 3 in +a single codebase. Read its `documentation `_! + +:mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`. + +Here are the most common changes required to write compatible code. + +String types +------------ + +The ``basestring`` and ``unicode`` types were removed in Python 3, and the +meaning of ``str`` changed. To test these types, use the following idioms:: + + isinstance(myvalue, six.string_types) # replacement for basestring + isinstance(myvalue, six.text_type) # replacement for unicode + isinstance(myvalue, bytes) # replacement for str + +Python ≥ 2.6 provides ``bytes`` as an alias for ``str``, so you don't need +:attr:`six.binary_type`. + +``long`` +-------- + +The ``long`` type no longer exists in Python 3. ``1L`` is a syntax error. Use +:data:`six.integer_types` check if a value is an integer or a long:: + + isinstance(myvalue, six.integer_types) # replacement for (int, long) + +``xrange`` +---------- + +Import :func:`six.moves.xrange` wherever you use ``xrange``. + +Moved modules +------------- + +Some modules were renamed in Python 3. The :mod:`django.utils.six.moves +` module provides a compatible location to import them. + +In addition to six' defaults, Django's version provides ``dummy_thread`` as +``_dummy_thread``. + +PY3 +--- + +If you need different code in Python 2 and Python 3, check :data:`six.PY3`:: + + if six.PY3: + # do stuff Python 3-wise + else: + # do stuff Python 2-wise + +This is a last resort solution when :mod:`six` doesn't provide an appropriate +function. -- cgit v1.3 From cc65f4ec8db21ee24f954bf52c39749b4b7caca4 Mon Sep 17 00:00:00 2001 From: Roman Haritonov Date: Sun, 22 Jul 2012 18:54:47 +0400 Subject: Documentation: Fix link to uWSGI deployment --- docs/topics/install.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 291b22cb3e..a11a44baa1 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -62,7 +62,7 @@ for information on how to configure mod_wsgi once you have it installed. If you can't use mod_wsgi for some reason, fear not: Django supports many other -deployment options. One is :doc:`uWSGI `; it works +deployment options. One is :doc:`uWSGI `; it works very well with `nginx`_. Another is :doc:`FastCGI `, perfect for using Django with servers other than Apache. Additionally, Django follows the WSGI spec (:pep:`3333`), which allows it to run on a variety of -- cgit v1.3 From ebc89a800ac743bccb56810526c352fedd2aaa3e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 22 Jul 2012 19:48:10 +0200 Subject: Fixed a broken link in the Python 3 docs. Thanks ptone for the report. --- docs/topics/python3.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index cfa38d9bec..7c1cb53150 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -66,8 +66,8 @@ Use :func:`six.reraise`. Writing compatible code with six ================================ -six is the canonical compatibility library for supporting Python 2 and 3 in -a single codebase. Read its `documentation `_! +six_ is the canonical compatibility library for supporting Python 2 and 3 in +a single codebase. Read its documentation! :mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`. -- cgit v1.3 From 6006c1f076ae7661a601ab6efa4087caf935fbba Mon Sep 17 00:00:00 2001 From: nklas Date: Wed, 25 Jul 2012 01:45:56 +0700 Subject: Fixed a small typo. --- docs/releases/1.4.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index a091869645..01532cc04c 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -37,7 +37,7 @@ Other notable new features in Django 1.4 include: the ability to `bulk insert <#model-objects-bulk-create-in-the-orm>`_ large datasets for improved performance, and `QuerySet.prefetch_related`_, a method to batch-load related objects - in areas where :meth:`~django.db.models.QuerySet.select_related` does't + in areas where :meth:`~django.db.models.QuerySet.select_related` doesn't work. * Some nice security additions, including `improved password hashing`_ -- cgit v1.3 From f758bdab5eec3e615598948dd5bcf9bb7b910c9d Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 24 Jul 2012 17:24:16 -0300 Subject: Fixed #18271 -- Changed stage at which TransactionTestCase flushes DB tables. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the flush was done before the test case execution and now it is performed after it. Other changes to the testing infrastructure include: * TransactionTestCase now doesn't reset autoincrement sequences either (previous behavior can achieved by using `reset_sequences`.) With this, no implicit such reset is performed by any of the provided TestCase classes. * New ordering of test cases: All unittest tes cases are run first and doctests are run at the end. THse changes could be backward-incompatible with test cases that relied on some kind of state being preserved between tests. Please read the relevant sections of the release notes and testing documentation for further details. Thanks Andreas Pelme for the initial patch. Karen Tracey and Anssi Kääriäinen for the feedback and Anssi for reviewing. This also fixes #12408. --- django/core/management/commands/flush.py | 4 +- django/core/management/sql.py | 7 +- django/db/backends/__init__.py | 13 ++ django/db/backends/mysql/base.py | 27 ++-- django/db/backends/oracle/base.py | 21 +-- .../db/backends/postgresql_psycopg2/operations.py | 34 ++--- django/test/simple.py | 4 +- django/test/testcases.py | 62 ++++++--- docs/releases/1.5.txt | 51 ++++++++ docs/topics/testing.txt | 141 +++++++++++++++------ tests/regressiontests/test_runner/tests.py | 3 + 11 files changed, 269 insertions(+), 98 deletions(-) (limited to 'docs') diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 2fc2e7ed26..ac7b7a3599 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -29,6 +29,8 @@ class Command(NoArgsCommand): connection = connections[db] verbosity = int(options.get('verbosity')) interactive = options.get('interactive') + # 'reset_sequences' is a stealth option + reset_sequences = options.get('reset_sequences', True) self.style = no_style() @@ -40,7 +42,7 @@ class Command(NoArgsCommand): except ImportError: pass - sql_list = sql_flush(self.style, connection, only_django=True) + sql_list = sql_flush(self.style, connection, only_django=True, reset_sequences=reset_sequences) if interactive: confirm = raw_input("""You have requested a flush of the database. diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 46d3cf28ed..7579cbe8ab 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -98,7 +98,7 @@ def sql_delete(app, style, connection): return output[::-1] # Reverse it, to deal with table dependencies. -def sql_flush(style, connection, only_django=False): +def sql_flush(style, connection, only_django=False, reset_sequences=True): """ Returns a list of the SQL statements used to flush the database. @@ -109,9 +109,8 @@ def sql_flush(style, connection, only_django=False): tables = connection.introspection.django_table_names(only_existing=True) else: tables = connection.introspection.table_names() - statements = connection.ops.sql_flush( - style, tables, connection.introspection.sequence_list() - ) + seqs = connection.introspection.sequence_list() if reset_sequences else () + statements = connection.ops.sql_flush(style, tables, seqs) return statements def sql_custom(app, style, connection): diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index a896f5fd08..6e23ad5bb5 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -748,11 +748,24 @@ class BaseDatabaseOperations(object): the given database tables (without actually removing the tables themselves). + The returned value also includes SQL statements required to reset DB + sequences passed in :param sequences:. + The `style` argument is a Style object as returned by either color_style() or no_style() in django.core.management.color. """ raise NotImplementedError() + def sequence_reset_by_name_sql(self, style, sequences): + """ + Returns a list of the SQL statements required to reset sequences + passed in :param sequences:. + + The `style` argument is a Style object as returned by either + color_style() or no_style() in django.core.management.color. + """ + return [] + def sequence_reset_sql(self, style, model_list): """ Returns a list of the SQL statements required to reset sequences for diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index ec65207ed8..2222f89cf0 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -262,22 +262,25 @@ class DatabaseOperations(BaseDatabaseOperations): for table in tables: sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table)))) sql.append('SET FOREIGN_KEY_CHECKS = 1;') - - # Truncate already resets the AUTO_INCREMENT field from - # MySQL version 5.0.13 onwards. Refs #16961. - if self.connection.mysql_version < (5,0,13): - sql.extend( - ["%s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('TABLE'), - style.SQL_TABLE(self.quote_name(sequence['table'])), - style.SQL_KEYWORD('AUTO_INCREMENT'), - style.SQL_FIELD('= 1'), - ) for sequence in sequences]) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + # Truncate already resets the AUTO_INCREMENT field from + # MySQL version 5.0.13 onwards. Refs #16961. + if self.connection.mysql_version < (5, 0, 13): + return ["%s %s %s %s %s;" % \ + (style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('TABLE'), + style.SQL_TABLE(self.quote_name(sequence['table'])), + style.SQL_KEYWORD('AUTO_INCREMENT'), + style.SQL_FIELD('= 1'), + ) for sequence in sequences] + else: + return [] + def validate_autopk_value(self, value): # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. if value == 0: diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 32ae420ce0..b08113fed7 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -298,18 +298,23 @@ WHEN (new.%(col_name)s IS NULL) for table in tables] # Since we've just deleted all the rows, running our sequence # ALTER code will reset the sequence to 0. - for sequence_info in sequences: - sequence_name = self._get_sequence_name(sequence_info['table']) - table_name = self.quote_name(sequence_info['table']) - column_name = self.quote_name(sequence_info['column'] or 'id') - query = _get_sequence_reset_sql() % {'sequence': sequence_name, - 'table': table_name, - 'column': column_name} - sql.append(query) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + sql = [] + for sequence_info in sequences: + sequence_name = self._get_sequence_name(sequence_info['table']) + table_name = self.quote_name(sequence_info['table']) + column_name = self.quote_name(sequence_info['column'] or 'id') + query = _get_sequence_reset_sql() % {'sequence': sequence_name, + 'table': table_name, + 'column': column_name} + sql.append(query) + return sql + def sequence_reset_sql(self, style, model_list): from django.db import models output = [] diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index e93a15512b..40fe629110 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -85,25 +85,29 @@ class DatabaseOperations(BaseDatabaseOperations): (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables])) )] - - # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements - # to reset sequence indices - for sequence_info in sequences: - table_name = sequence_info['table'] - column_name = sequence_info['column'] - if not (column_name and len(column_name) > 0): - # This will be the case if it's an m2m using an autogenerated - # intermediate table (see BaseDatabaseIntrospection.sequence_list) - column_name = 'id' - sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_TABLE(self.quote_name(table_name)), - style.SQL_FIELD(column_name)) - ) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements + # to reset sequence indices + sql = [] + for sequence_info in sequences: + table_name = sequence_info['table'] + column_name = sequence_info['column'] + if not (column_name and len(column_name) > 0): + # This will be the case if it's an m2m using an autogenerated + # intermediate table (see BaseDatabaseIntrospection.sequence_list) + column_name = 'id' + sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_TABLE(self.quote_name(table_name)), + style.SQL_FIELD(column_name)) + ) + return sql + def tablespace_sql(self, tablespace, inline=False): if inline: return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace) diff --git a/django/test/simple.py b/django/test/simple.py index 4f05284543..bf0219d53f 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -5,7 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.db.models import get_app, get_apps from django.test import _doctest as doctest from django.test.utils import setup_test_environment, teardown_test_environment -from django.test.testcases import OutputChecker, DocTestRunner, TestCase +from django.test.testcases import OutputChecker, DocTestRunner from django.utils import unittest from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule @@ -263,7 +263,7 @@ class DjangoTestSuiteRunner(object): for test in extra_tests: suite.addTest(test) - return reorder_suite(suite, (TestCase,)) + return reorder_suite(suite, (unittest.TestCase,)) def setup_databases(self, **kwargs): from django.db import connections, DEFAULT_DB_ALIAS diff --git a/django/test/testcases.py b/django/test/testcases.py index b9aae21e8e..b60188bf30 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -23,6 +23,7 @@ from django.core import mail from django.core.exceptions import ValidationError, ImproperlyConfigured from django.core.handlers.wsgi import WSGIHandler from django.core.management import call_command +from django.core.management.color import no_style from django.core.signals import request_started from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer, WSGIServerException) @@ -444,10 +445,15 @@ class SimpleTestCase(ut2.TestCase): class TransactionTestCase(SimpleTestCase): + # The class we'll use for the test client self.client. # Can be overridden in derived classes. client_class = Client + # Subclasses can ask for resetting of auto increment sequence before each + # test case + reset_sequences = False + def _pre_setup(self): """Performs any pre-test setup. This includes: @@ -462,22 +468,36 @@ class TransactionTestCase(SimpleTestCase): self._urlconf_setup() mail.outbox = [] + def _reset_sequences(self, db_name): + conn = connections[db_name] + if conn.features.supports_sequence_reset: + sql_list = \ + conn.ops.sequence_reset_by_name_sql(no_style(), + conn.introspection.sequence_list()) + if sql_list: + try: + cursor = conn.cursor() + for sql in sql_list: + cursor.execute(sql) + except Exception: + transaction.rollback_unless_managed(using=db_name) + raise + transaction.commit_unless_managed(using=db_name) + def _fixture_setup(self): - # If the test case has a multi_db=True flag, flush all databases. - # Otherwise, just flush default. - if getattr(self, 'multi_db', False): - databases = connections - else: - databases = [DEFAULT_DB_ALIAS] - for db in databases: - call_command('flush', verbosity=0, interactive=False, database=db, - skip_validation=True) + # If the test case has a multi_db=True flag, act on all databases. + # Otherwise, just on the default DB. + db_names = connections if getattr(self, 'multi_db', False) else [DEFAULT_DB_ALIAS] + for db_name in db_names: + # Reset sequences + if self.reset_sequences: + self._reset_sequences(db_name) if hasattr(self, 'fixtures'): # We have to use this slightly awkward syntax due to the fact # that we're using *args and **kwargs together. call_command('loaddata', *self.fixtures, - **{'verbosity': 0, 'database': db, 'skip_validation': True}) + **{'verbosity': 0, 'database': db_name, 'skip_validation': True}) def _urlconf_setup(self): if hasattr(self, 'urls'): @@ -534,7 +554,12 @@ class TransactionTestCase(SimpleTestCase): conn.close() def _fixture_teardown(self): - pass + # If the test case has a multi_db=True flag, flush all databases. + # Otherwise, just flush default. + databases = connections if getattr(self, 'multi_db', False) else [DEFAULT_DB_ALIAS] + for db in databases: + call_command('flush', verbosity=0, interactive=False, database=db, + skip_validation=True, reset_sequences=False) def _urlconf_teardown(self): if hasattr(self, '_old_root_urlconf'): @@ -808,22 +833,21 @@ class TestCase(TransactionTestCase): if not connections_support_transactions(): return super(TestCase, self)._fixture_setup() + assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances' + # If the test case has a multi_db=True flag, setup all databases. # Otherwise, just use default. - if getattr(self, 'multi_db', False): - databases = connections - else: - databases = [DEFAULT_DB_ALIAS] + db_names = connections if getattr(self, 'multi_db', False) else [DEFAULT_DB_ALIAS] - for db in databases: - transaction.enter_transaction_management(using=db) - transaction.managed(True, using=db) + for db_name in db_names: + transaction.enter_transaction_management(using=db_name) + transaction.managed(True, using=db_name) disable_transaction_methods() from django.contrib.sites.models import Site Site.objects.clear_cache() - for db in databases: + for db in db_names: if hasattr(self, 'fixtures'): call_command('loaddata', *self.fixtures, **{ diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index fd9ae4f038..aae8b25e07 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -188,6 +188,57 @@ Session not saved on 500 responses Django's session middleware will skip saving the session data if the response's status code is 500. +Changes in tests execution +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some changes have been introduced in the execution of tests that might be +backward-incompatible for some testing setups: + +Database flushing in ``django.test.TransactionTestCase`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, the test database was truncated *before* each test run in a +:class:`~django.test.TransactionTestCase`. + +In order to be able to run unit tests in any order and to make sure they are +always isolated from each other, :class:`~django.test.TransactionTestCase` will +now reset the database *after* each test run instead. + +No more implict DB sequences reset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:class:`~django.test.TransactionTestCase` tests used to reset primary key +sequences automatically together with the database flushing actions described +above. + +This has been changed so no sequences are implicitly reset. This can cause +:class:`~django.test.TransactionTestCase` tests that depend on hard-coded +primary key values to break. + +The new :attr:`~django.test.TransactionTestCase.reset_sequences` attribute can +be used to force the old behavior for :class:`~django.test.TransactionTestCase` +that might need it. + +Ordering of tests +^^^^^^^^^^^^^^^^^ + +In order to make sure all ``TestCase`` code starts with a clean database, +tests are now executed in the following order: + +* First, all unittests (including :class:`unittest.TestCase`, + :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and + :class:`~django.test.TransactionTestCase`) are run with no particular ordering + guaranteed nor enforced among them. + +* Then any other tests (e.g. doctests) that may alter the database without + restoring it to its original state are run. + +This should not cause any problems unless you have existing doctests which +assume a :class:`~django.test.TransactionTestCase` executed earlier left some +database state behind or unit tests that rely on some form of state being +preserved after the execution of other tests. Such tests are already very +fragile, and must now be changed to be able to run independently. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index aa274d83c9..1f4c970d3e 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -478,6 +478,32 @@ If there are any circular dependencies in the :setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured`` exception will be raised. +Order in which tests are executed +--------------------------------- + +In order to guarantee that all ``TestCase`` code starts with a clean database, +the Django test runner reorders tests in the following way: + +* First, all unittests (including :class:`unittest.TestCase`, + :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and + :class:`~django.test.TransactionTestCase`) are run with no particular ordering + guaranteed nor enforced among them. + +* Then any other tests (e.g. doctests) that may alter the database without + restoring it to its original state are run. + +.. versionchanged:: 1.5 + Before Django 1.5, the only guarantee was that + :class:`~django.test.TestCase` tests were always ran first, before any other + tests. + +.. note:: + + The new ordering of tests may reveal unexpected dependencies on test case + ordering. This is the case with doctests that relied on state left in the + database by a given :class:`~django.test.TransactionTestCase` test, they + must be updated to be able to run independently. + Other test conditions --------------------- @@ -1109,8 +1135,11 @@ The following is a simple unit test using the request factory:: response = my_view(request) self.assertEqual(response.status_code, 200) -TestCase --------- +Test cases +---------- + +Provided test case classes +~~~~~~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: django.test @@ -1124,16 +1153,19 @@ Normal Python unit test classes extend a base class of Hierarchy of Django unit testing classes +TestCase +^^^^^^^^ + .. class:: TestCase() This class provides some additional capabilities that can be useful for testing Web sites. Converting a normal :class:`unittest.TestCase` to a Django :class:`TestCase` is -easy: just change the base class of your test from :class:`unittest.TestCase` to -:class:`django.test.TestCase`. All of the standard Python unit test -functionality will continue to be available, but it will be augmented with some -useful additions, including: +easy: Just change the base class of your test from `'unittest.TestCase'` to +`'django.test.TestCase'`. All of the standard Python unit test functionality +will continue to be available, but it will be augmented with some useful +additions, including: * Automatic loading of fixtures. @@ -1141,11 +1173,18 @@ useful additions, including: * Creates a TestClient instance. -* Django-specific assertions for testing for things - like redirection and form errors. +* Django-specific assertions for testing for things like redirection and form + errors. + +.. versionchanged:: 1.5 + The order in which tests are run has changed. See `Order in which tests are + executed`_. ``TestCase`` inherits from :class:`~django.test.TransactionTestCase`. +TransactionTestCase +^^^^^^^^^^^^^^^^^^^ + .. class:: TransactionTestCase() Django ``TestCase`` classes make use of database transaction facilities, if @@ -1157,38 +1196,66 @@ behavior, you should use a Django ``TransactionTestCase``. ``TransactionTestCase`` and ``TestCase`` are identical except for the manner in which the database is reset to a known state and the ability for test code -to test the effects of commit and rollback. A ``TransactionTestCase`` resets -the database before the test runs by truncating all tables and reloading -initial data. A ``TransactionTestCase`` may call commit and rollback and -observe the effects of these calls on the database. - -A ``TestCase``, on the other hand, does not truncate tables and reload initial -data at the beginning of a test. Instead, it encloses the test code in a -database transaction that is rolled back at the end of the test. It also -prevents the code under test from issuing any commit or rollback operations -on the database, to ensure that the rollback at the end of the test restores -the database to its initial state. In order to guarantee that all ``TestCase`` -code starts with a clean database, the Django test runner runs all ``TestCase`` -tests first, before any other tests (e.g. doctests) that may alter the -database without restoring it to its original state. - -When running on a database that does not support rollback (e.g. MySQL with the -MyISAM storage engine), ``TestCase`` falls back to initializing the database -by truncating tables and reloading initial data. +to test the effects of commit and rollback: -``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`. +* A ``TransactionTestCase`` resets the database after the test runs by + truncating all tables. A ``TransactionTestCase`` may call commit and rollback + and observe the effects of these calls on the database. + +* A ``TestCase``, on the other hand, does not truncate tables after a test. + Instead, it encloses the test code in a database transaction that is rolled + back at the end of the test. It also prevents the code under test from + issuing any commit or rollback operations on the database, to ensure that the + rollback at the end of the test restores the database to its initial state. + + When running on a database that does not support rollback (e.g. MySQL with the + MyISAM storage engine), ``TestCase`` falls back to initializing the database + by truncating tables and reloading initial data. .. note:: - The ``TestCase`` use of rollback to un-do the effects of the test code - may reveal previously-undetected errors in test code. For example, - test code that assumes primary keys values will be assigned starting at - one may find that assumption no longer holds true when rollbacks instead - of table truncation are being used to reset the database. Similarly, - the reordering of tests so that all ``TestCase`` classes run first may - reveal unexpected dependencies on test case ordering. In such cases a - quick fix is to switch the ``TestCase`` to a ``TransactionTestCase``. - A better long-term fix, that allows the test to take advantage of the - speed benefit of ``TestCase``, is to fix the underlying test problem. + + .. versionchanged:: 1.5 + + Prior to 1.5, ``TransactionTestCase`` flushed the database tables *before* + each test. In Django 1.5, this is instead done *after* the test has been run. + + When the flush took place before the test, it was guaranteed that primary + key values started at one in :class:`~django.test.TransactionTestCase` + tests. + + Tests should not depend on this behaviour, but for legacy tests that do, the + :attr:`~TransactionTestCase.reset_sequences` attribute can be used until + the test has been properly updated. + +.. versionchanged:: 1.5 + The order in which tests are run has changed. See `Order in which tests are + executed`_. + +``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`. + +.. attribute:: TransactionTestCase.reset_sequences + + .. versionadded:: 1.5 + + Setting ``reset_sequences = True`` on a ``TransactionTestCase`` will make + sure sequences are always reset before the test run:: + + class TestsThatDependsOnPrimaryKeySequences(TransactionTestCase): + reset_sequences = True + + def test_animal_pk(self): + lion = Animal.objects.create(name="lion", sound="roar") + # lion.pk is guaranteed to always be 1 + self.assertEqual(lion.pk, 1) + + Unless you are explicitly testing primary keys sequence numbers, it is + recommended that you do not hard code primary key values in tests. + + Using ``reset_sequences = True`` will slow down the test, since the primary + key reset is an relatively expensive database operation. + +SimpleTestCase +^^^^^^^^^^^^^^ .. class:: SimpleTestCase() diff --git a/tests/regressiontests/test_runner/tests.py b/tests/regressiontests/test_runner/tests.py index 8c6dabf771..c723f162a4 100644 --- a/tests/regressiontests/test_runner/tests.py +++ b/tests/regressiontests/test_runner/tests.py @@ -267,6 +267,9 @@ class AutoIncrementResetTest(TransactionTestCase): and check that both times they get "1" as their PK value. That is, we test that AutoField values start from 1 for each transactional test case. """ + + reset_sequences = True + @skipUnlessDBFeature('supports_sequence_reset') def test_autoincrement_reset1(self): p = Person.objects.create(first_name='Jack', last_name='Smith') -- cgit v1.3 From f1128e54746ca211254f4f6b0a37809812957b3e Mon Sep 17 00:00:00 2001 From: Piet Delport Date: Wed, 25 Jul 2012 01:17:27 +0200 Subject: Fix typo. --- docs/ref/databases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 1f4d09f6cb..74e6b48f07 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -111,7 +111,7 @@ outputs a single ``CREATE INDEX`` statement. However, if the database type for the field is either ``varchar`` or ``text`` (e.g., used by ``CharField``, ``FileField``, and ``TextField``), then Django will create an additional index that uses an appropriate `PostgreSQL operator class`_ -for the column. The extra index is necessary to correctly perfrom +for the column. The extra index is necessary to correctly perform lookups that use the ``LIKE`` operator in their SQL, as is done with the ``contains`` and ``startswith`` lookup types. -- cgit v1.3 From 50837434dbafe7d542aa4bd499f50314b1bac36f Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 24 Jul 2012 22:44:28 -0300 Subject: Clarified default name of M2M relationship DB table. --- docs/ref/models/fields.txt | 22 +++++++++++----------- docs/topics/db/models.txt | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) (limited to 'docs') diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 23dcf4bd9f..5039ba4373 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1074,15 +1074,14 @@ the model is related. This works exactly the same as it does for Database Representation ~~~~~~~~~~~~~~~~~~~~~~~ -Behind the scenes, Django creates an intermediary join table to -represent the many-to-many relationship. By default, this table name -is generated using the name of the many-to-many field and the model -that contains it. Since some databases don't support table names above -a certain length, these table names will be automatically truncated to -64 characters and a uniqueness hash will be used. This means you might -see table names like ``author_books_9cdf4``; this is perfectly normal. -You can manually provide the name of the join table using the -:attr:`~ManyToManyField.db_table` option. +Behind the scenes, Django creates an intermediary join table to represent the +many-to-many relationship. By default, this table name is generated using the +name of the many-to-many field and the name of the table for the model that +contains it. Since some databases don't support table names above a certain +length, these table names will be automatically truncated to 64 characters and a +uniqueness hash will be used. This means you might see table names like +``author_books_9cdf4``; this is perfectly normal. You can manually provide the +name of the join table using the :attr:`~ManyToManyField.db_table` option. .. _manytomany-arguments: @@ -1138,8 +1137,9 @@ that control how the relationship functions. .. attribute:: ManyToManyField.db_table The name of the table to create for storing the many-to-many data. If this - is not provided, Django will assume a default name based upon the names of - the two tables being joined. + is not provided, Django will assume a default name based upon the names of: + the table for the model defining the relationship and the name of the field + itself. .. _ref-onetoone: diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 4010ff6f9c..ce15dc9535 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -384,7 +384,8 @@ Extra fields on many-to-many relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When you're only dealing with simple many-to-many relationships such as -mixing and matching pizzas and toppings, a standard :class:`~django.db.models.ManyToManyField` is all you need. However, sometimes +mixing and matching pizzas and toppings, a standard +:class:`~django.db.models.ManyToManyField` is all you need. However, sometimes you may need to associate data with the relationship between two models. For example, consider the case of an application tracking the musical groups -- cgit v1.3 From c3a05d8794ea79aa7321ba8d1a36423a7d202d60 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Tue, 24 Jul 2012 16:55:08 -1000 Subject: Changed the word "brackets" to "parentheses" I want to change the word "brackets" to "parentheses" because when I think of brackets, I think of [], and when I think of parentheses, I think of (), and when I originally read this, I found the word confusing. --- docs/ref/templates/builtins.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 71f57acdbf..500a47c6f1 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -419,7 +419,7 @@ will be interpreted like: if (athlete_list and coach_list) or cheerleader_list -Use of actual brackets in the :ttag:`if` tag is invalid syntax. If you need +Use of actual parentheses in the :ttag:`if` tag is invalid syntax. If you need them to indicate precedence, you should use nested :ttag:`if` tags. :ttag:`if` tags may also use the operators ``==``, ``!=``, ``<``, ``>``, -- cgit v1.3 From c7ac44e64ba0862e77753bf4859102bb1dbfe883 Mon Sep 17 00:00:00 2001 From: nklas Date: Wed, 25 Jul 2012 13:20:26 +0700 Subject: Update docs/topics/signals.txt Fixed a typo. --- docs/topics/signals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index fa668cc8c7..db1bcb03df 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -235,7 +235,7 @@ Remember that you're allowed to change this list of arguments at any time, so ge Sending signals --------------- -There are two ways to send send signals in Django. +There are two ways to send signals in Django. .. method:: Signal.send(sender, **kwargs) .. method:: Signal.send_robust(sender, **kwargs) -- cgit v1.3 From 206c248f1e8be71890d248cc705702a5eda6d0c8 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 25 Jul 2012 10:44:43 +0200 Subject: Ticket 18667: fix typo in CBV doc --- docs/ref/class-based-views/generic-display.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index b90cbf95b2..bbf0d4f05a 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -54,7 +54,7 @@ many projects they are typically the most commonly used views. from article.views import ArticleDetailView urlpatterns = patterns('', - url(r'^(?P[-_\w]+)/$', ArticleDetailView.as_view(), 'article-detail'), + url(r'^(?P[-_\w]+)/$', ArticleDetailView.as_view(), name='article-detail'), ) .. class:: django.views.generic.list.ListView -- cgit v1.3 From 69f4856f23ff61b8816901c1f3369c7d8237d97c Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 25 Jul 2012 10:57:30 +0200 Subject: Fixed a typo in the admin reference docs. Thanks Yohan Boniface for the report. --- docs/ref/contrib/admin/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index ad4f6ba3cd..f28aa4687b 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -115,7 +115,7 @@ subclass:: .. attribute:: ModelAdmin.actions_selection_counter - Controls whether a selection counter is display next to the action dropdown. + Controls whether a selection counter is displayed next to the action dropdown. By default, the admin changelist will display it (``actions_selection_counter = True``). -- cgit v1.3 From 7d06f975fe445f0393455b3eb9ec17dbe04f2ec3 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 25 Jul 2012 22:32:31 +0200 Subject: Fixed #18614 -- Added missing imports in code samples. --- docs/topics/forms/index.txt | 3 +++ 1 file changed, 3 insertions(+) (limited to 'docs') diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index b843107871..4693de6c7e 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -91,6 +91,9 @@ The standard pattern for processing a form in a view looks like this: .. code-block:: python + from django.shortcuts import render + from django.http import HttpResponseRedirect + def contact(request): if request.method == 'POST': # If the form has been submitted... form = ContactForm(request.POST) # A form bound to the POST data -- cgit v1.3 From ab6cd1c839b136cbc94178da433b2e97ab7f6061 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 25 Jul 2012 09:12:59 +0200 Subject: [py3] Updated dict-like data structures for Python 3. The keys/items/values methods return iterators in Python 3, and the iterkeys/items/values methods don't exist in Python 3. The behavior under Python 2 is unchanged. --- django/utils/datastructures.py | 135 +++++++++++++++----------- django/utils/six.py | 9 ++ docs/topics/python3.txt | 15 +++ tests/regressiontests/utils/datastructures.py | 68 +++++++------ 4 files changed, 140 insertions(+), 87 deletions(-) (limited to 'docs') diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 4d265ca719..bbd31ad36c 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,6 +1,7 @@ import copy import warnings from types import GeneratorType +from django.utils import six class MergeDict(object): @@ -31,38 +32,48 @@ class MergeDict(object): except KeyError: return default + # This is used by MergeDicts of MultiValueDicts. def getlist(self, key): for dict_ in self.dicts: - if key in dict_.keys(): + if key in dict_: return dict_.getlist(key) return [] - def iteritems(self): + def _iteritems(self): seen = set() for dict_ in self.dicts: - for item in dict_.iteritems(): - k, v = item + for item in six.iteritems(dict_): + k = item[0] if k in seen: continue seen.add(k) yield item - def iterkeys(self): - for k, v in self.iteritems(): + def _iterkeys(self): + for k, v in self._iteritems(): yield k - def itervalues(self): - for k, v in self.iteritems(): + def _itervalues(self): + for k, v in self._iteritems(): yield v - def items(self): - return list(self.iteritems()) + if six.PY3: + items = _iteritems + keys = _iterkeys + values = _itervalues + else: + iteritems = _iteritems + iterkeys = _iterkeys + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) - def keys(self): - return list(self.iterkeys()) + def keys(self): + return list(self.iterkeys()) - def values(self): - return list(self.itervalues()) + def values(self): + return list(self.itervalues()) def has_key(self, key): for dict_ in self.dicts: @@ -71,7 +82,8 @@ class MergeDict(object): return False __contains__ = has_key - __iter__ = iterkeys + + __iter__ = _iterkeys def copy(self): """Returns a copy of this object.""" @@ -117,7 +129,7 @@ class SortedDict(dict): data = list(data) super(SortedDict, self).__init__(data) if isinstance(data, dict): - self.keyOrder = data.keys() + self.keyOrder = list(six.iterkeys(data)) else: self.keyOrder = [] seen = set() @@ -128,7 +140,7 @@ class SortedDict(dict): def __deepcopy__(self, memo): return self.__class__([(key, copy.deepcopy(value, memo)) - for key, value in self.iteritems()]) + for key, value in six.iteritems(self)]) def __copy__(self): # The Python's default copy implementation will alter the state @@ -162,28 +174,38 @@ class SortedDict(dict): self.keyOrder.remove(result[0]) return result - def items(self): - return zip(self.keyOrder, self.values()) - - def iteritems(self): + def _iteritems(self): for key in self.keyOrder: yield key, self[key] - def keys(self): - return self.keyOrder[:] - - def iterkeys(self): - return iter(self.keyOrder) - - def values(self): - return map(self.__getitem__, self.keyOrder) + def _iterkeys(self): + for key in self.keyOrder: + yield key - def itervalues(self): + def _itervalues(self): for key in self.keyOrder: yield self[key] + if six.PY3: + items = _iteritems + keys = _iterkeys + values = _itervalues + else: + iteritems = _iteritems + iterkeys = _iterkeys + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) + + def keys(self): + return list(self.iterkeys()) + + def values(self): + return list(self.itervalues()) + def update(self, dict_): - for k, v in dict_.iteritems(): + for k, v in six.iteritems(dict_): self[k] = v def setdefault(self, key, default): @@ -226,7 +248,7 @@ class SortedDict(dict): Replaces the normal dict.__repr__ with a version that returns the keys in their sorted order. """ - return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in six.iteritems(self)]) def clear(self): super(SortedDict, self).clear() @@ -356,38 +378,41 @@ class MultiValueDict(dict): """Appends an item to the internal list associated with key.""" self.setlistdefault(key).append(value) - def items(self): - """ - Returns a list of (key, value) pairs, where value is the last item in - the list associated with the key. - """ - return [(key, self[key]) for key in self.keys()] - - def iteritems(self): + def _iteritems(self): """ Yields (key, value) pairs, where value is the last item in the list associated with the key. """ - for key in self.keys(): - yield (key, self[key]) - - def lists(self): - """Returns a list of (key, list) pairs.""" - return super(MultiValueDict, self).items() + for key in self: + yield key, self[key] - def iterlists(self): + def _iterlists(self): """Yields (key, list) pairs.""" - return super(MultiValueDict, self).iteritems() + return six.iteritems(super(MultiValueDict, self)) - def values(self): - """Returns a list of the last value on every key list.""" - return [self[key] for key in self.keys()] - - def itervalues(self): + def _itervalues(self): """Yield the last value on every key list.""" - for key in self.iterkeys(): + for key in self: yield self[key] + if six.PY3: + items = _iteritems + lists = _iterlists + values = _itervalues + else: + iteritems = _iteritems + iterlists = _iterlists + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) + + def lists(self): + return list(self.iterlists()) + + def values(self): + return list(self.itervalues()) + def copy(self): """Returns a shallow copy of this object.""" return copy.copy(self) @@ -410,7 +435,7 @@ class MultiValueDict(dict): self.setlistdefault(key).append(value) except TypeError: raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): self.setlistdefault(key).append(value) def dict(self): diff --git a/django/utils/six.py b/django/utils/six.py index c74f9fa7df..e226bba09e 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -355,4 +355,13 @@ def with_metaclass(meta, base=object): ### Additional customizations for Django ### +if PY3: + _iterlists = "lists" +else: + _iterlists = "iterlists" + +def iterlists(d): + """Return an iterator over the values of a MultiValueDict.""" + return getattr(d, _iterlists)() + add_move(MovedModule("_dummy_thread", "dummy_thread")) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 7c1cb53150..3f799edac7 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -120,3 +120,18 @@ If you need different code in Python 2 and Python 3, check :data:`six.PY3`:: This is a last resort solution when :mod:`six` doesn't provide an appropriate function. + +.. module:: django.utils.six + +Customizations of six +===================== + +The version of six bundled with Django includes a few additional tools: + +.. function:: iterlists(MultiValueDict) + + Returns an iterator over the lists of values of a + :class:`~django.utils.datastructures.MultiValueDict`. This replaces + :meth:`~django.utils.datastructures.MultiValueDict.iterlists()` on Python + 2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on + Python 3. diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index aea5393aab..dbc65d37a8 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -9,6 +9,7 @@ import warnings from django.test import SimpleTestCase from django.utils.datastructures import (DictWrapper, ImmutableList, MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) +from django.utils import six class SortedDictTests(SimpleTestCase): @@ -25,19 +26,19 @@ class SortedDictTests(SimpleTestCase): self.d2[7] = 'seven' def test_basic_methods(self): - self.assertEqual(self.d1.keys(), [7, 1, 9]) - self.assertEqual(self.d1.values(), ['seven', 'one', 'nine']) - self.assertEqual(self.d1.items(), [(7, 'seven'), (1, 'one'), (9, 'nine')]) + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9]) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'one', 'nine']) + self.assertEqual(list(six.iteritems(self.d1)), [(7, 'seven'), (1, 'one'), (9, 'nine')]) def test_overwrite_ordering(self): - """ Overwriting an item keeps it's place. """ + """ Overwriting an item keeps its place. """ self.d1[1] = 'ONE' - self.assertEqual(self.d1.values(), ['seven', 'ONE', 'nine']) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'ONE', 'nine']) def test_append_items(self): """ New items go to the end. """ self.d1[0] = 'nil' - self.assertEqual(self.d1.keys(), [7, 1, 9, 0]) + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9, 0]) def test_delete_and_insert(self): """ @@ -45,18 +46,22 @@ class SortedDictTests(SimpleTestCase): at the end. """ del self.d2[7] - self.assertEqual(self.d2.keys(), [1, 9, 0]) + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0]) self.d2[7] = 'lucky number 7' - self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0, 7]) - def test_change_keys(self): - """ - Changing the keys won't do anything, it's only a copy of the - keys dict. - """ - k = self.d2.keys() - k.remove(9) - self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + if not six.PY3: + def test_change_keys(self): + """ + Changing the keys won't do anything, it's only a copy of the + keys dict. + + This test doesn't make sense under Python 3 because keys is + an iterator. + """ + k = self.d2.keys() + k.remove(9) + self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) def test_init_keys(self): """ @@ -68,18 +73,18 @@ class SortedDictTests(SimpleTestCase): tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) d = SortedDict(tuples) - self.assertEqual(d.keys(), [2, 1]) + self.assertEqual(list(six.iterkeys(d)), [2, 1]) real_dict = dict(tuples) - self.assertEqual(sorted(real_dict.values()), ['one', 'second-two']) + self.assertEqual(sorted(six.itervalues(real_dict)), ['one', 'second-two']) # Here the order of SortedDict values *is* what we are testing - self.assertEqual(d.values(), ['second-two', 'one']) + self.assertEqual(list(six.itervalues(d)), ['second-two', 'one']) def test_overwrite(self): self.d1[1] = 'not one' self.assertEqual(self.d1[1], 'not one') - self.assertEqual(self.d1.keys(), self.d1.copy().keys()) + self.assertEqual(list(six.iterkeys(self.d1)), list(six.iterkeys(self.d1.copy()))) def test_append(self): self.d1[13] = 'thirteen' @@ -115,8 +120,8 @@ class SortedDictTests(SimpleTestCase): def test_copy(self): orig = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) copied = copy.copy(orig) - self.assertEqual(orig.keys(), [1, 0, 2]) - self.assertEqual(copied.keys(), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(orig)), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(copied)), [1, 0, 2]) def test_clear(self): self.d1.clear() @@ -178,12 +183,12 @@ class MergeDictTests(SimpleTestCase): self.assertEqual(mm.getlist('key4'), ['value5', 'value6']) self.assertEqual(mm.getlist('undefined'), []) - self.assertEqual(sorted(mm.keys()), ['key1', 'key2', 'key4']) - self.assertEqual(len(mm.values()), 3) + self.assertEqual(sorted(six.iterkeys(mm)), ['key1', 'key2', 'key4']) + self.assertEqual(len(list(six.itervalues(mm))), 3) - self.assertTrue('value1' in mm.values()) + self.assertTrue('value1' in six.itervalues(mm)) - self.assertEqual(sorted(mm.items(), key=lambda k: k[0]), + self.assertEqual(sorted(six.iteritems(mm), key=lambda k: k[0]), [('key1', 'value1'), ('key2', 'value3'), ('key4', 'value6')]) @@ -201,10 +206,10 @@ class MultiValueDictTests(SimpleTestCase): self.assertEqual(d['name'], 'Simon') self.assertEqual(d.get('name'), 'Simon') self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) - self.assertEqual(list(d.iteritems()), + self.assertEqual(list(six.iteritems(d)), [('position', 'Developer'), ('name', 'Simon')]) - self.assertEqual(list(d.iterlists()), + self.assertEqual(list(six.iterlists(d)), [('position', ['Developer']), ('name', ['Adrian', 'Simon'])]) @@ -224,8 +229,7 @@ class MultiValueDictTests(SimpleTestCase): d.setlist('lastname', ['Holovaty', 'Willison']) self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison']) - self.assertEqual(d.values(), ['Developer', 'Simon', 'Willison']) - self.assertEqual(list(d.itervalues()), + self.assertEqual(list(six.itervalues(d)), ['Developer', 'Simon', 'Willison']) def test_appendlist(self): @@ -260,8 +264,8 @@ class MultiValueDictTests(SimpleTestCase): 'pm': ['Rory'], }) d = mvd.dict() - self.assertEqual(d.keys(), mvd.keys()) - for key in mvd.keys(): + self.assertEqual(list(six.iterkeys(d)), list(six.iterkeys(mvd))) + for key in six.iterkeys(mvd): self.assertEqual(d[key], mvd[key]) self.assertEqual({}, MultiValueDict().dict()) -- cgit v1.3