Enabling private pages

July 22, 2018, 1:36 a.m.   wschaub  


I wanted to make a private blog post so I could share a future blog post with someone for review before publishing. Turns out that the implementation of this blog software prevents the page privacy settings from working out of the box.

I would like to say I figured this out myself but all of the credit goes to Mattwestcott on the wagtailcms slack server.

As far as I understand it the implementation of the BlogPost model changes the URL routing for a PostPage which renders the privacy checks useless.

The following diff shows the changes I made based on his help.

The first change is to the BlogPage model itself. I'm not 100% sure what the hook is doing but it does seem to remove the prepended date for private pages which allows the protection to kick in which is what I want.

blog/models.py

@@ -13,6 +13,7 @@ from django.utils.dateformat import DateFormat
 from django.utils.formats import date_format

 import wagtail
+from wagtail.core import hooks
 from wagtail.core.models import Page
 from wagtail.core.fields import RichTextField

@@ -58,7 +59,7 @@ class BlogPage(RoutablePageMixin, Page):
         return context

     def get_posts(self):
-        return PostPage.objects.descendant_of(self).live().order_by('-date')
+        return PostPage.objects.descendant_of(self).live().public().order_by('-date')

     @route(r'^(\d{4})/$')
     @route(r'^(\d{4})/(\d{2})/$')
@@ -81,6 +82,10 @@ class BlogPage(RoutablePageMixin, Page):
         post_page = self.get_posts().filter(slug=slug).first()
         if not post_page:
             raise Http404
+        for fn in hooks.get_hooks('before_serve_page'):
+            result = fn(post_page, request, args, kwargs)
+            if isinstance(result, HttpResponse):
+                return result
         return Page.serve(post_page, request, *args, **kwargs)

     @route(r'^tag/(?P<tag>[-\w]+)/$')

The for fn in hooks.get_hooks(... bit is what was suggested. The change return PostPage.objects.descendant_of(self).live().public().order_by('-date') in the get_posts method is my change to be sure private pages do not show up in the Blog index. I could probably fix this at the template layer instead and I intend to look into that later.

Now on to a few other changes that I've added:

blog/templates/blog/password_required.html

This is the suggested template from the wagtail docs nothing special we refer back to it in settings.py later on.

+++ b/longearsforlife/blog/templates/blog/password_required.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <title>Password required</title>
+    </head>
+    <body>
+        <h1>Password required</h1>
+        <p>You need a password to access this page.</p>
+        <form action="{{ action_url }}" method="POST">
+            {% csrf_token %}
+
+            {{ form.non_field_errors }}
+
+            <div>
+                {{ form.password.errors }}
+                {{ form.password.label_tag }}
+                {{ form.password }}
+            </div>
+
+            {% for field in form.hidden_fields %}
+                {{ field }}
+            {% endfor %}
+            <input type="submit" value="Continue" />
+        </form>
+    </body>
+</html>

longearsforlife/settings.py

we point to the password required template here.

@@ -151,3 +151,4 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
 MEDIA_URL = '/media/'
 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

+PASSWORD_REQUIRED_TEMPLATE = 'blog/password_required.html'

longearsforlife/urls.py

I'm pretty sure I need to add the login urls to urls.py so we did that.

@@ -29,6 +29,7 @@ urlpatterns = [
     url(r'^cms/', include(wagtailadmin_urls)),
     url(r'^documents/', include(wagtaildocs_urls)),
     url(r'^blog/', include(wagtail_urls)),
+    url(r'^', include('django.contrib.auth.urls')),
     url(r'^$', redirect_to_blog, name='root')
 ]