Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixed #6681 -- Don't break docutils when rendering reStructuredText. #1277

Closed
wants to merge 10 commits into from

8 participants

@mrmachine

No description provided.

@mrmachine

This latest commit avoids the need to set a global default at all, so it should be compatible with any 3rd party code that needs to publish reStructuredText, and any 3rd party code that changes the global default like we were won't break admindocs.

We should probably squash these before merging, but I've left the earlier commit just to show the progression for any reviewers.

akaariai and others added some commits
@akaariai akaariai Fixed ordering related test failure
Also PEP8 + python_2_unicode_compatible cleanup done.
263b873
@mjtamlyn mjtamlyn Merge pull request #1441 from loic/ticket16986
Fixed #16986 -- Model.clean() can report errors on individual fields.
0b771fc
@dominicrodger dominicrodger Fixed #20852 - Fixed incorrectly generated left quotes in docs.
Sphinx generates left single quotes for apostrophes after
code markup, when right single quotes are required. The
easiest way to fix this is just by inserting the unicode
character for a right single quote.

Instances of the problem were found by looking for
">‘" in the generated HTML.
c33d1ca
Tai Lee Fixed #15511 -- Allow optional fields on ``MultiValueField` subclasses.
The `MultiValueField` class gets a new ``require_all_fields`` argument that
defaults to ``True``. If set to ``False``, individual fields can be made
optional, and a new ``incomplete`` validation error will be raised if any
required fields have empty values.

The ``incomplete`` error message can be defined on a `MultiValueField`
subclass or on each individual field. Skip duplicate errors.
1280675
@timgraham
Owner

Any idea if we can add a test for this?

@mrmachine

Rebased onto master. Tests added. Lucky, because I found a bug in my implementation. Now we also restore the default role after temporarily setting it to cmsreference.

Do you want me to squash all these commits down?

Thanks for the review!

@timgraham timgraham commented on the diff
tests/admin_docs/tests.py
@@ -43,3 +44,43 @@ def test_xview_class(self):
user.save()
response = self.client.head('/xview/class/')
self.assertFalse('X-View' in response)
+
+
+class DefaultRoleTest(TestCase):
@timgraham Owner

These tests should be skipped if docutils isn't installed

try:
    import docutils
except ImportError:
    docutils = None


@unittest.skipUnless(docutils, "no docutils installed.")
class DefaultRoleTest(TestCase):
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@timgraham timgraham commented on the diff
tests/admin_docs/tests.py
((19 lines not shown))
+ markup % 'views/title/')
+ self.assertEqual(utils.parse_rst('`title`', 'template'),
+ markup % 'templates/title/')
+ self.assertEqual(utils.parse_rst('`title`', 'filter'),
+ markup % 'filters/#title')
+ self.assertEqual(utils.parse_rst('`title`', 'tag'),
+ markup % 'tags/#title')
+
+ def test_publish_parts(self):
+ """
+ Tests that Django hasn't broken the default role for interpreted text
+ when ``publish_parts`` is used directly, by setting it to
+ ``cmsreference``. See #6681.
+ """
+ try:
+ from docutils.core import publish_parts
@timgraham Owner

can this be removed with the skip logic I added? If not, could you add a comment indicating the purpose?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@timgraham
Owner

It's easy enough to squash the commits when merging and having the individual commits makes reviewing a bit easier -- thanks again!

@timgraham
Owner

merged in bcd4c3f - thanks!

@timgraham timgraham closed this
@mrmachine mrmachine deleted the thirstydigital:tickets/6681-rst-default-role branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 6, 2013
  1. @akaariai

    Fixed ordering related test failure

    akaariai authored
    Also PEP8 + python_2_unicode_compatible cleanup done.
  2. @mjtamlyn

    Merge pull request #1441 from loic/ticket16986

    mjtamlyn authored
    Fixed #16986 -- Model.clean() can report errors on individual fields.
  3. @dominicrodger @timgraham

    Fixed #20852 - Fixed incorrectly generated left quotes in docs.

    dominicrodger authored timgraham committed
    Sphinx generates left single quotes for apostrophes after
    code markup, when right single quotes are required. The
    easiest way to fix this is just by inserting the unicode
    character for a right single quote.
    
    Instances of the problem were found by looking for
    ">‘" in the generated HTML.
  4. @timgraham

    Fixed #15511 -- Allow optional fields on ``MultiValueField` subclasses.

    Tai Lee authored timgraham committed
    The `MultiValueField` class gets a new ``require_all_fields`` argument that
    defaults to ``True``. If set to ``False``, individual fields can be made
    optional, and a new ``incomplete`` validation error will be raised if any
    required fields have empty values.
    
    The ``incomplete`` error message can be defined on a `MultiValueField`
    subclass or on each individual field. Skip duplicate errors.
  5. @kux @timgraham
  6. @unho @timgraham

    Added section labels in cache docs

    unho authored timgraham committed
  7. @collinanderson @timgraham
Commits on Aug 7, 2013
  1. Don't set a global default interpreted role function for reStructured…

    Tai Lee authored
    …Text.
    
    Instead, use the `default-role` directive to change the default only within
    the `parse_rst()` function.
  2. Add tests. Restore default role after temporarily setting it to ``cms…

    Tai Lee authored
    …reference`` in ``parse_rst()``.
