Repository: wagtail/wagtail

Issue:
CMS Admin Exception after deleting a page
### Issue Summary

The _Your most recent edits_ panel causes a 500 error when one of the recent edits was to a page which has since been deleted.

### Steps to Reproduce

1. Edit a page.
2. Delete the page.
3. View the CMS admin home page.

This seems to be a result of the page edit links in this panel. E.g. https://github.com/wagtail/wagtail/blob/stable/4.0.x/wagtail/admin/templates/wagtailadmin/home/recent_edits.html#L23

This is the stack trace of the exception:

```
NoReverseMatch: Reverse for 'edit' with arguments '('',)' not found. 1 pattern(s) tried: ['admin/cms/pages/(?P<page_id>[0-9]+)/edit/\\Z']
  File "django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "django/core/handlers/base.py", line 220, in _get_response
    response = response.render()
  File "django/template/response.py", line 114, in render
    self.content = self.rendered_content
  File "django/template/response.py", line 92, in rendered_content
    return template.render(context, self._request)
  File "django/template/backends/django.py", line 62, in render
    return self.template.render(context)
  File "django/template/base.py", line 175, in render
    return self._render(context)
  File "django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/defaulttags.py", line 322, in render
    return nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/defaulttags.py", line 238, in render
    nodelist.append(node.render_annotated(context))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/library.py", line 237, in render
    output = self.func(*resolved_args, **resolved_kwargs)
  File "wagtail/admin/templatetags/wagtailadmin_tags.py", line 876, in component
    return obj.render_html(context)
  File "wagtail/admin/ui/components.py", line 20, in render_html
    return template.render(context_data)
  File "django/template/backends/django.py", line 62, in render
    return self.template.render(context)
  File "django/template/base.py", line 175, in render
    return self._render(context)
  File "django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/defaulttags.py", line 322, in render
    return nodelist.render(context)
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "wagtail/admin/templatetags/wagtailadmin_tags.py", line 948, in render
    children = self.nodelist.render(context) if self.nodelist else ""
  File "django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/defaulttags.py", line 238, in render
    nodelist.append(node.render_annotated(context))
  File "django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "django/template/defaulttags.py", line 472, in render
    url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)
  File "django/urls/base.py", line 88, in reverse
    return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
  File "django/urls/resolvers.py", line 828, in _reverse_with_prefix
    raise NoReverseMatch(msg)
```

### Technical details

-   Python version: 3.10
-   Django version: 4.1
-   Wagtail version: 4.0.1
-   Browser version: not important



Discussion:
Hi @crgwbr - thanks for the report. However, I'm unable to replicate this as follows on a fresh Wagtail 4.0.1 instance:

* From a newly created Python 3.10 virtualenv:
  ```
  pip install "wagtail==4.0.1"
  wagtail start myproject
  cd myproject
  ./manage.py migrate
  ./manage.py createsuperuser
  ./manage.py runserver
  ```
* Log in to http://localhost:8000/admin/
* From http://localhost:8000/admin/pages/3/edit/ , change the title from Home to Home2 and save as draft
* Return to http://localhost:8000/admin/; observe that "Home2" appears in "Your most recent edits"
* Go back to http://localhost:8000/admin/pages/3/edit/ and select "Delete" from the top menu
* Select Yes - at this point I'm redirected back to http://localhost:8000/admin/pages/3/edit/ which gives a 404 error. This _is_ a bug and should be investigated...
* Go back to http://localhost:8000/admin/ - this renders as expected, showing 0 pages and no "Your most recent edits" panel.

My best guess is that the error you're seeing is caused by custom code in your project - however, if you're able to replicate this on a fresh Wagtail instance, please provide further details and I'll be happy to reopen and investigate further.
Hi, I am facing the same issue, What I did was created a page, and edited it and after that, I had some other work so I kept it idle for 20 min and when I looked back at the admin page I got the same error in the `/admin/` page.

So what I did after that was, I deleted the recent model I created but the results were the same. Then I revert back to wagtail 4.0 but still, I was getting the same Issue. 

**Solved**
I have removed all my migrations and dropped my DB tables to start over with Wagtail v 4.0 and there is no error after editing and deleting the page with the same custom code I created at the very first.

Look like it's a bug either for wagtail 4.0 or 4.0.1 

Resolving patch:
diff --git a/wagtail/admin/views/home.py b/wagtail/admin/views/home.py
--- a/wagtail/admin/views/home.py
+++ b/wagtail/admin/views/home.py
@@ -207,9 +207,12 @@ def get_context_data(self, parent_context):
         # The revision's object_id is a string, so cast it to int first.
         page_keys = [int(pr.object_id) for pr in last_edits]
         pages = Page.objects.specific().in_bulk(page_keys)
-        context["last_edits"] = [
-            [revision, pages.get(int(revision.object_id))] for revision in last_edits
-        ]
+        context["last_edits"] = []
+        for revision in last_edits:
+            page = pages.get(int(revision.object_id))
+            if page:
+                context["last_edits"].append([revision, page])
+
         context["request"] = request
         return context
 


Test patch:
diff --git a/wagtail/admin/tests/pages/test_dashboard.py b/wagtail/admin/tests/pages/test_dashboard.py
--- a/wagtail/admin/tests/pages/test_dashboard.py
+++ b/wagtail/admin/tests/pages/test_dashboard.py
@@ -20,10 +20,11 @@ def setUp(self):
             content="Some content here",
         )
         self.root_page.add_child(instance=child_page)
-        child_page.save_revision().publish()
+        self.revision = child_page.save_revision()
+        self.revision.publish()
         self.child_page = SimplePage.objects.get(id=child_page.id)
 
-        self.create_superuser(username="alice", password="password")
+        self.user_alice = self.create_superuser(username="alice", password="password")
         self.create_superuser(username="bob", password="password")
 
     def change_something(self, title):
@@ -81,6 +82,21 @@ def test_your_recent_edits(self):
         response = self.go_to_dashboard_response()
         self.assertIn("Your most recent edits", response.content.decode("utf-8"))
 
+    def test_missing_page_record(self):
+        # Ensure that the panel still renders when one of the returned revision records
+        # has no corresponding Page object. It's unclear how this happens, since revisions
+        # are deleted on page deletion, but there are reports of this happening in
+        # https://github.com/wagtail/wagtail/issues/9185
+
+        # edit the revision object to be owned by Alice and have an unrecognised object ID
+        self.revision.user = self.user_alice
+        self.revision.object_id = "999999"
+        self.revision.save()
+
+        self.login(username="alice", password="password")
+        response = self.client.get(reverse("wagtailadmin_home"))
+        self.assertEqual(response.status_code, 200)
+
     def test_panel(self):
         """Test if the panel actually returns expected pages"""
         self.login(username="bob", password="password")