wagtail-seeder 1.0.0__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 (32) hide show
  1. wagtail_seeder-1.0.0/LICENSE +28 -0
  2. wagtail_seeder-1.0.0/MANIFEST.in +10 -0
  3. wagtail_seeder-1.0.0/PKG-INFO +334 -0
  4. wagtail_seeder-1.0.0/README.md +309 -0
  5. wagtail_seeder-1.0.0/docs/compatibility.md +166 -0
  6. wagtail_seeder-1.0.0/pyproject.toml +49 -0
  7. wagtail_seeder-1.0.0/setup.cfg +4 -0
  8. wagtail_seeder-1.0.0/src/wagtail_seeder/__init__.py +3 -0
  9. wagtail_seeder-1.0.0/src/wagtail_seeder/apps.py +7 -0
  10. wagtail_seeder-1.0.0/src/wagtail_seeder/config.py +56 -0
  11. wagtail_seeder-1.0.0/src/wagtail_seeder/engine.py +1665 -0
  12. wagtail_seeder-1.0.0/src/wagtail_seeder/guards.py +62 -0
  13. wagtail_seeder-1.0.0/src/wagtail_seeder/management/__init__.py +0 -0
  14. wagtail_seeder-1.0.0/src/wagtail_seeder/management/commands/__init__.py +0 -0
  15. wagtail_seeder-1.0.0/src/wagtail_seeder/management/commands/inspect_wagtail_seed.py +54 -0
  16. wagtail_seeder-1.0.0/src/wagtail_seeder/management/commands/seed_wagtail.py +38 -0
  17. wagtail_seeder-1.0.0/src/wagtail_seeder/management/commands/seed_wagtail_pages.py +35 -0
  18. wagtail_seeder-1.0.0/src/wagtail_seeder/migrations/0001_initial.py +27 -0
  19. wagtail_seeder-1.0.0/src/wagtail_seeder/migrations/__init__.py +0 -0
  20. wagtail_seeder-1.0.0/src/wagtail_seeder/models.py +10 -0
  21. wagtail_seeder-1.0.0/src/wagtail_seeder/providers.py +32 -0
  22. wagtail_seeder-1.0.0/src/wagtail_seeder/report.py +23 -0
  23. wagtail_seeder-1.0.0/src/wagtail_seeder/static/wagtail_seeder/dashboard.css +153 -0
  24. wagtail_seeder-1.0.0/src/wagtail_seeder/static/wagtail_seeder/dashboard.js +183 -0
  25. wagtail_seeder-1.0.0/src/wagtail_seeder/templates/wagtail_seeder/dashboard.html +100 -0
  26. wagtail_seeder-1.0.0/src/wagtail_seeder/values.py +545 -0
  27. wagtail_seeder-1.0.0/src/wagtail_seeder/wagtail_hooks.py +105 -0
  28. wagtail_seeder-1.0.0/src/wagtail_seeder.egg-info/PKG-INFO +334 -0
  29. wagtail_seeder-1.0.0/src/wagtail_seeder.egg-info/SOURCES.txt +30 -0
  30. wagtail_seeder-1.0.0/src/wagtail_seeder.egg-info/dependency_links.txt +1 -0
  31. wagtail_seeder-1.0.0/src/wagtail_seeder.egg-info/requires.txt +11 -0
  32. wagtail_seeder-1.0.0/src/wagtail_seeder.egg-info/top_level.txt +1 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Four Digits
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,10 @@
1
+ recursive-include src/wagtail_seeder/templates *.html
2
+ recursive-include src/wagtail_seeder/static *.css *.js
3
+ include README.md LICENSE
4
+ include docs/compatibility.md
5
+ prune tests
6
+ prune documents
7
+ prune original_images
8
+ prune build
9
+ prune dist
10
+ global-exclude __pycache__ *.py[cod] *.sqlite3
@@ -0,0 +1,334 @@
1
+ Metadata-Version: 2.4
2
+ Name: wagtail-seeder
3
+ Version: 1.0.0
4
+ Summary: Safe development-data seeding for Wagtail projects.
5
+ Author: Four Digits
6
+ License-Expression: BSD-3-Clause
7
+ Classifier: Framework :: Django
8
+ Classifier: Framework :: Wagtail
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: wagtail<8,>=7
15
+ Requires-Dist: Faker>=24
16
+ Requires-Dist: Pillow>=10
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8; extra == "dev"
19
+ Requires-Dist: pytest-django>=4.8; extra == "dev"
20
+ Requires-Dist: psycopg[binary]>=3.1; extra == "dev"
21
+ Requires-Dist: ruff>=0.8; extra == "dev"
22
+ Requires-Dist: build>=1; extra == "dev"
23
+ Requires-Dist: twine>=6.1; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # Wagtail Seeder
27
+
28
+ Development-data seeder for Wagtail 7.x (Python 3.10 and later). It creates representative
29
+ pages, media, snippets and settings by inspecting installed Wagtail models.
30
+
31
+ ## Installation
32
+
33
+ Install from PyPI:
34
+
35
+ ```bash
36
+ python -m pip install wagtail-seeder
37
+ ```
38
+
39
+ Add `"wagtail_seeder"` to `INSTALLED_APPS`. Configure
40
+ an explicit environment; the package fails closed when it is omitted or unsupported:
41
+
42
+ ```python
43
+ WAGTAIL_SEEDER = {
44
+ "ENVIRONMENT": "dev",
45
+ "HOMEPAGE_MODEL": "pages.HomePage",
46
+ "DEFAULT_SITE_PORT": 80, # use 8000 for Django's runserver
47
+ }
48
+ ```
49
+
50
+ Apply the project's migrations, then inspect and seed:
51
+
52
+ ```bash
53
+ python manage.py migrate
54
+ python manage.py inspect_wagtail_seed --format=json --require-complete
55
+ python manage.py seed_wagtail --confirm --require-complete
56
+ python manage.py seed_wagtail_pages --page-type pages.ArticlePage
57
+ ```
58
+
59
+ `seed_wagtail` is destructive: it removes Wagtail `Site` objects and pages below
60
+ its technical root. Use an isolated development database. `seed_wagtail_pages`
61
+ preserves existing pages and reuses representatives when the configured count is met.
62
+ During a full reset, nullable protected links into the old page tree are detached
63
+ and populated again. Required protected links block deletion; the original sites,
64
+ pages and detached links are restored when deletion fails.
65
+ The same actions are available through **Seeder** in the Wagtail admin to active
66
+ users with the explicitly assigned `wagtail_seeder.run_seeder` permission.
67
+ Superusers also need a direct or group grant; their automatic Django permission
68
+ bypass does not apply. Existing users are not granted access on upgrade.
69
+ CLI commands require the explicit allowed environment and do not impersonate a user.
70
+
71
+ After `migrate`, assign **Can run Wagtail Seeder** through Django's user/group
72
+ administration or Wagtail's group permissions. Users also need normal Wagtail
73
+ admin access. Removing the grant immediately hides the menu and blocks seed requests.
74
+
75
+ The dashboard includes a moving loading bar and elapsed time during a seed,
76
+ searchable page selection, and a searchable, sortable result table with status
77
+ filters. The loading bar indicates activity rather than a completion percentage.
78
+ The last report remains available after refreshing until the next seed. Report
79
+ items include `is_summary` (default `False`); summary rows are excluded from
80
+ outcome counts so a failure is counted once.
81
+
82
+ ## Sites and languages
83
+
84
+ A single-site project needs only the configuration above. Use `SITE_ROOTS` for
85
+ multiple public sites or languages. Every root creates its own `Site`; descendants,
86
+ page links, settings and translatable snippets stay within their applicable scope.
87
+
88
+ ```python
89
+ WAGTAIL_SEEDER = {
90
+ "ENVIRONMENT": "dev",
91
+ "DEFAULT_SITE_PORT": 80,
92
+ "SITE_ROOTS": [
93
+ {
94
+ "page_model": "pages.HomePage",
95
+ "hostname": "localhost",
96
+ "language_code": "nl",
97
+ "is_default_site": True,
98
+ },
99
+ {
100
+ "page_model": "pages.HomePage",
101
+ "hostname": "en.localhost",
102
+ "language_code": "en",
103
+ },
104
+ ],
105
+ }
106
+ ```
107
+
108
+ `language_code` must be in Wagtail's content languages. It resolves or creates a
109
+ `Locale`; it does not enable translation settings or translate text. Omit it to use
110
+ the project's default content language. Do not put a language string in
111
+ `field_values["locale"]`: that field expects a `Locale` instance.
112
+
113
+ Each root requires `page_model`; optional values include `hostname`, `port`,
114
+ `site_name`, `is_default_site`, `slug` and protected `field_values`. Give it a unique
115
+ `key` when another configured root uses `parent_page_key`.
116
+
117
+ `SEED_ROOT_PAGES` uses the same page configuration without creating another Site.
118
+ Use it only for technical branches that otherwise cannot be reached.
119
+ `parent_page_model` or `parent_page_key` deliberately selects an explicit parent;
120
+ `force_root: True` deliberately overrides creation restrictions at the technical
121
+ root. An explicit parent and `force_root` cannot be combined.
122
+
123
+ ## Generation and validation
124
+
125
+ The seeder creates draft page structures before filling dependencies and publishing.
126
+ Editor presets keep their existing text and structure while empty required fields
127
+ and nested blocks are filled. Existing valid relationships are preserved, including
128
+ relationships in explicitly declared many-to-many intermediate models.
129
+ Scalar fields required by the page editor are filled even when the model permits
130
+ blank values, without changing the model's field definitions.
131
+ It uses Wagtail's allowed parent/child models, `can_create_at()`, global `max_count`
132
+ and per-parent limits. Titles come from model names: `NewsOverviewPage` becomes
133
+ **News Overview**. Valid defaults and populated content are retained, including
134
+ visibility flags, locales, translation keys and configured values.
135
+
136
+ `PAGE_COUNTS` maps model labels to positive counts **per public tree and locale**;
137
+ the default is one of each applicable concrete type. A subclass does not count as
138
+ its parent type. Wagtail's global singleton limits still take precedence.
139
+ `UNIQUE_PAGE_SCOPES` adds post-seed checks for combinations of `site`, `region`
140
+ and/or `language`.
141
+
142
+ Stream/List blocks and InlinePanels follow their minimum/maximum counts, including
143
+ nested `block_counts`. `REQUIRED_STREAMFIELD_ITEM_COUNTS` supplies additional
144
+ business constraints not declared on the block:
145
+
146
+ ```python
147
+ "REQUIRED_STREAMFIELD_ITEM_COUNTS": {"pages.ArticlePage.highlights": 2}
148
+ ```
149
+
150
+ `SNIPPET_COUNTS` accepts model labels or legacy class names (labels take precedence).
151
+ The default is four; use zero to request no new instances. Translatable snippets
152
+ are generated for every configured root locale. Existing instances are reused and
153
+ validated, and stale chooser links are repaired without replacing their text.
154
+ Site settings are populated before saving, then revisited after their hooks have
155
+ created any additional pages. Page-owned inlines keep their original parent.
156
+
157
+ When `wagtailstreamforms` is installed, the seeder builds local contact forms from
158
+ registered single-line, email and multiline field blocks and configured templates.
159
+ It also supports older installations with separate FormField models. Submission
160
+ hooks stay empty; the seeder does not submit forms or send mail.
161
+
162
+ The inspector only reads configuration and model structure; it does not execute
163
+ model validation or prove rendering works. `seed_wagtail --require-complete` checks
164
+ both that graph and the results of actual model/block validation. Missing mandatory
165
+ content fails the command. Optional unsupported blocks/inlines are reported as
166
+ skipped; declared exclusions are intentional. Failed objects are rolled back,
167
+ while successful objects remain available for diagnosis.
168
+
169
+ ## Small project providers
170
+
171
+ Use providers for business rules that cannot be inferred from field declarations.
172
+ Keep them in the consuming project and configure dotted paths:
173
+
174
+ ```python
175
+ "MODEL_VALUE_PROVIDERS": ["myproject.seeding.model_values"],
176
+ "FIELD_VALUE_PROVIDERS": ["myproject.seeding.field_values"],
177
+ "BLOCK_VALUE_PROVIDERS": ["myproject.seeding.block_values"],
178
+ ```
179
+
180
+ Model providers receive `(instance, context)` during `prepare` (before generic
181
+ population) and `validate` (before validation). Make them idempotent. Field providers
182
+ receive `(instance, field, context)` during `pre`/`post`, including relation and
183
+ StreamField fields. Block providers receive `(block, context)`. Field and block
184
+ providers return `wagtail_seeder.providers.UNHANDLED` to delegate generation;
185
+ returning `None` intentionally supplies an empty value.
186
+
187
+ Context includes `seeder`, `config`, scoped `pages`, `phase`, `instance`/`owner`,
188
+ `site`, `locale`, a zero-based `index`, `field_name` and a tuple `block_path`.
189
+ For model providers, `index` is stable within the model's ordered instances and
190
+ locale scope. For nested block providers it identifies the current stream/list item.
191
+ Protected `field_values` bypass generic field generation and link repair; model
192
+ providers may deliberately change them.
193
+
194
+ For example, a project that retrieves exactly one navigation object per language
195
+ can assign the configured languages using the model provider's index. Match
196
+ `SNIPPET_COUNTS` to the number of languages. See the
197
+ [configuration guide](docs/compatibility.md) for generic examples covering multiple
198
+ sites, custom validation, technical page types and external dependencies.
199
+
200
+ ## External integrations
201
+
202
+ Exclude models and blocks whose validation or rendering needs an external service:
203
+
204
+ ```python
205
+ "EXCLUDED_SNIPPET_MODELS": ["newsletter.MailingList"],
206
+ "SKIP_BLOCK_CLASS_NAMES": {"NewsletterBlock": "requires an external service"},
207
+ ```
208
+
209
+ Exclusions also apply to indirect chooser/required-relation creation. Block exclusions
210
+ match class names, qualified names and subclasses. A required excluded dependency
211
+ is an error; a provider or a different valid local block is needed. External optional
212
+ video URL fields with empty valid defaults stay empty. Seeder settings cannot turn
213
+ off network calls hardcoded in a project's page rendering or save hooks.
214
+
215
+ Other optional keys are `ALLOWED_ENVIRONMENTS` (default `{"dev"}`),
216
+ `ALLOWED_HOSTNAMES` (dashboard request guard), `FAKER_LOCALE` (default `"nl_NL"`),
217
+ `DEFAULT_SITE_HOSTNAME` (default `"localhost"`) and `DEFAULT_SITE_PORT`
218
+ (default `8000`). Complete local compatibility requires testing the generated
219
+ frontend and editor in the consuming project; static inspection cannot guarantee
220
+ arbitrary custom validation or JavaScript behavior.
221
+
222
+ ## Development and testing
223
+
224
+ The repository separates the distributable package from its local test project:
225
+
226
+ ```text
227
+ src/wagtail_seeder/ Python package, management commands and admin templates
228
+ tests/test_project/ Runnable Django/Wagtail project, local database and media
229
+ tests/models.py Example page and relation models
230
+ tests/migrations/ One initial migration for the example models
231
+ tests/test_*.py Automated tests
232
+ ```
233
+
234
+ From the repository root, create an environment and install the package in editable
235
+ mode. Changes in `src` then take effect immediately:
236
+
237
+ ```bash
238
+ python -m venv .venv
239
+ source .venv/bin/activate
240
+ python -m pip install -e ".[dev]"
241
+ pytest
242
+ ```
243
+
244
+ The default installation uses the latest available Wagtail 7.x. To test the lowest
245
+ supported release series, use a separate environment and install with
246
+ `python -m pip install -e ".[dev]" "wagtail>=7,<7.1"`.
247
+
248
+ Run the same checks as CI:
249
+
250
+ ```bash
251
+ python tests/test_project/manage.py check
252
+ python tests/test_project/manage.py makemigrations --check --dry-run
253
+ ruff check .
254
+ ruff format --check .
255
+ pytest
256
+ ```
257
+
258
+ Automated tests use an isolated database and temporary media directories. They do
259
+ not change the local demo database or leave generated media in the repository root.
260
+
261
+ ## Local Wagtail project
262
+
263
+ No external project or Bakery Demo checkout is required. From the repository root:
264
+
265
+ ```bash
266
+ python tests/test_project/manage.py migrate
267
+ python tests/test_project/manage.py createsuperuser
268
+ python tests/test_project/manage.py inspect_wagtail_seed --require-complete
269
+ python tests/test_project/manage.py seed_wagtail --confirm --require-complete
270
+ python tests/test_project/manage.py runserver
271
+ ```
272
+
273
+ Open `http://127.0.0.1:8000/admin/` and log in. Explicitly assign **Can run Wagtail
274
+ Seeder** to the account or its group before opening **Seeder** in the menu.
275
+ Visit `http://127.0.0.1:8000/admin/seeder/` to test a full seed or
276
+ selected page types. The confirmation checkbox is required for a destructive full
277
+ seed. Open `http://127.0.0.1:8000/` to browse the generated pages.
278
+
279
+ For example, test the selective command with:
280
+
281
+ ```bash
282
+ python tests/test_project/manage.py seed_wagtail_pages --page-type tests.ArticlePage
283
+ ```
284
+
285
+ The local database, uploaded/generated media and collected static files live under
286
+ `tests/test_project/` and are ignored by Git. The project is configured for local
287
+ development only.
288
+ The example models use one initial migration. If a local demo database was created
289
+ with the previous migration layout, recreate that demo database before migrating.
290
+ Installed packages continue to use the separate `wagtail_seeder` permission migration.
291
+
292
+ ## Building and releasing
293
+
294
+ Build the wheel and source distribution and check their metadata:
295
+
296
+ ```bash
297
+ python -m build
298
+ python -m twine check dist/*
299
+ python scripts/check_distribution.py
300
+ tar -tf dist/wagtail_seeder-*.tar.gz
301
+ unzip -l dist/wagtail_seeder-*.whl
302
+ ```
303
+
304
+ Before publishing, install the built wheel in the target project's environment
305
+ with `python -m pip install /path/to/wagtail_seeder-1.0.0-py3-none-any.whl`, then
306
+ apply migrations and the local Seeder configuration. Check a full seed, selective
307
+ seeding, frontend rendering and saving generated pages in the editor. This tests
308
+ the packaged assets and any custom validation in the application.
309
+
310
+ Runtime code and templates come from `src/wagtail_seeder/`. Tests, demo databases
311
+ and media are excluded from both distributions. The source archive also contains
312
+ the README, license and build metadata.
313
+
314
+ Public releases are published from GitLab CI to PyPI using
315
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/using-a-publisher/).
316
+ Before the first release, configure a pending publisher on PyPI (or a publisher
317
+ on the existing project) with:
318
+
319
+ - PyPI project: `wagtail-seeder`
320
+ - GitLab namespace: `fourdigits/private-packages`
321
+ - GitLab project: `wagtail-seeder`
322
+ - Pipeline file: `.gitlab-ci.yml`
323
+ - Environment: `pypi`
324
+
325
+ Protect the `v*` tag pattern in GitLab. No stored PyPI API token is needed.
326
+ Update the version in the project metadata and
327
+ `src/wagtail_seeder/__init__.py`, then push a protected tag matching that version,
328
+ for example `v1.0.0`. CI requires the tag and both version declarations to agree,
329
+ and publishes only after tests and package checks pass. Published versions cannot
330
+ be overwritten; bump the version for each new release.
331
+
332
+ ## License
333
+
334
+ BSD-3-Clause. See [LICENSE](LICENSE).
@@ -0,0 +1,309 @@
1
+ # Wagtail Seeder
2
+
3
+ Development-data seeder for Wagtail 7.x (Python 3.10 and later). It creates representative
4
+ pages, media, snippets and settings by inspecting installed Wagtail models.
5
+
6
+ ## Installation
7
+
8
+ Install from PyPI:
9
+
10
+ ```bash
11
+ python -m pip install wagtail-seeder
12
+ ```
13
+
14
+ Add `"wagtail_seeder"` to `INSTALLED_APPS`. Configure
15
+ an explicit environment; the package fails closed when it is omitted or unsupported:
16
+
17
+ ```python
18
+ WAGTAIL_SEEDER = {
19
+ "ENVIRONMENT": "dev",
20
+ "HOMEPAGE_MODEL": "pages.HomePage",
21
+ "DEFAULT_SITE_PORT": 80, # use 8000 for Django's runserver
22
+ }
23
+ ```
24
+
25
+ Apply the project's migrations, then inspect and seed:
26
+
27
+ ```bash
28
+ python manage.py migrate
29
+ python manage.py inspect_wagtail_seed --format=json --require-complete
30
+ python manage.py seed_wagtail --confirm --require-complete
31
+ python manage.py seed_wagtail_pages --page-type pages.ArticlePage
32
+ ```
33
+
34
+ `seed_wagtail` is destructive: it removes Wagtail `Site` objects and pages below
35
+ its technical root. Use an isolated development database. `seed_wagtail_pages`
36
+ preserves existing pages and reuses representatives when the configured count is met.
37
+ During a full reset, nullable protected links into the old page tree are detached
38
+ and populated again. Required protected links block deletion; the original sites,
39
+ pages and detached links are restored when deletion fails.
40
+ The same actions are available through **Seeder** in the Wagtail admin to active
41
+ users with the explicitly assigned `wagtail_seeder.run_seeder` permission.
42
+ Superusers also need a direct or group grant; their automatic Django permission
43
+ bypass does not apply. Existing users are not granted access on upgrade.
44
+ CLI commands require the explicit allowed environment and do not impersonate a user.
45
+
46
+ After `migrate`, assign **Can run Wagtail Seeder** through Django's user/group
47
+ administration or Wagtail's group permissions. Users also need normal Wagtail
48
+ admin access. Removing the grant immediately hides the menu and blocks seed requests.
49
+
50
+ The dashboard includes a moving loading bar and elapsed time during a seed,
51
+ searchable page selection, and a searchable, sortable result table with status
52
+ filters. The loading bar indicates activity rather than a completion percentage.
53
+ The last report remains available after refreshing until the next seed. Report
54
+ items include `is_summary` (default `False`); summary rows are excluded from
55
+ outcome counts so a failure is counted once.
56
+
57
+ ## Sites and languages
58
+
59
+ A single-site project needs only the configuration above. Use `SITE_ROOTS` for
60
+ multiple public sites or languages. Every root creates its own `Site`; descendants,
61
+ page links, settings and translatable snippets stay within their applicable scope.
62
+
63
+ ```python
64
+ WAGTAIL_SEEDER = {
65
+ "ENVIRONMENT": "dev",
66
+ "DEFAULT_SITE_PORT": 80,
67
+ "SITE_ROOTS": [
68
+ {
69
+ "page_model": "pages.HomePage",
70
+ "hostname": "localhost",
71
+ "language_code": "nl",
72
+ "is_default_site": True,
73
+ },
74
+ {
75
+ "page_model": "pages.HomePage",
76
+ "hostname": "en.localhost",
77
+ "language_code": "en",
78
+ },
79
+ ],
80
+ }
81
+ ```
82
+
83
+ `language_code` must be in Wagtail's content languages. It resolves or creates a
84
+ `Locale`; it does not enable translation settings or translate text. Omit it to use
85
+ the project's default content language. Do not put a language string in
86
+ `field_values["locale"]`: that field expects a `Locale` instance.
87
+
88
+ Each root requires `page_model`; optional values include `hostname`, `port`,
89
+ `site_name`, `is_default_site`, `slug` and protected `field_values`. Give it a unique
90
+ `key` when another configured root uses `parent_page_key`.
91
+
92
+ `SEED_ROOT_PAGES` uses the same page configuration without creating another Site.
93
+ Use it only for technical branches that otherwise cannot be reached.
94
+ `parent_page_model` or `parent_page_key` deliberately selects an explicit parent;
95
+ `force_root: True` deliberately overrides creation restrictions at the technical
96
+ root. An explicit parent and `force_root` cannot be combined.
97
+
98
+ ## Generation and validation
99
+
100
+ The seeder creates draft page structures before filling dependencies and publishing.
101
+ Editor presets keep their existing text and structure while empty required fields
102
+ and nested blocks are filled. Existing valid relationships are preserved, including
103
+ relationships in explicitly declared many-to-many intermediate models.
104
+ Scalar fields required by the page editor are filled even when the model permits
105
+ blank values, without changing the model's field definitions.
106
+ It uses Wagtail's allowed parent/child models, `can_create_at()`, global `max_count`
107
+ and per-parent limits. Titles come from model names: `NewsOverviewPage` becomes
108
+ **News Overview**. Valid defaults and populated content are retained, including
109
+ visibility flags, locales, translation keys and configured values.
110
+
111
+ `PAGE_COUNTS` maps model labels to positive counts **per public tree and locale**;
112
+ the default is one of each applicable concrete type. A subclass does not count as
113
+ its parent type. Wagtail's global singleton limits still take precedence.
114
+ `UNIQUE_PAGE_SCOPES` adds post-seed checks for combinations of `site`, `region`
115
+ and/or `language`.
116
+
117
+ Stream/List blocks and InlinePanels follow their minimum/maximum counts, including
118
+ nested `block_counts`. `REQUIRED_STREAMFIELD_ITEM_COUNTS` supplies additional
119
+ business constraints not declared on the block:
120
+
121
+ ```python
122
+ "REQUIRED_STREAMFIELD_ITEM_COUNTS": {"pages.ArticlePage.highlights": 2}
123
+ ```
124
+
125
+ `SNIPPET_COUNTS` accepts model labels or legacy class names (labels take precedence).
126
+ The default is four; use zero to request no new instances. Translatable snippets
127
+ are generated for every configured root locale. Existing instances are reused and
128
+ validated, and stale chooser links are repaired without replacing their text.
129
+ Site settings are populated before saving, then revisited after their hooks have
130
+ created any additional pages. Page-owned inlines keep their original parent.
131
+
132
+ When `wagtailstreamforms` is installed, the seeder builds local contact forms from
133
+ registered single-line, email and multiline field blocks and configured templates.
134
+ It also supports older installations with separate FormField models. Submission
135
+ hooks stay empty; the seeder does not submit forms or send mail.
136
+
137
+ The inspector only reads configuration and model structure; it does not execute
138
+ model validation or prove rendering works. `seed_wagtail --require-complete` checks
139
+ both that graph and the results of actual model/block validation. Missing mandatory
140
+ content fails the command. Optional unsupported blocks/inlines are reported as
141
+ skipped; declared exclusions are intentional. Failed objects are rolled back,
142
+ while successful objects remain available for diagnosis.
143
+
144
+ ## Small project providers
145
+
146
+ Use providers for business rules that cannot be inferred from field declarations.
147
+ Keep them in the consuming project and configure dotted paths:
148
+
149
+ ```python
150
+ "MODEL_VALUE_PROVIDERS": ["myproject.seeding.model_values"],
151
+ "FIELD_VALUE_PROVIDERS": ["myproject.seeding.field_values"],
152
+ "BLOCK_VALUE_PROVIDERS": ["myproject.seeding.block_values"],
153
+ ```
154
+
155
+ Model providers receive `(instance, context)` during `prepare` (before generic
156
+ population) and `validate` (before validation). Make them idempotent. Field providers
157
+ receive `(instance, field, context)` during `pre`/`post`, including relation and
158
+ StreamField fields. Block providers receive `(block, context)`. Field and block
159
+ providers return `wagtail_seeder.providers.UNHANDLED` to delegate generation;
160
+ returning `None` intentionally supplies an empty value.
161
+
162
+ Context includes `seeder`, `config`, scoped `pages`, `phase`, `instance`/`owner`,
163
+ `site`, `locale`, a zero-based `index`, `field_name` and a tuple `block_path`.
164
+ For model providers, `index` is stable within the model's ordered instances and
165
+ locale scope. For nested block providers it identifies the current stream/list item.
166
+ Protected `field_values` bypass generic field generation and link repair; model
167
+ providers may deliberately change them.
168
+
169
+ For example, a project that retrieves exactly one navigation object per language
170
+ can assign the configured languages using the model provider's index. Match
171
+ `SNIPPET_COUNTS` to the number of languages. See the
172
+ [configuration guide](docs/compatibility.md) for generic examples covering multiple
173
+ sites, custom validation, technical page types and external dependencies.
174
+
175
+ ## External integrations
176
+
177
+ Exclude models and blocks whose validation or rendering needs an external service:
178
+
179
+ ```python
180
+ "EXCLUDED_SNIPPET_MODELS": ["newsletter.MailingList"],
181
+ "SKIP_BLOCK_CLASS_NAMES": {"NewsletterBlock": "requires an external service"},
182
+ ```
183
+
184
+ Exclusions also apply to indirect chooser/required-relation creation. Block exclusions
185
+ match class names, qualified names and subclasses. A required excluded dependency
186
+ is an error; a provider or a different valid local block is needed. External optional
187
+ video URL fields with empty valid defaults stay empty. Seeder settings cannot turn
188
+ off network calls hardcoded in a project's page rendering or save hooks.
189
+
190
+ Other optional keys are `ALLOWED_ENVIRONMENTS` (default `{"dev"}`),
191
+ `ALLOWED_HOSTNAMES` (dashboard request guard), `FAKER_LOCALE` (default `"nl_NL"`),
192
+ `DEFAULT_SITE_HOSTNAME` (default `"localhost"`) and `DEFAULT_SITE_PORT`
193
+ (default `8000`). Complete local compatibility requires testing the generated
194
+ frontend and editor in the consuming project; static inspection cannot guarantee
195
+ arbitrary custom validation or JavaScript behavior.
196
+
197
+ ## Development and testing
198
+
199
+ The repository separates the distributable package from its local test project:
200
+
201
+ ```text
202
+ src/wagtail_seeder/ Python package, management commands and admin templates
203
+ tests/test_project/ Runnable Django/Wagtail project, local database and media
204
+ tests/models.py Example page and relation models
205
+ tests/migrations/ One initial migration for the example models
206
+ tests/test_*.py Automated tests
207
+ ```
208
+
209
+ From the repository root, create an environment and install the package in editable
210
+ mode. Changes in `src` then take effect immediately:
211
+
212
+ ```bash
213
+ python -m venv .venv
214
+ source .venv/bin/activate
215
+ python -m pip install -e ".[dev]"
216
+ pytest
217
+ ```
218
+
219
+ The default installation uses the latest available Wagtail 7.x. To test the lowest
220
+ supported release series, use a separate environment and install with
221
+ `python -m pip install -e ".[dev]" "wagtail>=7,<7.1"`.
222
+
223
+ Run the same checks as CI:
224
+
225
+ ```bash
226
+ python tests/test_project/manage.py check
227
+ python tests/test_project/manage.py makemigrations --check --dry-run
228
+ ruff check .
229
+ ruff format --check .
230
+ pytest
231
+ ```
232
+
233
+ Automated tests use an isolated database and temporary media directories. They do
234
+ not change the local demo database or leave generated media in the repository root.
235
+
236
+ ## Local Wagtail project
237
+
238
+ No external project or Bakery Demo checkout is required. From the repository root:
239
+
240
+ ```bash
241
+ python tests/test_project/manage.py migrate
242
+ python tests/test_project/manage.py createsuperuser
243
+ python tests/test_project/manage.py inspect_wagtail_seed --require-complete
244
+ python tests/test_project/manage.py seed_wagtail --confirm --require-complete
245
+ python tests/test_project/manage.py runserver
246
+ ```
247
+
248
+ Open `http://127.0.0.1:8000/admin/` and log in. Explicitly assign **Can run Wagtail
249
+ Seeder** to the account or its group before opening **Seeder** in the menu.
250
+ Visit `http://127.0.0.1:8000/admin/seeder/` to test a full seed or
251
+ selected page types. The confirmation checkbox is required for a destructive full
252
+ seed. Open `http://127.0.0.1:8000/` to browse the generated pages.
253
+
254
+ For example, test the selective command with:
255
+
256
+ ```bash
257
+ python tests/test_project/manage.py seed_wagtail_pages --page-type tests.ArticlePage
258
+ ```
259
+
260
+ The local database, uploaded/generated media and collected static files live under
261
+ `tests/test_project/` and are ignored by Git. The project is configured for local
262
+ development only.
263
+ The example models use one initial migration. If a local demo database was created
264
+ with the previous migration layout, recreate that demo database before migrating.
265
+ Installed packages continue to use the separate `wagtail_seeder` permission migration.
266
+
267
+ ## Building and releasing
268
+
269
+ Build the wheel and source distribution and check their metadata:
270
+
271
+ ```bash
272
+ python -m build
273
+ python -m twine check dist/*
274
+ python scripts/check_distribution.py
275
+ tar -tf dist/wagtail_seeder-*.tar.gz
276
+ unzip -l dist/wagtail_seeder-*.whl
277
+ ```
278
+
279
+ Before publishing, install the built wheel in the target project's environment
280
+ with `python -m pip install /path/to/wagtail_seeder-1.0.0-py3-none-any.whl`, then
281
+ apply migrations and the local Seeder configuration. Check a full seed, selective
282
+ seeding, frontend rendering and saving generated pages in the editor. This tests
283
+ the packaged assets and any custom validation in the application.
284
+
285
+ Runtime code and templates come from `src/wagtail_seeder/`. Tests, demo databases
286
+ and media are excluded from both distributions. The source archive also contains
287
+ the README, license and build metadata.
288
+
289
+ Public releases are published from GitLab CI to PyPI using
290
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/using-a-publisher/).
291
+ Before the first release, configure a pending publisher on PyPI (or a publisher
292
+ on the existing project) with:
293
+
294
+ - PyPI project: `wagtail-seeder`
295
+ - GitLab namespace: `fourdigits/private-packages`
296
+ - GitLab project: `wagtail-seeder`
297
+ - Pipeline file: `.gitlab-ci.yml`
298
+ - Environment: `pypi`
299
+
300
+ Protect the `v*` tag pattern in GitLab. No stored PyPI API token is needed.
301
+ Update the version in the project metadata and
302
+ `src/wagtail_seeder/__init__.py`, then push a protected tag matching that version,
303
+ for example `v1.0.0`. CI requires the tag and both version declarations to agree,
304
+ and publishes only after tests and package checks pass. Published versions cannot
305
+ be overwritten; bump the version for each new release.
306
+
307
+ ## License
308
+
309
+ BSD-3-Clause. See [LICENSE](LICENSE).