Restore PageManager behaviour by setting it on an abstract superclass of Page

Django's standard behaviour is to preserve managers that are set on abstract
superclasses, so this allows us to eliminate the metaclass hackery.

Fixes #2933
pull/2935/merge
Matt Westcott 2016-08-22 23:08:13 +01:00
rodzic bfff095f8a
commit 7bc819640d
4 zmienionych plików z 38 dodań i 11 usunięć

Wyświetl plik

@ -14,6 +14,7 @@ Changelog
* Fix: Wagtail's middleware classes are now compatible with Django 1.10's new-style middleware (Karl Hobley)
* Fix: The `Pages.can_create_at` method is now checked in the create page view (Mikalai Radchuk)
* Fix: Fixed regression on Django 1.10.1 causing Page subclasses to fail to use PageManager (Matt Westcott)
1.6 (15.08.2016)

Wyświetl plik

@ -15,3 +15,24 @@ Bug fixes
* Wagtail's middleware classes are now compatible with Django 1.10's `new-style middleware <https://docs.djangoproject.com/en/1.10/releases/1.10/#new-style-middleware>`_ (Karl Hobley)
* The :meth:`~wagtail.wagtailcore.models.Page.can_create_at` method is now checked in the create page view (Mikalai Radchuk)
* Fixed regression on Django 1.10.1 causing Page subclasses to fail to use PageManager (Matt Westcott)
Upgrade considerations
======================
Multi-level inheritance and custom managers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The inheritance rules for :ref:`custom_page_managers` have been updated to match Django's standard behaviour. In the vast majority of scenarios there will be no change. However, in the specific case where a page model with a custom ``objects`` manager is subclassed further, the subclass will be assigned a plain ``Manager`` instead of a ``PageManager``, and will now need to explicitly override this with a ``PageManager`` to function correctly:
.. code-block:: python
class EventPage(Page):
objects = EventManager()
class SpecialEventPage(EventPage):
# Previously SpecialEventPage.objects would be set to a PageManager automatically;
# this now needs to be set explicitly
objects = PageManager()

Wyświetl plik

@ -440,6 +440,8 @@ This is because ``Page`` enforces ordering QuerySets by path. Instead, you must
news_items = NewsItemPage.objects.live().order_by('-publication_date')
.. _custom_page_managers:
Custom Page managers
--------------------

Wyświetl plik

@ -261,14 +261,6 @@ class PageBase(models.base.ModelBase):
# don't proceed with all this page type registration stuff
return
# Override the default `objects` attribute with a `PageManager`.
# Managers are not inherited by MTI child models, so `Page` subclasses
# will get a plain `Manager` instead of a `PageManager`.
# If the developer has set their own custom `Manager` subclass, do not
# clobber it.
if not cls._meta.abstract and type(cls.objects) is models.Manager:
PageManager().contribute_to_class(cls, 'objects')
if 'template' not in dct:
# Define a default template path derived from the app name and model name
cls.template = "%s/%s.html" % (cls._meta.app_label, camelcase_to_underscore(name))
@ -289,8 +281,21 @@ class PageBase(models.base.ModelBase):
PAGE_MODEL_CLASSES.append(cls)
class AbstractPage(MP_Node):
"""
Abstract superclass for Page. According to Django's inheritance rules, managers set on
abstract models are inherited by subclasses, but managers set on concrete models that are extended
via multi-table inheritance are not. We therefore need to attach PageManager to an abstract
superclass to ensure that it is retained by subclasses of Page.
"""
objects = PageManager()
class Meta:
abstract = True
@python_2_unicode_compatible
class Page(six.with_metaclass(PageBase, MP_Node, index.Indexed, ClusterableModel)):
class Page(six.with_metaclass(PageBase, AbstractPage, index.Indexed, ClusterableModel)):
title = models.CharField(
verbose_name=_('title'),
max_length=255,
@ -391,8 +396,6 @@ class Page(six.with_metaclass(PageBase, MP_Node, index.Indexed, ClusterableModel
# Do not allow plain Page instances to be created through the Wagtail admin
is_creatable = False
objects = PageManager()
def __init__(self, *args, **kwargs):
super(Page, self).__init__(*args, **kwargs)
if not self.id and not self.content_type_id: