diff --git a/CHANGELOG.txt b/CHANGELOG.txt index fa594087d..1f5e4fa35 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -22,6 +22,7 @@ Changelog * `wagtailforms.models.AbstractEmailForm` now supports multiple email recipients (Serafeim Papastefanos) * Added the ``include_block`` template tag for improved StreamField template inclusion (Matt Westcott) * Added ability to delete users through Settings -> Users (Vincent Audebert; thanks also to Ludolf Takens and Tobias Schmidt for alternative implementations) + * Page previews now pass additional HTTP headers, to simulate the page being viewed by the logged-in user and avoid clashes with middleware (Robert Rollins) * Fix: Email templates and document uploader now support custom `STATICFILES_STORAGE` (Jonny Scholes) * Fix: Removed alignment options (deprecated in HTML and not rendered by Wagtail) from `TableBlock` context menu (Moritz Pfeiffer) * Fix: Fixed incorrect CSS path on ModelAdmin's "choose a parent page" view diff --git a/docs/releases/1.6.rst b/docs/releases/1.6.rst index fda4abe9e..f6ef31218 100644 --- a/docs/releases/1.6.rst +++ b/docs/releases/1.6.rst @@ -37,6 +37,7 @@ Minor features * ``wagtailforms.models.AbstractEmailForm`` now supports multiple email recipients (Serafeim Papastefanos) * Added the ``include_block`` template tag for improved StreamField template inclusion. See :doc:`/topics/streamfield` for documentation (Matt Westcott) * Added ability to delete users through Settings -> Users (Vincent Audebert; thanks also to Ludolf Takens and Tobias Schmidt for alternative implementations) + * Page previews now pass additional HTTP headers, to simulate the page being viewed by the logged-in user and avoid clashes with middleware (Robert Rollins) Bug fixes diff --git a/wagtail/wagtailadmin/templatetags/wagtailuserbar.py b/wagtail/wagtailadmin/templatetags/wagtailuserbar.py index 8c7cd1413..a8e62cdd0 100644 --- a/wagtail/wagtailadmin/templatetags/wagtailuserbar.py +++ b/wagtail/wagtailadmin/templatetags/wagtailuserbar.py @@ -40,6 +40,11 @@ def wagtailuserbar(context, position='bottom-right'): if not request.user.has_perm('wagtailadmin.access_admin'): return '' + # Don't render if this is a preview. Since some routes can render the userbar without going through Page.serve(), + # request.is_preview might not be defined. + if getattr(request, 'is_preview', False): + return '' + # Only render if the context contains a variable referencing a saved page page = get_page_instance(context) if page is None: diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index 6f45effb0..1f2e76208 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -496,7 +496,7 @@ def delete(request, page_id): def view_draft(request, page_id): page = get_object_or_404(Page, id=page_id).get_latest_revision_as_page() - return page.serve_preview(page.dummy_request(), page.default_preview_mode) + return page.serve_preview(page.dummy_request(request), page.default_preview_mode) def preview_on_edit(request, page_id): @@ -516,7 +516,7 @@ def preview_on_edit(request, page_id): page.full_clean() preview_mode = request.GET.get('mode', page.default_preview_mode) - response = page.serve_preview(page.dummy_request(), preview_mode) + response = page.serve_preview(page.dummy_request(request), preview_mode) response['X-Wagtail-Preview'] = 'ok' return response @@ -576,7 +576,7 @@ def preview_on_create(request, content_type_app_name, content_type_model_name, p page.path = Page._get_children_path_interval(parent_page.path)[1] preview_mode = request.GET.get('mode', page.default_preview_mode) - response = page.serve_preview(page.dummy_request(), preview_mode) + response = page.serve_preview(page.dummy_request(request), preview_mode) response['X-Wagtail-Preview'] = 'ok' return response @@ -1013,4 +1013,4 @@ def revisions_view(request, page_id, revision_id): revision = get_object_or_404(page.revisions, id=revision_id) revision_page = revision.as_page_object() - return revision_page.serve_preview(page.dummy_request(), page.default_preview_mode) + return revision_page.serve_preview(page.dummy_request(request), page.default_preview_mode) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 57dff11f2..a58ec4120 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -1168,12 +1168,15 @@ class Page(six.with_metaclass(PageBase, MP_Node, index.Indexed, ClusterableModel user_perms = UserPagePermissionsProxy(user) return user_perms.for_page(self) - def dummy_request(self): + def dummy_request(self, original_request=None, **meta): """ Construct a HttpRequest object that is, as far as possible, representative of ones that would receive this page as a response. Used for previewing / moderation and any other place where we want to display a view of this page in the admin interface without going through the regular page routing logic. + + If you pass in a real request object as original_request, additional information (e.g. client IP, cookies) + will be included in the dummy request. """ url = self.full_url if url: @@ -1195,14 +1198,30 @@ class Page(six.with_metaclass(PageBase, MP_Node, index.Indexed, ClusterableModel path = '/' port = 80 - request = WSGIRequest({ + dummy_values = { 'REQUEST_METHOD': 'GET', 'PATH_INFO': path, 'SERVER_NAME': hostname, 'SERVER_PORT': port, 'HTTP_HOST': hostname, 'wsgi.input': StringIO(), - }) + } + + # Add important values from the original request object, if it was provided. + if original_request: + if original_request.META.get('REMOTE_ADDR'): + dummy_values['REMOTE_ADDR'] = original_request.META['REMOTE_ADDR'] + if original_request.META.get('HTTP_X_FORWARDED_FOR'): + dummy_values['HTTP_X_FORWARDED_FOR'] = original_request.META['HTTP_X_FORWARDED_FOR'] + if original_request.META.get('HTTP_COOKIE'): + dummy_values['HTTP_COOKIE'] = original_request.META['HTTP_COOKIE'] + if original_request.META.get('HTTP_USER_AGENT'): + dummy_values['HTTP_USER_AGENT'] = original_request.META['HTTP_USER_AGENT'] + + # Add additional custom metadata sent by the caller. + dummy_values.update(**meta) + + request = WSGIRequest(dummy_values) # Apply middleware to the request - see http://www.mellowmorning.com/2011/04/18/mock-django-request-for-testing/ handler = BaseHandler() diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index 0f19e0dda..7d64302f8 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -10,6 +10,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.http import Http404, HttpRequest from django.test import Client, TestCase +from django.test.client import RequestFactory from django.test.utils import override_settings from wagtail.tests.testapp.models import ( @@ -1156,6 +1157,24 @@ class TestDummyRequest(TestCase): self.assertEqual(request.path, '/events/') self.assertEqual(request.META['HTTP_HOST'], 'localhost') + def test_dummy_request_for_accessible_page_with_original_request(self): + event_index = Page.objects.get(url_path='/home/events/') + original_headers = { + 'REMOTE_ADDR': '192.168.0.1', + 'HTTP_X_FORWARDED_FOR': '192.168.0.2,192.168.0.3', + 'HTTP_COOKIE': "test=1;blah=2", + 'HTTP_USER_AGENT': "Test Agent", + } + factory = RequestFactory(**original_headers) + original_request = factory.get('/home/events/') + request = event_index.dummy_request(original_request) + + # request should have the all the special headers we set in original_request + self.assertEqual(request.META['REMOTE_ADDR'], original_request.META['REMOTE_ADDR']) + self.assertEqual(request.META['HTTP_X_FORWARDED_FOR'], original_request.META['HTTP_X_FORWARDED_FOR']) + self.assertEqual(request.META['HTTP_COOKIE'], original_request.META['HTTP_COOKIE']) + self.assertEqual(request.META['HTTP_USER_AGENT'], original_request.META['HTTP_USER_AGENT']) + @override_settings(ALLOWED_HOSTS=['production.example.com']) def test_dummy_request_for_inaccessible_page_should_use_valid_host(self): root_page = Page.objects.get(url_path='/')