Merge pull request #185 from kaedroho/pagequery

Page QuerySet
pull/191/head
Karl Hobley 2014-04-08 12:55:20 +01:00
commit 128516922f
4 zmienionych plików z 414 dodań i 0 usunięć

Wyświetl plik

@ -5,6 +5,7 @@ Changelog
~~~~~~~~~~~~~~~~
* Added toolbar to allow logged-in users to add and edit pages from the site front-end
* Support for alternative image processing backends such as Wand, via the WAGTAILIMAGES_BACKENDS setting
* Added custom Query set for Pages with some handy methods for querying pages
* Editor's guide documentation
* Editor interface now outputs form media CSS / JS, to support custom widgets with assets
* Migrations and user management now correctly handle custom AUTH_USER_MODEL settings

Wyświetl plik

@ -14,6 +14,7 @@ from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailcore.util import camelcase_to_underscore
from wagtail.wagtailcore.query import PageQuerySet
from wagtail.wagtailsearch import Indexed, get_search_backend
@ -128,6 +129,59 @@ def get_navigable_page_content_type_ids():
return _NAVIGABLE_PAGE_CONTENT_TYPE_IDS
class PageManager(models.Manager):
def get_query_set(self):
return PageQuerySet(self.model).order_by('path')
def live(self):
return self.get_query_set().live()
def not_live(self):
return self.get_query_set().not_live()
def page(self, other):
return self.get_query_set().page(other)
def not_page(self, other):
return self.get_query_set().not_page(other)
def descendant_of(self, other, inclusive=False):
return self.get_query_set().descendant_of(other, inclusive)
def not_descendant_of(self, other, inclusive=False):
return self.get_query_set().not_descendant_of(other, inclusive)
def child_of(self, other):
return self.get_query_set().child_of(other)
def not_child_of(self, other):
return self.get_query_set().not_child_of(other)
def ancestor_of(self, other, inclusive=False):
return self.get_query_set().ancestor_of(other, inclusive)
def not_ancestor_of(self, other, inclusive=False):
return self.get_query_set().not_ancestor_of(other, inclusive)
def parent_of(self, other):
return self.get_query_set().parent_of(other)
def not_parent_of(self, other):
return self.get_query_set().not_parent_of(other)
def sibling_of(self, other, inclusive=False):
return self.get_query_set().sibling_of(other, inclusive)
def not_sibling_of(self, other, inclusive=False):
return self.get_query_set().not_sibling_of(other, inclusive)
def type(self, model):
return self.get_query_set().type(model)
def not_type(self, model):
return self.get_query_set().not_type(model)
class PageBase(models.base.ModelBase):
"""Metaclass for Page"""
def __init__(cls, name, bases, dct):
@ -138,6 +192,9 @@ class PageBase(models.base.ModelBase):
# don't proceed with all this page type registration stuff
return
# Add page 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))

Wyświetl plik

