mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-20 15:00:57 +00:00
Added @route decorator
This commit is contained in:
parent
f7b2cfbb4e
commit
7200def791
5 changed files with 175 additions and 51 deletions
|
|
@ -1,49 +1,46 @@
|
|||
.. _routable_page_mixin:
|
||||
|
||||
====================================
|
||||
Embedding URL configuration in Pages
|
||||
====================================
|
||||
==============
|
||||
Routable pages
|
||||
==============
|
||||
|
||||
The ``RoutablePageMixin`` mixin provides a convenient way for a page to respond on multiple sub-URLs with different views. For example, a blog section on a site might provide several different types of index page at URLs like ``/blog/2013/06/``, ``/blog/authors/bob/``, ``/blog/tagged/python/``, all served by the same ``BlogIndex`` page.
|
||||
The ``RoutablePageMixin`` mixin provides a convenient way for a page to respond on multiple sub-URLs with different views. For example, a blog section on a site might provide several different types of index page at URLs like ``/blog/2013/06/``, ``/blog/authors/bob/``, ``/blog/tagged/python/``, all served by the same page instance.
|
||||
|
||||
A ``Page`` using ``RoutablePageMixin`` exists within the page tree like any other page, but URL paths underneath it are checked against a list of patterns, using Django's urlconf scheme. If none of the patterns match, control is passed to subpages as usual (or failing that, a 404 error is thrown).
|
||||
A ``Page`` using ``RoutablePageMixin`` exists within the page tree like any other page, but URL paths underneath it are checked against a list of patterns. If none of the patterns match, control is passed to subpages as usual (or failing that, a 404 error is thrown).
|
||||
|
||||
|
||||
The basics
|
||||
==========
|
||||
|
||||
To use ``RoutablePageMixin``, you need to make your class inherit from both :class:`wagtail.contrib.wagtailroutablepage.models.RoutablePageMixin` and :class:`wagtail.wagtailcore.models.Page`, and configure the ``subpage_urls`` attribute with your URL configuration.
|
||||
To use ``RoutablePageMixin``, you need to make your class inherits from both :class:`wagtail.contrib.wagtailroutablepage.models.RoutablePageMixin` and :class:`wagtail.wagtailcore.models.Page`, then define some view methods and decorate them with ``wagtail.contrib.wagtailroutablepage.models.route``.
|
||||
|
||||
Here's an example of an ``EventPage`` with three views:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin, route
|
||||
|
||||
|
||||
class EventPage(RoutablePageMixin, Page):
|
||||
subpage_urls = (
|
||||
url(r'^$', 'current_events', name='current_events'),
|
||||
url(r'^past/$', 'past_events', name='past_events'),
|
||||
url(r'^year/(\d+)/$', 'events_for_year', name='events_for_year'),
|
||||
)
|
||||
...
|
||||
|
||||
@route(r'^$')
|
||||
def current_events(self, request):
|
||||
"""
|
||||
View function for the current events page
|
||||
"""
|
||||
...
|
||||
|
||||
@route(r'^past/$')
|
||||
def past_events(self, request):
|
||||
"""
|
||||
View function for the past events page
|
||||
"""
|
||||
...
|
||||
|
||||
def events_for_year(self, request):
|
||||
@route(r'^year/(\d+)/$')
|
||||
def events_for_year(self, request, year):
|
||||
"""
|
||||
View function for the events for year page
|
||||
"""
|
||||
|
|
@ -56,29 +53,7 @@ The ``RoutablePageMixin`` class
|
|||
.. automodule:: wagtail.contrib.wagtailroutablepage.models
|
||||
.. autoclass:: RoutablePageMixin
|
||||
|
||||
.. autoattribute:: subpage_urls
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
|
||||
class MyPage(RoutablePageMixin, Page):
|
||||
subpage_urls = (
|
||||
url(r'^$', 'main', name='main'),
|
||||
url(r'^archive/$', 'archive', name='archive'),
|
||||
url(r'^archive/(?P<year>[0-9]{4})/$', 'archive', name='archive'),
|
||||
)
|
||||
|
||||
def main(self, request):
|
||||
...
|
||||
|
||||
def archive(self, request, year=None):
|
||||
...
|
||||
.. automethod:: get_subpage_urls
|
||||
|
||||
.. automethod:: resolve_subpage
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,35 @@ from six import string_types
|
|||
|
||||
from django.http import Http404
|
||||
from django.core.urlresolvers import RegexURLResolver
|
||||
from django.conf.urls import url
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailcore.url_routing import RouteResult
|
||||
|
||||
|
||||
_creation_counter = 0
|
||||
|
||||
|
||||
def route(pattern, name=None):
|
||||
def decorator(view_func):
|
||||
global _creation_counter
|
||||
_creation_counter += 1
|
||||
|
||||
# Make sure page has _routablepage_routes attribute
|
||||
if not hasattr(view_func, '_routablepage_routes'):
|
||||
view_func._routablepage_routes = []
|
||||
|
||||
# Add new route to view
|
||||
view_func._routablepage_routes.append((
|
||||
url(pattern, view_func, name=(name or view_func.__name__)),
|
||||
_creation_counter,
|
||||
))
|
||||
|
||||
return view_func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class RoutablePageMixin(object):
|
||||
"""
|
||||
This class can be mixed in to a Page subclass to allow urlconfs to be
|
||||
|
|
@ -19,10 +43,21 @@ class RoutablePageMixin(object):
|
|||
|
||||
@classmethod
|
||||
def get_subpage_urls(cls):
|
||||
# Old style
|
||||
if cls.subpage_urls:
|
||||
return cls.subpage_urls
|
||||
|
||||
return ()
|
||||
# New style
|
||||
routes = []
|
||||
for attr in dir(cls):
|
||||
val = getattr(cls, attr)
|
||||
if hasattr(val, '_routablepage_routes'):
|
||||
routes.extend(val._routablepage_routes)
|
||||
|
||||
return tuple([
|
||||
route[0]
|
||||
for route in sorted(routes, key=lambda route: route[1])
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def get_resolver(cls):
|
||||
|
|
@ -48,9 +83,14 @@ class RoutablePageMixin(object):
|
|||
"""
|
||||
view, args, kwargs = self.get_resolver().resolve(path)
|
||||
|
||||
# If view is a string, find it as an attribute of self
|
||||
if isinstance(view, string_types):
|
||||
view = getattr(self, view)
|
||||
if self.subpage_urls: # Old style
|
||||
# If view is a string, find it as an attribute of self
|
||||
if isinstance(view, string_types):
|
||||
view = getattr(self, view)
|
||||
|
||||
else: # New style
|
||||
# Bind the method
|
||||
view = view.__get__(self, type(self))
|
||||
|
||||
return view, args, kwargs
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
from django.test import TestCase, RequestFactory
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site
|
||||
from wagtail.tests.routablepage.models import RoutablePageTest, routable_page_external_view
|
||||
from wagtail.tests.routablepage.models import OldStyleRoutablePageTest, NewStyleRoutablePageTest, routable_page_external_view
|
||||
from wagtail.contrib.wagtailroutablepage.templatetags.wagtailroutablepage_tags import routablepageurl
|
||||
|
||||
|
||||
class TestRoutablePage(TestCase):
|
||||
class TestNewStyleRoutablePage(TestCase):
|
||||
model = NewStyleRoutablePageTest
|
||||
|
||||
def setUp(self):
|
||||
self.home_page = Page.objects.get(id=2)
|
||||
self.routable_page = self.home_page.add_child(instance=RoutablePageTest(
|
||||
self.routable_page = self.home_page.add_child(instance=self.model(
|
||||
title="Routable Page",
|
||||
slug='routable-page',
|
||||
live=True,
|
||||
|
|
@ -38,10 +41,17 @@ class TestRoutablePage(TestCase):
|
|||
def test_resolve_external_view(self):
|
||||
view, args, kwargs = self.routable_page.resolve_subpage('/external/joe-bloggs/')
|
||||
|
||||
self.assertEqual(view, routable_page_external_view)
|
||||
self.assertEqual(view, self.routable_page.external_view)
|
||||
self.assertEqual(args, ('joe-bloggs', ))
|
||||
self.assertEqual(kwargs, {})
|
||||
|
||||
def test_resolve_external_view_other_route(self):
|
||||
view, args, kwargs = self.routable_page.resolve_subpage('/external-no-arg/')
|
||||
|
||||
self.assertEqual(view, self.routable_page.external_view)
|
||||
self.assertEqual(args, ())
|
||||
self.assertEqual(kwargs, {})
|
||||
|
||||
def test_reverse_main_view(self):
|
||||
url = self.routable_page.reverse_subpage('main')
|
||||
|
||||
|
|
@ -57,11 +67,25 @@ class TestRoutablePage(TestCase):
|
|||
|
||||
self.assertEqual(url, 'archive/author/joe-bloggs/')
|
||||
|
||||
def test_reverse_overridden_name(self):
|
||||
url = self.routable_page.reverse_subpage('name_overridden')
|
||||
|
||||
self.assertEqual(url, 'override-name-test/')
|
||||
|
||||
def test_reverse_overridden_name_default_doesnt_work(self):
|
||||
with self.assertRaises(NoReverseMatch):
|
||||
self.routable_page.reverse_subpage('override_name_test')
|
||||
|
||||
def test_reverse_external_view(self):
|
||||
url = self.routable_page.reverse_subpage('external_view', args=('joe-bloggs', ))
|
||||
|
||||
self.assertEqual(url, 'external/joe-bloggs/')
|
||||
|
||||
def test_reverse_external_view_other_route(self):
|
||||
url = self.routable_page.reverse_subpage('external_view')
|
||||
|
||||
self.assertEqual(url, 'external-no-arg/')
|
||||
|
||||
def test_get_main_view(self):
|
||||
response = self.client.get(self.routable_page.url)
|
||||
|
||||
|
|
@ -82,10 +106,39 @@ class TestRoutablePage(TestCase):
|
|||
|
||||
self.assertContains(response, "EXTERNAL VIEW: joe-bloggs")
|
||||
|
||||
def test_get_external_view_other_route(self):
|
||||
response = self.client.get(self.routable_page.url + 'external-no-arg/')
|
||||
|
||||
class TestRoutablePageTemplateTag(TestRoutablePage):
|
||||
self.assertContains(response, "EXTERNAL VIEW: ARG NOT SET")
|
||||
|
||||
|
||||
class TestOldStyleRoutablePage(TestNewStyleRoutablePage):
|
||||
model = OldStyleRoutablePageTest
|
||||
|
||||
def test_resolve_external_view(self):
|
||||
view, args, kwargs = self.routable_page.resolve_subpage('/external/joe-bloggs/')
|
||||
|
||||
self.assertEqual(view, routable_page_external_view)
|
||||
self.assertEqual(args, ('joe-bloggs', ))
|
||||
self.assertEqual(kwargs, {})
|
||||
|
||||
test_resolve_external_view_other_route = None
|
||||
test_reverse_external_view_other_route = None
|
||||
test_get_external_view_other_route = None
|
||||
|
||||
test_reverse_overridden_name = None
|
||||
test_reverse_overridden_name_default_doesnt_work = None
|
||||
|
||||
|
||||
class TestRoutablePageTemplateTag(TestCase):
|
||||
def setUp(self):
|
||||
super(TestRoutablePageTemplateTag, self).setUp()
|
||||
self.home_page = Page.objects.get(id=2)
|
||||
self.routable_page = self.home_page.add_child(instance=NewStyleRoutablePageTest(
|
||||
title="Routable Page",
|
||||
slug='routable-page',
|
||||
live=True,
|
||||
))
|
||||
|
||||
self.rf = RequestFactory()
|
||||
self.request = self.rf.get(self.routable_page.url)
|
||||
self.request.site = Site.find_for_request(self.request)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import wagtail.contrib.wagtailroutablepage.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailcore', '0013_update_golive_expire_help_text'),
|
||||
('routablepagetests', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OldStyleRoutablePageTest',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(primary_key=True, auto_created=True, serialize=False, to='wagtailcore.Page', parent_link=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(wagtail.contrib.wagtailroutablepage.models.RoutablePageMixin, 'wagtailcore.page'),
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name='RoutablePageTest',
|
||||
new_name='NewStyleRoutablePageTest',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
from django.http import HttpResponse
|
||||
from django.conf.urls import url
|
||||
|
||||
from wagtail.contrib.wagtailroutablepage.models import RoutablePage
|
||||
from wagtail.contrib.wagtailroutablepage.models import RoutablePage, route
|
||||
|
||||
|
||||
def routable_page_external_view(request, arg):
|
||||
def routable_page_external_view(request, arg="ARG NOT SET"):
|
||||
return HttpResponse("EXTERNAL VIEW: " + arg)
|
||||
|
||||
class RoutablePageTest(RoutablePage):
|
||||
|
||||
class OldStyleRoutablePageTest(RoutablePage):
|
||||
subpage_urls = (
|
||||
url(r'^$', 'main', name='main'),
|
||||
url(r'^archive/year/(\d+)/$', 'archive_by_year', name='archive_by_year'),
|
||||
|
|
@ -23,3 +24,28 @@ class RoutablePageTest(RoutablePage):
|
|||
|
||||
def main(self, request):
|
||||
return HttpResponse("MAIN VIEW")
|
||||
|
||||
|
||||
class NewStyleRoutablePageTest(RoutablePage):
|
||||
@route(r'^$')
|
||||
def main(self, request):
|
||||
return HttpResponse("MAIN VIEW")
|
||||
|
||||
@route(r'^archive/year/(\d+)/$')
|
||||
def archive_by_year(self, request, year):
|
||||
return HttpResponse("ARCHIVE BY YEAR: " + str(year))
|
||||
|
||||
@route(r'^archive/author/(?P<author_slug>.+)/$')
|
||||
def archive_by_author(self, request, author_slug):
|
||||
return HttpResponse("ARCHIVE BY AUTHOR: " + author_slug)
|
||||
|
||||
@route(r'^external/(.+)/$')
|
||||
@route(r'^external-no-arg/$')
|
||||
def external_view(self, *args, **kwargs):
|
||||
return routable_page_external_view(*args, **kwargs)
|
||||
|
||||
# By default, the method name would be used as the url name but when the
|
||||
# "name" kwarg is specified, this should override the default.
|
||||
@route(r'^override-name-test/$', name='name_overridden')
|
||||
def override_name_test(self, request):
|
||||
pass
|
||||
|
|
|
|||
Loading…
Reference in a new issue