wagtail 5.2.7__py3-none-any.whl → 5.2.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wagtail/__init__.py +1 -1
- wagtail/blocks/migrations/migrate_operation.py +2 -0
- wagtail/blocks/migrations/operations.py +10 -10
- wagtail/blocks/stream_block.py +1 -1
- wagtail/contrib/forms/tests/test_models.py +7 -5
- wagtail/search/backends/database/mysql/mysql.py +6 -2
- wagtail/search/tests/test_mysql_backend.py +75 -0
- wagtail/test/snippets/apps.py +10 -0
- wagtail/test/streamfield_migrations/testutils.py +4 -4
- wagtail/tests/streamfield_migrations/test_migration_names.py +6 -6
- wagtail/tests/streamfield_migrations/test_migrations.py +99 -13
- wagtail/tests/test_streamfield.py +18 -0
- wagtail/users/permission_order.py +16 -2
- wagtail/users/templatetags/wagtailusers_tags.py +3 -2
- {wagtail-5.2.7.dist-info → wagtail-5.2.8.dist-info}/METADATA +1 -1
- {wagtail-5.2.7.dist-info → wagtail-5.2.8.dist-info}/RECORD +20 -20
- {wagtail-5.2.7.dist-info → wagtail-5.2.8.dist-info}/LICENSE +0 -0
- {wagtail-5.2.7.dist-info → wagtail-5.2.8.dist-info}/WHEEL +0 -0
- {wagtail-5.2.7.dist-info → wagtail-5.2.8.dist-info}/entry_points.txt +0 -0
- {wagtail-5.2.7.dist-info → wagtail-5.2.8.dist-info}/top_level.txt +0 -0
wagtail/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ from wagtail.utils.version import get_semver_version, get_version
|
|
|
6
6
|
|
|
7
7
|
# major.minor.patch.release.number
|
|
8
8
|
# release must be one of alpha, beta, rc, or final
|
|
9
|
-
VERSION = (5, 2,
|
|
9
|
+
VERSION = (5, 2, 8, "final", 1)
|
|
10
10
|
|
|
11
11
|
__version__ = get_version(VERSION)
|
|
12
12
|
|
|
@@ -110,6 +110,8 @@ class MigrateStreamData(RunPython):
|
|
|
110
110
|
|
|
111
111
|
updated_model_instances_buffer = []
|
|
112
112
|
for instance in model_queryset.iterator(chunk_size=self.chunk_size):
|
|
113
|
+
if instance.raw_content is None:
|
|
114
|
+
continue
|
|
113
115
|
|
|
114
116
|
revision_query_maker.append_instance_data_for_revision_query(instance)
|
|
115
117
|
|
|
@@ -153,29 +153,29 @@ class StreamChildrenToListBlockOperation(BaseBlockOperation):
|
|
|
153
153
|
super().__init__()
|
|
154
154
|
self.block_name = block_name
|
|
155
155
|
self.list_block_name = list_block_name
|
|
156
|
-
self.temp_blocks = []
|
|
157
156
|
|
|
158
157
|
def apply(self, block_value):
|
|
158
|
+
candidate_blocks = []
|
|
159
159
|
mapped_block_value = []
|
|
160
160
|
for child_block in block_value:
|
|
161
161
|
if child_block["type"] == self.block_name:
|
|
162
|
-
|
|
162
|
+
candidate_blocks.append(child_block)
|
|
163
163
|
else:
|
|
164
164
|
mapped_block_value.append(child_block)
|
|
165
165
|
|
|
166
|
-
self.map_temp_blocks_to_list_items()
|
|
166
|
+
list_items = self.map_temp_blocks_to_list_items(candidate_blocks)
|
|
167
167
|
|
|
168
|
-
if
|
|
169
|
-
new_list_block = {"type": self.list_block_name, "value":
|
|
168
|
+
if list_items:
|
|
169
|
+
new_list_block = {"type": self.list_block_name, "value": list_items}
|
|
170
170
|
mapped_block_value.append(new_list_block)
|
|
171
171
|
|
|
172
172
|
return mapped_block_value
|
|
173
173
|
|
|
174
|
-
def map_temp_blocks_to_list_items(self):
|
|
175
|
-
|
|
176
|
-
for block in
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
def map_temp_blocks_to_list_items(self, blocks):
|
|
175
|
+
list_items = []
|
|
176
|
+
for block in blocks:
|
|
177
|
+
list_items.append({**block, "type": "item"})
|
|
178
|
+
return list_items
|
|
179
179
|
|
|
180
180
|
@property
|
|
181
181
|
def operation_name_fragment(self):
|
wagtail/blocks/stream_block.py
CHANGED
|
@@ -653,7 +653,7 @@ class StreamValue(MutableSequence):
|
|
|
653
653
|
raw_values = OrderedDict(
|
|
654
654
|
(i, raw_item["value"])
|
|
655
655
|
for i, raw_item in enumerate(self._raw_data)
|
|
656
|
-
if
|
|
656
|
+
if self._bound_blocks[i] is None and raw_item["type"] == type_name
|
|
657
657
|
)
|
|
658
658
|
# pass the raw block values to bulk_to_python as a list
|
|
659
659
|
converted_values = child_block.bulk_to_python(raw_values.values())
|
|
@@ -611,11 +611,13 @@ class TestFormPageWithCustomFormBuilder(WagtailTestUtils, TestCase):
|
|
|
611
611
|
html=True,
|
|
612
612
|
)
|
|
613
613
|
# check ip address field has rendered
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
)
|
|
614
|
+
# (not comparing HTML directly because https://docs.djangoproject.com/en/5.1/releases/5.1.5/
|
|
615
|
+
# added a maxlength attribute)
|
|
616
|
+
soup = self.get_soup(response.content)
|
|
617
|
+
input = soup.find("input", {"name": "device_ip_address"})
|
|
618
|
+
self.assertEqual(input["type"], "text")
|
|
619
|
+
self.assertEqual(input["required"], "")
|
|
620
|
+
self.assertEqual(input["id"], "id_device_ip_address")
|
|
619
621
|
|
|
620
622
|
def test_post_invalid_form(self):
|
|
621
623
|
response = self.client.post(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import warnings
|
|
2
3
|
from collections import OrderedDict
|
|
3
4
|
|
|
@@ -336,9 +337,12 @@ class MySQLSearchQueryCompiler(BaseSearchQueryCompiler):
|
|
|
336
337
|
|
|
337
338
|
def build_search_query_content(self, query, invert=False):
|
|
338
339
|
if isinstance(query, PlainText):
|
|
339
|
-
|
|
340
|
+
# For Boolean full text search queries in MySQL,
|
|
341
|
+
# non-alphanumeric characters act as separators
|
|
342
|
+
terms = [term for term in re.split(r"\W+", query.query_string) if term]
|
|
343
|
+
|
|
340
344
|
if not terms:
|
|
341
|
-
return
|
|
345
|
+
return SearchQuery("")
|
|
342
346
|
|
|
343
347
|
last_term = terms.pop()
|
|
344
348
|
|
|
@@ -68,6 +68,81 @@ class TestMySQLSearchBackend(BackendTests, TransactionTestCase):
|
|
|
68
68
|
all_other_titles | {"JavaScript: The Definitive Guide"},
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
+
def test_empty_search(self):
|
|
72
|
+
results = self.backend.search("", models.Book.objects.all())
|
|
73
|
+
self.assertSetEqual(
|
|
74
|
+
{r.title for r in results},
|
|
75
|
+
set(),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
results = self.backend.search(" ", models.Book.objects.all())
|
|
79
|
+
self.assertSetEqual(
|
|
80
|
+
{r.title for r in results},
|
|
81
|
+
set(),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
results = self.backend.search("*", models.Book.objects.all())
|
|
85
|
+
self.assertSetEqual(
|
|
86
|
+
{r.title for r in results},
|
|
87
|
+
set(),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def test_empty_autocomplete(self):
|
|
91
|
+
results = self.backend.autocomplete("", models.Book.objects.all())
|
|
92
|
+
self.assertSetEqual(
|
|
93
|
+
{r.title for r in results},
|
|
94
|
+
set(),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
results = self.backend.autocomplete(" ", models.Book.objects.all())
|
|
98
|
+
self.assertSetEqual(
|
|
99
|
+
{r.title for r in results},
|
|
100
|
+
set(),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
results = self.backend.autocomplete("*", models.Book.objects.all())
|
|
104
|
+
self.assertSetEqual(
|
|
105
|
+
{r.title for r in results},
|
|
106
|
+
set(),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def test_symbols_in_search_term(self):
|
|
110
|
+
# symbols as their own tokens should be ignored
|
|
111
|
+
results = self.backend.search("javascript @ parts", models.Book.objects.all())
|
|
112
|
+
self.assertSetEqual(
|
|
113
|
+
{r.title for r in results},
|
|
114
|
+
{"JavaScript: The good parts"},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
results = self.backend.search("javascript parts @", models.Book.objects.all())
|
|
118
|
+
self.assertSetEqual(
|
|
119
|
+
{r.title for r in results},
|
|
120
|
+
{"JavaScript: The good parts"},
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
results = self.backend.search("@ javascript parts", models.Book.objects.all())
|
|
124
|
+
self.assertSetEqual(
|
|
125
|
+
{r.title for r in results},
|
|
126
|
+
{"JavaScript: The good parts"},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# tokens containing both symbols and alphanumerics should not be discarded
|
|
130
|
+
# or treated as equivalent to the same token without symbols
|
|
131
|
+
results = self.backend.search("java@script parts", models.Book.objects.all())
|
|
132
|
+
self.assertSetEqual(
|
|
133
|
+
{r.title for r in results},
|
|
134
|
+
set(),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def test_autocomplete_with_symbols(self):
|
|
138
|
+
# the * is not part of the autocomplete mechanism, but if someone includes it
|
|
139
|
+
# we want it to be gracefully ignored
|
|
140
|
+
results = self.backend.autocomplete("parts javasc*", models.Book.objects.all())
|
|
141
|
+
self.assertSetEqual(
|
|
142
|
+
{r.title for r in results},
|
|
143
|
+
{"JavaScript: The good parts"},
|
|
144
|
+
)
|
|
145
|
+
|
|
71
146
|
@skip(
|
|
72
147
|
"The MySQL backend doesn't support choosing individual fields for the search, only (body, title) or (autocomplete) fields may be searched."
|
|
73
148
|
)
|
wagtail/test/snippets/apps.py
CHANGED
|
@@ -7,3 +7,13 @@ class WagtailSnippetsTestsAppConfig(AppConfig):
|
|
|
7
7
|
name = "wagtail.test.snippets"
|
|
8
8
|
label = "snippetstests"
|
|
9
9
|
verbose_name = _("Wagtail snippets tests")
|
|
10
|
+
|
|
11
|
+
def ready(self):
|
|
12
|
+
# Test registration of permission order within the group permissions view,
|
|
13
|
+
# as per https://docs.wagtail.org/en/stable/extending/customizing_group_views.html#customizing-the-group-editor-permissions-ordering
|
|
14
|
+
# Invoking `register` from `ready` confirms that it does not perform any database queries -
|
|
15
|
+
# if it did, it would fail (on a standard test run without --keepdb at least) because the
|
|
16
|
+
# test database hasn't been migrated yet.
|
|
17
|
+
from wagtail.users.permission_order import register
|
|
18
|
+
|
|
19
|
+
register("snippetstests.fancysnippet", order=999)
|
|
@@ -10,7 +10,7 @@ class MigrationTestMixin:
|
|
|
10
10
|
default_operation_and_block_path = []
|
|
11
11
|
app_name = None
|
|
12
12
|
|
|
13
|
-
def init_migration(self, revisions_from=None,
|
|
13
|
+
def init_migration(self, revisions_from=None, operations_and_block_paths=None):
|
|
14
14
|
migration = Migration(
|
|
15
15
|
"test_migration", "wagtail_streamfield_migration_toolkit_test"
|
|
16
16
|
)
|
|
@@ -18,7 +18,7 @@ class MigrationTestMixin:
|
|
|
18
18
|
app_name=self.app_name,
|
|
19
19
|
model_name=self.model.__name__,
|
|
20
20
|
field_name="content",
|
|
21
|
-
operations_and_block_paths=
|
|
21
|
+
operations_and_block_paths=operations_and_block_paths
|
|
22
22
|
or self.default_operation_and_block_path,
|
|
23
23
|
revisions_from=revisions_from,
|
|
24
24
|
)
|
|
@@ -29,11 +29,11 @@ class MigrationTestMixin:
|
|
|
29
29
|
def apply_migration(
|
|
30
30
|
self,
|
|
31
31
|
revisions_from=None,
|
|
32
|
-
|
|
32
|
+
operations_and_block_paths=None,
|
|
33
33
|
):
|
|
34
34
|
migration = self.init_migration(
|
|
35
35
|
revisions_from=revisions_from,
|
|
36
|
-
|
|
36
|
+
operations_and_block_paths=operations_and_block_paths,
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
loader = MigrationLoader(connection=connection)
|
|
@@ -13,35 +13,35 @@ class MigrationNameTest(TestCase, MigrationTestMixin):
|
|
|
13
13
|
app_name = "wagtail_streamfield_migration_toolkit_test"
|
|
14
14
|
|
|
15
15
|
def test_rename(self):
|
|
16
|
-
|
|
16
|
+
operations_and_block_paths = [
|
|
17
17
|
(
|
|
18
18
|
RenameStreamChildrenOperation(old_name="char1", new_name="renamed1"),
|
|
19
19
|
"",
|
|
20
20
|
)
|
|
21
21
|
]
|
|
22
22
|
migration = self.init_migration(
|
|
23
|
-
|
|
23
|
+
operations_and_block_paths=operations_and_block_paths
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
suggested_name = migration.suggest_name()
|
|
27
27
|
self.assertEqual(suggested_name, "rename_char1_to_renamed1")
|
|
28
28
|
|
|
29
29
|
def test_remove(self):
|
|
30
|
-
|
|
30
|
+
operations_and_block_paths = [
|
|
31
31
|
(
|
|
32
32
|
RemoveStreamChildrenOperation(name="char1"),
|
|
33
33
|
"",
|
|
34
34
|
)
|
|
35
35
|
]
|
|
36
36
|
migration = self.init_migration(
|
|
37
|
-
|
|
37
|
+
operations_and_block_paths=operations_and_block_paths
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
suggested_name = migration.suggest_name()
|
|
41
41
|
self.assertEqual(suggested_name, "remove_char1")
|
|
42
42
|
|
|
43
43
|
def test_multiple(self):
|
|
44
|
-
|
|
44
|
+
operations_and_block_paths = [
|
|
45
45
|
(
|
|
46
46
|
RenameStreamChildrenOperation(old_name="char1", new_name="renamed1"),
|
|
47
47
|
"",
|
|
@@ -52,7 +52,7 @@ class MigrationNameTest(TestCase, MigrationTestMixin):
|
|
|
52
52
|
),
|
|
53
53
|
]
|
|
54
54
|
migration = self.init_migration(
|
|
55
|
-
|
|
55
|
+
operations_and_block_paths=operations_and_block_paths
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
suggested_name = migration.suggest_name()
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import json
|
|
3
3
|
|
|
4
|
-
from django.db
|
|
4
|
+
from django.db import connection
|
|
5
|
+
from django.db.models import F, JSONField, TextField
|
|
5
6
|
from django.db.models.functions import Cast
|
|
6
7
|
from django.test import TestCase
|
|
7
8
|
from django.utils import timezone
|
|
8
9
|
|
|
9
|
-
from wagtail.blocks.migrations.operations import
|
|
10
|
+
from wagtail.blocks.migrations.operations import (
|
|
11
|
+
RenameStreamChildrenOperation,
|
|
12
|
+
StreamChildrenToListBlockOperation,
|
|
13
|
+
)
|
|
10
14
|
from wagtail.test.streamfield_migrations import factories, models
|
|
11
15
|
from wagtail.test.streamfield_migrations.testutils import MigrationTestMixin
|
|
12
16
|
|
|
@@ -24,8 +28,8 @@ class BaseMigrationTest(TestCase, MigrationTestMixin):
|
|
|
24
28
|
]
|
|
25
29
|
app_name = None
|
|
26
30
|
|
|
27
|
-
def
|
|
28
|
-
|
|
31
|
+
def _get_test_instances(self):
|
|
32
|
+
return [
|
|
29
33
|
self.factory(
|
|
30
34
|
content__0__char1="Test char 1",
|
|
31
35
|
content__1__char1="Test char 2",
|
|
@@ -44,6 +48,9 @@ class BaseMigrationTest(TestCase, MigrationTestMixin):
|
|
|
44
48
|
),
|
|
45
49
|
]
|
|
46
50
|
|
|
51
|
+
def setUp(self):
|
|
52
|
+
instances = self._get_test_instances()
|
|
53
|
+
|
|
47
54
|
self.original_raw_data = {}
|
|
48
55
|
self.original_revisions = {}
|
|
49
56
|
|
|
@@ -102,9 +109,7 @@ class BaseMigrationTest(TestCase, MigrationTestMixin):
|
|
|
102
109
|
|
|
103
110
|
self.apply_migration()
|
|
104
111
|
|
|
105
|
-
instances = self.model.objects.all()
|
|
106
|
-
raw_content=Cast(F("content"), JSONField())
|
|
107
|
-
)
|
|
112
|
+
instances = self.model.objects.all()
|
|
108
113
|
|
|
109
114
|
for instance in instances:
|
|
110
115
|
old_revisions = self.original_revisions[instance.id]
|
|
@@ -128,9 +133,7 @@ class BaseMigrationTest(TestCase, MigrationTestMixin):
|
|
|
128
133
|
revisions_from = timezone.now() + datetime.timedelta(days=2)
|
|
129
134
|
self.apply_migration(revisions_from=revisions_from)
|
|
130
135
|
|
|
131
|
-
instances = self.model.objects.all()
|
|
132
|
-
raw_content=Cast(F("content"), JSONField())
|
|
133
|
-
)
|
|
136
|
+
instances = self.model.objects.all()
|
|
134
137
|
|
|
135
138
|
for instance in instances:
|
|
136
139
|
old_revisions = self.original_revisions[instance.id]
|
|
@@ -159,9 +162,7 @@ class BaseMigrationTest(TestCase, MigrationTestMixin):
|
|
|
159
162
|
revisions_from = timezone.now() - datetime.timedelta(days=2)
|
|
160
163
|
self.apply_migration(revisions_from=revisions_from)
|
|
161
164
|
|
|
162
|
-
instances = self.model.objects.all()
|
|
163
|
-
raw_content=Cast(F("content"), JSONField())
|
|
164
|
-
)
|
|
165
|
+
instances = self.model.objects.all()
|
|
165
166
|
|
|
166
167
|
for instance in instances:
|
|
167
168
|
old_revisions = self.original_revisions[instance.id]
|
|
@@ -209,3 +210,88 @@ class TestPage(BaseMigrationTest):
|
|
|
209
210
|
|
|
210
211
|
def test_migrate_revisions_from_date(self):
|
|
211
212
|
self._test_migrate_revisions_from_date()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TestNullStreamField(BaseMigrationTest):
|
|
216
|
+
"""
|
|
217
|
+
Migrations are processed if the underlying JSON is null.
|
|
218
|
+
|
|
219
|
+
This might occur if we're operating on a StreamField that was added to a model that
|
|
220
|
+
had existing records.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
model = models.SamplePage
|
|
224
|
+
factory = factories.SamplePageFactory
|
|
225
|
+
has_revisions = True
|
|
226
|
+
app_name = "streamfield_migration_tests"
|
|
227
|
+
|
|
228
|
+
def _get_test_instances(self):
|
|
229
|
+
return self.factory.create_batch(1, content=None)
|
|
230
|
+
|
|
231
|
+
def setUp(self):
|
|
232
|
+
super().setUp()
|
|
233
|
+
|
|
234
|
+
# Bypass StreamField/StreamBlock processing that cast a None stream field value
|
|
235
|
+
# to the empty StreamValue, and set the underlying JSON to null.
|
|
236
|
+
with connection.cursor() as cursor:
|
|
237
|
+
cursor.execute(f"UPDATE {self.model._meta.db_table} SET content = 'null'")
|
|
238
|
+
|
|
239
|
+
def assert_null_content(self):
|
|
240
|
+
"""
|
|
241
|
+
The raw JSON of all instances for this test is null.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
instances = self.model.objects.all().annotate(
|
|
245
|
+
raw_content=Cast(F("content"), TextField())
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
for instance in instances:
|
|
249
|
+
with self.subTest(instance=instance):
|
|
250
|
+
self.assertEqual(instance.raw_content, "null")
|
|
251
|
+
|
|
252
|
+
def test_migrate_stream_data(self):
|
|
253
|
+
self.assert_null_content()
|
|
254
|
+
self.apply_migration()
|
|
255
|
+
self.assert_null_content()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class StreamChildrenToListBlockOperationTestCase(BaseMigrationTest):
|
|
259
|
+
model = models.SamplePage
|
|
260
|
+
factory = factories.SamplePageFactory
|
|
261
|
+
has_revisions = True
|
|
262
|
+
app_name = "streamfield_migration_tests"
|
|
263
|
+
|
|
264
|
+
def _get_test_instances(self):
|
|
265
|
+
return self.factory.create_batch(
|
|
266
|
+
size=3,
|
|
267
|
+
# Each content stream field has a single char block instance.
|
|
268
|
+
content__0__char1__value="Char Block 1",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def test_state_not_shared_across_instances(self):
|
|
272
|
+
"""
|
|
273
|
+
StreamChildrenToListBlockOperation doesn't share state across model instances.
|
|
274
|
+
|
|
275
|
+
As a single operation instance is used to transform the data of multiple model
|
|
276
|
+
instances, we should not store model instance state on the operation instance.
|
|
277
|
+
See https://github.com/wagtail/wagtail/issues/12391.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
self.apply_migration(
|
|
281
|
+
operations_and_block_paths=[
|
|
282
|
+
(
|
|
283
|
+
StreamChildrenToListBlockOperation(
|
|
284
|
+
block_name="char1", list_block_name="list1"
|
|
285
|
+
),
|
|
286
|
+
"",
|
|
287
|
+
)
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
for instance in self.model.objects.all().annotate(
|
|
291
|
+
raw_content=Cast(F("content"), JSONField())
|
|
292
|
+
):
|
|
293
|
+
new_block = instance.raw_content[0]
|
|
294
|
+
self.assertEqual(new_block["type"], "list1")
|
|
295
|
+
self.assertEqual(len(new_block["value"]), 1)
|
|
296
|
+
self.assertEqual(new_block["value"][0]["type"], "item")
|
|
297
|
+
self.assertEqual(new_block["value"][0]["value"], "Char Block 1")
|
|
@@ -237,6 +237,24 @@ class TestStreamValueAccess(TestCase):
|
|
|
237
237
|
self.assertEqual(fetched_body[1].block_type, "text")
|
|
238
238
|
self.assertEqual(fetched_body[1].value, "bar")
|
|
239
239
|
|
|
240
|
+
def test_can_append_on_queried_instance(self):
|
|
241
|
+
# The test is analog to test_can_append(), but instead of working with the
|
|
242
|
+
# in-memory version from JSONStreamModel.objects.create(), we query a fresh
|
|
243
|
+
# instance from the db.
|
|
244
|
+
# It tests adding data to child blocks that
|
|
245
|
+
# have not yet been lazy loaded. This would previously crash.
|
|
246
|
+
self.json_body = JSONStreamModel.objects.get(pk=self.json_body.pk)
|
|
247
|
+
self.json_body.body.append(("text", "bar"))
|
|
248
|
+
self.json_body.save()
|
|
249
|
+
|
|
250
|
+
fetched_body = JSONStreamModel.objects.get(id=self.json_body.id).body
|
|
251
|
+
self.assertIsInstance(fetched_body, StreamValue)
|
|
252
|
+
self.assertEqual(len(fetched_body), 2)
|
|
253
|
+
self.assertEqual(fetched_body[0].block_type, "text")
|
|
254
|
+
self.assertEqual(fetched_body[0].value, "foo")
|
|
255
|
+
self.assertEqual(fetched_body[1].block_type, "text")
|
|
256
|
+
self.assertEqual(fetched_body[1].value, "bar")
|
|
257
|
+
|
|
240
258
|
|
|
241
259
|
class TestStreamFieldRenderingBase(TestCase):
|
|
242
260
|
model = JSONStreamModel
|
|
@@ -2,6 +2,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
2
2
|
|
|
3
3
|
from wagtail.coreutils import resolve_model_string
|
|
4
4
|
|
|
5
|
+
content_types_to_register = []
|
|
5
6
|
CONTENT_TYPE_ORDER = {}
|
|
6
7
|
|
|
7
8
|
|
|
@@ -13,5 +14,18 @@ def register(model, **kwargs):
|
|
|
13
14
|
"""
|
|
14
15
|
order = kwargs.pop("order", None)
|
|
15
16
|
if order is not None:
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
# We typically call this at application startup, when the database may not be ready,
|
|
18
|
+
# and so we can't look up the content type yet. Instead we will queue up the
|
|
19
|
+
# (model, order) pair to be processed when the lookup is requested.
|
|
20
|
+
content_types_to_register.append((model, order))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_content_type_order_lookup():
|
|
24
|
+
if content_types_to_register:
|
|
25
|
+
for model, order in content_types_to_register:
|
|
26
|
+
content_type = ContentType.objects.get_for_model(
|
|
27
|
+
resolve_model_string(model)
|
|
28
|
+
)
|
|
29
|
+
CONTENT_TYPE_ORDER[content_type.id] = order
|
|
30
|
+
content_types_to_register.clear()
|
|
31
|
+
return CONTENT_TYPE_ORDER
|
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
from django import template
|
|
5
5
|
|
|
6
6
|
from wagtail import hooks
|
|
7
|
-
from wagtail.users.permission_order import
|
|
7
|
+
from wagtail.users.permission_order import get_content_type_order_lookup
|
|
8
8
|
|
|
9
9
|
register = template.Library()
|
|
10
10
|
|
|
@@ -42,9 +42,10 @@ def format_permissions(permission_bound_field):
|
|
|
42
42
|
# get a distinct and ordered list of the content types that these permissions relate to.
|
|
43
43
|
# relies on Permission model default ordering, dict.fromkeys() retaining that order
|
|
44
44
|
# from the queryset, and the stability of sorted().
|
|
45
|
+
content_type_order = get_content_type_order_lookup()
|
|
45
46
|
content_type_ids = sorted(
|
|
46
47
|
dict.fromkeys(permissions.values_list("content_type_id", flat=True)),
|
|
47
|
-
key=lambda ct:
|
|
48
|
+
key=lambda ct: content_type_order.get(ct, float("inf")),
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
# iterate over permission_bound_field to build a lookup of individual renderable
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
wagtail/__init__.py,sha256=
|
|
1
|
+
wagtail/__init__.py,sha256=US8Zk6hZvFVh0vWFBCAhEpA3LrNuRjXc4KaLQ0Z8nLA,724
|
|
2
2
|
wagtail/apps.py,sha256=38kXTdHoQzFnpUqDNxFpsqn2dut4V0u9rLOkhqCoYkc,713
|
|
3
3
|
wagtail/compat.py,sha256=L41FhlX4xy5KgTdJ63smtM78mtKf1mxkPeOs8kyOwS0,538
|
|
4
4
|
wagtail/coreutils.py,sha256=ZlmtoHC5YzXmhdKi13QkCSmBryAmS7k8s5jgQjahpEQ,20660
|
|
@@ -1018,11 +1018,11 @@ wagtail/blocks/base.py,sha256=10jhNwVVfDh8TCpCEbZTD1Kqywexn2Y5jHhpls5SRbA,26168
|
|
|
1018
1018
|
wagtail/blocks/field_block.py,sha256=rYs7G2bHLzf4I7I47k0q_wbE7ExVCQ8WeD0eIEZpiOA,31376
|
|
1019
1019
|
wagtail/blocks/list_block.py,sha256=WxZlGR7cgA_KvX7Z2222X5oADVDC5jKNjhM9my3UWY0,16375
|
|
1020
1020
|
wagtail/blocks/static_block.py,sha256=e7f-zVewRCvdnmvsVtUn1fpoEbHfDIIA6G-yaXeD-vo,1627
|
|
1021
|
-
wagtail/blocks/stream_block.py,sha256=
|
|
1021
|
+
wagtail/blocks/stream_block.py,sha256=woVuZXnEV-DklTgify0VskD7RkDmRd9VbDVZhwvkBas,30161
|
|
1022
1022
|
wagtail/blocks/struct_block.py,sha256=Qu7Zo5Qk2VCw3zEyuWukWSVT_CSUnz6k3kLbe7ezGOQ,15122
|
|
1023
1023
|
wagtail/blocks/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1024
|
-
wagtail/blocks/migrations/migrate_operation.py,sha256=
|
|
1025
|
-
wagtail/blocks/migrations/operations.py,sha256=
|
|
1024
|
+
wagtail/blocks/migrations/migrate_operation.py,sha256=2yCTGHiQHoaaQEHpqpnHZ4ouPvstUgMlm8iixancOdg,14288
|
|
1025
|
+
wagtail/blocks/migrations/operations.py,sha256=rUmr3FNazwgDt7c5xEu9YvHLOLgstS5tedxLHmk3sNo,10582
|
|
1026
1026
|
wagtail/blocks/migrations/utils.py,sha256=dk09vGBYRb-3sGPkFeWR5rxrboVgnlAlk6OGE4tqo2I,9544
|
|
1027
1027
|
wagtail/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1028
1028
|
wagtail/contrib/forms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -1161,7 +1161,7 @@ wagtail/contrib/forms/templates/wagtailforms/submissions_index.html,sha256=4rISA
|
|
|
1161
1161
|
wagtail/contrib/forms/templates/wagtailforms/panels/form_responses_panel.html,sha256=iqKD050fLAHG2CZe-cK6ZsSlsHNirABtGtkfGvisc_4,331
|
|
1162
1162
|
wagtail/contrib/forms/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1163
1163
|
wagtail/contrib/forms/tests/test_forms.py,sha256=7tRUwJA2j9xzr28NpRC9hDYeOEfR1VTmBCPlny7UPVw,13634
|
|
1164
|
-
wagtail/contrib/forms/tests/test_models.py,sha256=
|
|
1164
|
+
wagtail/contrib/forms/tests/test_models.py,sha256=7iecOyH2PyNcu0yUsmMJ-Flm1FktXjtHGR7BMkra2OA,31958
|
|
1165
1165
|
wagtail/contrib/forms/tests/test_views.py,sha256=rXirGlom9s9veeodsYEU3mcBTCsRryT3D-pjErI05nM,73044
|
|
1166
1166
|
wagtail/contrib/forms/tests/utils.py,sha256=OESefxdqGRgL1lDItVPSFNw_FJNB4X0PvozdvAhrpkc,6043
|
|
1167
1167
|
wagtail/contrib/frontend_cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -3184,7 +3184,7 @@ wagtail/search/backends/elasticsearch8.py,sha256=n3y05d_atzbnkaM4F1BXp3OhayE-J6u
|
|
|
3184
3184
|
wagtail/search/backends/database/__init__.py,sha256=u8dKGRONweefcHoIlCIJgxpU5DgWAARDz1sefSUq-Rw,1727
|
|
3185
3185
|
wagtail/search/backends/database/fallback.py,sha256=fBa63S05v_deEqwPMTOU3qRjJ7U7Db6D0_OIZuwwrIs,7774
|
|
3186
3186
|
wagtail/search/backends/database/mysql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3187
|
-
wagtail/search/backends/database/mysql/mysql.py,sha256=
|
|
3187
|
+
wagtail/search/backends/database/mysql/mysql.py,sha256=WwSrOSeUL6iZK6FfsL_6hRl_9GK6jOelzXcODBomktk,23505
|
|
3188
3188
|
wagtail/search/backends/database/mysql/query.py,sha256=MnLF8OnH281YT48IwlHqq4WdUnDBEfYN2lU9AbJ-jAM,9684
|
|
3189
3189
|
wagtail/search/backends/database/postgres/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3190
3190
|
wagtail/search/backends/database/postgres/postgres.py,sha256=PJff9wSNF4HPWqfs5vtpYHh_K56Jh1BaLe7wSxQVoGY,26403
|
|
@@ -3316,7 +3316,7 @@ wagtail/search/tests/test_elasticsearch7_backend.py,sha256=NxjMGh1vUjgmaLn6MqULX
|
|
|
3316
3316
|
wagtail/search/tests/test_elasticsearch8_backend.py,sha256=3EKGnVO8ldl8em5rE0xG4b_BXVFvh7QGk719FSXGbzE,482
|
|
3317
3317
|
wagtail/search/tests/test_index_functions.py,sha256=jbkpVqO-jsbNLGl9E8vXvxebMa60Ea82qPWacPcv1Y0,7681
|
|
3318
3318
|
wagtail/search/tests/test_indexed_class.py,sha256=WU_6y6iTxI02HX0xvoXfA21aml9W8i53HffepSgQGVY,5674
|
|
3319
|
-
wagtail/search/tests/test_mysql_backend.py,sha256=
|
|
3319
|
+
wagtail/search/tests/test_mysql_backend.py,sha256=WmID5KO2E7EQzjQUEARnyN5eDlt3-64QNqASUZHO0d4,6310
|
|
3320
3320
|
wagtail/search/tests/test_page_search.py,sha256=1oPBcgyJQpJmAQaNAhDAWXdVe2xrZ6kf7clefPZ0JLY,2363
|
|
3321
3321
|
wagtail/search/tests/test_postgres_backend.py,sha256=QAR78RKMAUCH9beNu3r1Qxuhb865K8NU1C9sWC8lVco,7406
|
|
3322
3322
|
wagtail/search/tests/test_postgres_stemming.py,sha256=zUqIANnIu4FD8SQtDd0xtCKH6x5voe9mdgA7nK0g3Wk,1346
|
|
@@ -3696,7 +3696,7 @@ wagtail/test/search/migrations/0001_initial.py,sha256=_xWfml2RnP3RBGHWLnzZ8GrQ0Y
|
|
|
3696
3696
|
wagtail/test/search/migrations/0002_bookunindexed.py,sha256=vC2kHmuGZinDB7rWe2NN1nqw5o09clHjUZb73CbqwJ4,1311
|
|
3697
3697
|
wagtail/test/search/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3698
3698
|
wagtail/test/snippets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3699
|
-
wagtail/test/snippets/apps.py,sha256=
|
|
3699
|
+
wagtail/test/snippets/apps.py,sha256=Oh5pcAGUgm8fKABmXLK8PrHieRvsuREXVJHQqWAsRMQ,916
|
|
3700
3700
|
wagtail/test/snippets/forms.py,sha256=cWcsv-6rFsDemGUSjb-yRu77e9qrjDL14tMI6V_ZmRI,174
|
|
3701
3701
|
wagtail/test/snippets/models.py,sha256=c0ranLPa4iHL_Lm-bO-0xIPxB914Us9eXMpiyHAbtg0,2836
|
|
3702
3702
|
wagtail/test/snippets/migrations/0001_initial.py,sha256=AqtNn9DR9LwSP2t7TrkXWWZdxW7v_sMXY1AxaFJU6kg,2115
|
|
@@ -3714,7 +3714,7 @@ wagtail/test/snippets/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
3714
3714
|
wagtail/test/streamfield_migrations/apps.py,sha256=4oPD49SeDtjk3E4--RSa5CSGhGAMlUTM2bHn7J_RLfw,286
|
|
3715
3715
|
wagtail/test/streamfield_migrations/factories.py,sha256=8S2QrVBZ1-qf41YsStExePOflNa1EhqjvGK5i8Brzrc,2270
|
|
3716
3716
|
wagtail/test/streamfield_migrations/models.py,sha256=d5A6C63xaH3eGbwUbZgNcukbLiZ8JS0Tk1w0Zexy4qs,1270
|
|
3717
|
-
wagtail/test/streamfield_migrations/testutils.py,sha256=
|
|
3717
|
+
wagtail/test/streamfield_migrations/testutils.py,sha256=joVrQFjmgyCi62gcRnCIIbcDe_UYMlD_ChZj3oVW7Zc,1469
|
|
3718
3718
|
wagtail/test/streamfield_migrations/migrations/0001_initial.py,sha256=LDTry71fjBnzIzDvpPZnwHXvuYqJIYPg9OwFTpkk5z4,16308
|
|
3719
3719
|
wagtail/test/streamfield_migrations/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3720
3720
|
wagtail/test/testapp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -3862,7 +3862,7 @@ wagtail/tests/test_revision_model.py,sha256=-AQl_PWLAfP_5qpbAdC2l-CsT03L17qc7lpW
|
|
|
3862
3862
|
wagtail/tests/test_rich_text.py,sha256=1AuzDkNAzl2Sr0zeVhdSMYESLyOt6_bdsIwBMlYmU9k,13391
|
|
3863
3863
|
wagtail/tests/test_signals.py,sha256=A_VSQxD4CJ7_h5c9qGI-K-mQvuuaJYn0qiyFe_kP3OE,3671
|
|
3864
3864
|
wagtail/tests/test_sites.py,sha256=syJCEPMHdKXfbxc_Vb7CH9ymuPVZiWDAae4JmJPmPx0,8488
|
|
3865
|
-
wagtail/tests/test_streamfield.py,sha256=
|
|
3865
|
+
wagtail/tests/test_streamfield.py,sha256=1DWDtUAIfEWCKMZbdSmMbssz-K7qRACx5AL0ic_t2Wc,28014
|
|
3866
3866
|
wagtail/tests/test_telepath.py,sha256=muiOryoRmkISEHVu9GPSrKJEVB_EvpOufhvuJF-HrTk,10374
|
|
3867
3867
|
wagtail/tests/test_tests.py,sha256=Y801jv650IAENPg-1pGTiicrHc9uHijg-Ga2DIDh0yM,17074
|
|
3868
3868
|
wagtail/tests/test_translatablemixin.py,sha256=zcVkhZhQKuEWxY392Ftdec5MUZFW4ctzcbC0efLEayc,7644
|
|
@@ -3875,8 +3875,8 @@ wagtail/tests/tests.py,sha256=Zxb4AmmyhQzj8D_zTpB8kY6jEKRq_tt6_ykFFqnpjMQ,28975
|
|
|
3875
3875
|
wagtail/tests/utils.py,sha256=P3wf4sfFxcHO7qFU5Q1yDmByZ7DC4sAiyMPu4PEBDGI,389
|
|
3876
3876
|
wagtail/tests/streamfield_migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3877
3877
|
wagtail/tests/streamfield_migrations/test_bad_data.py,sha256=fK57skgrOCkFveHbyiZefNWqkwHSczmeFplPChwRoko,9436
|
|
3878
|
-
wagtail/tests/streamfield_migrations/test_migration_names.py,sha256=
|
|
3879
|
-
wagtail/tests/streamfield_migrations/test_migrations.py,sha256=
|
|
3878
|
+
wagtail/tests/streamfield_migrations/test_migration_names.py,sha256=BZQ5UnatWlxnFu8IK2Q-mEVW3cY041bixgtJ9CTuelk,1881
|
|
3879
|
+
wagtail/tests/streamfield_migrations/test_migrations.py,sha256=qunwTE0mquUUN0g_yD8OHWQ6WM_beuW59yIA5lyD56M,10908
|
|
3880
3880
|
wagtail/tests/streamfield_migrations/test_nested_structures.py,sha256=cBD0ToxlYqNH_UfKzX43_tZb4CcKwglmz38S-Hh5sWM,32238
|
|
3881
3881
|
wagtail/tests/streamfield_migrations/test_old_list.py,sha256=L_JMTvDn5qUHV_IhwidtWaw9X-636XD75fpBlWVeTgU,10196
|
|
3882
3882
|
wagtail/tests/streamfield_migrations/test_simple_structures.py,sha256=-_dlN0BssdkUvLmL3sjtcmWMYzqcARJQfyDHzPiONOw,22123
|
|
@@ -3884,7 +3884,7 @@ wagtail/users/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
3884
3884
|
wagtail/users/apps.py,sha256=VZbo2PEZXatFIfR5ruJryrgLcuxelJws3BMrG6b5vcs,339
|
|
3885
3885
|
wagtail/users/forms.py,sha256=NxbLd8wlsrxi1PCQ5hbcM5V53gekzUZGTfa9vQrjZaU,14983
|
|
3886
3886
|
wagtail/users/models.py,sha256=sZhWBk732DjdZWxLyl_mdMJuPpgX5GY8audAxSrPxv4,2993
|
|
3887
|
-
wagtail/users/permission_order.py,sha256=
|
|
3887
|
+
wagtail/users/permission_order.py,sha256=4HtB1k6qu9NPus8ni59PzYFOVvQCjrhLvWdqMqkNuLs,1130
|
|
3888
3888
|
wagtail/users/utils.py,sha256=zBjp_tEKsRQ4qULkDukCmdu2qJZnPb5ivcwWWRwnulk,1631
|
|
3889
3889
|
wagtail/users/wagtail_hooks.py,sha256=owznymtqg4NSrnNYHsHcGHbWIabPWzaMmlJWMMnJBYI,5566
|
|
3890
3890
|
wagtail/users/widgets.py,sha256=bwhne7LDJRziGekBq4p3MrNgJAo4gqKV4idfwgB-fiI,267
|
|
@@ -4038,7 +4038,7 @@ wagtail/users/templates/wagtailusers/users/index.html,sha256=UqcJoWGU9Pfv8tE25GH
|
|
|
4038
4038
|
wagtail/users/templates/wagtailusers/users/list.html,sha256=diXGYC4wWaWF_AOe_1FvPtWH5UOqQxy_O62ZnniOGK8,2974
|
|
4039
4039
|
wagtail/users/templates/wagtailusers/users/results.html,sha256=ZIRsUgss4IFEXMcsEyfw8ZozHSYRnWV5cTrHfcBDzxM,1372
|
|
4040
4040
|
wagtail/users/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4041
|
-
wagtail/users/templatetags/wagtailusers_tags.py,sha256=
|
|
4041
|
+
wagtail/users/templatetags/wagtailusers_tags.py,sha256=7Fe_A55UaP5jMW2zBJeDWjpx9yR3thkxDakx7ZVTpHE,4561
|
|
4042
4042
|
wagtail/users/tests/__init__.py,sha256=-_5MwigQVfaxvNytkEKUk-0sG90N-APaWajAxNmQgX0,192
|
|
4043
4043
|
wagtail/users/tests/test_admin_views.py,sha256=5D-cRee7-MZukWSvPyml3NdF6FyR1_LMOy3OY5aMj2M,92833
|
|
4044
4044
|
wagtail/users/tests/test_bulk_actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -4071,9 +4071,9 @@ wagtail/utils/urlpatterns.py,sha256=RDhVScxdm-RV4HSMjWElyrbEoTPsXu841_SKMgoFKtY,
|
|
|
4071
4071
|
wagtail/utils/utils.py,sha256=UALGn0KOyqi5ZoXOozWX99ZPUsVZP51ET8d7sZ1jlm0,430
|
|
4072
4072
|
wagtail/utils/version.py,sha256=40WGMIy8nSPQJFF01p7c38L444SexH2Cb-bQmR8ztXg,1478
|
|
4073
4073
|
wagtail/utils/widgets.py,sha256=oTpTMcmQ0Y4MugbSKp_Uigl97ftWdHvMfvhrrLyVmBs,1529
|
|
4074
|
-
wagtail-5.2.
|
|
4075
|
-
wagtail-5.2.
|
|
4076
|
-
wagtail-5.2.
|
|
4077
|
-
wagtail-5.2.
|
|
4078
|
-
wagtail-5.2.
|
|
4079
|
-
wagtail-5.2.
|
|
4074
|
+
wagtail-5.2.8.dist-info/LICENSE,sha256=0aiL7_RJ2YkOjscmRI7opwmuURrY6h8MR0B24nrdRQU,1512
|
|
4075
|
+
wagtail-5.2.8.dist-info/METADATA,sha256=_msb-14XJUO-jZKV6qpBOjmb6nMBhNInOQNjQJZbhl0,3850
|
|
4076
|
+
wagtail-5.2.8.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
4077
|
+
wagtail-5.2.8.dist-info/entry_points.txt,sha256=R14Z0xKoufNcDaku0EWDKM-K8J4ap0EImO8C-df8HVM,53
|
|
4078
|
+
wagtail-5.2.8.dist-info/top_level.txt,sha256=zcKgvuRTi0gSgVzJ1qMoERCwhQ_i0n9bkyxza3oh9as,8
|
|
4079
|
+
wagtail-5.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|