kopia lustrzana https://github.com/wagtail/wagtail
commit
128516922f
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
|
@ -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())
|
||||
|
|
Ładowanie…
Reference in New Issue