Repository: wagtail/wagtail

Issue:
Crash when editing a draft page that has no revisions
Wagtail expects draft pages to have a latest revision. If you unpublish a page in bakerydemo (where none of the pages have revisions out of the box) and attempt to edit it, you get the following error:

```
Environment:


Request Method: GET
Request URL: http://localhost:8000/admin/pages/35/edit/

Django Version: 2.0.3
Python Version: 3.6.1
Installed Applications:
['bakerydemo.base',
 'bakerydemo.blog',
 'bakerydemo.breads',
 'bakerydemo.locations',
 'bakerydemo.search',
 'condensedinlinepanel',
 'unionpanel',
 'wagtail.contrib.search_promotions',
 'wagtail.contrib.forms',
 'wagtail.contrib.redirects',
 'wagtail.embeds',
 'wagtail.sites',
 'wagtail.users',
 'wagtail.snippets',
 'wagtail.documents',
 'wagtail.images',
 'wagtail.search',
 'wagtail.admin',
 'wagtail.contrib.modeladmin',
 'wagtail.contrib.routable_page',
 'wagtail.core',
 'rest_framework',
 'modelcluster',
 'taggit',
 'wagtailfontawesome',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'wagtail.core.middleware.SiteMiddleware',
 'wagtail.contrib.redirects.middleware.RedirectMiddleware']



Traceback:

File "/home/vagrant/.virtualenvs/bakerydemo/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  35.             response = get_response(request)

File "/home/vagrant/.virtualenvs/bakerydemo/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  128.                 response = self.process_exception_by_middleware(e, request)

File "/home/vagrant/.virtualenvs/bakerydemo/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  126.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/vagrant/.virtualenvs/bakerydemo/lib/python3.6/site-packages/django/views/decorators/cache.py" in _cache_controlled
  31.             response = viewfunc(request, *args, **kw)

File "/vagrant/wagtail/wagtail/admin/urls/__init__.py" in wrapper
  102.             return view_func(request, *args, **kwargs)

File "/vagrant/wagtail/wagtail/admin/decorators.py" in decorated_view
  34.             return view_func(request, *args, **kwargs)

File "/vagrant/wagtail/wagtail/admin/views/pages.py" in edit
  521.         page_for_status = latest_revision.page.specific

Exception Type: AttributeError at /admin/pages/35/edit/
Exception Value: 'NoneType' object has no attribute 'page'
```


Discussion:
We should probably be using `page.get_latest_revision_as_page()` instead of `latest_revision.page.specific`, as it includes a fallback for using the real page record if there are no revisions: https://github.com/wagtail/wagtail/blob/master/wagtail/core/models.py#L679-L682

Resolving patch:
diff --git a/wagtail/admin/views/pages.py b/wagtail/admin/views/pages.py
--- a/wagtail/admin/views/pages.py
+++ b/wagtail/admin/views/pages.py
@@ -308,8 +308,9 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
 
 
 def edit(request, page_id):
-    latest_revision = get_object_or_404(Page, id=page_id).get_latest_revision()
-    page = get_object_or_404(Page, id=page_id).get_latest_revision_as_page()
+    real_page_record = get_object_or_404(Page, id=page_id)
+    latest_revision = real_page_record.get_latest_revision()
+    page = real_page_record.get_latest_revision_as_page()
     parent = page.get_parent()
 
     content_type = ContentType.objects.get_for_model(page)
@@ -515,9 +516,9 @@ def edit(request, page_id):
 
         messages.warning(request, _("This page is currently awaiting moderation"), buttons=buttons)
 
-    # Page status needs to present the version of the page containing the correct live URL
-    if page.has_unpublished_changes:
-        page_for_status = latest_revision.page.specific
+    if page.live and page.has_unpublished_changes:
+        # Page status needs to present the version of the page containing the correct live URL
+        page_for_status = real_page_record.specific
     else:
         page_for_status = page
 


Test patch:
diff --git a/wagtail/admin/tests/test_pages_views.py b/wagtail/admin/tests/test_pages_views.py
--- a/wagtail/admin/tests/test_pages_views.py
+++ b/wagtail/admin/tests/test_pages_views.py
@@ -1225,6 +1225,15 @@ def setUp(self):
         )
         self.root_page.add_child(instance=self.single_event_page)
 
+        self.unpublished_page = SimplePage(
+            title="Hello unpublished world!",
+            slug="hello-unpublished-world",
+            content="hello",
+            live=False,
+            has_unpublished_changes=True,
+        )
+        self.root_page.add_child(instance=self.unpublished_page)
+
         # Login
         self.user = self.login()
 
@@ -1237,6 +1246,11 @@ def test_page_edit(self):
         self.assertContains(response, '<legend>Speaker lineup</legend>')
         self.assertContains(response, 'Add speakers')
 
+    def test_edit_draft_page_with_no_revisions(self):
+        # Tests that the edit page loads
+        response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.unpublished_page.id, )))
+        self.assertEqual(response.status_code, 200)
+
     def test_edit_multipart(self):
         """
         Test checks if 'enctype="multipart/form-data"' is added and only to forms that require multipart encoding.