Replace `content_json` `TextField` with `content` `JSONField` in `PageRevision`

pull/8046/head
Sage Abdullah 2022-02-22 19:38:51 +07:00 zatwierdzone przez Jacob Topp-Mugglestone
rodzic 861a509b32
commit bae76a2af0
11 zmienionych plików z 90 dodań i 53 usunięć

Wyświetl plik

@ -490,7 +490,7 @@ The ``locale`` and ``translation_key`` fields have a unique key constraint to pr
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.
- Revisions can be created from any :class:`~wagtail.core.models.Page` object by calling its :meth:`~Page.save_revision` method
- The content of the page is JSON-serialised and stored in the :attr:`~PageRevision.content_json` field
- 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.core.models.Page` object by calling the :meth:`~PageRevision.as_page_object` method
Database fields
@ -520,12 +520,17 @@ Database fields
This links to the user that created the revision
.. attribute:: content_json
.. attribute:: content
(text)
(dict)
This field contains the JSON content for the page at the time the revision was created
.. versionchanged:: 2.17
The field has been renamed from ``content_json`` to ``content`` and it now uses :class:`~django.db.models.JSONField` instead of
:class:`~django.db.models.TextField`.
Managers
~~~~~~~~

Wyświetl plik

@ -24,6 +24,7 @@ Here are other changes related to the redesign:
* Remove UI code for legacy browser support: polyfills, IE11 workarounds, Modernizr (Thibaud Colas)
* Remove redirect auto-creation recipe from documentation as this feature is now supported in Wagtail core (Andy Babic)
* Remove IE11 warnings (Gianluca De Cola)
* Replace `content_json` `TextField` with `content` `JSONField` in `PageRevision` (Sage Abdullah)
### Bug fixes
@ -40,3 +41,9 @@ Here are other changes related to the redesign:
* IE11 support was officially dropped in Wagtail 2.15, as of this release there will no longer be a warning shown to users of this browser.
* Wagtail is fully compatible with Microsoft Edge, Microsofts replacement for Internet Explorer. You may consider using its `IE mode <https://docs.microsoft.com/en-us/deployedge/edge-ie-mode>`_ to keep access to IE11-only sites, while other sites and apps like Wagtail can leverage modern browser capabilities.
## Replaced `content_json` `TextField` with `content` `JSONField` in `PageRevision`
* The `content_json` field in the `PageRevision` model has been renamed to `content`.
* The field now internally uses `JSONField` instead of `TextField`.
* If you have a large number of `PageRevision` objects, running the migrations might take a while.

Wyświetl plik

@ -677,10 +677,10 @@ Note that the above migration will work on published Page objects only. If you a
page.save()
for revision in page.revisions.all():
revision_data = json.loads(revision.content_json)
revision_data = revision.content
revision_data, changed = pagerevision_converter(revision_data)
if changed:
revision.content_json = json.dumps(revision_data, cls=DjangoJSONEncoder)
revision.content = revision_data
revision.save()

Wyświetl plik

@ -326,7 +326,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
def test_edit_post_scheduled(self):
# put go_live_at and expire_at several days away from the current date, to avoid
# false matches in content_json__contains tests
# false matches in content__ tests
go_live_at = timezone.now() + datetime.timedelta(days=10)
expire_at = timezone.now() + datetime.timedelta(days=20)
post_data = {
@ -358,12 +358,12 @@ class TestPageEdit(TestCase, WagtailTestUtils):
# 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, content_json__contains=str(go_live_at.date())
page=child_page_new, content__go_live_at__startswith=str(go_live_at.date())
).exists()
)
self.assertTrue(
PageRevision.objects.filter(
page=child_page_new, content_json__contains=str(expire_at.date())
page=child_page_new, content__expire_at__startswith=str(expire_at.date())
).exists()
)

Wyświetl plik

@ -1,4 +1,3 @@
import json
import logging
import uuid
@ -182,7 +181,7 @@ class CopyPageAction:
revision.page = page_copy
# Update ID fields in content
revision_content = json.loads(revision.content_json)
revision_content = revision.content
revision_content["pk"] = page_copy.pk
for child_relation in get_all_child_relations(specific_page):
@ -207,7 +206,7 @@ class CopyPageAction:
copied_child_object.pk if copied_child_object else None
)
revision.content_json = json.dumps(revision_content)
revision.content = revision_content
# Save
revision.save()

Wyświetl plik

