wagtail-write-api 0.2.2__tar.gz → 0.2.3__tar.gz

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.
Files changed (76) hide show
  1. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/PKG-INFO +1 -1
  2. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/api/pages.md +2 -2
  3. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/guides/rich-text.md +6 -0
  4. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/pyproject.toml +1 -1
  5. wagtail_write_api-0.2.3/src/wagtail_write_api/__init__.py +1 -0
  6. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/converters/rich_text.py +24 -0
  7. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_rich_text.py +48 -0
  8. wagtail_write_api-0.2.2/src/wagtail_write_api/__init__.py +0 -1
  9. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/.github/workflows/docs.yml +0 -0
  10. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/.github/workflows/publish.yml +0 -0
  11. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/.gitignore +0 -0
  12. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/CLAUDE.md +0 -0
  13. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/LICENSE +0 -0
  14. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/Makefile +0 -0
  15. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/README.md +0 -0
  16. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/api/authentication.md +0 -0
  17. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/api/images.md +0 -0
  18. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/api/schema-discovery.md +0 -0
  19. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/development/contributing.md +0 -0
  20. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/development/example-app.md +0 -0
  21. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/getting-started/configuration.md +0 -0
  22. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/getting-started/installation.md +0 -0
  23. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/getting-started/quickstart.md +0 -0
  24. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/guides/permissions.md +0 -0
  25. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/guides/streamfield.md +0 -0
  26. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/guides/workflow.md +0 -0
  27. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/docs/index.md +0 -0
  28. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/Makefile +0 -0
  29. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/example_project/__init__.py +0 -0
  30. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/example_project/settings.py +0 -0
  31. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/example_project/urls.py +0 -0
  32. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/example_project/wsgi.py +0 -0
  33. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/manage.py +0 -0
  34. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/__init__.py +0 -0
  35. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/management/__init__.py +0 -0
  36. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/management/commands/__init__.py +0 -0
  37. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/management/commands/seed_demo.py +0 -0
  38. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/migrations/0001_initial.py +0 -0
  39. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/migrations/__init__.py +0 -0
  40. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/models.py +0 -0
  41. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/example/testapp/wagtail_hooks.py +0 -0
  42. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/api.py +0 -0
  43. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/apps.py +0 -0
  44. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/auth.py +0 -0
  45. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/converters/__init__.py +0 -0
  46. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/endpoints/__init__.py +0 -0
  47. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/endpoints/images.py +0 -0
  48. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/endpoints/pages.py +0 -0
  49. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/endpoints/schema_discovery.py +0 -0
  50. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/management/__init__.py +0 -0
  51. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/management/commands/__init__.py +0 -0
  52. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/management/commands/create_api_token.py +0 -0
  53. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/migrations/0001_initial.py +0 -0
  54. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/migrations/__init__.py +0 -0
  55. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/models.py +0 -0
  56. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/permissions.py +0 -0
  57. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/schema/__init__.py +0 -0
  58. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/schema/fields.py +0 -0
  59. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/schema/generator.py +0 -0
  60. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/schema/registry.py +0 -0
  61. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/settings.py +0 -0
  62. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/urls.py +0 -0
  63. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/src/wagtail_write_api/utils.py +0 -0
  64. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/__init__.py +0 -0
  65. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/conftest.py +0 -0
  66. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_auth.py +0 -0
  67. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_images_api.py +0 -0
  68. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_pages_read.py +0 -0
  69. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_pages_workflow.py +0 -0
  70. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_pages_write.py +0 -0
  71. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_schema_generation.py +0 -0
  72. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_smoke.py +0 -0
  73. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/tests/test_streamfield.py +0 -0
  74. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/uv.lock +0 -0
  75. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/wagtail-write-api-plan.md +0 -0
  76. {wagtail_write_api-0.2.2 → wagtail_write_api-0.2.3}/zensical.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wagtail-write-api
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A read/write REST API for Wagtail CMS
5
5
  Project-URL: Homepage, https://tomdyson.github.io/wagtail-write-api/
6
6
  Project-URL: Repository, https://github.com/tomdyson/wagtail-write-api
@@ -34,8 +34,8 @@ GET /pages/
34
34
  | Parameter | Type | Description |
35
35
  |-----------|------|-------------|
36
36
  | `type` | string | Filter by page type, e.g. `blog.BlogPage` |
37
- | `parent` | int | Direct children of this page ID |
38
- | `descendant_of` | int | All descendants of this page ID |
37
+ | `parent` | int or string | Direct children of this page ID or URL path (e.g. `5` or `/blog/`) |
38
+ | `descendant_of` | int or string | All descendants of this page ID or URL path |
39
39
  | `status` | string | `draft`, `live`, or `live+draft` |
40
40
  | `slug` | string | Exact slug match (may return multiple if slug exists under different parents) |
41
41
  | `path` | string | Exact URL path match, e.g. `/blog/my-post/` (always 0 or 1 result) |