This page is out of date. Refresh to see the latest.
Showing with 285 additions and 64 deletions.
  1. +2 −0  django/contrib/admin/widgets.py
  2. +12 −4 django/contrib/admindocs/utils.py
  3. +1 −0  django/db/models/query.py
  4. +24 −8 django/forms/fields.py
  5. +2 −2 docs/howto/custom-management-commands.txt
  6. +4 −4 docs/howto/error-reporting.txt
  7. +1 −1  docs/intro/tutorial05.txt
  8. +1 −1  docs/ref/class-based-views/mixins-date-based.txt
  9. +1 −1  docs/ref/contrib/admin/index.txt
  10. +1 −1  docs/ref/contrib/auth.txt
  11. +1 −1  docs/ref/contrib/comments/moderation.txt
  12. +1 −1  docs/ref/django-admin.txt
  13. +40 −1 docs/ref/forms/fields.txt
  14. +1 −1  docs/ref/models/options.txt
  15. +1 −1  docs/ref/models/querysets.txt
  16. +2 −2 docs/ref/settings.txt
  17. +1 −1  docs/ref/templates/api.txt
  18. +2 −2 docs/releases/1.3.txt
  19. +2 −2 docs/releases/1.6.txt
  20. +6 −1 docs/releases/1.7.txt
  21. +4 −0 docs/topics/cache.txt
  22. +1 −1  docs/topics/class-based-views/mixins.txt
  23. +3 −3 docs/topics/db/managers.txt
  24. +1 −1  docs/topics/db/queries.txt
  25. +1 −1  docs/topics/db/tablespaces.txt
  26. +2 −2 docs/topics/forms/modelforms.txt
  27. +41 −0 tests/admin_docs/tests.py
  28. +2 −1  tests/admin_docs/urls.py
  29. +8 −1 tests/admin_widgets/tests.py
  30. +20 −11 tests/foreign_object/models.py
  31. +13 −7 tests/foreign_object/tests.py
  32. +70 −0 tests/forms_tests/tests/test_forms.py
  33. +13 −1 tests/select_for_update/tests.py