@ -150,9 +150,7 @@ class PublishPageRevisionAction:
)
# Update alias pages
page.update_aliases(
revision=revision, user=user, _content_json=revision.content_json
)
page.update_aliases(revision=revision, user=user, _content=revision.content)
if log_action:
data = None

Wyświetl plik

@ -1,5 +1,3 @@
import json
from django.core.management.base import BaseCommand
from django.utils import dateparse, timezone
@ -7,7 +5,7 @@ from wagtail.core.models import Page, PageRevision
def revision_date_expired(r):
expiry_str = json.loads(r.content_json).get("expire_at")
expiry_str = r.content.get("expire_at")
if not expiry_str:
return False
expire_at = dateparse.parse_datetime(expiry_str)
@ -72,7 +70,7 @@ class Command(BaseCommand):
self.stdout.write("Expiry datetime\t\tSlug\t\tName")
self.stdout.write("---------------\t\t----\t\t----")
for er in expired_revs:
rev_data = json.loads(er.content_json)
rev_data = er.content
self.stdout.write(
"{0}\t{1}\t{2}".format(
dateparse.parse_datetime(
@ -100,7 +98,7 @@ class Command(BaseCommand):
self.stdout.write("Go live datetime\t\tSlug\t\tName")
self.stdout.write("---------------\t\t\t----\t\t----")
for rp in revs_for_publishing:
rev_data = json.loads(rp.content_json)
rev_data = rp.content
self.stdout.write(
"{0}\t\t{1}\t{2}".format(
rp.approved_go_live_at.strftime("%Y-%m-%d %H:%M"),

Wyświetl plik

@ -1,5 +1,8 @@
import json
from django.core.management.base import BaseCommand
from django.db import models
from django.db.models.functions import Cast
from modelcluster.models import get_all_child_relations
from wagtail.core.models import PageRevision, get_page_models
@ -32,9 +35,12 @@ class Command(BaseCommand):
from_text = options["from_text"]
to_text = options["to_text"]
for revision in PageRevision.objects.filter(content_json__contains=from_text):
revision.content_json = revision.content_json.replace(from_text, to_text)
revision.save(update_fields=["content_json"])
for revision in PageRevision.objects.annotate(
content_text=Cast("content", output_field=models.TextField())
).filter(content_text__contains=from_text):
replacement = revision.content_text.replace(from_text, to_text)
revision.content = json.loads(replacement)
revision.save(update_fields=["content"])
for page_class in get_page_models():
self.stdout.write("scanning %s" % page_class._meta.verbose_name)

Wyświetl plik

@ -0,0 +1,27 @@
# Generated by Django 4.0.2 on 2022-02-22 13:06
import django.core.serializers.json
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("wagtailcore", "0066_collection_management_permissions"),
]
operations = [
migrations.AlterField(
model_name="pagerevision",
name="content_json",
field=models.JSONField(
encoder=django.core.serializers.json.DjangoJSONEncoder,
verbose_name="content JSON",
),
),
migrations.RenameField(
model_name="pagerevision",
old_name="content_json",
new_name="content",
),
]

Wyświetl plik

@ -10,7 +10,6 @@ as Page.
"""
import functools
import json
import logging
import uuid
from io import StringIO
@ -25,6 +24,7 @@ from django.core.cache import cache
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.handlers.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest
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
@ -900,7 +900,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
# Create revision
revision = self.revisions.create(
content_json=self.to_json(),
content=self.serializable_data(),
user=user,
submitted_for_moderation=submitted_for_moderation,
approved_go_live_at=approved_go_live_at,
@ -990,7 +990,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
return self.specific
def update_aliases(
self, *, revision=None, user=None, _content_json=None, _updated_ids=None
self, *, revision=None, user=None, _content=None, _updated_ids=None
):
"""
Publishes all aliases that follow this page with the latest content from this page.
@ -1005,8 +1005,8 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
specific_self = self.specific
# Only compute this if necessary since it's quite a heavy operation
if _content_json is None:
_content_json = self.to_json()
if _content is None:
_content = self.serializable_data()
# A list of IDs that have already been updated. This is just in case someone has
# created an alias loop (which is impossible to do with the UI Wagtail provides)
@ -1029,7 +1029,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
]
# Copy field content
alias_updated = alias.with_content_json(_content_json)
alias_updated = alias.with_content_json(_content)
# Publish the alias if it's currently in draft
alias_updated.live = True
@ -1115,7 +1115,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
alias.update_aliases(
revision=revision,
_content_json=_content_json,
_content=_content,
_updated_ids=_updated_ids,
)
@ -1936,10 +1936,10 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
context["action_url"] = action_url
return TemplateResponse(request, self.password_required_template, context)
def with_content_json(self, content_json):
def with_content_json(self, content):
"""
Returns a new version of the page with field values updated to reflect changes
in the provided ``content_json`` (which usually comes from a previously-saved
in the provided ``content`` (which usually comes from a previously-saved
page revision).
Certain field values are preserved in order to prevent errors if the returned
@ -1960,24 +1960,22 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
* ``wagtail_admin_comments`` (COMMENTS_RELATION_NAME)
"""
data = json.loads(content_json)
# Old revisions (pre Wagtail 2.15) may have saved comment data under the name 'comments'
# rather than the current relation name as set by COMMENTS_RELATION_NAME;
# if a 'comments' field exists and looks like our comments model, alter the data to use
# COMMENTS_RELATION_NAME before restoring
if (
COMMENTS_RELATION_NAME not in data
and "comments" in data
and isinstance(data["comments"], list)
and len(data["comments"])
and isinstance(data["comments"][0], dict)
and "contentpath" in data["comments"][0]
COMMENTS_RELATION_NAME not in content
and "comments" in content
and isinstance(content["comments"], list)
and len(content["comments"])
and isinstance(content["comments"][0], dict)
and "contentpath" in content["comments"][0]
):
data[COMMENTS_RELATION_NAME] = data["comments"]
del data["comments"]
content[COMMENTS_RELATION_NAME] = content["comments"]
del content["comments"]
obj = self.specific_class.from_serializable_data(data)
obj = self.specific_class.from_serializable_data(content)
# These should definitely never change between revisions
obj.id = self.id
@ -2153,7 +2151,9 @@ class PageRevision(models.Model):
blank=True,
on_delete=models.SET_NULL,
)
content_json = models.TextField(verbose_name=_("content JSON"))
content = models.JSONField(
verbose_name=_("content JSON"), encoder=DjangoJSONEncoder
)
approved_go_live_at = models.DateTimeField(
verbose_name=_("approved go live at"), null=True, blank=True, db_index=True
)
@ -2200,7 +2200,7 @@ class PageRevision(models.Model):
)
def as_page_object(self):
return self.page.specific.with_content_json(self.content_json)
return self.page.specific.with_content_json(self.content)
def approve_moderation(self, user=None):
if self.submitted_for_moderation:
@ -3467,7 +3467,7 @@ class WorkflowState(models.Model):
return PageRevision.objects.filter(
page_id=self.page_id,
id__in=self.task_states.values_list("page_revision_id", flat=True),
).defer("content_json")
).defer("content")
def _get_applicable_task_states(self):
"""Returns the set of task states whose status applies to the current revision"""