@ -0,0 +1,109 @@
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
# hack to import our patched copy of treebeard at wagtail/vendor/django-treebeard -
# based on http://stackoverflow.com/questions/17211078/how-to-temporarily-modify-sys-path-in-python
import sys
import os
treebeard_path = os.path.join(os.path.dirname(__file__), '..', 'vendor', 'django-treebeard')
sys.path.insert(0, treebeard_path)
from treebeard.mp_tree import MP_NodeQuerySet
sys.path.pop(0)
class PageQuerySet(MP_NodeQuerySet):
"""
Defines some extra query set methods that are useful for pages.
"""
def live_q(self):
return Q(live=True)
def live(self):
return self.filter(self.live_q())
def not_live(self):
return self.exclude(self.live_q())
def page_q(self, other):
return Q(id=other.id)
def page(self, other):
return self.filter(self.page_q(other))
def not_page(self, other):
return self.exclude(self.page_q(other))
def descendant_of_q(self, other, inclusive=False):
q = Q(path__startswith=other.path) & Q(depth__gte=other.depth)
if not inclusive:
q &= ~self.page_q(other)
return q
def descendant_of(self, other, inclusive=False):
return self.filter(self.descendant_of_q(other, inclusive))
def not_descendant_of(self, other, inclusive=False):
return self.exclude(self.descendant_of_q(other, inclusive))
def child_of_q(self, other):
return self.descendant_of_q(other) & Q(depth=other.depth + 1)
def child_of(self, other):
return self.filter(self.child_of_q(other))
def not_child_of(self, other):
return self.exclude(self.child_of_q(other))
def ancestor_of_q(self, other, inclusive=False):
paths = [
other.path[0:pos]
for pos in range(0, len(other.path) + 1, other.steplen)[1:]
]
q = Q(path__in=paths)
if not inclusive:
q &= ~self.page_q(other)
return q
def ancestor_of(self, other, inclusive=False):
return self.filter(self.ancestor_of_q(other, inclusive))
def not_ancestor_of(self, other, inclusive=False):
return self.exclude(self.ancestor_of_q(other, inclusive))
def parent_of_q(self, other):
return Q(path=self.model._get_parent_path_from_path(other.path))
def parent_of(self, other):
return self.filter(self.parent_of_q(other))
def not_parent_of(self, other):
return self.exclude(self.parent_of_q(other))
def sibling_of_q(self, other, inclusive=False):
q = Q(path__startswith=self.model._get_parent_path_from_path(other.path)) & Q(depth=other.depth)
if not inclusive:
q &= ~self.page_q(other)
return q
def sibling_of(self, other, inclusive=False):
return self.filter(self.sibling_of_q(other, inclusive))
def not_sibling_of(self, other, inclusive=False):
return self.exclude(self.sibling_of_q(other, inclusive))
def type_q(self, model):
content_type = ContentType.objects.get_for_model(model)
return Q(content_type=content_type)
def type(self, model):
return self.filter(self.type_q(model))
def not_type(self, model):
return self.exclude(self.type_q(model))

Wyświetl plik

