Skip to content

Commit 624f66e

Browse files
committed
script to migrate draft -> slate
1 parent f430591 commit 624f66e

File tree

6 files changed

+123
-33
lines changed

6 files changed

+123
-33
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from plone import api
2+
from plone.restapi.blocks import visit_blocks
3+
from zope.component.hooks import setSite
4+
from textwrap import indent
5+
import json
6+
import transaction
7+
8+
BLOCK_FIELDS = {
9+
"icons_and_numbers": ["columns.text"],
10+
"icons_and_text": ["description", "columns.text"],
11+
"numbers": ["numbers.text"],
12+
"slider": ["description"],
13+
"text1": ["content"],
14+
"text5": ["description", "text1", "text2", "content"],
15+
"text6": ["content"],
16+
"text7": ["content"],
17+
}
18+
19+
def find_values(value: dict, fields):
20+
for field in fields:
21+
if "." in field:
22+
field, rest = field.split(".", 1)
23+
else:
24+
rest = None
25+
if field not in value:
26+
continue
27+
nextvalue = value[field]
28+
if rest and isinstance(nextvalue, list):
29+
for item in nextvalue:
30+
yield from find_values(item, [rest])
31+
else:
32+
yield value, field, nextvalue
33+
34+
35+
def process_entities(text, entityRanges, entityMap):
36+
if len(entityRanges) == 1:
37+
offset = entityRanges[0]["offset"]
38+
length = entityRanges[0]["length"]
39+
key = entityRanges[0]["key"]
40+
pretext, linktext, posttext = text[:offset], text[offset:offset + length], text[offset + length:]
41+
result = []
42+
if pretext:
43+
result.append({"text": pretext})
44+
url = entityMap[str(key)]["data"]["url"]
45+
result.append({
46+
"type": "link",
47+
"children": [{"text": linktext}],
48+
"data": {"url": url}
49+
})
50+
if posttext:
51+
result.append({"text": posttext})
52+
return result
53+
elif len(entityRanges) == 0:
54+
return [{"text": text}]
55+
else:
56+
print(json.dumps(text, indent=4))
57+
breakpoint()
58+
59+
60+
def migrate_draft_to_slate(value, entityMap=None) -> list:
61+
match value:
62+
case {"blocks": [*blocks], "entityMap": entityMap}:
63+
result = []
64+
for block in blocks:
65+
result.extend(migrate_draft_to_slate(block, entityMap))
66+
return result
67+
case {"type": blocktype, "text": text, "entityRanges": entityRanges}:
68+
children = process_entities(text, entityRanges, entityMap)
69+
match blocktype:
70+
case "unstyled" | "align-left" | "buttons":
71+
return [{"type": "p", "children": children}]
72+
case "unordered-list-item":
73+
return [{"type": "ul", "children": [{"li": {"children": [{"text": text}]}}]}]
74+
case "blockquote":
75+
return [{"type": "blockquote", "children": children}]
76+
case _:
77+
breakpoint()
78+
raise Exception()
79+
case _:
80+
print(json.dumps(value, indent=4))
81+
breakpoint()
82+
raise Exception()
83+
84+
85+
def migrate_items():
86+
catalog = api.portal.get_tool(name="portal_catalog")
87+
i = 0
88+
for brain in catalog.unrestrictedSearchResults(block_types=list(BLOCK_FIELDS.keys())):
89+
obj = brain.getObject()
90+
91+
for block in visit_blocks(obj, obj.blocks):
92+
block_type = block.get("@type")
93+
for container, field, value in find_values(block, BLOCK_FIELDS.get(block_type, [])):
94+
if isinstance(value, list):
95+
# no value, or already migrated
96+
continue
97+
elif value is None:
98+
newvalue = []
99+
else:
100+
newvalue = migrate_draft_to_slate(value)
101+
if newvalue != value:
102+
container[field] = newvalue
103+
obj._p_changed = True
104+
i += 1
105+
print(f"{brain.getPath()} / {block_type} / {field}")
106+
print(indent(json.dumps(value, indent=4), " "))
107+
print()
108+
print(indent(json.dumps(newvalue, indent=4), " "))
109+
print()
110+
111+
print(f"Total fields processed: {i}")
112+
transaction.commit()
113+
114+
setSite(app.Plone)
115+
migrate_items()

