wagtail: Preview mode doesn't work under WSGI/Gunicorn
Issue Summary
Attempt to preview any page results in exception.
Steps to Reproduce
- Open any page in the WagTail CMS for editing
- Click Preview button
Site is running under nginx/gunicorn. When I run Django development server, this problem doesn’t happen, preview works as expected. This is clearly related to gunicorn, but when I run local instance of gunicorn, it doesn’t happen either. Only on the remote site.
It seems that tricks that Wagtail uses to load preview in a new window causes the problem.
After reading Wagtail source code I found this:
preview_mode = request.GET.get('mode', page.default_preview_mode)
response = page.serve_preview(page.dummy_request(request), preview_mode)
response['X-Wagtail-Preview'] = 'ok'
return response
in wagtail/wagtailadmin/views/pages.py
Apparently, differences in requests under local django devserver and gunicorn causes some malfunction in the Django authentication middleware and it fails to load currently logged in user while in preview mode.
Since this happens only in WagTail preview and all other views and dependencies functioned perfectly well in this setup for years, I assume this is a Wagtail bug.
I have “fixed” this issue in my case by changing the line from:
response = page.serve_preview(page.dummy_request(request), preview_mode)
to:
response = page.serve_preview(request, preview_mode)
On line 562 in wagtail/wagtailadmin/views/pages.py
I see that master branch uses the same dummy request technique that probably will also fail when running under gunicorn.
Technical details
AttributeError at /cms/pages/29/edit/preview/
'WSGIRequest' object has no attribute 'user'
Request Method: POST
Request URL: https://staging.royalorchard.com/cms/pages/29/edit/preview/
Django Version: 1.10.6
Python Executable: /home/rolc/.virtualenvs/dev/bin/python3
Python Version: 3.5.2
Python Path: ['/home/rolc/dev', '/home/rolc/.virtualenvs/dev/bin', '/home/rolc/.virtualenvs/dev/lib/python35.zip', '/home/rolc/.virtualenvs/dev/lib/python3.5', '/home/rolc/.virtualenvs/dev/lib/python3.5/plat-x86_64-linux-gnu', '/home/rolc/.virtualenvs/dev/lib/python3.5/lib-dynload', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages']
Server time: Fri, 14 Apr 2017 03:21:22 -0400
Installed Applications:
('django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.humanize',
'django.contrib.staticfiles',
'django.contrib.admindocs',
'django.contrib.flatpages',
'tagging',
'django_extensions',
'filebrowser',
'crispy_forms',
'floppyforms',
'allauth',
'allauth.account',
'allauth.socialaccount',
'reversion',
'papertrail',
'wagtail.wagtailforms',
'wagtail.wagtailredirects',
'wagtail.wagtailembeds',
'wagtail.wagtailsites',
'wagtail.wagtailusers',
'wagtail.wagtailsnippets',
'wagtail.wagtaildocs',
'wagtail.wagtailimages',
'wagtail.wagtailsearch',
'wagtail.wagtailadmin',
'wagtail.wagtailcore',
'wagtail.contrib.table_block',
'modelcluster',
'taggit',
'taggit_templatetags2',
'royal_orchard.users',
'royal_orchard.reports',
'royal_orchard.documents',
'royal_orchard.emails',
'royal_orchard.blog',
'royal_orchard.reservations',
'royal_orchard.vendors',
'royal_orchard.flickr',
'royal_orchard.search',
'royal_orchard.userpages',
'tinymce',
'sorl.thumbnail',
'constance',
'constance.backends.database',
'djmail',
'bootstrap_pagination')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.contrib.admindocs.middleware.XViewMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'wagtail.wagtailcore.middleware.SiteMiddleware',
'wagtail.wagtailredirects.middleware.RedirectMiddleware')
Traceback:
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
42. response = get_response(request)
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/core/handlers/base.py" in _legacy_get_response
249. response = self._get_response(request)
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
217. response = self.process_exception_by_middleware(e, request)
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
215. response = response.render()
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/template/response.py" in render
109. self.content = self.rendered_content
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/template/response.py" in rendered_content
86. content = template.render(context, self._request)
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/template/backends/django.py" in render
66. return self.template.render(context)
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/django/template/base.py" in render
206. with context.bind_template(self):
File "/usr/lib/python3.5/contextlib.py" in __enter__
59. return next(self.gen)
File "/home/rolc/.virtualenvs/dev/lib/python3.5/site-packages/debug_toolbar/panels/templates/panel.py" in _request_context_bind_template
49. context = processor(self.request)
File "/home/rolc/dev/royal_orchard/blog/context_processors.py" in context
10. user = request.user
Exception Type: AttributeError at /cms/pages/29/edit/preview/
Exception Value: 'WSGIRequest' object has no attribute 'user'
Request information:
USER: Robert ***********
GET: No GET data
POST:
seo_title = ''
expire_at = ''
csrfmiddlewaretoken = '******'
body-0-type = 'paragraph'
body-count = '1'
go_live_at = ''
body-0-deleted = ''
search_description = ''
body-0-order = '0'
slug = '******-history'
title = '****** History'
next = ''
body-0-value = '<p>I feel so very privileged to be invited into your home and if we also feel a little intimidated because we are talking to folks who have generational knowledge of this wonderful house and I know as we do so we are little bit nervous about giving you advice on wh</p>'
- Python version: 3.5.2.
- Django version: 1.10.6.
- Wagtail version: 1.9.
- Browser version: Firefox 52.0.2 on Ubuntu Linux.
- Gunicorn: 19.7.1
- Nginx: 1.10.0
About this issue
- Original URL
- State: open
- Created 7 years ago
- Reactions: 1
- Comments: 27 (13 by maintainers)
Commits related to this issue
- Introduce make_preview_request method to supersede dummy_request Django-1.10-style middleware inherently doesn't support applying middleware to the request independently of running the view function,... — committed to gasman/wagtail by gasman 5 years ago
- Upgrade Wagtail to v2.7.2 (#4) * Adds Collections section to editor's guide * Add form media to users/edit.html (#5390) * Adds screen-reader-only text to the close button for modals (#5274, #53... — committed to wethegit/wagtail by huynhsontung 4 years ago
@gasman Thank you! Your suggestion was right. I’ve found the problem: Wagtail uses its own
Sitemodel which is set tolocalhostby default. Since, on staging/production sites there’s no localhost insettings.ALLOWED_HOSTS,CommonMiddlewarewas failing and exception message was silently suppressed. So, no session and authentication middleware were run, but exception was lost somewhere, so it was almost impossible to find the real cause of the problem.I think this is still a bug. It should be either documented somewhere to set proper hostname for the wagtail
Site(can it take value from DjangoSitewhich is properly set to staging hostname?) or the real exception should be displayed to the user, not silently ignored.See #5427 for a full diagnosis and (hopefully) fix.
@BenSturmfels @SalahAdDin The problem only occurs when wagtail site hostname mismatches the real hostname. You need to check if you have the site hostname set correctly in the Django admin.
Reopening this, since the current behaviour is a regression from the pre-Django-1.10 middleware where we ignored any responses (error or otherwise) originating from middleware. I’m not sure of the best way to fix it, though…
I can’t see a good way to do that with Django’s current middleware implementation - in the old-style middleware, we called
process_requeston each middleware class in turn, so we knew when a middleware was returning its own response. In the new style, the outer classes completely wrap the inner ones, so there’s no way to tell from outside whether the response originates from the middleware or the actual view.This might work, but it feels like a “band-aid” fix - it’s possible that other middlewares will have similar issues. (In fact, I’m not sure that CommonMiddleware is the culprit here - checking
ALLOWED_HOSTSseems to happen in the Request object itself, so it could occur in any place that tries to retrieve hostname.)This isn’t an option - the page template will often rely on variables that are inserted in middleware, such as
request.userorrequest.site. For similar reasons I don’t think we can avoid the dummy_request mechanism - the template author should be free to use any properties of the request object, so it’s Wagtail’s responsibility to make sure that the request object is as close as possible to the one that would be seen on a real page request.One possibility is to add a system check that generates a warning on startup if any Site records have hostnames that aren’t covered by ALLOWED_HOSTS. However, one goal I’ve always had with the design of Wagtail is that Site records should just work out of the box for single-site installations, and you shouldn’t have to care about configuring them until you come to build a multi-site project. (For example, that’s why
Page.get_url()returns a local URL without the hostname on single-site installations). So maybe it’s better to pre-emptively check ALLOWED_HOSTS indummy_request, and silently fix up the host header if we can see it’s going to break…Incidentally, the unhelpfulness of the
'WSGIRequest' object has no attribute 'user'error is not Wagtail’s fault - it’s because you have code in context_processors.py that’s not robust enough against error conditions (i.e. it’s expecting to find attributes ofrequestthat aren’t guaranteed to be there).Quick fix for my site was to add localhost to the ALLOWED_HOSTS string. Worked like a charm.