@@ -92,6 +92,12 @@ WAGTAIL_WRITE_API = {
92
92
  !!! tip "For CMS editors"
93
93
  If your client needs to read content, edit it, and write it back, use `"wagtail"` format. This preserves the internal link references (page IDs) through the round trip. With `"html"` format, internal links are expanded to URL paths, which can't be losslessly converted back.
94
94
 
95
+ ## StreamField blocks sent to a RichTextField
96
+
97
+ If a client accidentally sends StreamField-style blocks (a list of `{"type": ..., "value": ...}` dicts) to a `RichTextField`, the API extracts the HTML content from the blocks rather than storing a stringified list. Paragraph and text block values are concatenated, and heading blocks are converted to HTML heading tags.
98
+
99
+ This is a convenience fallback — for best results, send one of the documented input formats above.
100
+
95
101
  ## Rich text in StreamField
96
102
 
97
103
  `RichTextBlock` values in StreamField accept the same input format:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wagtail-write-api"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "A read/write REST API for Wagtail CMS"
9
9
  readme = "README.md"
10
10
  license = "BSD-3-Clause"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.3"
@@ -14,6 +14,9 @@ def convert_rich_text_input(value) -> str:
14
14
  if isinstance(value, str):
15
15
  return value
16
16
 
17
+ if isinstance(value, list):
18
+ return _blocks_to_html(value)
19
+
17
20
  if not isinstance(value, dict):
18
21
  return str(value)
19
22
 
@@ -54,3 +57,24 @@ def markdown_to_wagtail(md_text: str) -> str:
54
57
  # The wagtail links are already in the output as <a linktype="..." id="...">
55
58
  # because we replaced them before markdown processing
56
59
  return html
60
+
61
+
62
+ def _blocks_to_html(blocks: list) -> str:
63
+ """Extract HTML from a list of StreamField-style blocks.
64
+
65
+ Handles the case where a client sends block data to a RichTextField —
66
+ we pull out the text content rather than storing a stringified list.
67
+ """
68
+ parts = []
69
+ for block in blocks:
70
+ if not isinstance(block, dict):
71
+ continue
72
+ value = block.get("value", "")
73
+ block_type = block.get("type", "")
74
+ if block_type == "heading" and isinstance(value, dict):
75
+ size = value.get("size", "h2")
76
+ text = value.get("text", "")
77
+ parts.append(f"<{size}>{text}</{size}>")
78
+ elif isinstance(value, str):
79
+ parts.append(value)
80
+ return "".join(parts)
@@ -87,6 +87,54 @@ class TestRichTextInput:
87
87
  page = SimplePage.objects.get(title="Plain String")
88
88
  assert page.body == "<p>Just plain HTML</p>"
89
89
 
90
+ def test_streamfield_blocks_coerced_to_html(self, api_client, auth_header, home_page):
91
+ """StreamField-style blocks sent to a RichTextField are coerced to HTML."""
92
+ response = api_client.post(
93
+ "/api/write/v1/pages/",
94
+ data=json.dumps(
95
+ {
96
+ "type": "testapp.SimplePage",
97
+ "parent": home_page.id,
98
+ "title": "Blocks Coerced",
99
+ "body": [
100
+ {"type": "paragraph", "value": "<p>Hello</p>", "id": "a"},
101
+ {"type": "paragraph", "value": "<p>World</p>", "id": "b"},
102
+ ],
103
+ }
104
+ ),
105
+ content_type="application/json",
106
+ **auth_header,
107
+ )
108
+ assert response.status_code == 201
109
+ page = SimplePage.objects.get(title="Blocks Coerced")
110
+ assert isinstance(page.body, str)
111
+ assert "<p>Hello</p>" in page.body
112
+ assert "<p>World</p>" in page.body
113
+ assert "[{" not in page.body # not stringified Python
114
+
115
+ def test_streamfield_heading_blocks_coerced(self, api_client, auth_header, home_page):
116
+ """Heading blocks are converted to HTML heading tags."""
117
+ response = api_client.post(
118
+ "/api/write/v1/pages/",
119
+ data=json.dumps(
120
+ {
121
+ "type": "testapp.SimplePage",
122
+ "parent": home_page.id,
123
+ "title": "Heading Coerced",
124
+ "body": [
125
+ {"type": "heading", "value": {"text": "Title", "size": "h2"}, "id": "a"},
126
+ {"type": "paragraph", "value": "<p>Body text</p>", "id": "b"},
127
+ ],
128
+ }
129
+ ),
130
+ content_type="application/json",
131
+ **auth_header,
132
+ )
133
+ assert response.status_code == 201
134
+ page = SimplePage.objects.get(title="Heading Coerced")
135
+ assert "<h2>Title</h2>" in page.body
136
+ assert "<p>Body text</p>" in page.body
137
+
90
138
  def test_markdown_wagtail_page_link(self, api_client, auth_header, home_page):
91
139
  response = api_client.post(
92
140
  "/api/write/v1/pages/",
@@ -1 +0,0 @@
1
- __version__ = "0.2.2"