Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace PageRevision with generic Revision model #8441

Merged
merged 64 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
0fafedb
Rename PageRevision model to Revision
laymonage Apr 26, 2022
231b6ad
Turn Revision's page field into CharField
laymonage Apr 26, 2022
257da10
Rename Revision's page field into object_id
laymonage Apr 26, 2022
2c7af13
Add nullable content_type ForeignKey and content_object GenericForeig…
laymonage Apr 26, 2022
0f544ac
Merge PageRevision -> Revision migrations into one
laymonage Apr 26, 2022
9d02ff2
Add revisions GenericRelation to Page model
laymonage Apr 26, 2022
5b8578e
Add migration for populating revision content type
laymonage Apr 26, 2022
3225eba
Make content_type ForeignKey in Revision non-nullable
laymonage Apr 26, 2022
73d1d4e
Add page_revisions manager to Revision
laymonage Apr 26, 2022
3a3a3b8
Rename PageRevision imports to Revision
laymonage Apr 26, 2022
665104f
Remove PageRevision alias
laymonage Apr 26, 2022
f3896bb
Extract common queries into RevisionQuerySet
laymonage Apr 27, 2022
775197e
Use page_revisions manager when applicable and fix page queries to us…
laymonage Apr 27, 2022
54a7cb6
Fix select_related() into prefetch_related()
laymonage Apr 27, 2022
e3121c5
Fix .page and .page_id access to use .content_object and .object_id
laymonage Apr 27, 2022
e059fc8
Use get_default_page_content_type()
laymonage Apr 27, 2022
7715d52
Keep using page instead of content_object for reverse lookups
laymonage Apr 27, 2022
222b601
Fix setting object reference in revisions when copying a page
laymonage Apr 27, 2022
11ad590
Also check for content_type in get_previous() and get_next()
laymonage Apr 27, 2022
8b7c396
Fix submitted_for_moderation reset to be generic
laymonage Apr 27, 2022
870caca
Force revisions lookup and creation to use default Page model's Conte…
laymonage Apr 27, 2022
5f3e776
Fix test_specific_query_with_annotation
laymonage Apr 27, 2022
4c6d327
Fix in_bulk() query in RecentEditsPanel
laymonage Apr 27, 2022
ca697c0
Pass page instance directly to PublishPageRevisionAction
laymonage Apr 28, 2022
d93b290
Mock Page.specific_class instead of ContentType.model_class
laymonage Apr 28, 2022
b68bfc5
Override filter() in RevisionQuerySet to always cast object_id to string
laymonage Apr 29, 2022
731f1e9
Fix raw SQL query for mysql in RecentEditsPanel
laymonage Apr 29, 2022
e882a37
Revert "Pass page instance directly to PublishPageRevisionAction"
laymonage Apr 29, 2022
a9658fa
Rename revision.content_object back to revision.page
laymonage Apr 29, 2022
a2bf4a2
Remove GenericForeignKey and GenericRelation usage
laymonage Apr 29, 2022
b7f8946
Fix page revision creation and query by adding base_content_type
laymonage Apr 29, 2022
4aa6b6a
Fix UserPagePermissionsPolicy.revisions_for_moderation()
laymonage Apr 29, 2022
f811c63
Remove prefetch_related on Revision's page/content_object
laymonage Apr 29, 2022
10470c8
Change test_page_queryset.TestSpecificQuery.test_specific_query_with_…
laymonage Apr 29, 2022
ff04275
Fix updating Page.first_published_at
laymonage Apr 29, 2022
59b5bab
Change some prefetch_related() to use select_related() again
laymonage Apr 29, 2022
0fb722b
Undo unnecessary edits and update comments
laymonage Apr 29, 2022
54918ee
Make sure to only select page revisions in RecentEditPanel's raw SQL …
laymonage Apr 29, 2022
8db8768
Add page_id cached property to Revision
laymonage Apr 29, 2022
c03b826
Use KeyTextTransform to extract content_type id from revision content
laymonage Apr 29, 2022
90726ad
Add comment for customised RevisionQuerySet.filter()
laymonage Apr 29, 2022
c7d1c97
Use content_type_id to help avoid unnecessary db queries
laymonage Apr 29, 2022
ddc54a9
Make sure to use the same content_type in is_latest_revision()
laymonage Apr 29, 2022
ebe7d69
Set default value for base_content_type
laymonage Apr 29, 2022
5592f0f
Add content_object property to Revision
laymonage Apr 29, 2022
75011a3
Update docs
laymonage May 17, 2022
c7609cb
Add indexes to Revision model
laymonage May 4, 2022
d68392d
Remove references to page where possible
laymonage May 4, 2022
72bd678
Add missing migration for verbose names
laymonage May 4, 2022
5f97e96
Use base_content_type_id for queries
laymonage May 4, 2022
0285ef8
Remove unnecessary caching
laymonage May 4, 2022
10ed5ed
Docs fixes
laymonage May 4, 2022
aa98d22
Rename migration file
laymonage May 4, 2022
61de421
Retain cached_property for content_object so that they can be replaced
laymonage May 4, 2022
aa14c57
Fix page revision query in preview_revision_for_task
laymonage May 5, 2022
c6bad7b
Raise warnings when accessing .page and .page_id in Revision
laymonage May 5, 2022
7e7aded
Change .page and .page_id -> .content_object .object_id
laymonage May 5, 2022
28bd7ff
Deprecate as_page_object() in favour of as_object()
laymonage May 6, 2022
f480c2b
Replace as_page_object() -> as_object()
laymonage May 6, 2022
f9e6db3
Update docs
laymonage May 6, 2022
7634d47
Add for_instance() method to RevisionQuerySet and Revision managers
laymonage May 6, 2022
4c619b8
Fix lint issue
laymonage May 10, 2022
dc0f41d
Make as_object() always use the specific content type and document sp…
laymonage May 12, 2022
83e49d0
Minor docs fixes
laymonage May 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Changelog
* Fix: Typo in `ResumeWorkflowActionFormatter` message (Stefan Hammer)
* Fix: Throw a meaningful error when saving an image to an unrecognised image format (Christian Franke)
* Fix: Remove extra padding for headers with breadcrumbs on mobile viewport (Steven Steinwand)
* Replace `PageRevision` with generic `Revision` model (Sage Abdullah)


