Search code examples
pythonsqldjangodjango-querysetwagtail

In Wagtail 4.0, How do I query for revisions whose pages are not live?


I am upgrading some code from wagtail 3.0 to wagtail 4.0. My code has one problem query in it that I can not figure out how to fix.

In the old code, the query looks like this:

PageRevision.objects.filter(approved_go_live_at__isnull=False, page__live=False)

With PageRevision being deprecated, I updated it to the following

Revision.page_revisions.filter(approved_go_live_at__isnull=False, page__live=False)

This resulted in an error, caused by a type mismatch in sql:

ProgrammingError: operator does not exist: character varying = integer
LINE 1: ...core_page" ON ("wagtailcore_revision"."object_id" = "wagtail...
                                                             ^
HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.

After reexamining the docs I changed it to:

Revision.page_revisions.filter(approved_go_live_at__isnull=False, content_object__live=False)

This just got a different error:

FieldError: Field 'content_object' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.

Now I am confused, because content_object is a field directly on Revision, so it shouldn't be a 'reverse` relation.

Looking at Page, it seems like it does have a GenericRelation, (with related_query_name=page) pointing back to Revision. But that's what I tried to use the first time and got a sql type mismatch.

The documentation talks about type casting, but I don't see how to get django to type cast in the JOIN clause that it generates.

Final Question: How can I query for Revisions, filtering by a field on the related Page?


Solution

  • If you're using PostgreSQL, this is caused by a 12-year-old Django issue https://code.djangoproject.com/ticket/16055 that will be fixed in Django 5.0 (yet to be released at the time of writing).

    A possible workaround:

    from django.contrib.contenttypes.models import ContentType
    from wagtail.models import Page, Revision
    
    base_page_ct = ContentType.objects.get_for_model(Page)
    not_live_ids = Page.objects.not_live().values_list('id', flat=True)
    Revision.page_revisions.filter(base_content_type=base_page_ct, object_id__in=not_live_ids)
    

    How can I query for relations, filtering by a field on the related Page?

    I assume you mean "query for revisions"?

    Using a similar trick as above, you can filter the page first, use values_list to get the IDs, then filter the revisions by the base Page content type and the object IDs.

    Since QuerySets are lazy, the first query to get the page IDs won't be evaluated directly and instead will be used as a subquery to revision query.