@ -335,3 +335,250 @@ class TestPagePermission(TestCase):
self.assertTrue(homepage_perms.can_move_to(root))
self.assertFalse(homepage_perms.can_move_to(unpublished_event_page))
class TestPageQuerySet(TestCase):
fixtures = ['test.json']
def test_live(self):
pages = Page.objects.live()
# All pages must be live
for page in pages:
self.assertTrue(page.live)
# Check that the homepage is in the results
homepage = Page.objects.get(url_path='/home/')
self.assertTrue(pages.filter(id=homepage.id).exists())
def test_not_live(self):
pages = Page.objects.not_live()
# All pages must not be live
for page in pages:
self.assertFalse(page.live)
# Check that "someone elses event" is in the results
event = Page.objects.get(url_path='/home/events/someone-elses-event/')
self.assertTrue(pages.filter(id=event.id).exists())
def test_page(self):
homepage = Page.objects.get(url_path='/home/')
pages = Page.objects.page(homepage)
# Should only select the homepage
self.assertEqual(pages.count(), 1)
self.assertEqual(pages.first(), homepage)
def test_not_page(self):
homepage = Page.objects.get(url_path='/home/')
pages = Page.objects.not_page(homepage)
# Should select everything except for the homepage
self.assertEqual(pages.count(), Page.objects.all().count() - 1)
for page in pages:
self.assertNotEqual(page, homepage)
def test_descendant_of(self):
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.descendant_of(events_index)
# Check that all pages descend from events index
for page in pages:
self.assertTrue(page.get_ancestors().filter(id=events_index.id).exists())
def test_descendant_of_inclusive(self):
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.descendant_of(events_index, inclusive=True)
# Check that all pages descend from events index, includes event index
for page in pages:
self.assertTrue(page == events_index or page.get_ancestors().filter(id=events_index.id).exists())
# Check that event index was included
self.assertTrue(pages.filter(id=events_index.id).exists())
def test_not_descendant_of(self):
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.not_descendant_of(events_index)
# Check that no pages descend from events_index
for page in pages:
self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
# As this is not inclusive, events index should be in the results
self.assertTrue(pages.filter(id=events_index.id).exists())
def test_not_descendant_of_inclusive(self):
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.not_descendant_of(events_index, inclusive=True)
# Check that all pages descend from homepage but not events index
for page in pages:
self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
# As this is inclusive, events index should not be in the results
self.assertFalse(pages.filter(id=events_index.id).exists())
def test_child_of(self):
homepage = Page.objects.get(url_path='/home/')
pages = Page.objects.child_of(homepage)
# Check that all pages are children of homepage
for page in pages:
self.assertEqual(page.get_parent(), homepage)
def test_not_child_of(self):
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.not_child_of(events_index)
# Check that all pages are not children of events_index
for page in pages:
self.assertNotEqual(page.get_parent(), events_index)
def test_ancestor_of(self):
root_page = Page.objects.get(id=1)
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.ancestor_of(events_index)
self.assertEqual(pages.count(), 2)
self.assertEqual(pages[0], root_page)
self.assertEqual(pages[1], homepage)
def test_ancestor_of_inclusive(self):
root_page = Page.objects.get(id=1)
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.ancestor_of(events_index, inclusive=True)
self.assertEqual(pages.count(), 3)
self.assertEqual(pages[0], root_page)
self.assertEqual(pages[1], homepage)
self.assertEqual(pages[2], events_index)
def test_not_ancestor_of(self):
root_page = Page.objects.get(id=1)
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.not_ancestor_of(events_index)
# Test that none of the ancestors are in pages
for page in pages:
self.assertNotEqual(page, root_page)
self.assertNotEqual(page, homepage)
# Test that events index is in pages
self.assertTrue(pages.filter(id=events_index.id).exists())
def test_not_ancestor_of_inclusive(self):
root_page = Page.objects.get(id=1)
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.not_ancestor_of(events_index, inclusive=True)
# Test that none of the ancestors or the events_index are in pages
for page in pages:
self.assertNotEqual(page, root_page)
self.assertNotEqual(page, homepage)
self.assertNotEqual(page, events_index)
def test_parent_of(self):
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.parent_of(events_index)
# Pages must only contain homepage
self.assertEqual(pages.count(), 1)
self.assertEqual(pages[0], homepage)
def test_not_parent_of(self):
homepage = Page.objects.get(url_path='/home/')
events_index = Page.objects.get(url_path='/home/events/')
pages = Page.objects.not_parent_of(events_index)
# Pages must not contain homepage
for page in pages:
self.assertNotEqual(page, homepage)
# Test that events index is in pages
self.assertTrue(pages.filter(id=events_index.id).exists())
def test_sibling_of(self):
events_index = Page.objects.get(url_path='/home/events/')
event = Page.objects.get(url_path='/home/events/christmas/')
pages = Page.objects.sibling_of(event)
# Check that all pages are children of events_index
for page in pages:
self.assertEqual(page.get_parent(), events_index)
# Check that the event is not included
self.assertFalse(pages.filter(id=event.id).exists())
def test_sibling_of_inclusive(self):
events_index = Page.objects.get(url_path='/home/events/')
event = Page.objects.get(url_path='/home/events/christmas/')
pages = Page.objects.sibling_of(event, inclusive=True)
# Check that all pages are children of events_index
for page in pages:
self.assertEqual(page.get_parent(), events_index)
# Check that the event is included
self.assertTrue(pages.filter(id=event.id).exists())
def test_not_sibling_of(self):
events_index = Page.objects.get(url_path='/home/events/')
event = Page.objects.get(url_path='/home/events/christmas/')
pages = Page.objects.not_sibling_of(event)
# Check that all pages are not children of events_index
for page in pages:
if page != event:
self.assertNotEqual(page.get_parent(), events_index)
# Check that the event is included
self.assertTrue(pages.filter(id=event.id).exists())
# Test that events index is in pages
self.assertTrue(pages.filter(id=events_index.id).exists())
def test_not_sibling_of_inclusive(self):
events_index = Page.objects.get(url_path='/home/events/')
event = Page.objects.get(url_path='/home/events/christmas/')
pages = Page.objects.not_sibling_of(event, inclusive=True)
# Check that all pages are not children of events_index
for page in pages:
self.assertNotEqual(page.get_parent(), events_index)
# Check that the event is not included
self.assertFalse(pages.filter(id=event.id).exists())
# Test that events index is in pages
self.assertTrue(pages.filter(id=events_index.id).exists())
def test_type(self):
pages = Page.objects.type(EventPage)
# Check that all objects are EventPages
for page in pages:
self.assertIsInstance(page.specific, EventPage)
# Check that "someone elses event" is in the results
event = Page.objects.get(url_path='/home/events/someone-elses-event/')
self.assertTrue(pages.filter(id=event.id).exists())
def test_not_type(self):
pages = Page.objects.not_type(EventPage)
# Check that no objects are EventPages
for page in pages:
self.assertNotIsInstance(page.specific, EventPage)
# Check that the homepage is in the results
homepage = Page.objects.get(url_path='/home/')
self.assertTrue(pages.filter(id=homepage.id).exists())