3.0 (16.05.2022)
Expand Down
88 changes: 66 additions & 22 deletions docs/reference/pages/model_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -484,25 +484,43 @@ The ``locale`` and ``translation_key`` fields have a unique key constraint to pr
.. autoattribute:: localized


.. _page-revision-model-ref:
.. _revision-model-ref:

``PageRevision``
================
``Revision``
============

Every time a page is edited a new ``PageRevision`` is created and saved to the database. It can be used to find the full history of all changes that have been made to a page and it also provides a place for new changes to be kept before going live.
Every time a page is edited a new ``Revision`` is created and saved to the database. It can be used to find the full history of all changes that have been made to a page and it also provides a place for new changes to be kept before going live.

- Revisions can be created from any :class:`~wagtail.models.Page` object by calling its :meth:`~Page.save_revision` method
- The content of the page is JSON-serialisable and stored in the :attr:`~PageRevision.content` field
- You can retrieve a ``PageRevision`` as a :class:`~wagtail.models.Page` object by calling the :meth:`~PageRevision.as_page_object` method
- The content of the page is JSON-serialisable and stored in the :attr:`~Revision.content` field
- You can retrieve a ``Revision`` as a :class:`~wagtail.models.Page` object by calling the :meth:`~Revision.as_object` method

.. versionchanged:: 4.0

The model has been renamed from ``PageRevision`` to ``Revision`` and it now references the ``Page`` model using a combination of an ``object_id`` :class:`~django.db.models.CharField` and foreign keys to :class:`~django.contrib.contenttypes.models.ContentType`.

Database fields
~~~~~~~~~~~~~~~

.. class:: PageRevision
.. class:: Revision

.. attribute:: page
.. attribute:: content_type

(foreign key to :class:`~wagtail.models.Page`)
(foreign key to :class:`~django.contrib.contenttypes.models.ContentType`)

This is the content type of the object this revision belongs to. For page revisions, this means the content type of the specific page type.

.. attribute:: base_content_type

(foreign key to :class:`~django.contrib.contenttypes.models.ContentType`)

