From 5306285ce21d59742d67a447c9c1a1e4be6d86a4 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 18 Apr 2013 20:38:07 +0200 Subject: Complemented documentation following commit be9ae693c Refs #17840. Thanks Carl Meyer for noticing the omission. --- docs/ref/forms/fields.txt | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'docs/ref') diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index c8b8044d26..03898e60ea 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -467,6 +467,10 @@ For each field, we describe the default widget used if you don't specify The ``max_value`` and ``min_value`` error messages may contain ``%(limit_value)s``, which will be substituted by the appropriate limit. + .. versionchanged:: 1.6 + Similarly, the ``max_digits``, ``max_decimal_places`` and + ``max_whole_digits`` error messages may contain ``%(max)s``. + Takes four optional arguments: .. attribute:: max_value @@ -1013,6 +1017,11 @@ objects (in the case of ``ModelMultipleChoiceField``) into the The empty and normalized values were changed to be consistently ``QuerySets`` instead of ``[]`` and ``QuerySet`` respectively. + .. versionchanged:: 1.6 + The ``invalid_choice`` message may contain ``%(value)s`` and the + ``invalid_pk_value`` message may contain ``%(pk)s``, which will be + substituted by the appropriate values. + Allows the selection of one or more model objects, suitable for representing a many-to-many relation. As with :class:`ModelChoiceField`, you can use ``label_from_instance`` to customize the object -- cgit v1.3 From 78c842a3230f026ad678d243e5459cd6b314d99a Mon Sep 17 00:00:00 2001 From: Juan Catalano Date: Sun, 24 Mar 2013 22:53:48 -0700 Subject: Adapted uses of versionchanged/versionadded to the new form. Refs #20104. --- docs/_ext/djangodocs.py | 2 +- docs/howto/custom-management-commands.txt | 2 +- docs/howto/legacy-databases.txt | 4 +-- docs/intro/contributing.txt | 4 +-- docs/ref/class-based-views/base.txt | 1 + docs/ref/class-based-views/generic-date-based.txt | 2 +- docs/ref/class-based-views/mixins-date-based.txt | 1 + docs/ref/class-based-views/mixins-editing.txt | 8 ++--- docs/ref/class-based-views/mixins-simple.txt | 1 - docs/ref/clickjacking.txt | 1 + docs/ref/contrib/admin/index.txt | 7 +++- docs/ref/contrib/contenttypes.txt | 24 ++++++------- docs/ref/contrib/gis/geoquerysets.txt | 1 + docs/ref/contrib/sites.txt | 1 + docs/ref/databases.txt | 1 + docs/ref/django-admin.txt | 19 +++++++---- docs/ref/exceptions.txt | 1 + docs/ref/forms/fields.txt | 3 ++ docs/ref/forms/models.txt | 4 +-- docs/ref/forms/validation.txt | 8 ++--- docs/ref/forms/widgets.txt | 1 + docs/ref/middleware.txt | 1 + docs/ref/models/fields.txt | 5 +-- docs/ref/models/instances.txt | 5 +-- docs/ref/models/querysets.txt | 32 ++++++++++-------- docs/ref/request-response.txt | 19 ++++++----- docs/ref/settings.txt | 23 +++++++------ docs/ref/template-response.txt | 14 ++++---- docs/ref/templates/api.txt | 1 + docs/ref/templates/builtins.txt | 41 ++++++++++++----------- docs/ref/unicode.txt | 28 ++++++++-------- docs/releases/1.0.txt | 1 + docs/topics/auth/customizing.txt | 6 ++-- docs/topics/auth/default.txt | 10 +++--- docs/topics/db/managers.txt | 1 + docs/topics/db/multi-db.txt | 1 + docs/topics/db/queries.txt | 2 ++ docs/topics/db/sql.txt | 1 + docs/topics/db/transactions.txt | 34 ++++++++++--------- docs/topics/forms/formsets.txt | 2 ++ docs/topics/http/middleware.txt | 1 + docs/topics/http/sessions.txt | 2 ++ docs/topics/http/shortcuts.txt | 2 ++ docs/topics/i18n/translation.txt | 1 + docs/topics/pagination.txt | 4 +-- docs/topics/serialization.txt | 12 +++---- docs/topics/signals.txt | 2 +- docs/topics/testing/overview.txt | 7 ++++ 48 files changed, 206 insertions(+), 148 deletions(-) (limited to 'docs/ref') diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 5878037f7f..3ae770da20 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -66,7 +66,7 @@ class VersionDirective(Directive): msg = """Only one argument accepted for directive '{directive_name}::'. Comments should be provided as content, not as an extra argument.""".format(directive_name=self.name) - raise ValueError(msg) + raise self.error(msg) env = self.state.document.settings.env ret = [] diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 7a31fc44e3..34e68d3700 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -256,7 +256,7 @@ All attributes can be set in your derived class and can be used in .. versionadded:: 1.6 -The ``leave_locale_alone`` option was added in Django 1.6. + The ``leave_locale_alone`` option was added in Django 1.6. Methods ------- diff --git a/docs/howto/legacy-databases.txt b/docs/howto/legacy-databases.txt index 6846e4b2df..4522ca01c0 100644 --- a/docs/howto/legacy-databases.txt +++ b/docs/howto/legacy-databases.txt @@ -75,8 +75,8 @@ access to your precious data on a model by model basis. .. versionchanged:: 1.6 -The behavior by which introspected models are created as unmanaged ones is new -in Django 1.6. + The behavior by which introspected models are created as unmanaged ones is new + in Django 1.6. Install the core Django tables ============================== diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 8747375b0a..dd7136c877 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -389,8 +389,8 @@ This is a new feature, so it should be documented. Add the following on line .. versionadded:: 1.5 - The current value of the field will be displayed as a clickable link above the - input widget. + The current value of the field will be displayed as a clickable link above the + input widget. For more information on writing documentation, including an explanation of what the ``versionadded`` bit is all about, see diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt index 2073458314..3ba7c38c43 100644 --- a/docs/ref/class-based-views/base.txt +++ b/docs/ref/class-based-views/base.txt @@ -105,6 +105,7 @@ TemplateView in the URL. .. versionchanged:: 1.5 + The context used to be populated with a ``{{ params }}`` dictionary of the parameters captured in the URL. Now those parameters are first-level context variables. diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 4144c382f8..4dcb788779 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -142,7 +142,7 @@ YearArchiveView .. versionchanged:: 1.5 - Previously, this returned a string. + Previously, this returned a string. * ``next_year``: A :class:`~datetime.date` object representing the first day of the next year, according to diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt index 75f2a77615..1a1a4d531b 100644 --- a/docs/ref/class-based-views/mixins-date-based.txt +++ b/docs/ref/class-based-views/mixins-date-based.txt @@ -330,5 +330,6 @@ BaseDateListView :meth:`QuerySet.dates()`. .. versionchanged:: 1.5 + The ``ordering`` parameter was added, and the default order was changed to ascending. diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index a4175369aa..3f32269742 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -206,10 +206,10 @@ ProcessFormView .. versionadded:: 1.6 - ``success_url`` may contain dictionary string formatting, which - will be interpolated against the object's field attributes. For - example, you could use ``success_url="/parent/%(parent_id)s/"`` to - redirect to a URL composed out of the ``parent_id`` field on a model. + ``success_url`` may contain dictionary string formatting, which + will be interpolated against the object's field attributes. For + example, you could use ``success_url="/parent/%(parent_id)s/"`` to + redirect to a URL composed out of the ``parent_id`` field on a model. .. method:: get_success_url() diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index 51b0386654..6796675529 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -67,7 +67,6 @@ TemplateResponseMixin .. attribute:: content_type .. versionadded:: 1.5 - The ``content_type`` attribute was added. The content type to use for the response. ``content_type`` is passed as a keyword argument to ``response_class``. Default is ``None`` -- diff --git a/docs/ref/clickjacking.txt b/docs/ref/clickjacking.txt index ce27148ad3..0b81243b92 100644 --- a/docs/ref/clickjacking.txt +++ b/docs/ref/clickjacking.txt @@ -62,6 +62,7 @@ To set the same ``X-Frame-Options`` value for all responses in your site, put ) .. versionchanged:: 1.6 + This middleware is enabled in the settings file generated by :djadmin:`startproject`. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index c567bc1db4..43f0398566 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -18,6 +18,7 @@ The admin is enabled in the default project template used by :djadmin:`startproject`. .. versionchanged:: 1.6 + In previous versions, the admin wasn't enabled by default. For reference, here are the requirements: @@ -1341,6 +1342,7 @@ templates used by the :class:`ModelAdmin` views: return qs.filter(author=request.user) .. versionchanged:: 1.6 + The ``get_queryset`` method was previously named ``queryset``. .. method:: ModelAdmin.message_user(request, message, level=messages.INFO, extra_tags='', fail_silently=False) @@ -1348,7 +1350,9 @@ templates used by the :class:`ModelAdmin` views: Sends a message to the user using the :mod:`django.contrib.messages` backend. See the :ref:`custom ModelAdmin example `. - .. versionadded:: 1.5 + .. versionchanged:: 1.5 + + Keyword arguments were added in Django 1.5. Keyword arguments allow you to change the message level, add extra CSS tags, or fail silently if the ``contrib.messages`` framework is not @@ -1451,6 +1455,7 @@ in your own admin JavaScript without including a second copy, you can use the ``django.jQuery`` object on changelist and add/edit views. .. versionchanged:: 1.6 + The embedded jQuery has been upgraded from 1.4.2 to 1.9.1. The :class:`ModelAdmin` class requires jQuery by default, so there is no need diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index 388172c43e..4fa119bc70 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -234,18 +234,18 @@ lookup:: .. versionadded:: 1.5 -Prior to Django 1.5, -:meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model` and -:meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_models` -always returned the :class:`~django.contrib.contenttypes.models.ContentType` -associated with the concrete model of the specified one(s). That means there -was no way to retrieve the -:class:`~django.contrib.contenttypes.models.ContentType` of a proxy model -using those methods. As of Django 1.5 you can now pass a boolean flag – -``for_concrete_model`` and ``for_concrete_models`` respectively – to specify -wether or not you want to retrieve the -:class:`~django.contrib.contenttypes.models.ContentType` for the concrete or -direct model. + Prior to Django 1.5, + :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model` and + :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_models` + always returned the :class:`~django.contrib.contenttypes.models.ContentType` + associated with the concrete model of the specified one(s). That means there + was no way to retrieve the + :class:`~django.contrib.contenttypes.models.ContentType` of a proxy model + using those methods. As of Django 1.5 you can now pass a boolean flag – + ``for_concrete_model`` and ``for_concrete_models`` respectively – to specify + wether or not you want to retrieve the + :class:`~django.contrib.contenttypes.models.ContentType` for the concrete or + direct model. Generic relations ================= diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index 97217e3c38..5e51e4cbf3 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -950,6 +950,7 @@ __ http://geohash.org/ *Availability*: PostGIS, SpatiaLite .. versionchanged:: 1.5 + ``geojson`` support for Spatialite > 3.0 has been added. Attaches a ``geojson`` attribute to every model in the queryset that contains the diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 139a9b377f..65838dfa3e 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -252,6 +252,7 @@ Enabling the sites framework ============================ .. versionchanged:: 1.6 + In previous versions, the sites framework was enabled by default. To enable the sites framework, follow these steps: diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 395abd90dd..35f0cc6b41 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -182,6 +182,7 @@ Django supports MySQL 5.0.3 and higher. data on all database schema. Django's ``inspectdb`` feature uses it. .. versionchanged:: 1.5 + The minimum version requirement of MySQL 5.0.3 was set in Django 1.5. Django expects the database to support Unicode (UTF-8 encoding) and delegates to diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 1d3f1b8d1d..ec49705add 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -98,6 +98,7 @@ Can be run as a cronjob or directly to clean out old data from the database (only expired sessions at the moment). .. versionchanged:: 1.5 + :djadmin:`cleanup` is deprecated. Use :djadmin:`clearsessions` instead. compilemessages @@ -122,7 +123,7 @@ Example usage:: .. versionchanged:: 1.6 -Added the ability to specify multiple locales. + Added the ability to specify multiple locales. createcachetable ---------------- @@ -173,6 +174,7 @@ The :djadminopt:`--all` option may be provided to display all settings, even if they have Django's default value. Such settings are prefixed by ``"###"``. .. versionadded:: 1.6 + The :djadminopt:`--all` option was added. dumpdata @@ -307,8 +309,8 @@ database to introspect. .. versionchanged:: 1.6 -The behavior by which introspected models are created as unmanaged ones is new -in Django 1.6. + The behavior by which introspected models are created as unmanaged ones is new + in Django 1.6. loaddata ------------------------------ @@ -467,7 +469,7 @@ You can also use commas to separate multiple locales:: .. versionchanged:: 1.6 -Added the ability to specify multiple locales. + Added the ability to specify multiple locales. .. django-admin-option:: --domain @@ -778,8 +780,6 @@ 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: @@ -807,9 +807,13 @@ behavior you can use the ``--no-startup`` option. e.g.:: django-admin.py shell --plain --no-startup +.. versionadded:: 1.5 + + The ``--interface`` option was added in Django 1.5. + .. versionadded:: 1.6 -The ``--no-startup`` option was added in Django 1.6. + The ``--no-startup`` option was added in Django 1.6. sql ------------------------- @@ -1353,6 +1357,7 @@ for any other exception. If you specify ``--traceback``, ``django-admin.py`` will also output a full stack trace when a ``CommandError`` is raised. .. versionchanged:: 1.6 + Previously, Django didn't show a full stack trace by default for exceptions other than ``CommandError``. diff --git a/docs/ref/exceptions.txt b/docs/ref/exceptions.txt index 93bb9ed251..f9a1715180 100644 --- a/docs/ref/exceptions.txt +++ b/docs/ref/exceptions.txt @@ -138,6 +138,7 @@ the underlying database exceptions. See :pep:`249`, the Python Database API Specification v2.0, for further information. .. versionchanged:: 1.6 + Previous version of Django only wrapped ``DatabaseError`` and ``IntegrityError``. diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 03898e60ea..29f889445d 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -468,6 +468,7 @@ For each field, we describe the default widget used if you don't specify ``%(limit_value)s``, which will be substituted by the appropriate limit. .. versionchanged:: 1.6 + Similarly, the ``max_digits``, ``max_decimal_places`` and ``max_whole_digits`` error messages may contain ``%(max)s``. @@ -1014,10 +1015,12 @@ objects (in the case of ``ModelMultipleChoiceField``) into the ``invalid_pk_value`` .. versionchanged:: 1.5 + The empty and normalized values were changed to be consistently ``QuerySets`` instead of ``[]`` and ``QuerySet`` respectively. .. versionchanged:: 1.6 + The ``invalid_choice`` message may contain ``%(value)s`` and the ``invalid_pk_value`` message may contain ``%(pk)s``, which will be substituted by the appropriate values. diff --git a/docs/ref/forms/models.txt b/docs/ref/forms/models.txt index dd0a422fd0..7e3a1470b6 100644 --- a/docs/ref/forms/models.txt +++ b/docs/ref/forms/models.txt @@ -42,7 +42,7 @@ Model Form Functions .. versionchanged:: 1.6 - The ``widgets`` and the ``validate_max`` parameters were added. + The ``widgets`` and the ``validate_max`` parameters were added. .. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False) @@ -57,4 +57,4 @@ Model Form Functions .. versionchanged:: 1.6 - The ``widgets`` and the ``validate_max`` parameters were added. + The ``widgets`` and the ``validate_max`` parameters were added. diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 978c985b55..3aaa69b6ea 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -359,7 +359,7 @@ considering aren't valid, we must remember to remove them from the .. versionchanged:: 1.5 -Django used to remove the ``cleaned_data`` attribute entirely if there were -any errors in the form. Since version 1.5, ``cleaned_data`` is present even if -the form doesn't validate, but it contains only field values that did -validate. + Django used to remove the ``cleaned_data`` attribute entirely if there were + any errors in the form. Since version 1.5, ``cleaned_data`` is present even if + the form doesn't validate, but it contains only field values that did + validate. diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 514e8b3dc0..678f2e6949 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -522,6 +522,7 @@ Selector and checkbox widgets ``True`` if the checkbox should be checked for that value. .. versionchanged:: 1.5 + Exceptions from ``check_test`` used to be silenced by its caller, this is no longer the case, they will propagate upwards. diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 20bb2fb751..03885a2215 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -206,6 +206,7 @@ Transaction middleware .. class:: TransactionMiddleware .. versionchanged:: 1.6 + ``TransactionMiddleware`` is deprecated. The documentation of transactions contains :ref:`upgrade instructions `. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 7ef251c907..d322904ec9 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -378,6 +378,7 @@ If you need to accept :attr:`~Field.null` values then use :class:`NullBooleanField` instead. .. versionchanged:: 1.6 + The default value of ``BooleanField`` was changed from ``False`` to ``None`` when :attr:`Field.default` isn't defined. @@ -956,8 +957,8 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional .. versionadded:: 1.5 -The current value of the field will be displayed as a clickable link above the -input widget. + The current value of the field will be displayed as a clickable link above the + input widget. Relationship fields diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 9f583c42ac..b4b162a9ea 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -297,8 +297,9 @@ follows this algorithm: didn't update anything, Django executes an ``INSERT``. .. versionchanged:: 1.6 - Previously Django used ``SELECT`` - if not found ``INSERT`` else ``UPDATE`` - algorithm. The old algorithm resulted in one more query in ``UPDATE`` case. + + Previously Django used ``SELECT`` - if not found ``INSERT`` else ``UPDATE`` + algorithm. The old algorithm resulted in one more query in ``UPDATE`` case. The one gotcha here is that you should be careful not to specify a primary-key value explicitly when saving new objects, if you cannot guarantee the diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 9c1337d59f..3dde0d5411 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -554,11 +554,13 @@ Returns a ``DateQuerySet`` — a ``QuerySet`` that evaluates to a list of particular kind within the contents of the ``QuerySet``. .. versionchanged:: 1.6 + ``dates`` used to return a list of :class:`datetime.datetime` objects. ``field`` should be the name of a ``DateField`` of your model. .. versionchanged:: 1.6 + ``dates`` used to accept operating on a ``DateTimeField``. ``kind`` should be either ``"year"``, ``"month"`` or ``"day"``. Each @@ -1121,11 +1123,11 @@ to ``defer()``:: .. 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, doing so will result in an error. + 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, doing so will result in an error. .. note:: @@ -1193,20 +1195,20 @@ 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. Also note that using :meth:`only` and omitting a field requested -using :meth:`select_related` is an error as well. +options. .. versionchanged:: 1.5 -.. note:: + Using :meth:`only` and omitting a field requested using + :meth:`select_related` is an error as well. - When calling :meth:`~django.db.models.Model.save()` for instances with - deferred fields, only the loaded fields will be saved. See - :meth:`~django.db.models.Model.save()` for more details. + .. note:: + + When calling :meth:`~django.db.models.Model.save()` for instances with + deferred fields, only the loaded fields will be saved. See + :meth:`~django.db.models.Model.save()` for more details. using ~~~~~ @@ -1424,6 +1426,7 @@ 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. .. versionadded:: 1.5 + The ``batch_size`` parameter was added in version 1.5. count @@ -1725,7 +1728,8 @@ methods on your models. It does, however, emit the (including cascaded deletions). .. versionadded:: 1.5 - Allow fast-path deletion of objects + + Allow fast-path deletion of objects. Django needs to fetch objects into memory to send signals and handle cascades. However, if there are no cascades and no signals, then Django may take a diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 0f62741c5d..2fac7f2f9c 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -94,6 +94,7 @@ All attributes should be considered read-only, unless stated otherwise below. :attr:`HttpRequest.body` attribute instead. .. versionchanged:: 1.5 + Before Django 1.5, HttpRequest.POST contained non-form data. It's possible that a request can come in via POST with an empty ``POST`` @@ -563,19 +564,19 @@ streaming response if (and only if) no middleware accesses the .. versionchanged:: 1.5 -This technique is fragile and was deprecated in Django 1.5. If you need the -response to be streamed from the iterator to the client, you should use the -:class:`StreamingHttpResponse` class instead. + This technique is fragile and was deprecated in Django 1.5. If you need the + response to be streamed from the iterator to the client, you should use the + :class:`StreamingHttpResponse` class instead. -As of Django 1.7, when :class:`HttpResponse` is instantiated with an -iterator, it will consume it immediately, store the response content as a -string, and discard the iterator. + As of Django 1.7, when :class:`HttpResponse` is instantiated with an + iterator, it will consume it immediately, store the response content as a + string, and discard the iterator. .. versionchanged:: 1.5 -You can now use :class:`HttpResponse` as a file-like object even if it was -instantiated with an iterator. Django will consume and save the content of -the iterator on first access. + You can now use :class:`HttpResponse` as a file-like object even if it was + instantiated with an iterator. Django will consume and save the content of + the iterator on first access. Setting headers ~~~~~~~~~~~~~~~ diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 1fc9d2ff92..0d8b5bfd56 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1486,6 +1486,7 @@ randomly-generated ``SECRET_KEY`` to each new project. execution vulnerabilities. .. versionchanged:: 1.5 + Django will now refuse to start if :setting:`SECRET_KEY` is not set. .. setting:: SECURE_PROXY_SSL_HEADER @@ -1771,7 +1772,7 @@ See also :setting:`DATE_INPUT_FORMATS` and :setting:`DATETIME_INPUT_FORMATS`. .. versionchanged:: 1.6 -Input format with microseconds has been added. + Input format with microseconds has been added. .. _datetime: http://docs.python.org/library/datetime.html#strftime-strptime-behavior @@ -2055,11 +2056,11 @@ decorator, for example. .. versionchanged:: 1.5 -This setting now also accepts view function names and -:ref:`named URL patterns ` which can be used to reduce -configuration duplication since you no longer have to define the URL in two -places (``settings`` and URLconf). -For backward compatibility reasons the default remains unchanged. + This setting now also accepts view function names and + :ref:`named URL patterns ` which can be used to reduce + configuration duplication since you no longer have to define the URL in two + places (``settings`` and URLconf). + For backward compatibility reasons the default remains unchanged. .. setting:: LOGIN_URL @@ -2073,11 +2074,11 @@ The URL where requests are redirected for login, especially when using the .. versionchanged:: 1.5 -This setting now also accepts view function names and -:ref:`named URL patterns ` which can be used to reduce -configuration duplication since you no longer have to define the URL in two -places (``settings`` and URLconf). -For backward compatibility reasons the default remains unchanged. + This setting now also accepts view function names and + :ref:`named URL patterns ` which can be used to reduce + configuration duplication since you no longer have to define the URL in two + places (``settings`` and URLconf). + For backward compatibility reasons the default remains unchanged. .. setting:: LOGOUT_URL diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt index 5c13ec7d96..cdefe2fae8 100644 --- a/docs/ref/template-response.txt +++ b/docs/ref/template-response.txt @@ -78,13 +78,13 @@ Methods .. versionchanged:: 1.5 - Historically, this parameter was only called ``mimetype`` (now - deprecated), 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, - :setting:`DEFAULT_CONTENT_TYPE` is used. + Historically, this parameter was only called ``mimetype`` (now + deprecated), 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, + :setting:`DEFAULT_CONTENT_TYPE` is used. .. method:: SimpleTemplateResponse.resolve_context(context) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 0162f78eed..677aa13cbb 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -272,6 +272,7 @@ Every context contains ``True``, ``False`` and ``None``. As you would expect, these variables resolve to the corresponding Python objects. .. versionadded:: 1.5 + Before Django 1.5, these variables weren't a special case, and they resolved to ``None`` unless you defined them in the context. diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 123e114c4a..474fb4d84a 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -191,19 +191,19 @@ call to ``{% cycle %}`` doesn't specify silent:: .. versionchanged:: 1.6 -To improve safety, future versions of ``cycle`` will automatically escape -their output. You're encouraged to activate this behavior by loading -``cycle`` from the ``future`` template library:: + To improve safety, future versions of ``cycle`` will automatically escape + their output. You're encouraged to activate this behavior by loading + ``cycle`` from the ``future`` template library:: - {% load cycle from future %} + {% load cycle from future %} -When using the ``future`` version, you can disable auto-escaping with:: + When using the ``future`` version, you can disable auto-escaping with:: - {% for o in some_list %} - - ... - - {% endfor %} + {% for o in some_list %} + + ... + + {% endfor %} .. templatetag:: debug @@ -294,21 +294,21 @@ to escape the variables in the firstof tag, you must do so explicitly:: .. versionchanged:: 1.6 -To improve safety, future versions of ``firstof`` will automatically escape -their output. You're encouraged to activate this behavior by loading -``firstof`` from the ``future`` template library:: + To improve safety, future versions of ``firstof`` will automatically escape + their output. You're encouraged to activate this behavior by loading + ``firstof`` from the ``future`` template library:: - {% load firstof from future %} + {% load firstof from future %} -When using the ``future`` version, you can disable auto-escaping with:: + When using the ``future`` version, you can disable auto-escaping with:: - {% autoescape off %} - {% firstof var1 var2 var3 "fallback value" %} - {% endautoescape %} + {% autoescape off %} + {% firstof var1 var2 var3 "fallback value" %} + {% endautoescape %} -Or if only some variables should be escaped, you can use:: + Or if only some variables should be escaped, you can use:: - {% firstof var1 var2|safe var3 "fallback value"|safe %} + {% firstof var1 var2|safe var3 "fallback value"|safe %} .. templatetag:: for @@ -1065,6 +1065,7 @@ by the context as to the current application. Don't forget to put quotes around the function path or pattern name! .. versionchanged:: 1.5 + The first parameter used not to be quoted, which was inconsistent with other template tags. Since Django 1.5, it is evaluated according to the usual rules: it can be a quoted string or a variable that will be diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index bd5bdc96a9..e5074285e4 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -47,26 +47,26 @@ You can use Unicode strings, or you can use normal strings (sometimes called .. versionchanged:: 1.5 -In Python 3, the logic is reversed, that is normal strings are Unicode, and -when you want to specifically create a bytestring, you have to prefix the -string with a 'b'. As we are doing in Django code from version 1.5, -we recommend that you import ``unicode_literals`` from the __future__ library -in your code. Then, when you specifically want to create a bytestring literal, -prefix the string with 'b'. + In Python 3, the logic is reversed, that is normal strings are Unicode, and + when you want to specifically create a bytestring, you have to prefix the + string with a 'b'. As we are doing in Django code from version 1.5, + we recommend that you import ``unicode_literals`` from the __future__ library + in your code. Then, when you specifically want to create a bytestring literal, + prefix the string with 'b'. -Python 2 legacy:: + Python 2 legacy:: - my_string = "This is a bytestring" - my_unicode = u"This is an Unicode string" + my_string = "This is a bytestring" + my_unicode = u"This is an Unicode string" -Python 2 with unicode literals or Python 3:: + Python 2 with unicode literals or Python 3:: - from __future__ import unicode_literals + from __future__ import unicode_literals - my_string = b"This is a bytestring" - my_unicode = "This is an Unicode string" + my_string = b"This is a bytestring" + my_unicode = "This is an Unicode string" -See also :doc:`Python 3 compatibility `. + See also :doc:`Python 3 compatibility `. .. warning:: diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index be61311232..65ecde2fea 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -66,6 +66,7 @@ We can't possibly document everything that's new in 1.0, but the documentation will be your definitive guide. Anywhere you see something like: .. versionadded:: 1.0 + This feature is new in Django 1.0 You'll know that you're looking at something new or changed. diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index 143a729f37..a5d7d3f9a1 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -83,9 +83,9 @@ processing at the first positive match. .. versionadded:: 1.6 -If a backend raises a :class:`~django.core.exceptions.PermissionDenied` -exception, authentication will immediately fail. Django won't check the -backends that follow. + If a backend raises a :class:`~django.core.exceptions.PermissionDenied` + exception, authentication will immediately fail. Django won't check the + backends that follow. Writing an authentication backend --------------------------------- diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index a38ee84841..e666cded75 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -435,10 +435,10 @@ The login_required decorator .. versionchanged:: 1.5 - The :setting:`settings.LOGIN_URL ` also accepts - view function names and :ref:`named URL patterns `. - This allows you to freely remap your login view within your URLconf - without having to update the setting. + The :setting:`settings.LOGIN_URL ` also accepts + view function names and :ref:`named URL patterns `. + This allows you to freely remap your login view within your URLconf + without having to update the setting. .. note:: @@ -759,6 +759,7 @@ patterns. mail will be sent either. .. versionchanged:: 1.6 + Previously, error messages indicated whether a given email was registered. @@ -1041,6 +1042,7 @@ Thus, you can check permissions in template ``{% if %}`` statements: {% endif %} .. versionadded:: 1.5 + Permission lookup by "if in". It is possible to also look permissions up by ``{% if in %}`` statements. diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt index 56bdd16e84..2a0f7e4ce0 100644 --- a/docs/topics/db/managers.txt +++ b/docs/topics/db/managers.txt @@ -176,6 +176,7 @@ your choice of default manager in order to avoid a situation where overriding work with. .. versionchanged:: 1.6 + The ``get_queryset`` method was previously named ``get_query_set``. .. _managers-for-related-objects: diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index 182099cc3a..ac329cc4fc 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -681,6 +681,7 @@ In addition, some objects are automatically created just after database). .. versionchanged:: 1.5 + Previously, ``ContentType`` and ``Permission`` instances were created only in the default database. diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index f19302974d..c4f7ee59ae 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -638,6 +638,7 @@ that were modified more than 3 days after they were published:: >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3)) .. versionadded:: 1.5 + ``.bitand()`` and ``.bitor()`` The ``F()`` objects now support bitwise operations by ``.bitand()`` and @@ -646,6 +647,7 @@ The ``F()`` objects now support bitwise operations by ``.bitand()`` and >>> F('somefield').bitand(16) .. versionchanged:: 1.5 + The previously undocumented operators ``&`` and ``|`` no longer produce bitwise operations, use ``.bitand()`` and ``.bitor()`` instead. diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 34cfa382d3..3bf0684b29 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -222,6 +222,7 @@ For example:: return row .. versionchanged:: 1.6 + In Django 1.5 and earlier, after performing a data changing operation, you had to call ``transaction.commit_unless_managed()`` to ensure your changes were committed to the database. Since Django now defaults to database-level diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index d48365dc9e..e6f20c4255 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -22,6 +22,7 @@ integrity of ORM operations that require multiple queries, especially ` queries. .. versionchanged:: 1.6 + Previous version of Django featured :ref:`a more complicated default behavior `. @@ -78,6 +79,7 @@ Middleware run outside of the transaction, and so does the rendering of template responses. .. versionchanged:: 1.6 + Django used to provide this feature via ``TransactionMiddleware``, which is now deprecated. @@ -204,6 +206,7 @@ To avoid this, you can :ref:`deactivate the transaction management `, but it isn't recommended. .. versionchanged:: 1.6 + Before Django 1.6, autocommit was turned off, and it was emulated by forcing a commit after write operations in the ORM. @@ -224,6 +227,7 @@ where you want to run your own transaction-controlling middleware or do something really strange. .. versionchanged:: 1.6 + This used to be controlled by the ``TRANSACTIONS_MANAGED`` setting. Low-level APIs @@ -312,10 +316,10 @@ rollback that would be performed by ``transaction.rollback()``. .. versionchanged:: 1.6 -When the :func:`atomic` decorator is nested, it creates a savepoint to allow -partial commit or rollback. You're strongly encouraged to use :func:`atomic` -rather than the functions described below, but they're still part of the -public API, and there's no plan to deprecate them. + When the :func:`atomic` decorator is nested, it creates a savepoint to allow + partial commit or rollback. You're strongly encouraged to use :func:`atomic` + rather than the functions described below, but they're still part of the + public API, and there's no plan to deprecate them. Each of these functions takes a ``using`` argument which should be the name of a database for which the behavior applies. If no ``using`` argument is @@ -354,20 +358,20 @@ The following example demonstrates the use of savepoints:: @transaction.atomic def viewfunc(request): - a.save() - # transaction now contains a.save() + a.save() + # transaction now contains a.save() - sid = transaction.savepoint() + sid = transaction.savepoint() - b.save() - # transaction now contains a.save() and b.save() + b.save() + # transaction now contains a.save() and b.save() - if want_to_keep_b: - transaction.savepoint_commit(sid) - # open transaction still contains a.save() and b.save() - else: - transaction.savepoint_rollback(sid) - # open transaction now contains only a.save() + if want_to_keep_b: + transaction.savepoint_commit(sid) + # open transaction still contains a.save() and b.save() + else: + transaction.savepoint_rollback(sid) + # open transaction now contains only a.save() Database-specific notes ======================= diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 269ac5b4b6..9d77cd5274 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -111,6 +111,7 @@ affect validation. If ``validate_max=True`` is passed to the validation. See :ref:`validate_max`. .. versionchanged:: 1.6 + The ``validate_max`` parameter was added to :func:`~django.forms.formsets.formset_factory`. Also, the behavior of ``FormSet`` was brought in line with that of ``ModelFormSet`` so that it @@ -310,6 +311,7 @@ should use custom formset validation. using forged POST requests. .. versionchanged:: 1.6 + The ``validate_max`` parameter was added to :func:`~django.forms.formsets.formset_factory`. diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index 18243c77ce..503d4322e0 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -204,6 +204,7 @@ Dealing with streaming responses ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionchanged:: 1.5 + ``response`` may also be an :class:`~django.http.StreamingHttpResponse` object. diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index f5c688e254..acad61eb2a 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -71,6 +71,7 @@ default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the name of that cache. .. versionchanged:: 1.5 + The :setting:`SESSION_CACHE_ALIAS` setting was added. Once your cache is configured, you've got two choices for how to store data in @@ -451,6 +452,7 @@ 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-vs-persistent-sessions: diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 961f0b9d96..52a2935977 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -51,6 +51,7 @@ Optional arguments the :setting:`DEFAULT_CONTENT_TYPE` setting. .. versionchanged:: 1.5 + This parameter used to be called ``mimetype``. ``status`` @@ -129,6 +130,7 @@ Optional arguments the :setting:`DEFAULT_CONTENT_TYPE` setting. .. versionchanged:: 1.5 + This parameter used to be called ``mimetype``. diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index ed0dd33a0f..2ce9d8d2bc 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1529,6 +1529,7 @@ selection based on data from the request. It customizes content for each user. ``'django.middleware.locale.LocaleMiddleware'``. .. versionchanged:: 1.6 + In previous versions, ``LocaleMiddleware`` wasn't enabled by default. Because middleware order matters, you should follow these guidelines: diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt index 17747c22ff..9da71563c3 100644 --- a/docs/topics/pagination.txt +++ b/docs/topics/pagination.txt @@ -252,7 +252,7 @@ Methods .. versionchanged:: 1.5 - Raises :exc:`InvalidPage` if next page doesn't exist. + Raises :exc:`InvalidPage` if next page doesn't exist. .. method:: Page.previous_page_number() @@ -260,7 +260,7 @@ Methods .. versionchanged:: 1.5 - Raises :exc:`InvalidPage` if previous page doesn't exist. + Raises :exc:`InvalidPage` if previous page doesn't exist. .. method:: Page.start_index() diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index ce39f6cd28..cb34117997 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -124,8 +124,8 @@ Calling ``DeserializedObject.save()`` saves the object to the database. .. versionchanged:: 1.6 -In previous versions of Django, the ``pk`` attribute had to be present -on the serialized data or a ``DeserializationError`` would be raised. + In previous versions of Django, the ``pk`` attribute had to be present + on the serialized data or a ``DeserializationError`` would be raised. This ensures that deserializing is a non-destructive operation even if the data in your serialized representation doesn't match what's currently in the @@ -144,11 +144,11 @@ The Django object itself can be inspected as ``deserialized_object.object``. .. versionadded:: 1.5 -If fields in the serialized data do not exist on a model, -a ``DeserializationError`` will be raised unless the ``ignorenonexistent`` -argument is passed in as True:: + If fields in the serialized data do not exist on a model, + a ``DeserializationError`` will be raised unless the ``ignorenonexistent`` + argument is passed in as True:: - serializers.deserialize("xml", data, ignorenonexistent=True) + serializers.deserialize("xml", data, ignorenonexistent=True) .. _serialization-formats: diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index d611da4a37..a97fb2f14f 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -134,7 +134,7 @@ to. .. versionchanged:: 1.5 -The ability to pass a list of signals was added. + The ability to pass a list of signals was added. .. admonition:: Where should this code live? diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index 259c39618b..cd7c98c85c 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -235,6 +235,7 @@ the Django test runner reorders tests in the following way: 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. @@ -612,6 +613,7 @@ Use the ``django.test.client.Client`` class to make requests. a ``Content-Type`` header is set to ``content_type``. .. versionchanged:: 1.5 + :meth:`Client.options` used to process ``data`` like :meth:`Client.get`. @@ -627,6 +629,7 @@ Use the ``django.test.client.Client`` class to make requests. a ``Content-Type`` header is set to ``content_type``. .. versionchanged:: 1.5 + :meth:`Client.put` used to process ``data`` like :meth:`Client.post`. @@ -650,6 +653,7 @@ Use the ``django.test.client.Client`` class to make requests. a ``Content-Type`` header is set to ``content_type``. .. versionchanged:: 1.5 + :meth:`Client.delete` used to process ``data`` like :meth:`Client.get`. @@ -940,6 +944,7 @@ to test the effects of commit and rollback: 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`_. @@ -990,6 +995,7 @@ additions, including: errors. .. versionchanged:: 1.5 + The order in which tests are run has changed. See `Order in which tests are executed`_. @@ -1581,6 +1587,7 @@ your test suite. ``False``, which turns the comparison into a Python set comparison. .. versionchanged:: 1.6 + The method now checks for undefined order and raises ``ValueError`` if undefined order is spotted. The ordering is seen as undefined if the given ``qs`` isn't ordered and the comparison is against more -- cgit v1.3 From b47b0211f52dad9d816db587a33f2d7055a226b3 Mon Sep 17 00:00:00 2001 From: Thomas Thurman Date: Mon, 29 Apr 2013 14:30:51 +0100 Subject: Corrected "it's" to "its" when used possessively. Fixed #20327. --- docs/ref/contrib/gis/geos.txt | 2 +- docs/ref/contrib/gis/install/geolibs.txt | 2 +- docs/ref/templates/builtins.txt | 2 +- docs/releases/1.3-alpha-1.txt | 2 +- docs/topics/forms/media.txt | 2 +- docs/topics/logging.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 4d44638488..12e4e55165 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -845,7 +845,7 @@ include the SRID value (in other words, EWKB). .. class:: WKBWriter ``WKBWriter`` provides the most control over its output. By default it -returns OGC-compliant WKB when it's ``write`` method is called. However, +returns OGC-compliant WKB when its ``write`` method is called. However, it has properties that allow for the creation of EWKB, a superset of the WKB standard that includes additional information. diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 74ebf6a35f..c9a1b405a3 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -194,7 +194,7 @@ Configure, make and install:: .. note:: - Because GeoDjango has it's own Python interface, the preceding instructions + Because GeoDjango has its own Python interface, the preceding instructions do not build GDAL's own Python bindings. The bindings may be built by adding the ``--with-python`` flag when running ``configure``. See `GDAL/OGR In Python`__ for more information on GDAL's bindings. diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 474fb4d84a..e0800037df 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -176,7 +176,7 @@ just declare the cycle, but not output the first value, you can add a This will output a list of ```` elements with ``class`` alternating between ``row1`` and ``row2``; the subtemplate will have -access to ``rowcolors`` in it's context that matches the class of the +access to ``rowcolors`` in its context that matches the class of the ```` that encloses it. If the ``silent`` keyword were to be omitted, ``row1`` would be emitted as normal text, outside the ```` element. diff --git a/docs/releases/1.3-alpha-1.txt b/docs/releases/1.3-alpha-1.txt index c71736dc60..42947d9a44 100644 --- a/docs/releases/1.3-alpha-1.txt +++ b/docs/releases/1.3-alpha-1.txt @@ -87,7 +87,7 @@ To access this library, Django provides the ``django.utils.unittest`` module alias. If you are using Python 2.7, or you have installed unittest2 locally, Django will map the alias to the installed version of the unittest library. Otherwise, -Django will use it's own bundled version of unittest2. +Django will use its own bundled version of unittest2. To use this alias, simply use:: diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index 98e70e5e77..c0d63bb8cf 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -146,7 +146,7 @@ basic Calendar widget from the example above:: -The FancyCalendar widget inherits all the media from it's parent widget. If +The FancyCalendar widget inherits all the media from its parent widget. If you don't want media to be inherited in this way, add an ``extend=False`` declaration to the media declaration:: diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index cb22a57e84..a31dc01cc5 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -169,7 +169,7 @@ issued on the ``project.interesting`` and ``project.interesting.stuff`` loggers. This propagation can be controlled on a per-logger basis. If -you don't want a particular logger to propagate to it's parents, you +you don't want a particular logger to propagate to its parents, you can turn off this behavior. Making logging calls -- cgit v1.3 From 1267d2d9bc8bbb38406a676de31c861ec40b5567 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 29 Apr 2013 19:40:03 +0200 Subject: Fixed #20330 -- Normalized spelling of "web server". Thanks Baptiste Mispelon for the report. --- django/core/handlers/base.py | 4 ++-- docs/howto/deployment/fastcgi.txt | 2 +- docs/howto/static-files/index.txt | 2 +- docs/intro/tutorial01.txt | 2 +- docs/ref/settings.txt | 8 ++++---- docs/ref/views.txt | 2 +- docs/topics/security.txt | 8 ++++---- 7 files changed, 14 insertions(+), 14 deletions(-) (limited to 'docs/ref') diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 900ea8e6b7..acc74db6f5 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -237,7 +237,7 @@ def get_path_info(environ): """ path_info = environ.get('PATH_INFO', str('/')) # Under Python 3, strings in environ are decoded with ISO-8859-1; - # re-encode to recover the original bytestring provided by the webserver. + # re-encode to recover the original bytestring provided by the web server. if six.PY3: path_info = path_info.encode('iso-8859-1') # It'd be better to implement URI-to-IRI decoding, see #19508. @@ -266,7 +266,7 @@ def get_script_name(environ): else: script_name = environ.get('SCRIPT_NAME', str('')) # Under Python 3, strings in environ are decoded with ISO-8859-1; - # re-encode to recover the original bytestring provided by the webserver. + # re-encode to recover the original bytestring provided by the web server. if six.PY3: script_name = script_name.encode('iso-8859-1') # It'd be better to implement URI-to-IRI decoding, see #19508. diff --git a/docs/howto/deployment/fastcgi.txt b/docs/howto/deployment/fastcgi.txt index 6a5acfb7cc..507e50d1a2 100644 --- a/docs/howto/deployment/fastcgi.txt +++ b/docs/howto/deployment/fastcgi.txt @@ -112,7 +112,7 @@ Running a preforked server on a Unix domain socket:: .. admonition:: Socket security - Django's default umask requires that the webserver and the Django fastcgi + Django's default umask requires that the web server and the Django fastcgi process be run with the same group **and** user. For increased security, you can run them under the same group but as different users. If you do this, you will need to set the umask to 0002 using the ``umask`` argument diff --git a/docs/howto/static-files/index.txt b/docs/howto/static-files/index.txt index a26fc04cc9..1fdad94143 100644 --- a/docs/howto/static-files/index.txt +++ b/docs/howto/static-files/index.txt @@ -106,7 +106,7 @@ for gathering static files in a single directory so you can serve them easily. This will copy all files from your static folders into the :setting:`STATIC_ROOT` directory. -3. Use a webserver of your choice to serve the +3. Use a web server of your choice to serve the files. :doc:`/howto/static-files/deployment` covers some common deployment strategies for static files. diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index d1d2242411..a0e776ae69 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -123,7 +123,7 @@ These files are: "table of contents" of your Django-powered site. You can read more about URLs in :doc:`/topics/http/urls`. -* :file:`mysite/wsgi.py`: An entry-point for WSGI-compatible webservers to +* :file:`mysite/wsgi.py`: An entry-point for WSGI-compatible web servers to serve your project. See :doc:`/howto/deployment/wsgi/index` for more details. .. _more about packages: http://docs.python.org/tutorial/modules.html#packages diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 0d8b5bfd56..01c9089028 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -67,7 +67,7 @@ A list of strings representing the host/domain names that this Django site can serve. This is a security measure to prevent an attacker from poisoning caches and password reset emails with links to malicious hosts by submitting requests with a fake HTTP ``Host`` header, which is possible even under many -seemingly-safe webserver configurations. +seemingly-safe web server configurations. Values in this list can be fully qualified names (e.g. ``'www.example.com'``), in which case they will be matched against the request's ``Host`` header @@ -1265,9 +1265,9 @@ see the current list of translated languages by looking in .. _online source: https://github.com/django/django/blob/master/django/conf/global_settings.py -The list is a tuple of two-tuples in the format -(:term:`language code`, ``language name``) -- for example, -``('ja', 'Japanese')``. +The list is a tuple of two-tuples in the format +(:term:`language code`, ``language name``) -- for example, +``('ja', 'Japanese')``. This specifies which languages are available for language selection. See :doc:`/topics/i18n/index`. diff --git a/docs/ref/views.txt b/docs/ref/views.txt index 3753f83f07..8c9c3e3ed8 100644 --- a/docs/ref/views.txt +++ b/docs/ref/views.txt @@ -18,7 +18,7 @@ convenience, you'd like to have Django serve for you in local development. The :func:`~django.views.static.serve` view can be used to serve any directory you give it. (This view is **not** hardened for production use and should be used only as a development aid; you should serve these files in production -using a real front-end webserver). +using a real front-end web server). The most likely example is user-uploaded content in :setting:`MEDIA_ROOT`. ``django.contrib.staticfiles`` is intended for static assets and has no diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 566202eefa..22135a72ea 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -168,7 +168,7 @@ certain cases. While these values are sanitized to prevent Cross Site Scripting attacks, a fake ``Host`` value can be used for Cross-Site Request Forgery, cache poisoning attacks, and poisoning links in emails. -Because even seemingly-secure webserver configurations are susceptible to fake +Because even seemingly-secure web server configurations are susceptible to fake ``Host`` headers, Django validates ``Host`` headers against the :setting:`ALLOWED_HOSTS` setting in the :meth:`django.http.HttpRequest.get_host()` method. @@ -181,15 +181,15 @@ For more details see the full :setting:`ALLOWED_HOSTS` documentation. .. warning:: - Previous versions of this document recommended configuring your webserver to + Previous versions of this document recommended configuring your web server to ensure it validates incoming HTTP ``Host`` headers. While this is still - recommended, in many common webservers a configuration that seems to + recommended, in many common web servers a configuration that seems to validate the ``Host`` header may not in fact do so. For instance, even if Apache is configured such that your Django site is served from a non-default virtual host with the ``ServerName`` set, it is still possible for an HTTP request to match this virtual host and supply a fake ``Host`` header. Thus, Django now requires that you set :setting:`ALLOWED_HOSTS` explicitly rather - than relying on webserver configuration. + than relying on web server configuration. Additionally, as of 1.3.1, Django requires you to explicitly enable support for the ``X-Forwarded-Host`` header (via the :setting:`USE_X_FORWARDED_HOST` -- cgit v1.3 From ce45240df4e2b424ff4852b1cc71d01a031457c9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Tue, 30 Apr 2013 21:39:41 +0200 Subject: Fixed #20336 -- Removed obsolete paragraph from the docs. Thanks Baptiste Mispelon. --- docs/ref/templates/builtins.txt | 5 ----- 1 file changed, 5 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index e0800037df..287fd4f59e 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -657,11 +657,6 @@ The arguments can be hard-coded strings, so the following is valid:: ... {% endifequal %} -It is only possible to compare an argument to template variables or strings. -You cannot check for equality with Python objects such as ``True`` or -``False``. If you need to test if something is true or false, use the -:ttag:`if` tag instead. - An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the ``==`` operator. -- cgit v1.3 From 6bdeed1b811ddf9a920e925ad05d82cffbf13c3a Mon Sep 17 00:00:00 2001 From: Michael Manfre Date: Wed, 1 May 2013 12:13:09 -0600 Subject: Fixed #20338 - Document FQDN behavior with ALLOWED_HOSTS --- docs/ref/settings.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'docs/ref') diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 01c9089028..f2d418d4d9 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -79,6 +79,20 @@ responsible to provide your own validation of the ``Host`` header (perhaps in a middleware; if so this middleware must be listed first in :setting:`MIDDLEWARE_CLASSES`). +.. note:: + + If you want to also allow the `fully qualified domain name (FQDN)`_, which + some browsers can send in the Host header, you must explicitly add another + ALLOWED_HOSTS entry that includes a trailing period. This entry can also be + a subdomain wildcard:: + + ALLOWED_HOSTS = [ + '.example.com', # Allow domain and subdomains + '.example.com.', # Also allow FQDN and subdomains + ] + +.. _`fully qualified domain name (FQDN)`: http://en.wikipedia.org/wiki/Fully_qualified_domain_name + If the ``Host`` header (or ``X-Forwarded-Host`` if :setting:`USE_X_FORWARDED_HOST` is enabled) does not match any value in this list, the :meth:`django.http.HttpRequest.get_host()` method will raise -- cgit v1.3 From 1fff8daf889822aa1dd9adba19c94bb522d960b4 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 8 May 2013 12:57:35 +0200 Subject: Fixed test failures on MySQL. Some tests failed when the time zone definitions were loaded in MySQL and pytz wasn't installed. This setup isn't supported. --- django/db/backends/mysql/base.py | 14 ++++++++++++++ django/db/models/sql/compiler.py | 2 +- docs/ref/models/querysets.txt | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index d26bd9fd60..d9f66ad5ef 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -30,6 +30,11 @@ if (version < (1, 2, 1) or (version[:3] == (1, 2, 1) and from MySQLdb.converters import conversions, Thing2Literal from MySQLdb.constants import FIELD_TYPE, CLIENT +try: + import pytz +except ImportError: + pytz = None + from django.conf import settings from django.db import utils from django.db.backends import * @@ -186,6 +191,15 @@ class DatabaseFeatures(BaseDatabaseFeatures): @cached_property def has_zoneinfo_database(self): + # MySQL accepts full time zones names (eg. Africa/Nairobi) but rejects + # abbreviations (eg. EAT). When pytz isn't installed and the current + # time zone is LocalTimezone (the only sensible value in this + # context), the current time zone name will be an abbreviation. As a + # consequence, MySQL cannot perform time zone conversions reliably. + if pytz is None: + return False + + # Test if the time zone definitions are installed. cursor = self.connection.cursor() cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1") return cursor.fetchone() is not None diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 3444b74ac3..018fc098ea 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1092,7 +1092,7 @@ class SQLDateTimeCompiler(SQLCompiler): if datetime is None: raise ValueError("Database returned an invalid value " "in QuerySet.dates(). Are time zone " - "definitions installed?") + "definitions and pytz installed?") datetime = datetime.replace(tzinfo=None) datetime = timezone.make_aware(datetime, self.query.tzinfo) yield datetime diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 3dde0d5411..d27214a66c 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -627,7 +627,8 @@ object. If it's ``None``, Django uses the :ref:`current time zone - SQLite: install pytz_ — conversions are actually performed in Python. - PostgreSQL: no requirements (see `Time Zones`_). - Oracle: no requirements (see `Choosing a Time Zone File`_). - - MySQL: load the time zone tables with `mysql_tzinfo_to_sql`_. + - MySQL: install pytz_ and load the time zone tables with + `mysql_tzinfo_to_sql`_. .. _pytz: http://pytz.sourceforge.net/ .. _Time Zones: http://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-TIMEZONES -- cgit v1.3 From e81e319f15f448d550d7e30b204d9490a9999b99 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 5 May 2013 19:44:43 +0200 Subject: Fixed #20025 -- Pointed to a MySQLdb fork for Python 3. Made a few minor compatibility adjustments. --- django/db/backends/mysql/base.py | 3 ++- django/db/backends/mysql/validation.py | 3 ++- docs/ref/databases.txt | 13 +++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) (limited to 'docs/ref') diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index d9f66ad5ef..945d737354 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -405,8 +405,9 @@ class DatabaseWrapper(BaseDatabaseWrapper): kwargs = { 'conv': django_conversions, 'charset': 'utf8', - 'use_unicode': True, } + if not six.PY3: + kwargs['use_unicode'] = True settings_dict = self.settings_dict if settings_dict['USER']: kwargs['user'] = settings_dict['USER'] diff --git a/django/db/backends/mysql/validation.py b/django/db/backends/mysql/validation.py index de7474d1e5..2ce957cce7 100644 --- a/django/db/backends/mysql/validation.py +++ b/django/db/backends/mysql/validation.py @@ -10,6 +10,7 @@ class DatabaseValidation(BaseDatabaseValidation): from django.db import models varchar_fields = (models.CharField, models.CommaSeparatedIntegerField, models.SlugField) - if isinstance(f, varchar_fields) and f.max_length > 255 and f.unique: + if (isinstance(f, varchar_fields) and f.unique + and (f.max_length is None or int(f.max_length) > 255)): msg = '"%(name)s": %(cls)s cannot have a "max_length" greater than 255 when using "unique=True".' errors.add(opts, msg % {'name': f.name, 'cls': f.__class__.__name__}) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 35f0cc6b41..f28079ae33 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -252,6 +252,15 @@ required for full MySQL support in Django. .. _MySQLdb: http://sourceforge.net/projects/mysql-python +Python 3 +-------- + +At the time of writing, the latest release of MySQLdb (1.2.4) doesn't support +Python 3. In order to use MySQL under Python 3, you'll have to install an +unofficial fork, such as `MySQL-for-Python-3`_. + +.. _MySQL-for-Python-3: https://github.com/clelland/MySQL-for-Python-3 + Creating your database ---------------------- @@ -361,8 +370,8 @@ Here's a sample configuration which uses a MySQL option file:: default-character-set = utf8 Several other MySQLdb connection options may be useful, such as ``ssl``, -``use_unicode``, ``init_command``, and ``sql_mode``. Consult the -`MySQLdb documentation`_ for more details. +``init_command``, and ``sql_mode``. Consult the `MySQLdb documentation`_ for +more details. .. _MySQL option file: http://dev.mysql.com/doc/refman/5.0/en/option-files.html .. _MySQLdb documentation: http://mysql-python.sourceforge.net/ -- cgit v1.3 From 7476d96f83a004d674244aeb7a66289035427396 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 8 May 2013 15:33:02 +0200 Subject: Marked tests of BinaryFields as expected failures on MySQL and Python 3. Current ports of MySQLdb are very buggy in this area. --- docs/ref/databases.txt | 3 +++ tests/model_fields/tests.py | 6 +++++- tests/serializers_regress/tests.py | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index f28079ae33..2ef048216f 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -259,6 +259,9 @@ At the time of writing, the latest release of MySQLdb (1.2.4) doesn't support Python 3. In order to use MySQL under Python 3, you'll have to install an unofficial fork, such as `MySQL-for-Python-3`_. +This port is still in alpha. In particular, it doesn't support binary data, +making it impossible to use :class:`django.db.models.BinaryField`. + .. _MySQL-for-Python-3: https://github.com/clelland/MySQL-for-Python-3 Creating your database diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 9af8325040..035a5c2ae3 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -6,7 +6,7 @@ from decimal import Decimal from django import test from django import forms from django.core.exceptions import ValidationError -from django.db import models, IntegrityError +from django.db import connection, models, IntegrityError from django.db.models.fields.files import FieldFile from django.utils import six from django.utils import unittest @@ -455,6 +455,10 @@ class BinaryFieldTests(test.TestCase): # Test default value self.assertEqual(bytes(dm.short_data), b'\x08') + if connection.vendor == 'mysql' and six.PY3: + # Existing MySQL DB-API drivers fail on binary data. + test_set_and_retrieve = unittest.expectedFailure(test_set_and_retrieve) + def test_max_length(self): dm = DataModel(short_data=self.binary_data*4) self.assertRaises(ValidationError, dm.full_clean) diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index 72e3825d91..04b4d4c839 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -26,7 +26,7 @@ from django.test import TestCase from django.utils import six from django.utils.encoding import force_text from django.utils.functional import curry -from django.utils.unittest import skipUnless +from django.utils.unittest import expectedFailure, skipUnless from .models import (BinaryData, BooleanData, CharData, DateData, DateTimeData, EmailData, FileData, FilePathData, DecimalData, FloatData, IntegerData, IPAddressData, @@ -459,6 +459,11 @@ def serializerTest(format, self): for klass, count in instance_count.items(): self.assertEqual(count, klass.objects.count()) +if connection.vendor == 'mysql' and six.PY3: + # Existing MySQL DB-API drivers fail on binary data. + serializerTest = expectedFailure(serializerTest) + + def naturalKeySerializerTest(format, self): # Create all the objects defined in the test data objects = [] -- cgit v1.3 From 3d595c3bc38cf939503b69ce7a2802d5663f85b9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 9 May 2013 15:42:14 +0200 Subject: Fixed #20215 -- Disabled persistent connections by default. --- django/db/utils.py | 2 +- docs/howto/deployment/checklist.txt | 9 +++++++++ docs/ref/databases.txt | 33 +++++++++++++++++++++------------ docs/ref/settings.txt | 2 +- docs/releases/1.6.txt | 24 ++---------------------- 5 files changed, 34 insertions(+), 36 deletions(-) (limited to 'docs/ref') diff --git a/django/db/utils.py b/django/db/utils.py index 936b42039d..e84060f9b3 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -169,7 +169,7 @@ class ConnectionHandler(object): conn.setdefault('ENGINE', 'django.db.backends.dummy') if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']: conn['ENGINE'] = 'django.db.backends.dummy' - conn.setdefault('CONN_MAX_AGE', 600) + conn.setdefault('CONN_MAX_AGE', 0) conn.setdefault('OPTIONS', {}) conn.setdefault('TIME_ZONE', 'UTC' if settings.USE_TZ else settings.TIME_ZONE) for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']: diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt index b092048870..b72be75497 100644 --- a/docs/howto/deployment/checklist.txt +++ b/docs/howto/deployment/checklist.txt @@ -157,6 +157,15 @@ Performance optimizations Setting :setting:`DEBUG = False ` disables several features that are only useful in development. In addition, you can tune the following settings. +:setting:`CONN_MAX_AGE` +----------------------- + +Enabling `persistent database connections `_ +can result in a nice speed-up when connecting to the database accounts for a +significant part of the request processing time. + +This helps a lot on virtualized hosts with limited network performance. + :setting:`TEMPLATE_LOADERS` --------------------------- diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 2ef048216f..7555acaaba 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -22,14 +22,14 @@ Persistent connections .. versionadded:: 1.6 Persistent connections avoid the overhead of re-establishing a connection to -the database in each request. By default, connections are kept open for up 10 -minutes — if not specified, :setting:`CONN_MAX_AGE` defaults to 600 seconds. +the database in each request. They're controlled by the +:setting:`CONN_MAX_AGE` parameter which defines the maximum lifetime of a +connection. It can be set independently for each database. -Django 1.5 and earlier didn't have persistent connections. To restore the -legacy behavior of closing the connection at the end of every request, set -:setting:`CONN_MAX_AGE` to ``0``. - -For unlimited persistent connections, set :setting:`CONN_MAX_AGE` to ``None``. +The default value is ``0``, preserving the historical behavior of closing the +database connection at the end of each request. To enable persistent +connections, set :setting:`CONN_MAX_AGE` to a positive number of seconds. For +unlimited persistent connections, set it to ``None``. Connection management ~~~~~~~~~~~~~~~~~~~~~ @@ -64,13 +64,22 @@ least as many simultaneous connections as you have worker threads. Sometimes a database won't be accessed by the majority of your views, for example because it's the database of an external system, or thanks to caching. -In such cases, you should set :setting:`CONN_MAX_AGE` to a lower value, or -even ``0``, because it doesn't make sense to maintain a connection that's -unlikely to be reused. This will help keep the number of simultaneous -connections to this database small. +In such cases, you should set :setting:`CONN_MAX_AGE` to a low value or even +``0``, because it doesn't make sense to maintain a connection that's unlikely +to be reused. This will help keep the number of simultaneous connections to +this database small. The development server creates a new thread for each request it handles, -negating the effect of persistent connections. +negating the effect of persistent connections. Don't enable them during +development. + +When Django establishes a connection to the database, it sets up appropriate +parameters, depending on the backend being used. If you enable persistent +connections, this setup is no longer repeated every request. If you modify +parameters such as the connection's isolation level or time zone, you should +either restore Django's defaults at the end of each request, force an +appropriate value at the beginning of each request, or disable persistent +connections. .. _postgresql-notes: diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index f2d418d4d9..04b42aeeb2 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -509,7 +509,7 @@ CONN_MAX_AGE .. versionadded:: 1.6 -Default: ``600`` +Default: ``0`` The lifetime of a database connection, in seconds. Use ``0`` to close database connections at the end of each request — Django's historical behavior — and diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index 3e15f5b767..611b661415 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -66,13 +66,8 @@ Persistent database connections Django now supports reusing the same database connection for several requests. This avoids the overhead of re-establishing a connection at the beginning of -each request. - -By default, database connections will kept open for 10 minutes. This behavior -is controlled by the :setting:`CONN_MAX_AGE` setting. To restore the previous -behavior of closing the connection at the end of each request, set -:setting:`CONN_MAX_AGE` to ``0``. See :ref:`persistent-database-connections` -for details. +each request. For backwards compatibility, this feature is disabled by +default. See :ref:`persistent-database-connections` for details. Time zone aware aggregation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -298,21 +293,6 @@ Django 1.6 introduces ``hour``, ``minute``, and ``second`` lookups on ``hour``, ``minute``, or ``second``, the new lookups will clash with you field names. Append an explicit :lookup:`exact` lookup if this is an issue. -Persistent database connections -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Connection setup not repeated for each request -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When Django establishes a connection to the database, it sets up appropriate -parameters, depending on the backend being used. Since `persistent database -connections `_ are enabled by default in -Django 1.6, this setup isn't repeated at every request any more. If you -modifiy parameters such as the connection's isolation level or time zone, you -should either restore Django's defaults at the end of each request, force an -appropriate value at the beginning of each request, or disable persistent -connections. - ``BooleanField`` no longer defaults to ``False`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3 From f026a519aea8f3ea7ca339bfbbb007e1ee0068b0 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 21 Feb 2013 21:56:55 +0000 Subject: Fixed #19733 - deprecated ModelForms without 'fields' or 'exclude', and added '__all__' shortcut This also updates all dependent functionality, including modelform_factory and modelformset_factory, and the generic views `ModelFormMixin`, `CreateView` and `UpdateView` which gain a new `fields` attribute. --- django/contrib/admin/options.py | 14 +- django/contrib/auth/forms.py | 1 + django/contrib/contenttypes/generic.py | 9 +- django/contrib/flatpages/forms.py | 1 + .../contrib/formtools/tests/wizard/test_forms.py | 4 +- .../formtools/tests/wizard/wizardtests/tests.py | 1 + django/forms/models.py | 57 ++++++- django/views/generic/edit.py | 11 +- docs/ref/class-based-views/generic-editing.txt | 2 + docs/ref/class-based-views/mixins-editing.txt | 12 ++ docs/ref/contrib/admin/index.txt | 34 ++++- docs/ref/forms/models.txt | 8 + docs/releases/1.6.txt | 52 +++++++ docs/topics/auth/customizing.txt | 1 + docs/topics/class-based-views/generic-editing.txt | 37 +++-- docs/topics/forms/modelforms.txt | 163 +++++++++++---------- tests/admin_validation/tests.py | 2 + tests/bug639/models.py | 1 + tests/foreign_object/tests.py | 1 + tests/forms_tests/tests/test_regressions.py | 1 + tests/forms_tests/tests/tests.py | 4 + tests/generic_relations/tests.py | 1 + tests/generic_views/test_edit.py | 44 +++++- tests/generic_views/test_forms.py | 1 + tests/generic_views/views.py | 9 ++ tests/i18n/forms.py | 1 + tests/inline_formsets/tests.py | 10 +- tests/model_forms/tests.py | 111 ++++++++++++-- tests/model_forms_regress/tests.py | 57 ++++++- tests/model_formsets/tests.py | 89 +++++------ tests/model_formsets_regress/tests.py | 28 ++-- tests/model_inheritance_regress/tests.py | 2 + tests/modeladmin/tests.py | 9 +- tests/timezones/forms.py | 1 + 34 files changed, 578 insertions(+), 201 deletions(-) (limited to 'docs/ref') diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 543655b357..9bc3d55454 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -5,7 +5,7 @@ from django import forms from django.conf import settings from django.forms.formsets import all_valid, DELETION_FIELD_NAME from django.forms.models import (modelform_factory, modelformset_factory, - inlineformset_factory, BaseInlineFormSet) + inlineformset_factory, BaseInlineFormSet, modelform_defines_fields) from django.contrib.contenttypes.models import ContentType from django.contrib.admin import widgets, helpers from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects, @@ -488,6 +488,10 @@ class ModelAdmin(BaseModelAdmin): "formfield_callback": partial(self.formfield_for_dbfield, request=request), } defaults.update(kwargs) + + if defaults['fields'] is None and not modelform_defines_fields(defaults['form']): + defaults['fields'] = forms.ALL_FIELDS + try: return modelform_factory(self.model, **defaults) except FieldError as e: @@ -523,6 +527,10 @@ class ModelAdmin(BaseModelAdmin): "formfield_callback": partial(self.formfield_for_dbfield, request=request), } defaults.update(kwargs) + if (defaults.get('fields') is None + and not modelform_defines_fields(defaults.get('form'))): + defaults['fields'] = forms.ALL_FIELDS + return modelform_factory(self.model, **defaults) def get_changelist_formset(self, request, **kwargs): @@ -1527,6 +1535,10 @@ class InlineModelAdmin(BaseModelAdmin): return result defaults['form'] = DeleteProtectedModelForm + + if defaults['fields'] is None and not modelform_defines_fields(defaults['form']): + defaults['fields'] = forms.ALL_FIELDS + return inlineformset_factory(self.parent_model, self.model, **defaults) def get_fieldsets(self, request, obj=None): diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 42abde2f19..e44e7a703e 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -130,6 +130,7 @@ class UserChangeForm(forms.ModelForm): class Meta: model = User + fields = '__all__' def __init__(self, *args, **kwargs): super(UserChangeForm, self).__init__(*args, **kwargs) diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index cc03f799f3..399d24aa87 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -13,8 +13,9 @@ from django.db.models import signals from django.db.models.fields.related import ForeignObject, ForeignObjectRel from django.db.models.related import PathInfo from django.db.models.sql.where import Constraint -from django.forms import ModelForm -from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance +from django.forms import ModelForm, ALL_FIELDS +from django.forms.models import (BaseModelFormSet, modelformset_factory, save_instance, + modelform_defines_fields) from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets from django.contrib.contenttypes.models import ContentType from django.utils import six @@ -480,6 +481,10 @@ class GenericInlineModelAdmin(InlineModelAdmin): "exclude": exclude } defaults.update(kwargs) + + if defaults['fields'] is None and not modelform_defines_fields(defaults['form']): + defaults['fields'] = ALL_FIELDS + return generic_inlineformset_factory(self.model, **defaults) class GenericStackedInline(GenericInlineModelAdmin): diff --git a/django/contrib/flatpages/forms.py b/django/contrib/flatpages/forms.py index a848875a9f..80938116ad 100644 --- a/django/contrib/flatpages/forms.py +++ b/django/contrib/flatpages/forms.py @@ -12,6 +12,7 @@ class FlatpageForm(forms.ModelForm): class Meta: model = FlatPage + fields = '__all__' def clean_url(self): url = self.cleaned_data['url'] diff --git a/django/contrib/formtools/tests/wizard/test_forms.py b/django/contrib/formtools/tests/wizard/test_forms.py index 14c6e6a685..7425755cf5 100644 --- a/django/contrib/formtools/tests/wizard/test_forms.py +++ b/django/contrib/formtools/tests/wizard/test_forms.py @@ -60,9 +60,11 @@ class TestModel(models.Model): class TestModelForm(forms.ModelForm): class Meta: model = TestModel + fields = '__all__' -TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2) +TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2, + fields='__all__') class TestWizard(WizardView): diff --git a/django/contrib/formtools/tests/wizard/wizardtests/tests.py b/django/contrib/formtools/tests/wizard/wizardtests/tests.py index 1ee5dbdc78..3c2dbc3efb 100644 --- a/django/contrib/formtools/tests/wizard/wizardtests/tests.py +++ b/django/contrib/formtools/tests/wizard/wizardtests/tests.py @@ -15,6 +15,7 @@ from django.utils._os import upath class UserForm(forms.ModelForm): class Meta: model = User + fields = '__all__' UserFormSet = forms.models.modelformset_factory(User, form=UserForm, extra=2) diff --git a/django/forms/models.py b/django/forms/models.py index 5e7797809a..af5cda8faf 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -5,6 +5,8 @@ and database field objects. from __future__ import absolute_import, unicode_literals +import warnings + from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError from django.forms.fields import Field, ChoiceField from django.forms.forms import BaseForm, get_declared_fields @@ -22,8 +24,12 @@ from django.utils.translation import ugettext_lazy as _, ugettext __all__ = ( 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField', + 'ALL_FIELDS', ) +ALL_FIELDS = '__all__' + + def construct_instance(form, instance, fields=None, exclude=None): """ Constructs and returns a model instance from the bound ``form``'s @@ -211,7 +217,7 @@ class ModelFormMetaclass(type): # of ('foo',) for opt in ['fields', 'exclude']: value = getattr(opts, opt) - if isinstance(value, six.string_types): + if isinstance(value, six.string_types) and value != ALL_FIELDS: msg = ("%(model)s.Meta.%(opt)s cannot be a string. " "Did you mean to type: ('%(value)s',)?" % { 'model': new_class.__name__, @@ -222,6 +228,20 @@ class ModelFormMetaclass(type): if opts.model: # If a model is defined, extract form fields from it. + + if opts.fields is None and opts.exclude is None: + # This should be some kind of assertion error once deprecation + # cycle is complete. + warnings.warn("Creating a ModelForm without either the 'fields' attribute " + "or the 'exclude' attribute is deprecated - form %s " + "needs updating" % name, + PendingDeprecationWarning) + + if opts.fields == ALL_FIELDS: + # sentinel for fields_for_model to indicate "get the list of + # fields from the model" + opts.fields = None + fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback) # make sure opts.fields doesn't specify an invalid field @@ -394,7 +414,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, Returns a ModelForm containing form fields for the given model. ``fields`` is an optional list of field names. If provided, only the named - fields will be included in the returned fields. + fields will be included in the returned fields. If omitted or '__all__', + all fields will be used. ``exclude`` is an optional list of field names. If provided, the named fields will be excluded from the returned fields, even if they are listed @@ -434,6 +455,15 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, 'formfield_callback': formfield_callback } + # The ModelFormMetaclass will trigger a similar warning/error, but this will + # be difficult to debug for code that needs updating, so we produce the + # warning here too. + if (getattr(Meta, 'fields', None) is None and + getattr(Meta, 'exclude', None) is None): + warnings.warn("Calling modelform_factory without defining 'fields' or " + "'exclude' explicitly is deprecated", + PendingDeprecationWarning, stacklevel=2) + # Instatiate type(form) in order to use the same metaclass as form. return type(form)(class_name, (form,), form_class_attrs) @@ -701,6 +731,21 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None, """ Returns a FormSet class for the given Django model class. """ + # modelform_factory will produce the same warning/error, but that will be + # difficult to debug for code that needs upgrading, so we produce the + # warning here too. This logic is reproducing logic inside + # modelform_factory, but it can be removed once the deprecation cycle is + # complete, since the validation exception will produce a helpful + # stacktrace. + meta = getattr(form, 'Meta', None) + if meta is None: + meta = type(str('Meta'), (object,), {}) + if (getattr(meta, 'fields', fields) is None and + getattr(meta, 'exclude', exclude) is None): + warnings.warn("Calling modelformset_factory without defining 'fields' or " + "'exclude' explicitly is deprecated", + PendingDeprecationWarning, stacklevel=2) + form = modelform_factory(model, form=form, fields=fields, exclude=exclude, formfield_callback=formfield_callback, widgets=widgets) @@ -1091,3 +1136,11 @@ class ModelMultipleChoiceField(ModelChoiceField): initial_set = set([force_text(value) for value in self.prepare_value(initial)]) data_set = set([force_text(value) for value in data]) return data_set != initial_set + + +def modelform_defines_fields(form_class): + return (form_class is not None and ( + hasattr(form_class, '_meta') and + (form_class._meta.fields is not None or + form_class._meta.exclude is not None) + )) diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py index 5b97fc81c9..e2cc741ffb 100644 --- a/django/views/generic/edit.py +++ b/django/views/generic/edit.py @@ -1,3 +1,5 @@ +import warnings + from django.forms import models as model_forms from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponseRedirect @@ -95,7 +97,14 @@ class ModelFormMixin(FormMixin, SingleObjectMixin): # Try to get a queryset and extract the model class # from that model = self.get_queryset().model - return model_forms.modelform_factory(model) + + fields = getattr(self, 'fields', None) + if fields is None: + warnings.warn("Using ModelFormMixin (base class of %s) without " + "the 'fields' attribute is deprecated." % self.__class__.__name__, + PendingDeprecationWarning) + + return model_forms.modelform_factory(model, fields=fields) def get_form_kwargs(self): """ diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 1dbb427036..555ba40cfb 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -110,6 +110,7 @@ CreateView class AuthorCreate(CreateView): model = Author + fields = ['name'] UpdateView ---------- @@ -152,6 +153,7 @@ UpdateView class AuthorUpdate(UpdateView): model = Author + fields = ['name'] DeleteView ---------- diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index 3f32269742..51d8628818 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -116,6 +116,18 @@ ModelFormMixin by examining ``self.object`` or :attr:`~django.views.generic.detail.SingleObjectMixin.queryset`. + .. attribute:: fields + + .. versionadded:: 1.6 + + A list of names of fields. This is interpreted the same way as the + ``Meta.fields`` attribute of :class:`~django.forms.ModelForm`. + + This is a required attribute if you are generating the form class + automatically (e.g. using ``model``). Omitting this attribute will + result in all fields being used, but this behaviour is deprecated + and will be removed in Django 1.8. + .. attribute:: success_url The URL to redirect to when the form is successfully processed. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 43f0398566..67e498ee91 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -335,6 +335,22 @@ subclass:: For an example see the section `Adding custom validation to the admin`_. + .. admonition:: Note + + .. versionchanged:: 1.6 + + If you define the ``Meta.model`` attribute on a + :class:`~django.forms.ModelForm`, you must also define the + ``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However, + since the admin has its own way of defining fields, the ``Meta.fields`` + attribute will be ignored. + + If the ``ModelForm`` is only going to be used for the admin, the easiest + solution is to omit the ``Meta.model`` attribute, since ``ModelAdmin`` + will provide the correct model to use. Alternatively, you can set + ``fields = []`` in the ``Meta`` class to satisfy the validation on the + ``ModelForm``. + .. admonition:: Note If your ``ModelForm`` and ``ModelAdmin`` both define an ``exclude`` @@ -1283,13 +1299,24 @@ templates used by the :class:`ModelAdmin` views: on the changelist page. To use a custom form, for example:: class MyForm(forms.ModelForm): - class Meta: - model = MyModel + pass class MyModelAdmin(admin.ModelAdmin): def get_changelist_form(self, request, **kwargs): return MyForm + .. admonition:: Note + + .. versionchanged:: 1.6 + + If you define the ``Meta.model`` attribute on a + :class:`~django.forms.ModelForm`, you must also define the + ``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However, + ``ModelAdmin`` ignores this value, overriding it with the + :attr:`ModelAdmin.list_editable` attribute. The easiest solution is to + omit the ``Meta.model`` attribute, since ``ModelAdmin`` will provide the + correct model to use. + .. method:: ModelAdmin.get_changelist_formset(self, request, **kwargs) Returns a :ref:`ModelFormSet ` class for use on the @@ -1490,9 +1517,6 @@ needed. Now within your form you can add your own custom validation for any field:: class MyArticleAdminForm(forms.ModelForm): - class Meta: - model = Article - def clean_name(self): # do something that validates your data return self.cleaned_data["name"] diff --git a/docs/ref/forms/models.txt b/docs/ref/forms/models.txt index 7e3a1470b6..9b3480758a 100644 --- a/docs/ref/forms/models.txt +++ b/docs/ref/forms/models.txt @@ -25,6 +25,14 @@ Model Form Functions See :ref:`modelforms-factory` for example usage. + .. versionchanged:: 1.6 + + You must provide the list of fields explicitly, either via keyword arguments + ``fields`` or ``exclude``, or the corresponding attributes on the form's + inner ``Meta`` class. See :ref:`modelforms-selecting-fields` for more + information. Omitting any definition of the fields to use will result in all + fields being used, but this behaviour is deprecated. + .. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False) Returns a ``FormSet`` class for the given ``model`` class. diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index 611b661415..9cce36aac3 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -532,3 +532,55 @@ including it in an URLconf, simply replace:: with:: (r'^prefix/(?P\d+)/(?P.*)/$', 'django.contrib.contenttypes.views.shortcut'), + +``ModelForm`` without ``fields`` or ``exclude`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, if you wanted a :class:`~django.forms.ModelForm` to use all fields on +the model, you could simply omit the ``Meta.fields`` attribute, and all fields +would be used. + +This can lead to security problems where fields are added to the model and, +unintentionally, automatically become editable by end users. In some cases, +particular with boolean fields, it is possible for this problem to be completely +invisible. This is a form of `Mass assignment vulnerability +`_. + +For this reason, this behaviour is deprecated, and using the ``Meta.exclude`` +option is strongly discouraged. Instead, all fields that are intended for +inclusion in the form should be listed explicitly in the ``fields`` attribute. + +If this security concern really does not apply in your case, there is a shortcut +to explicitly indicate that all fields should be used - use the special value +``"__all__"`` for the fields attribute:: + + class MyModelForm(ModelForm): + class Meta: + fields = "__all__" + model = MyModel + +If you have custom ``ModelForms`` that only need to be used in the admin, there +is another option. The admin has its own methods for defining fields +(``fieldsets`` etc.), and so adding a list of fields to the ``ModelForm`` is +redundant. Instead, simply omit the ``Meta`` inner class of the ``ModelForm``, +or omit the ``Meta.model`` attribute. Since the ``ModelAdmin`` subclass knows +which model it is for, it can add the necessary attributes to derive a +functioning ``ModelForm``. This behaviour also works for earlier Django +versions. + +``UpdateView`` and ``CreateView`` without explicit fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The generic views :class:`~django.views.generic.edit.CreateView` and +:class:`~django.views.generic.edit.UpdateView`, and anything else derived from +:class:`~django.views.generic.edit.ModelFormMixin`, are vulnerable to the +security problem described in the section above, because they can automatically +create a ``ModelForm`` that uses all fields for a model. + +For this reason, if you use these views for editing models, you must also supply +the ``fields`` attribute, which is a list of model fields and works in the same +way as the :class:`~django.forms.ModelForm` ``Meta.fields`` attribute. Alternatively, +you can set set the ``form_class`` attribute to a ``ModelForm`` that explicitly +defines the fields to be used. Defining an ``UpdateView`` or ``CreateView`` +subclass to be used with a model but without an explicit list of fields is +deprecated. diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index a5d7d3f9a1..b53bbe8211 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -1051,6 +1051,7 @@ code would be required in the app's ``admin.py`` file:: class Meta: model = MyUser + fields = ['email', 'password', 'date_of_birth', 'is_active', 'is_admin'] def clean_password(self): # Regardless of what the user provides, return the initial value. diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt index 66ba36fd87..86c5280159 100644 --- a/docs/topics/class-based-views/generic-editing.txt +++ b/docs/topics/class-based-views/generic-editing.txt @@ -114,9 +114,11 @@ here; we don't have to write any logic ourselves:: class AuthorCreate(CreateView): model = Author + fields = ['name'] class AuthorUpdate(UpdateView): model = Author + fields = ['name'] class AuthorDelete(DeleteView): model = Author @@ -126,6 +128,17 @@ here; we don't have to write any logic ourselves:: We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not just ``reverse`` as the urls are not loaded when the file is imported. +.. versionchanged:: 1.6 + +In Django 1.6, the ``fields`` attribute was added, which works the same way as +the ``fields`` attribute on the inner ``Meta`` class on +:class:`~django.forms.ModelForm`. + +Omitting the fields attribute will work as previously, but is deprecated and +this attribute will be required from 1.8 (unless you define the form class in +another way). + + Finally, we hook these new views into the URLconf:: # urls.py @@ -177,33 +190,17 @@ the foreign key relation to the model:: # ... -Create a custom :class:`~django.forms.ModelForm` in order to exclude the -``created_by`` field and prevent the user from editing it: - -.. code-block:: python - - # forms.py - from django import forms - from myapp.models import Author - - class AuthorForm(forms.ModelForm): - class Meta: - model = Author - exclude = ('created_by',) - -In the view, use the custom -:attr:`~django.views.generic.edit.FormMixin.form_class` and override -:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the -user:: +In the view, ensure that you exclude ``created_by`` in the list of fields to +edit, and override +:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user:: # views.py from django.views.generic.edit import CreateView from myapp.models import Author - from myapp.forms import AuthorForm class AuthorCreate(CreateView): - form_class = AuthorForm model = Author + fields = ['name'] def form_valid(self, form): form.instance.created_by = self.request.user diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index eaf2bbbaf2..e58dade736 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -28,6 +28,7 @@ For example:: >>> class ArticleForm(ModelForm): ... class Meta: ... model = Article + ... fields = ['pub_date', 'headline', 'content', 'reporter'] # Creating a form to add an article. >>> form = ArticleForm() @@ -39,11 +40,13 @@ For example:: Field types ----------- -The generated ``Form`` class will have a form field for every model field. Each -model field has a corresponding default form field. For example, a -``CharField`` on a model is represented as a ``CharField`` on a form. A -model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is -the full list of conversions: +The generated ``Form`` class will have a form field for every model field +specified, in the order specified in the ``fields`` attribute. + +Each model field has a corresponding default form field. For example, a +``CharField`` on a model is represented as a ``CharField`` on a form. A model +``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is the +full list of conversions: =============================== ======================================== Model field Form field @@ -168,10 +171,13 @@ Consider this set of models:: class AuthorForm(ModelForm): class Meta: model = Author + fields = ['name', 'title', 'birth_date'] class BookForm(ModelForm): class Meta: model = Book + fields = ['name', 'authors'] + With these models, the ``ModelForm`` subclasses above would be roughly equivalent to this (the only difference being the ``save()`` method, which @@ -288,47 +294,66 @@ method is used to determine whether a form requires multipart file upload (and hence whether ``request.FILES`` must be passed to the form), etc. See :ref:`binding-uploaded-files` for more information. -Using a subset of fields on the form ------------------------------------- +.. _modelforms-selecting-fields: -In some cases, you may not want all the model fields to appear on the generated -form. There are three ways of telling ``ModelForm`` to use only a subset of the -model fields: +Selecting the fields to use +--------------------------- -1. Set ``editable=False`` on the model field. As a result, *any* form - created from the model via ``ModelForm`` will not include that - field. +It is strongly recommended that you explicitly set all fields that should be +edited in the form using the ``fields`` attribute. Failure to do so can easily +lead to security problems when a form unexpectedly allows a user to set certain +fields, especially when new fields are added to a model. Depending on how the +form is rendered, the problem may not even be visible on the web page. -2. Use the ``fields`` attribute of the ``ModelForm``'s inner ``Meta`` - class. This attribute, if given, should be a list of field names - to include in the form. The order in which the fields names are specified - in that list is respected when the form renders them. +The alternative approach would be to include all fields automatically, or +blacklist only some. This fundamental approach is known to be much less secure +and has led to serious exploits on major websites (e.g. `GitHub +`_). -3. Use the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` - class. This attribute, if given, should be a list of field names - to exclude from the form. +There are, however, two shortcuts available for cases where you can guarantee +these security concerns do not apply to you: -For example, if you want a form for the ``Author`` model (defined -above) that includes only the ``name`` and ``birth_date`` fields, you would -specify ``fields`` or ``exclude`` like this:: +1. Set the ``fields`` attribute to the special value ``'__all__'`` to indicate + that all fields in the model should be used. For example:: - class PartialAuthorForm(ModelForm): - class Meta: - model = Author - fields = ('name', 'birth_date') + class AuthorForm(ModelForm): + class Meta: + model = Author + fields = '__all__' - class PartialAuthorForm(ModelForm): - class Meta: - model = Author - exclude = ('title',) +2. Set the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class to + a list of fields to be excluded from the form. + + For example:: + + class PartialAuthorForm(ModelForm): + class Meta: + model = Author + exclude = ['title'] + + Since the ``Author`` model has the 3 fields ``name``, ``title`` and + ``birth_date``, this will result in the fields ``name`` and ``birth_date`` + being present on the form. + +If either of these are used, the order the fields appear in the form will be the +order the fields are defined in the model, with ``ManyToManyField`` instances +appearing last. + +In addition, Django applies the following rule: if you set ``editable=False`` on +the model field, *any* form created from the model via ``ModelForm`` will not +include that field. + +.. versionchanged:: 1.6 + + Before version 1.6, the ``'__all__'`` shortcut did not exist, but omitting + the ``fields`` attribute had the same effect. Omitting both ``fields`` and + ``exclude`` is now deprecated, but will continue to work as before until + version 1.8 -Since the Author model has only 3 fields, 'name', 'title', and -'birth_date', the forms above will contain exactly the same fields. .. note:: - If you specify ``fields`` or ``exclude`` when creating a form with - ``ModelForm``, then the fields that are not in the resulting form + Any fields not included in a form by the above logic will not be set by the form's ``save()`` method. Also, if you manually add the excluded fields back to the form, they will not be initialized from the model instance. @@ -401,15 +426,19 @@ field, you could do the following:: class Meta: model = Article + fields = ['pub_date', 'headline', 'content', 'reporter'] + If you want to override a field's default label, then specify the ``label`` parameter when declaring the form field:: - >>> class ArticleForm(ModelForm): - ... pub_date = DateField(label='Publication date') - ... - ... class Meta: - ... model = Article + class ArticleForm(ModelForm): + pub_date = DateField(label='Publication date') + + class Meta: + model = Article + fields = ['pub_date', 'headline', 'content', 'reporter'] + .. note:: @@ -436,6 +465,7 @@ parameter when declaring the form field:: class Meta: model = Article + fields = ['headline', 'content'] You must ensure that the type of the form field can be used to set the contents of the corresponding model field. When they are not compatible, @@ -444,30 +474,6 @@ parameter when declaring the form field:: See the :doc:`form field documentation ` for more information on fields and their arguments. -Changing the order of fields ----------------------------- - -By default, a ``ModelForm`` will render fields in the same order that they are -defined on the model, with ``ManyToManyField`` instances appearing last. If -you want to change the order in which fields are rendered, you can use the -``fields`` attribute on the ``Meta`` class. - -The ``fields`` attribute defines the subset of model fields that will be -rendered, and the order in which they will be rendered. For example given this -model:: - - class Book(models.Model): - author = models.ForeignKey(Author) - title = models.CharField(max_length=100) - -the ``author`` field would be rendered first. If we wanted the title field -to be rendered first, we could specify the following ``ModelForm``:: - - >>> class BookForm(ModelForm): - ... class Meta: - ... model = Book - ... fields = ('title', 'author') - .. _overriding-modelform-clean-method: Overriding the clean() method @@ -550,21 +556,19 @@ definition. This may be more convenient if you do not have many customizations to make:: >>> from django.forms.models import modelform_factory - >>> BookForm = modelform_factory(Book) + >>> BookForm = modelform_factory(Book, fields=("author", "title")) This can also be used to make simple modifications to existing forms, for -example by specifying which fields should be displayed:: - - >>> Form = modelform_factory(Book, form=BookForm, fields=("author",)) - -... or which fields should be excluded:: - - >>> Form = modelform_factory(Book, form=BookForm, exclude=("title",)) - -You can also specify the widgets to be used for a given field:: +example by specifying the widgets to be used for a given field:: >>> from django.forms import Textarea - >>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()}) + >>> Form = modelform_factory(Book, form=BookForm, + widgets={"title": Textarea()}) + +The fields to include can be specified using the ``fields`` and ``exclude`` +keyword arguments, or the corresponding attributes on the ``ModelForm`` inner +``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields` +documentation. .. _model-formsets: @@ -688,11 +692,10 @@ database. If a given instance's data didn't change in the bound data, the instance won't be saved to the database and won't be included in the return value (``instances``, in the above example). -When fields are missing from the form (for example because they have -been excluded), these fields will not be set by the ``save()`` -method. You can find more information about this restriction, which -also holds for regular ``ModelForms``, in `Using a subset of fields on -the form`_. +When fields are missing from the form (for example because they have been +excluded), these fields will not be set by the ``save()`` method. You can find +more information about this restriction, which also holds for regular +``ModelForms``, in `Selecting the fields to use`_. Pass ``commit=False`` to return the unsaved model instances:: diff --git a/tests/admin_validation/tests.py b/tests/admin_validation/tests.py index 6ce0ee7e03..16f73c6390 100644 --- a/tests/admin_validation/tests.py +++ b/tests/admin_validation/tests.py @@ -285,6 +285,8 @@ class ValidationTestCase(TestCase): extra_data = forms.CharField() class Meta: model = Song + fields = '__all__' + class FieldsOnFormOnlyAdmin(admin.ModelAdmin): form = SongForm diff --git a/tests/bug639/models.py b/tests/bug639/models.py index e641555c87..fa8e7d2c07 100644 --- a/tests/bug639/models.py +++ b/tests/bug639/models.py @@ -25,3 +25,4 @@ class Photo(models.Model): class PhotoForm(ModelForm): class Meta: model = Photo + fields = '__all__' diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index 2ca13cb786..55dd6a0f47 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -322,6 +322,7 @@ class FormsTests(TestCase): class ArticleForm(forms.ModelForm): class Meta: model = Article + fields = '__all__' def test_foreign_object_form(self): # A very crude test checking that the non-concrete fields do not get form fields. diff --git a/tests/forms_tests/tests/test_regressions.py b/tests/forms_tests/tests/test_regressions.py index be9dc8c593..74509a0f1a 100644 --- a/tests/forms_tests/tests/test_regressions.py +++ b/tests/forms_tests/tests/test_regressions.py @@ -139,6 +139,7 @@ class FormsRegressionsTestCase(TestCase): class CheeseForm(ModelForm): class Meta: model = Cheese + fields = '__all__' form = CheeseForm({ 'name': 'Brie', diff --git a/tests/forms_tests/tests/tests.py b/tests/forms_tests/tests/tests.py index 80e2cadcc3..deda4822b8 100644 --- a/tests/forms_tests/tests/tests.py +++ b/tests/forms_tests/tests/tests.py @@ -17,11 +17,13 @@ from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group, class ChoiceFieldForm(ModelForm): class Meta: model = ChoiceFieldModel + fields = '__all__' class OptionalMultiChoiceModelForm(ModelForm): class Meta: model = OptionalMultiChoiceModel + fields = '__all__' class FileForm(Form): @@ -139,6 +141,7 @@ class FormsModelTestCase(TestCase): class BoundaryForm(ModelForm): class Meta: model = BoundaryModel + fields = '__all__' f = BoundaryForm({'positive_integer': 100}) self.assertTrue(f.is_valid()) @@ -154,6 +157,7 @@ class FormsModelTestCase(TestCase): class DefaultsForm(ModelForm): class Meta: model = Defaults + fields = '__all__' self.assertEqual(DefaultsForm().fields['name'].initial, 'class default value') self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1)) diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py index 27b25185ea..dd9dc506ca 100644 --- a/tests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -247,6 +247,7 @@ class CustomWidget(forms.TextInput): class TaggedItemForm(forms.ModelForm): class Meta: model = TaggedItem + fields = '__all__' widgets = {'tag': CustomWidget} class GenericInlineFormsetTest(TestCase): diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py index c54d632363..54eab7ffa4 100644 --- a/tests/generic_views/test_edit.py +++ b/tests/generic_views/test_edit.py @@ -1,12 +1,14 @@ from __future__ import absolute_import +import warnings + from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse from django import forms from django.test import TestCase from django.utils.unittest import expectedFailure from django.views.generic.base import View -from django.views.generic.edit import FormMixin +from django.views.generic.edit import FormMixin, CreateView, UpdateView from . import views from .models import Artist, Author @@ -34,6 +36,7 @@ class ModelFormMixinTests(TestCase): form_class = views.AuthorGetQuerySetFormView().get_form_class() self.assertEqual(form_class._meta.model, Author) + class CreateViewTests(TestCase): urls = 'generic_views.urls' @@ -112,6 +115,45 @@ class CreateViewTests(TestCase): self.assertEqual(res.status_code, 302) self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/') + def test_create_view_with_restricted_fields(self): + + class MyCreateView(CreateView): + model = Author + fields = ['name'] + + self.assertEqual(list(MyCreateView().get_form_class().base_fields), + ['name']) + + def test_create_view_all_fields(self): + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", PendingDeprecationWarning) + + class MyCreateView(CreateView): + model = Author + fields = '__all__' + + self.assertEqual(list(MyCreateView().get_form_class().base_fields), + ['name', 'slug']) + self.assertEqual(len(w), 0) + + + def test_create_view_without_explicit_fields(self): + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", PendingDeprecationWarning) + + class MyCreateView(CreateView): + model = Author + + # Until end of the deprecation cycle, should still create the form + # as before: + self.assertEqual(list(MyCreateView().get_form_class().base_fields), + ['name', 'slug']) + + # but with a warning: + self.assertEqual(w[0].category, PendingDeprecationWarning) + class UpdateViewTests(TestCase): urls = 'generic_views.urls' diff --git a/tests/generic_views/test_forms.py b/tests/generic_views/test_forms.py index e036ad8afc..8c118e32a6 100644 --- a/tests/generic_views/test_forms.py +++ b/tests/generic_views/test_forms.py @@ -11,6 +11,7 @@ class AuthorForm(forms.ModelForm): class Meta: model = Author + fields = ['name', 'slug'] class ContactForm(forms.Form): diff --git a/tests/generic_views/views.py b/tests/generic_views/views.py index 69bfcb32dd..aa8777e8c6 100644 --- a/tests/generic_views/views.py +++ b/tests/generic_views/views.py @@ -85,15 +85,18 @@ class ContactView(generic.FormView): class ArtistCreate(generic.CreateView): model = Artist + fields = '__all__' class NaiveAuthorCreate(generic.CreateView): queryset = Author.objects.all() + fields = '__all__' class AuthorCreate(generic.CreateView): model = Author success_url = '/list/authors/' + fields = '__all__' class SpecializedAuthorCreate(generic.CreateView): @@ -112,19 +115,23 @@ class AuthorCreateRestricted(AuthorCreate): class ArtistUpdate(generic.UpdateView): model = Artist + fields = '__all__' class NaiveAuthorUpdate(generic.UpdateView): queryset = Author.objects.all() + fields = '__all__' class AuthorUpdate(generic.UpdateView): model = Author success_url = '/list/authors/' + fields = '__all__' class OneAuthorUpdate(generic.UpdateView): success_url = '/list/authors/' + fields = '__all__' def get_object(self): return Author.objects.get(pk=1) @@ -184,6 +191,8 @@ class BookDetail(BookConfig, generic.DateDetailView): pass class AuthorGetQuerySetFormView(generic.edit.ModelFormMixin): + fields = '__all__' + def get_queryset(self): return Author.objects.all() diff --git a/tests/i18n/forms.py b/tests/i18n/forms.py index abb99f443a..6e4def9c5e 100644 --- a/tests/i18n/forms.py +++ b/tests/i18n/forms.py @@ -24,3 +24,4 @@ class CompanyForm(forms.ModelForm): class Meta: model = Company + fields = '__all__' diff --git a/tests/inline_formsets/tests.py b/tests/inline_formsets/tests.py index df682d34ef..ad8a666cb5 100644 --- a/tests/inline_formsets/tests.py +++ b/tests/inline_formsets/tests.py @@ -10,7 +10,7 @@ from .models import Poet, Poem, School, Parent, Child class DeletionTests(TestCase): def test_deletion(self): - PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True) + PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__") poet = Poet.objects.create(name='test') poem = poet.poem_set.create(name='test poem') data = { @@ -32,7 +32,7 @@ class DeletionTests(TestCase): Make sure that an add form that is filled out, but marked for deletion doesn't cause validation errors. """ - PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True) + PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__") poet = Poet.objects.create(name='test') data = { 'poem_set-TOTAL_FORMS': '1', @@ -60,7 +60,7 @@ class DeletionTests(TestCase): Make sure that a change form that is filled out, but marked for deletion doesn't cause validation errors. """ - PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True) + PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__") poet = Poet.objects.create(name='test') poem = poet.poem_set.create(name='test poem') data = { @@ -115,8 +115,8 @@ class InlineFormsetFactoryTest(TestCase): """ These should both work without a problem. """ - inlineformset_factory(Parent, Child, fk_name='mother') - inlineformset_factory(Parent, Child, fk_name='father') + inlineformset_factory(Parent, Child, fk_name='mother', fields="__all__") + inlineformset_factory(Parent, Child, fk_name='father', fields="__all__") def test_exception_on_unspecified_foreign_key(self): """ diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 96c3ecbdce..c5db011404 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals import datetime import os from decimal import Decimal +import warnings from django import forms from django.core.exceptions import FieldError @@ -30,19 +31,25 @@ if test_images: class ImageFileForm(forms.ModelForm): class Meta: model = ImageFile + fields = '__all__' + class OptionalImageFileForm(forms.ModelForm): class Meta: model = OptionalImageFile + fields = '__all__' + class ProductForm(forms.ModelForm): class Meta: model = Product + fields = '__all__' class PriceForm(forms.ModelForm): class Meta: model = Price + fields = '__all__' class BookForm(forms.ModelForm): @@ -66,11 +73,13 @@ class ExplicitPKForm(forms.ModelForm): class PostForm(forms.ModelForm): class Meta: model = Post + fields = '__all__' class DerivedPostForm(forms.ModelForm): class Meta: model = DerivedPost + fields = '__all__' class CustomAuthorForm(forms.ModelForm): @@ -78,61 +87,79 @@ class CustomAuthorForm(forms.ModelForm): class Meta: model = Author + fields = '__all__' class FlexDatePostForm(forms.ModelForm): class Meta: model = FlexibleDatePost + fields = '__all__' class BaseCategoryForm(forms.ModelForm): class Meta: model = Category + fields = '__all__' class ArticleForm(forms.ModelForm): class Meta: model = Article + fields = '__all__' class ArticleForm(forms.ModelForm): class Meta: model = Article + fields = '__all__' + class PartialArticleForm(forms.ModelForm): class Meta: model = Article fields = ('headline','pub_date') + class RoykoForm(forms.ModelForm): class Meta: model = Author + fields = '__all__' + class TestArticleForm(forms.ModelForm): class Meta: model = Article + fields = '__all__' + class PartialArticleFormWithSlug(forms.ModelForm): class Meta: model = Article - fields=('headline', 'slug', 'pub_date') + fields = ('headline', 'slug', 'pub_date') + class ArticleStatusForm(forms.ModelForm): class Meta: model = ArticleStatus + fields = '__all__' + class InventoryForm(forms.ModelForm): class Meta: model = Inventory + fields = '__all__' + class SelectInventoryForm(forms.Form): items = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode') + class CustomFieldForExclusionForm(forms.ModelForm): class Meta: model = CustomFieldForExclusionModel fields = ['name', 'markup'] + class ShortCategory(forms.ModelForm): name = forms.CharField(max_length=5) slug = forms.CharField(max_length=5) @@ -140,30 +167,44 @@ class ShortCategory(forms.ModelForm): class Meta: model = Category + fields = '__all__' + class ImprovedArticleForm(forms.ModelForm): class Meta: model = ImprovedArticle + fields = '__all__' + class ImprovedArticleWithParentLinkForm(forms.ModelForm): class Meta: model = ImprovedArticleWithParentLink + fields = '__all__' + class BetterAuthorForm(forms.ModelForm): class Meta: model = BetterAuthor + fields = '__all__' + class AuthorProfileForm(forms.ModelForm): class Meta: model = AuthorProfile + fields = '__all__' + class TextFileForm(forms.ModelForm): class Meta: model = TextFile + fields = '__all__' + class BigIntForm(forms.ModelForm): class Meta: model = BigInt + fields = '__all__' + class ModelFormWithMedia(forms.ModelForm): class Media: @@ -173,19 +214,25 @@ class ModelFormWithMedia(forms.ModelForm): } class Meta: model = TextFile + fields = '__all__' + class CommaSeparatedIntegerForm(forms.ModelForm): - class Meta: - model = CommaSeparatedInteger + class Meta: + model = CommaSeparatedInteger + fields = '__all__' + class PriceFormWithoutQuantity(forms.ModelForm): class Meta: model = Price exclude = ('quantity',) + class ColourfulItemForm(forms.ModelForm): class Meta: model = ColourfulItem + fields = '__all__' class ModelFormBaseTest(TestCase): @@ -193,6 +240,25 @@ class ModelFormBaseTest(TestCase): self.assertEqual(list(BaseCategoryForm.base_fields), ['name', 'slug', 'url']) + def test_missing_fields_attribute(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", PendingDeprecationWarning) + + class MissingFieldsForm(forms.ModelForm): + class Meta: + model = Category + + # There is some internal state in warnings module which means that + # if a warning has been seen already, the catch_warnings won't + # have recorded it. The following line therefore will not work reliably: + + # self.assertEqual(w[0].category, PendingDeprecationWarning) + + # Until end of the deprecation cycle, should still create the + # form as before: + self.assertEqual(list(MissingFieldsForm.base_fields), + ['name', 'slug', 'url']) + def test_extra_fields(self): class ExtraFields(BaseCategoryForm): some_extra_field = forms.BooleanField() @@ -206,6 +272,33 @@ class ModelFormBaseTest(TestCase): class Meta: model = Category + fields = '__all__' + + self.assertTrue(isinstance(ReplaceField.base_fields['url'], + forms.fields.BooleanField)) + + def test_replace_field_variant_2(self): + # Should have the same result as before, + # but 'fields' attribute specified differently + class ReplaceField(forms.ModelForm): + url = forms.BooleanField() + + class Meta: + model = Category + fields = ['url'] + + self.assertTrue(isinstance(ReplaceField.base_fields['url'], + forms.fields.BooleanField)) + + def test_replace_field_variant_3(self): + # Should have the same result as before, + # but 'fields' attribute specified differently + class ReplaceField(forms.ModelForm): + url = forms.BooleanField() + + class Meta: + model = Category + fields = [] # url will still appear, since it is explicit above self.assertTrue(isinstance(ReplaceField.base_fields['url'], forms.fields.BooleanField)) @@ -216,19 +309,11 @@ class ModelFormBaseTest(TestCase): class Meta: model = Author + fields = '__all__' wf = AuthorForm({'name': 'Richard Lockridge'}) self.assertTrue(wf.is_valid()) - def test_limit_fields(self): - class LimitFields(forms.ModelForm): - class Meta: - model = Category - fields = ['url'] - - self.assertEqual(list(LimitFields.base_fields), - ['url']) - def test_limit_nonexistent_field(self): expected_msg = 'Unknown field(s) (nonexistent) specified for Category' with self.assertRaisesMessage(FieldError, expected_msg): @@ -294,6 +379,7 @@ class ModelFormBaseTest(TestCase): """ class Meta: model = Article + fields = '__all__' # MixModelForm is now an Article-related thing, because MixModelForm.Meta # overrides BaseCategoryForm.Meta. @@ -348,6 +434,7 @@ class ModelFormBaseTest(TestCase): class Meta: model = Category + fields = '__all__' class SubclassMeta(SomeCategoryForm): """ We can also subclass the Meta inner class to change the fields diff --git a/tests/model_forms_regress/tests.py b/tests/model_forms_regress/tests.py index 90c907f2a6..3f15c35938 100644 --- a/tests/model_forms_regress/tests.py +++ b/tests/model_forms_regress/tests.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, unicode_literals from datetime import date +import warnings from django import forms from django.core.exceptions import FieldError, ValidationError @@ -43,9 +44,12 @@ class ModelMultipleChoiceFieldTests(TestCase): f.clean([p.pk for p in Person.objects.all()[8:9]]) self.assertTrue(self._validator_run) + class TripleForm(forms.ModelForm): class Meta: model = Triple + fields = '__all__' + class UniqueTogetherTests(TestCase): def test_multiple_field_unique_together(self): @@ -63,15 +67,18 @@ class UniqueTogetherTests(TestCase): form = TripleForm({'left': '1', 'middle': '3', 'right': '1'}) self.assertTrue(form.is_valid()) + class TripleFormWithCleanOverride(forms.ModelForm): class Meta: model = Triple + fields = '__all__' def clean(self): if not self.cleaned_data['left'] == self.cleaned_data['right']: raise forms.ValidationError('Left and right should be equal') return self.cleaned_data + class OverrideCleanTests(TestCase): def test_override_clean(self): """ @@ -84,6 +91,7 @@ class OverrideCleanTests(TestCase): # by form.full_clean(). self.assertEqual(form.instance.left, 1) + # Regression test for #12960. # Make sure the cleaned_data returned from ModelForm.clean() is applied to the # model instance. @@ -95,6 +103,8 @@ class PublicationForm(forms.ModelForm): class Meta: model = Publication + fields = '__all__' + class ModelFormCleanTest(TestCase): def test_model_form_clean_applies_to_model(self): @@ -103,9 +113,12 @@ class ModelFormCleanTest(TestCase): publication = form.save() self.assertEqual(publication.title, 'TEST') + class FPForm(forms.ModelForm): class Meta: model = FilePathModel + fields = '__all__' + class FilePathFieldTests(TestCase): def test_file_path_field_blank(self): @@ -133,7 +146,8 @@ class ManyToManyCallableInitialTests(TestCase): book3 = Publication.objects.create(title="Third Book", date_published=date(2009,1,1)) # Create a ModelForm, instantiate it, and check that the output is as expected - ModelForm = modelform_factory(Article, formfield_callback=formfield_for_dbfield) + ModelForm = modelform_factory(Article, fields="__all__", + formfield_callback=formfield_for_dbfield) form = ModelForm() self.assertHTMLEqual(form.as_ul(), """
  • Hold down "Control", or "Command" on a Mac, to select more than one.
  • """ % (book1.pk, book2.pk, book3.pk)) + class CFFForm(forms.ModelForm): class Meta: model = CustomFF + fields = '__all__' + class CustomFieldSaveTests(TestCase): def test_save(self): @@ -168,9 +185,12 @@ class ModelChoiceIteratorTests(TestCase): f = Form() self.assertEqual(len(f.fields["publications"].choices), 1) + class RealPersonForm(forms.ModelForm): class Meta: model = RealPerson + fields = '__all__' + class CustomModelFormSaveMethod(TestCase): def test_string_message(self): @@ -230,9 +250,12 @@ class TestTicket11183(TestCase): self.assertTrue(field1 is not ModelChoiceForm.base_fields['person']) self.assertTrue(field1.widget.choices.field is field1) + class HomepageForm(forms.ModelForm): class Meta: model = Homepage + fields = '__all__' + class URLFieldTests(TestCase): def test_url_on_modelform(self): @@ -274,6 +297,7 @@ class FormFieldCallbackTests(TestCase): class Meta: model = Person widgets = {'name': widget} + fields = "__all__" Form = modelform_factory(Person, form=BaseForm) self.assertTrue(Form.base_fields['name'].widget is widget) @@ -285,11 +309,11 @@ class FormFieldCallbackTests(TestCase): widget = forms.Textarea() # Without a widget should not set the widget to textarea - Form = modelform_factory(Person) + Form = modelform_factory(Person, fields="__all__") self.assertNotEqual(Form.base_fields['name'].widget.__class__, forms.Textarea) # With a widget should not set the widget to textarea - Form = modelform_factory(Person, widgets={'name':widget}) + Form = modelform_factory(Person, fields="__all__", widgets={'name':widget}) self.assertEqual(Form.base_fields['name'].widget.__class__, forms.Textarea) def test_custom_callback(self): @@ -307,6 +331,7 @@ class FormFieldCallbackTests(TestCase): class Meta: model = Person widgets = {'name': widget} + fields = "__all__" _ = modelform_factory(Person, form=BaseForm, formfield_callback=callback) @@ -317,7 +342,7 @@ class FormFieldCallbackTests(TestCase): def test_bad_callback(self): # A bad callback provided by user still gives an error - self.assertRaises(TypeError, modelform_factory, Person, + self.assertRaises(TypeError, modelform_factory, Person, fields="__all__", formfield_callback='not a function or callable') @@ -362,6 +387,8 @@ class InvalidFieldAndFactory(TestCase): class DocumentForm(forms.ModelForm): class Meta: model = Document + fields = '__all__' + class FileFieldTests(unittest.TestCase): def test_clean_false(self): @@ -425,6 +452,7 @@ class FileFieldTests(unittest.TestCase): self.assertTrue('something.txt' in rendered) self.assertTrue('myfile-clear' in rendered) + class EditionForm(forms.ModelForm): author = forms.ModelChoiceField(queryset=Person.objects.all()) publication = forms.ModelChoiceField(queryset=Publication.objects.all()) @@ -433,6 +461,8 @@ class EditionForm(forms.ModelForm): class Meta: model = Edition + fields = '__all__' + class UniqueErrorsTests(TestCase): def setUp(self): @@ -473,7 +503,7 @@ class EmptyFieldsTestCase(TestCase): def test_empty_fields_to_construct_instance(self): "No fields should be set on a model instance if construct_instance receives fields=()" - form = modelform_factory(Person)({'name': 'John Doe'}) + form = modelform_factory(Person, fields="__all__")({'name': 'John Doe'}) self.assertTrue(form.is_valid()) instance = construct_instance(form, Person(), fields=()) self.assertEqual(instance.name, '') @@ -485,10 +515,25 @@ class CustomMetaclass(ModelFormMetaclass): new.base_fields = {} return new + class CustomMetaclassForm(six.with_metaclass(CustomMetaclass, forms.ModelForm)): pass + class CustomMetaclassTestCase(TestCase): def test_modelform_factory_metaclass(self): - new_cls = modelform_factory(Person, form=CustomMetaclassForm) + new_cls = modelform_factory(Person, fields="__all__", form=CustomMetaclassForm) self.assertEqual(new_cls.base_fields, {}) + + +class TestTicket19733(TestCase): + def test_modelform_factory_without_fields(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", PendingDeprecationWarning) + # This should become an error once deprecation cycle is complete. + form = modelform_factory(Person) + self.assertEqual(w[0].category, PendingDeprecationWarning) + + def test_modelform_factory_with_all_fields(self): + form = modelform_factory(Person, fields="__all__") + self.assertEqual(form.base_fields.keys(), ["name"]) diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 8d0c017a61..8cfdf53995 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -21,7 +21,7 @@ from .models import (Author, BetterAuthor, Book, BookWithCustomPK, class DeletionTests(TestCase): def test_deletion(self): - PoetFormSet = modelformset_factory(Poet, can_delete=True) + PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True) poet = Poet.objects.create(name='test') data = { 'form-TOTAL_FORMS': '1', @@ -41,7 +41,7 @@ class DeletionTests(TestCase): Make sure that an add form that is filled out, but marked for deletion doesn't cause validation errors. """ - PoetFormSet = modelformset_factory(Poet, can_delete=True) + PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True) poet = Poet.objects.create(name='test') # One existing untouched and two new unvalid forms data = { @@ -75,7 +75,7 @@ class DeletionTests(TestCase): Make sure that a change form that is filled out, but marked for deletion doesn't cause validation errors. """ - PoetFormSet = modelformset_factory(Poet, can_delete=True) + PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True) poet = Poet.objects.create(name='test') data = { 'form-TOTAL_FORMS': '1', @@ -100,7 +100,7 @@ class DeletionTests(TestCase): class ModelFormsetTest(TestCase): def test_simple_save(self): qs = Author.objects.all() - AuthorFormSet = modelformset_factory(Author, extra=3) + AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=3) formset = AuthorFormSet(queryset=qs) self.assertEqual(len(formset.forms), 3) @@ -138,7 +138,7 @@ class ModelFormsetTest(TestCase): # we'll use it to display them in alphabetical order by name. qs = Author.objects.order_by('name') - AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=False) + AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=1, can_delete=False) formset = AuthorFormSet(queryset=qs) self.assertEqual(len(formset.forms), 3) @@ -176,7 +176,7 @@ class ModelFormsetTest(TestCase): # marked for deletion, make sure we don't save that form. qs = Author.objects.order_by('name') - AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=True) + AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=1, can_delete=True) formset = AuthorFormSet(queryset=qs) self.assertEqual(len(formset.forms), 4) @@ -256,7 +256,7 @@ class ModelFormsetTest(TestCase): author4 = Author.objects.create(name='John Steinbeck') - AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, extra=1, can_delete=True) + AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, fields="__all__", extra=1, can_delete=True) data = { 'form-TOTAL_FORMS': '2', # the number of forms rendered 'form-INITIAL_FORMS': '1', # the number of forms with initial data @@ -294,22 +294,22 @@ class ModelFormsetTest(TestCase): qs = Author.objects.order_by('name') - AuthorFormSet = modelformset_factory(Author, max_num=None, extra=3) + AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=None, extra=3) formset = AuthorFormSet(queryset=qs) self.assertEqual(len(formset.forms), 6) self.assertEqual(len(formset.extra_forms), 3) - AuthorFormSet = modelformset_factory(Author, max_num=4, extra=3) + AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=4, extra=3) formset = AuthorFormSet(queryset=qs) self.assertEqual(len(formset.forms), 4) self.assertEqual(len(formset.extra_forms), 1) - AuthorFormSet = modelformset_factory(Author, max_num=0, extra=3) + AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=0, extra=3) formset = AuthorFormSet(queryset=qs) self.assertEqual(len(formset.forms), 3) self.assertEqual(len(formset.extra_forms), 0) - AuthorFormSet = modelformset_factory(Author, max_num=None) + AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=None) formset = AuthorFormSet(queryset=qs) self.assertQuerysetEqual(formset.get_queryset(), [ '', @@ -317,7 +317,7 @@ class ModelFormsetTest(TestCase): '', ]) - AuthorFormSet = modelformset_factory(Author, max_num=0) + AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=0) formset = AuthorFormSet(queryset=qs) self.assertQuerysetEqual(formset.get_queryset(), [ '', @@ -325,7 +325,7 @@ class ModelFormsetTest(TestCase): '', ]) - AuthorFormSet = modelformset_factory(Author, max_num=4) + AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=4) formset = AuthorFormSet(queryset=qs) self.assertQuerysetEqual(formset.get_queryset(), [ '', @@ -343,7 +343,7 @@ class ModelFormsetTest(TestCase): author.save() return author - PoetFormSet = modelformset_factory(Poet, form=PoetForm) + PoetFormSet = modelformset_factory(Poet, fields="__all__", form=PoetForm) data = { 'form-TOTAL_FORMS': '3', # the number of forms rendered @@ -387,7 +387,7 @@ class ModelFormsetTest(TestCase): self.assertFalse("subtitle" in formset.forms[0].fields) def test_model_inheritance(self): - BetterAuthorFormSet = modelformset_factory(BetterAuthor) + BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields="__all__") formset = BetterAuthorFormSet() self.assertEqual(len(formset.forms), 1) self.assertHTMLEqual(formset.forms[0].as_p(), @@ -440,7 +440,7 @@ class ModelFormsetTest(TestCase): # We can also create a formset that is tied to a parent model. This is # how the admin system's edit inline functionality works. - AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3) + AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3, fields="__all__") author = Author.objects.create(name='Charles Baudelaire') formset = AuthorBooksFormSet(instance=author) @@ -474,7 +474,7 @@ class ModelFormsetTest(TestCase): # another one. This time though, an edit form will be available for # every existing book. - AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2) + AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__") author = Author.objects.get(name='Charles Baudelaire') formset = AuthorBooksFormSet(instance=author) @@ -514,7 +514,7 @@ class ModelFormsetTest(TestCase): def test_inline_formsets_save_as_new(self): # The save_as_new parameter lets you re-associate the data to a new # instance. This is used in the admin for save_as functionality. - AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2) + AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__") author = Author.objects.create(name='Charles Baudelaire') data = { @@ -553,7 +553,7 @@ class ModelFormsetTest(TestCase): # primary key that is not the fk to the parent object. self.maxDiff = 1024 - AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1) + AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1, fields="__all__") author = Author.objects.create(pk=1, name='Charles Baudelaire') formset = AuthorBooksFormSet2(instance=author) @@ -585,7 +585,7 @@ class ModelFormsetTest(TestCase): # Test inline formsets where the inline-edited object uses multi-table # inheritance, thus has a non AutoField yet auto-created primary key. - AuthorBooksFormSet3 = inlineformset_factory(Author, AlternateBook, can_delete=False, extra=1) + AuthorBooksFormSet3 = inlineformset_factory(Author, AlternateBook, can_delete=False, extra=1, fields="__all__") author = Author.objects.create(pk=1, name='Charles Baudelaire') formset = AuthorBooksFormSet3(instance=author) @@ -616,7 +616,7 @@ class ModelFormsetTest(TestCase): # Test inline formsets where the inline-edited object has a # unique_together constraint with a nullable member - AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2) + AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2, fields="__all__") author = Author.objects.create(pk=1, name='Charles Baudelaire') data = { @@ -640,7 +640,7 @@ class ModelFormsetTest(TestCase): self.assertEqual(book2.title, 'Les Fleurs du Mal') def test_inline_formsets_with_custom_save_method(self): - AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2) + AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__") author = Author.objects.create(pk=1, name='Charles Baudelaire') book1 = Book.objects.create(pk=1, author=author, title='Les Paradis Artificiels') book2 = Book.objects.create(pk=2, author=author, title='Les Fleurs du Mal') @@ -655,7 +655,7 @@ class ModelFormsetTest(TestCase): poem.save() return poem - PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm) + PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm, fields="__all__") data = { 'poem_set-TOTAL_FORMS': '3', # the number of forms rendered @@ -732,7 +732,7 @@ class ModelFormsetTest(TestCase): def test_custom_pk(self): # We need to ensure that it is displayed - CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey) + CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey, fields="__all__") formset = CustomPrimaryKeyFormSet() self.assertEqual(len(formset.forms), 1) self.assertHTMLEqual(formset.forms[0].as_p(), @@ -743,7 +743,7 @@ class ModelFormsetTest(TestCase): place = Place.objects.create(pk=1, name='Giordanos', city='Chicago') - FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False) + FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False, fields="__all__") formset = FormSet(instance=place) self.assertEqual(len(formset.forms), 2) self.assertHTMLEqual(formset.forms[0].as_p(), @@ -799,7 +799,7 @@ class ModelFormsetTest(TestCase): # Ensure a custom primary key that is a ForeignKey or OneToOneField get rendered for the user to choose. - FormSet = modelformset_factory(OwnerProfile) + FormSet = modelformset_factory(OwnerProfile, fields="__all__") formset = FormSet() self.assertHTMLEqual(formset.forms[0].as_p(), '

    ' % (band2.id, self.band.id)) class AdminConcertForm(forms.ModelForm): - class Meta: - model = Concert + pass def __init__(self, *args, **kwargs): super(AdminConcertForm, self).__init__(*args, **kwargs) @@ -685,9 +681,6 @@ class ValidationTests(unittest.TestCase): class AdminBandForm(forms.ModelForm): delete = forms.BooleanField() - class Meta: - model = Band - class BandAdmin(ModelAdmin): form = AdminBandForm diff --git a/tests/timezones/forms.py b/tests/timezones/forms.py index 3c9c31167e..45fb1d080b 100644 --- a/tests/timezones/forms.py +++ b/tests/timezones/forms.py @@ -11,3 +11,4 @@ class EventSplitForm(forms.Form): class EventModelForm(forms.ModelForm): class Meta: model = Event + fields = '__all__' -- cgit v1.3