Wyświetl plik

@ -1,5 +1,4 @@
import datetime
import json
import unittest
from unittest.mock import Mock
@ -1493,7 +1492,7 @@ class TestCopyPage(TestCase):
# Check that the ids within the revision were updated correctly
new_revision = new_christmas_event.revisions.first()
new_revision_content = json.loads(new_revision.content_json)
new_revision_content = new_revision.content
self.assertEqual(new_revision_content["pk"], new_christmas_event.id)
self.assertEqual(
new_revision_content["speakers"][0]["page"], new_christmas_event.id
@ -3310,7 +3309,7 @@ class TestPageWithContentJSON(TestCase):
# Take a json representation of the page and update it
# with some alternative values
content = json.loads(original_page.to_json())
content = original_page.serializable_data()
content.update(
title="About them",
draft_title="About them",
@ -3333,10 +3332,8 @@ class TestPageWithContentJSON(TestCase):
owner=1,
)
# Convert values back to json and pass them to with_content_json()
# to get an updated version of the page
content_json = json.dumps(content)
updated_page = original_page.with_content_json(content_json)
# Pass the values to with_content_json() to get an updated version of the page
updated_page = original_page.with_content_json(content)
# The following attributes values should have changed
for attr_name in ("title", "slug", "content", "url_path", "show_in_menus"):
@ -3345,7 +3342,7 @@ class TestPageWithContentJSON(TestCase):
)
# The following attribute values should have been preserved,
# despite new values being provided in content_json
# despite new values being provided in content
for attr_name in (
"pk",
"path",