mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-21 07:10:59 +00:00
Merge branch 'master' into unpublish_signal
Conflicts: wagtail/wagtailadmin/views/pages.py
This commit is contained in:
commit
c8f1f82914
50 changed files with 9213 additions and 1431 deletions
|
|
@ -3,7 +3,10 @@ Changelog
|
|||
|
||||
0.5 (xx.xx.20xx)
|
||||
~~~~~~~~~~~~~~~~
|
||||
* Added RoutablePage model to allow embedding Django-style URL routing within a page
|
||||
* Explorer nav now rendered separately and fetched with AJAX when needed
|
||||
* Added decorator syntax for hooks
|
||||
* Replaced lxml dependency with html5lib, to simplify installation
|
||||
|
||||
0.4.1 (14.07.2014)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -197,13 +197,13 @@ In addition to the model fields provided, ``Page`` has many properties and metho
|
|||
.. automodule:: wagtail.wagtailcore.models
|
||||
.. autoclass:: Page
|
||||
|
||||
.. method:: specific
|
||||
.. autoattribute:: specific
|
||||
|
||||
Return this page in its most specific subclassed form.
|
||||
.. autoattribute:: specific_class
|
||||
|
||||
.. automethod:: url
|
||||
.. autoattribute:: url
|
||||
|
||||
.. automethod:: full_url
|
||||
.. autoattribute:: full_url
|
||||
|
||||
.. automethod:: relative_url
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ In addition to the model fields provided, ``Page`` has many properties and metho
|
|||
|
||||
.. automethod:: get_template
|
||||
|
||||
.. automethod:: preview_modes
|
||||
.. autoattribute:: preview_modes
|
||||
|
||||
.. automethod:: serve_preview
|
||||
|
||||
|
|
@ -381,6 +381,8 @@ Examples:
|
|||
|
||||
.. automethod:: public
|
||||
|
||||
See: :ref:`private_pages`
|
||||
|
||||
.. note::
|
||||
|
||||
This doesn't filter out unpublished pages. If you want to only have published public pages, use ``.live().public()``
|
||||
|
|
@ -394,6 +396,8 @@ Examples:
|
|||
|
||||
.. automethod:: search
|
||||
|
||||
See: :ref:`wagtailsearch_for_python_developers`
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ sys.path.insert(0, os.path.abspath('..'))
|
|||
# be configured
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'wagtail.tests.settings'
|
||||
|
||||
# Use SQLite3 database engine so it doesn't attempt to use psycopg2 on RTD
|
||||
os.environ['DATABASE_ENGINE'] = 'django.db.backends.sqlite3'
|
||||
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
Frontend cache purging
|
||||
======================
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Many websites use a frontend cache such as Varnish, Squid or Cloudflare to gain extra performance. The downside of using a frontend cache though is that they don't respond well to updating content and will often keep an old version of a page cached after it has been updated.
|
||||
|
||||
This document describes how to configure Wagtail to purge old versions of pages from a frontend cache whenever a page gets updated.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ It supports Django 1.6.2+ on Python 2.6, 2.7, 3.2, 3.3 and 3.4. Django 1.7 suppo
|
|||
search/index
|
||||
form_builder
|
||||
model_recipes
|
||||
routable_page
|
||||
advanced_topics
|
||||
deploying
|
||||
performance
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ Performance
|
|||
|
||||
Wagtail is designed for speed, both in the editor interface and on the front-end, but if you want even better performance or you need to handle very high volumes of traffic, here are some tips on eking out the most from your installation.
|
||||
|
||||
|
||||
Editor interface
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
We have tried to minimise external dependencies for a working installation of Wagtail, in order to make it as simple as possible to get going. However, a number of default settings can be configured for better performance:
|
||||
|
||||
|
||||
Cache
|
||||
-----
|
||||
|
||||
|
|
@ -25,16 +27,19 @@ We recommend `Redis <http://redis.io/>`_ as a fast, persistent cache. Install Re
|
|||
|
||||
Without a persistent cache, Wagtail will recreate all compressable assets at each server start, e.g. when any files change under ```./manage.py runserver```.
|
||||
|
||||
|
||||
Search
|
||||
------
|
||||
|
||||
Wagtail has strong support for `Elasticsearch <http://www.elasticsearch.org/>`_ - both in the editor interface and for users of your site - but can fall back to a database search if Elasticsearch isn't present. Elasticsearch is faster and more powerful than the Django ORM for text search, so we recommend installing it or using a hosted service like `Searchly <http://www.searchly.com/>`_.
|
||||
|
||||
|
||||
Database
|
||||
--------
|
||||
|
||||
Wagtail is tested on SQLite, and should work on other Django-supported database backends, but we recommend PostgreSQL for production use.
|
||||
|
||||
|
||||
Public users
|
||||
~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -42,3 +47,7 @@ Caching proxy
|
|||
-------------
|
||||
|
||||
To support high volumes of traffic with excellent response times, we recommend a caching proxy. Both `Varnish <http://www.varnish-cache.org/>`_ and `Squid <http://www.squid-cache.org/>`_ have been tested in production. Hosted proxies like `Cloudflare <https://www.cloudflare.com/>`_ should also work well.
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Wagtail supports automatic cache invalidation for Varnish/Squid. See :ref:`frontend_cache_purging` for more information.
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
Private pages
|
||||
=============
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Users with publish permission on a page can set it to be private by clicking the 'Privacy' control in the top right corner of the page explorer or editing interface, and setting a password. Users visiting this page, or any of its subpages, will be prompted to enter a password before they can view the page.
|
||||
|
||||
Private pages work on Wagtail out of the box - the site implementer does not need to do anything to set them up. However, the default "password required" form is only a bare-bones HTML page, and site implementers may wish to replace this with a page customised to their site design.
|
||||
|
||||
|
||||
Setting up a global "password required" page
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ A basic template suitable for use as ``PASSWORD_REQUIRED_TEMPLATE`` might look l
|
|||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Setting a "password required" page for a specific page type
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,15 @@ Wagtail 0.5 release notes - IN DEVELOPMENT
|
|||
:depth: 1
|
||||
|
||||
|
||||
Whats new
|
||||
=========
|
||||
What's new
|
||||
==========
|
||||
|
||||
RoutablePage
|
||||
~~~~~~~~~~~~
|
||||
|
||||
A ``RoutablePage`` model has been added to allow embedding Django-style URL routing within a page.
|
||||
|
||||
:ref:`routable_page`
|
||||
|
||||
|
||||
Minor features
|
||||
|
|
@ -27,6 +34,16 @@ Core
|
|||
MenuItem('Kittens!', '/kittens/', classnames='icon icon-folder-inverse', order=1000)
|
||||
)
|
||||
|
||||
* The lxml library (used for whitelisting and rewriting of rich text fields) has been replaced with the pure-python html5lib library, to simplify installation.
|
||||
|
||||
|
||||
Admin
|
||||
-----
|
||||
|
||||
* Explorer nav now rendered separately and fetched with AJAX when needed.
|
||||
|
||||
This improves the general performance of the admin interface for large sites.
|
||||
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
|
|
|||
88
docs/routable_page.rst
Normal file
88
docs/routable_page.rst
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
.. _routable_page:
|
||||
|
||||
====================================
|
||||
Embedding URL configuration in Pages
|
||||
====================================
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
The ``RoutablePage`` class 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.
|
||||
|
||||
A ``RoutablePage`` 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).
|
||||
|
||||
|
||||
The basics
|
||||
==========
|
||||
|
||||
To use ``RoutablePage``, you need to make your class inherit from :class:`wagtail.contrib.wagtailroutablepage.models.RoutablePage` and configure the ``subpage_urls`` attribute with your URL configuration.
|
||||
|
||||
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 RoutablePage
|
||||
|
||||
|
||||
class EventPage(RoutablePage):
|
||||
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'),
|
||||
)
|
||||
|
||||
def current_events(self, request):
|
||||
"""
|
||||
View function for the current events page
|
||||
"""
|
||||
...
|
||||
|
||||
def past_events(self, request):
|
||||
"""
|
||||
View function for the current events page
|
||||
"""
|
||||
...
|
||||
|
||||
def events_for_year(self, request):
|
||||
"""
|
||||
View function for the events for year page
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
The ``RoutablePage`` class
|
||||
==========================
|
||||
|
||||
.. automodule:: wagtail.contrib.wagtailroutablepage.models
|
||||
.. autoclass:: RoutablePage
|
||||
|
||||
.. autoattribute:: subpage_urls
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
subpage_urls = (
|
||||
url(r'^$', 'serve', name='main'),
|
||||
url(r'^archive/$', 'archive', name='archive'),
|
||||
)
|
||||
|
||||
.. automethod:: resolve_subpage
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
view, args, kwargs = page.resolve_subpage('/past/')
|
||||
response = view(request, *args, **kwargs)
|
||||
|
||||
.. automethod:: reverse_subpage
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url = page.url + page.reverse_subpage('events_for_year', args=('2014', ))
|
||||
|
|
@ -31,6 +31,11 @@ All methods of ``PageQuerySet`` are supported by wagtailsearch:
|
|||
Indexing extra fields
|
||||
=====================
|
||||
|
||||
.. versionchanged:: 0.4
|
||||
|
||||
The ``indexed_fields`` configuration format was replaced with ``search_fields``
|
||||
|
||||
|
||||
Fields need to be explicitly added to the search configuration in order for you to be able to search/filter on them.
|
||||
|
||||
You can add new fields to the search index by overriding the ``search_fields`` property and appending a list of extra ``SearchField``/``FilterField`` objects to it.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
Sitemap generation
|
||||
==================
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
This document describes how to create XML sitemaps for your Wagtail website using the ``wagtail.contrib.wagtailsitemaps`` module.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -92,26 +92,49 @@ Then in your own page templates, you can include your snippet template tag with:
|
|||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Binding Pages to Snippets
|
||||
-------------------------
|
||||
|
||||
An alternate strategy for including snippets might involve explicitly binding a specific page object to a specific snippet object. Lets add another snippet class to see how that might work:
|
||||
In the above example, the list of adverts is a fixed list, displayed as part of the template independently of the page content. This might be what you want for a common panel in a sidebar, say - but in other scenarios you may wish to refer to a snippet within page content. This can be done by defining a foreign key to the snippet model within your page model, and adding a ``SnippetChooserPanel`` to the page's ``content_panels`` definitions. For example, if you wanted to be able to specify an advert to appear on ``BookPage``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
|
||||
# ...
|
||||
class BookPage(Page):
|
||||
advert = models.ForeignKey(
|
||||
'demo.Advert',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+'
|
||||
)
|
||||
|
||||
BookPage.content_panels = [
|
||||
SnippetChooserPanel('advert', Advert),
|
||||
# ...
|
||||
]
|
||||
|
||||
|
||||
The snippet could then be accessed within your template as ``self.advert``.
|
||||
|
||||
To attach multiple adverts to a page, the ``SnippetChooserPanel`` can be placed on an inline child object of ``BookPage``, rather than on ``BookPage`` itself. Here this child model is named ``BookPageAdvertPlacement`` (so called because there is one such object for each time that an advert is placed on a BookPage):
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.db import models
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailadmin.edit_handlers import PageChooserPanel
|
||||
from wagtail.wagtailsnippets.models import register_snippet
|
||||
from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
|
||||
|
||||
from modelcluster.fields import ParentalKey
|
||||
|
||||
...
|
||||
|
||||
class AdvertPlacement(models.Model):
|
||||
page = ParentalKey('wagtailcore.Page', related_name='advert_placements')
|
||||
class BookPageAdvertPlacement(Orderable, models.Model):
|
||||
page = ParentalKey('demo.BookPage', related_name='advert_placements')
|
||||
advert = models.ForeignKey('demo.Advert', related_name='+')
|
||||
|
||||
class Meta:
|
||||
|
|
@ -119,25 +142,27 @@ An alternate strategy for including snippets might involve explicitly binding a
|
|||
verbose_name_plural = "Advert Placements"
|
||||
|
||||
panels = [
|
||||
PageChooserPanel('page'),
|
||||
SnippetChooserPanel('advert', Advert),
|
||||
]
|
||||
|
||||
def __unicode__(self):
|
||||
return self.page.title + " -> " + self.advert.text
|
||||
|
||||
register_snippet(AdvertPlacement)
|
||||
class BookPage(Page):
|
||||
...
|
||||
|
||||
The class ``AdvertPlacement`` has two properties, ``page`` and ``advert``, which point to other models. Wagtail provides a ``PageChooserPanel`` and ``SnippetChooserPanel`` to let us make painless selection of those properties in the Wagtail admin. Note also the ``Meta`` class, which you can stock with the ``verbose_name`` and ``verbose_name_plural`` properties to override the snippet labels in the Wagtail admin. The text representation of the class has also gotten fancy, using both properties to construct a compound label showing the relationship it forms between a page and an Advert.
|
||||
BookPage.content_panels = [
|
||||
InlinePanel(BookPage, 'advert_placements', label="Adverts"),
|
||||
# ...
|
||||
]
|
||||
|
||||
With this snippet in place, we can use the reverse ``related_name`` lookup label ``advert_placements`` to iterate over any placements within our template files. In the template for a ``Page``-derived model, we could include the following:
|
||||
|
||||
These child objects are now accessible through the page's ``advert_placements`` property, and from there we can access the linked Advert snippet as ``advert``. In the template for ``BookPage``, we could include the following:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% if self.advert_placements %}
|
||||
{% for advert_placement in self.advert_placements.all %}
|
||||
<p><a href="{{ advert_placement.advert.url }}">{{ advert_placement.advert.text }}</a></p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for advert_placement in self.advert_placements.all %}
|
||||
<p><a href="{{ advert_placement.advert.url }}">{{ advert_placement.advert.text }}</a></p>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
# Production-configured Wagtail installation.
|
||||
# BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE!
|
||||
# For a non-dummy email backend configure Django's EMAIL_BACKEND
|
||||
|
|
@ -12,7 +13,7 @@ PROJECT_ROOT=/usr/local/django
|
|||
|
||||
echo "This script overwrites key files, and should only be run on a new box."
|
||||
read -p "Type 'yes' to confirm: " CONFIRM
|
||||
[ “$CONFIRM” == “yes” ] || exit
|
||||
[ "$CONFIRM" == "yes" ] || exit
|
||||
|
||||
read -p "Enter a name for your project [$PROJECT]: " U_PROJECT
|
||||
if [ ! -z "$U_PROJECT" ]; then
|
||||
|
|
@ -35,7 +36,7 @@ SERVER_IP=`ifconfig eth0 |grep "inet addr" | cut -d: -f2 | cut -d" " -f1`
|
|||
|
||||
aptitude update
|
||||
aptitude -y install git python-pip nginx postgresql redis-server
|
||||
aptitude -y install postgresql-server-dev-all python-dev libxml2-dev libxslt-dev libjpeg62-dev
|
||||
aptitude -y install postgresql-server-dev-all python-dev libjpeg62-dev
|
||||
|
||||
perl -pi -e "s/^(local\s+all\s+postgres\s+)peer$/\1trust/" /etc/postgresql/9.1/main/pg_hba.conf
|
||||
service postgresql reload
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
# Production-configured Wagtail installation.
|
||||
# BUT, SECURE SERVICES/ACCOUNT FOR FULL PRODUCTION USE!
|
||||
# For a non-dummy email backend configure Django's EMAIL_BACKEND
|
||||
|
|
@ -10,7 +11,7 @@ PROJECT_ROOT=/usr/local/django
|
|||
|
||||
echo "This script overwrites key files, and should only be run on a new box."
|
||||
read -p "Type 'yes' to confirm: " CONFIRM
|
||||
[ “$CONFIRM” == “yes” ] || exit
|
||||
[ "$CONFIRM" == "yes" ] || exit
|
||||
|
||||
read -p "Enter a name for your project [$PROJECT]: " U_PROJECT
|
||||
if [ ! -z "$U_PROJECT" ]; then
|
||||
|
|
@ -33,7 +34,7 @@ SERVER_IP=`ifconfig eth0 |grep "inet addr" | cut -d: -f2 | cut -d" " -f1`
|
|||
|
||||
aptitude update
|
||||
aptitude -y install git python-pip nginx postgresql redis-server
|
||||
aptitude -y install postgresql-server-dev-all python-dev libxml2-dev libxslt-dev libjpeg62-dev
|
||||
aptitude -y install postgresql-server-dev-all python-dev libjpeg62-dev
|
||||
|
||||
perl -pi -e "s/^(local\s+all\s+postgres\s+)peer$/\1trust/" /etc/postgresql/9.1/main/pg_hba.conf
|
||||
service postgresql reload
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -32,7 +32,7 @@ install_requires = [
|
|||
"django-treebeard==2.0",
|
||||
"Pillow>=2.3.0",
|
||||
"beautifulsoup4>=4.3.2",
|
||||
"lxml>=3.3.0",
|
||||
"html5lib==0.999",
|
||||
"Unidecode>=0.04.14",
|
||||
"six==1.7.3",
|
||||
'requests==2.3.0',
|
||||
|
|
|
|||
0
wagtail/contrib/wagtailroutablepage/__init__.py
Normal file
0
wagtail/contrib/wagtailroutablepage/__init__.py
Normal file
74
wagtail/contrib/wagtailroutablepage/models.py
Normal file
74
wagtail/contrib/wagtailroutablepage/models.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
from six import string_types
|
||||
|
||||
from django.http import Http404
|
||||
from django.core.urlresolvers import get_resolver
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailcore.url_routing import RouteResult
|
||||
|
||||
|
||||
class RoutablePage(Page):
|
||||
"""
|
||||
This class extends Page by adding methods to allow urlconfs to be embedded inside pages
|
||||
"""
|
||||
#: Set this to a tuple of ``django.conf.urls.url`` objects.
|
||||
subpage_urls = None
|
||||
|
||||
def reverse_subpage(self, name, args=None, kwargs=None):
|
||||
"""
|
||||
This method does the same job as Djangos' built in "urlresolvers.reverse()" function for subpage urlconfs.
|
||||
"""
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
|
||||
if self.subpage_urls is None:
|
||||
raise ImproperlyConfigured("You must set 'subpage_urls' on " + type(self).__name__)
|
||||
|
||||
resolver = get_resolver(self.subpage_urls)
|
||||
return resolver.reverse(name, *args, **kwargs)
|
||||
|
||||
def resolve_subpage(self, path):
|
||||
"""
|
||||
This finds a view method/function from a URL path.
|
||||
"""
|
||||
if self.subpage_urls is None:
|
||||
raise ImproperlyConfigured("You must set 'subpage_urls' on " + type(self).__name__)
|
||||
|
||||
resolver = get_resolver(self.subpage_urls)
|
||||
view, args, kwargs = resolver.resolve(path)
|
||||
|
||||
# If view is a string, find it as an attribute of self
|
||||
if isinstance(view, string_types):
|
||||
view = getattr(self, view)
|
||||
|
||||
return view, args, kwargs
|
||||
|
||||
def route(self, request, path_components):
|
||||
"""
|
||||
This hooks the subpage urls into Wagtails routing.
|
||||
"""
|
||||
if self.live:
|
||||
try:
|
||||
path = '/'
|
||||
if path_components:
|
||||
path += '/'.join(path_components) + '/'
|
||||
|
||||
view, args, kwargs = self.resolve_subpage(path)
|
||||
return RouteResult(self, args=(view, args, kwargs))
|
||||
except Http404:
|
||||
pass
|
||||
|
||||
return super(RoutablePage, self).route(request, path_components)
|
||||
|
||||
def serve(self, request, view, args, kwargs):
|
||||
return view(request, *args, **kwargs)
|
||||
|
||||
def serve_preview(self, request, mode_name):
|
||||
view, args, kwargs = self.resolve_subpage('/')
|
||||
return view(*args, **kwargs)
|
||||
|
||||
is_abstract = True
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
82
wagtail/contrib/wagtailroutablepage/tests.py
Normal file
82
wagtail/contrib/wagtailroutablepage/tests.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.tests.models import RoutablePageTest, routable_page_external_view
|
||||
|
||||
|
||||
class TestRoutablePage(TestCase):
|
||||
def setUp(self):
|
||||
self.home_page = Page.objects.get(id=2)
|
||||
self.routable_page = self.home_page.add_child(instance=RoutablePageTest(
|
||||
title="Routable Page",
|
||||
slug='routable-page',
|
||||
live=True,
|
||||
))
|
||||
|
||||
def test_resolve_main_view(self):
|
||||
view, args, kwargs = self.routable_page.resolve_subpage('/')
|
||||
|
||||
self.assertEqual(view, self.routable_page.main)
|
||||
self.assertEqual(args, ())
|
||||
self.assertEqual(kwargs, {})
|
||||
|
||||
def test_resolve_archive_by_year_view(self):
|
||||
view, args, kwargs = self.routable_page.resolve_subpage('/archive/year/2014/')
|
||||
|
||||
self.assertEqual(view, self.routable_page.archive_by_year)
|
||||
self.assertEqual(args, ('2014', ))
|
||||
self.assertEqual(kwargs, {})
|
||||
|
||||
def test_resolve_archive_by_author_view(self):
|
||||
view, args, kwargs = self.routable_page.resolve_subpage('/archive/author/joe-bloggs/')
|
||||
|
||||
self.assertEqual(view, self.routable_page.archive_by_author)
|
||||
self.assertEqual(args, ())
|
||||
self.assertEqual(kwargs, {'author_slug': 'joe-bloggs'})
|
||||
|
||||
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, {})
|
||||
|
||||
def test_reverse_main_view(self):
|
||||
url = self.routable_page.reverse_subpage('main')
|
||||
|
||||
self.assertEqual(url, '')
|
||||
|
||||
def test_reverse_archive_by_year_view(self):
|
||||
url = self.routable_page.reverse_subpage('archive_by_year', args=('2014', ))
|
||||
|
||||
self.assertEqual(url, 'archive/year/2014/')
|
||||
|
||||
def test_reverse_archive_by_author_view(self):
|
||||
url = self.routable_page.reverse_subpage('archive_by_author', kwargs={'author_slug': 'joe-bloggs'})
|
||||
|
||||
self.assertEqual(url, 'archive/author/joe-bloggs/')
|
||||
|
||||
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_get_main_view(self):
|
||||
response = self.client.get(self.routable_page.url)
|
||||
|
||||
self.assertContains(response, "MAIN VIEW")
|
||||
|
||||
def test_get_archive_by_year_view(self):
|
||||
response = self.client.get(self.routable_page.url + 'archive/year/2014/')
|
||||
|
||||
self.assertContains(response, "ARCHIVE BY YEAR: 2014")
|
||||
|
||||
def test_get_archive_by_author_view(self):
|
||||
response = self.client.get(self.routable_page.url + 'archive/author/joe-bloggs/')
|
||||
|
||||
self.assertContains(response, "ARCHIVE BY AUTHOR: joe-bloggs")
|
||||
|
||||
def test_get_external_view(self):
|
||||
response = self.client.get(self.routable_page.url + 'external/joe-bloggs/')
|
||||
|
||||
self.assertContains(response, "EXTERNAL VIEW: joe-bloggs")
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@import "../variables.scss";
|
||||
@import "../mixins.scss";
|
||||
@import "../grid.scss";
|
||||
@import "wagtailadmin/scss/variables.scss";
|
||||
@import "wagtailadmin/scss/mixins.scss";
|
||||
@import "wagtailadmin/scss/grid.scss";
|
||||
|
||||
section{
|
||||
border-top:1px solid $color-grey-3;
|
||||
|
|
@ -50,6 +50,9 @@ section{
|
|||
.color-green{
|
||||
background-color:$color-green;
|
||||
}
|
||||
.color-blue{
|
||||
background-color:$color-blue;
|
||||
}
|
||||
.color-grey-1{
|
||||
background-color:$color-grey-1;
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
{% block extra_css %}
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}wagtailadmin/scss/layouts/styleguide.scss" type="text/x-scss" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}wagtailstyleguide/scss/styleguide.scss" type="text/x-scss" />
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
<ul class="unlist">
|
||||
<li><a href="#palette">Colour palette</a></li>
|
||||
<li><a href="#typography">Typography</a></li>
|
||||
<li><a href="#help">Help text</a></li>
|
||||
<li><a href="#listings">Listings</a></li>
|
||||
<li><a href="#buttons">Buttons</a></li>
|
||||
<li><a href="#dropdowns">Dropdown buttons</a></li>
|
||||
|
|
@ -59,6 +60,7 @@
|
|||
<li class="color-red">color-red</li>
|
||||
<li class="color-orange">color-orange</li>
|
||||
<li class="color-green">color-green</li>
|
||||
<li class="color-blue">color-blue</li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
|
@ -88,6 +90,26 @@
|
|||
|
||||
</section>
|
||||
|
||||
<section id="help">
|
||||
<h2>Help text</h2>
|
||||
|
||||
<p>Help text is not to be confused with the messages that appear in a banner drop down from the top of the screen. Help text are permanent instructions, visible on every page view, that explain or warn about something.
|
||||
|
||||
<div class="help-block help-info">
|
||||
<p>This is help text that might be just for information, explaining what happens next, or drawing the user's attention to something they're about to do</p>
|
||||
<p>It could be multiple lines</p>
|
||||
</div>
|
||||
|
||||
<p class="help-block help-warning">
|
||||
A warning message might be output in cases where a user's action could have serious consequences
|
||||
</p>
|
||||
|
||||
<div class="help-block help-critical">
|
||||
A critical message would probably be rare, in cases where a particularly brittle or dangerously destructive action could be performed and needs to be warned about.
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="listings">
|
||||
<h2>Listings</h2>
|
||||
|
||||
|
|
@ -416,6 +438,7 @@
|
|||
<li class="icon icon-date">date</li>
|
||||
<li class="icon icon-time">time</li>
|
||||
<li class="icon icon-form">form</li>
|
||||
<li class="icon icon-site">site</li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from django.db import models
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.conf.urls import url
|
||||
from django.http import HttpResponse
|
||||
|
||||
from modelcluster.fields import ParentalKey
|
||||
|
||||
|
|
@ -12,6 +14,7 @@ from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel
|
|||
from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField
|
||||
from wagtail.wagtailsnippets.models import register_snippet
|
||||
from wagtail.wagtailsearch import indexed
|
||||
from wagtail.contrib.wagtailroutablepage.models import RoutablePage
|
||||
|
||||
|
||||
EVENT_AUDIENCE_CHOICES = (
|
||||
|
|
@ -383,9 +386,31 @@ class SearchTestOldConfig(models.Model, indexed.Indexed):
|
|||
},
|
||||
}
|
||||
|
||||
|
||||
class SearchTestOldConfigList(models.Model, indexed.Indexed):
|
||||
"""
|
||||
This tests that the Indexed class can correctly handle models that
|
||||
use the old "indexed_fields" configuration format using a list.
|
||||
"""
|
||||
indexed_fields = ['title', 'content']
|
||||
|
||||
|
||||
def routable_page_external_view(request, arg):
|
||||
return HttpResponse("EXTERNAL VIEW: " + arg)
|
||||
|
||||
class RoutablePageTest(RoutablePage):
|
||||
subpage_urls = (
|
||||
url(r'^$', 'main', name='main'),
|
||||
url(r'^archive/year/(\d+)/$', 'archive_by_year', name='archive_by_year'),
|
||||
url(r'^archive/author/(?P<author_slug>.+)/$', 'archive_by_author', name='archive_by_author'),
|
||||
url(r'^external/(.+)/$', routable_page_external_view, name='external_view')
|
||||
)
|
||||
|
||||
def archive_by_year(self, request, year):
|
||||
return HttpResponse("ARCHIVE BY YEAR: " + str(year))
|
||||
|
||||
def archive_by_author(self, request, author_slug):
|
||||
return HttpResponse("ARCHIVE BY AUTHOR: " + author_slug)
|
||||
|
||||
def main(self, request):
|
||||
return HttpResponse("MAIN VIEW")
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ STATICFILES_FINDERS = (
|
|||
)
|
||||
|
||||
USE_TZ = True
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
|
||||
'django.core.context_processors.request',
|
||||
|
|
@ -72,6 +71,7 @@ INSTALLED_APPS = [
|
|||
'wagtail.wagtailforms',
|
||||
'wagtail.contrib.wagtailstyleguide',
|
||||
'wagtail.contrib.wagtailsitemaps',
|
||||
'wagtail.contrib.wagtailroutablepage',
|
||||
'wagtail.tests',
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ $(function(){
|
|||
$('body').addClass('ready');
|
||||
|
||||
// Enable toggle to open/close nav
|
||||
$('#nav-toggle').click(function(){
|
||||
$(document).on('click', '#nav-toggle', function(){
|
||||
$('body').toggleClass('nav-open');
|
||||
if(!$('body').hasClass('nav-open')){
|
||||
$('body').addClass('nav-closed');
|
||||
|
|
@ -21,12 +21,30 @@ $(function(){
|
|||
}
|
||||
});
|
||||
|
||||
// Enable swishy section navigation menu
|
||||
$('.explorer').addClass('dl-menuwrapper').dlmenu({
|
||||
animationClasses : {
|
||||
classin : 'dl-animate-in-2',
|
||||
classout : 'dl-animate-out-2'
|
||||
// Dynamically load menu on request.
|
||||
$(document).on('click', '.dl-trigger', function(){
|
||||
var $this = $(this);
|
||||
var $explorer = $('#explorer');
|
||||
|
||||
$this.addClass('icon-spinner');
|
||||
|
||||
if(!$explorer.children().length){
|
||||
$explorer.load(window.explorer_menu_url, function() {
|
||||
$this.removeClass('icon-spinner');
|
||||
|
||||
$explorer.addClass('dl-menuwrapper').dlmenu({
|
||||
animationClasses : {
|
||||
classin : 'dl-animate-in-2',
|
||||
classout : 'dl-animate-out-2'
|
||||
}
|
||||
});
|
||||
$explorer.dlmenu('openMenu');
|
||||
});
|
||||
}else{
|
||||
$explorer.dlmenu('openMenu');
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Resize nav to fit height of window. This is an unimportant bell/whistle to make it look nice
|
||||
|
|
@ -35,9 +53,6 @@ $(function(){
|
|||
$('.nav-main').each(function(){
|
||||
var thisHeight = $(this).height();
|
||||
var footerHeight = $('.footer', $(this)).height();
|
||||
|
||||
// $(this).css({'height':thisHeight - footerHeight, 'overflow-y':'scroll'});
|
||||
// $('> ul', $(this)).height(thisHeight)
|
||||
});
|
||||
};
|
||||
fitNav();
|
||||
|
|
@ -86,22 +101,6 @@ $(function(){
|
|||
}
|
||||
});
|
||||
|
||||
/* Bulk-selection */
|
||||
$(document).on('click', 'thead .bulk', function(){
|
||||
$(this).closest('table').find('tbody .bulk input').each(function(){
|
||||
$(this).prop('checked', !$(this).prop('checked'));
|
||||
});
|
||||
});
|
||||
|
||||
$('#menu-search input').bind('focus', function(){
|
||||
$('#menu-search').addClass('focussed');
|
||||
}).bind('blur', function(){
|
||||
$('#menu-search').removeClass('focussed');
|
||||
});
|
||||
$('#menu-search').bind('focus click', function(){
|
||||
$(this).addClass('focussed');
|
||||
});
|
||||
|
||||
/* Header search behaviour */
|
||||
if(window.headerSearch){
|
||||
var search_current_index = 0;
|
||||
|
|
|
|||
|
|
@ -194,6 +194,13 @@ a.tag:hover{
|
|||
}
|
||||
}
|
||||
|
||||
/* general image style */
|
||||
img{
|
||||
max-width:100%;
|
||||
height:auto;
|
||||
border: 3px solid $color-grey-4;
|
||||
}
|
||||
|
||||
|
||||
/* make a block-level element inline */
|
||||
.inline{
|
||||
|
|
|
|||
|
|
@ -250,6 +250,9 @@
|
|||
.icon-form:before{
|
||||
content:"$";
|
||||
}
|
||||
.icon-site:before{
|
||||
content:"@";
|
||||
}
|
||||
|
||||
.icon.text-replace{
|
||||
font-size:0em;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
/*
|
||||
Messages are specific to Django's "Messaging" system which adds messages into the session,
|
||||
for display on the next page visited. These appear as an animated banner at the top of the page.
|
||||
|
||||
For inline help text, see typography.scss
|
||||
*/
|
||||
|
||||
.messages{
|
||||
position:relative;
|
||||
z-index:5;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
h1,h2,h3,h4,h5,h6{
|
||||
font-weight:normal;
|
||||
}
|
||||
|
||||
h1{
|
||||
line-height:1.3em;
|
||||
font-size:1.5em;
|
||||
text-transform:uppercase;
|
||||
color:$color-grey-1;
|
||||
font-weight:600;
|
||||
|
||||
span{
|
||||
text-transform:none;
|
||||
font-weight:300;
|
||||
}
|
||||
|
||||
.homepage &{
|
||||
text-transform:none;
|
||||
}
|
||||
}
|
||||
h2{
|
||||
text-transform:uppercase;
|
||||
font-size:1.3em;
|
||||
font-family:Open Sans;
|
||||
font-weight:600;
|
||||
color:$color-grey-2;
|
||||
|
||||
.page-explorer &{
|
||||
text-transform:none;
|
||||
}
|
||||
}
|
||||
a{
|
||||
outline:none;
|
||||
color:$color-link;
|
||||
text-decoration:none;
|
||||
|
||||
&:hover{
|
||||
color:$color-link-hover;
|
||||
}
|
||||
}
|
||||
|
||||
code{
|
||||
@include box-shadow(inset 0px 0px 4px 0px rgba(0, 0, 0, 0.2));
|
||||
background-color:$color-fieldset-hover;
|
||||
padding:2px 5px;
|
||||
}
|
||||
|
||||
kbd{
|
||||
@include border-radius(3px);
|
||||
font-family:Open Sans, Arial, sans-serif;
|
||||
border:1px solid $color-grey-2;
|
||||
border-color:rgba(0,0,0,0.2);
|
||||
padding:0.3em 0.5em;
|
||||
}
|
||||
|
||||
/* Help text formatters */
|
||||
|
||||
.help-block{
|
||||
padding:1em;
|
||||
margin:1em 0;
|
||||
|
||||
p{
|
||||
margin-top:0;
|
||||
|
||||
&:last-child{
|
||||
margin-bottom:0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.help-info, .help-warning, .help-critical{
|
||||
@include border-radius(3px);
|
||||
border:1px solid $color-grey-4;
|
||||
padding-left:3.5em;
|
||||
position:relative;
|
||||
|
||||
&:before{
|
||||
font-family:wagtail;
|
||||
position:absolute;
|
||||
left:1em;
|
||||
top:0.7em;
|
||||
content:"?";
|
||||
font-size:1.4em;
|
||||
}
|
||||
}
|
||||
.help-info{
|
||||
border-color:$color-blue;
|
||||
|
||||
&:before{
|
||||
color:$color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.help-warning{
|
||||
border-color:$color-orange;
|
||||
|
||||
&:before{
|
||||
color:$color-orange;
|
||||
content:"!";
|
||||
}
|
||||
}
|
||||
|
||||
.help-critical{
|
||||
border-color:$color-red;
|
||||
|
||||
&:before{
|
||||
color:$color-red;
|
||||
content:"!";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
@import "components/explorer.scss";
|
||||
@import "components/icons.scss";
|
||||
@import "components/typography.scss";
|
||||
@import "components/tabs.scss";
|
||||
@import "components/dropdowns.scss";
|
||||
@import "components/modals.scss";
|
||||
|
|
@ -35,67 +36,6 @@ body{
|
|||
}
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6{
|
||||
font-weight:normal;
|
||||
}
|
||||
|
||||
h1{
|
||||
line-height:1.3em;
|
||||
font-size:1.5em;
|
||||
text-transform:uppercase;
|
||||
color:$color-grey-1;
|
||||
font-weight:600;
|
||||
|
||||
span{
|
||||
text-transform:none;
|
||||
font-weight:300;
|
||||
}
|
||||
|
||||
.homepage &{
|
||||
text-transform:none;
|
||||
}
|
||||
}
|
||||
h2{
|
||||
text-transform:uppercase;
|
||||
font-size:1.3em;
|
||||
font-family:Open Sans;
|
||||
font-weight:600;
|
||||
color:$color-grey-2;
|
||||
|
||||
.page-explorer &{
|
||||
text-transform:none;
|
||||
}
|
||||
}
|
||||
a{
|
||||
outline:none;
|
||||
color:$color-link;
|
||||
text-decoration:none;
|
||||
|
||||
&:hover{
|
||||
color:$color-link-hover;
|
||||
}
|
||||
}
|
||||
|
||||
code{
|
||||
@include box-shadow(inset 0px 0px 4px 0px rgba(0, 0, 0, 0.2));
|
||||
background-color:$color-fieldset-hover;
|
||||
padding:2px 5px;
|
||||
}
|
||||
|
||||
kbd{
|
||||
@include border-radius(3px);
|
||||
font-family:Open Sans, Arial, sans-serif;
|
||||
border:1px solid $color-grey-2;
|
||||
border-color:rgba(0,0,0,0.2);
|
||||
padding:0.3em 0.5em;
|
||||
}
|
||||
|
||||
img{
|
||||
max-width:100%;
|
||||
height:auto;
|
||||
border: 3px solid $color-grey-4;
|
||||
}
|
||||
|
||||
.browsermessage{
|
||||
background-color:$color-red;
|
||||
color:white;
|
||||
|
|
@ -163,7 +103,6 @@ img{
|
|||
}
|
||||
|
||||
.nav-main{
|
||||
|
||||
top: 43px;
|
||||
bottom: 0px;
|
||||
overflow: auto;
|
||||
|
|
@ -226,6 +165,15 @@ img{
|
|||
&:hover{
|
||||
color:white;
|
||||
}
|
||||
|
||||
/* only really used for spinners */
|
||||
&:after{
|
||||
font-size:1.5em;
|
||||
margin:0;
|
||||
position:absolute;
|
||||
right:0.5em;
|
||||
margin-top:0.15em;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar{
|
||||
|
|
|
|||
9800
wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail-icomoon.json
Executable file → Normal file
9800
wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail-icomoon.json
Executable file → Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 60 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -27,6 +27,7 @@ $breakpoint-desktop-larger: 100em; /* 1600px */
|
|||
$color-teal: #43b1b0;
|
||||
$color-teal-darker: darken($color-teal, 10%);
|
||||
$color-teal-dark: #246060;
|
||||
$color-blue: #71b2d4;
|
||||
$color-red: #cd3238;
|
||||
$color-orange:#e9b04d;
|
||||
$color-green: #189370;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/bootstrap-tab.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/jquery.dlmenu.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/core.js"></script>
|
||||
<script>
|
||||
window.explorer_menu_url = "{% url 'wagtailadmin_explorer_nav' %}";
|
||||
</script>
|
||||
{% endcompress %}
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@
|
|||
{% main_nav %}
|
||||
</div>
|
||||
|
||||
<nav class="explorer">
|
||||
<ul class="dl-menu">
|
||||
{% explorer_nav %}
|
||||
</ul>
|
||||
</nav>
|
||||
<nav id="explorer" class="explorer"></nav>
|
||||
</div>
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
{% load wagtailadmin_tags %}
|
||||
|
||||
{% for page, children in nodes %}
|
||||
<li {% if children %}class="has-children"{% endif %}>
|
||||
<a href="{% url 'wagtailadmin_explore' page.id %}" class="icon icon-folder-open-inverse">{{ page.title }}</a>
|
||||
{% if children %}
|
||||
<div class="children icon icon-arrow-right"></div>
|
||||
<ul class="dl-submenu">
|
||||
{% explorer_subnav children %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
<ul class="dl-menu">
|
||||
{% include "wagtailadmin/shared/explorer_nav_child.html" %}
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{% load wagtailadmin_tags %}
|
||||
|
||||
{% for page, children in nodes %}
|
||||
<li {% if children %}class="has-children"{% endif %}>
|
||||
<a href="{% url 'wagtailadmin_explore' page.id %}" class="icon icon-folder-open-inverse">{{ page.title }}</a>
|
||||
{% if children %}
|
||||
<div class="children icon icon-arrow-right"></div>
|
||||
<ul class="dl-submenu">
|
||||
{% explorer_subnav children %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
@ -21,7 +21,7 @@ def explorer_nav():
|
|||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('wagtailadmin/shared/explorer_nav.html')
|
||||
@register.inclusion_tag('wagtailadmin/shared/explorer_nav_child.html')
|
||||
def explorer_subnav(nodes):
|
||||
return {
|
||||
'nodes': nodes
|
||||
|
|
@ -31,7 +31,7 @@ def explorer_subnav(nodes):
|
|||
@register.inclusion_tag('wagtailadmin/shared/main_nav.html', takes_context=True)
|
||||
def main_nav(context):
|
||||
menu_items = [
|
||||
MenuItem(_('Explorer'), '#', classnames='icon icon-folder-open-inverse dl-trigger', order=100),
|
||||
MenuItem(_('Explorer'), urlresolvers.reverse('wagtailadmin_explore_root'), classnames='icon icon-folder-open-inverse dl-trigger', order=100),
|
||||
MenuItem(_('Search'), urlresolvers.reverse('wagtailadmin_pages_search'), classnames='icon icon-search', order=200),
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,17 @@ from wagtail.wagtailcore.signals import page_published, page_unpublished
|
|||
from wagtail.wagtailusers.models import UserProfile
|
||||
|
||||
|
||||
def submittable_timestamp(timestamp):
|
||||
"""
|
||||
Helper function to translate a possibly-timezone-aware datetime into the format used in the
|
||||
go_live_at / expire_at form fields - "YYYY-MM-DD hh:mm", with no timezone indicator.
|
||||
This will be interpreted as being in the server's timezone (settings.TIME_ZONE), so we
|
||||
need to pass it through timezone.localtime to ensure that the client and server are in
|
||||
agreement about what the timestamp means.
|
||||
"""
|
||||
return str(timezone.localtime(timestamp)).split('.')[0]
|
||||
|
||||
|
||||
class TestPageExplorer(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
|
|
@ -180,8 +191,8 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
'title': "New page!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
'go_live_at': submittable_timestamp(go_live_at),
|
||||
'expire_at': submittable_timestamp(expire_at),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
|
|
@ -203,8 +214,8 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
'title': "New page!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(timezone.now() + timedelta(days=2)).split('.')[0],
|
||||
'expire_at': str(timezone.now() + timedelta(days=1)).split('.')[0],
|
||||
'go_live_at': submittable_timestamp(timezone.now() + timedelta(days=2)),
|
||||
'expire_at': submittable_timestamp(timezone.now() + timedelta(days=1)),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
|
|
@ -219,7 +230,7 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
'title': "New page!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'expire_at': str(timezone.now() + timedelta(days=-1)).split('.')[0],
|
||||
'expire_at': submittable_timestamp(timezone.now() + timedelta(days=-1)),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
|
|
@ -268,8 +279,8 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'action-publish': "Publish",
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
'go_live_at': submittable_timestamp(go_live_at),
|
||||
'expire_at': submittable_timestamp(expire_at),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data)
|
||||
|
||||
|
|
@ -428,14 +439,16 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
self.assertTrue(child_page_new.has_unpublished_changes)
|
||||
|
||||
def test_edit_post_scheduled(self):
|
||||
go_live_at = timezone.now() + timedelta(days=1)
|
||||
expire_at = timezone.now() + timedelta(days=2)
|
||||
# put go_live_at and expire_at several days away from the current date, to avoid
|
||||
# false matches in content_json__contains tests
|
||||
go_live_at = timezone.now() + timedelta(days=10)
|
||||
expire_at = timezone.now() + timedelta(days=20)
|
||||
post_data = {
|
||||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
'go_live_at': submittable_timestamp(go_live_at),
|
||||
'expire_at': submittable_timestamp(expire_at),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
|
|
@ -459,8 +472,8 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'go_live_at': str(timezone.now() + timedelta(days=2)).split('.')[0],
|
||||
'expire_at': str(timezone.now() + timedelta(days=1)).split('.')[0],
|
||||
'go_live_at': submittable_timestamp(timezone.now() + timedelta(days=2)),
|
||||
'expire_at': submittable_timestamp(timezone.now() + timedelta(days=1)),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
|
|
@ -475,7 +488,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
'title': "I've been edited!",
|
||||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'expire_at': str(timezone.now() + timedelta(days=-1)).split('.')[0],
|
||||
'expire_at': submittable_timestamp(timezone.now() + timedelta(days=-1)),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
|
|
@ -525,8 +538,8 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'action-publish': "Publish",
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
'go_live_at': submittable_timestamp(go_live_at),
|
||||
'expire_at': submittable_timestamp(expire_at),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
|
|
@ -550,8 +563,8 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
'content': "Some content",
|
||||
'slug': 'hello-world',
|
||||
'action-publish': "Publish",
|
||||
'go_live_at': str(go_live_at).split('.')[0],
|
||||
'expire_at': str(expire_at).split('.')[0],
|
||||
'go_live_at': submittable_timestamp(go_live_at),
|
||||
'expire_at': submittable_timestamp(expire_at),
|
||||
}
|
||||
response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,3 +43,17 @@ class TestSendEmailTask(TestCase):
|
|||
self.assertEqual(mail.outbox[0].subject, "Test subject")
|
||||
self.assertEqual(mail.outbox[0].body, "Test content")
|
||||
self.assertEqual(mail.outbox[0].to, ["nobody@email.com"])
|
||||
|
||||
|
||||
class TestExplorerNavView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.homepage = Page.objects.get(id=2).specific
|
||||
self.login()
|
||||
|
||||
def test_explorer_nav_view(self):
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
self.assertEqual(response.context['nodes'][0][0], self.homepage)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ urlpatterns += [
|
|||
|
||||
url(r'^failwhale/$', home.error_test, name='wagtailadmin_error_test'),
|
||||
|
||||
url(r'^explorer-nav/$', pages.explorer_nav, name='wagtailadmin_explorer_nav'),
|
||||
|
||||
url(r'^pages/$', pages.index, name='wagtailadmin_explore_root'),
|
||||
url(r'^pages/(\d+)/$', pages.index, name='wagtailadmin_explore'),
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,17 @@ from wagtail.wagtailadmin.forms import SearchForm
|
|||
from wagtail.wagtailadmin import tasks, signals
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailcore.models import Page, PageRevision
|
||||
from wagtail.wagtailcore.models import Page, PageRevision, get_navigation_menu_items
|
||||
from wagtail.wagtailcore.signals import page_published, page_unpublished
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
def explorer_nav(request):
|
||||
return render(request, 'wagtailadmin/shared/explorer_nav.html', {
|
||||
'nodes': get_navigation_menu_items(),
|
||||
})
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
def index(request, parent_page_id=None):
|
||||
if parent_page_id:
|
||||
|
|
|
|||
|
|
@ -381,10 +381,11 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, indexed.Index
|
|||
cursor.execute(update_statement,
|
||||
[new_url_path, len(old_url_path) + 1, self.path + '%', self.id])
|
||||
|
||||
#: Return this page in its most specific subclassed form.
|
||||
@cached_property
|
||||
def specific(self):
|
||||
"""
|
||||
Return this page in its most specific subclassed form.
|
||||
Return this page in its most specific subclassed form.
|
||||
"""
|
||||
# the ContentType.objects manager keeps a cache, so this should potentially
|
||||
# avoid a database lookup over doing self.content_type. I think.
|
||||
|
|
@ -395,11 +396,13 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, indexed.Index
|
|||
else:
|
||||
return content_type.get_object_for_this_type(id=self.id)
|
||||
|
||||
#: Return the class that this page would be if instantiated in its
|
||||
#: most specific form
|
||||
@cached_property
|
||||
def specific_class(self):
|
||||
"""
|
||||
return the class that this page would be if instantiated in its
|
||||
most specific form
|
||||
Return the class that this page would be if instantiated in its
|
||||
most specific form
|
||||
"""
|
||||
content_type = ContentType.objects.get_for_id(self.content_type_id)
|
||||
return content_type.model_class()
|
||||
|
|
|
|||
|
|
@ -157,9 +157,21 @@ LINK_HANDLERS = {
|
|||
}
|
||||
|
||||
|
||||
# Prepare a whitelisting engine with custom behaviour:
|
||||
# rewrite any elements with a data-embedtype or data-linktype attribute
|
||||
class DbWhitelister(Whitelister):
|
||||
"""
|
||||
A custom whitelisting engine to convert the HTML as returned by the rich text editor
|
||||
into the pseudo-HTML format stored in the database (in which images, documents and other
|
||||
linked objects are identified by ID rather than URL):
|
||||
|
||||
* implements a 'construct_whitelister_element_rules' hook so that other apps can modify
|
||||
the whitelist ruleset (e.g. to permit additional HTML elements beyond those in the base
|
||||
Whitelister module);
|
||||
* replaces any element with a 'data-embedtype' attribute with an <embed> element, with
|
||||
attributes supplied by the handler for that type as defined in EMBED_HANDLERS;
|
||||
* rewrites the attributes of any <a> element with a 'data-linktype' attribute, as
|
||||
determined by the handler for that type defined in LINK_HANDLERS, while keeping the
|
||||
element content intact.
|
||||
"""
|
||||
has_loaded_custom_whitelist_rules = False
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ class TestDbWhitelister(TestCase):
|
|||
self.assertHtmlEqual(expected, output_html)
|
||||
|
||||
def test_image_embed_is_rewritten(self):
|
||||
input_html = '<p>OMG look at this picture of a kitten: <figure data-embedtype="image" data-id="5" data-format="image-with-caption" data-alt="A cute kitten" class="fancy-image"><img src="/media/images/kitten.jpg" width="320" height="200" alt="A cute kitten" /><figcaption>A kitten, yesterday.</figcaption></figure></p>'
|
||||
input_html = '<p>OMG look at this picture of a kitten:</p><figure data-embedtype="image" data-id="5" data-format="image-with-caption" data-alt="A cute kitten" class="fancy-image"><img src="/media/images/kitten.jpg" width="320" height="200" alt="A cute kitten" /><figcaption>A kitten, yesterday.</figcaption></figure>'
|
||||
output_html = DbWhitelister.clean(input_html)
|
||||
expected = '<p>OMG look at this picture of a kitten: <embed embedtype="image" id="5" format="image-with-caption" alt="A cute kitten" /></p>'
|
||||
expected = '<p>OMG look at this picture of a kitten:</p><embed embedtype="image" id="5" format="image-with-caption" alt="A cute kitten" />'
|
||||
self.assertHtmlEqual(expected, output_html)
|
||||
|
||||
def test_media_embed_is_rewritten(self):
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class Whitelister(object):
|
|||
def clean(cls, html):
|
||||
"""Clean up an HTML string to contain just the allowed elements /
|
||||
attributes"""
|
||||
doc = BeautifulSoup(html, 'lxml')
|
||||
doc = BeautifulSoup(html, 'html5lib')
|
||||
cls.clean_node(doc, doc)
|
||||
return doc.decode()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ warnings.warn(
|
|||
"Use {% load wagtailembeds_tags %} instead.", DeprecationWarning)
|
||||
|
||||
|
||||
from wagtail.wagtailembeds.templatetags.wagtailembeds_tags import register, embed, embedly
|
||||
from wagtail.wagtailembeds.templatetags.wagtailembeds_tags import register, embed
|
||||
|
|
|
|||
|
|
@ -19,12 +19,3 @@ def embed(url, max_width=None):
|
|||
return ''
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def embedly(url, max_width=None):
|
||||
warnings.warn(
|
||||
"The 'embedly' filter has been renamed. "
|
||||
"Use 'embed' instead.", DeprecationWarning)
|
||||
|
||||
return embed(url, max_width)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from wagtail.wagtailembeds.embeds import (
|
|||
AccessDeniedEmbedlyException,
|
||||
)
|
||||
from wagtail.wagtailembeds.embeds import embedly as wagtail_embedly, oembed as wagtail_oembed
|
||||
from wagtail.wagtailembeds.templatetags.wagtailembeds_tags import embed as embed_filter, embedly as embedly_filter
|
||||
from wagtail.wagtailembeds.templatetags.wagtailembeds_tags import embed as embed_filter
|
||||
|
||||
|
||||
class TestEmbeds(TestCase):
|
||||
|
|
|
|||
Loading…
Reference in a new issue