frontend/core

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 4239f53489fadcc9de82186158c803d91b454096

frontend/src/components/Blocks/IconsAndText/View.jsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,12 @@ import { Container } from 'semantic-ui-react';
66
import { TextBlockView } from '@plone/volto-slate/blocks/Text';
77

88
const View = ({ data }) => {
9-
const checkHasContent = (content) => {
10-
if (content) {
11-
let blocks = content.blocks.filter((block) => block?.text !== '');
12-
return blocks.length > 0 ? true : false;
13-
}
14-
};
159
let content = (
1610
<>
17-
{(data.title || checkHasContent(data.description)) && (
11+
{(data.title || data.description) && (
1812
<div className="block-content-header">
1913
{data.title && <div className={cx('title')}>{data.title}</div>}
20-
{checkHasContent(data.description) && (
14+
{data.description && (
2115
<div className="description">
2216
<TextBlockView data={{ value: data.description }} />
2317
</div>

frontend/src/components/Blocks/ImageColumns/View.jsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,9 @@ import { Container } from 'semantic-ui-react';
66
import { TextBlockView } from '@plone/volto-slate/blocks/Text';
77

88
const View = ({ data }) => {
9-
const checkHasContent = (content) => {
10-
if (content) {
11-
let blocks = content.blocks.filter((block) => block?.text !== '');
12-
return blocks.length > 0 ? true : false;
13-
}
14-
};
15-
169
const content = (
1710
<>
18-
{(data.title || checkHasContent(data.description)) && (
11+
{(data.title || data.description) && (
1912
<div className="block-content-header">
2013
{data.title && (
2114
<div
@@ -26,7 +19,7 @@ const View = ({ data }) => {
2619
{data.title}
2720
</div>
2821
)}
29-
{checkHasContent(data.description) && (
22+
{data.description && (
3023
<div className="description">
3124
<TextBlockView data={{ value: data.description }} />
3225
</div>

frontend/src/components/Blocks/Slider/View.jsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import React from 'react';
88
import cx from 'classnames';
99
import PropTypes from 'prop-types';
1010
import Body from './Body';
11-
import config from '@plone/volto/registry';
1211
import { TextBlockView } from '@plone/volto-slate/blocks/Text';
1312

1413
/**
@@ -17,20 +16,14 @@ import { TextBlockView } from '@plone/volto-slate/blocks/Text';
1716
* @extends Component
1817
*/
1918
const View = ({ data }) => {
20-
const checkHasContent = (content) => {
21-
if (content) {
22-
let blocks = content.blocks.filter((block) => block?.text !== '');
23-
return blocks.length > 0 ? true : false;
24-
}
25-
};
2619
return (
2720
<div className="block slider">
2821
{data.showMainTitleAndDescription && (
2922
<>
30-
{(data.title || checkHasContent(data.description)) && (
23+
{(data.title || data.description) && (
3124
<div className="block-content-header">
3225
{data.title && <div className={cx('title')}>{data.title}</div>}
33-
{checkHasContent(data.description) && (
26+
{data.description && (
3427
<div className="description">
3528
<TextBlockView data={{ value: data.description }} />
3629
</div>

frontend/src/components/Blocks/Text6/View.jsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ const View = (props) => {
1919
const { data } = props;
2020
let href = data.link_to?.[0]?.['@id'];
2121

22-
const checkHasContent = (content) => {
23-
if (content) {
24-
let blocks = content.blocks.filter((block) => block?.text !== '');
25-
return blocks.length > 0 ? true : false;
26-
}
27-
};
2822
return (
2923
<PresetWrapper
3024
{...props.data}
@@ -53,7 +47,7 @@ const View = (props) => {
5347
</div>
5448
)}
5549

56-
{checkHasContent(data.content) && (
50+
{data.content && (
5751
<div className="content">
5852
<TextBlockView data={{ value: data.content }} />
5953
</div>

0 commit comments

Comments
 (0)