This is the base content type of the object this revision belongs to. For page revisions, this means the content type of the :class:`~wagtail.models.Page` model.

.. attribute:: object_id

(string)

This represents the primary key of the object this revision belongs to.

.. attribute:: submitted_for_moderation

Expand Down Expand Up @@ -536,38 +554,56 @@ Database fields
Managers
~~~~~~~~

.. class:: PageRevision
.. class:: Revision
:noindex:

.. attribute:: objects

This manager is used to retrieve all of the ``PageRevision`` objects in the database
This manager is used to retrieve all of the ``Revision`` objects in the database.

Example:

.. code-block:: python

PageRevision.objects.all()
Revision.objects.all()

.. attribute:: page_revisions

This manager is used to retrieve all of the ``Revision`` objects that belong to pages.

Example:

.. code-block:: python

Revision.page_revisions.all()

.. versionadded:: 4.0

This manager is added as a shorthand to retrieve page revisions.

.. attribute:: submitted_revisions

This manager is used to retrieve all of the ``PageRevision`` objects that are awaiting moderator approval
This manager is used to retrieve all of the ``Revision`` objects that are awaiting moderator approval.

Example:

.. code-block:: python

PageRevision.submitted_revisions.all()
Revision.submitted_revisions.all()

Methods and properties
~~~~~~~~~~~~~~~~~~~~~~

.. class:: PageRevision
.. class:: Revision
:noindex:

.. automethod:: as_page_object
.. automethod:: as_object

This method retrieves this revision as an instance of its object's specific class. If the revision belongs to a page, it will be an instance of the :class:`~wagtail.models.Page`'s specific subclass.

This method retrieves this revision as an instance of its :class:`~wagtail.models.Page` subclass.
.. versionadded:: 4.0

This method has been renamed from ``as_page_object()`` to ``as_object()``.

.. automethod:: approve_moderation

Expand All @@ -579,11 +615,19 @@ Methods and properties

.. automethod:: is_latest_revision

Returns ``True`` if this revision is its page's latest revision
Returns ``True`` if this revision is the object's latest revision

.. automethod:: publish

Calling this will copy the content of this revision into the live page object. If the page is in draft, it will be published.
Calling this will copy the content of this revision into the live object. If the object is in draft, it will be published.

.. autoattribute:: content_object

This property returns the object this revision belongs to as an instance of the base class.

.. autoattribute:: specific_content_object

This property returns the object this revision belongs to as an instance of the specific class.

``GroupPagePermission``
=======================
Expand Down Expand Up @@ -837,7 +881,7 @@ Database fields

.. attribute:: page_revision

(foreign key to ``PageRevision``)
(foreign key to ``Revision``)

The page revision this task state was created on.

Expand Down Expand Up @@ -1049,7 +1093,7 @@ Database fields

.. attribute:: revision

(foreign key to :class:`PageRevision`)
(foreign key to :class:`Revision`)

A foreign key to the current page revision.

Expand Down Expand Up @@ -1111,7 +1155,7 @@ Database fields

.. attribute:: revision_created

(foreign key to :class:`PageRevision`)
(foreign key to :class:`Revision`)

A foreign key to the revision on which the comment was created.

Expand Down
6 changes: 3 additions & 3 deletions docs/reference/signals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Signals
=======

Wagtail's :ref:`page-revision-model-ref` and :ref:`page-model-ref` implement
Wagtail's :ref:`revision-model-ref` and :ref:`page-model-ref` implement
:doc:`Signals <topics/signals>` from ``django.dispatch``.
Signals are useful for creating side-effects from page publish/unpublish events.

Expand All @@ -13,11 +13,11 @@ For example, you could use signals to send publish notifications to a messaging
``page_published``
------------------

This signal is emitted from a ``PageRevision`` when a revision is set to `published`.
This signal is emitted from a ``Revision`` when a page revision is set to `published`.

:sender: The page ``class``.
:instance: The specific ``Page`` instance.
:revision: The ``PageRevision`` that was published.
:revision: The ``Revision`` that was published.
:kwargs: Any other arguments passed to ``page_published.send()``.

