kopia lustrzana https://github.com/wagtail/wagtail
Replace `PageRevision` with generic `Revision` model (#8441)
rodzic
501b28c62b
commit
52e5abfe62
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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 :class:`~wagtail.models.Page` subclass.
|
||||
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.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
|
||||
This method has been renamed from ``as_page_object()`` to ``as_object()``.
|
||||
|
||||
.. automethod:: approve_moderation
|
||||
|
||||
|
@ -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``
|
||||
=======================
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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.
|
||||
|
|
|
@ -190,7 +190,7 @@ class CopyPageAction:
|
|||
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
|
||||
|
|
|
@ -37,7 +37,7 @@ class PublishPageRevisionAction:
|
|||
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
|
||||
|
@ -73,7 +73,7 @@ class PublishPageRevisionAction:
|
|||
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
|
||||
|
@ -110,7 +110,7 @@ class PublishPageRevisionAction:
|
|||
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
|
||||
|
@ -119,11 +119,11 @@ class PublishPageRevisionAction:
|
|||
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:
|
||||
|
|
|
@ -57,7 +57,7 @@ class RevertToPageRevisionAction:
|
|||
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,
|
||||
|
|
|
@ -38,5 +38,5 @@ class RevertToPageRevisionAPIAction(APIAction):
|
|||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -18,25 +18,25 @@
|
|||
</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>
|
||||
|
@ -52,18 +52,18 @@
|
|||
</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>
|
||||
|
|
|
@ -19,39 +19,39 @@
|
|||
<tbody>
|
||||
{% for task_state, actions, workflow_tasks in states %}
|
||||
{% with task_state.page_revision as revision %}
|
||||
{% 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>
|
||||
<a href="{% url 'wagtailadmin_pages:edit' revision.object_id %}" title="{% trans 'Edit 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>
|
||||
{% if actions %}
|
||||
<ul class="actions">
|
||||
{% for action_name, action_label, modal in actions %}
|
||||
<li>
|
||||
<button class="button button-small button-secondary" data-workflow-action-url="{% url 'wagtailadmin_pages:workflow_action' revision.page.id action_name task_state.id %}" {% if modal %}data-launch-modal{% endif %}>{{ action_label }}</button>
|
||||
<button class="button button-small button-secondary" data-workflow-action-url="{% url 'wagtailadmin_pages:workflow_action' revision.object_id action_name task_state.id %}" {% if modal %}data-launch-modal{% endif %}>{{ action_label }}</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if page_perms.can_edit %}
|
||||
<li>
|
||||
<a href="{% url 'wagtailadmin_pages:edit' revision.page.id %}" class="button button-small button-secondary">{% trans 'Edit' %}</a>
|
||||
<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:workflow_preview' revision.page.id task_state.task.id %}" class="button button-small button-secondary" target="_blank" rel="noreferrer">{% trans 'Preview' %}</a>
|
||||
<a href="{% url 'wagtailadmin_pages:workflow_preview' revision.object_id task_state.task.id %}" class="button button-small button-secondary" target="_blank" rel="noreferrer">{% trans 'Preview' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<p>{% blocktrans trimmed with title=revision.page.get_admin_display_title|safe %}The page "{{ title }}" has been approved.{% endblocktrans %}</p>
|
||||
<p>{% trans "You can view the page here:" %} <a href="{{ revision.page.full_url }}">{{ revision.page.full_url }}</a></p>
|
||||
<p>{% blocktrans trimmed with title=revision.content_object.get_admin_display_title|safe %}The page "{{ title }}" has been approved.{% endblocktrans %}</p>
|
||||
<p>{% trans "You can view the page here:" %} <a href="{{ revision.content_object.full_url }}">{{ revision.content_object.full_url }}</a></p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% blocktrans trimmed with title=revision.page.get_admin_display_title|safe %}The page "{{ title }}" has been approved.{% endblocktrans %}
|
||||
{% blocktrans trimmed with title=revision.content_object.get_admin_display_title|safe %}The page "{{ title }}" has been approved.{% endblocktrans %}
|
||||
|
||||
{% trans "You can view the page here:" %} {{ revision.page.full_url }}
|
||||
{% trans "You can view the page here:" %} {{ revision.content_object.full_url }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% blocktrans trimmed with title=revision.page.get_admin_display_title|safe %}The page "{{ title }}" has been approved{% endblocktrans %}
|
||||
{% blocktrans trimmed with title=revision.content_object.get_admin_display_title|safe %}The page "{{ title }}" has been approved{% endblocktrans %}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
{% base_url_setting as base_url %}
|
||||
|
||||
{% block content %}
|
||||
<p>{% blocktrans trimmed with title=revision.page.get_admin_display_title|safe %}The page "{{ title }}" has been rejected.{% endblocktrans %}</p>
|
||||
<p>{% trans "You can edit the page here:"%} <a href="{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.page.id %}">{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.page.id %}</a></p>
|
||||
<p>{% blocktrans trimmed with title=revision.content_object.get_admin_display_title|safe %}The page "{{ title }}" has been rejected.{% endblocktrans %}</p>
|
||||
<p>{% trans "You can edit the page here:"%} <a href="{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.object_id %}">{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.object_id %}</a></p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% base_url_setting as base_url %}
|
||||
|
||||
{% block content %}
|
||||
{% blocktrans trimmed with title=revision.page.get_admin_display_title|safe %}The page "{{ title }}" has been rejected.{% endblocktrans %}
|
||||
{% blocktrans trimmed with title=revision.content_object.get_admin_display_title|safe %}The page "{{ title }}" has been rejected.{% endblocktrans %}
|
||||
|
||||
{% trans "You can edit the page here:"%} {{ base_url }}{% url 'wagtailadmin_pages:edit' revision.page.id %}
|
||||
{% trans "You can edit the page here:"%} {{ base_url }}{% url 'wagtailadmin_pages:edit' revision.object_id %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% blocktrans trimmed with title=revision.page.get_admin_display_title|safe %}The page "{{ title }}" has been rejected{% endblocktrans %}
|
||||
{% blocktrans trimmed with title=revision.content_object.get_admin_display_title|safe %}The page "{{ title }}" has been rejected{% endblocktrans %}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
{% base_url_setting as base_url %}
|
||||
|
||||
{% block content %}
|
||||
<p>{% blocktrans trimmed with page=revision.page|safe editor=revision.user|user_display_name %}The page "{{ page }}" has been submitted for moderation by {{ editor }}.{% endblocktrans %}</p>
|
||||
<p>{% blocktrans trimmed with page=revision.content_object|safe editor=revision.user|user_display_name %}The page "{{ page }}" has been submitted for moderation by {{ editor }}.{% endblocktrans %}</p>
|
||||
|
||||
<p>
|
||||
{% if revision.page.is_previewable %}
|
||||
{% if revision.content_object.is_previewable %}
|
||||
{% trans "You can preview the page here:" %} <a href="{{ base_url }}{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}">{{ base_url }}{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}</a><br/>
|
||||
{% endif %}
|
||||
{% trans "You can edit the page here:" %} <a href="{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.page.id %}">{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.page.id %}</a>
|
||||
{% trans "You can edit the page here:" %} <a href="{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.object_id %}">{{ base_url }}{% url 'wagtailadmin_pages:edit' revision.object_id %}</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
{% base_url_setting as base_url %}
|
||||
|
||||
{% block content %}
|
||||
{% blocktrans trimmed with page=revision.page|safe editor=revision.user|user_display_name %}The page "{{ page }}" has been submitted for moderation by {{ editor }}.{% endblocktrans %}
|
||||
{% blocktrans trimmed with page=revision.content_object|safe editor=revision.user|user_display_name %}The page "{{ page }}" has been submitted for moderation by {{ editor }}.{% endblocktrans %}
|
||||
|
||||
{% if revision.page.is_previewable %}{% trans "You can preview the page here:" %} {{ base_url }}{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}{% endif %}
|
||||
{% trans "You can edit the page here:" %} {{ base_url }}{% url 'wagtailadmin_pages:edit' revision.page.id %}
|
||||
{% if revision.content_object.is_previewable %}{% trans "You can preview the page here:" %} {{ base_url }}{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}{% endif %}
|
||||
{% trans "You can edit the page here:" %} {{ base_url }}{% url 'wagtailadmin_pages:edit' revision.object_id %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% blocktrans trimmed with page=revision.page|safe %}The page "{{ page }}" has been submitted for moderation{% endblocktrans %}
|
||||
{% blocktrans trimmed with page=revision.content_object|safe %}The page "{{ page }}" has been submitted for moderation{% endblocktrans %}
|
||||
|
|
|
@ -11,7 +11,7 @@ from wagtail.admin.userbar import (
|
|||
ExplorePageItem,
|
||||
RejectModerationEditPageItem,
|
||||
)
|
||||
from wagtail.models import PAGE_TEMPLATE_VAR, Page, PageRevision
|
||||
from wagtail.models import PAGE_TEMPLATE_VAR, Page, Revision
|
||||
from wagtail.users.models import UserProfile
|
||||
|
||||
register = template.Library()
|
||||
|
@ -62,13 +62,17 @@ def wagtailuserbar(context, position="bottom-right"):
|
|||
if revision_id:
|
||||
items = [
|
||||
AdminItem(),
|
||||
ExplorePageItem(PageRevision.objects.get(id=revision_id).page),
|
||||
EditPageItem(PageRevision.objects.get(id=revision_id).page),
|
||||
ExplorePageItem(
|
||||
Revision.page_revisions.get(id=revision_id).content_object
|
||||
),
|
||||
EditPageItem(
|
||||
Revision.page_revisions.get(id=revision_id).content_object
|
||||
),
|
||||
ApproveModerationEditPageItem(
|
||||
PageRevision.objects.get(id=revision_id)
|
||||
Revision.page_revisions.get(id=revision_id)
|
||||
),
|
||||
RejectModerationEditPageItem(
|
||||
PageRevision.objects.get(id=revision_id)
|
||||
Revision.page_revisions.get(id=revision_id)
|
||||
),
|
||||
]
|
||||
else:
|
||||
|
|
|
@ -1858,9 +1858,7 @@ class TestRevertToPageRevisionAction(AdminAPITestCase):
|
|||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
content = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
content, {"message": "No PageRevision matches the given query."}
|
||||
)
|
||||
self.assertEqual(content, {"message": "No Revision matches the given query."})
|
||||
|
||||
|
||||
# Overwrite imported test cases do Django doesn't run them
|
||||
|
|
|
@ -766,7 +766,7 @@ class TestPageCopy(TestCase, WagtailTestUtils):
|
|||
new_page = EventPage.objects.last()
|
||||
|
||||
# Hack: get the page instance from the edit form which assembles it from the
|
||||
# latest revision. While we could do new_page.get_latest_revision().as_page_object()
|
||||
# latest revision. While we could do new_page.get_latest_revision().as_object()
|
||||
# this follow the edit view closer and should it change the test is less
|
||||
# prone to continue working because we're skipping some step
|
||||
response = self.client.get(
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.utils import timezone
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.tests.pages.timestamps import submittable_timestamp
|
||||
from wagtail.models import GroupPagePermission, Locale, Page, PageRevision
|
||||
from wagtail.models import GroupPagePermission, Locale, Page, Revision
|
||||
from wagtail.signals import page_published
|
||||
from wagtail.test.testapp.models import (
|
||||
BusinessChild,
|
||||
|
@ -458,7 +458,7 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
|
||||
# No revisions with approved_go_live_at
|
||||
self.assertFalse(
|
||||
PageRevision.objects.filter(page=page)
|
||||
Revision.page_revisions.filter(object_id=page.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -610,7 +610,7 @@ class TestPageCreation(TestCase, WagtailTestUtils):
|
|||
|
||||
# A revision with approved_go_live_at should exist now
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=page)
|
||||
Revision.page_revisions.filter(object_id=page.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
|
|
@ -94,7 +94,8 @@ class TestRecentEditsPanel(TestCase, WagtailTestUtils):
|
|||
|
||||
# check if the revision is the revision of edited Page
|
||||
self.assertEqual(
|
||||
ctx["last_edits"][0][0].page, Page.objects.get(pk=self.child_page.id)
|
||||
ctx["last_edits"][0][0].content_object,
|
||||
Page.objects.get(pk=self.child_page.id),
|
||||
)
|
||||
# check if the page in this list is the specific page of this revision
|
||||
self.assertEqual(
|
||||
|
|
|
@ -22,8 +22,8 @@ from wagtail.models import (
|
|||
Locale,
|
||||
Page,
|
||||
PageLogEntry,
|
||||
PageRevision,
|
||||
PageSubscription,
|
||||
Revision,
|
||||
Site,
|
||||
)
|
||||
from wagtail.signals import page_published
|
||||
|
@ -346,21 +346,21 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
|
||||
# A revision with approved_go_live_at should not exist
|
||||
self.assertFalse(
|
||||
PageRevision.objects.filter(page=child_page_new)
|
||||
Revision.page_revisions.filter(object_id=child_page_new.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
||||
# But a revision with go_live_at and expire_at in their content json *should* exist
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(
|
||||
page=child_page_new,
|
||||
Revision.page_revisions.filter(
|
||||
object_id=child_page_new.id,
|
||||
content__go_live_at__startswith=str(go_live_at.date()),
|
||||
).exists()
|
||||
)
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(
|
||||
page=child_page_new,
|
||||
Revision.page_revisions.filter(
|
||||
object_id=child_page_new.id,
|
||||
content__expire_at__startswith=str(expire_at.date()),
|
||||
).exists()
|
||||
)
|
||||
|
@ -551,7 +551,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
|
||||
# Instead a revision with approved_go_live_at should now exist
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=child_page_new)
|
||||
Revision.page_revisions.filter(object_id=child_page_new.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -597,7 +597,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
|
||||
# Instead a revision with approved_go_live_at should now exist
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=child_page_new)
|
||||
Revision.page_revisions.filter(object_id=child_page_new.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -625,7 +625,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
|
||||
# And a revision with approved_go_live_at should not exist
|
||||
self.assertFalse(
|
||||
PageRevision.objects.filter(page=child_page_new)
|
||||
Revision.page_revisions.filter(object_id=child_page_new.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -664,7 +664,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
|
||||
# Instead a revision with approved_go_live_at should now exist
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=child_page_new)
|
||||
Revision.page_revisions.filter(object_id=child_page_new.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -719,7 +719,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
|
||||
# Instead a revision with approved_go_live_at should now exist
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=child_page_new)
|
||||
Revision.page_revisions.filter(object_id=child_page_new.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -753,7 +753,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
|
||||
# And a revision with approved_go_live_at should not exist
|
||||
self.assertFalse(
|
||||
PageRevision.objects.filter(page=child_page_new)
|
||||
Revision.page_revisions.filter(object_id=child_page_new.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -1244,7 +1244,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
|
|||
# (which is not a valid content language under the current configuration)
|
||||
Locale.objects.update(language_code="de")
|
||||
|
||||
PageRevision.objects.filter(page_id=self.child_page.id).delete()
|
||||
Revision.page_revisions.filter(object_id=self.child_page.id).delete()
|
||||
|
||||
# Tests that the edit page loads
|
||||
response = self.client.get(
|
||||
|
@ -1772,7 +1772,7 @@ class TestIssue3982(TestCase, WagtailTestUtils):
|
|||
)
|
||||
page = SimplePage.objects.get()
|
||||
self.assertFalse(page.live)
|
||||
revision = PageRevision.objects.get(page=page)
|
||||
revision = Revision.page_revisions.get(object_id=page.id)
|
||||
revision.submitted_for_moderation = True
|
||||
revision.save()
|
||||
response = self.client.post(
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.core.mail import EmailMultiAlternatives
|
|||
from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from wagtail.models import Page, PageRevision
|
||||
from wagtail.models import Page, Revision
|
||||
from wagtail.signals import page_published
|
||||
from wagtail.test.testapp.models import SimplePage
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
@ -145,7 +145,7 @@ class TestApproveRejectModeration(TestCase, WagtailTestUtils):
|
|||
|
||||
# Revision must no longer be submitted for moderation
|
||||
self.assertFalse(
|
||||
PageRevision.objects.get(id=self.revision.id).submitted_for_moderation
|
||||
Revision.page_revisions.get(id=self.revision.id).submitted_for_moderation
|
||||
)
|
||||
|
||||
def test_reject_moderation_view_bad_revision_id(self):
|
||||
|
|
|
@ -37,7 +37,7 @@ class TestModerationList(TestCase, WagtailTestUtils):
|
|||
self.revision = self.page.get_latest_revision()
|
||||
|
||||
self.edit_page_url = reverse(
|
||||
"wagtailadmin_pages:edit", args=(self.revision.page.id,)
|
||||
"wagtailadmin_pages:edit", args=(self.revision.object_id,)
|
||||
)
|
||||
self.preview_page_url = reverse(
|
||||
"wagtailadmin_pages:preview_for_moderation", args=(self.revision.id,)
|
||||
|
|
|
@ -110,7 +110,9 @@ class ModeratePageItem(BaseItem):
|
|||
if not request.user.has_perm("wagtailadmin.access_admin"):
|
||||
return ""
|
||||
|
||||
if not self.revision.page.permissions_for_user(request.user).can_publish():
|
||||
if not self.revision.content_object.permissions_for_user(
|
||||
request.user
|
||||
).can_publish():
|
||||
return ""
|
||||
|
||||
return super().render(request)
|
||||
|
|
|
@ -16,10 +16,11 @@ from wagtail.admin.site_summary import SiteSummaryPanel
|
|||
from wagtail.admin.ui.components import Component
|
||||
from wagtail.models import (
|
||||
Page,
|
||||
PageRevision,
|
||||
Revision,
|
||||
TaskState,
|
||||
UserPagePermissionsProxy,
|
||||
WorkflowState,
|
||||
get_default_page_content_type,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
|
@ -53,7 +54,7 @@ class PagesForModerationPanel(Component):
|
|||
user_perms = UserPagePermissionsProxy(request.user)
|
||||
context["page_revisions_for_moderation"] = (
|
||||
user_perms.revisions_for_moderation()
|
||||
.select_related("page", "user")
|
||||
.select_related("user")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
context["request"] = request
|
||||
|
@ -102,7 +103,6 @@ class WorkflowPagesToModeratePanel(Component):
|
|||
.select_related(
|
||||
"page_revision",
|
||||
"task",
|
||||
"page_revision__page",
|
||||
"page_revision__user",
|
||||
)
|
||||
.order_by("-started_at")
|
||||
|
@ -111,7 +111,7 @@ class WorkflowPagesToModeratePanel(Component):
|
|||
(
|
||||
state,
|
||||
state.task.specific.get_actions(
|
||||
page=state.page_revision.page, user=request.user
|
||||
page=state.page_revision.content_object, user=request.user
|
||||
),
|
||||
state.workflow_state.all_tasks_with_status(),
|
||||
)
|
||||
|
@ -162,35 +162,37 @@ class RecentEditsPanel(Component):
|
|||
if connection.vendor == "mysql":
|
||||
# MySQL can't handle the subselect created by the ORM version -
|
||||
# it fails with "This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'"
|
||||
last_edits = PageRevision.objects.raw(
|
||||
last_edits = Revision.objects.raw(
|
||||
"""
|
||||
SELECT wp.* FROM
|
||||
wagtailcore_pagerevision wp JOIN (
|
||||
SELECT max(created_at) AS max_created_at, page_id FROM
|
||||
wagtailcore_pagerevision WHERE user_id = %s GROUP BY page_id ORDER BY max_created_at DESC LIMIT %s
|
||||
) AS max_rev ON max_rev.max_created_at = wp.created_at ORDER BY wp.created_at DESC
|
||||
SELECT wr.* FROM
|
||||
wagtailcore_revision wr JOIN (
|
||||
SELECT max(created_at) AS max_created_at, object_id FROM
|
||||
wagtailcore_revision WHERE user_id = %s AND base_content_type_id = %s GROUP BY object_id ORDER BY max_created_at DESC LIMIT %s
|
||||
) AS max_rev ON max_rev.max_created_at = wr.created_at ORDER BY wr.created_at DESC
|
||||
""",
|
||||
[
|
||||
User._meta.pk.get_db_prep_value(request.user.pk, connection),
|
||||
get_default_page_content_type().id,
|
||||
edit_count,
|
||||
],
|
||||
)
|
||||
else:
|
||||
last_edits_dates = (
|
||||
PageRevision.objects.filter(user=request.user)
|
||||
.values("page_id")
|
||||
Revision.page_revisions.filter(user=request.user)
|
||||
.values("object_id")
|
||||
.annotate(latest_date=Max("created_at"))
|
||||
.order_by("-latest_date")
|
||||
.values("latest_date")[:edit_count]
|
||||
)
|
||||
last_edits = PageRevision.objects.filter(
|
||||
last_edits = Revision.page_revisions.filter(
|
||||
created_at__in=last_edits_dates
|
||||
).order_by("-created_at")
|
||||
|
||||
page_keys = [pr.page_id for pr in last_edits]
|
||||
# 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(revision.page_id)] for revision in last_edits
|
||||
[revision, pages.get(int(revision.object_id))] for revision in last_edits
|
||||
]
|
||||
context["request"] = request
|
||||
return context
|
||||
|
|
|
@ -16,7 +16,7 @@ from wagtail.log_actions import registry as log_action_registry
|
|||
from wagtail.models import (
|
||||
Page,
|
||||
PageLogEntry,
|
||||
PageRevision,
|
||||
Revision,
|
||||
TaskState,
|
||||
UserPagePermissionsProxy,
|
||||
WorkflowState,
|
||||
|
@ -83,8 +83,8 @@ def workflow_history_detail(request, page_id, workflow_state_id):
|
|||
# It's possible that the page is edited while the workflow is running, so some
|
||||
# tasks may be repeated. All tasks that have been completed no matter what
|
||||
# revision needs to be displayed on this page.
|
||||
page_revisions = PageRevision.objects.filter(
|
||||
page=page,
|
||||
page_revisions = Revision.page_revisions.filter(
|
||||
object_id=page.id,
|
||||
id__in=TaskState.objects.filter(workflow_state=workflow_state).values_list(
|
||||
"page_revision_id", flat=True
|
||||
),
|
||||
|
|
|
@ -6,19 +6,19 @@ from django.views.decorators.http import require_GET
|
|||
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.mail import send_moderation_notification
|
||||
from wagtail.models import PageRevision
|
||||
from wagtail.models import Revision
|
||||
|
||||
|
||||
def approve_moderation(request, revision_id):
|
||||
revision = get_object_or_404(PageRevision, id=revision_id)
|
||||
if not revision.page.permissions_for_user(request.user).can_publish():
|
||||
revision = get_object_or_404(Revision.page_revisions, id=revision_id)
|
||||
if not revision.content_object.permissions_for_user(request.user).can_publish():
|
||||
raise PermissionDenied
|
||||
|
||||
if not revision.submitted_for_moderation:
|
||||
messages.error(
|
||||
request,
|
||||
_("The page '{0}' is not currently awaiting moderation.").format(
|
||||
revision.page.specific_deferred.get_admin_display_title()
|
||||
revision.content_object.specific_deferred.get_admin_display_title()
|
||||
),
|
||||
)
|
||||
return redirect("wagtailadmin_home")
|
||||
|
@ -27,16 +27,19 @@ def approve_moderation(request, revision_id):
|
|||
revision.approve_moderation(user=request.user)
|
||||
|
||||
message = _("Page '{0}' published.").format(
|
||||
revision.page.specific_deferred.get_admin_display_title()
|
||||
revision.content_object.specific_deferred.get_admin_display_title()
|
||||
)
|
||||
buttons = []
|
||||
if revision.page.url is not None:
|
||||
if revision.content_object.url is not None:
|
||||
buttons.append(
|
||||
messages.button(revision.page.url, _("View live"), new_window=False)
|
||||
messages.button(
|
||||
revision.content_object.url, _("View live"), new_window=False
|
||||
)
|
||||
)
|
||||
buttons.append(
|
||||
messages.button(
|
||||
reverse("wagtailadmin_pages:edit", args=(revision.page.id,)), _("Edit")
|
||||
reverse("wagtailadmin_pages:edit", args=(revision.content_object.id,)),
|
||||
_("Edit"),
|
||||
)
|
||||
)
|
||||
messages.success(request, message, buttons=buttons)
|
||||
|
@ -48,15 +51,15 @@ def approve_moderation(request, revision_id):
|
|||
|
||||
|
||||
def reject_moderation(request, revision_id):
|
||||
revision = get_object_or_404(PageRevision, id=revision_id)
|
||||
if not revision.page.permissions_for_user(request.user).can_publish():
|
||||
revision = get_object_or_404(Revision.page_revisions, id=revision_id)
|
||||
if not revision.content_object.permissions_for_user(request.user).can_publish():
|
||||
raise PermissionDenied
|
||||
|
||||
if not revision.submitted_for_moderation:
|
||||
messages.error(
|
||||
request,
|
||||
_("The page '{0}' is not currently awaiting moderation.").format(
|
||||
revision.page.specific_deferred.get_admin_display_title()
|
||||
revision.content_object.specific_deferred.get_admin_display_title()
|
||||
),
|
||||
)
|
||||
return redirect("wagtailadmin_home")
|
||||
|
@ -67,11 +70,13 @@ def reject_moderation(request, revision_id):
|
|||
messages.success(
|
||||
request,
|
||||
_("Page '{0}' rejected for publication.").format(
|
||||
revision.page.specific_deferred.get_admin_display_title()
|
||||
revision.content_object.specific_deferred.get_admin_display_title()
|
||||
),
|
||||
buttons=[
|
||||
messages.button(
|
||||
reverse("wagtailadmin_pages:edit", args=(revision.page.id,)),
|
||||
reverse(
|
||||
"wagtailadmin_pages:edit", args=(revision.content_object.id,)
|
||||
),
|
||||
_("Edit"),
|
||||
)
|
||||
],
|
||||
|
@ -85,20 +90,20 @@ def reject_moderation(request, revision_id):
|
|||
|
||||
@require_GET
|
||||
def preview_for_moderation(request, revision_id):
|
||||
revision = get_object_or_404(PageRevision, id=revision_id)
|
||||
if not revision.page.permissions_for_user(request.user).can_publish():
|
||||
revision = get_object_or_404(Revision.page_revisions, id=revision_id)
|
||||
if not revision.content_object.permissions_for_user(request.user).can_publish():
|
||||
raise PermissionDenied
|
||||
|
||||
if not revision.submitted_for_moderation:
|
||||
messages.error(
|
||||
request,
|
||||
_("The page '{0}' is not currently awaiting moderation.").format(
|
||||
revision.page.specific_deferred.get_admin_display_title()
|
||||
revision.content_object.specific_deferred.get_admin_display_title()
|
||||
),
|
||||
)
|
||||
return redirect("wagtailadmin_home")
|
||||
|
||||
page = revision.as_page_object()
|
||||
page = revision.as_object()
|
||||
|
||||
try:
|
||||
preview_mode = page.default_preview_mode
|
||||
|
|
|
@ -27,7 +27,7 @@ def revisions_revert(request, page_id, revision_id):
|
|||
raise PermissionDenied
|
||||
|
||||
revision = get_object_or_404(page.revisions, id=revision_id)
|
||||
revision_page = revision.as_page_object()
|
||||
revision_page = revision.as_object()
|
||||
|
||||
content_type = ContentType.objects.get_for_model(page)
|
||||
page_class = content_type.model_class()
|
||||
|
@ -95,7 +95,7 @@ def revisions_view(request, page_id, revision_id):
|
|||
raise PermissionDenied
|
||||
|
||||
revision = get_object_or_404(page.revisions, id=revision_id)
|
||||
revision_page = revision.as_page_object()
|
||||
revision_page = revision.as_object()
|
||||
|
||||
try:
|
||||
preview_mode = page.default_preview_mode
|
||||
|
@ -119,14 +119,12 @@ def revisions_compare(request, page_id, revision_id_a, revision_id_b):
|
|||
elif revision_id_a == "earliest":
|
||||
revision_a = page.revisions.order_by("created_at", "id").first()
|
||||
if revision_a:
|
||||
revision_a = revision_a.as_page_object()
|
||||
revision_a = revision_a.as_object()
|
||||
revision_a_heading = _("Earliest")
|
||||
else:
|
||||
raise Http404
|
||||
else:
|
||||
revision_a = get_object_or_404(
|
||||
page.revisions, id=revision_id_a
|
||||
).as_page_object()
|
||||
revision_a = get_object_or_404(page.revisions, id=revision_id_a).as_object()
|
||||
revision_a_heading = str(
|
||||
get_object_or_404(page.revisions, id=revision_id_a).created_at
|
||||
)
|
||||
|
@ -141,14 +139,12 @@ def revisions_compare(request, page_id, revision_id_a, revision_id_b):
|
|||
elif revision_id_b == "latest":
|
||||
revision_b = page.revisions.order_by("created_at", "id").last()
|
||||
if revision_b:
|
||||
revision_b = revision_b.as_page_object()
|
||||
revision_b = revision_b.as_object()
|
||||
revision_b_heading = _("Latest")
|
||||
else:
|
||||
raise Http404
|
||||
else:
|
||||
revision_b = get_object_or_404(
|
||||
page.revisions, id=revision_id_b
|
||||
).as_page_object()
|
||||
revision_b = get_object_or_404(page.revisions, id=revision_id_b).as_object()
|
||||
revision_b_heading = str(
|
||||
get_object_or_404(page.revisions, id=revision_id_b).created_at
|
||||
)
|
||||
|
|
|
@ -10,7 +10,13 @@ from django.views.generic import View
|
|||
from wagtail.admin import messages
|
||||
from wagtail.admin.auth import user_has_any_page_permission, user_passes_test
|
||||
from wagtail.admin.modal_workflow import render_modal_workflow
|
||||
from wagtail.models import Page, Task, TaskState, WorkflowState
|
||||
from wagtail.models import (
|
||||
Page,
|
||||
Task,
|
||||
TaskState,
|
||||
WorkflowState,
|
||||
get_default_page_content_type,
|
||||
)
|
||||
|
||||
|
||||
class BaseWorkflowFormView(View):
|
||||
|
@ -235,7 +241,10 @@ def preview_revision_for_task(request, page_id, task_id):
|
|||
task = get_object_or_404(Task, id=task_id).specific
|
||||
try:
|
||||
task_state = TaskState.objects.get(
|
||||
page_revision__page=page, task=task, status=TaskState.STATUS_IN_PROGRESS
|
||||
page_revision__base_content_type=get_default_page_content_type(),
|
||||
page_revision__object_id=page.id,
|
||||
task=task,
|
||||
status=TaskState.STATUS_IN_PROGRESS,
|
||||
)
|
||||
except TaskState.DoesNotExist:
|
||||
messages.error(
|
||||
|
@ -251,7 +260,7 @@ def preview_revision_for_task(request, page_id, task_id):
|
|||
if not task.get_actions(page, request.user):
|
||||
raise PermissionDenied
|
||||
|
||||
page_to_view = revision.as_page_object()
|
||||
page_to_view = revision.as_object()
|
||||
|
||||
# TODO: provide workflow actions within this view
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from wagtail.admin.userbar import (
|
|||
EditPageItem,
|
||||
RejectModerationEditPageItem,
|
||||
)
|
||||
from wagtail.models import Page, PageRevision
|
||||
from wagtail.models import Page, Revision
|
||||
|
||||
|
||||
@permission_required("wagtailadmin.access_admin", raise_exception=True)
|
||||
|
@ -40,10 +40,10 @@ def for_frontend(request, page_id):
|
|||
@permission_required("wagtailadmin.access_admin", raise_exception=True)
|
||||
def for_moderation(request, revision_id):
|
||||
items = [
|
||||
EditPageItem(PageRevision.objects.get(id=revision_id).page),
|
||||
AddPageItem(PageRevision.objects.get(id=revision_id).page),
|
||||
ApproveModerationEditPageItem(PageRevision.objects.get(id=revision_id)),
|
||||
RejectModerationEditPageItem(PageRevision.objects.get(id=revision_id)),
|
||||
EditPageItem(Revision.page_revisions.get(id=revision_id).content_object),
|
||||
AddPageItem(Revision.page_revisions.get(id=revision_id).content_object),
|
||||
ApproveModerationEditPageItem(Revision.page_revisions.get(id=revision_id)),
|
||||
RejectModerationEditPageItem(Revision.page_revisions.get(id=revision_id)),
|
||||
]
|
||||
|
||||
for fn in hooks.get_hooks("construct_wagtail_userbar"):
|
||||
|
|
|
@ -18,9 +18,9 @@ from wagtail.models import ( # noqa
|
|||
PageLogEntryQuerySet,
|
||||
PageManager,
|
||||
PagePermissionTester,
|
||||
PageRevision,
|
||||
PageSubscription,
|
||||
PageViewRestriction,
|
||||
Revision,
|
||||
SubmittedRevisionsManager,
|
||||
Task,
|
||||
TaskManager,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from wagtail.models import PageLogEntry, PageRevision
|
||||
from wagtail.models import PageLogEntry, Revision
|
||||
|
||||
|
||||
def get_comparison(page, revision_a, revision_b):
|
||||
|
@ -19,29 +19,32 @@ class Command(BaseCommand):
|
|||
def handle(self, *args, **options):
|
||||
current_page_id = None
|
||||
missing_models_content_type_ids = set()
|
||||
for revision in (
|
||||
PageRevision.objects.order_by("page_id", "created_at")
|
||||
.select_related("page")
|
||||
.iterator()
|
||||
):
|
||||
for revision in Revision.page_revisions.order_by(
|
||||
"object_id", "created_at"
|
||||
).iterator():
|
||||
# This revision is for a page type that is no longer in the database. Bail out early.
|
||||
if revision.page.content_type_id in missing_models_content_type_ids:
|
||||
if (
|
||||
revision.content_object.content_type_id
|
||||
in missing_models_content_type_ids
|
||||
):
|
||||
continue
|
||||
if not revision.page.specific_class:
|
||||
missing_models_content_type_ids.add(revision.page.content_type_id)
|
||||
if not revision.content_object.specific_class:
|
||||
missing_models_content_type_ids.add(
|
||||
revision.content_object.content_type_id
|
||||
)
|
||||
continue
|
||||
|
||||
is_new_page = revision.page_id != current_page_id
|
||||
is_new_page = revision.object_id != current_page_id
|
||||
if is_new_page:
|
||||
# reset previous revision when encountering a new page.
|
||||
previous_revision = None
|
||||
|
||||
has_content_changes = False
|
||||
current_page_id = revision.page_id
|
||||
current_page_id = revision.object_id
|
||||
|
||||
if not PageLogEntry.objects.filter(revision=revision).exists():
|
||||
try:
|
||||
current_revision_as_page = revision.as_page_object()
|
||||
current_revision_as_page = revision.as_object()
|
||||
except Exception:
|
||||
# restoring old revisions may fail if e.g. they have an on_delete=PROTECT foreign key
|
||||
# to a no-longer-existing model instance. We cannot compare changes between two
|
||||
|
@ -49,11 +52,11 @@ class Command(BaseCommand):
|
|||
# change at the point that it went from restorable to non-restorable or vice versa.
|
||||
current_revision_as_page = None
|
||||
|
||||
published = revision.id == revision.page.live_revision_id
|
||||
published = revision.id == revision.content_object.live_revision_id
|
||||
|
||||
if previous_revision is not None:
|
||||
try:
|
||||
previous_revision_as_page = previous_revision.as_page_object()
|
||||
previous_revision_as_page = previous_revision.as_object()
|
||||
except Exception:
|
||||
previous_revision_as_page = None
|
||||
|
||||
|
@ -72,7 +75,7 @@ class Command(BaseCommand):
|
|||
else:
|
||||
# Must use .specific so the comparison picks up all fields, not just base Page ones.
|
||||
comparison = get_comparison(
|
||||
revision.page.specific,
|
||||
revision.content_object.specific,
|
||||
previous_revision_as_page,
|
||||
current_revision_as_page,
|
||||
)
|
||||
|
@ -106,7 +109,7 @@ class Command(BaseCommand):
|
|||
|
||||
def log_page_action(self, action, revision, has_content_changes):
|
||||
PageLogEntry.objects.log_action(
|
||||
instance=revision.page.specific,
|
||||
instance=revision.content_object.specific,
|
||||
action=action,
|
||||
data={},
|
||||
revision=None if action == "wagtail.create" else revision,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.utils import dateparse, timezone
|
||||
|
||||
from wagtail.models import Page, PageRevision
|
||||
from wagtail.models import Page, Revision
|
||||
|
||||
|
||||
def revision_date_expired(r):
|
||||
|
@ -58,7 +58,7 @@ class Command(BaseCommand):
|
|||
# 2. get all page revisions for moderation that have been expired
|
||||
expired_revs = [
|
||||
r
|
||||
for r in PageRevision.objects.filter(submitted_for_moderation=True)
|
||||
for r in Revision.page_revisions.filter(submitted_for_moderation=True)
|
||||
if revision_date_expired(r)
|
||||
]
|
||||
if dryrun:
|
||||
|
@ -88,7 +88,7 @@ class Command(BaseCommand):
|
|||
er.save()
|
||||
|
||||
# 3. get all revisions that need to be published
|
||||
revs_for_publishing = PageRevision.objects.filter(
|
||||
revs_for_publishing = Revision.page_revisions.filter(
|
||||
approved_go_live_at__lt=timezone.now()
|
||||
)
|
||||
if dryrun:
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand
|
|||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
||||
from wagtail.models import PageRevision
|
||||
from wagtail.models import Revision
|
||||
|
||||
try:
|
||||
from wagtail.models import WorkflowState
|
||||
|
@ -39,7 +39,7 @@ class Command(BaseCommand):
|
|||
|
||||
def purge_revisions(days=None):
|
||||
# exclude revisions which have been submitted for moderation in the old system
|
||||
purgeable_revisions = PageRevision.objects.exclude(
|
||||
purgeable_revisions = Revision.page_revisions.exclude(
|
||||
submitted_for_moderation=True
|
||||
).exclude(
|
||||
# and exclude revisions with an approved_go_live_at date
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# Generated by Django 4.0.3 on 2022-04-26 12:31
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("wagtailcore", "0069_log_entry_jsonfield"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name="PageRevision",
|
||||
new_name="Revision",
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="revision",
|
||||
options={"verbose_name": "revision", "verbose_name_plural": "revisions"},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="revision",
|
||||
name="page",
|
||||
field=models.CharField(max_length=255, verbose_name="object id"),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="revision",
|
||||
old_name="page",
|
||||
new_name="object_id",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="revision",
|
||||
name="content_type",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="+",
|
||||
to="contenttypes.contenttype",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="revision",
|
||||
name="base_content_type",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="+",
|
||||
to="contenttypes.contenttype",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="revision",
|
||||
index=models.Index(
|
||||
fields=["content_type", "object_id"],
|
||||
name="content_object_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="revision",
|
||||
index=models.Index(
|
||||
fields=["base_content_type", "object_id"],
|
||||
name="base_content_object_idx",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,37 @@
|
|||
# Generated by Django 4.0.3 on 2022-04-26 13:18
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.db.models.fields.json import KeyTextTransform
|
||||
from django.db.models.functions import Cast
|
||||
|
||||
|
||||
def populate_revision_content_type(apps, schema_editor):
|
||||
ContentType = apps.get_model("contenttypes.ContentType")
|
||||
Revision = apps.get_model("wagtailcore.Revision")
|
||||
page_type = ContentType.objects.get(app_label="wagtailcore", model="page")
|
||||
Revision.objects.all().update(
|
||||
base_content_type=page_type,
|
||||
content_type_id=Cast(
|
||||
KeyTextTransform("content_type", models.F("content")),
|
||||
output_field=models.PositiveIntegerField(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def empty_revision_content_type(apps, schema_editor):
|
||||
Revision = apps.get_model("wagtailcore.Revision")
|
||||
Revision.objects.all().update(base_content_type=None, content_type=None)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("wagtailcore", "0070_rename_pagerevision_revision"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
populate_revision_content_type,
|
||||
empty_revision_content_type,
|
||||
)
|
||||
]
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 4.0.3 on 2022-04-26 13:28
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
("wagtailcore", "0071_populate_revision_content_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="revision",
|
||||
name="content_type",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.contenttype",
|
||||
related_name="+",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="revision",
|
||||
name="base_content_type",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.contenttype",
|
||||
related_name="+",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -12,6 +12,7 @@ as Page.
|
|||
import functools
|
||||
import logging
|
||||
import uuid
|
||||
import warnings
|
||||
from io import StringIO
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
@ -28,7 +29,7 @@ from django.core.serializers.json import DjangoJSONEncoder
|
|||
from django.db import models, transaction
|
||||
from django.db.models import DEFERRED, Q, Value
|
||||
from django.db.models.expressions import OuterRef, Subquery
|
||||
from django.db.models.functions import Concat, Substr
|
||||
from django.db.models.functions import Cast, Concat, Substr
|
||||
from django.dispatch import receiver
|
||||
from django.http import Http404
|
||||
from django.template.response import TemplateResponse
|
||||
|
@ -78,6 +79,7 @@ from wagtail.signals import (
|
|||
)
|
||||
from wagtail.treebeard import TreebeardPathFixMixin
|
||||
from wagtail.url_routing import RouteResult
|
||||
from wagtail.utils.deprecation import RemovedInWagtail50Warning
|
||||
|
||||
from .audit_log import ( # noqa
|
||||
BaseLogEntry,
|
||||
|
@ -315,7 +317,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
verbose_name=_("latest revision created at"), null=True, editable=False
|
||||
)
|
||||
live_revision = models.ForeignKey(
|
||||
"PageRevision",
|
||||
"Revision",
|
||||
related_name="+",
|
||||
verbose_name=_("live revision"),
|
||||
on_delete=models.SET_NULL,
|
||||
|
@ -400,6 +402,12 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def revisions(self):
|
||||
# This acts as a replacement for Django's related manager since we don't
|
||||
# use a GenericRelation/GenericForeignKey.
|
||||
return Revision.page_revisions.filter(object_id=self.id)
|
||||
|
||||
@classmethod
|
||||
def get_streamfield_names(cls):
|
||||
return get_streamfield_names(cls)
|
||||
|
@ -899,12 +907,22 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
comment.save()
|
||||
|
||||
# Create revision
|
||||
revision = self.revisions.create(
|
||||
content=self.serializable_data(),
|
||||
user=user,
|
||||
# We want to always use the default Page model's ContentType as the
|
||||
# base_content_type so that we can query for page revisions without
|
||||
# having to know the specific Page type.
|
||||
revision = Revision.objects.create(
|
||||
content_type_id=self.content_type_id,
|
||||
base_content_type_id=get_default_page_content_type().id,
|
||||
object_id=self.id,
|
||||
submitted_for_moderation=submitted_for_moderation,
|
||||
user=user,
|
||||
approved_go_live_at=approved_go_live_at,
|
||||
content=self.serializable_data(),
|
||||
)
|
||||
# This is necessary to prevent fetching a fresh page instance when
|
||||
# changing first_published_at.
|
||||
# Ref: https://github.com/wagtail/wagtail/pull/3498
|
||||
revision.content_object = self
|
||||
|
||||
for comment in new_comments:
|
||||
comment.revision_created = revision
|
||||
|
@ -985,7 +1003,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
latest_revision = self.get_latest_revision()
|
||||
|
||||
if latest_revision:
|
||||
return latest_revision.as_page_object()
|
||||
return latest_revision.as_object()
|
||||
else:
|
||||
return self.specific
|
||||
|
||||
|
@ -998,7 +1016,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
This is called by Wagtail whenever a page with aliases is published.
|
||||
|
||||
:param revision: The revision of the original page that we are updating to (used for logging purposes)
|
||||
:type revision: PageRevision, optional
|
||||
:type revision: Revision, optional
|
||||
:param user: The user who is publishing (used for logging purposes)
|
||||
:type user: User, optional
|
||||
"""
|
||||
|
@ -2144,17 +2162,59 @@ class Orderable(models.Model):
|
|||
ordering = ["sort_order"]
|
||||
|
||||
|
||||
class RevisionQuerySet(models.QuerySet):
|
||||
def page_revisions(self):
|
||||
return self.filter(base_content_type=get_default_page_content_type())
|
||||
|
||||
def submitted(self):
|
||||
return self.filter(submitted_for_moderation=True)
|
||||
|
||||
def for_instance(self, instance):
|
||||
return self.filter(
|
||||
content_type=ContentType.objects.get_for_model(
|
||||
instance, for_concrete_model=False
|
||||
),
|
||||
object_id=str(instance.pk),
|
||||
)
|
||||
|
||||
def filter(self, *args, **kwargs):
|
||||
# Make sure the object_id is a string, so queries that use the target model's
|
||||
# id still works even when its id is not a string
|
||||
# (e.g. Revision.objects.filter(object_id=some_page.id)).
|
||||
if "object_id" in kwargs:
|
||||
kwargs["object_id"] = str(kwargs["object_id"])
|
||||
return super().filter(*args, **kwargs)
|
||||
|
||||
|
||||
class PageRevisionsManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return RevisionQuerySet(self.model, using=self._db).page_revisions()
|
||||
|
||||
def for_instance(self, instance):
|
||||
return self.get_queryset().for_instance(instance)
|
||||
|
||||
def submitted(self):
|
||||
return self.get_queryset().submitted()
|
||||
|
||||
|
||||
class SubmittedRevisionsManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(submitted_for_moderation=True)
|
||||
return RevisionQuerySet(self.model, using=self._db).submitted()
|
||||
|
||||
def for_instance(self, instance):
|
||||
return self.get_queryset().for_instance(instance)
|
||||
|
||||
|
||||
class PageRevision(models.Model):
|
||||
page = models.ForeignKey(
|
||||
"Page",
|
||||
verbose_name=_("page"),
|
||||
related_name="revisions",
|
||||
on_delete=models.CASCADE,
|
||||
class Revision(models.Model):
|
||||
content_type = models.ForeignKey(
|
||||
ContentType, on_delete=models.CASCADE, related_name="+"
|
||||
)
|
||||
base_content_type = models.ForeignKey(
|
||||
ContentType, on_delete=models.CASCADE, related_name="+"
|
||||
)
|
||||
object_id = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("object id"),
|
||||
)
|
||||
submitted_for_moderation = models.BooleanField(
|
||||
verbose_name=_("submitted for moderation"), default=False, db_index=True
|
||||
|
@ -2175,8 +2235,40 @@ class PageRevision(models.Model):
|
|||
)
|
||||
|
||||
objects = models.Manager()
|
||||
page_revisions = PageRevisionsManager()
|
||||
submitted_revisions = SubmittedRevisionsManager()
|
||||
|
||||
@cached_property
|
||||
def content_object(self):
|
||||
return self.base_content_type.get_object_for_this_type(pk=self.object_id)
|
||||
|
||||
@cached_property
|
||||
def specific_content_object(self):
|
||||
if isinstance(self.content_object, Page):
|
||||
return self.content_object.specific
|
||||
return self.content_type.get_object_for_this_type(pk=self.object_id)
|
||||
|
||||
@property
|
||||
def page(self):
|
||||
warnings.warn(
|
||||
"Revisions should access .content_object instead of .page "
|
||||
"to retrieve the object.",
|
||||
category=RemovedInWagtail50Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.content_object
|
||||
|
||||
@property
|
||||
def page_id(self):
|
||||
warnings.warn(
|
||||
"Revisions should access .object_id instead of .page_id "
|
||||
"to retrieve the object's primary key. For page revisions, "
|
||||
"you may need to cast the object_id to integer first.",
|
||||
category=RemovedInWagtail50Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return int(self.object_id)
|
||||
|
||||
def save(self, user=None, *args, **kwargs):
|
||||
# Set default value for created_at to now
|
||||
# We cannot use auto_now_add as that will override
|
||||
|
@ -2184,12 +2276,19 @@ class PageRevision(models.Model):
|
|||
if self.created_at is None:
|
||||
self.created_at = timezone.now()
|
||||
|
||||
# Set default value for base_content_type to the content_type.
|
||||
# Page revisions should set this to the default Page model's content type,
|
||||
# but the distinction may not be necessary for models that do not use inheritance.
|
||||
if self.base_content_type_id is None:
|
||||
self.base_content_type_id = self.content_type_id
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
if self.submitted_for_moderation:
|
||||
# ensure that all other revisions of this page have the 'submitted for moderation' flag unset
|
||||
self.page.revisions.exclude(id=self.id).update(
|
||||
submitted_for_moderation=False
|
||||
)
|
||||
# ensure that all other revisions of this object have the 'submitted for moderation' flag unset
|
||||
Revision.objects.filter(
|
||||
base_content_type_id=self.base_content_type_id,
|
||||
object_id=self.object_id,
|
||||
).exclude(id=self.id).update(submitted_for_moderation=False)
|
||||
|
||||
if (
|
||||
self.approved_go_live_at is None
|
||||
|
@ -2197,7 +2296,7 @@ class PageRevision(models.Model):
|
|||
and "approved_go_live_at" in kwargs["update_fields"]
|
||||
):
|
||||
# Log scheduled revision publish cancellation
|
||||
page = self.as_page_object()
|
||||
page = self.as_object()
|
||||
# go_live_at = kwargs['update_fields'][]
|
||||
log(
|
||||
instance=page,
|
||||
|
@ -2215,19 +2314,28 @@ class PageRevision(models.Model):
|
|||
revision=self,
|
||||
)
|
||||
|
||||
def as_object(self):
|
||||
return self.specific_content_object.with_content_json(self.content)
|
||||
|
||||
def as_page_object(self):
|
||||
return self.page.specific.with_content_json(self.content)
|
||||
warnings.warn(
|
||||
"Revisions should use .as_object() instead of .as_page_object() "
|
||||
"to create the object.",
|
||||
category=RemovedInWagtail50Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.as_object()
|
||||
|
||||
def approve_moderation(self, user=None):
|
||||
if self.submitted_for_moderation:
|
||||
logger.info(
|
||||
'Page moderation approved: "%s" id=%d revision_id=%d',
|
||||
self.page.title,
|
||||
self.page.id,
|
||||
self.content_object.title,
|
||||
self.content_object.id,
|
||||
self.id,
|
||||
)
|
||||
log(
|
||||
instance=self.as_page_object(),
|
||||
instance=self.as_object(),
|
||||
action="wagtail.moderation.approve",
|
||||
user=user,
|
||||
revision=self,
|
||||
|
@ -2238,12 +2346,12 @@ class PageRevision(models.Model):
|
|||
if self.submitted_for_moderation:
|
||||
logger.info(
|
||||
'Page moderation rejected: "%s" id=%d revision_id=%d',
|
||||
self.page.title,
|
||||
self.page.id,
|
||||
self.content_object.title,
|
||||
self.content_object.id,
|
||||
self.id,
|
||||
)
|
||||
log(
|
||||
instance=self.as_page_object(),
|
||||
instance=self.as_object(),
|
||||
action="wagtail.moderation.reject",
|
||||
user=user,
|
||||
revision=self,
|
||||
|
@ -2257,7 +2365,10 @@ class PageRevision(models.Model):
|
|||
# newer than any revision that might exist in the database
|
||||
return True
|
||||
latest_revision = (
|
||||
PageRevision.objects.filter(page_id=self.page_id)
|
||||
Revision.objects.filter(
|
||||
base_content_type_id=self.base_content_type_id,
|
||||
object_id=self.object_id,
|
||||
)
|
||||
.order_by("-created_at", "-id")
|
||||
.first()
|
||||
)
|
||||
|
@ -2268,7 +2379,7 @@ class PageRevision(models.Model):
|
|||
|
||||
try:
|
||||
next_revision = self.get_next()
|
||||
except PageRevision.DoesNotExist:
|
||||
except Revision.DoesNotExist:
|
||||
next_revision = None
|
||||
|
||||
if next_revision:
|
||||
|
@ -2287,17 +2398,33 @@ class PageRevision(models.Model):
|
|||
).execute()
|
||||
|
||||
def get_previous(self):
|
||||
return self.get_previous_by_created_at(page=self.page)
|
||||
return self.get_previous_by_created_at(
|
||||
base_content_type_id=self.base_content_type_id,
|
||||
object_id=self.object_id,
|
||||
)
|
||||
|
||||
def get_next(self):
|
||||
return self.get_next_by_created_at(page=self.page)
|
||||
return self.get_next_by_created_at(
|
||||
base_content_type_id=self.base_content_type_id,
|
||||
object_id=self.object_id,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '"' + str(self.page) + '" at ' + str(self.created_at)
|
||||
return '"' + str(self.content_object) + '" at ' + str(self.created_at)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("page revision")
|
||||
verbose_name_plural = _("page revisions")
|
||||
verbose_name = _("revision")
|
||||
verbose_name_plural = _("revisions")
|
||||
indexes = [
|
||||
models.Index(
|
||||
fields=["content_type", "object_id"],
|
||||
name="content_object_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["base_content_type", "object_id"],
|
||||
name="base_content_object_idx",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
PAGE_PERMISSION_TYPES = [
|
||||
|
@ -2366,9 +2493,9 @@ class UserPagePermissionsProxy:
|
|||
|
||||
# Deal with the trivial cases first...
|
||||
if not self.user.is_active:
|
||||
return PageRevision.objects.none()
|
||||
return Revision.objects.none()
|
||||
if self.user.is_superuser:
|
||||
return PageRevision.submitted_revisions.all()
|
||||
return Revision.page_revisions.submitted()
|
||||
|
||||
# get the list of pages for which they have direct publish permission
|
||||
# (i.e. they can publish any page within this subtree)
|
||||
|
@ -2378,16 +2505,20 @@ class UserPagePermissionsProxy:
|
|||
.distinct()
|
||||
)
|
||||
if not publishable_pages_paths:
|
||||
return PageRevision.objects.none()
|
||||
return Revision.objects.none()
|
||||
|
||||
# compile a filter expression to apply to the PageRevision.submitted_revisions manager:
|
||||
# compile a filter expression to apply to the Revision.page_revisions.submitted() queryset:
|
||||
# return only those pages whose paths start with one of the publishable_pages paths
|
||||
only_my_sections = Q(page__path__startswith=publishable_pages_paths[0])
|
||||
only_my_sections = Q(path__startswith=publishable_pages_paths[0])
|
||||
for page_path in publishable_pages_paths[1:]:
|
||||
only_my_sections = only_my_sections | Q(page__path__startswith=page_path)
|
||||
only_my_sections = only_my_sections | Q(path__startswith=page_path)
|
||||
|
||||
# return the filtered queryset
|
||||
return PageRevision.submitted_revisions.filter(only_my_sections)
|
||||
return Revision.page_revisions.submitted().filter(
|
||||
object_id__in=Page.objects.filter(only_my_sections).values_list(
|
||||
Cast("pk", output_field=models.CharField()), flat=True
|
||||
)
|
||||
)
|
||||
|
||||
def for_page(self, page):
|
||||
"""Return a PagePermissionTester object that can be used to query whether this user has
|
||||
|
@ -3480,8 +3611,8 @@ class WorkflowState(models.Model):
|
|||
|
||||
def revisions(self):
|
||||
"""Returns all page revisions associated with task states linked to the current workflow state"""
|
||||
return PageRevision.objects.filter(
|
||||
page_id=self.page_id,
|
||||
return Revision.page_revisions.filter(
|
||||
object_id=self.page_id,
|
||||
id__in=self.task_states.values_list("page_revision_id", flat=True),
|
||||
).defer("content")
|
||||
|
||||
|
@ -3623,7 +3754,7 @@ class TaskState(models.Model):
|
|||
related_name="task_states",
|
||||
)
|
||||
page_revision = models.ForeignKey(
|
||||
"PageRevision",
|
||||
"Revision",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("page revision"),
|
||||
related_name="task_states",
|
||||
|
@ -3798,7 +3929,7 @@ class TaskState(models.Model):
|
|||
|
||||
def log_state_change_action(self, user, action):
|
||||
"""Log the approval/rejection action"""
|
||||
page = self.page_revision.as_page_object()
|
||||
page = self.page_revision.as_object()
|
||||
next_task = self.workflow_state.get_next_task()
|
||||
next_task_data = None
|
||||
if next_task:
|
||||
|
@ -3888,7 +4019,7 @@ class PageLogEntry(BaseLogEntry):
|
|||
)
|
||||
# Pointer to a specific page revision
|
||||
revision = models.ForeignKey(
|
||||
"wagtailcore.PageRevision",
|
||||
"wagtailcore.Revision",
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.DO_NOTHING,
|
||||
|
@ -3950,7 +4081,7 @@ class Comment(ClusterableModel):
|
|||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
revision_created = models.ForeignKey(
|
||||
PageRevision,
|
||||
Revision,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="created_comments",
|
||||
null=True,
|
||||
|
|
|
@ -456,13 +456,13 @@ class PageQuerySet(SearchableQuerySetMixin, TreeQuerySet):
|
|||
Annotates each page with the existence of an approved go live time.
|
||||
Used by `approved_schedule` property on `wagtailcore.models.Page`.
|
||||
"""
|
||||
from .models import PageRevision
|
||||
from .models import Revision
|
||||
|
||||
return self.annotate(
|
||||
_approved_schedule=Exists(
|
||||
PageRevision.objects.exclude(approved_go_live_at__isnull=True).filter(
|
||||
page__pk=OuterRef("pk")
|
||||
)
|
||||
Revision.page_revisions.exclude(
|
||||
approved_go_live_at__isnull=True
|
||||
).filter(object_id=OuterRef("pk"))
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.db import models
|
|||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from wagtail.models import Collection, Page, PageLogEntry, PageRevision
|
||||
from wagtail.models import Collection, Page, PageLogEntry, Revision
|
||||
from wagtail.signals import page_published, page_unpublished
|
||||
from wagtail.test.testapp.models import EventPage, SecretPage, SimplePage
|
||||
|
||||
|
@ -191,7 +191,7 @@ class TestPublishScheduledPagesCommand(TestCase):
|
|||
p = Page.objects.get(slug="hello-world")
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=p)
|
||||
Revision.page_revisions.filter(object_id=p.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -203,7 +203,7 @@ class TestPublishScheduledPagesCommand(TestCase):
|
|||
self.assertTrue(p.first_published_at)
|
||||
self.assertFalse(p.has_unpublished_changes)
|
||||
self.assertFalse(
|
||||
PageRevision.objects.filter(page=p)
|
||||
Revision.page_revisions.filter(object_id=p.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -251,7 +251,7 @@ class TestPublishScheduledPagesCommand(TestCase):
|
|||
p = Page.objects.get(slug="hello-world")
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=p)
|
||||
Revision.page_revisions.filter(object_id=p.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -261,7 +261,7 @@ class TestPublishScheduledPagesCommand(TestCase):
|
|||
p = Page.objects.get(slug="hello-world")
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=p)
|
||||
Revision.page_revisions.filter(object_id=p.id)
|
||||
.exclude(approved_go_live_at__isnull=True)
|
||||
.exists()
|
||||
)
|
||||
|
@ -336,14 +336,18 @@ class TestPublishScheduledPagesCommand(TestCase):
|
|||
p = Page.objects.get(slug="hello-world")
|
||||
self.assertFalse(p.live)
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(page=p, submitted_for_moderation=True).exists()
|
||||
Revision.page_revisions.filter(
|
||||
object_id=p.id, submitted_for_moderation=True
|
||||
).exists()
|
||||
)
|
||||
|
||||
management.call_command("publish_scheduled_pages")
|
||||
|
||||
p = Page.objects.get(slug="hello-world")
|
||||
self.assertFalse(
|
||||
PageRevision.objects.filter(page=p, submitted_for_moderation=True).exists()
|
||||
Revision.page_revisions.filter(
|
||||
object_id=p.id, submitted_for_moderation=True
|
||||
).exists()
|
||||
)
|
||||
|
||||
|
||||
|
@ -379,8 +383,12 @@ class TestPurgeRevisionsCommand(TestCase):
|
|||
self.run_command()
|
||||
|
||||
# revision 1 should be deleted, revision 2 should not be
|
||||
self.assertNotIn(revision_1, PageRevision.objects.filter(page=self.page))
|
||||
self.assertIn(revision_2, PageRevision.objects.filter(page=self.page))
|
||||
self.assertNotIn(
|
||||
revision_1, Revision.page_revisions.filter(object_id=self.page.id)
|
||||
)
|
||||
self.assertIn(
|
||||
revision_2, Revision.page_revisions.filter(object_id=self.page.id)
|
||||
)
|
||||
|
||||
def test_revisions_in_moderation_not_purged(self):
|
||||
|
||||
|
@ -391,8 +399,8 @@ class TestPurgeRevisionsCommand(TestCase):
|
|||
self.run_command()
|
||||
|
||||
self.assertTrue(
|
||||
PageRevision.objects.filter(
|
||||
page=self.page, submitted_for_moderation=True
|
||||
Revision.page_revisions.filter(
|
||||
object_id=self.page.id, submitted_for_moderation=True
|
||||
).exists()
|
||||
)
|
||||
|
||||
|
@ -408,7 +416,9 @@ class TestPurgeRevisionsCommand(TestCase):
|
|||
self.run_command()
|
||||
# even though no longer the latest revision, the old revision should stay as it is
|
||||
# attached to an in progress workflow
|
||||
self.assertIn(revision, PageRevision.objects.filter(page=self.page))
|
||||
self.assertIn(
|
||||
revision, Revision.page_revisions.filter(object_id=self.page.id)
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
@ -422,7 +432,9 @@ class TestPurgeRevisionsCommand(TestCase):
|
|||
|
||||
self.run_command()
|
||||
|
||||
self.assertIn(approved_revision, PageRevision.objects.filter(page=self.page))
|
||||
self.assertIn(
|
||||
approved_revision, Revision.page_revisions.filter(object_id=self.page.id)
|
||||
)
|
||||
|
||||
def test_purge_revisions_with_date_cutoff(self):
|
||||
|
||||
|
@ -433,7 +445,9 @@ class TestPurgeRevisionsCommand(TestCase):
|
|||
self.run_command(days=30)
|
||||
|
||||
# revision should not be deleted, as it is younger than 30 days
|
||||
self.assertIn(old_revision, PageRevision.objects.filter(page=self.page))
|
||||
self.assertIn(
|
||||
old_revision, Revision.page_revisions.filter(object_id=self.page.id)
|
||||
)
|
||||
|
||||
old_revision.created_at = timezone.now() - timedelta(days=31)
|
||||
old_revision.save()
|
||||
|
@ -441,7 +455,9 @@ class TestPurgeRevisionsCommand(TestCase):
|
|||
self.run_command(days=30)
|
||||
|
||||
# revision is now older than 30 days, so should be deleted
|
||||
self.assertNotIn(old_revision, PageRevision.objects.filter(page=self.page))
|
||||
self.assertNotIn(
|
||||
old_revision, Revision.page_revisions.filter(object_id=self.page.id)
|
||||
)
|
||||
|
||||
|
||||
class TestCreateLogEntriesFromRevisionsCommand(TestCase):
|
||||
|
@ -509,8 +525,9 @@ class TestCreateLogEntriesFromRevisionsCommand(TestCase):
|
|||
|
||||
def test_command_doesnt_crash_for_revisions_without_page_model(self):
|
||||
with mock.patch(
|
||||
"wagtail.models.ContentType.model_class",
|
||||
"wagtail.models.Page.specific_class",
|
||||
return_value=None,
|
||||
new_callable=mock.PropertyMock,
|
||||
):
|
||||
management.call_command("create_log_entries_from_revisions")
|
||||
self.assertEqual(PageLogEntry.objects.count(), 0)
|
||||
|
|
|
@ -1489,7 +1489,7 @@ class TestCopyPage(TestCase):
|
|||
# get_latest_revision_as_page might bypass the revisions table if it determines
|
||||
# that there are no draft edits since publish - so retrieve it explicitly from the
|
||||
# revision data, to ensure it's been updated there too
|
||||
latest_revision = new_christmas_event.get_latest_revision().as_page_object()
|
||||
latest_revision = new_christmas_event.get_latest_revision().as_object()
|
||||
self.assertEqual(latest_revision.title, "New christmas event")
|
||||
self.assertEqual(latest_revision.slug, "new-christmas-event")
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ from wagtail.test.testapp.models import (
|
|||
SingleEventPage,
|
||||
StreamPage,
|
||||
)
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
||||
|
||||
class TestPageQuerySet(TestCase):
|
||||
|
@ -697,7 +698,7 @@ class TestPageQuerySetSearch(TestCase):
|
|||
self.assertNotIn((EventPage, unpublished_event), unpublish_signals_fired)
|
||||
|
||||
|
||||
class TestSpecificQuery(TestCase):
|
||||
class TestSpecificQuery(TestCase, WagtailTestUtils):
|
||||
"""
|
||||
Test the .specific() queryset method. This is isolated in its own test case
|
||||
because it is sensitive to database changes that might happen for other
|
||||
|
@ -816,15 +817,18 @@ class TestSpecificQuery(TestCase):
|
|||
# Ensure annotations are reapplied to specific() page queries
|
||||
|
||||
pages = Page.objects.live()
|
||||
pages.first().save_revision()
|
||||
pages.last().save_revision()
|
||||
user = self.create_test_user()
|
||||
pages.first().subscribers.create(user=user, comment_notifications=False)
|
||||
pages.last().subscribers.create(user=user, comment_notifications=False)
|
||||
|
||||
results = (
|
||||
Page.objects.live().specific().annotate(revision_count=Count("revisions"))
|
||||
Page.objects.live()
|
||||
.specific()
|
||||
.annotate(subscribers_count=Count("subscribers"))
|
||||
)
|
||||
|
||||
self.assertEqual(results.first().revision_count, 1)
|
||||
self.assertEqual(results.last().revision_count, 1)
|
||||
self.assertEqual(results.first().subscribers_count, 1)
|
||||
self.assertEqual(results.last().subscribers_count, 1)
|
||||
|
||||
def test_specific_query_with_search_and_annotation(self):
|
||||
# Ensure annotations are reapplied to specific() page queries
|
||||
|
|
Ładowanie…
Reference in New Issue