View
2  django/contrib/admin/widgets.py
@@ -116,6 +116,8 @@ def url_params_from_lookup_dict(lookups):
if lookups and hasattr(lookups, 'items'):
items = []
for k, v in lookups.items():
+ if callable(v):
+ v = v()
if isinstance(v, (tuple, list)):
v = ','.join([str(x) for x in v])
elif isinstance(v, bool):
View
16 django/contrib/admindocs/utils.py
@@ -67,9 +67,18 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None):
}
if thing_being_parsed:
thing_being_parsed = force_bytes("<%s>" % thing_being_parsed)
- parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
- destination_path=None, writer_name='html',
- settings_overrides=overrides)
+ # Wrap ``text`` in some reST that sets the default role to ``cmsreference``,
+ # then restores it.
+ source = """
+.. default-role:: cmsreference
+
+%s
+
+.. default-role::
+"""
+ parts = docutils.core.publish_parts(source % text,
+ source_path=thing_being_parsed, destination_path=None,
+ writer_name='html', settings_overrides=overrides)
return mark_safe(parts['fragment'])
#
@@ -100,7 +109,6 @@ def default_reference_role(name, rawtext, text, lineno, inliner, options=None, c
if docutils_is_available:
docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role)
- docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference'
for name, urlbase in ROLES.items():
create_reference_role(name, urlbase)
View
1  django/db/models/query.py
@@ -742,6 +742,7 @@ def select_for_update(self, **kwargs):
# Default to false for nowait
nowait = kwargs.pop('nowait', False)
obj = self._clone()
+ obj._for_write = True
obj.query.select_for_update = True
obj.query.select_for_update_nowait = nowait
return obj
View
32 django/forms/fields.py
@@ -955,15 +955,20 @@ class MultiValueField(Field):
"""
default_error_messages = {
'invalid': _('Enter a list of values.'),
+ 'incomplete': _('Enter a complete value.'),
}
def __init__(self, fields=(), *args, **kwargs):
+ self.require_all_fields = kwargs.pop('require_all_fields', True)
super(MultiValueField, self).__init__(*args, **kwargs)
- # Set 'required' to False on the individual fields, because the
- # required validation will be handled by MultiValueField, not by those
- # individual fields.
for f in fields:
- f.required = False
+ f.error_messages.setdefault('incomplete',
+ self.error_messages['incomplete'])
+ if self.require_all_fields:
+ # Set 'required' to False on the individual fields, because the
+ # required validation will be handled by MultiValueField, not
+ # by those individual fields.
+ f.required = False
self.fields = fields
def validate(self, value):
@@ -993,15 +998,26 @@ def clean(self, value):
field_value = value[i]
except IndexError:
field_value = None
- if self.required and field_value in self.empty_values:
- raise ValidationError(self.error_messages['required'], code='required')
+ if field_value in self.empty_values:
+ if self.require_all_fields:
+ # Raise a 'required' error if the MultiValueField is
+ # required and any field is empty.
+ if self.required:
+ raise ValidationError(self.error_messages['required'], code='required')
+ elif field.required:
+ # Otherwise, add an 'incomplete' error to the list of
+ # collected errors and skip field cleaning, if a required
+ # field is empty.
+ if field.error_messages['incomplete'] not in errors:
+ errors.append(field.error_messages['incomplete'])
+ continue
try:
clean_data.append(field.clean(field_value))
except ValidationError as e:
# Collect all validation errors in a single list, which we'll
# raise at the end of clean(), rather than raising a single
- # exception for the first error we encounter.
- errors.extend(e.error_list)
+ # exception for the first error we encounter. Skip duplicates.
+ errors.extend(m for m in e.error_list if m not in errors)
if errors:
raise ValidationError(errors)
View
4 docs/howto/custom-management-commands.txt
@@ -193,7 +193,7 @@ Attributes
----------
All attributes can be set in your derived class and can be used in
-:class:`BaseCommand`'s :ref:`subclasses<ref-basecommand-subclasses>`.
+:class:`BaseCommand`s :ref:`subclasses<ref-basecommand-subclasses>`.
.. attribute:: BaseCommand.args
@@ -267,7 +267,7 @@ the :meth:`~BaseCommand.handle` method must be implemented.
.. admonition:: Implementing a constructor in a subclass
If you implement ``__init__`` in your subclass of :class:`BaseCommand`,
- you must call :class:`BaseCommand`'s ``__init__``.
+ you must call :class:`BaseCommand`s ``__init__``.
.. code-block:: python
View
8 docs/howto/error-reporting.txt
@@ -120,8 +120,8 @@ Filtering sensitive information
Error reports are really helpful for debugging errors, so it is generally
useful to record as much relevant information about those errors as possible.
For example, by default Django records the `full traceback`_ for the
-exception raised, each `traceback frame`_'s local variables, and the
-:class:`~django.http.HttpRequest`'s :ref:`attributes<httprequest-attributes>`.
+exception raised, each `traceback frame`_s local variables, and the
+:class:`~django.http.HttpRequest`s :ref:`attributes<httprequest-attributes>`.
However, sometimes certain types of information may be too sensitive and thus
may not be appropriate to be kept track of, for example a user's password or
@@ -164,7 +164,7 @@ production environment (that is, where :setting:`DEBUG` is set to ``False``):
.. admonition:: When using mutiple decorators
If the variable you want to hide is also a function argument (e.g.
- '``user``' in the following example), and if the decorated function has
+ '``user`` in the following example), and if the decorated function has
mutiple decorators, then make sure to place ``@sensitive_variables`` at
the top of the decorator chain. This way it will also hide the function
argument as it gets passed through the other decorators::
@@ -232,7 +232,7 @@ own filter class and tell Django to use it via the
DEFAULT_EXCEPTION_REPORTER_FILTER = 'path.to.your.CustomExceptionReporterFilter'
You may also control in a more granular way which filter to use within any
-given view by setting the ``HttpRequest``'s ``exception_reporter_filter``
+given view by setting the ``HttpRequest``s ``exception_reporter_filter``
attribute::
def my_view(request):
View
2  docs/intro/tutorial05.txt
@@ -132,7 +132,7 @@ We identify a bug
Fortunately, there's a little bug in the ``polls`` application for us to fix
right away: the ``Poll.was_published_recently()`` method returns ``True`` if
the ``Poll`` was published within the last day (which is correct) but also if
-the ``Poll``'s ``pub_date`` field is in the future (which certainly isn't).
+the ``Poll``s ``pub_date`` field is in the future (which certainly isn't).
You can see this in the Admin; create a poll whose date lies in the future;
you'll see that the ``Poll`` change list claims it was published recently.
View
2  docs/ref/class-based-views/mixins-date-based.txt
@@ -225,7 +225,7 @@ DateMixin
.. attribute:: date_field
The name of the ``DateField`` or ``DateTimeField`` in the
- ``QuerySet``'s model that the date-based archive should use to
+ ``QuerySet``s model that the date-based archive should use to
determine the list of objects to display on the page.
When :doc:`time zone support </topics/i18n/timezones>` is enabled and
View
2  docs/ref/contrib/admin/index.txt
@@ -1615,7 +1615,7 @@ in your own admin JavaScript without including a second copy, you can use the
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
-to add jQuery to your ``ModelAdmin``'s list of media resources unless you have
+to add jQuery to your ``ModelAdmin``s list of media resources unless you have
a specifc need. For example, if you require the jQuery library to be in the
global namespace (for example when using third-party jQuery plugins) or if you
need a newer version of jQuery, you will have to include your own copy.
View
2  docs/ref/contrib/auth.txt
@@ -243,7 +243,7 @@ Manager methods
be called.
The ``extra_fields`` keyword arguments are passed through to the
- :class:`~django.contrib.auth.models.User`'s ``__init__`` method to
+ :class:`~django.contrib.auth.models.User`s ``__init__`` method to
allow setting arbitrary fields on a :ref:`custom User model
<auth-custom-user>`.
View
2  docs/ref/contrib/comments/moderation.txt
@@ -54,7 +54,7 @@ following model, which would represent entries in a Weblog::
Now, suppose that we want the following steps to be applied whenever a
new comment is posted on an ``Entry``:
-1. If the ``Entry``'s ``enable_comments`` field is ``False``, the
+1. If the ``Entry``s ``enable_comments`` field is ``False``, the
comment will simply be disallowed (i.e., immediately deleted).
2. If the ``enable_comments`` field is ``True``, the comment will be
View
2  docs/ref/django-admin.txt
@@ -560,7 +560,7 @@ several lines in language files.
.. django-admin-option:: --no-location
-Use the ``--no-location`` option to not write '``#: filename:line``'
+Use the ``--no-location`` option to not write '``#: filename:line``
comment lines in language files. Note that using this option makes it harder
for technically skilled translators to understand each message's context.
View
41 docs/ref/forms/fields.txt
@@ -877,7 +877,7 @@ Slightly complex built-in ``Field`` classes
* Normalizes to: the type returned by the ``compress`` method of the subclass.
* Validates that the given value against each of the fields specified
as an argument to the ``MultiValueField``.
- * Error message keys: ``required``, ``invalid``
+ * Error message keys: ``required``, ``invalid``, ``incomplete``
Aggregates the logic of multiple fields that together produce a single
value.
@@ -898,6 +898,45 @@ Slightly complex built-in ``Field`` classes
Once all fields are cleaned, the list of clean values is combined into
a single value by :meth:`~MultiValueField.compress`.
+ Also takes one extra optional argument:
+
+ .. attribute:: require_all_fields
+
+ .. versionadded:: 1.7
+
+ Defaults to ``True``, in which case a ``required`` validation error
+ will be raised if no value is supplied for any field.
+
+ When set to ``False``, the :attr:`Field.required` attribute can be set
+ to ``False`` for individual fields to make them optional. If no value
+ is supplied for a required field, an ``incomplete`` validation error
+ will be raised.
+
+ A default ``incomplete`` error message can be defined on the
+ :class:`MultiValueField` subclass, or different messages can be defined
+ on each individual field. For example::
+
+ from django.core.validators import RegexValidator
+
+ class PhoneField(MultiValueField):
+ def __init__(self, *args, **kwargs):
+ # Define one message for all fields.
+ error_messages = {
+ 'incomplete': 'Enter a country code and phone number.',
+ }
+ # Or define a different message for each field.
+ fields = (
+ CharField(error_messages={'incomplete': 'Enter a country code.'},
+ validators=[RegexValidator(r'^\d+$', 'Enter a valid country code.')]),
+ CharField(error_messages={'incomplete': 'Enter a phone number.'},
+ validators=[RegexValidator(r'^\d+$', 'Enter a valid phone number.')]),
+ CharField(validators=[RegexValidator(r'^\d+$', 'Enter a valid extension.')],
+ required=False),
+ )
+ super(PhoneField, self).__init__(
+ self, error_messages=error_messages, fields=fields,
+ require_all_fields=False, *args, **kwargs)
+
.. attribute:: MultiValueField.widget
Must be a subclass of :class:`django.forms.MultiWidget`.
View
2  docs/ref/models/options.txt
@@ -90,7 +90,7 @@ Django quotes column and table names behind the scenes.
The name of an orderable field in the model, typically a :class:`DateField`,
:class:`DateTimeField`, or :class:`IntegerField`. This specifies the default
- field to use in your model :class:`Manager`'s
+ field to use in your model :class:`Manager`s
:meth:`~django.db.models.query.QuerySet.latest` and
:meth:`~django.db.models.query.QuerySet.earliest` methods.
View
2  docs/ref/models/querysets.txt
@@ -1924,7 +1924,7 @@ as_manager
.. versionadded:: 1.7
Class method that returns an instance of :class:`~django.db.models.Manager`
-with a copy of the ``QuerySet``'s methods. See
+with a copy of the ``QuerySet``s methods. See
:ref:`create-manager-with-queryset-methods` for more details.
.. _field-lookups:
View
4 docs/ref/settings.txt
@@ -1744,7 +1744,7 @@ Default::
A tuple of template loader classes, specified as strings. Each ``Loader`` class
knows how to import templates from a particular source. Optionally, a tuple can be
-used instead of a string. The first item in the tuple should be the ``Loader``'s
+used instead of a string. The first item in the tuple should be the ``Loader``s
module, subsequent items are passed to the ``Loader`` during initialization. See
:doc:`/ref/templates/api`.
@@ -2478,7 +2478,7 @@ files</howto/static-files/index>` for more details about usage.
your static files from their permanent locations into one directory for
ease of deployment; it is **not** a place to store your static files
permanently. You should do that in directories that will be found by
- :doc:`staticfiles</ref/contrib/staticfiles>`'s
+ :doc:`staticfiles</ref/contrib/staticfiles>`s
:setting:`finders<STATICFILES_FINDERS>`, which by default, are
``'static/'`` app sub-directories and any directories you include in
:setting:`STATICFILES_DIRS`).
View
2  docs/ref/templates/api.txt
@@ -708,7 +708,7 @@ class. Here are the template loaders that come with Django:
with your own ``admin/base_site.html`` in ``myproject.polls``. You must
then make sure that your ``myproject.polls`` comes *before*
``django.contrib.admin`` in :setting:`INSTALLED_APPS`, otherwise
- ``django.contrib.admin``'s will be loaded first and yours will be ignored.
+ ``django.contrib.admin``s will be loaded first and yours will be ignored.
Note that the loader performs an optimization when it is first imported:
it caches a list of which :setting:`INSTALLED_APPS` packages have a
View
4 docs/releases/1.3.txt
@@ -365,7 +365,7 @@ In earlier Django versions, when a model instance containing a
file from the backend storage. This opened the door to several data-loss
scenarios, including rolled-back transactions and fields on different models
referencing the same file. In Django 1.3, when a model is deleted the
-:class:`~django.db.models.FileField`'s ``delete()`` method won't be called. If
+:class:`~django.db.models.FileField`s ``delete()`` method won't be called. If
you need cleanup of orphaned files, you'll need to handle it yourself (for
instance, with a custom management command that can be run manually or
scheduled to run periodically via e.g. cron).
@@ -654,7 +654,7 @@ Password reset view now accepts ``from_email``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :func:`django.contrib.auth.views.password_reset` view now accepts a
-``from_email`` parameter, which is passed to the ``password_reset_form``'s
+``from_email`` parameter, which is passed to the ``password_reset_form``s
``save()`` method as a keyword argument. If you are using this view with a
custom password reset form, then you will need to ensure your form's ``save()``
method accepts this keyword argument.
View
4 docs/releases/1.6.txt
@@ -183,7 +183,7 @@ Minor features
* The :attr:`~django.views.generic.edit.DeletionMixin.success_url` of
:class:`~django.views.generic.edit.DeletionMixin` is now interpolated with
- its ``object``\'s ``__dict__``.
+ its ``object``s ``__dict__``.
* :class:`~django.http.HttpResponseRedirect` and
:class:`~django.http.HttpResponsePermanentRedirect` now provide an ``url``
@@ -330,7 +330,7 @@ Minor features
* :class:`~django.forms.ModelForm` fields can now override error messages
defined in model fields by using the
- :attr:`~django.forms.Field.error_messages` argument of a ``Field``'s
+ :attr:`~django.forms.Field.error_messages` argument of a ``Field``s
constructor. To take advantage of this new feature with your custom fields,
:ref:`see the updated recommendation <raising-validation-error>` for raising
a ``ValidationError``.
View
7 docs/releases/1.7.txt
@@ -122,6 +122,11 @@ Minor features
``html_email_template_name`` parameter used to send a multipart HTML email
for password resets.
+* :class:`~django.forms.MultiValueField` allows optional subfields by setting
+ the ``require_all_fields`` argument to ``False``. The ``required`` attribute
+ for each individual field will be respected, and a new ``incomplete``
+ validation error will be raised when any required fields are empty.
+
Backwards incompatible changes in 1.7
=====================================
@@ -141,7 +146,7 @@ Miscellaneous
have a custom :class:`~django.core.files.uploadhandler.FileUploadHandler`
that implements ``new_file()``, be sure it accepts this new parameter.
-* :class:`ModelFormSet<django.forms.models.BaseModelFormSet>`'s no longer
+* :class:`ModelFormSet<django.forms.models.BaseModelFormSet>`s no longer
delete instances when ``save(commit=False)`` is called. See
:attr:`~django.forms.formsets.BaseFormSet.can_delete` for instructions on how
to manually delete objects from deleted forms.
View
4 docs/topics/cache.txt
@@ -39,6 +39,8 @@ Django also works well with "upstream" caches, such as `Squid
caches that you don't directly control but to which you can provide hints (via
HTTP headers) about which parts of your site should be cached, and how.
+.. _setting-up-the-cache:
+
Setting up the cache
====================
@@ -152,6 +154,8 @@ permanent storage -- they're all intended to be solutions for caching, not
storage -- but we point this out here because memory-based caching is
particularly temporary.
+.. _database-caching:
+
Database caching
----------------
View
2  docs/topics/class-based-views/mixins.txt
@@ -288,7 +288,7 @@ object. In order to do this, we need to have two different querysets:
``Book`` queryset for use by :class:`~django.views.generic.list.ListView`
Since we have access to the ``Publisher`` whose books we want to list, we
- simply override ``get_queryset()`` and use the ``Publisher``'s
+ simply override ``get_queryset()`` and use the ``Publisher``s
:ref:`reverse foreign key manager<backwards-related-objects>`.
``Publisher`` queryset for use in :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
View
6 docs/topics/db/managers.txt
@@ -100,7 +100,7 @@ access ``self.model`` to get the model class to which they're attached.
Modifying initial Manager QuerySets
-----------------------------------
-A ``Manager``'s base ``QuerySet`` returns all objects in the system. For
+A ``Manager``s base ``QuerySet`` returns all objects in the system. For
example, using this model::
from django.db import models
@@ -111,7 +111,7 @@ example, using this model::
...the statement ``Book.objects.all()`` will return all books in the database.
-You can override a ``Manager``\'s base ``QuerySet`` by overriding the
+You can override a ``Manager``s base ``QuerySet`` by overriding the
``Manager.get_queryset()`` method. ``get_queryset()`` should return a
``QuerySet`` with the properties you require.
@@ -246,7 +246,7 @@ Creating ``Manager`` with ``QuerySet`` methods
In lieu of the above approach which requires duplicating methods on both the
``QuerySet`` and the ``Manager``, :meth:`QuerySet.as_manager()
<django.db.models.query.QuerySet.as_manager>` can be used to create an instance
-of ``Manager`` with a copy of a custom ``QuerySet``'s methods::
+of ``Manager`` with a copy of a custom ``QuerySet``s methods::
class Person(models.Model):
...
View
2  docs/topics/db/queries.txt
@@ -720,7 +720,7 @@ efficient code.
In a newly created :class:`~django.db.models.query.QuerySet`, the cache is
empty. The first time a :class:`~django.db.models.query.QuerySet` is evaluated
-- and, hence, a database query happens -- Django saves the query results in
-the :class:`~django.db.models.query.QuerySet`\'s cache and returns the results
+the :class:`~django.db.models.query.QuerySet`s cache and returns the results
that have been explicitly requested (e.g., the next element, if the
:class:`~django.db.models.query.QuerySet` is being iterated over). Subsequent
evaluations of the :class:`~django.db.models.query.QuerySet` reuse the cached
View
2  docs/topics/db/tablespaces.txt
@@ -30,7 +30,7 @@ Declaring tablespaces for indexes
---------------------------------
You can pass the :attr:`~django.db.models.Field.db_tablespace` option to a
-``Field`` constructor to specify an alternate tablespace for the ``Field``'s
+``Field`` constructor to specify an alternate tablespace for the ``Field``s
column index. If no index would be created for the column, the option is
ignored.
View
4 docs/topics/forms/modelforms.txt
@@ -392,7 +392,7 @@ these security concerns do not apply to you:
model = Author
fields = '__all__'
-2. Set the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class to
+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::
@@ -757,7 +757,7 @@ Specifying widgets to use in the form with ``widgets``
.. versionadded:: 1.6
Using the ``widgets`` parameter, you can specify a dictionary of values to
-customize the ``ModelForm``'s widget class for a particular field. This
+customize the ``ModelForm``s widget class for a particular field. This
works the same way as the ``widgets`` dictionary on the inner ``Meta``
class of a ``ModelForm`` works::
View
41 tests/admin_docs/tests.py
@@ -1,4 +1,5 @@
from django.contrib.auth.models import User
+from django.contrib.admindocs import utils
from django.test import TestCase
from django.test.utils import override_settings
@@ -43,3 +44,43 @@ def test_xview_class(self):
user.save()
response = self.client.head('/xview/class/')
self.assertFalse('X-View' in response)
+
+
+class DefaultRoleTest(TestCase):
@timgraham Owner

These tests should be skipped if docutils isn't installed

try:
    import docutils
except ImportError:
    docutils = None


@unittest.skipUnless(docutils, "no docutils installed.")
class DefaultRoleTest(TestCase):
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ urls = 'admin_docs.urls'
+
+ def test_parse_rst(self):
+ """
+ Tests that ``django.contrib.admindocs.utils.parse_rst`` uses
+ ``cmsreference`` as the default role.
+ """
+ markup = ('<p><a class="reference external" href="/admindocs/%s">'
+ 'title</a></p>\n')
+ self.assertEqual(utils.parse_rst('`title`', 'model'),
+ markup % 'models/title/')
+ self.assertEqual(utils.parse_rst('`title`', 'view'),
+ markup % 'views/title/')
+ self.assertEqual(utils.parse_rst('`title`', 'template'),
+ markup % 'templates/title/')
+ self.assertEqual(utils.parse_rst('`title`', 'filter'),
+ markup % 'filters/#title')
+ self.assertEqual(utils.parse_rst('`title`', 'tag'),
+ markup % 'tags/#title')
+
+ def test_publish_parts(self):
+ """
+ Tests that Django hasn't broken the default role for interpreted text
+ when ``publish_parts`` is used directly, by setting it to
+ ``cmsreference``. See #6681.
+ """
+ try:
+ from docutils.core import publish_parts
@timgraham Owner

can this be removed with the skip logic I added? If not, could you add a comment indicating the purpose?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ import docutils.parsers.rst.roles
+ except ImportError:
+ return
+ self.assertNotEqual(docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE,
+ 'cmsreference')
+ source = 'reST, `interpreted text`, default role.'
+ markup = '<p>reST, <cite>interpreted text</cite>, default role.</p>\n'
+ parts = publish_parts(source=source, writer_name="html4css1")
+ self.assertEqual(parts['fragment'], markup)
View
3  tests/admin_docs/urls.py
@@ -1,8 +1,9 @@
-from django.conf.urls import patterns
+from django.conf.urls import include, patterns
from . import views
urlpatterns = patterns('',
+ (r'^admindocs/', include('django.contrib.admindocs.urls')),
(r'^xview/func/$', views.xview_dec(views.xview)),
(r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())),
)
View
9 tests/admin_widgets/tests.py
@@ -226,6 +226,13 @@ def test_url_params_from_lookup_dict_any_iterable(self):
self.assertEqual(lookup1, {'color__in': 'red,blue'})
self.assertEqual(lookup1, lookup2)
+ def test_url_params_from_lookup_dict_callable(self):
+ def my_callable():
+ return 'works'
+ lookup1 = widgets.url_params_from_lookup_dict({'myfield': my_callable})
+ lookup2 = widgets.url_params_from_lookup_dict({'myfield': my_callable()})
+ self.assertEqual(lookup1, lookup2)
+
class FilteredSelectMultipleWidgetTest(DjangoTestCase):
def test_render(self):
@@ -915,4 +922,4 @@ class AdminRawIdWidgetSeleniumChromeTests(AdminRawIdWidgetSeleniumFirefoxTests):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
class AdminRawIdWidgetSeleniumIETests(AdminRawIdWidgetSeleniumFirefoxTests):
- webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
+ webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
View
31 tests/foreign_object/models.py
@@ -5,14 +5,15 @@
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import get_language
+@python_2_unicode_compatible
class Country(models.Model):
# Table Column Fields
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
-
+@python_2_unicode_compatible
class Person(models.Model):
# Table Column Fields
name = models.CharField(max_length=128)
@@ -26,9 +27,10 @@ class Person(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Group(models.Model):
# Table Column Fields
name = models.CharField(max_length=128)
@@ -38,10 +40,11 @@ class Group(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Membership(models.Model):
# Table Column Fields
membership_country = models.ForeignKey(Country)
@@ -51,17 +54,19 @@ class Membership(models.Model):
group_id = models.IntegerField()
# Relation Fields
- person = models.ForeignObject(Person,
+ person = models.ForeignObject(
+ Person,
from_fields=['membership_country', 'person_id'],
to_fields=['person_country_id', 'id'])
- group = models.ForeignObject(Group,
+ group = models.ForeignObject(
+ Group,
from_fields=['membership_country', 'group_id'],
to_fields=['group_country', 'id'])
class Meta:
ordering = ('date_joined', 'invite_reason')
- def __unicode__(self):
+ def __str__(self):
return "%s is a member of %s" % (self.person.name, self.group.name)
@@ -73,17 +78,20 @@ class Friendship(models.Model):
to_friend_id = models.IntegerField()
# Relation Fields
- from_friend = models.ForeignObject(Person,
+ from_friend = models.ForeignObject(
+ Person,
from_fields=['from_friend_country', 'from_friend_id'],
to_fields=['person_country_id', 'id'],
related_name='from_friend')
- to_friend_country = models.ForeignObject(Country,
+ to_friend_country = models.ForeignObject(
+ Country,
from_fields=['to_friend_country_id'],
to_fields=['id'],
related_name='to_friend_country')
- to_friend = models.ForeignObject(Person,
+ to_friend = models.ForeignObject(
+ Person,
from_fields=['to_friend_country_id', 'to_friend_id'],
to_fields=['person_country_id', 'id'],
related_name='to_friend')
@@ -159,5 +167,6 @@ class ArticleTag(models.Model):
name = models.CharField(max_length=255)
class ArticleIdea(models.Model):
- articles = models.ManyToManyField(Article, related_name="ideas", related_query_name="idea_things")
+ articles = models.ManyToManyField(Article, related_name="ideas",
+ related_query_name="idea_things")
name = models.CharField(max_length=255)
View
20 tests/foreign_object/tests.py
@@ -9,6 +9,9 @@
from django.core.exceptions import FieldError
from django import forms
+# Note that these tests are testing internal implementation details.
+# ForeignObject is not part of public API.
+
class MultiColumnFKTests(TestCase):
def setUp(self):
# Creating countries
@@ -142,9 +145,9 @@ def test_select_related_foreignkey_forward_works(self):
Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
with self.assertNumQueries(1):
- people = [m.person for m in Membership.objects.select_related('person')]
+ people = [m.person for m in Membership.objects.select_related('person').order_by('pk')]
- normal_people = [m.person for m in Membership.objects.all()]
+ normal_people = [m.person for m in Membership.objects.all().order_by('pk')]
self.assertEqual(people, normal_people)
def test_prefetch_foreignkey_forward_works(self):
@@ -152,19 +155,22 @@ def test_prefetch_foreignkey_forward_works(self):
Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
with self.assertNumQueries(2):
- people = [m.person for m in Membership.objects.prefetch_related('person')]
+ people = [
+ m.person for m in Membership.objects.prefetch_related('person').order_by('pk')]
- normal_people = [m.person for m in Membership.objects.all()]
+ normal_people = [m.person for m in Membership.objects.order_by('pk')]
self.assertEqual(people, normal_people)
def test_prefetch_foreignkey_reverse_works(self):
Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
with self.assertNumQueries(2):
- membership_sets = [list(p.membership_set.all())
- for p in Person.objects.prefetch_related('membership_set')]
+ membership_sets = [
+ list(p.membership_set.all())
+ for p in Person.objects.prefetch_related('membership_set').order_by('pk')]
- normal_membership_sets = [list(p.membership_set.all()) for p in Person.objects.all()]
+ normal_membership_sets = [list(p.membership_set.all())
+ for p in Person.objects.order_by('pk')]
self.assertEqual(membership_sets, normal_membership_sets)
def test_m2m_through_forward_returns_valid_members(self):
View
70 tests/forms_tests/tests/test_forms.py
@@ -4,6 +4,7 @@
import datetime
from django.core.files.uploadedfile import SimpleUploadedFile
+from django.core.validators import RegexValidator
from django.forms import *
from django.http import QueryDict
from django.template import Template, Context
@@ -1792,6 +1793,75 @@ class NameForm(Form):
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data, {'name' : 'fname lname'})
+ def test_multivalue_optional_subfields(self):
+ class PhoneField(MultiValueField):
+ def __init__(self, *args, **kwargs):
+ fields = (
+ CharField(label='Country Code', validators=[
+ RegexValidator(r'^\+\d{1,2}$', message='Enter a valid country code.')]),
+ CharField(label='Phone Number'),
+ CharField(label='Extension', error_messages={'incomplete': 'Enter an extension.'}),
+ CharField(label='Label', required=False, help_text='E.g. home, work.'),
+ )
+ super(PhoneField, self).__init__(fields, *args, **kwargs)
+
+ def compress(self, data_list):
+ if data_list:
+ return '%s.%s ext. %s (label: %s)' % tuple(data_list)
+ return None
+
+ # An empty value for any field will raise a `required` error on a
+ # required `MultiValueField`.
+ f = PhoneField()
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, [])
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, ['+61'])
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, ['+61', '287654321', '123'])
+ self.assertEqual('+61.287654321 ext. 123 (label: Home)', f.clean(['+61', '287654321', '123', 'Home']))
+ self.assertRaisesMessage(ValidationError,
+ "'Enter a valid country code.'", f.clean, ['61', '287654321', '123', 'Home'])
+
+ # Empty values for fields will NOT raise a `required` error on an
+ # optional `MultiValueField`
+ f = PhoneField(required=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(None, f.clean([]))
+ self.assertEqual('+61. ext. (label: )', f.clean(['+61']))
+ self.assertEqual('+61.287654321 ext. 123 (label: )', f.clean(['+61', '287654321', '123']))
+ self.assertEqual('+61.287654321 ext. 123 (label: Home)', f.clean(['+61', '287654321', '123', 'Home']))
+ self.assertRaisesMessage(ValidationError,
+ "'Enter a valid country code.'", f.clean, ['61', '287654321', '123', 'Home'])
+
+ # For a required `MultiValueField` with `require_all_fields=False`, a
+ # `required` error will only be raised if all fields are empty. Fields
+ # can individually be required or optional. An empty value for any
+ # required field will raise an `incomplete` error.
+ f = PhoneField(require_all_fields=False)
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
+ self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, [])
+ self.assertRaisesMessage(ValidationError, "'Enter a complete value.'", f.clean, ['+61'])
+ self.assertEqual('+61.287654321 ext. 123 (label: )', f.clean(['+61', '287654321', '123']))
+ six.assertRaisesRegex(self, ValidationError,
+ "'Enter a complete value\.', u?'Enter an extension\.'", f.clean, ['', '', '', 'Home'])
+ self.assertRaisesMessage(ValidationError,
+ "'Enter a valid country code.'", f.clean, ['61', '287654321', '123', 'Home'])
+
+ # For an optional `MultiValueField` with `require_all_fields=False`, we
+ # don't get any `required` error but we still get `incomplete` errors.
+ f = PhoneField(required=False, require_all_fields=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(None, f.clean([]))
+ self.assertRaisesMessage(ValidationError, "'Enter a complete value.'", f.clean, ['+61'])
+ self.assertEqual('+61.287654321 ext. 123 (label: )', f.clean(['+61', '287654321', '123']))
+ six.assertRaisesRegex(self, ValidationError,
+ "'Enter a complete value\.', u?'Enter an extension\.'", f.clean, ['', '', '', 'Home'])
+ self.assertRaisesMessage(ValidationError,
+ "'Enter a valid country code.'", f.clean, ['61', '287654321', '123', 'Home'])
+
def test_custom_empty_values(self):
"""
Test that form fields can customize what is considered as an empty value
View
14 tests/select_for_update/tests.py
@@ -5,11 +5,13 @@
import unittest
from django.conf import settings
-from django.db import transaction, connection
+from django.db import transaction, connection, router
from django.db.utils import ConnectionHandler, DEFAULT_DB_ALIAS, DatabaseError
from django.test import (TransactionTestCase, skipIfDBFeature,
skipUnlessDBFeature)
+from multiple_database.tests import TestRouter
+
from .models import Person
# Some tests require threading, which might not be available. So create a
@@ -254,3 +256,13 @@ def test_transaction_dirty_managed(self):
"""
people = list(Person.objects.select_for_update())
self.assertTrue(transaction.is_dirty())
+
+ @skipUnlessDBFeature('has_select_for_update')
+ def test_select_for_update_on_multidb(self):
+ old_routers = router.routers
+ try:
+ router.routers = [TestRouter()]
+ query = Person.objects.select_for_update()
+ self.assertEqual(router.db_for_write(Person), query.db)
+ finally:
+ router.routers = old_routers
Something went wrong with that request. Please try again.