To listen to a signal, implement ``page_published.connect(receiver, sender, **kwargs)``. Here's a simple
Expand Down
9 changes: 8 additions & 1 deletion docs/releases/4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ depth: 1
* Fix typo in `ResumeWorkflowActionFormatter` message (Stefan Hammer)
* Throw a meaningful error when saving an image to an unrecognised image format (Christian Franke)
* Remove extra padding for headers with breadcrumbs on mobile viewport (Steven Steinwand)
* Replace `PageRevision` with generic `Revision` model (Sage Abdullah)


## Upgrade considerations
Expand All @@ -34,8 +35,14 @@ depth: 1

The `wagtail.contrib.modeladmin.helpers.AdminURLHelper` class now accepts a `base_url_path` keyword argument on its constructor. Custom subclasses of this class should be updated to accept this keyword argument.


### Dropped support for Safari 13

Safari 13 will no longer be officially supported as of this release, this deviates the current support for the last 3 version of Safari by a few months and was required to add better support for RTL languages.

### `PageRevision` replaced with `Revision`

The `PageRevision` model has been replaced with a generic `Revision` model. If you use the `PageRevision` model in your code, make sure that:

* Creation of `PageRevision` objects should be updated to create `Revision` objects using the page's `id` as the `object_id`, the default `Page` model's content type as the `base_content_type`, and the page's specific content type as the `content_type`.
* Queries that use the `PageRevision.objects` manager should be updated to use the `Revision.page_revisions` manager.
* Queries for `Page` objects that use `PageRevision.page_id` should be updated to cast the `Revision.object_id` to an integer before using it in the query.
2 changes: 1 addition & 1 deletion wagtail/actions/copy_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def _copy_page(
revision.pk = None
revision.submitted_for_moderation = False
revision.approved_go_live_at = None
revision.page = page_copy
revision.object_id = page_copy.id

# Update ID fields in content
revision_content = revision.content
Expand Down
12 changes: 6 additions & 6 deletions wagtail/actions/publish_page_revision.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(
self, revision, user=None, changed=True, log_action=True, previous_revision=None
):
self.revision = revision
self.page = self.revision.as_page_object()
self.page = self.revision.as_object()
self.user = user
self.changed = changed
self.log_action = log_action
Expand Down Expand Up @@ -73,7 +73,7 @@ def log_scheduling_action(self):
def _publish_page_revision(
self, revision, page, user, changed, log_action, previous_revision
):
from wagtail.models import COMMENTS_RELATION_NAME, PageRevision
from wagtail.models import COMMENTS_RELATION_NAME, Revision

if page.go_live_at and page.go_live_at > timezone.now():
page.has_unpublished_changes = True
Expand Down Expand Up @@ -110,7 +110,7 @@ def _publish_page_revision(
page.first_published_at = now

if previous_revision:
previous_revision_page = previous_revision.as_page_object()
previous_revision_page = previous_revision.as_object()
old_page_title = (
previous_revision_page.title
if page.title != previous_revision_page.title
Expand All @@ -119,11 +119,11 @@ def _publish_page_revision(
else:
try:
previous = revision.get_previous()
except PageRevision.DoesNotExist:
except Revision.DoesNotExist:
previous = None
old_page_title = (
previous.page.title
if previous and page.title != previous.page.title
previous.content_object.title
if previous and page.title != previous.content_object.title
else None
)
else:
Expand Down
2 changes: 1 addition & 1 deletion wagtail/actions/revert_to_page_revision.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def check(self, skip_permission_checks=False):
def execute(self, skip_permission_checks=False):
self.check(skip_permission_checks=skip_permission_checks)

return self.revision.as_page_object().save_revision(
return self.revision.as_object().save_revision(
previous_revision=self.revision,
user=self.user,
log_action=self.log_action,
Expand Down
2 changes: 1 addition & 1 deletion wagtail/admin/api/actions/revert_to_page_revision.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ def execute(self, instance, data):
except RevertToPageRevisionError as e:
raise BadRequestError(e.args[0])

serializer = self.view.get_serializer(new_revision.as_page_object())
serializer = self.view.get_serializer(new_revision.as_object())
return Response(serializer.data, status=status.HTTP_200_OK)
2 changes: 1 addition & 1 deletion wagtail/admin/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def send_moderation_notification(revision, notification, excluded_user=None):
settings, "WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS", True
)
recipient_users = users_with_page_permission(
revision.page, "publish", include_superusers
revision.content_object, "publish", include_superusers
)
elif notification in ["rejected", "approved"]:
# Get submitter
Expand Down
26 changes: 13 additions & 13 deletions wagtail/admin/templates/wagtailadmin/home/pages_for_moderation.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@ <h2 class="title-wrapper">{% trans 'Pages awaiting moderation' %}</h2>
</thead>
<tbody>
{% for revision in page_revisions_for_moderation %}
{% page_permissions revision.page as page_perms %}
{% page_permissions revision.content_object as page_perms %}
<tr>
<td class="title" valign="top">
<div class="title-wrapper">
{% if page_perms.can_edit %}
<a href="{% url 'wagtailadmin_pages:edit' revision.page.id %}" title="{% trans 'Edit this page' %}">{{ revision.page.specific_deferred.get_admin_display_title }}</a>
{% elif revision.page.is_previewable %}
<a href="{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}" title="{% trans 'Preview this page' %}">{{ revision.page.specific_deferred.get_admin_display_title }}</a>
<a href="{% url 'wagtailadmin_pages:edit' revision.object_id %}" title="{% trans 'Edit this page' %}">{{ revision.content_object.specific_deferred.get_admin_display_title }}</a>
{% elif revision.content_object.is_previewable %}
<a href="{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}" title="{% trans 'Preview this page' %}">{{ revision.content_object.specific_deferred.get_admin_display_title }}</a>
{% else %}
{{ revision.page.specific_deferred.get_admin_display_title }}
{{ revision.content_object.specific_deferred.get_admin_display_title }}
{% endif %}

{% i18n_enabled as show_locale_labels %}
{% if show_locale_labels and revision.page.locale_id %}
{% locale_label_from_id revision.page.locale_id as locale_label %}
{% if show_locale_labels and revision.content_object.locale_id %}
{% locale_label_from_id revision.content_object.locale_id as locale_label %}
<span class="status-tag status-tag--label">{{ locale_label }}</span>
{% endif %}
{% include "wagtailadmin/pages/listing/_privacy_indicator.html" with page=revision.page %}
{% include "wagtailadmin/pages/listing/_locked_indicator.html" with page=revision.page %}
{% include "wagtailadmin/pages/listing/_privacy_indicator.html" with page=revision.content_object %}
{% include "wagtailadmin/pages/listing/_locked_indicator.html" with page=revision.content_object %}
</div>
<ul class="actions">
<li>
Expand All @@ -52,18 +52,18 @@ <h2 class="title-wrapper">{% trans 'Pages awaiting moderation' %}</h2>
</form>
</li>
{% if page_perms.can_edit %}
<li><a href="{% url 'wagtailadmin_pages:edit' revision.page.id %}" class="button button-small button-secondary">{% trans 'Edit' %}</a></li>
<li><a href="{% url 'wagtailadmin_pages:edit' revision.object_id %}" class="button button-small button-secondary">{% trans 'Edit' %}</a></li>
{% endif %}
{% if revision.page.is_previewable %}
{% if revision.content_object.is_previewable %}
<li><a href="{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}" class="button button-small button-secondary" target="_blank" rel="noreferrer">{% trans 'Preview' %}</a></li>
{% endif %}
</ul>
</td>
<td valign="top">
<a href="{% url 'wagtailadmin_explore' revision.page.get_parent.id %}">{{ revision.page.get_parent.specific_deferred.get_admin_display_title }}</a>
<a href="{% url 'wagtailadmin_explore' revision.content_object.get_parent.id %}">{{ revision.content_object.get_parent.specific_deferred.get_admin_display_title }}</a>
</td>
<td class="type" valign="top">
{{ revision.page.content_type.model_class.get_verbose_name }}
{{ revision.content_object.content_type.model_class.get_verbose_name }}
</td>
<td valign="top">
<div class="human-readable-date" title="{{ revision.created_at|date:"DATETIME_FORMAT" }}">{% blocktrans trimmed with time_period=revision.created_at|timesince_simple %}{{ time_period }}{% endblocktrans %} </div>
Expand Down
Loading