0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Avoid querying with GenericRelations to prevent crash on Postgres

Upstream issue: https://code.djangoproject.com/ticket/16055
This commit is contained in:
Sage Abdullah 2022-12-22 09:43:30 +00:00
parent b151180cc8
commit c008fdfb39
No known key found for this signature in database
GPG Key ID: EB1A33CC51CC0217
4 changed files with 65 additions and 18 deletions

View File

@ -21,6 +21,7 @@ from wagtail.models import (
WorkflowPage,
WorkflowState,
WorkflowTask,
get_default_page_content_type,
)
from wagtail.signals import page_published
from wagtail.test.testapp.models import SimplePage, SimpleTask
@ -1168,8 +1169,8 @@ class TestApproveRejectWorkflow(TestCase, WagtailTestUtils):
# Check that the workflow was approved
workflow_state = WorkflowState.objects.get(
page=self.page, requested_by=self.submitter
workflow_state = WorkflowState.objects.for_instance(self.page).get(
requested_by=self.submitter
)
self.assertEqual(workflow_state.status, workflow_state.STATUS_APPROVED)
@ -1303,8 +1304,8 @@ class TestApproveRejectWorkflow(TestCase, WagtailTestUtils):
self.assertEqual(response.status_code, 200)
# Check that the workflow state was marked as cancelled
workflow_state = WorkflowState.objects.get(
page=self.page, requested_by=self.submitter
workflow_state = WorkflowState.objects.for_instance(self.page).get(
requested_by=self.submitter
)
self.assertEqual(workflow_state.status, WorkflowState.STATUS_CANCELLED)
@ -1322,8 +1323,8 @@ class TestApproveRejectWorkflow(TestCase, WagtailTestUtils):
# Check that the workflow was marked as needing changes
workflow_state = WorkflowState.objects.get(
page=self.page, requested_by=self.submitter
workflow_state = WorkflowState.objects.for_instance(self.page).get(
requested_by=self.submitter
)
self.assertEqual(workflow_state.status, workflow_state.STATUS_NEEDS_CHANGES)
@ -1434,8 +1435,8 @@ class TestApproveRejectWorkflow(TestCase, WagtailTestUtils):
# Check that the workflow was approved
workflow_state = WorkflowState.objects.get(
page=self.page, requested_by=self.submitter
workflow_state = WorkflowState.objects.for_instance(self.page).get(
requested_by=self.submitter
)
self.assertEqual(workflow_state.status, workflow_state.STATUS_APPROVED)
@ -1902,7 +1903,9 @@ class TestDisableViews(TestCase, WagtailTestUtils):
self.assertEqual(response.status_code, 302)
self.workflow.refresh_from_db()
self.assertIs(self.workflow.active, False)
states = WorkflowState.objects.filter(page=self.page, workflow=self.workflow)
states = WorkflowState.objects.for_instance(self.page).filter(
workflow=self.workflow
)
self.assertEqual(
states.filter(status=WorkflowState.STATUS_IN_PROGRESS).count(), 0
)
@ -1964,7 +1967,9 @@ class TestDisableViews(TestCase, WagtailTestUtils):
self.task_1.refresh_from_db()
self.assertIs(self.task_1.active, False)
states = TaskState.objects.filter(
workflow_state__page=self.page, task=self.task_1.task_ptr
workflow_state__base_content_type_id=get_default_page_content_type().id,
workflow_state__object_id=str(self.page.id),
task=self.task_1.task_ptr,
)
self.assertEqual(states.filter(status=TaskState.STATUS_IN_PROGRESS).count(), 0)
self.assertEqual(states.filter(status=TaskState.STATUS_CANCELLED).count(), 1)

View File

@ -5,7 +5,8 @@ from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import permission_required
from django.db import connection
from django.db.models import Max, Q
from django.db.models import CharField, IntegerField, Max, OuterRef, Q, Subquery
from django.db.models.functions import Cast
from django.forms import Media
from django.http import Http404, HttpResponse
from django.template.loader import render_to_string
@ -118,10 +119,23 @@ class UserPagesInWorkflowModerationPanel(Component):
request = parent_context["request"]
context = super().get_context_data(parent_context)
if getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True):
# Need to cast the page ids to string because Postgres doesn't support
# implicit type casts when querying on GenericRelations. We also need
# to cast the object_id to integer when querying the pages for the same reason.
# https://code.djangoproject.com/ticket/16055
# Once the issue is resolved, the subquery can be removed and the
# filter can be changed to:
# Q(page__owner=request.user) | Q(requested_by=request.user)
owned_by_user = Subquery(
Page.objects.filter(
owner=request.user,
id=Cast(OuterRef("object_id"), output_field=IntegerField()),
).values_list(Cast("id", output_field=CharField()), flat=True)
)
# Find in progress workflow states which are either requested by the user or on pages owned by the user
context["workflow_states"] = (
WorkflowState.objects.active()
.filter(Q(page__owner=request.user) | Q(requested_by=request.user))
.filter(Q(object_id__in=owned_by_user) | Q(requested_by=request.user))
.prefetch_related("content_object")
.select_related(
"current_task_state",

View File

@ -20,6 +20,7 @@ from wagtail.models import (
TaskState,
UserPagePermissionsProxy,
WorkflowState,
get_default_page_content_type,
)
@ -59,7 +60,7 @@ def workflow_history(request, page_id):
if not user_perms.for_page(page).can_edit():
raise PermissionDenied
workflow_states = WorkflowState.objects.filter(page=page).order_by("-created_at")
workflow_states = WorkflowState.objects.for_instance(page).order_by("-created_at")
paginator = Paginator(workflow_states, per_page=20)
workflow_states = paginator.get_page(request.GET.get("p"))
@ -81,7 +82,14 @@ def workflow_history_detail(request, page_id, workflow_state_id):
if not user_perms.for_page(page).can_edit():
raise PermissionDenied
workflow_state = get_object_or_404(WorkflowState, page=page, id=workflow_state_id)
# Change to page=page once this issue is resolved:
# https://code.djangoproject.com/ticket/16055
workflow_state = get_object_or_404(
WorkflowState,
base_content_type_id=get_default_page_content_type().id,
object_id=str(page.id),
id=workflow_state_id,
)
# Get QuerySet of all revisions that have existed during this workflow state
# It's possible that the page is edited while the workflow is running, so some

View File

@ -2,6 +2,8 @@ import datetime
import django_filters
from django.contrib.auth import get_user_model
from django.db.models import CharField
from django.db.models.functions import Cast
from django.utils.translation import gettext_lazy as _
from wagtail.admin.filters import (
@ -16,6 +18,7 @@ from wagtail.models import (
UserPagePermissionsProxy,
Workflow,
WorkflowState,
get_default_page_content_type,
)
from .base import ReportView
@ -137,7 +140,17 @@ class WorkflowView(ReportView):
def get_queryset(self):
pages = UserPagePermissionsProxy(self.request.user).editable_pages()
return WorkflowState.objects.filter(page__in=pages).order_by("-created_at")
# Need to cast the page ids to string because Postgres doesn't support
# implicit type casts when querying on GenericRelations
# https://code.djangoproject.com/ticket/16055
# Once the issue is resolved, we can change the query to
# page__in=pages
page_ids = pages.values_list(Cast("id", output_field=CharField()), flat=True)
return (
WorkflowState.objects.for_pages()
.filter(object_id__in=page_ids)
.order_by("-created_at")
)
class WorkflowTasksView(ReportView):
@ -174,6 +187,13 @@ class WorkflowTasksView(ReportView):
def get_queryset(self):
pages = UserPagePermissionsProxy(self.request.user).editable_pages()
return TaskState.objects.filter(workflow_state__page__in=pages).order_by(
"-started_at"
)
# Need to cast the page ids to string because Postgres doesn't support
# implicit type casts when querying on GenericRelations
# https://code.djangoproject.com/ticket/16055
# Once the issue is resolved, we can change the query to
# workflow_state__page_in=pages
page_ids = pages.values_list(Cast("id", output_field=CharField()), flat=True)
return TaskState.objects.filter(
workflow_state__base_content_type_id=get_default_page_content_type().id,
workflow_state__object_id__in=page_ids,
).order_by("-started_at")