diff --git a/docs/reference/pages/model_reference.rst b/docs/reference/pages/model_reference.rst index babab45f5..c1891c28f 100644 --- a/docs/reference/pages/model_reference.rst +++ b/docs/reference/pages/model_reference.rst @@ -226,6 +226,8 @@ In addition to the model fields provided, ``Page`` has many properties and metho Forms must be a subclass of :class:`~wagtail.admin.forms.WagtailAdminPageForm`. See :ref:`custom_edit_handler_forms` for more information. + .. automethod:: with_content_json + .. _site-model-ref: ``Site`` diff --git a/wagtail/core/models.py b/wagtail/core/models.py index 60ad867ef..92d18909d 100644 --- a/wagtail/core/models.py +++ b/wagtail/core/models.py @@ -1414,6 +1414,53 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase): context['action_url'] = action_url return TemplateResponse(request, self.password_required_template, context) + def with_content_json(self, content_json): + """ + Returns a new version of the page with field values updated to reflect changes + in the provided ``content_json`` (which usually comes from a previously-saved + page revision). + + Certain field values are preserved in order to prevent errors if the returned + page is saved, such as ``id``, ``content_type`` and some tree-related values. + The following field values are also preserved, as they are considered to be + meaningful to the page as a whole, rather than to a specific revision: + + * ``draft_title`` + * ``live`` + * ``has_unpublished_changes`` + * ``owner`` + * ``locked`` + * ``latest_revision_created_at`` + * ``first_published_at`` + """ + + obj = self.specific_class.from_json(content_json) + + # These should definitely never change between revisions + obj.pk = self.pk + obj.content_type = self.content_type + + # Override possibly-outdated tree parameter fields + obj.path = self.path + obj.depth = self.depth + obj.numchild = self.numchild + + # Update url_path to reflect potential slug changes, but maintining the page's + # existing tree position + obj.set_url_path(self.get_parent()) + + # Ensure other values that are meaningful for the page as a whole (rather than + # to a specific revision) are preserved + obj.draft_title = self.draft_title + obj.live = self.live + obj.has_unpublished_changes = self.has_unpublished_changes + obj.owner = self.owner + obj.locked = self.locked + obj.latest_revision_created_at = self.latest_revision_created_at + obj.first_published_at = self.first_published_at + + return obj + class Meta: verbose_name = _('page') verbose_name_plural = _('pages') @@ -1464,30 +1511,7 @@ class PageRevision(models.Model): self.page.revisions.exclude(id=self.id).update(submitted_for_moderation=False) def as_page_object(self): - obj = self.page.specific_class.from_json(self.content_json) - - # Override the possibly-outdated tree parameter fields from this revision object - # with up-to-date values - obj.pk = self.page.pk - obj.path = self.page.path - obj.depth = self.page.depth - obj.numchild = self.page.numchild - - # Populate url_path based on the revision's current slug and the parent page as determined - # by path - obj.set_url_path(self.page.get_parent()) - - # also copy over other properties which are meaningful for the page as a whole, not a - # specific revision of it - obj.draft_title = self.page.draft_title - obj.live = self.page.live - obj.has_unpublished_changes = self.page.has_unpublished_changes - obj.owner = self.page.owner - obj.locked = self.page.locked - obj.latest_revision_created_at = self.page.latest_revision_created_at - obj.first_published_at = self.page.first_published_at - - return obj + return self.page.specific.with_content_json(self.content_json) def approve_moderation(self): if self.submitted_for_moderation: