From f4245815ba546153e9551afa6db9a9397a042348 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 17 Jun 2014 09:37:35 +0100 Subject: [PATCH 01/76] Added sitemap generator --- wagtail/contrib/wagtailsitemaps/__init__.py | 0 wagtail/contrib/wagtailsitemaps/models.py | 0 .../wagtailsitemaps/sitemap_generator.py | 25 +++++++++++++++++++ .../templates/wagtailsitemaps/sitemap.xml | 13 ++++++++++ wagtail/contrib/wagtailsitemaps/views.py | 23 +++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 wagtail/contrib/wagtailsitemaps/__init__.py create mode 100644 wagtail/contrib/wagtailsitemaps/models.py create mode 100644 wagtail/contrib/wagtailsitemaps/sitemap_generator.py create mode 100644 wagtail/contrib/wagtailsitemaps/templates/wagtailsitemaps/sitemap.xml create mode 100644 wagtail/contrib/wagtailsitemaps/views.py diff --git a/wagtail/contrib/wagtailsitemaps/__init__.py b/wagtail/contrib/wagtailsitemaps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/contrib/wagtailsitemaps/models.py b/wagtail/contrib/wagtailsitemaps/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/contrib/wagtailsitemaps/sitemap_generator.py b/wagtail/contrib/wagtailsitemaps/sitemap_generator.py new file mode 100644 index 000000000..53b5dd664 --- /dev/null +++ b/wagtail/contrib/wagtailsitemaps/sitemap_generator.py @@ -0,0 +1,25 @@ +from django.template.loader import render_to_string + + +class Sitemap(object): + template = 'wagtailsitemaps/sitemap.xml' + + def __init__(self, site): + self.site = site + + def get_pages(self): + return self.site.root_page.get_descendants(inclusive=True).live().order_by('path') + + def get_urls(self): + for page in self.get_pages(): + latest_revision = page.get_latest_revision() + + yield { + 'location': page.url, + 'lastmod': latest_revision.created_at if latest_revision else None + } + + def render(self): + return render_to_string(self.template, { + 'urlset': self.get_urls() + }) diff --git a/wagtail/contrib/wagtailsitemaps/templates/wagtailsitemaps/sitemap.xml b/wagtail/contrib/wagtailsitemaps/templates/wagtailsitemaps/sitemap.xml new file mode 100644 index 000000000..30ca3c024 --- /dev/null +++ b/wagtail/contrib/wagtailsitemaps/templates/wagtailsitemaps/sitemap.xml @@ -0,0 +1,13 @@ + + +{% spaceless %} +{% for url in urlset %} + + {{ url.location }} + {% if url.lastmod %}{{ url.lastmod|date:"Y-m-d" }}{% endif %} + {% if url.changefreq %}{{ url.changefreq }}{% endif %} + {% if url.priority %}{{ url.priority }}{% endif %} + +{% endfor %} +{% endspaceless %} + diff --git a/wagtail/contrib/wagtailsitemaps/views.py b/wagtail/contrib/wagtailsitemaps/views.py new file mode 100644 index 000000000..aa5fc2e71 --- /dev/null +++ b/wagtail/contrib/wagtailsitemaps/views.py @@ -0,0 +1,23 @@ +from django.shortcuts import render +from django.http import HttpResponse +from django.core.cache import cache + +from .sitemap_generator import Sitemap + + +def sitemap(request): + cache_key = 'wagtail-sitemap:' + str(request.site.id) + sitemap_xml = cache.get(cache_key) + + if not sitemap_xml: + # Rerender sitemap + sitemap = Sitemap(request.site) + sitemap_xml = sitemap.render() + + cache.set(cache_key, sitemap_xml, 6000) + + # Build response + response = HttpResponse(sitemap_xml) + response['Content-Type'] = "text/xml; charset=utf-8" + + return response From 8e1b2cf019f0ad6c6b78ce5e8b16a5a1f123c7d9 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 23 Jun 2014 13:43:21 +0100 Subject: [PATCH 02/76] Sitemap timeout can now be configured with a setting --- wagtail/contrib/wagtailsitemaps/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wagtail/contrib/wagtailsitemaps/views.py b/wagtail/contrib/wagtailsitemaps/views.py index aa5fc2e71..4ef02de0b 100644 --- a/wagtail/contrib/wagtailsitemaps/views.py +++ b/wagtail/contrib/wagtailsitemaps/views.py @@ -1,6 +1,7 @@ from django.shortcuts import render from django.http import HttpResponse from django.core.cache import cache +from django.conf import settings from .sitemap_generator import Sitemap @@ -14,7 +15,7 @@ def sitemap(request): sitemap = Sitemap(request.site) sitemap_xml = sitemap.render() - cache.set(cache_key, sitemap_xml, 6000) + cache.set(cache_key, sitemap_xml, getattr(settings, 'WAGTAILSITEMAPS_CACHE_TIMEOUT', 6000)) # Build response response = HttpResponse(sitemap_xml) From ee7aebc9daf4517f2540e80ebe30c53685af3067 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 23 Jun 2014 13:43:34 +0100 Subject: [PATCH 03/76] Added tests for sitemaps --- runtests.py | 1 + wagtail/contrib/wagtailsitemaps/tests.py | 71 ++++++++++++++++++++++++ wagtail/tests/urls.py | 3 + 3 files changed, 75 insertions(+) create mode 100644 wagtail/contrib/wagtailsitemaps/tests.py diff --git a/runtests.py b/runtests.py index 6fd37ebeb..286a9e6b0 100755 --- a/runtests.py +++ b/runtests.py @@ -84,6 +84,7 @@ if not settings.configured: 'wagtail.wagtailsearch', 'wagtail.wagtailredirects', 'wagtail.wagtailforms', + 'wagtail.contrib.wagtailsitemaps', 'wagtail.tests', ], diff --git a/wagtail/contrib/wagtailsitemaps/tests.py b/wagtail/contrib/wagtailsitemaps/tests.py new file mode 100644 index 000000000..4adf3f98b --- /dev/null +++ b/wagtail/contrib/wagtailsitemaps/tests.py @@ -0,0 +1,71 @@ +from django.test import TestCase +from django.core.urlresolvers import reverse + +from wagtail.wagtailcore.models import Page, Site +from wagtail.tests.models import SimplePage + +from .sitemap_generator import Sitemap + + +class TestSitemapGenerator(TestCase): + def setUp(self): + self.home_page = Page.objects.get(id=2) + + self.child_page = self.home_page.add_child(instance=SimplePage( + title="Hello world!", + slug='hello-world', + live=True, + )) + + self.unpublished_child_page = self.home_page.add_child(instance=SimplePage( + title="Unpublished", + slug='unpublished', + live=False, + )) + + self.site = Site.objects.get(is_default_site=True) + + def test_get_pages(self): + sitemap = Sitemap(self.site) + pages = sitemap.get_pages() + + self.assertIn(self.child_page.page_ptr, pages) + self.assertNotIn(self.unpublished_child_page.page_ptr, pages) + + def test_get_urls(self): + sitemap = Sitemap(self.site) + urls = [url['location'] for url in sitemap.get_urls()] + + self.assertIn('/', urls) # Homepage + self.assertIn('/hello-world/', urls) # Child page + + def test_render(self): + sitemap = Sitemap(self.site) + xml = sitemap.render() + + # Check that a URL has made it into the xml + self.assertIn('/hello-world/', xml) + + +class TestSitemapView(TestCase): + def test_sitemap_view(self): + response = self.client.get('/sitemap.xml') + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsitemaps/sitemap.xml') + self.assertEqual(response['Content-Type'], 'text/xml; charset=utf-8') + + def test_sitemap_view_cache(self): + first_response = self.client.get('/sitemap.xml') + + self.assertEqual(first_response.status_code, 200) + self.assertTemplateUsed(first_response, 'wagtailsitemaps/sitemap.xml') + + # Hit the view again. Should come from the cache this time + second_response = self.client.get('/sitemap.xml') + + self.assertEqual(second_response.status_code, 200) + self.assertTemplateNotUsed(second_response, 'wagtailsitemaps/sitemap.xml') # Sitemap should not be re rendered + + # Check that the content is the same + self.assertEqual(first_response.content, second_response.content) diff --git a/wagtail/tests/urls.py b/wagtail/tests/urls.py index d04c871a3..83e12adfb 100644 --- a/wagtail/tests/urls.py +++ b/wagtail/tests/urls.py @@ -4,6 +4,7 @@ from wagtail.wagtailcore import urls as wagtail_urls from wagtail.wagtailadmin import urls as wagtailadmin_urls from wagtail.wagtaildocs import urls as wagtaildocs_urls from wagtail.wagtailsearch.urls import frontend as wagtailsearch_frontend_urls +from wagtail.contrib.wagtailsitemaps.views import sitemap # Signal handlers from wagtail.wagtailsearch import register_signal_handlers as wagtailsearch_register_signal_handlers @@ -15,6 +16,8 @@ urlpatterns = patterns('', url(r'^search/', include(wagtailsearch_frontend_urls)), url(r'^documents/', include(wagtaildocs_urls)), + url(r'^sitemap\.xml$', sitemap), + # For anything not caught by a more specific rule above, hand over to # Wagtail's serving mechanism url(r'', include(wagtail_urls)), From d76c8613fb77683dfdffd74e2cafd4eda4a242ad Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 23 Jun 2014 14:08:42 +0100 Subject: [PATCH 04/76] Added get_sitemap_urls method to page and use it in sitemaps --- .../wagtailsitemaps/sitemap_generator.py | 8 ++----- wagtail/wagtailcore/models.py | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/wagtail/contrib/wagtailsitemaps/sitemap_generator.py b/wagtail/contrib/wagtailsitemaps/sitemap_generator.py index 53b5dd664..eb08d81b7 100644 --- a/wagtail/contrib/wagtailsitemaps/sitemap_generator.py +++ b/wagtail/contrib/wagtailsitemaps/sitemap_generator.py @@ -12,12 +12,8 @@ class Sitemap(object): def get_urls(self): for page in self.get_pages(): - latest_revision = page.get_latest_revision() - - yield { - 'location': page.url, - 'lastmod': latest_revision.created_at if latest_revision else None - } + for url in page.get_sitemap_urls(): + yield url def render(self): return render_to_string(self.template, { diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 1f0c98d83..c1e48750b 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -623,13 +623,32 @@ class Page(MP_Node, ClusterableModel, Indexed): """ return self.serve(self.dummy_request()) + def get_internal_paths(self): + """ + This returns a list of paths within this page. + This is used for static sites, sitemaps and cache invalidation. + """ + return ['/'] + + def get_sitemap_urls(self): + latest_revision = self.get_latest_revision() + + return [ + { + 'location': self.url + url[1:], + 'lastmod': latest_revision.created_at if latest_revision else None + } + for url in self.get_internal_paths() + ] + def get_static_site_paths(self): """ This is a generator of URL paths to feed into a static site generator Override this if you would like to create static versions of subpages """ # Yield paths for this page - yield '/' + for url in self.get_internal_paths(): + yield url # Yield paths for child pages for child in self.get_children().live(): From 865459e5eb5055a0959bab43227b5c00fbb8e9fe Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 30 Jun 2014 14:16:46 +0100 Subject: [PATCH 05/76] Added page_published signal --- .../wagtailadmin/tests/test_pages_views.py | 26 +++++++++++++++++++ wagtail/wagtailadmin/views/pages.py | 3 +++ wagtail/wagtailcore/signals.py | 4 +++ 3 files changed, 33 insertions(+) create mode 100644 wagtail/wagtailcore/signals.py diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index aa73e907d..963a048b2 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -7,6 +7,7 @@ from django.core.paginator import Paginator from wagtail.tests.models import SimplePage, EventPage, StandardIndex, StandardChild, BusinessIndex, BusinessChild, BusinessSubIndex from wagtail.tests.utils import unittest, WagtailTestUtils from wagtail.wagtailcore.models import Page, PageRevision +from wagtail.wagtailcore.signals import page_published from wagtail.wagtailusers.models import UserProfile @@ -170,6 +171,15 @@ class TestPageCreation(TestCase, WagtailTestUtils): self.assertFalse(page.live) def test_create_simplepage_post_publish(self): + # Connect a mock signal handler to page_published signal + signal_fired = [False] + signal_page = [None] + def page_published_handler(sender, page, **kwargs): + signal_fired[0] = True + signal_page[0] = page + page_published.connect(page_published_handler) + + # Post post_data = { 'title': "New page!", 'content': "Some content", @@ -187,6 +197,10 @@ class TestPageCreation(TestCase, WagtailTestUtils): self.assertIsInstance(page, SimplePage) self.assertTrue(page.live) + # Check that the page_published signal was fired + self.assertTrue(signal_fired[0]) + self.assertEqual(signal_page[0], page) + def test_create_simplepage_post_submit(self): # Create a moderator user for testing email moderator = User.objects.create_superuser('moderator', 'moderator@email.com', 'password') @@ -327,6 +341,14 @@ class TestPageEdit(TestCase, WagtailTestUtils): self.assertTrue(child_page_new.has_unpublished_changes) def test_page_edit_post_publish(self): + # Connect a mock signal handler to page_published signal + signal_fired = [False] + signal_page = [None] + def page_published_handler(sender, page, **kwargs): + signal_fired[0] = True + signal_page[0] = page + page_published.connect(page_published_handler) + # Tests publish from edit page post_data = { 'title': "I've been edited!", @@ -343,6 +365,10 @@ class TestPageEdit(TestCase, WagtailTestUtils): child_page_new = SimplePage.objects.get(id=self.child_page.id) self.assertEqual(child_page_new.title, post_data['title']) + # Check that the page_published signal was fired + self.assertTrue(signal_fired[0]) + self.assertEqual(signal_page[0], child_page_new) + # The page shouldn't have "has_unpublished_changes" flag set self.assertFalse(child_page_new.has_unpublished_changes) diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index f6490a224..d49864f44 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -13,6 +13,7 @@ from wagtail.wagtailadmin.forms import SearchForm from wagtail.wagtailadmin import tasks, hooks, signals from wagtail.wagtailcore.models import Page, PageRevision +from wagtail.wagtailcore.signals import page_published @permission_required('wagtailadmin.access_admin') @@ -158,6 +159,7 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_ page.save_revision(user=request.user, submitted_for_moderation=is_submitting) if is_publishing: + page_published.send(sender=page_class, page=page) messages.success(request, _("Page '{0}' published.").format(page.title)) elif is_submitting: messages.success(request, _("Page '{0}' submitted for moderation.").format(page.title)) @@ -238,6 +240,7 @@ def edit(request, page_id): page.save_revision(user=request.user, submitted_for_moderation=is_submitting) if is_publishing: + page_published.send(sender=page.__class__, page=page) messages.success(request, _("Page '{0}' published.").format(page.title)) elif is_submitting: messages.success(request, _("Page '{0}' submitted for moderation.").format(page.title)) diff --git a/wagtail/wagtailcore/signals.py b/wagtail/wagtailcore/signals.py new file mode 100644 index 000000000..38a0809c8 --- /dev/null +++ b/wagtail/wagtailcore/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import Signal + + +page_published = Signal(providing_args=['page']) From de229914f00d9e21209e475a0b5542deeb82bf24 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 30 Jun 2014 14:22:55 +0100 Subject: [PATCH 06/76] Fire page_published signal on moderation approval --- wagtail/wagtailadmin/tests/test_pages_views.py | 15 +++++++++++++++ wagtail/wagtailadmin/views/pages.py | 1 + 2 files changed, 16 insertions(+) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 963a048b2..1bab3b137 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -200,6 +200,7 @@ class TestPageCreation(TestCase, WagtailTestUtils): # Check that the page_published signal was fired self.assertTrue(signal_fired[0]) self.assertEqual(signal_page[0], page) + self.assertEqual(signal_page[0], signal_page[0].specific) def test_create_simplepage_post_submit(self): # Create a moderator user for testing email @@ -368,6 +369,7 @@ class TestPageEdit(TestCase, WagtailTestUtils): # Check that the page_published signal was fired self.assertTrue(signal_fired[0]) self.assertEqual(signal_page[0], child_page_new) + self.assertEqual(signal_page[0], signal_page[0].specific) # The page shouldn't have "has_unpublished_changes" flag set self.assertFalse(child_page_new.has_unpublished_changes) @@ -667,6 +669,14 @@ class TestApproveRejectModeration(TestCase, WagtailTestUtils): """ This posts to the approve moderation view and checks that the page was approved """ + # Connect a mock signal handler to page_published signal + signal_fired = [False] + signal_page = [None] + def page_published_handler(sender, page, **kwargs): + signal_fired[0] = True + signal_page[0] = page + page_published.connect(page_published_handler) + # Post response = self.client.post(reverse('wagtailadmin_pages_approve_moderation', args=(self.revision.id, )), { 'foo': "Must post something or the view won't see this as a POST request", @@ -678,6 +688,11 @@ class TestApproveRejectModeration(TestCase, WagtailTestUtils): # Page must be live self.assertTrue(Page.objects.get(id=self.page.id).live) + # Check that the page_published signal was fired + self.assertTrue(signal_fired[0]) + self.assertEqual(signal_page[0], self.page) + self.assertEqual(signal_page[0], signal_page[0].specific) + def test_approve_moderation_view_bad_revision_id(self): """ This tests that the approve moderation view handles invalid revision ids correctly diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index d49864f44..f829df9c5 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -609,6 +609,7 @@ def approve_moderation(request, revision_id): if request.POST: revision.publish() + page_published.send(sender=revision.page.__class__, page=revision.page.specific) messages.success(request, _("Page '{0}' published.").format(revision.page.title)) tasks.send_notification.delay(revision.id, 'approved', request.user.id) From 02daf67daf33ee1ff706b396f92d06b676217f6f Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 30 Jun 2014 14:32:18 +0100 Subject: [PATCH 07/76] Renamed 'page' argument of page_published signal to 'instance' This is to give more consistancy with djangos built in signals --- wagtail/wagtailadmin/tests/test_pages_views.py | 12 ++++++------ wagtail/wagtailadmin/views/pages.py | 6 +++--- wagtail/wagtailcore/signals.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 1bab3b137..a26feee9c 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -174,9 +174,9 @@ class TestPageCreation(TestCase, WagtailTestUtils): # Connect a mock signal handler to page_published signal signal_fired = [False] signal_page = [None] - def page_published_handler(sender, page, **kwargs): + def page_published_handler(sender, instance, **kwargs): signal_fired[0] = True - signal_page[0] = page + signal_page[0] = instance page_published.connect(page_published_handler) # Post @@ -345,9 +345,9 @@ class TestPageEdit(TestCase, WagtailTestUtils): # Connect a mock signal handler to page_published signal signal_fired = [False] signal_page = [None] - def page_published_handler(sender, page, **kwargs): + def page_published_handler(sender, instance, **kwargs): signal_fired[0] = True - signal_page[0] = page + signal_page[0] = instance page_published.connect(page_published_handler) # Tests publish from edit page @@ -672,9 +672,9 @@ class TestApproveRejectModeration(TestCase, WagtailTestUtils): # Connect a mock signal handler to page_published signal signal_fired = [False] signal_page = [None] - def page_published_handler(sender, page, **kwargs): + def page_published_handler(sender, instance, **kwargs): signal_fired[0] = True - signal_page[0] = page + signal_page[0] = instance page_published.connect(page_published_handler) # Post diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index f829df9c5..3f41bbc2b 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -159,7 +159,7 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_ page.save_revision(user=request.user, submitted_for_moderation=is_submitting) if is_publishing: - page_published.send(sender=page_class, page=page) + page_published.send(sender=page_class, instance=page) messages.success(request, _("Page '{0}' published.").format(page.title)) elif is_submitting: messages.success(request, _("Page '{0}' submitted for moderation.").format(page.title)) @@ -240,7 +240,7 @@ def edit(request, page_id): page.save_revision(user=request.user, submitted_for_moderation=is_submitting) if is_publishing: - page_published.send(sender=page.__class__, page=page) + page_published.send(sender=page.__class__, instance=page) messages.success(request, _("Page '{0}' published.").format(page.title)) elif is_submitting: messages.success(request, _("Page '{0}' submitted for moderation.").format(page.title)) @@ -609,7 +609,7 @@ def approve_moderation(request, revision_id): if request.POST: revision.publish() - page_published.send(sender=revision.page.__class__, page=revision.page.specific) + page_published.send(sender=revision.page.__class__, instance=revision.page.specific) messages.success(request, _("Page '{0}' published.").format(revision.page.title)) tasks.send_notification.delay(revision.id, 'approved', request.user.id) diff --git a/wagtail/wagtailcore/signals.py b/wagtail/wagtailcore/signals.py index 38a0809c8..2508759c6 100644 --- a/wagtail/wagtailcore/signals.py +++ b/wagtail/wagtailcore/signals.py @@ -1,4 +1,4 @@ from django.dispatch import Signal -page_published = Signal(providing_args=['page']) +page_published = Signal(providing_args=['instance']) From 000b8abafe22cc7df66d6221b51782daa79354f4 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 19 Jun 2014 15:52:34 +0100 Subject: [PATCH 08/76] Added frontend cache purger --- .../contrib/wagtailfrontendcache/__init__.py | 0 .../contrib/wagtailfrontendcache/models.py | 0 .../contrib/wagtailfrontendcache/purger.py | 14 +++++++++++ .../wagtailfrontendcache/signal_handlers.py | 24 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 wagtail/contrib/wagtailfrontendcache/__init__.py create mode 100644 wagtail/contrib/wagtailfrontendcache/models.py create mode 100644 wagtail/contrib/wagtailfrontendcache/purger.py create mode 100644 wagtail/contrib/wagtailfrontendcache/signal_handlers.py diff --git a/wagtail/contrib/wagtailfrontendcache/__init__.py b/wagtail/contrib/wagtailfrontendcache/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/contrib/wagtailfrontendcache/models.py b/wagtail/contrib/wagtailfrontendcache/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/contrib/wagtailfrontendcache/purger.py b/wagtail/contrib/wagtailfrontendcache/purger.py new file mode 100644 index 000000000..ca00408e2 --- /dev/null +++ b/wagtail/contrib/wagtailfrontendcache/purger.py @@ -0,0 +1,14 @@ +from urlparse import urlparse, urlunparse +import requests + +from django.conf import settings + + +def purge_page_from_cache(page): + # Build purge url + varnish_url = urlparse(getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', 'http://127.0.0.1:8000/')) + page_url = urlparse(page.url) + purge_url = urlunparse((varnish_url.scheme, varnish_url.netloc, page_url.path, page_url.params, page_url.query, page_url.fragment)) + + # Purge + requests.request('PURGE', purge_url) diff --git a/wagtail/contrib/wagtailfrontendcache/signal_handlers.py b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py new file mode 100644 index 000000000..7f5dde81b --- /dev/null +++ b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py @@ -0,0 +1,24 @@ +from django.db import models +from django.db.models.signals import post_save, post_delete + +from wagtail.wagtailcore.models import Page + +from wagtail.contrib.wagtailfrontendcache import purger + + +def post_save_signal_handler(instance, **kwargs): + purger.purge_page_from_cache(instance) + + +def post_delete_signal_handler(instance, **kwargs): + purger.purge_page_from_cache(instance) + + +def register_signal_handlers(): + # Get list of models that are page types + indexed_models = [model for model in models.get_models() if issubclass(model, Page)] + + # Loop through list and register signal handlers for each one + for model in indexed_models: + post_save.connect(post_save_signal_handler, sender=model) + post_delete.connect(post_delete_signal_handler, sender=model) From cd21dbc21a862ad3042694a383fe4aaaa5148fb6 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 24 Jun 2014 11:13:30 +0100 Subject: [PATCH 09/76] Added requests as a dependency --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7b26817cd..8ee251154 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ setup( "lxml>=3.3.0", 'unicodecsv>=0.9.4', 'Unidecode>=0.04.14', + 'requests==2.3.0', "BeautifulSoup==3.2.1", # django-compressor gets confused if we have lxml but not BS3 installed ], zip_safe=False, From d667836188b49209bf766b009bb0913829e6f3ad Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 24 Jun 2014 12:17:37 +0100 Subject: [PATCH 10/76] Fixes to the 'purger' module --- .../contrib/wagtailfrontendcache/purger.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/wagtail/contrib/wagtailfrontendcache/purger.py b/wagtail/contrib/wagtailfrontendcache/purger.py index ca00408e2..250f62840 100644 --- a/wagtail/contrib/wagtailfrontendcache/purger.py +++ b/wagtail/contrib/wagtailfrontendcache/purger.py @@ -1,14 +1,33 @@ -from urlparse import urlparse, urlunparse import requests +from requests.adapters import HTTPAdapter from django.conf import settings +class CustomHTTPAdapter(HTTPAdapter): + """ + Requests will always send requests to whatever server is in the netloc + part of the URL. This is a problem with purging the cache as this netloc + may point to a different server (such as an nginx instance running in + front of the cache). + + This class allows us to send a purge request directly to the cache server + with the host header still set correctly. It does this by changing the "url" + parameter of get_connection to always point to the cache server. Requests + will then use this connection to purge the page. + """ + def __init__(self, cache_url): + self.cache_url = cache_url + super(CustomHTTPAdapter, self).__init__() + + def get_connection(self, url, proxies=None): + return super(CustomHTTPAdapter, self).get_connection(self.cache_url, proxies) + + def purge_page_from_cache(page): - # Build purge url - varnish_url = urlparse(getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', 'http://127.0.0.1:8000/')) - page_url = urlparse(page.url) - purge_url = urlunparse((varnish_url.scheme, varnish_url.netloc, page_url.path, page_url.params, page_url.query, page_url.fragment)) + varnish_url = getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', 'http://127.0.0.1:8000/') # Purge - requests.request('PURGE', purge_url) + session = requests.Session() + session.mount('http://', CustomHTTPAdapter(varnish_url)) + session.request('PURGE', page.full_url) From 9c4ac7fc3164d14ac99feb73dfbc07135db8375e Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 24 Jun 2014 12:18:44 +0100 Subject: [PATCH 11/76] Renamed 'wagtailfrontendcache.purger' to 'wagtailfrontendcache.utils' --- wagtail/contrib/wagtailfrontendcache/signal_handlers.py | 6 +++--- .../contrib/wagtailfrontendcache/{purger.py => utils.py} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename wagtail/contrib/wagtailfrontendcache/{purger.py => utils.py} (100%) diff --git a/wagtail/contrib/wagtailfrontendcache/signal_handlers.py b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py index 7f5dde81b..ce23d9839 100644 --- a/wagtail/contrib/wagtailfrontendcache/signal_handlers.py +++ b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py @@ -3,15 +3,15 @@ from django.db.models.signals import post_save, post_delete from wagtail.wagtailcore.models import Page -from wagtail.contrib.wagtailfrontendcache import purger +from wagtail.contrib.wagtailfrontendcache.utils import purge_page_from_cache def post_save_signal_handler(instance, **kwargs): - purger.purge_page_from_cache(instance) + purge_page_from_cache(instance) def post_delete_signal_handler(instance, **kwargs): - purger.purge_page_from_cache(instance) + purge_page_from_cache(instance) def register_signal_handlers(): diff --git a/wagtail/contrib/wagtailfrontendcache/purger.py b/wagtail/contrib/wagtailfrontendcache/utils.py similarity index 100% rename from wagtail/contrib/wagtailfrontendcache/purger.py rename to wagtail/contrib/wagtailfrontendcache/utils.py From 341f53511625914d14321d37ad23efb5c51c4afa Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 24 Jun 2014 12:37:37 +0100 Subject: [PATCH 12/76] Allow purging of subpaths of pages --- wagtail/contrib/wagtailfrontendcache/utils.py | 8 +++++--- wagtail/wagtailcore/models.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/wagtail/contrib/wagtailfrontendcache/utils.py b/wagtail/contrib/wagtailfrontendcache/utils.py index 250f62840..9737ebbb0 100644 --- a/wagtail/contrib/wagtailfrontendcache/utils.py +++ b/wagtail/contrib/wagtailfrontendcache/utils.py @@ -25,9 +25,11 @@ class CustomHTTPAdapter(HTTPAdapter): def purge_page_from_cache(page): + # Get session varnish_url = getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', 'http://127.0.0.1:8000/') - - # Purge session = requests.Session() session.mount('http://', CustomHTTPAdapter(varnish_url)) - session.request('PURGE', page.full_url) + + # Purge paths from cache + for path in page.get_cached_paths(): + session.request('PURGE', page.full_url + path[1:]) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index ee34ecf26..04a05a42c 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -629,13 +629,27 @@ class Page(MP_Node, ClusterableModel, Indexed): """ return self.serve(self.dummy_request()) + def get_internal_paths(self): + """ + This returns a list of paths within this page. + This is used for static sites, sitemaps and cache invalidation. + """ + return ['/'] + + def get_cached_paths(self): + """ + This returns a list of paths to invalidate in a frontend cache + """ + return self.get_internal_paths() + def get_static_site_paths(self): """ This is a generator of URL paths to feed into a static site generator Override this if you would like to create static versions of subpages """ - # Yield paths for this page - yield '/' + # Yield paths for this page + for url in self.get_internal_paths(): + yield url # Yield paths for child pages for child in self.get_children().live(): From 23a85311facc451bccbfa8fd0adaa528cc79ca0b Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 30 Jun 2014 13:49:46 +0100 Subject: [PATCH 13/76] Renamed varnish_url variable to cache_serve_url --- wagtail/contrib/wagtailfrontendcache/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/contrib/wagtailfrontendcache/utils.py b/wagtail/contrib/wagtailfrontendcache/utils.py index 9737ebbb0..b95ed7e95 100644 --- a/wagtail/contrib/wagtailfrontendcache/utils.py +++ b/wagtail/contrib/wagtailfrontendcache/utils.py @@ -26,9 +26,9 @@ class CustomHTTPAdapter(HTTPAdapter): def purge_page_from_cache(page): # Get session - varnish_url = getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', 'http://127.0.0.1:8000/') + cache_server_url = getattr(settings, 'WAGTAILFRONTENDCACHE_LOCATION', 'http://127.0.0.1:8000/') session = requests.Session() - session.mount('http://', CustomHTTPAdapter(varnish_url)) + session.mount('http://', CustomHTTPAdapter(cache_server_url)) # Purge paths from cache for path in page.get_cached_paths(): From 5709a160e6aad62bcdd8ae35c1b8bf9e8a6f7b6c Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 30 Jun 2014 14:35:07 +0100 Subject: [PATCH 14/76] Use page_published signal for cache invalidation instead of post_save --- wagtail/contrib/wagtailfrontendcache/signal_handlers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wagtail/contrib/wagtailfrontendcache/signal_handlers.py b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py index ce23d9839..0fc92d46e 100644 --- a/wagtail/contrib/wagtailfrontendcache/signal_handlers.py +++ b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py @@ -1,12 +1,13 @@ from django.db import models -from django.db.models.signals import post_save, post_delete +from django.db.models.signals import post_delete from wagtail.wagtailcore.models import Page +from wagtail.wagtailcore.signals import page_published from wagtail.contrib.wagtailfrontendcache.utils import purge_page_from_cache -def post_save_signal_handler(instance, **kwargs): +def page_published_signal_handler(instance, **kwargs): purge_page_from_cache(instance) @@ -20,5 +21,5 @@ def register_signal_handlers(): # Loop through list and register signal handlers for each one for model in indexed_models: - post_save.connect(post_save_signal_handler, sender=model) + page_published.connect(page_published_signal_handler, sender=model) post_delete.connect(post_delete_signal_handler, sender=model) From 683f1cfa64e19639820a5bf53f48e1bd72b095bc Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 10:14:34 +0100 Subject: [PATCH 15/76] Added docs for sitemaps --- docs/sitemap_generation.rst | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs/sitemap_generation.rst diff --git a/docs/sitemap_generation.rst b/docs/sitemap_generation.rst new file mode 100644 index 000000000..3308a8f4c --- /dev/null +++ b/docs/sitemap_generation.rst @@ -0,0 +1,58 @@ +Sitemap generation +================== + +This document describes how to create XML sitemaps for your Wagtail website using the ``wagtail.contrib.wagtailsitemaps`` module. + + +Basic configuration +~~~~~~~~~~~~~~~~~~~ + +You firstly need to add ``"wagtail.contrib.wagtailsitemaps"`` to INSTALLED_APPS in your Django settings file: + + .. code-block:: python + + INSTALLED_APPS = [ + ... + + "wagtail.contrib.wagtailsitemaps", + ] + + +Then, in urls.py, you need to add a link to the ``wagtail.contrib.wagtailsitemaps.views.sitemap`` view which generates the sitemap: + +.. code-block:: python + + from wagtail.contrib.wagtailsitemaps.views import sitemap + + urlpatterns = patterns('', + ... + + url('^sitemap\.xml$', sitemap), + ) + + +You should now be able to browse to "/sitemap.xml" and see the sitemap working. + + +Customising +~~~~~~~~~~~ + +URLs +---- + +The Page class defines a ``get_sitemap_urls`` method which you can override to customise sitemaps per page type. This method must return a list of dictionaries, one dictionary per URL entry in the sitemap. You can exclude pages from the sitemap by returning an empty list. + +Each dictionary can contain the following: + + - **location** (required) - This is the full URL path to add into the sitemap. + - **lastmod** - A python date or datetime set to when the page was last modified. + - **changefreq** + - **priority** + +You can add more but you will need to override the ``wagtailsitemaps/sitemap.xml`` template in order for them to be displayed in the sitemap. + + +Cache +----- + +By default, sitemaps are cached for 100 minutes. You can change this by setting ``WAGTAILSITEMAPS_CACHE_TIMEOUT`` in your Django settings to the number of seconds you would like to cache to last for. From 8daf1c6de64ae96fc2e07a84189e924cdd505ade Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 11:00:55 +0100 Subject: [PATCH 16/76] Added docs for frontend cache purging --- docs/frontend_cache_purging.rst | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 docs/frontend_cache_purging.rst diff --git a/docs/frontend_cache_purging.rst b/docs/frontend_cache_purging.rst new file mode 100644 index 000000000..1825e92d2 --- /dev/null +++ b/docs/frontend_cache_purging.rst @@ -0,0 +1,102 @@ +Frontend cache purging +====================== + +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. + + +Setting it up +~~~~~~~~~~~~~ + +Firstly, add ``"wagtail.contrib.wagtailfrontendcache"`` to your INSTALLED_APPS: + + .. code-block:: python + + INSTALLED_APPS = [ + ... + + "wagtail.contrib.wagtailfrontendcache" + ] + + +The ``wagtailfrontendcache`` module provides a set of signal handlers which will automatically purge the cache whenever a page is published or deleted. You should register these somewhere at the top of your ``urls.py`` file: + +.. code-block:: python + + # urls.py + from wagtail.contrib.wagtailfrontendcache.signal_handlers import register_signal_handlers + + register_signal_handlers() + + +You then need to set the ``WAGTAILFRONTENDCACHE_LOCATION`` setting to the URL of your Varnish/Squid cache server. This must be a direct connection to the server and cannot go through another proxy. By default, this is set to ``http://127.0.0.1:8000`` which is very likely incorrect. + +Finally, make sure you have configured your frontend cache to accept PURGE requests: + + - `Varnish `_ + - `Squid `_ + + +Advanced useage +~~~~~~~~~~~~~~~ + +Purging more than one URL per page +---------------------------------- + +By default, Wagtail will only purge one URL per page. If your page has more than one URL to be purged, you will need to override the ``get_cached_paths`` method on your page type. + +.. code-block:: python + + class BlogIndexPage(Page): + def get_blog_items(self): + # This returns a Django paginator of blog items in this section + return Paginator(self.get_children().live().type(BlogPage), 10) + + def get_cached_paths(self): + # Yield the main URL + yield '/' + + # Yield one URL per page in the paginator to make sure all pages are purged + for page_number in range(1, self.get_blog_items().num_pages): + yield '/?page=' + str(page_number) + + +Purging index pages +------------------- + +Another problem is pages that list other pages (such as a blog index) will not be purged when a blog entry gets added, changed or deleted. You may want to purge the blog index page so the updates are added into the listing quickly. + +This can be solved by using the ``purge_page_from_cache`` utility function which can be found in the ``wagtail.contrib.wagtailfrontendcache.utils`` module. + +Lets take the the above BlogIndexPage as an example. We need to register a signal handler to run when one of the BlogPages get updated/deleted. This signal handler should call the ``purge_page_from_cache`` function on all BlogIndexPages that contain the BlogPage being updated/deleted. + + +.. code-block:: python + + # models.py + from django.db.models.signals import pre_delete + + from wagtail.wagtailcore.signals import page_published + from wagtail.contrib.wagtailfrontendcache.utils import purge_page_from_cache + + + ... + + + def blog_page_changed(blog_page): + # Find all the live BlogIndexPages that contain this blog_page + for blog_index in BlogIndexPage.objects.live(): + if blog_page in blog_index.get_blog_items().object_list: + # Purge this blog index + purge_page_from_cache(blog_index) + + + @register(page_published, sender=BlogPage): + def blog_published_handler(instance): + blog_page_changed(instance) + + + @register(pre_delete, sender=BlogPage) + def blog_deleted_handler(instance): + blog_page_changed(instance) From 83161e324ff2cc5433730cd6d092690d57c863ba Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Tue, 1 Jul 2014 16:45:44 +0100 Subject: [PATCH 17/76] Updates to management commands docs With inclusion in index --- docs/index.rst | 1 + docs/management_commands.rst | 35 ++++++++++++++--------------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index a35560f82..5d2344b18 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,7 @@ It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support deploying performance static_site_generation + management_commands contributing support roadmap diff --git a/docs/management_commands.rst b/docs/management_commands.rst index e5f6393f7..3f77fc3ef 100644 --- a/docs/management_commands.rst +++ b/docs/management_commands.rst @@ -4,56 +4,49 @@ Management commands publish_scheduled_pages ----------------------- -**./manage.py publish_scheduled_pages** - -This command publishes/unpublishes pages that have had these actions scheduled by an editor. - -It is recommended to run this command once an hour. +:code:`./manage.py publish_scheduled_pages` +This command publishes or unpublishes pages that have had these actions scheduled by an editor. It is recommended to run this command once an hour. fixtree ------- -**./manage.py fixtree** +:code:`./manage.py fixtree` This command scans for errors in your database and attempts to fix any issues it finds. - move_pages ---------- -**./manage.py move_pages from to** +:code:`manage.py move_pages from to` -This mass moves a bunch of pages from one section of the tree to another. +This command moves a selection of pages from one section of the tree to another. Options: - **from** - This is id of the page to move pages from. All descendants of this page will be moved to the destination. After the operation is complete, this page will have no children. + This is the **id** of the page to move pages from. All descendants of this page will be moved to the destination. After the operation is complete, this page will have no children. - **to** - This is the id of the page to move pages to. - + This is the **id** of the page to move pages to. update_index ------------ -**./manage.py update_index** +:code:`./manage.py update_index` -This command rebuilds the search index from scratch. It is only required when using ElasticSearch. +This command rebuilds the search index from scratch. It is only required when using Elasticsearch. It is recommended to run this command once a week and at the following times: - - Whenever any pages have been created through a script (eg, import) - - Whenever any changes have been made to models or search configuration -While this command is running, the search may not return any results so avoid running this command at peak times. + - whenever any pages have been created through a script (after an import, for example) + - whenever any changes have been made to models or search configuration +The search may not return any results while this command is running, so avoid running it at peak times. search_garbage_collect ---------------------- -**./manage.py search_garbage_collect** +:code:`./manage.py search_garbage_collect` -Wagtail keeps a log of search queries that are popular on your website. On high traffic websites, this log may get big and sometimes you may want to clean out old search queries. - -This command cleans out all search query logs that are more than one week old. +Wagtail keeps a log of search queries that are popular on your website. On high traffic websites, this log may get big and you may want to clean out old search queries. This command cleans out all search query logs that are more than one week old. From 479dd1edde7061fd722fb8eb9a5da84c6875ca6b Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Tue, 1 Jul 2014 16:53:47 +0100 Subject: [PATCH 18/76] Minor editing API doc tweaks --- docs/editing_api.rst | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/docs/editing_api.rst b/docs/editing_api.rst index c32b54d35..609237e76 100644 --- a/docs/editing_api.rst +++ b/docs/editing_api.rst @@ -4,26 +4,24 @@ Defining models with the Editing API .. note:: This documentation is currently being written. - Wagtail provides a highly-customizable editing interface consisting of several components: - * **Fields** — built-in content types to augment the basic types provided by Django. + * **Fields** — built-in content types to augment the basic types provided by Django * **Panels** — the basic editing blocks for fields, groups of fields, and related object clusters * **Choosers** — interfaces for finding related objects in a ForeignKey relationship -Configuring your models to use these components will shape the Wagtail editor to your needs. Wagtail also provides an API for injecting custom CSS and Javascript for further customization, including extending the hallo.js rich text editor. +Configuring your models to use these components will shape the Wagtail editor to your needs. Wagtail also provides an API for injecting custom CSS and JavaScript for further customization, including extending the hallo.js rich text editor. There is also an Edit Handler API for creating your own Wagtail editor components. - Defining Panels ~~~~~~~~~~~~~~~ -A "panel" is the basic editing block in Wagtail. Wagtail will automatically pick the appropriate editing widget for most Django field types, you just need to add a panel for each field you want to show in the Wagtail page editor, in the order you want them to appear. +A "panel" is the basic editing block in Wagtail. Wagtail will automatically pick the appropriate editing widget for most Django field types; implementors just need to add a panel for each field they want to show in the Wagtail page editor, in the order they want them to appear. There are four basic types of panels: - + ``FieldPanel( field_name, classname=None )`` This is the panel used for basic Django field types. ``field_name`` is the name of the class property used in your model definition. ``classname`` is a string of optional CSS classes given to the panel which are used in formatting and scripted interactivity. By default, panels are formatted as inset fields. The CSS class ``full`` can be used to format the panel so it covers the full width of the Wagtail page editor. The CSS class ``title`` can be used to mark a field as the source for auto-generated slug strings. @@ -34,18 +32,17 @@ There are four basic types of panels: This panel allows for the creation of a "cluster" of related objects over a join to a separate model, such as a list of related links or slides to an image carousel. This is a very powerful, but tricky feature which will take some space to cover, so we'll skip over it for now. For a full explanation on the usage of ``InlinePanel``, see :ref:`inline_panels`. ``FieldRowPanel( children, classname=None)`` - This panel is purely aesthetic. It creates a columnar layout in the editing interface, where each of the child Panels appears alongside each others rather than below. Use of FieldRowPanel particularly helps reduce the "snow-blindness" effect of seeing so many fields on the page, for complex models. It also improves the perceived association between fields of a similar nature. For example if you created a model representing an "Event" which had a starting date and ending date, it would be intuitive to find the start and end date on the same "row". + This panel is purely aesthetic. It creates a columnar layout in the editing interface, where each of the child Panels appears alongside each other rather than below. Use of FieldRowPanel particularly helps reduce the "snow-blindness" effect of seeing so many fields on the page, for complex models. It also improves the perceived association between fields of a similar nature. For example if you created a model representing an "Event" which had a starting date and ending date, it may be intuitive to find the start and end date on the same "row". FieldRowPanel should be used in combination with ``col*`` classnames added to each of the child Panels of the FieldRowPanel. The Wagtail editing interface is layed out using a grid system, in which the maximum width of the editor is 12 columns wide. Classes ``col1``-``col12`` can be applied to each child of a FieldRowPanel. The class ``col3`` will ensure that field appears 3 columns wide or a quarter the width. ``col4`` would cause the field to be 4 columns wide, or a third the width. **(In addition to these four, there are also Chooser Panels, detailed below.)** - Wagtail provides a tabbed interface to help organize panels. Three such tabs are provided: -* ``content_panels`` is the main tab, used for the bulk of your model's fields. -* ``promote_panels`` is suggested for organizing fields regarding the promotion of the page around the site and the internet e.g A field to dictate whether the page should show in site-wide menus, descriptive text that should appear in site search results, SEO friendly titles, OpenGraph meta tag content and other machine-readable information. -* ``settings_panels`` is essentially for non-copy fields. By default it contains the page's scheduled publishing fields. Other suggested fields e.g: a field to switch between one layout/style and another. +* ``content_panels`` is the main tab, used for the bulk of your model's fields. +* ``promote_panels`` is suggested for organizing fields regarding the promotion of the page around the site and the Internet. For example, a field to dictate whether the page should show in site-wide menus, descriptive text that should appear in site search results, SEO-friendly titles, OpenGraph meta tag content and other machine-readable information. +* ``settings_panels`` is essentially for non-copy fields. By default it contains the page's scheduled publishing fields. Other suggested fields could include a field to switch between one layout/style and another. Let's look at an example of a panel definition: @@ -70,7 +67,7 @@ Let's look at an example of a panel definition: FieldRowPanel([ FieldPanel('start_date', classname="col3"), FieldPanel('end_date', classname="col3"), - ]), + ]), ImageChooserPanel('splash_image'), DocumentChooserPanel('free_download'), PageChooserPanel('related_page'), @@ -134,7 +131,7 @@ One of the features of Wagtail is a unified image library, which you can access on_delete=models.SET_NULL, related_name='+' ) - + BookPage.content_panels = [ ImageChooserPanel('cover'), # ... @@ -240,7 +237,7 @@ Snippets are vanilla Django models you create yourself without a Wagtail-provide on_delete=models.SET_NULL, related_name='+' ) - + BookPage.content_panels = [ SnippetChooserPanel('advert', Advert), # ... @@ -387,11 +384,9 @@ hallo.js plugin names are prefixed with the ``"IKS."`` namespace, but the ``name For information on developing custom hallo.js plugins, see the project's page: https://github.com/bergie/hallo - Edit Handler API ~~~~~~~~~~~~~~~~ - Admin Hooks ----------- @@ -505,7 +500,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func .. _construct_main_menu: ``construct_main_menu`` - Add, remove, or alter ``MenuItem`` objects from the Wagtail admin menu. The callable passed to this hook must take a ``request`` object and a list of ``menu_items``; it must return a list of menu items. New items can be constructed from the ``MenuItem`` class by passing in: a ``label`` which will be the text in the menu item, the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up), CSS class ``name`` applied to the wrapping ``
  • `` of the menu item as ``"menu-{name}"``, CSS ``classnames`` which are used to give the link an icon, and an ``order`` integer which determine's the item's place in the menu. + Add, remove, or alter ``MenuItem`` objects from the Wagtail admin menu. The callable passed to this hook must take a ``request`` object and a list of ``menu_items``; it must return a list of menu items. New items can be constructed from the ``MenuItem`` class by passing in: a ``label`` which will be the text in the menu item, the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up), CSS class ``name`` applied to the wrapping ``
  • `` of the menu item as ``"menu-{name}"``, CSS ``classnames`` which are used to give the link an icon, and an ``order`` integer which determine's the item's place in the menu. .. code-block:: python @@ -632,4 +627,3 @@ Custom Choosers Tests ----- - From bdf175ae41ebcd15180aa46ebf305c1dc4c8003d Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 09:52:55 +0100 Subject: [PATCH 19/76] fixed: generated content font size bug in IE --- .../wagtailadmin/static/wagtailadmin/scss/components/forms.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss index 75f46b262..2fa0827b8 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss @@ -476,7 +476,7 @@ li.focused > .help{ &.field-small{ .input{ &:before, &:after{ - font-size:1.5em; + font-size:1.3rem; /* REMs are necessary here because IE doesn't treat generated content correctly */ top:0.3em; } &:before{ From 7e65a4d578aa39fd8b88e51a7da1c2a0f15f7f9f Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 10:08:31 +0100 Subject: [PATCH 20/76] button metrics at mobile improved --- .../wagtailadmin/scss/components/forms.scss | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss index 2fa0827b8..aa8968f04 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss @@ -264,7 +264,7 @@ input[type=submit], input[type=reset], input[type=button], .button, button{ left:0; top:0; width:2em; - line-height:2em; + line-height:1.85em; height:100%; text-align:center; background-color:rgba(0,0,0,0.2); @@ -278,7 +278,7 @@ input[type=submit], input[type=reset], input[type=button], .button, button{ &:before{ width:2em; font-size:0.9rem; - line-height:2em; + line-height:1.65em; } } @@ -303,13 +303,6 @@ input[type=submit], input[type=reset], input[type=button], button{ } } -button.icon{ - &:before, - &:after{ - line-height:0; - } -} - .multiple{ @include transition(max-height 10s ease); padding:0; @@ -778,11 +771,12 @@ input[type=submit], input[type=reset], input[type=button], .button, button{ } &.bicolor{ - padding-left:3.5em; + padding-left:3.7em; &:before{ - width:2.2em; - line-height:2.45em; + width:2em; + line-height:2.2em; + font-size:1.1rem; } } From 7ddcf0fbaceb32707f547d7580b25af0cc5d5f59 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 10:17:21 +0100 Subject: [PATCH 21/76] Fixing #402, missing chooser button. Field inherited wrong template. --- .../wagtailsearch/editorspicks/includes/editorspicks_form.html | 2 +- .../templates/wagtailsearch/queries/chooser_field.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html index 01e891ccd..9a45dbef0 100644 --- a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html +++ b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_form.html @@ -17,7 +17,7 @@ {% endif %}
  • - {% include "wagtailadmin/edit_handlers/field_panel_field.html" with field=form.description only %} + {% include "wagtailadmin/shared/field.html" with field=form.description only %}
  • diff --git a/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser_field.html b/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser_field.html index 317ba89d9..26ffa2959 100644 --- a/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser_field.html +++ b/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser_field.html @@ -1,4 +1,4 @@ -{% extends "wagtailadmin/edit_handlers/field_panel_field.html" %} +{% extends "wagtailadmin/shared/field.html" %} {% block form_field %}
    From f3a70c9f297cd5c48c6a4e91429f5999e578f3da Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 10:21:28 +0100 Subject: [PATCH 22/76] correctly formatted query chooser modal content --- .../wagtailsearch/queries/chooser/chooser.html | 17 +++++++++-------- .../wagtailsearch/queries/chooser/results.html | 7 +++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/chooser.html b/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/chooser.html index b78f94b60..4859c8c1c 100644 --- a/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/chooser.html +++ b/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/chooser.html @@ -2,15 +2,16 @@ {% trans "Popular search terms" as pop_str %} {% include "wagtailadmin/shared/header.html" with title=pop_str %} -
    + +
    {% include "wagtailsearch/queries/chooser/results.html" %}
    diff --git a/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/results.html b/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/results.html index 78de1e937..a9e25e641 100644 --- a/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/results.html +++ b/wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/results.html @@ -1,8 +1,7 @@ {% load i18n %} - - - + + @@ -20,7 +19,7 @@ {% endfor %} {% else %} - + {% endif %}
    {% trans "Terms" %}

    {% trans "No results found" %}

    {% trans "No results found" %}

    From 1cc258f404e81d3114897fdfee293f3ba8c04f20 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 14:35:10 +0100 Subject: [PATCH 23/76] Don't install unicodecsv on Python 3 and fallback to pythons built in csv library it it isn't installed. --- setup.py | 44 +++++++++++++++++++++++------------ wagtail/wagtailforms/views.py | 14 +++++++++-- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index 7b26817cd..d4c750a19 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,8 @@ #!/usr/bin/env python +import sys + + try: from setuptools import setup, find_packages except ImportError: @@ -16,6 +19,31 @@ except ImportError: pass +PY3 = sys.version_info[0] == 3 + + +install_requires = [ + "Django>=1.6.2,<1.7", + "South>=0.8.4", + "django-compressor>=1.3", + "django-libsass>=0.1", + "django-modelcluster>=0.1", + "django-taggit==0.11.2", + "django-treebeard==2.0", + "Pillow>=2.3.0", + "beautifulsoup4>=4.3.2", + "lxml>=3.3.0", + "Unidecode>=0.04.14", + "BeautifulSoup==3.2.1", # django-compressor gets confused if we have lxml but not BS3 installed +] + + +if not PY3: + install_requires += [ + "unicodecsv>=0.9.4" + ] + + setup( name='wagtail', version='0.3.1', @@ -40,20 +68,6 @@ setup( 'Framework :: Django', 'Topic :: Internet :: WWW/HTTP :: Site Management', ], - install_requires=[ - "Django>=1.6.2,<1.7", - "South>=0.8.4", - "django-compressor>=1.3", - "django-libsass>=0.1", - "django-modelcluster>=0.1", - "django-taggit==0.11.2", - "django-treebeard==2.0", - "Pillow>=2.3.0", - "beautifulsoup4>=4.3.2", - "lxml>=3.3.0", - 'unicodecsv>=0.9.4', - 'Unidecode>=0.04.14', - "BeautifulSoup==3.2.1", # django-compressor gets confused if we have lxml but not BS3 installed - ], + install_requires=install_requires, zip_safe=False, ) diff --git a/wagtail/wagtailforms/views.py b/wagtail/wagtailforms/views.py index 2174642c6..f0dce9010 100644 --- a/wagtail/wagtailforms/views.py +++ b/wagtail/wagtailforms/views.py @@ -1,5 +1,11 @@ import datetime -import unicodecsv + +try: + import unicodecsv as csv + using_unicodecsv = True +except ImportError: + import csv + using_unicodecsv = False from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.exceptions import PermissionDenied @@ -65,7 +71,11 @@ def list_submissions(request, page_id): # return a CSV instead response = HttpResponse(content_type='text/csv; charset=utf-8') response['Content-Disposition'] = 'attachment;filename=export.csv' - writer = unicodecsv.writer(response, encoding='utf-8') + + if using_unicodecsv: + writer = csv.writer(response, encoding='utf-8') + else: + writer = csv.writer(response) header_row = ['Submission date'] + [label for name, label in data_fields] From 85a68a34f1812d11eb36cc9f3899206b61d21687 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 10:22:08 +0100 Subject: [PATCH 24/76] Requirements changes - Removed Beautiful Soup 3 - Added six --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d4c750a19..fe8673f28 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ install_requires = [ "beautifulsoup4>=4.3.2", "lxml>=3.3.0", "Unidecode>=0.04.14", - "BeautifulSoup==3.2.1", # django-compressor gets confused if we have lxml but not BS3 installed + "six==1.7.3", ] From cf15e903900ec169554dd2d5da527d5681b485c9 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 14:55:33 +0100 Subject: [PATCH 25/76] Fixed absolute import issues --- wagtail/wagtailimages/backends/__init__.py | 10 +++++++--- wagtail/wagtailimages/backends/base.py | 5 ----- wagtail/wagtailredirects/middleware.py | 2 +- wagtail/wagtailredirects/views.py | 2 +- wagtail/wagtailsearch/__init__.py | 6 +++--- wagtail/wagtailsearch/backends/__init__.py | 9 +++++++-- wagtail/wagtailsearch/backends/base.py | 5 ----- wagtail/wagtailsearch/forms.py | 3 ++- wagtail/wagtailsearch/views/__init__.py | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/wagtail/wagtailimages/backends/__init__.py b/wagtail/wagtailimages/backends/__init__.py index 62d0c1c78..e60b89688 100644 --- a/wagtail/wagtailimages/backends/__init__.py +++ b/wagtail/wagtailimages/backends/__init__.py @@ -8,11 +8,15 @@ except ImportError: # for Python 2.6, fall back on django.utils.importlib (deprecated as of Django 1.7) from django.utils.importlib import import_module -from django.utils import six import sys -from django.conf import settings -from base import InvalidImageBackendError +from django.utils import six +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + + +class InvalidImageBackendError(ImproperlyConfigured): + pass # Pinched from django 1.7 source code. # TODO: Replace this with "from django.utils.module_loading import import_string" diff --git a/wagtail/wagtailimages/backends/base.py b/wagtail/wagtailimages/backends/base.py index ce07420e3..7024c358d 100644 --- a/wagtail/wagtailimages/backends/base.py +++ b/wagtail/wagtailimages/backends/base.py @@ -1,9 +1,4 @@ from django.conf import settings -from django.core.exceptions import ImproperlyConfigured - - -class InvalidImageBackendError(ImproperlyConfigured): - pass class BaseImageBackend(object): diff --git a/wagtail/wagtailredirects/middleware.py b/wagtail/wagtailredirects/middleware.py index 6fdd6fa27..e65d1be98 100644 --- a/wagtail/wagtailredirects/middleware.py +++ b/wagtail/wagtailredirects/middleware.py @@ -1,6 +1,6 @@ from django import http -import models +from wagtail.wagtailredirects import models # Originally pinched from: https://github.com/django/django/blob/master/django/contrib/redirects/middleware.py diff --git a/wagtail/wagtailredirects/views.py b/wagtail/wagtailredirects/views.py index 8dfd687f1..2a57ae57b 100644 --- a/wagtail/wagtailredirects/views.py +++ b/wagtail/wagtailredirects/views.py @@ -8,7 +8,7 @@ from django.views.decorators.vary import vary_on_headers from wagtail.wagtailadmin.edit_handlers import ObjectList from wagtail.wagtailadmin.forms import SearchForm -import models +from wagtail.wagtailredirects import models REDIRECT_EDIT_HANDLER = ObjectList(models.Redirect.content_panels) diff --git a/wagtail/wagtailsearch/__init__.py b/wagtail/wagtailsearch/__init__.py index 2b074bd9c..ed896f445 100644 --- a/wagtail/wagtailsearch/__init__.py +++ b/wagtail/wagtailsearch/__init__.py @@ -1,3 +1,3 @@ -from indexed import Indexed -from signal_handlers import register_signal_handlers -from backends import get_search_backend \ No newline at end of file +from wagtail.wagtailsearch.indexed import Indexed +from wagtail.wagtailsearch.signal_handlers import register_signal_handlers +from wagtail.wagtailsearch.backends import get_search_backend \ No newline at end of file diff --git a/wagtail/wagtailsearch/backends/__init__.py b/wagtail/wagtailsearch/backends/__init__.py index 5e1b4fc5d..cf33cf5c1 100644 --- a/wagtail/wagtailsearch/backends/__init__.py +++ b/wagtail/wagtailsearch/backends/__init__.py @@ -8,10 +8,15 @@ except ImportError: # for Python 2.6, fall back on django.utils.importlib (deprecated as of Django 1.7) from django.utils.importlib import import_module -from django.utils import six import sys + +from django.utils import six from django.conf import settings -from base import InvalidSearchBackendError +from django.core.exceptions import ImproperlyConfigured + + +class InvalidSearchBackendError(ImproperlyConfigured): + pass # Pinched from django 1.7 source code. diff --git a/wagtail/wagtailsearch/backends/base.py b/wagtail/wagtailsearch/backends/base.py index 689be7bbe..393ecae1b 100644 --- a/wagtail/wagtailsearch/backends/base.py +++ b/wagtail/wagtailsearch/backends/base.py @@ -1,13 +1,8 @@ from django.db import models -from django.core.exceptions import ImproperlyConfigured from wagtail.wagtailsearch.indexed import Indexed -class InvalidSearchBackendError(ImproperlyConfigured): - pass - - class BaseSearch(object): def __init__(self, params): pass diff --git a/wagtail/wagtailsearch/forms.py b/wagtail/wagtailsearch/forms.py index 9485e4619..aaffad052 100644 --- a/wagtail/wagtailsearch/forms.py +++ b/wagtail/wagtailsearch/forms.py @@ -1,7 +1,8 @@ from django import forms from django.forms.models import inlineformset_factory from django.utils.translation import ugettext_lazy as _ -import models + +from wagtail.wagtailsearch import models class QueryForm(forms.Form): diff --git a/wagtail/wagtailsearch/views/__init__.py b/wagtail/wagtailsearch/views/__init__.py index 46e2526f6..7f39327bd 100644 --- a/wagtail/wagtailsearch/views/__init__.py +++ b/wagtail/wagtailsearch/views/__init__.py @@ -1 +1 @@ -from frontend import search \ No newline at end of file +from wagtail.wagtailsearch.views.frontend import search \ No newline at end of file From c90d61fa2acb4d58df1d0f29c1ae1120884fb93c Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 14:50:21 +0100 Subject: [PATCH 26/76] Use six to import urllib and StringIO --- wagtail/wagtailadmin/templatetags/gravatar.py | 5 +++-- wagtail/wagtailcore/models.py | 5 +++-- .../tests/test_management_commands.py | 3 ++- wagtail/wagtailcore/tests/test_page_model.py | 2 +- .../tests/test_page_permissions.py | 2 +- .../wagtailcore/tests/test_page_queryset.py | 2 +- wagtail/wagtailcore/whitelist.py | 3 ++- wagtail/wagtailembeds/embeds.py | 16 ++++++++++------ wagtail/wagtailembeds/tests.py | 19 ++++++++++--------- wagtail/wagtailimages/models.py | 5 +++-- wagtail/wagtailimages/tests.py | 4 ++-- wagtail/wagtailredirects/models.py | 2 +- wagtail/wagtailsearch/tests/test_backends.py | 4 +++- wagtail/wagtailsearch/tests/test_queries.py | 2 +- 14 files changed, 43 insertions(+), 31 deletions(-) diff --git a/wagtail/wagtailadmin/templatetags/gravatar.py b/wagtail/wagtailadmin/templatetags/gravatar.py index 5c69f4fd2..15c161015 100644 --- a/wagtail/wagtailadmin/templatetags/gravatar.py +++ b/wagtail/wagtailadmin/templatetags/gravatar.py @@ -8,9 +8,10 @@ ### ### just make sure to update the "default" image path below -import urllib import hashlib +from six.moves.urllib.parse import urlencode + from django import template register = template.Library() @@ -31,7 +32,7 @@ class GravatarUrlNode(template.Node): size = int(self.size) * 2 # requested at retina size by default and scaled down at point of use with css gravatar_url = "//www.gravatar.com/avatar/" + hashlib.md5(email.lower()).hexdigest() + "?" - gravatar_url += urllib.urlencode({'s': str(size), 'd': default}) + gravatar_url += urlencode({'s': str(size), 'd': default}) return gravatar_url diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 95730e334..77691e1b9 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -1,7 +1,8 @@ -from StringIO import StringIO -from urlparse import urlparse import warnings +from six import StringIO +from six.moves.urllib.parse import urlparse + from modelcluster.models import ClusterableModel from django.db import models, connection, transaction diff --git a/wagtail/wagtailcore/tests/test_management_commands.py b/wagtail/wagtailcore/tests/test_management_commands.py index ae19167c2..1e3ff28e1 100644 --- a/wagtail/wagtailcore/tests/test_management_commands.py +++ b/wagtail/wagtailcore/tests/test_management_commands.py @@ -1,6 +1,7 @@ -from StringIO import StringIO from datetime import timedelta +from six import StringIO + from django.test import TestCase, Client from django.http import HttpRequest, Http404 from django.core import management diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index 4cbdd9993..58c359d72 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -1,4 +1,4 @@ -from StringIO import StringIO +from six import StringIO from django.test import TestCase, Client from django.http import HttpRequest, Http404 diff --git a/wagtail/wagtailcore/tests/test_page_permissions.py b/wagtail/wagtailcore/tests/test_page_permissions.py index 111b059e6..eb2fe3257 100644 --- a/wagtail/wagtailcore/tests/test_page_permissions.py +++ b/wagtail/wagtailcore/tests/test_page_permissions.py @@ -1,4 +1,4 @@ -from StringIO import StringIO +from six import StringIO from django.test import TestCase, Client from django.http import HttpRequest, Http404 diff --git a/wagtail/wagtailcore/tests/test_page_queryset.py b/wagtail/wagtailcore/tests/test_page_queryset.py index 57599173c..2c4e46c9c 100644 --- a/wagtail/wagtailcore/tests/test_page_queryset.py +++ b/wagtail/wagtailcore/tests/test_page_queryset.py @@ -1,4 +1,4 @@ -from StringIO import StringIO +from six import StringIO from django.test import TestCase, Client from django.http import HttpRequest, Http404 diff --git a/wagtail/wagtailcore/whitelist.py b/wagtail/wagtailcore/whitelist.py index 9a5f67cf1..d82a2b2cd 100644 --- a/wagtail/wagtailcore/whitelist.py +++ b/wagtail/wagtailcore/whitelist.py @@ -2,8 +2,9 @@ A generic HTML whitelisting engine, designed to accommodate subclassing to override specific rules. """ +from six.moves.urllib.parse import urlparse + from bs4 import BeautifulSoup, NavigableString, Tag -from urlparse import urlparse ALLOWED_URL_SCHEMES = ['', 'http', 'https', 'ftp', 'mailto', 'tel'] diff --git a/wagtail/wagtailembeds/embeds.py b/wagtail/wagtailembeds/embeds.py index 2ec974d8a..ebc3e1491 100644 --- a/wagtail/wagtailembeds/embeds.py +++ b/wagtail/wagtailembeds/embeds.py @@ -1,4 +1,5 @@ import sys +from datetime import datetime try: from importlib import import_module @@ -6,13 +7,16 @@ except ImportError: # for Python 2.6, fall back on django.utils.importlib (deprecated as of Django 1.7) from django.utils.importlib import import_module +from six.moves.urllib.request import urlopen, Request +from six.moves.urllib.error import URLError +from six.moves.urllib.parse import urlencode + from django.conf import settings -from datetime import datetime from django.utils import six + from wagtail.wagtailembeds.oembed_providers import get_oembed_provider from wagtail.wagtailembeds.models import Embed -import urllib2, urllib -import json + class EmbedNotFoundException(Exception): pass @@ -99,11 +103,11 @@ def oembed(url, max_width=None): params['maxwidth'] = max_width # Perform request - request = urllib2.Request(provider + '?' + urllib.urlencode(params)) + request = Request(provider + '?' + urlencode(params)) request.add_header('User-agent', 'Mozilla/5.0') try: - r = urllib2.urlopen(request) - except urllib2.URLError: + r = urlopen(request) + except URLError: raise EmbedNotFoundException oembed = json.loads(r.read()) diff --git a/wagtail/wagtailembeds/tests.py b/wagtail/wagtailembeds/tests.py index 405566981..cc67e6b9b 100644 --- a/wagtail/wagtailembeds/tests.py +++ b/wagtail/wagtailembeds/tests.py @@ -1,5 +1,6 @@ +from six.moves.urllib.request import urlopen + from mock import patch -import urllib2 try: import embedly @@ -222,7 +223,7 @@ class TestOembed(TestCase): self.assertRaises(EmbedNotFoundException, wagtail_oembed, "http://www.youtube.com/watch/") - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_oembed_photo_request(self, loads, urlopen) : urlopen.return_value = self.dummy_response @@ -233,7 +234,7 @@ class TestOembed(TestCase): self.assertEqual(result['html'], '') loads.assert_called_with("foo") - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_oembed_return_values(self, loads, urlopen): urlopen.return_value = self.dummy_response @@ -268,7 +269,7 @@ class TestEmbedFilter(TestCase): return "foo" self.dummy_response = DummyResponse() - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_valid_embed(self, loads, urlopen): urlopen.return_value = self.dummy_response @@ -277,7 +278,7 @@ class TestEmbedFilter(TestCase): result = embed_filter('http://www.youtube.com/watch/') self.assertEqual(result, '') - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopenn') @patch('json.loads') def test_render_filter(self, loads, urlopen): urlopen.return_value = self.dummy_response @@ -288,7 +289,7 @@ class TestEmbedFilter(TestCase): result = temp.render(context) self.assertEqual(result, '') - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_render_filter_nonexistent_type(self, loads, urlopen): urlopen.return_value = self.dummy_response @@ -307,7 +308,7 @@ class TestEmbedlyFilter(TestEmbedFilter): return "foo" self.dummy_response = DummyResponse() - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_valid_embed(self, loads, urlopen): urlopen.return_value = self.dummy_response @@ -316,7 +317,7 @@ class TestEmbedlyFilter(TestEmbedFilter): result = embedly_filter('http://www.youtube.com/watch/') self.assertEqual(result, '') - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_render_filter(self, loads, urlopen): urlopen.return_value = self.dummy_response @@ -327,7 +328,7 @@ class TestEmbedlyFilter(TestEmbedFilter): result = temp.render(context) self.assertEqual(result, '') - @patch('urllib2.urlopen') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_render_filter_nonexistent_type(self, loads, urlopen): urlopen.return_value = self.dummy_response diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 3ff2f9782..fe898b4d6 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -1,7 +1,8 @@ -import StringIO import os.path import re +from six import BytesIO + from taggit.managers import TaggableManager from django.core.files import File @@ -208,7 +209,7 @@ class Filter(models.Model): image = method(image, self.method_arg) - output = StringIO.StringIO() + output = BytesIO() backend.save_image(image, output, file_format) # and then close the input file diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index 5b7b2fd14..af4f305e7 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -18,11 +18,11 @@ from wagtail.wagtailimages.backends import get_image_backend from wagtail.wagtailimages.backends.pillow import PillowBackend def get_test_image_file(): - from StringIO import StringIO + from six import BytesIO from PIL import Image from django.core.files.images import ImageFile - f = StringIO() + f = BytesIO() image = Image.new('RGB', (640, 480), 'white') image.save(f, 'PNG') return ImageFile(f, name='test.png') diff --git a/wagtail/wagtailredirects/models.py b/wagtail/wagtailredirects/models.py index 6440ad383..d04b4911e 100644 --- a/wagtail/wagtailredirects/models.py +++ b/wagtail/wagtailredirects/models.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, PageChooserPanel -from urlparse import urlparse +from six.moves.urllib.parse import urlparse class Redirect(models.Model): diff --git a/wagtail/wagtailsearch/tests/test_backends.py b/wagtail/wagtailsearch/tests/test_backends.py index fa7b4d9fa..e8a175f21 100644 --- a/wagtail/wagtailsearch/tests/test_backends.py +++ b/wagtail/wagtailsearch/tests/test_backends.py @@ -1,12 +1,14 @@ +from six import StringIO + from django.test import TestCase from django.test.utils import override_settings from django.conf import settings from django.core import management + from wagtail.tests.utils import unittest from wagtail.wagtailsearch import models, get_search_backend from wagtail.wagtailsearch.backends.db import DBSearch from wagtail.wagtailsearch.backends import InvalidSearchBackendError -from StringIO import StringIO # Register wagtailsearch signal handlers diff --git a/wagtail/wagtailsearch/tests/test_queries.py b/wagtail/wagtailsearch/tests/test_queries.py index fb47478a2..10785187e 100644 --- a/wagtail/wagtailsearch/tests/test_queries.py +++ b/wagtail/wagtailsearch/tests/test_queries.py @@ -1,4 +1,4 @@ -from StringIO import StringIO +from six import StringIO from django.test import TestCase from django.core import management From 600de852d8b47a90f18611edc5767a40cdb4362e Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 27 Mar 2014 15:00:17 +0000 Subject: [PATCH 27/76] Use six.with_metaclass to add metaclass to Page --- wagtail/wagtailcore/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 77691e1b9..aeb41d732 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -1,5 +1,6 @@ import warnings +import six from six import StringIO from six.moves.urllib.parse import urlparse @@ -256,9 +257,7 @@ class PageBase(models.base.ModelBase): PAGE_MODEL_CLASSES.append(cls) -class Page(MP_Node, ClusterableModel, Indexed): - __metaclass__ = PageBase - +class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)): title = models.CharField(max_length=255, help_text=_("The page title as you'd like it to be seen by the public")) slug = models.SlugField(help_text=_("The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/")) # TODO: enforce uniqueness on slug field per parent (will have to be done at the Django From 688a9f1c6875160d9d3ff459ee69ba3b9768b8fb Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 15:40:00 +0100 Subject: [PATCH 28/76] Replaced occurances of 'unicode' with 'six.text_type' --- wagtail/wagtailadmin/edit_handlers.py | 4 +++- wagtail/wagtailadmin/menu.py | 4 +++- wagtail/wagtailcore/whitelist.py | 2 +- wagtail/wagtailsnippets/views/chooser.py | 4 +++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/wagtail/wagtailadmin/edit_handlers.py b/wagtail/wagtailadmin/edit_handlers.py index ab776061b..d7b4b5528 100644 --- a/wagtail/wagtailadmin/edit_handlers.py +++ b/wagtail/wagtailadmin/edit_handlers.py @@ -2,6 +2,8 @@ import copy import re import datetime +from six import text_type + from taggit.forms import TagWidget from modelcluster.forms import ClusterForm, ClusterFormMetaclass @@ -245,7 +247,7 @@ class EditHandler(object): """ rendered_fields = self.rendered_fields() missing_fields_html = [ - unicode(self.form[field_name]) + text_type(self.form[field_name]) for field_name in self.form.fields if field_name not in rendered_fields ] diff --git a/wagtail/wagtailadmin/menu.py b/wagtail/wagtailadmin/menu.py index b99e3b0ee..dcc241baa 100644 --- a/wagtail/wagtailadmin/menu.py +++ b/wagtail/wagtailadmin/menu.py @@ -1,3 +1,5 @@ +from six import text_type + from django.utils.text import slugify from django.utils.html import format_html @@ -7,7 +9,7 @@ class MenuItem(object): self.label = label self.url = url self.classnames = classnames - self.name = (name or slugify(unicode(label))) + self.name = (name or slugify(text_type(label))) self.order = order def render_html(self): diff --git a/wagtail/wagtailcore/whitelist.py b/wagtail/wagtailcore/whitelist.py index d82a2b2cd..0859ccfa8 100644 --- a/wagtail/wagtailcore/whitelist.py +++ b/wagtail/wagtailcore/whitelist.py @@ -83,7 +83,7 @@ class Whitelister(object): attributes""" doc = BeautifulSoup(html, 'lxml') cls.clean_node(doc, doc) - return unicode(doc) + return doc.decode() @classmethod def clean_node(cls, doc, node): diff --git a/wagtail/wagtailsnippets/views/chooser.py b/wagtail/wagtailsnippets/views/chooser.py index f7905e025..478c66a60 100644 --- a/wagtail/wagtailsnippets/views/chooser.py +++ b/wagtail/wagtailsnippets/views/chooser.py @@ -1,5 +1,7 @@ import json +from six import text_type + from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import permission_required @@ -35,7 +37,7 @@ def chosen(request, content_type_app_name, content_type_model_name, id): snippet_json = json.dumps({ 'id': item.id, - 'string': unicode(item), + 'string': text_type(item), }) return render_modal_workflow( From 409e6e781b38f990a69a47b8850af03a2e14caca Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 15:53:53 +0100 Subject: [PATCH 29/76] Replaced occurances of 'basestring' with 'six.string_types' --- wagtail/wagtailadmin/edit_handlers.py | 3 ++- wagtail/wagtailcore/models.py | 3 ++- wagtail/wagtailsearch/indexed.py | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/wagtail/wagtailadmin/edit_handlers.py b/wagtail/wagtailadmin/edit_handlers.py index d7b4b5528..638072fa8 100644 --- a/wagtail/wagtailadmin/edit_handlers.py +++ b/wagtail/wagtailadmin/edit_handlers.py @@ -2,6 +2,7 @@ import copy import re import datetime +from six import string_types from six import text_type from taggit.forms import TagWidget @@ -485,7 +486,7 @@ class BasePageChooserPanel(BaseChooserPanel): def target_content_type(cls): if cls._target_content_type is None: if cls.page_type: - if isinstance(cls.page_type, basestring): + if isinstance(cls.page_type, string_types): # translate the passed model name into an actual model class from django.db.models import get_model try: diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index aeb41d732..a73c898e5 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -1,6 +1,7 @@ import warnings import six +from six import string_types from six import StringIO from six.moves.urllib.parse import urlparse @@ -536,7 +537,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)): else: res = [] for page_type in cls.subpage_types: - if isinstance(page_type, basestring): + if isinstance(page_type, string_types): try: app_label, model_name = page_type.split(".") except ValueError: diff --git a/wagtail/wagtailsearch/indexed.py b/wagtail/wagtailsearch/indexed.py index aa750d7b9..dfed7799a 100644 --- a/wagtail/wagtailsearch/indexed.py +++ b/wagtail/wagtailsearch/indexed.py @@ -1,3 +1,5 @@ +from six import string_types + from django.db import models @@ -37,7 +39,7 @@ class Indexed(object): indexed_fields = cls.indexed_fields if isinstance(indexed_fields, tuple): indexed_fields = list(indexed_fields) - if isinstance(indexed_fields, basestring): + if isinstance(indexed_fields, string_types): indexed_fields = [indexed_fields] if isinstance(indexed_fields, list): indexed_fields = dict((field, dict(type="string")) for field in indexed_fields) From 0272411222a93b022f7975e04a02731fe70f6a3a Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 15:57:19 +0100 Subject: [PATCH 30/76] Replaced occurances of 'dict.iteritems' with 'dict.items' --- wagtail/wagtailadmin/views/pages.py | 2 +- wagtail/wagtailsearch/tests/test_backends.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index fd0ed85b2..29585717f 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -343,7 +343,7 @@ def edit(request, page_id): edit_handler = edit_handler_class(instance=page, form=form) errors_debug = ( repr(edit_handler.form.errors) - + repr([(name, formset.errors) for (name, formset) in edit_handler.form.formsets.iteritems() if formset.errors]) + + repr([(name, formset.errors) for (name, formset) in edit_handler.form.formsets.items() if formset.errors]) ) else: form = form_class(instance=page) diff --git a/wagtail/wagtailsearch/tests/test_backends.py b/wagtail/wagtailsearch/tests/test_backends.py index e8a175f21..8fab1d152 100644 --- a/wagtail/wagtailsearch/tests/test_backends.py +++ b/wagtail/wagtailsearch/tests/test_backends.py @@ -21,7 +21,7 @@ class BackendTests(object): def setUp(self): # Search WAGTAILSEARCH_BACKENDS for an entry that uses the given backend path - for (backend_name, backend_conf) in settings.WAGTAILSEARCH_BACKENDS.iteritems(): + for backend_name, backend_conf in settings.WAGTAILSEARCH_BACKENDS.items(): if backend_conf['BACKEND'] == self.backend_path: self.backend = get_search_backend(backend_name) break From b56c18cb19005c1e42006f440b92514e50326626 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 16:44:19 +0100 Subject: [PATCH 31/76] Replaced occurance of 'xrange' with 'range' --- wagtail/wagtailsearch/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailsearch/forms.py b/wagtail/wagtailsearch/forms.py index aaffad052..75d672d67 100644 --- a/wagtail/wagtailsearch/forms.py +++ b/wagtail/wagtailsearch/forms.py @@ -49,7 +49,7 @@ class EditorsPickFormSet(EditorsPickFormSetBase): # Check there is at least one non-deleted form. non_deleted_forms = self.total_form_count() non_empty_forms = 0 - for i in xrange(0, self.total_form_count()): + for i in range(0, self.total_form_count()): form = self.forms[i] if self.can_delete and self._should_delete_form(form): non_deleted_forms -= 1 From 5cd1d8276917e0305f8a9a4d6b17ac803ed0b784 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 15:43:35 +0100 Subject: [PATCH 32/76] Cast email address to bytes before hashing into gravatar url --- wagtail/wagtailadmin/templatetags/gravatar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/templatetags/gravatar.py b/wagtail/wagtailadmin/templatetags/gravatar.py index 15c161015..c831e6cb5 100644 --- a/wagtail/wagtailadmin/templatetags/gravatar.py +++ b/wagtail/wagtailadmin/templatetags/gravatar.py @@ -10,6 +10,7 @@ import hashlib +from six import b from six.moves.urllib.parse import urlencode from django import template @@ -31,7 +32,7 @@ class GravatarUrlNode(template.Node): default = "blank" size = int(self.size) * 2 # requested at retina size by default and scaled down at point of use with css - gravatar_url = "//www.gravatar.com/avatar/" + hashlib.md5(email.lower()).hexdigest() + "?" + gravatar_url = "//www.gravatar.com/avatar/" + hashlib.md5(b(email.lower())).hexdigest() + "?" gravatar_url += urlencode({'s': str(size), 'd': default}) return gravatar_url From 99f7b5656063400f917cf917c94dfb3327519d3f Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 15:50:04 +0100 Subject: [PATCH 33/76] Replaced __unicode__ methods with __str__ methods --- wagtail/tests/models.py | 12 +++++++++--- wagtail/wagtailcore/models.py | 10 +++++++--- wagtail/wagtaildocs/models.py | 4 +++- wagtail/wagtailembeds/models.py | 4 +++- wagtail/wagtailforms/models.py | 4 +++- wagtail/wagtailimages/models.py | 4 +++- wagtail/wagtailsearch/models.py | 4 +++- wagtail/wagtailusers/models.py | 4 +++- 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/wagtail/tests/models.py b/wagtail/tests/models.py index ca6d411da..ff6dc2dcc 100644 --- a/wagtail/tests/models.py +++ b/wagtail/tests/models.py @@ -1,6 +1,9 @@ from django.db import models from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.utils.encoding import python_2_unicode_compatible + from modelcluster.fields import ParentalKey + from wagtail.wagtailcore.models import Page, Orderable from wagtail.wagtailcore.fields import RichTextField from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel, PageChooserPanel @@ -259,6 +262,7 @@ FormPage.content_panels = [ # Snippets +@python_2_unicode_compatible class Advert(models.Model): url = models.URLField(null=True, blank=True) text = models.CharField(max_length=255) @@ -268,7 +272,7 @@ class Advert(models.Model): FieldPanel('text'), ] - def __unicode__(self): + def __str__(self): return self.text @@ -281,18 +285,20 @@ register_snippet(Advert) # to ensure specific [in]correct register ordering # AlphaSnippet is registered during TestSnippetOrdering +@python_2_unicode_compatible class AlphaSnippet(models.Model): text = models.CharField(max_length=255) - def __unicode__(self): + def __str__(self): return self.text # ZuluSnippet is registered during TestSnippetOrdering +@python_2_unicode_compatible class ZuluSnippet(models.Model): text = models.CharField(max_length=255) - def __unicode__(self): + def __str__(self): return self.text diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index a73c898e5..e3a8e91eb 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -22,6 +22,7 @@ from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError from django.utils.functional import cached_property +from django.utils.encoding import python_2_unicode_compatible from treebeard.mp_tree import MP_Node @@ -36,6 +37,7 @@ class SiteManager(models.Manager): return self.get(hostname=hostname, port=port) +@python_2_unicode_compatible class Site(models.Model): hostname = models.CharField(max_length=255, db_index=True) port = models.IntegerField(default=80, help_text=_("Set this to something other than 80 if you need a specific port number to appear in URLs (e.g. development on port 8000). Does not affect request handling (so port forwarding still works).")) @@ -48,7 +50,7 @@ class Site(models.Model): def natural_key(self): return (self.hostname, self.port) - def __unicode__(self): + def __str__(self): return self.hostname + ("" if self.port == 80 else (":%d" % self.port)) + (" [default]" if self.is_default_site else "") @staticmethod @@ -258,6 +260,7 @@ class PageBase(models.base.ModelBase): PAGE_MODEL_CLASSES.append(cls) +@python_2_unicode_compatible class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)): title = models.CharField(max_length=255, help_text=_("The page title as you'd like it to be seen by the public")) slug = models.SlugField(help_text=_("The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/")) @@ -301,7 +304,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)): # created as self.content_type = ContentType.objects.get_for_model(self) - def __unicode__(self): + def __str__(self): return self.title is_abstract = True # don't offer Page in the list of page types a superuser can create @@ -771,6 +774,7 @@ class SubmittedRevisionsManager(models.Manager): return super(SubmittedRevisionsManager, self).get_queryset().filter(submitted_for_moderation=True) +@python_2_unicode_compatible class PageRevision(models.Model): page = models.ForeignKey('Page', related_name='revisions') submitted_for_moderation = models.BooleanField(default=False) @@ -828,7 +832,7 @@ class PageRevision(models.Model): self.submitted_for_moderation = False page.revisions.update(submitted_for_moderation=False) - def __unicode__(self): + def __str__(self): return '"' + unicode(self.page) + '" at ' + unicode(self.created_at) diff --git a/wagtail/wagtaildocs/models.py b/wagtail/wagtaildocs/models.py index 2149320f7..a87b9c02b 100644 --- a/wagtail/wagtaildocs/models.py +++ b/wagtail/wagtaildocs/models.py @@ -9,10 +9,12 @@ from django.dispatch import Signal from django.core.urlresolvers import reverse from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import python_2_unicode_compatible from wagtail.wagtailadmin.taggable import TagSearchable +@python_2_unicode_compatible class Document(models.Model, TagSearchable): title = models.CharField(max_length=255, verbose_name=_('Title')) file = models.FileField(upload_to='documents' , verbose_name=_('File')) @@ -30,7 +32,7 @@ class Document(models.Model, TagSearchable): }, } - def __unicode__(self): + def __str__(self): return self.title @property diff --git a/wagtail/wagtailembeds/models.py b/wagtail/wagtailembeds/models.py index c147860f0..4f61c19d7 100644 --- a/wagtail/wagtailembeds/models.py +++ b/wagtail/wagtailembeds/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils.encoding import python_2_unicode_compatible EMBED_TYPES = ( @@ -9,6 +10,7 @@ EMBED_TYPES = ( ) +@python_2_unicode_compatible class Embed(models.Model): url = models.URLField() max_width = models.SmallIntegerField(null=True, blank=True) @@ -25,5 +27,5 @@ class Embed(models.Model): class Meta: unique_together = ('url', 'max_width') - def __unicode__(self): + def __str__(self): return self.url diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index 75f0eceee..c74ace88b 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -2,6 +2,7 @@ from django.db import models from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ from django.utils.text import slugify +from django.utils.encoding import python_2_unicode_compatible from unidecode import unidecode import json @@ -32,6 +33,7 @@ FORM_FIELD_CHOICES = ( HTML_EXTENSION_RE = re.compile(r"(.*)\.html") +@python_2_unicode_compatible class FormSubmission(models.Model): """Data for a Form submission.""" @@ -43,7 +45,7 @@ class FormSubmission(models.Model): def get_data(self): return json.loads(self.form_data) - def __unicode__(self): + def __str__(self): return self.form_data diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index fe898b4d6..db73b048a 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -14,6 +14,7 @@ from django.utils.safestring import mark_safe from django.utils.html import escape, format_html_join from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import python_2_unicode_compatible from unidecode import unidecode @@ -22,6 +23,7 @@ from wagtail.wagtailimages.backends import get_image_backend from .utils import validate_image_format +@python_2_unicode_compatible class AbstractImage(models.Model, TagSearchable): title = models.CharField(max_length=255, verbose_name=_('Title') ) @@ -55,7 +57,7 @@ class AbstractImage(models.Model, TagSearchable): }, } - def __unicode__(self): + def __str__(self): return self.title def get_rendition(self, filter): diff --git a/wagtail/wagtailsearch/models.py b/wagtail/wagtailsearch/models.py index c8c2f2fa4..abb479295 100644 --- a/wagtail/wagtailsearch/models.py +++ b/wagtail/wagtailsearch/models.py @@ -2,11 +2,13 @@ import datetime from django.db import models from django.utils import timezone +from django.utils.encoding import python_2_unicode_compatible from wagtail.wagtailsearch.indexed import Indexed from wagtail.wagtailsearch.utils import normalise_query_string, MAX_QUERY_STRING_LENGTH +@python_2_unicode_compatible class Query(models.Model): query_string = models.CharField(max_length=MAX_QUERY_STRING_LENGTH, unique=True) @@ -23,7 +25,7 @@ class Query(models.Model): daily_hits.hits = models.F('hits') + 1 daily_hits.save() - def __unicode__(self): + def __str__(self): return self.query_string @property diff --git a/wagtail/wagtailusers/models.py b/wagtail/wagtailusers/models.py index 4e8bfc63d..eb0aa0c9e 100644 --- a/wagtail/wagtailusers/models.py +++ b/wagtail/wagtailusers/models.py @@ -1,8 +1,10 @@ from django.db import models from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class UserProfile(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL) @@ -25,5 +27,5 @@ class UserProfile(models.Model): def get_for_user(cls, user): return cls.objects.get_or_create(user=user)[0] - def __unicode__(self): + def __str__(self): return self.user.username From 6bd8e056b818fa0ee8a4f5c43fe3bdc0f9e5e9df Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 15:51:44 +0100 Subject: [PATCH 34/76] Another unicode replaced with text_type --- wagtail/wagtailforms/models.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index c74ace88b..41414d391 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -1,13 +1,16 @@ +import json +import re + +from six import text_type + +from unidecode import unidecode + from django.db import models from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ from django.utils.text import slugify from django.utils.encoding import python_2_unicode_compatible -from unidecode import unidecode -import json -import re - from wagtail.wagtailcore.models import Page, Orderable, UserPagePermissionsProxy, get_page_types from wagtail.wagtailadmin.edit_handlers import FieldPanel from wagtail.wagtailadmin import tasks @@ -75,7 +78,7 @@ class AbstractFormField(Orderable): # unidecode will return an ascii string while slugify wants a # unicode string on the other hand, slugify returns a safe-string # which will be converted to a normal str - return str(slugify(unicode(unidecode(self.label)))) + return str(slugify(text_type(unidecode(self.label)))) panels = [ FieldPanel('label'), From 623e77391d5140020841f0eb3194854bf578867c Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 16:05:10 +0100 Subject: [PATCH 35/76] Use python 3 print function --- .../commands/publish_scheduled_pages.py | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py b/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py index b80ace728..cb0d4cab8 100644 --- a/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py +++ b/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import datetime import json from optparse import make_option @@ -31,7 +33,7 @@ class Command(BaseCommand): def handle(self, *args, **options): dryrun = False if options['dryrun']: - print "Will do a dry run." + print("Will do a dry run.") dryrun = True # 1. get all expired pages with live = True @@ -41,17 +43,17 @@ class Command(BaseCommand): ) if dryrun: if expired_pages: - print "Expired pages to be deactivated:" - print "Expiry datetime\t\tSlug\t\tName" - print "---------------\t\t----\t\t----" + print("Expired pages to be deactivated:") + print("Expiry datetime\t\tSlug\t\tName") + print("---------------\t\t----\t\t----") for ep in expired_pages: - print "{0}\t{1}\t{2}".format( + print("{0}\t{1}\t{2}".format( ep.expire_at.strftime("%Y-%m-%d %H:%M"), ep.slug, ep.title - ) + )) else: - print "No expired pages to be deactivated found." + print("No expired pages to be deactivated found.") else: expired_pages.update(expired=True, live=False) @@ -62,22 +64,22 @@ class Command(BaseCommand): ) if revision_date_expired(r) ] if dryrun: - print "---------------------------------" + print("---------------------------------") if expired_revs: - print "Expired revisions to be dropped from moderation queue:" - print "Expiry datetime\t\tSlug\t\tName" - print "---------------\t\t----\t\t----" + print("Expired revisions to be dropped from moderation queue:") + print("Expiry datetime\t\tSlug\t\tName") + print("---------------\t\t----\t\t----") for er in expired_revs: rev_data = json.loads(er.content_json) - print "{0}\t{1}\t{2}".format( + print("{0}\t{1}\t{2}".format( dateparse.parse_datetime( rev_data.get('expire_at') ).strftime("%Y-%m-%d %H:%M"), rev_data.get('slug'), rev_data.get('title') - ) + )) else: - print "No expired revision to be dropped from moderation." + print("No expired revision to be dropped from moderation.") else: for er in expired_revs: er.submitted_for_moderation = False @@ -88,20 +90,20 @@ class Command(BaseCommand): approved_go_live_at__lt=timezone.now() ) if dryrun: - print "---------------------------------" + print("---------------------------------") if revs_for_publishing: - print "Revisions to be published:" - print "Go live datetime\t\tSlug\t\tName" - print "---------------\t\t\t----\t\t----" + print("Revisions to be published:") + print("Go live datetime\t\tSlug\t\tName") + print("---------------\t\t\t----\t\t----") for rp in revs_for_publishing: rev_data = json.loads(rp.content_json) - print "{0}\t\t{1}\t{2}".format( + print("{0}\t\t{1}\t{2}".format( rp.approved_go_live_at.strftime("%Y-%m-%d %H:%M"), rev_data.get('slug'), rev_data.get('title') - ) + )) else: - print "No pages to go live." + print("No pages to go live.") else: for rp in revs_for_publishing: # just run publish for the revision -- since the approved go From b4cf0ab15a6784d45fd44bfff7a223a92bbce515 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 16:05:57 +0100 Subject: [PATCH 36/76] Fixed python 3 incompatible method for concatenating dictionaries --- wagtail/wagtailcore/rich_text.py | 4 ++-- wagtail/wagtailsearch/backends/elasticsearch.py | 5 +++-- wagtail/wagtailsearch/indexed.py | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailcore/rich_text.py b/wagtail/wagtailcore/rich_text.py index 8004cb30d..c5229f747 100644 --- a/wagtail/wagtailcore/rich_text.py +++ b/wagtail/wagtailcore/rich_text.py @@ -166,8 +166,8 @@ class DbWhitelister(Whitelister): def clean(cls, html): if not cls.has_loaded_custom_whitelist_rules: for fn in hooks.get_hooks('construct_whitelister_element_rules'): - cls.element_rules = dict( - cls.element_rules.items() + fn().items()) + cls.element_rules = cls.element_rules.copy() + cls.element_rules.update(fn()) cls.has_loaded_custom_whitelist_rules = True return super(DbWhitelister, cls).clean(html) diff --git a/wagtail/wagtailsearch/backends/elasticsearch.py b/wagtail/wagtailsearch/backends/elasticsearch.py index 4c7c79da9..e4d716ea1 100644 --- a/wagtail/wagtailsearch/backends/elasticsearch.py +++ b/wagtail/wagtailsearch/backends/elasticsearch.py @@ -338,10 +338,11 @@ class ElasticSearch(BaseSearch): indexed_fields = model.indexed_get_indexed_fields() # Make field list - fields = dict({ + fields = { "pk": dict(type="string", index="not_analyzed", store="yes"), "content_type": dict(type="string"), - }.items() + indexed_fields.items()) + } + fields.update(indexed_fields) # Put mapping self.es.indices.put_mapping(index=self.es_index, doc_type=content_type, body={ diff --git a/wagtail/wagtailsearch/indexed.py b/wagtail/wagtailsearch/indexed.py index dfed7799a..60c56c8b4 100644 --- a/wagtail/wagtailsearch/indexed.py +++ b/wagtail/wagtailsearch/indexed.py @@ -51,7 +51,8 @@ class Indexed(object): if parent: # Add parent fields into this list parent_indexed_fields = parent.indexed_get_indexed_fields() - indexed_fields = dict(parent_indexed_fields.items() + indexed_fields.items()) + parent_indexed_fields.update(indexed_fields) + indexed_fields = parent_indexed_fields return indexed_fields def indexed_get_document_id(self): From e58929d79b42e6fceb7c00035918a743a09cc832 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 16:29:58 +0100 Subject: [PATCH 37/76] Cast test docs to bytes before uploading --- wagtail/wagtaildocs/tests.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/wagtail/wagtaildocs/tests.py b/wagtail/wagtaildocs/tests.py index a8420406b..79b799a36 100644 --- a/wagtail/wagtaildocs/tests.py +++ b/wagtail/wagtaildocs/tests.py @@ -1,10 +1,14 @@ +from six import b + from django.test import TestCase -from wagtail.wagtaildocs import models -from wagtail.tests.utils import WagtailTestUtils + from django.contrib.auth.models import User, Group, Permission from django.core.urlresolvers import reverse from django.core.files.base import ContentFile +from wagtail.wagtaildocs import models +from wagtail.tests.utils import WagtailTestUtils + # TODO: Test serve view @@ -112,7 +116,7 @@ class TestDocumentAddView(TestCase, WagtailTestUtils): def test_post(self): # Build a fake file - fake_file = ContentFile("A boring example document") + fake_file = ContentFile(b("A boring example document")) fake_file.name = 'test.txt' # Submit @@ -134,7 +138,7 @@ class TestDocumentEditView(TestCase, WagtailTestUtils): self.login() # Build a fake file - fake_file = ContentFile("A boring example document") + fake_file = ContentFile(b("A boring example document")) fake_file.name = 'test.txt' # Create a document to edit @@ -147,7 +151,7 @@ class TestDocumentEditView(TestCase, WagtailTestUtils): def test_post(self): # Build a fake file - fake_file = ContentFile("A boring example document") + fake_file = ContentFile(b("A boring example document")) fake_file.name = 'test.txt' # Submit title change @@ -272,7 +276,7 @@ class TestDocumentChooserUploadView(TestCase, WagtailTestUtils): def test_post(self): # Build a fake file - fake_file = ContentFile("A boring example document") + fake_file = ContentFile(b("A boring example document")) fake_file.name = 'test.txt' # Submit From a4bd768a7db03fe768e848ec13fa8e8eca930600 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 16:11:08 +0100 Subject: [PATCH 38/76] Make sure that we are not iterating over tag.attrs.items() --- wagtail/wagtailcore/whitelist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailcore/whitelist.py b/wagtail/wagtailcore/whitelist.py index 0859ccfa8..4aaff780d 100644 --- a/wagtail/wagtailcore/whitelist.py +++ b/wagtail/wagtailcore/whitelist.py @@ -29,7 +29,7 @@ def attribute_rule(allowed_attrs): * if the lookup returns a truthy value, keep the attribute; if falsy, drop it """ def fn(tag): - for attr, val in tag.attrs.items(): + for attr, val in list(tag.attrs.items()): rule = allowed_attrs.get(attr) if rule: if callable(rule): From dbcb17c70670671c0e457cc6e767b2899920fd0a Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 17:22:23 +0100 Subject: [PATCH 39/76] Fixed wagtailembeds tests --- wagtail/wagtailembeds/embeds.py | 9 +++++++-- wagtail/wagtailembeds/tests.py | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailembeds/embeds.py b/wagtail/wagtailembeds/embeds.py index ebc3e1491..40bf48ec9 100644 --- a/wagtail/wagtailembeds/embeds.py +++ b/wagtail/wagtailembeds/embeds.py @@ -1,5 +1,6 @@ import sys from datetime import datetime +import json try: from importlib import import_module @@ -7,7 +8,11 @@ except ImportError: # for Python 2.6, fall back on django.utils.importlib (deprecated as of Django 1.7) from django.utils.importlib import import_module -from six.moves.urllib.request import urlopen, Request + +# Needs to be imported like this to allow @patch to work in tests +from six.moves.urllib import request as urllib_request + +from six.moves.urllib.request import Request from six.moves.urllib.error import URLError from six.moves.urllib.parse import urlencode @@ -106,7 +111,7 @@ def oembed(url, max_width=None): request = Request(provider + '?' + urlencode(params)) request.add_header('User-agent', 'Mozilla/5.0') try: - r = urlopen(request) + r = urllib_request.urlopen(request) except URLError: raise EmbedNotFoundException oembed = json.loads(r.read()) diff --git a/wagtail/wagtailembeds/tests.py b/wagtail/wagtailembeds/tests.py index cc67e6b9b..2c68ef744 100644 --- a/wagtail/wagtailembeds/tests.py +++ b/wagtail/wagtailembeds/tests.py @@ -1,4 +1,6 @@ from six.moves.urllib.request import urlopen +import six.moves.urllib.request +from six.moves.urllib.error import URLError from mock import patch @@ -218,8 +220,8 @@ class TestOembed(TestCase): self.assertRaises(EmbedNotFoundException, wagtail_oembed, "foo") def test_oembed_invalid_request(self): - config = {'side_effect': urllib2.URLError('foo')} - with patch.object(urllib2, 'urlopen', **config) as urlopen: + config = {'side_effect': URLError('foo')} + with patch.object(six.moves.urllib.request, 'urlopen', **config) as urlopen: self.assertRaises(EmbedNotFoundException, wagtail_oembed, "http://www.youtube.com/watch/") @@ -278,7 +280,7 @@ class TestEmbedFilter(TestCase): result = embed_filter('http://www.youtube.com/watch/') self.assertEqual(result, '') - @patch('six.moves.urllib.request.urlopenn') + @patch('six.moves.urllib.request.urlopen') @patch('json.loads') def test_render_filter(self, loads, urlopen): urlopen.return_value = self.dummy_response From 3248601cc1c91aa8e94d6f19cae23918c70395e4 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 1 Jul 2014 17:46:54 +0100 Subject: [PATCH 40/76] Fixed wagtailforms test --- wagtail/wagtailforms/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailforms/tests.py b/wagtail/wagtailforms/tests.py index 0e162e16a..38903d2ff 100644 --- a/wagtail/wagtailforms/tests.py +++ b/wagtail/wagtailforms/tests.py @@ -258,5 +258,5 @@ class TestFormsSubmissions(TestCase): # Check response self.assertEqual(response.status_code, 200) - data_line = response.content.split("\n")[1] + data_line = response.content.decode().split("\n")[1] self.assertTrue('new@example.com' in data_line) From 01c9ffb552f4359e1109ce6dd0d89d6280c037db Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 11:07:21 +0100 Subject: [PATCH 41/76] Fixed unresolved merge conflict --- wagtail/wagtailadmin/tests/test_pages_views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 7fe16d3b1..58c674f91 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -255,12 +255,11 @@ class TestPageCreation(TestCase, WagtailTestUtils): self.assertIsInstance(page, SimplePage) self.assertTrue(page.live) -<<<<<<< HEAD # Check that the page_published signal was fired self.assertTrue(signal_fired[0]) self.assertEqual(signal_page[0], page) self.assertEqual(signal_page[0], signal_page[0].specific) -======= + def test_create_simplepage_post_publish_scheduled(self): go_live_at = timezone.now() + timedelta(days=1) expire_at = timezone.now() + timedelta(days=2) @@ -288,7 +287,6 @@ class TestPageCreation(TestCase, WagtailTestUtils): # But Page won't be live self.assertFalse(page.live) self.assertTrue(page.status_string, "scheduled") ->>>>>>> master def test_create_simplepage_post_submit(self): # Create a moderator user for testing email From f7b9390202e45dfe0f50e5d20285f8c95889b237 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 11:16:13 +0100 Subject: [PATCH 42/76] removed max-height transition until it can be done without bad effects on long forms --- .../wagtailadmin/static/wagtailadmin/scss/components/forms.scss | 2 -- .../static/wagtailadmin/scss/layouts/page-editor.scss | 1 - 2 files changed, 3 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss index aa8968f04..3a8f67ba2 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss @@ -304,9 +304,7 @@ input[type=submit], input[type=reset], input[type=button], button{ } .multiple{ - @include transition(max-height 10s ease); padding:0; - max-height:10000px; max-width:1024px - 50px; overflow:hidden; diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss index 241d1aff4..50e77748d 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/layouts/page-editor.scss @@ -269,7 +269,6 @@ .multiple{ padding:0; - max-height:0px; } } From 1515d3b6d639d873217c68c80b7220f545a0bde1 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 11:40:22 +0100 Subject: [PATCH 43/76] Note about Help develop me label --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0e68b8c6b..e96542483 100644 --- a/README.rst +++ b/README.rst @@ -46,5 +46,9 @@ Wagtail supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 su Contributing ~~~~~~~~~~~~ -If you're a Python or Django developer, fork the repo and get stuck in! Send us a useful pull request and we'll post you a `t-shirt `_. Our immediate priorities are better docs, more tests, internationalisation and localisation. +If you're a Python or Django developer, fork the repo and get stuck in! + +We suggest you start by checking the ["Help develop me!"](https://github.com/torchbox/wagtail/issues?labels=Help+develop+me%21) label. + +Send us a useful pull request and we'll post you a `t-shirt `_. Our immediate priorities are better docs, more tests, internationalisation and localisation. From 4cd570c9791a29252f2641ae28139051f6b975ba Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 11:41:38 +0100 Subject: [PATCH 44/76] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e96542483..b9bc632a4 100644 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ Contributing ~~~~~~~~~~~~ If you're a Python or Django developer, fork the repo and get stuck in! -We suggest you start by checking the ["Help develop me!"](https://github.com/torchbox/wagtail/issues?labels=Help+develop+me%21) label. +We suggest you start by checking the `Help develop me! `_ label. Send us a useful pull request and we'll post you a `t-shirt `_. Our immediate priorities are better docs, more tests, internationalisation and localisation. From fee62898fcd2acdca15199385227803859c2533f Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 2 Jul 2014 11:44:39 +0100 Subject: [PATCH 45/76] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b9bc632a4..341e8d2bf 100644 --- a/README.rst +++ b/README.rst @@ -50,5 +50,5 @@ If you're a Python or Django developer, fork the repo and get stuck in! We suggest you start by checking the `Help develop me! `_ label. -Send us a useful pull request and we'll post you a `t-shirt `_. Our immediate priorities are better docs, more tests, internationalisation and localisation. +Send us a useful pull request and we'll post you a `t-shirt `_. From 5c61f75b6874b0a37ed9ca57342a0acfa44838d4 Mon Sep 17 00:00:00 2001 From: dv Date: Wed, 2 Jul 2014 19:10:19 +0800 Subject: [PATCH 46/76] fix wrong closing tag --- wagtail/wagtailusers/templates/wagtailusers/list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailusers/templates/wagtailusers/list.html b/wagtail/wagtailusers/templates/wagtailusers/list.html index c31b0bed4..798ab7fb4 100644 --- a/wagtail/wagtailusers/templates/wagtailusers/list.html +++ b/wagtail/wagtailusers/templates/wagtailusers/list.html @@ -35,7 +35,7 @@ {{ user.username }} {% if user.is_superuser %}{% trans "Admin" %}{% endif %}
    {% if user.is_active %}{% trans "Active" %}{% else %}{% trans "Inactive" %}{% endif %}
    - + {% endfor %} - \ No newline at end of file + From 29896dcf5e42dd4af59f68b068ab6727640e6aea Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 12:30:32 +0100 Subject: [PATCH 47/76] Take a copy of parents indexed fields before modifying it to prevent indexed fields configuration being changed accidentally --- wagtail/wagtailsearch/indexed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailsearch/indexed.py b/wagtail/wagtailsearch/indexed.py index 60c56c8b4..0b607a2c7 100644 --- a/wagtail/wagtailsearch/indexed.py +++ b/wagtail/wagtailsearch/indexed.py @@ -50,7 +50,7 @@ class Indexed(object): parent = cls.indexed_get_parent(require_model=False) if parent: # Add parent fields into this list - parent_indexed_fields = parent.indexed_get_indexed_fields() + parent_indexed_fields = parent.indexed_get_indexed_fields().copy() parent_indexed_fields.update(indexed_fields) indexed_fields = parent_indexed_fields return indexed_fields From ea28584b8bbbebbcf70ff213760f2c699ef58549 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 12:37:29 +0100 Subject: [PATCH 48/76] indexed_get_indexed_fields should always return a copy of the configuration (even it its already a dict) --- wagtail/wagtailsearch/indexed.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/wagtail/wagtailsearch/indexed.py b/wagtail/wagtailsearch/indexed.py index 0b607a2c7..e4e8b87cb 100644 --- a/wagtail/wagtailsearch/indexed.py +++ b/wagtail/wagtailsearch/indexed.py @@ -37,20 +37,25 @@ class Indexed(object): def indexed_get_indexed_fields(cls): # Get indexed fields for this class as dictionary indexed_fields = cls.indexed_fields - if isinstance(indexed_fields, tuple): - indexed_fields = list(indexed_fields) - if isinstance(indexed_fields, string_types): - indexed_fields = [indexed_fields] - if isinstance(indexed_fields, list): - indexed_fields = dict((field, dict(type="string")) for field in indexed_fields) - if not isinstance(indexed_fields, dict): - raise ValueError() + if isinstance(indexed_fields, dict): + # Make sure we have a copy to prevent us accidentally changing the configuration + indexed_fields = indexed_fields.copy() + else: + # Convert to dict + if isinstance(indexed_fields, tuple): + indexed_fields = list(indexed_fields) + if isinstance(indexed_fields, string_types): + indexed_fields = [indexed_fields] + if isinstance(indexed_fields, list): + indexed_fields = dict((field, dict(type="string")) for field in indexed_fields) + if not isinstance(indexed_fields, dict): + raise ValueError() # Get indexed fields for parent class parent = cls.indexed_get_parent(require_model=False) if parent: # Add parent fields into this list - parent_indexed_fields = parent.indexed_get_indexed_fields().copy() + parent_indexed_fields = parent.indexed_get_indexed_fields() parent_indexed_fields.update(indexed_fields) indexed_fields = parent_indexed_fields return indexed_fields From f642e78f60897cc0ed7147ee79f1de0727fe981c Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 12:52:09 +0100 Subject: [PATCH 49/76] Removed get_internal_paths I've changed my mind about this. --- wagtail/wagtailcore/models.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index c1e48750b..294f5d40b 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -623,22 +623,14 @@ class Page(MP_Node, ClusterableModel, Indexed): """ return self.serve(self.dummy_request()) - def get_internal_paths(self): - """ - This returns a list of paths within this page. - This is used for static sites, sitemaps and cache invalidation. - """ - return ['/'] - def get_sitemap_urls(self): latest_revision = self.get_latest_revision() return [ { - 'location': self.url + url[1:], + 'location': self.url, 'lastmod': latest_revision.created_at if latest_revision else None } - for url in self.get_internal_paths() ] def get_static_site_paths(self): @@ -646,9 +638,8 @@ class Page(MP_Node, ClusterableModel, Indexed): This is a generator of URL paths to feed into a static site generator Override this if you would like to create static versions of subpages """ - # Yield paths for this page - for url in self.get_internal_paths(): - yield url + # Yield path for this page + yield '/' # Yield paths for child pages for child in self.get_children().live(): From 3be8c0374ca0ce3ae3cea3b3f77ab188ce0e8a40 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 12:57:21 +0100 Subject: [PATCH 50/76] Improvements to sitemaps tests --- wagtail/contrib/wagtailsitemaps/tests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wagtail/contrib/wagtailsitemaps/tests.py b/wagtail/contrib/wagtailsitemaps/tests.py index 4adf3f98b..fd639e4ec 100644 --- a/wagtail/contrib/wagtailsitemaps/tests.py +++ b/wagtail/contrib/wagtailsitemaps/tests.py @@ -1,5 +1,6 @@ from django.test import TestCase from django.core.urlresolvers import reverse +from django.core.cache import cache from wagtail.wagtailcore.models import Page, Site from wagtail.tests.models import SimplePage @@ -46,6 +47,9 @@ class TestSitemapGenerator(TestCase): # Check that a URL has made it into the xml self.assertIn('/hello-world/', xml) + # Make sure the unpublished page didn't make it into the xml + self.assertNotIn('/unpublished/', xml) + class TestSitemapView(TestCase): def test_sitemap_view(self): @@ -56,11 +60,20 @@ class TestSitemapView(TestCase): self.assertEqual(response['Content-Type'], 'text/xml; charset=utf-8') def test_sitemap_view_cache(self): + cache_key = 'wagtail-sitemap:%d' % Site.objects.get(is_default_site=True).id + + # Check that the key is not in the cache + self.assertFalse(cache.has_key(cache_key)) + + # Hit the view first_response = self.client.get('/sitemap.xml') self.assertEqual(first_response.status_code, 200) self.assertTemplateUsed(first_response, 'wagtailsitemaps/sitemap.xml') + # Check that the key is in the cache + self.assertTrue(cache.has_key(cache_key)) + # Hit the view again. Should come from the cache this time second_response = self.client.get('/sitemap.xml') From 61fd67f2e9e4920ea2412f03363f5a54c598c804 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 13:01:49 +0100 Subject: [PATCH 51/76] Tweaks to sitemap generation docs --- docs/sitemap_generation.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sitemap_generation.rst b/docs/sitemap_generation.rst index 3308a8f4c..a750eadc1 100644 --- a/docs/sitemap_generation.rst +++ b/docs/sitemap_generation.rst @@ -31,7 +31,7 @@ Then, in urls.py, you need to add a link to the ``wagtail.contrib.wagtailsitemap ) -You should now be able to browse to "/sitemap.xml" and see the sitemap working. +You should now be able to browse to "/sitemap.xml" and see the sitemap working. By default, all published pages in your website will be added to the site map. Customising @@ -40,7 +40,7 @@ Customising URLs ---- -The Page class defines a ``get_sitemap_urls`` method which you can override to customise sitemaps per page type. This method must return a list of dictionaries, one dictionary per URL entry in the sitemap. You can exclude pages from the sitemap by returning an empty list. +The Page class defines a ``get_sitemap_urls`` method which you can override to customise sitemaps per page instance. This method must return a list of dictionaries, one dictionary per URL entry in the sitemap. You can exclude pages from the sitemap by returning an empty list. Each dictionary can contain the following: @@ -49,10 +49,10 @@ Each dictionary can contain the following: - **changefreq** - **priority** -You can add more but you will need to override the ``wagtailsitemaps/sitemap.xml`` template in order for them to be displayed in the sitemap. +You can add more but yoBy default, all published pages in your website will be added to the site map.u will need to override the ``wagtailsitemaps/sitemap.xml`` template in order for them to be displayed in the sitemap. Cache ----- -By default, sitemaps are cached for 100 minutes. You can change this by setting ``WAGTAILSITEMAPS_CACHE_TIMEOUT`` in your Django settings to the number of seconds you would like to cache to last for. +By default, sitemaps are cached for 100 minutes. You can change this by setting ``WAGTAILSITEMAPS_CACHE_TIMEOUT`` in your Django settings to the number of seconds you would like the cache to last for. From 8b4fc47c5697198d8456032fa85e868454f7a9bf Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 13:03:15 +0100 Subject: [PATCH 52/76] Removed get_internal_paths --- wagtail/wagtailcore/models.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 3e9cfd963..4da110b4d 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -686,27 +686,19 @@ class Page(MP_Node, ClusterableModel, Indexed): """ return self.serve(self.dummy_request()) - def get_internal_paths(self): - """ - This returns a list of paths within this page. - This is used for static sites, sitemaps and cache invalidation. - """ - return ['/'] - def get_cached_paths(self): """ This returns a list of paths to invalidate in a frontend cache """ - return self.get_internal_paths() + return ['/'] def get_static_site_paths(self): """ This is a generator of URL paths to feed into a static site generator Override this if you would like to create static versions of subpages """ - # Yield paths for this page - for url in self.get_internal_paths(): - yield url + # Yield paths for this page + yield '/' # Yield paths for child pages for child in self.get_children().live(): From ea292d05692c02024b82b917726daa6555278317 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 2 Jul 2014 13:12:13 +0100 Subject: [PATCH 53/76] Bump 1.6 testing from 1.6.2 to 1.6.5. Add job for python 3.4 (allowing failure for now). Add commented stub for 1.7.0 jobs when it's released (resulting in four jobs overall). --- .travis.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8764f2859..4dcfab09b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,16 @@ -# Python releases to test language: python +# Test matrix python: - - 2.7 -# Django releases + - 2.7 + - 3.4 env: - - DJANGO_VERSION=Django==1.6.2 + - DJANGO_VERSION=Django==1.6.5 + #- DJANGO_VERSION=Django==1.7.0 +matrix: + allow_failures: + - python: 3.4 + #- env: DJANGO_VERSION=Django==1.7.0 + fast_finish: true # Services services: - redis-server From 2f74885834b0e84b2d9e37edb93cd78aab2bce7a Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 13:45:43 +0100 Subject: [PATCH 54/76] Fixed bad paste --- docs/sitemap_generation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sitemap_generation.rst b/docs/sitemap_generation.rst index a750eadc1..9c6d50e48 100644 --- a/docs/sitemap_generation.rst +++ b/docs/sitemap_generation.rst @@ -49,7 +49,7 @@ Each dictionary can contain the following: - **changefreq** - **priority** -You can add more but yoBy default, all published pages in your website will be added to the site map.u will need to override the ``wagtailsitemaps/sitemap.xml`` template in order for them to be displayed in the sitemap. +You can add more but you will need to override the ``wagtailsitemaps/sitemap.xml`` template in order for them to be displayed in the sitemap. Cache From e6f47aa403f32558f4567e0b36890aec6d948a36 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 14:01:08 +0100 Subject: [PATCH 55/76] Take a copy of parents indexed fields before modifying it to prevent indexed fields configuration being changed accidentally --- wagtail/wagtailsearch/indexed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailsearch/indexed.py b/wagtail/wagtailsearch/indexed.py index e4e8b87cb..b53481727 100644 --- a/wagtail/wagtailsearch/indexed.py +++ b/wagtail/wagtailsearch/indexed.py @@ -55,7 +55,7 @@ class Indexed(object): parent = cls.indexed_get_parent(require_model=False) if parent: # Add parent fields into this list - parent_indexed_fields = parent.indexed_get_indexed_fields() + parent_indexed_fields = parent.indexed_get_indexed_fields().copy() parent_indexed_fields.update(indexed_fields) indexed_fields = parent_indexed_fields return indexed_fields From dd1b0ababeff09bdf10802aa61e61b9abf9e3155 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 14:44:02 +0100 Subject: [PATCH 56/76] Fixed check for user email address --- wagtail/wagtailadmin/templates/wagtailadmin/pages/edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/edit.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/edit.html index 1e1f7e9b3..901ee0ac1 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/edit.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/edit.html @@ -70,7 +70,7 @@ {% blocktrans with last_mod=page.get_latest_revision.created_at %}Last modified: {{ last_mod }}{% endblocktrans %} {% if page.get_latest_revision.user %} {% blocktrans with modified_by=page.get_latest_revision.user.get_full_name|default:page.get_latest_revision.user.username %}by {{ modified_by }}{% endblocktrans %} - {% if request.user.email %} + {% if page.get_latest_revision.user.email %} {% endif %} {% endif %} From 38ca434aeb52f8396c2407b8c52cb9f10f4aa06c Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 17:14:34 +0100 Subject: [PATCH 57/76] add tox tests for postgres on python3.3 and python3.4 --- tox.ini | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 11b8bb3a2..e1f888d6c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,17 @@ [deps] dj16= Django>=1.6,<1.7 - pyelasticsearch==0.6.1 - elasticutils==0.8.2 + elasticsearch==1.0.0 + mock==1.0.1 [tox] envlist = py26-dj16-postgres, py26-dj16-sqlite, py27-dj16-postgres, - py27-dj16-sqlite + py27-dj16-sqlite, + py33-dj16-postgres, + py34-dj16-postgres # mysql not currently supported # (wagtail.wagtailimages.tests.TestImageEditView currently fails with a @@ -17,6 +19,11 @@ envlist = # py26-dj16-mysql # py27-dj16-mysql +# South fails with sqlite on python3, because it tries to use DryRunMigrator which uses iteritems +# py33-dj16-sqlite, +# py34-dj16-sqlite + + [testenv] commands=./runtests.py @@ -67,3 +74,33 @@ deps = setenv = DATABASE_ENGINE=django.db.backends.mysql DATABASE_USER=wagtail + +[testenv:py33-dj16-postgres] +basepython=python3.3 +deps = + {[deps]dj16} + psycopg2==2.5.2 +setenv = + DATABASE_ENGINE=django.db.backends.postgresql_psycopg2 + +[testenv:py33-dj16-sqlite] +basepython=python3.3 +deps = + {[deps]dj16} +setenv = + DATABASE_ENGINE=django.db.backends.sqlite3 + +[testenv:py34-dj16-postgres] +basepython=python3.4 +deps = + {[deps]dj16} + psycopg2==2.5.2 +setenv = + DATABASE_ENGINE=django.db.backends.postgresql_psycopg2 + +[testenv:py34-dj16-sqlite] +basepython=python3.4 +deps = + {[deps]dj16} +setenv = + DATABASE_ENGINE=django.db.backends.sqlite3 From ab486a526a94f31704e5dc9076913b38ee63cecf Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 17:16:19 +0100 Subject: [PATCH 58/76] update travis config to not accept failures on 3.4 --- .travis.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4dcfab09b..48c8494b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,6 @@ python: env: - DJANGO_VERSION=Django==1.6.5 #- DJANGO_VERSION=Django==1.7.0 -matrix: - allow_failures: - - python: 3.4 - #- env: DJANGO_VERSION=Django==1.7.0 - fast_finish: true # Services services: - redis-server From 442286463ffe8b8daa5ad72009a41a3bacf5fb87 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 17:17:25 +0100 Subject: [PATCH 59/76] update setup.py with classifiers for Python3.3 and Python3.4 --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index fe8673f28..2d2939a9e 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,8 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'Framework :: Django', 'Topic :: Internet :: WWW/HTTP :: Site Management', ], From e31ff7f3c4215e7bb85c138a6e2e22b9a677824b Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 2 Jul 2014 17:31:06 +0100 Subject: [PATCH 60/76] Requirements version updates, documenting dependencies via requirements.io and updating supported python version docs. --- README.rst | 12 ++++++++---- requirements-dev.txt | 2 +- tox.ini | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 341e8d2bf..44f897b3b 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ :target: https://travis-ci.org/torchbox/wagtail .. image:: https://coveralls.io/repos/torchbox/wagtail/badge.png?branch=master&zxcv1 - :target: https://coveralls.io/r/torchbox/wagtail?branch=master + :target: https://coveralls.io/r/torchbox/wagtail?branch=master .. image:: https://pypip.in/v/wagtail/badge.png?zxcv :target: https://crate.io/packages/wagtail/ @@ -38,15 +38,19 @@ Getting started Documentation ~~~~~~~~~~~~~ -Available at `wagtail.readthedocs.org `_. and always being updated. +Available at `wagtail.readthedocs.org `_ and always being updated. Compatibility ~~~~~~~~~~~~~ -Wagtail supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support are in progress. +Wagtail supports Django 1.6.2+ on Python 2.6, 2.7, 3.3 and 3.4. + +Django 1.7 support is in progress pending further release candidate testing. + +Wagtail's dependencies are summarised at `requirements.io `_. Contributing ~~~~~~~~~~~~ -If you're a Python or Django developer, fork the repo and get stuck in! +If you're a Python or Django developer, fork the repo and get stuck in! We suggest you start by checking the `Help develop me! `_ label. diff --git a/requirements-dev.txt b/requirements-dev.txt index 4a9ff5c5e..1ddd2aa32 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ # For coverage and PEP8 linting coverage==3.7.1 -flake8==2.1.0 +flake8==2.1.1 mock==1.0.1 diff --git a/tox.ini b/tox.ini index e1f888d6c..c67a219b4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [deps] dj16= Django>=1.6,<1.7 - elasticsearch==1.0.0 + elasticsearch==1.1.0 mock==1.0.1 [tox] @@ -31,7 +31,7 @@ commands=./runtests.py basepython=python2.6 deps = {[deps]dj16} - psycopg2==2.5.2 + psycopg2==2.5.3 setenv = DATABASE_ENGINE=django.db.backends.postgresql_psycopg2 @@ -55,7 +55,7 @@ setenv = basepython=python2.7 deps = {[deps]dj16} - psycopg2==2.5.2 + psycopg2==2.5.3 setenv = DATABASE_ENGINE=django.db.backends.postgresql_psycopg2 @@ -79,7 +79,7 @@ setenv = basepython=python3.3 deps = {[deps]dj16} - psycopg2==2.5.2 + psycopg2==2.5.3 setenv = DATABASE_ENGINE=django.db.backends.postgresql_psycopg2 @@ -94,7 +94,7 @@ setenv = basepython=python3.4 deps = {[deps]dj16} - psycopg2==2.5.2 + psycopg2==2.5.3 setenv = DATABASE_ENGINE=django.db.backends.postgresql_psycopg2 From 80ea75d24495b77c1c4d4223ac28e7aebf12e6d5 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 2 Jul 2014 17:47:03 +0100 Subject: [PATCH 61/76] Bump flake8 to correct latest version. --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1ddd2aa32..b69c5d4e0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ # For coverage and PEP8 linting coverage==3.7.1 -flake8==2.1.1 +flake8==2.2.1 mock==1.0.1 From 0d5cabd1e7953e8e49c1a378be16ca389769feff Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Wed, 2 Jul 2014 17:50:03 +0100 Subject: [PATCH 62/76] Two new features in README --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 44f897b3b..2866e6c8a 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,9 @@ Wagtail is a Django content management system built originally for the `Royal Co * Support for tree-based content organisation * Optional preview->submit->approve workflow * Fast out of the box. `Varnish `_-friendly if you need it -* Excellent test coverage +* A simple `form builder `_ +* Optional `static site generation `_ +* Excellent `test coverage `_ Find out more at `wagtail.io `_. From a6637ffc18465e0e3005d8b5a730f94a926d6630 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 2 Jul 2014 17:51:36 +0100 Subject: [PATCH 63/76] Added some missing changelog entries --- CHANGELOG.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8ebe93959..66fad83d8 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,10 @@ Changelog 0.4 (xx.xx.20xx) ~~~~~~~~~~~~~~~~ * ElasticUtils/pyelasticsearch swapped for elasticsearch-py + * Python 3.3 and 3.4 support + * Added scheduled publishing + * Added frontend cache invalidator + * Added sitemap generator * Added notification preferences * Added 'original' as a resizing rule supported by the 'image' tag * Hallo.js updated to version 1.0.4 @@ -15,16 +19,22 @@ Changelog * Added styleguide (mainly for wagtail developers) * Aesthetic improvements to preview experience * 'image' tag now accepts extra keyword arguments to be output as attributes on the img tag + * Login screen redirects to dashboard if user is already logged in + * Renamed some template tag libraries + * Any extra arguments given to serve are now passed through to get_context and get_template * Added an 'attrs' property to image rendition objects to output src, width, height and alt attributes all in one go * Added 'construct_whitelister_element_rules' hook for customising the HTML whitelist used when saving rich text fields * Added 'in_menu' and 'not_in_menu' methods to PageQuerySet * Added 'get_next_siblings' and 'get_prev_siblings' to Page * Added init_new_page signal + * Added page_published signal * Fix: Animated GIFs are now coalesced before resizing * Fix: Wand backend clones images before modifying them * Fix: Admin breadcrumb now positioned correctly on mobile * Fix: Page chooser breadcrumb now updates the chooser modal instead of linking to Explorer * Fix: Embeds - Fixed crash when no HTML field is sent back from the embed provider + * Fix: Multiple sites with same hostname but different ports are now allowed + * Fix: No longer possible to create multiple sites with is_default_site = True 0.3.1 (03.06.2014) ~~~~~~~~~~~~~~~~~~ From 41ff7510098a4ef788b27b4ec3d0fcc774ba1d5c Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Wed, 2 Jul 2014 17:57:15 +0100 Subject: [PATCH 64/76] Update index.rst --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 5d2344b18..c96e77f10 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,7 @@ Welcome to Wagtail's documentation Wagtail is a modern, flexible CMS, built on Django. -It supports Django 1.6.2+ on Python 2.6 and 2.7. Django 1.7 and Python 3 support are in progress. +It supports Django 1.6.2+ on Python 2.6, 2.7, 3.3 and 3.4. Django 1.7 support is in progress pending further release candidate testing. .. toctree:: :maxdepth: 3 From 47c83e75b12ca345cb93e9ed9ca748fa98ac8302 Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Wed, 2 Jul 2014 17:57:30 +0100 Subject: [PATCH 65/76] Lets -> Let's --- docs/frontend_cache_purging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend_cache_purging.rst b/docs/frontend_cache_purging.rst index 1825e92d2..a949bdc6e 100644 --- a/docs/frontend_cache_purging.rst +++ b/docs/frontend_cache_purging.rst @@ -69,7 +69,7 @@ Another problem is pages that list other pages (such as a blog index) will not b This can be solved by using the ``purge_page_from_cache`` utility function which can be found in the ``wagtail.contrib.wagtailfrontendcache.utils`` module. -Lets take the the above BlogIndexPage as an example. We need to register a signal handler to run when one of the BlogPages get updated/deleted. This signal handler should call the ``purge_page_from_cache`` function on all BlogIndexPages that contain the BlogPage being updated/deleted. +Let's take the the above BlogIndexPage as an example. We need to register a signal handler to run when one of the BlogPages get updated/deleted. This signal handler should call the ``purge_page_from_cache`` function on all BlogIndexPages that contain the BlogPage being updated/deleted. .. code-block:: python From 7a80c8adca770bb4289d30d9a1504722b4524f02 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Mon, 2 Jun 2014 22:53:42 +0100 Subject: [PATCH 66/76] Move the 'hooks' module from wagtailadmin to wagtailcore Conflicts: wagtail/tests/wagtail_hooks.py wagtail/wagtailadmin/views/pages.py --- wagtail/tests/wagtail_hooks.py | 2 +- wagtail/wagtailadmin/hooks.py | 40 ++----------------- .../templatetags/wagtailadmin_tags.py | 2 +- wagtail/wagtailadmin/urls.py | 2 +- wagtail/wagtailadmin/views/home.py | 2 +- wagtail/wagtailadmin/views/pages.py | 3 +- wagtail/wagtailadmin/views/userbar.py | 2 +- wagtail/wagtailcore/hooks.py | 38 ++++++++++++++++++ wagtail/wagtaildocs/wagtail_hooks.py | 2 +- wagtail/wagtailembeds/wagtail_hooks.py | 2 +- wagtail/wagtailforms/wagtail_hooks.py | 2 +- wagtail/wagtailimages/wagtail_hooks.py | 2 +- wagtail/wagtailredirects/wagtail_hooks.py | 2 +- wagtail/wagtailsearch/wagtail_hooks.py | 2 +- wagtail/wagtailsnippets/wagtail_hooks.py | 2 +- wagtail/wagtailusers/wagtail_hooks.py | 2 +- 16 files changed, 56 insertions(+), 51 deletions(-) create mode 100644 wagtail/wagtailcore/hooks.py diff --git a/wagtail/tests/wagtail_hooks.py b/wagtail/tests/wagtail_hooks.py index 5dccca666..13e3e46d2 100644 --- a/wagtail/tests/wagtail_hooks.py +++ b/wagtail/tests/wagtail_hooks.py @@ -1,4 +1,4 @@ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailcore.whitelist import attribute_rule, check_url, allow_without_attributes def editor_css(): diff --git a/wagtail/wagtailadmin/hooks.py b/wagtail/wagtailadmin/hooks.py index d299c3346..75c3a8991 100644 --- a/wagtail/wagtailadmin/hooks.py +++ b/wagtail/wagtailadmin/hooks.py @@ -1,38 +1,4 @@ -from django.conf import settings -try: - from importlib import import_module -except ImportError: - # for Python 2.6, fall back on django.utils.importlib (deprecated as of Django 1.7) - from django.utils.importlib import import_module +# The 'hooks' module is now part of wagtailcore. +# Imports are provided here for backwards compatibility -_hooks = {} - -# TODO: support 'register' as a decorator: -# @hooks.register('construct_main_menu') -# def construct_main_menu(menu_items): -# ... - - -def register(hook_name, fn): - if hook_name not in _hooks: - _hooks[hook_name] = [] - _hooks[hook_name].append(fn) - -_searched_for_hooks = False - - -def search_for_hooks(): - global _searched_for_hooks - if not _searched_for_hooks: - for app_module in settings.INSTALLED_APPS: - try: - import_module('%s.wagtail_hooks' % app_module) - except ImportError: - continue - - _searched_for_hooks = True - - -def get_hooks(hook_name): - search_for_hooks() - return _hooks.get(hook_name, []) +from wagtail.wagtailcore.hooks import register, get_hooks diff --git a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py index 9f03c7b63..4a4d4d0eb 100644 --- a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py +++ b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py @@ -2,9 +2,9 @@ from django import template from django.core import urlresolvers from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks from wagtail.wagtailadmin.menu import MenuItem +from wagtail.wagtailcore import hooks from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy from wagtail.wagtailcore.utils import camelcase_to_underscore diff --git a/wagtail/wagtailadmin/urls.py b/wagtail/wagtailadmin/urls.py index 35bb63d5e..7ef2eb110 100644 --- a/wagtail/wagtailadmin/urls.py +++ b/wagtail/wagtailadmin/urls.py @@ -3,7 +3,7 @@ from django.conf import settings from wagtail.wagtailadmin.forms import LoginForm, PasswordResetForm from wagtail.wagtailadmin.views import account, chooser, home, pages, tags, userbar -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks urlpatterns = [ diff --git a/wagtail/wagtailadmin/views/home.py b/wagtail/wagtailadmin/views/home.py index 24800eefc..e68ed0cbf 100644 --- a/wagtail/wagtailadmin/views/home.py +++ b/wagtail/wagtailadmin/views/home.py @@ -4,9 +4,9 @@ from django.conf import settings from django.template import RequestContext from django.template.loader import render_to_string -from wagtail.wagtailadmin import hooks from wagtail.wagtailadmin.forms import SearchForm +from wagtail.wagtailcore import hooks from wagtail.wagtailcore.models import Page, PageRevision, UserPagePermissionsProxy from wagtail.wagtaildocs.models import Document diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index 1bc13c5f0..fd0c97b02 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -11,8 +11,9 @@ from django.views.decorators.vary import vary_on_headers from wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList from wagtail.wagtailadmin.forms import SearchForm -from wagtail.wagtailadmin import tasks, hooks, signals +from wagtail.wagtailadmin import tasks, signals +from wagtail.wagtailcore import hooks from wagtail.wagtailcore.models import Page, PageRevision from wagtail.wagtailcore.signals import page_published diff --git a/wagtail/wagtailadmin/views/userbar.py b/wagtail/wagtailadmin/views/userbar.py index 2b3749da0..c41b5aed1 100644 --- a/wagtail/wagtailadmin/views/userbar.py +++ b/wagtail/wagtailadmin/views/userbar.py @@ -2,7 +2,7 @@ from django.shortcuts import render from django.contrib.auth.decorators import permission_required from wagtail.wagtailadmin.userbar import EditPageItem, AddPageItem, ApproveModerationEditPageItem, RejectModerationEditPageItem -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailcore.models import Page, PageRevision diff --git a/wagtail/wagtailcore/hooks.py b/wagtail/wagtailcore/hooks.py new file mode 100644 index 000000000..d299c3346 --- /dev/null +++ b/wagtail/wagtailcore/hooks.py @@ -0,0 +1,38 @@ +from django.conf import settings +try: + from importlib import import_module +except ImportError: + # for Python 2.6, fall back on django.utils.importlib (deprecated as of Django 1.7) + from django.utils.importlib import import_module + +_hooks = {} + +# TODO: support 'register' as a decorator: +# @hooks.register('construct_main_menu') +# def construct_main_menu(menu_items): +# ... + + +def register(hook_name, fn): + if hook_name not in _hooks: + _hooks[hook_name] = [] + _hooks[hook_name].append(fn) + +_searched_for_hooks = False + + +def search_for_hooks(): + global _searched_for_hooks + if not _searched_for_hooks: + for app_module in settings.INSTALLED_APPS: + try: + import_module('%s.wagtail_hooks' % app_module) + except ImportError: + continue + + _searched_for_hooks = True + + +def get_hooks(hook_name): + search_for_hooks() + return _hooks.get(hook_name, []) diff --git a/wagtail/wagtaildocs/wagtail_hooks.py b/wagtail/wagtaildocs/wagtail_hooks.py index 3ab1ad9bf..a6e00f8db 100644 --- a/wagtail/wagtaildocs/wagtail_hooks.py +++ b/wagtail/wagtaildocs/wagtail_hooks.py @@ -4,7 +4,7 @@ from django.core import urlresolvers from django.utils.html import format_html, format_html_join from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtaildocs import admin_urls diff --git a/wagtail/wagtailembeds/wagtail_hooks.py b/wagtail/wagtailembeds/wagtail_hooks.py index 89c5e66a5..f9ea650a6 100644 --- a/wagtail/wagtailembeds/wagtail_hooks.py +++ b/wagtail/wagtailembeds/wagtail_hooks.py @@ -3,7 +3,7 @@ from django.conf.urls import include, url from django.core import urlresolvers from django.utils.html import format_html -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailembeds import urls diff --git a/wagtail/wagtailforms/wagtail_hooks.py b/wagtail/wagtailforms/wagtail_hooks.py index 277fc6e73..c584a5c4e 100644 --- a/wagtail/wagtailforms/wagtail_hooks.py +++ b/wagtail/wagtailforms/wagtail_hooks.py @@ -3,7 +3,7 @@ from django.conf import settings from django.conf.urls import include, url from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailforms import urls diff --git a/wagtail/wagtailimages/wagtail_hooks.py b/wagtail/wagtailimages/wagtail_hooks.py index 51492d404..611e18990 100644 --- a/wagtail/wagtailimages/wagtail_hooks.py +++ b/wagtail/wagtailimages/wagtail_hooks.py @@ -4,7 +4,7 @@ from django.core import urlresolvers from django.utils.html import format_html, format_html_join from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailimages import urls diff --git a/wagtail/wagtailredirects/wagtail_hooks.py b/wagtail/wagtailredirects/wagtail_hooks.py index 4dbe1a028..6ebec475b 100644 --- a/wagtail/wagtailredirects/wagtail_hooks.py +++ b/wagtail/wagtailredirects/wagtail_hooks.py @@ -2,7 +2,7 @@ from django.core import urlresolvers from django.conf.urls import include, url from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailredirects import urls from wagtail.wagtailadmin.menu import MenuItem diff --git a/wagtail/wagtailsearch/wagtail_hooks.py b/wagtail/wagtailsearch/wagtail_hooks.py index 1a656c0ef..3ca4601b3 100644 --- a/wagtail/wagtailsearch/wagtail_hooks.py +++ b/wagtail/wagtailsearch/wagtail_hooks.py @@ -2,7 +2,7 @@ from django.core import urlresolvers from django.conf.urls import include, url from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailsearch.urls import admin as admin_urls from wagtail.wagtailadmin.menu import MenuItem diff --git a/wagtail/wagtailsnippets/wagtail_hooks.py b/wagtail/wagtailsnippets/wagtail_hooks.py index 3745d1a51..501f29b31 100644 --- a/wagtail/wagtailsnippets/wagtail_hooks.py +++ b/wagtail/wagtailsnippets/wagtail_hooks.py @@ -4,7 +4,7 @@ from django.core import urlresolvers from django.utils.html import format_html from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailsnippets import urls diff --git a/wagtail/wagtailusers/wagtail_hooks.py b/wagtail/wagtailusers/wagtail_hooks.py index d006a4d93..6637430e1 100644 --- a/wagtail/wagtailusers/wagtail_hooks.py +++ b/wagtail/wagtailusers/wagtail_hooks.py @@ -2,7 +2,7 @@ from django.conf.urls import include, url from django.core import urlresolvers from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailusers import urls From 618dd96a88b9cb98c9f45ee2f63520bb472d6dc2 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 20:02:25 +0100 Subject: [PATCH 67/76] update wagtailstyleguide and wagtailcore.rich_text to import hooks from wagtailcore rather than wagtailadmin --- wagtail/contrib/wagtailstyleguide/wagtail_hooks.py | 2 +- wagtail/wagtailcore/rich_text.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py b/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py index ae2989be9..c2899b38e 100644 --- a/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py +++ b/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py @@ -4,7 +4,7 @@ from django.core import urlresolvers from django.utils.html import format_html, format_html_join from django.utils.translation import ugettext_lazy as _ -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailimages import urls diff --git a/wagtail/wagtailcore/rich_text.py b/wagtail/wagtailcore/rich_text.py index c5229f747..bd218d01d 100644 --- a/wagtail/wagtailcore/rich_text.py +++ b/wagtail/wagtailcore/rich_text.py @@ -13,7 +13,7 @@ from wagtail.wagtaildocs.models import Document from wagtail.wagtailimages.models import get_image_model from wagtail.wagtailimages.formats import get_image_format -from wagtail.wagtailadmin import hooks +from wagtail.wagtailcore import hooks # Define a set of 'embed handlers' and 'link handlers'. These handle the translation From 92dc6add90c8c1b85d75a0477ac047671ced4819 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 20:04:06 +0100 Subject: [PATCH 68/76] update hook examples in docs to import from wagtailcore, not wagtailadmin --- docs/editing_api.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/editing_api.rst b/docs/editing_api.rst index 609237e76..5113eb3b8 100644 --- a/docs/editing_api.rst +++ b/docs/editing_api.rst @@ -396,7 +396,7 @@ Registering functions with a Wagtail hook follows the following pattern: .. code-block:: python - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks hooks.register('hook', function) @@ -409,7 +409,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func .. code-block:: python - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks class UserbarPuppyLinkItem(object): def render(self, request): @@ -430,7 +430,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func from django.utils.safestring import mark_safe - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks class WelcomePanel(object): order = 50 @@ -456,7 +456,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func from django.http import HttpResponse - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks def do_after_page_create(request, page): return HttpResponse("Congrats on making content!", content_type="text/plain") @@ -484,7 +484,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func from django.http import HttpResponse from django.conf.urls import url - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks def admin_view( request ): return HttpResponse( \ @@ -506,7 +506,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func from django.core.urlresolvers import reverse - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem def construct_main_menu(request, menu_items): @@ -526,7 +526,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func from django.utils.html import format_html, format_html_join from django.conf import settings - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks def editor_js(): js_files = [ @@ -554,7 +554,7 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func from django.utils.html import format_html from django.conf import settings - from wagtail.wagtailadmin import hooks + from wagtail.wagtailcore import hooks def editor_css(): return format_html('') @patch('six.moves.urllib.request.urlopen') @@ -336,7 +344,7 @@ class TestEmbedlyFilter(TestEmbedFilter): urlopen.return_value = self.dummy_response loads.return_value = {'type': 'foo', 'url': 'http://www.example.com'} - temp = template.Template('{% load embed_filters %}{{ "http://www.youtube.com/watch/"|embedly }}') + temp = template.Template('{% load wagtailembeds_tags %}{{ "http://www.youtube.com/watch/"|embedly }}') context = template.Context() result = temp.render(context) self.assertEqual(result, '') From 352c7ce8f5a3e7d841d996251ec331cfdd65867d Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 21:05:16 +0100 Subject: [PATCH 70/76] replace assertEquals with assertEqual to prevent deprecation warnings on python 3 --- wagtail/wagtailadmin/tests/test_pages_views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 58c674f91..2e54218bf 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -190,9 +190,9 @@ class TestPageCreation(TestCase, WagtailTestUtils): # Find the page and check the scheduled times page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific - self.assertEquals(page.go_live_at.date(), go_live_at.date()) - self.assertEquals(page.expire_at.date(), expire_at.date()) - self.assertEquals(page.expired, False) + self.assertEqual(page.go_live_at.date(), go_live_at.date()) + self.assertEqual(page.expire_at.date(), expire_at.date()) + self.assertEqual(page.expired, False) self.assertTrue(page.status_string, "draft") # No revisions with approved_go_live_at @@ -278,9 +278,9 @@ class TestPageCreation(TestCase, WagtailTestUtils): # Find the page and check it page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific - self.assertEquals(page.go_live_at.date(), go_live_at.date()) - self.assertEquals(page.expire_at.date(), expire_at.date()) - self.assertEquals(page.expired, False) + self.assertEqual(page.go_live_at.date(), go_live_at.date()) + self.assertEqual(page.expire_at.date(), expire_at.date()) + self.assertEqual(page.expired, False) # A revision with approved_go_live_at should exist now self.assertTrue(PageRevision.objects.filter(page=page).exclude(approved_go_live_at__isnull=True).exists()) From 79de8bf88439fe56f6e24ae186c1f9365837ee3f Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 21:19:29 +0100 Subject: [PATCH 71/76] replace assertRegexpMatches with six.assertRegex to prevent deprecation warnings on python 3 --- wagtail/wagtailimages/tests.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index af4f305e7..48a31ae89 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -1,4 +1,5 @@ from mock import MagicMock +from django.utils import six from django.test import TestCase from django import template @@ -453,16 +454,14 @@ class TestFormat(TestCase): self.image, 'test alt text' ) - self.assertRegexpMatches( - result, + six.assertRegex(self, result, 'test alt text', - ) + ) def test_image_to_html_no_classnames(self): self.format.classnames = None result = self.format.image_to_html(self.image, 'test alt text') - self.assertRegexpMatches( - result, + six.assertRegex(self, result, 'test alt text' ) self.format.classnames = 'test classnames' From b45b1d5404b472c1bcff5d451bfa9ff4114168a7 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 2 Jul 2014 21:56:50 +0100 Subject: [PATCH 72/76] remove a load of unused imports --- wagtail/contrib/wagtailsitemaps/tests.py | 1 - wagtail/contrib/wagtailsitemaps/views.py | 1 - wagtail/contrib/wagtailstyleguide/views.py | 6 ------ wagtail/contrib/wagtailstyleguide/wagtail_hooks.py | 6 +----- wagtail/tests/utils.py | 3 --- wagtail/wagtailadmin/edit_handlers.py | 7 +------ wagtail/wagtailadmin/tests/test_account_management.py | 2 +- wagtail/wagtailadmin/tests/test_pages_views.py | 4 ++-- wagtail/wagtailadmin/tests/tests.py | 3 +-- wagtail/wagtailadmin/urls.py | 3 +-- wagtail/wagtailadmin/userbar.py | 1 - wagtail/wagtailcore/compat.py | 1 + .../management/commands/publish_scheduled_pages.py | 1 - wagtail/wagtailcore/models.py | 1 - wagtail/wagtailcore/tests/test_management_commands.py | 8 +++----- wagtail/wagtailcore/tests/test_page_model.py | 6 +----- wagtail/wagtailcore/tests/test_page_permissions.py | 10 +++------- wagtail/wagtailcore/tests/test_page_queryset.py | 11 +++-------- wagtail/wagtailcore/tests/test_whitelist.py | 2 +- wagtail/wagtailembeds/tests.py | 1 - wagtail/wagtailembeds/views/chooser.py | 1 - wagtail/wagtailsearch/backends/elasticsearch.py | 1 - wagtail/wagtailsearch/tests/test_editorspicks.py | 2 +- wagtail/wagtailsearch/tests/test_queries.py | 2 +- wagtail/wagtailsnippets/tests.py | 3 +-- 25 files changed, 22 insertions(+), 65 deletions(-) diff --git a/wagtail/contrib/wagtailsitemaps/tests.py b/wagtail/contrib/wagtailsitemaps/tests.py index fd639e4ec..d2d612fa2 100644 --- a/wagtail/contrib/wagtailsitemaps/tests.py +++ b/wagtail/contrib/wagtailsitemaps/tests.py @@ -1,5 +1,4 @@ from django.test import TestCase -from django.core.urlresolvers import reverse from django.core.cache import cache from wagtail.wagtailcore.models import Page, Site diff --git a/wagtail/contrib/wagtailsitemaps/views.py b/wagtail/contrib/wagtailsitemaps/views.py index 4ef02de0b..04f31fdae 100644 --- a/wagtail/contrib/wagtailsitemaps/views.py +++ b/wagtail/contrib/wagtailsitemaps/views.py @@ -1,4 +1,3 @@ -from django.shortcuts import render from django.http import HttpResponse from django.core.cache import cache from django.conf import settings diff --git a/wagtail/contrib/wagtailstyleguide/views.py b/wagtail/contrib/wagtailstyleguide/views.py index 9c830073c..9683289d2 100644 --- a/wagtail/contrib/wagtailstyleguide/views.py +++ b/wagtail/contrib/wagtailstyleguide/views.py @@ -1,16 +1,10 @@ from django import forms -from django.db import models from django.shortcuts import render from django.utils.translation import ugettext as _ from django.contrib import messages from django.contrib.auth.decorators import permission_required -from wagtail.wagtailadmin.edit_handlers import PageChooserPanel -from wagtail.wagtailimages.edit_handlers import ImageChooserPanel -from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel - from wagtail.wagtailadmin.forms import SearchForm -from wagtail.wagtailcore.fields import RichTextField CHOICES = ( diff --git a/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py b/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py index ae2989be9..5782ee736 100644 --- a/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py +++ b/wagtail/contrib/wagtailstyleguide/wagtail_hooks.py @@ -1,14 +1,10 @@ -from django.conf import settings -from django.conf.urls import include, url +from django.conf.urls import url from django.core import urlresolvers -from django.utils.html import format_html, format_html_join from django.utils.translation import ugettext_lazy as _ from wagtail.wagtailadmin import hooks from wagtail.wagtailadmin.menu import MenuItem -from wagtail.wagtailimages import urls - from . import views diff --git a/wagtail/tests/utils.py b/wagtail/tests/utils.py index 02327799b..c44549401 100644 --- a/wagtail/tests/utils.py +++ b/wagtail/tests/utils.py @@ -1,7 +1,4 @@ -from django.test import TestCase from django.contrib.auth.models import User -from django.utils.six.moves.urllib.parse import urlparse, ParseResult -from django.http import QueryDict # We need to make sure that we're using the same unittest library that Django uses internally # Otherwise, we get issues with the "SkipTest" and "ExpectedFailure" exceptions being recognised as errors diff --git a/wagtail/wagtailadmin/edit_handlers.py b/wagtail/wagtailadmin/edit_handlers.py index 638072fa8..e6ead286d 100644 --- a/wagtail/wagtailadmin/edit_handlers.py +++ b/wagtail/wagtailadmin/edit_handlers.py @@ -1,6 +1,4 @@ import copy -import re -import datetime from six import string_types from six import text_type @@ -12,13 +10,10 @@ from django.template.loader import render_to_string from django.template.defaultfilters import addslashes from django.utils.safestring import mark_safe from django import forms -from django.db import models from django.forms.models import fields_for_model from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured, ValidationError +from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured from django.core.urlresolvers import reverse -from django.conf import settings -from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy from wagtail.wagtailcore.models import Page diff --git a/wagtail/wagtailadmin/tests/test_account_management.py b/wagtail/wagtailadmin/tests/test_account_management.py index 7128f8885..09b493de9 100644 --- a/wagtail/wagtailadmin/tests/test_account_management.py +++ b/wagtail/wagtailadmin/tests/test_account_management.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.core import mail -from wagtail.tests.utils import unittest, WagtailTestUtils +from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailusers.models import UserProfile diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 2e54218bf..9e95e50b1 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -7,8 +7,8 @@ from django.core import mail from django.core.paginator import Paginator from django.utils import timezone -from wagtail.tests.models import SimplePage, EventPage, StandardIndex, StandardChild, BusinessIndex, BusinessChild, BusinessSubIndex -from wagtail.tests.utils import unittest, WagtailTestUtils +from wagtail.tests.models import SimplePage, EventPage, StandardIndex, BusinessIndex, BusinessChild, BusinessSubIndex +from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailcore.models import Page, PageRevision from wagtail.wagtailcore.signals import page_published from wagtail.wagtailusers.models import UserProfile diff --git a/wagtail/wagtailadmin/tests/tests.py b/wagtail/wagtailadmin/tests/tests.py index f014c026d..4d5491f1d 100644 --- a/wagtail/wagtailadmin/tests/tests.py +++ b/wagtail/wagtailadmin/tests/tests.py @@ -1,6 +1,5 @@ from django.test import TestCase -from wagtail.tests.models import SimplePage, EventPage -from wagtail.tests.utils import unittest, WagtailTestUtils +from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailcore.models import Page from wagtail.wagtailadmin.tasks import send_email_task from django.core.urlresolvers import reverse diff --git a/wagtail/wagtailadmin/urls.py b/wagtail/wagtailadmin/urls.py index 35bb63d5e..1487eacb6 100644 --- a/wagtail/wagtailadmin/urls.py +++ b/wagtail/wagtailadmin/urls.py @@ -1,7 +1,6 @@ from django.conf.urls import url -from django.conf import settings -from wagtail.wagtailadmin.forms import LoginForm, PasswordResetForm +from wagtail.wagtailadmin.forms import PasswordResetForm from wagtail.wagtailadmin.views import account, chooser, home, pages, tags, userbar from wagtail.wagtailadmin import hooks diff --git a/wagtail/wagtailadmin/userbar.py b/wagtail/wagtailadmin/userbar.py index a641a7b1d..195e936b3 100644 --- a/wagtail/wagtailadmin/userbar.py +++ b/wagtail/wagtailadmin/userbar.py @@ -1,4 +1,3 @@ -from django.core.urlresolvers import reverse from django.template import RequestContext from django.template.loader import render_to_string diff --git a/wagtail/wagtailcore/compat.py b/wagtail/wagtailcore/compat.py index 71fbc39ac..54133e6e9 100644 --- a/wagtail/wagtailcore/compat.py +++ b/wagtail/wagtailcore/compat.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.core.exceptions import ImproperlyConfigured # A setting that can be used in foreign key declarations AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') diff --git a/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py b/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py index cb0d4cab8..9a69f5f2e 100644 --- a/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py +++ b/wagtail/wagtailcore/management/commands/publish_scheduled_pages.py @@ -1,6 +1,5 @@ from __future__ import print_function -import datetime import json from optparse import make_option diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 29cadff56..7f3070615 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -18,7 +18,6 @@ from django.contrib.auth.models import Group from django.conf import settings from django.template.response import TemplateResponse from django.utils import timezone -from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError from django.utils.functional import cached_property diff --git a/wagtail/wagtailcore/tests/test_management_commands.py b/wagtail/wagtailcore/tests/test_management_commands.py index 1e3ff28e1..ad1aa33aa 100644 --- a/wagtail/wagtailcore/tests/test_management_commands.py +++ b/wagtail/wagtailcore/tests/test_management_commands.py @@ -2,14 +2,12 @@ from datetime import timedelta from six import StringIO -from django.test import TestCase, Client -from django.http import HttpRequest, Http404 +from django.test import TestCase from django.core import management -from django.contrib.auth.models import User from django.utils import timezone -from wagtail.wagtailcore.models import Page, PageRevision, Site, UserPagePermissionsProxy -from wagtail.tests.models import EventPage, EventIndex, SimplePage +from wagtail.wagtailcore.models import Page, PageRevision +from wagtail.tests.models import SimplePage class TestFixTreeCommand(TestCase): diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index 58c359d72..2a257c743 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -1,11 +1,7 @@ -from six import StringIO - from django.test import TestCase, Client from django.http import HttpRequest, Http404 -from django.core import management -from django.contrib.auth.models import User -from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy +from wagtail.wagtailcore.models import Page, Site from wagtail.tests.models import EventPage, EventIndex, SimplePage diff --git a/wagtail/wagtailcore/tests/test_page_permissions.py b/wagtail/wagtailcore/tests/test_page_permissions.py index eb2fe3257..be4797d3d 100644 --- a/wagtail/wagtailcore/tests/test_page_permissions.py +++ b/wagtail/wagtailcore/tests/test_page_permissions.py @@ -1,12 +1,8 @@ -from six import StringIO - -from django.test import TestCase, Client -from django.http import HttpRequest, Http404 -from django.core import management +from django.test import TestCase from django.contrib.auth.models import User -from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy -from wagtail.tests.models import EventPage, EventIndex, SimplePage +from wagtail.wagtailcore.models import Page, UserPagePermissionsProxy +from wagtail.tests.models import EventPage class TestPagePermission(TestCase): diff --git a/wagtail/wagtailcore/tests/test_page_queryset.py b/wagtail/wagtailcore/tests/test_page_queryset.py index 2c4e46c9c..bc07f0fba 100644 --- a/wagtail/wagtailcore/tests/test_page_queryset.py +++ b/wagtail/wagtailcore/tests/test_page_queryset.py @@ -1,12 +1,7 @@ -from six import StringIO +from django.test import TestCase -from django.test import TestCase, Client -from django.http import HttpRequest, Http404 -from django.core import management -from django.contrib.auth.models import User - -from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy -from wagtail.tests.models import EventPage, EventIndex, SimplePage +from wagtail.wagtailcore.models import Page +from wagtail.tests.models import EventPage class TestPageQuerySet(TestCase): diff --git a/wagtail/wagtailcore/tests/test_whitelist.py b/wagtail/wagtailcore/tests/test_whitelist.py index 28b63d9fb..d96100382 100644 --- a/wagtail/wagtailcore/tests/test_whitelist.py +++ b/wagtail/wagtailcore/tests/test_whitelist.py @@ -1,4 +1,4 @@ -from bs4 import BeautifulSoup, NavigableString +from bs4 import BeautifulSoup from django.test import TestCase from wagtail.wagtailcore.whitelist import ( diff --git a/wagtail/wagtailembeds/tests.py b/wagtail/wagtailembeds/tests.py index ea49ccdb5..73f6a7f4b 100644 --- a/wagtail/wagtailembeds/tests.py +++ b/wagtail/wagtailembeds/tests.py @@ -1,4 +1,3 @@ -from six.moves.urllib.request import urlopen import six.moves.urllib.request from six.moves.urllib.error import URLError diff --git a/wagtail/wagtailembeds/views/chooser.py b/wagtail/wagtailembeds/views/chooser.py index b364d83b7..5892ed91b 100644 --- a/wagtail/wagtailembeds/views/chooser.py +++ b/wagtail/wagtailembeds/views/chooser.py @@ -1,5 +1,4 @@ from django.forms.util import ErrorList -from django.conf import settings from django.utils.translation import ugettext as _ from wagtail.wagtailadmin.modal_workflow import render_modal_workflow diff --git a/wagtail/wagtailsearch/backends/elasticsearch.py b/wagtail/wagtailsearch/backends/elasticsearch.py index e4d716ea1..91536d744 100644 --- a/wagtail/wagtailsearch/backends/elasticsearch.py +++ b/wagtail/wagtailsearch/backends/elasticsearch.py @@ -1,7 +1,6 @@ from __future__ import absolute_import import json -import warnings from django.db import models diff --git a/wagtail/wagtailsearch/tests/test_editorspicks.py b/wagtail/wagtailsearch/tests/test_editorspicks.py index 99debab40..b2ebece9f 100644 --- a/wagtail/wagtailsearch/tests/test_editorspicks.py +++ b/wagtail/wagtailsearch/tests/test_editorspicks.py @@ -1,7 +1,7 @@ from django.test import TestCase from django.core.urlresolvers import reverse -from wagtail.tests.utils import unittest, WagtailTestUtils +from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailsearch import models diff --git a/wagtail/wagtailsearch/tests/test_queries.py b/wagtail/wagtailsearch/tests/test_queries.py index 10785187e..d4d2797df 100644 --- a/wagtail/wagtailsearch/tests/test_queries.py +++ b/wagtail/wagtailsearch/tests/test_queries.py @@ -4,7 +4,7 @@ from django.test import TestCase from django.core import management from wagtail.wagtailsearch import models -from wagtail.tests.utils import unittest, WagtailTestUtils +from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailsearch.utils import normalise_query_string diff --git a/wagtail/wagtailsnippets/tests.py b/wagtail/wagtailsnippets/tests.py index 1b1e012de..56b3f8b6a 100644 --- a/wagtail/wagtailsnippets/tests.py +++ b/wagtail/wagtailsnippets/tests.py @@ -1,8 +1,7 @@ from django.test import TestCase from django.core.urlresolvers import reverse -from django.contrib.auth.models import User -from wagtail.tests.utils import unittest, WagtailTestUtils +from wagtail.tests.utils import WagtailTestUtils from wagtail.tests.models import Advert, AlphaSnippet, ZuluSnippet from wagtail.wagtailsnippets.models import register_snippet, SNIPPET_MODELS From 09cdc99ff21d794a16034ec7f9f7679efafabfca Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 3 Jul 2014 10:14:01 +0100 Subject: [PATCH 73/76] Addressing issue where ordering of new inlinepanel items created two items of the same order --- wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js b/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js index 4f76ce8a2..a5dd67eeb 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/page-editor.js @@ -247,7 +247,10 @@ function InlinePanel(opts) { } self.initChildControls(fixPrefix(opts.emptyChildFormPrefix)); if (opts.canOrder) { - $(fixPrefix('#id_' + opts.emptyChildFormPrefix + '-ORDER')).val(formCount); + /* NB form hidden inputs use 0-based index and only increment formCount *after* this function is run. + Therefore formcount and order are currently equal and order must be incremented + to ensure it's *greater* than previous item */ + $(fixPrefix('#id_' + opts.emptyChildFormPrefix + '-ORDER')).val(formCount + 1); } self.updateMoveButtonDisabledStates(); From 1a3558cabd2751a975fcabd11979874ba17a54ee Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 3 Jul 2014 10:25:26 +0100 Subject: [PATCH 74/76] Added DeprecationWarning to wagtailadmin.hooks --- wagtail/wagtailadmin/hooks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wagtail/wagtailadmin/hooks.py b/wagtail/wagtailadmin/hooks.py index 75c3a8991..989c43bd8 100644 --- a/wagtail/wagtailadmin/hooks.py +++ b/wagtail/wagtailadmin/hooks.py @@ -1,4 +1,11 @@ # The 'hooks' module is now part of wagtailcore. # Imports are provided here for backwards compatibility +import warnings + +warnings.warn( + "The wagtail.wagtailadmin.hooks module has been moved. " + "Use wagtail.wagtailcore.hooks instead.", DeprecationWarning) + + from wagtail.wagtailcore.hooks import register, get_hooks From 3ab75c0cfeb0b5e216b30f4b68b5325ea9c0a3c4 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 3 Jul 2014 11:05:56 +0100 Subject: [PATCH 75/76] Update README.rst --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 2866e6c8a..8b99e265a 100644 --- a/README.rst +++ b/README.rst @@ -37,6 +37,8 @@ Getting started * To get you up and running quickly, we've provided a demonstration site with all the configuration in place, at `github.com/torchbox/wagtaildemo `_; see the `README `_ for installation instructions. * See the `Getting Started `_ docs for installation (with the demo app) on a fresh Debian/Ubuntu box with production-ready dependencies, on OS X and on a Vagrant box. * `Serafeim Papastefanos `_ has written a `tutorial `_ with all the steps to build a simple Wagtail site from scratch. +* We've also provided a skeletal django-template to get started on a blank site: +https://github.com/torchbox/wagtail-template Documentation ~~~~~~~~~~~~~ From b0ef94a2db6a5534f62bc959f2d63a40acabd9fd Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Thu, 3 Jul 2014 11:06:43 +0100 Subject: [PATCH 76/76] Update README.rst --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 8b99e265a..9ef31fa8e 100644 --- a/README.rst +++ b/README.rst @@ -37,8 +37,7 @@ Getting started * To get you up and running quickly, we've provided a demonstration site with all the configuration in place, at `github.com/torchbox/wagtaildemo `_; see the `README `_ for installation instructions. * See the `Getting Started `_ docs for installation (with the demo app) on a fresh Debian/Ubuntu box with production-ready dependencies, on OS X and on a Vagrant box. * `Serafeim Papastefanos `_ has written a `tutorial `_ with all the steps to build a simple Wagtail site from scratch. -* We've also provided a skeletal django-template to get started on a blank site: -https://github.com/torchbox/wagtail-template +* We've also provided a skeletal django-template to get started on a blank site: https://github.com/torchbox/wagtail-template Documentation ~~~~~~~~~~~~~