wagtail-developer-reference 0.1.3__tar.gz → 0.1.5__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.
- {wagtail_developer_reference-0.1.3/wagtail_developer_reference.egg-info → wagtail_developer_reference-0.1.5}/PKG-INFO +28 -7
- wagtail_developer_reference-0.1.5/README.md +69 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/pyproject.toml +1 -1
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/setup.py +1 -1
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference/templates/detail.html +3 -3
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference/templates/index.html +127 -46
- wagtail_developer_reference-0.1.5/wagtail_developer_reference/tests/__init__.py +0 -0
- wagtail_developer_reference-0.1.5/wagtail_developer_reference/tests/test_auditor.py +100 -0
- wagtail_developer_reference-0.1.5/wagtail_developer_reference/tests/test_registry.py +37 -0
- wagtail_developer_reference-0.1.5/wagtail_developer_reference/wagtail_hooks.py +749 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5/wagtail_developer_reference.egg-info}/PKG-INFO +28 -7
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference.egg-info/SOURCES.txt +4 -1
- wagtail_developer_reference-0.1.3/README.md +0 -48
- wagtail_developer_reference-0.1.3/wagtail_developer_reference/wagtail_hooks.py +0 -358
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/MANIFEST.in +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/setup.cfg +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference/__init__.py +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference/apps.py +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference/migrations/0001_initial.py +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference/migrations/__init__.py +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference/models.py +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference.egg-info/dependency_links.txt +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference.egg-info/requires.txt +0 -0
- {wagtail_developer_reference-0.1.3 → wagtail_developer_reference-0.1.5}/wagtail_developer_reference.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wagtail-developer-reference
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A visual auditing tool for Wagtail developers to track Page models and StreamField blocks.
|
|
5
5
|
Author-email: Four Digits <info@fourdigits.nl>
|
|
6
6
|
Project-URL: Homepage, https://github.com/fourdigits/wagtail-developer-reference
|
|
@@ -32,16 +32,18 @@ Requires-Dist: pytest-django; extra == "dev"
|
|
|
32
32
|
In large Wagtail projects, it’s easy to lose track of where specific blocks are used or which templates are being rendered. This tool gives you a "God-view" of your components without needing to dig through the database or shell.
|
|
33
33
|
|
|
34
34
|
## Features
|
|
35
|
-
* **
|
|
36
|
-
* **
|
|
37
|
-
* **
|
|
38
|
-
* **
|
|
35
|
+
* **Fast registry:** Code definitions are cached per worker; the normal registry response stays small and paginates at 20 rows.
|
|
36
|
+
* **On-demand exact usage:** PAGE and BLOCK counts are calculated only for a usage range, scoped to the exact page model, StreamField and nested definition path.
|
|
37
|
+
* **Scalable StreamField inspection:** Usage scans read raw StreamField JSON, avoiding chooser-object queries while keeping totals current.
|
|
38
|
+
* **Searchable block picker:** An accessible, collapsed picker searches all definitions server-side and returns at most 30 choices at once.
|
|
39
|
+
* **Multi-site aware:** Site root pages are included, nested subsites are excluded from parent-site counts, and an empty site filter aggregates globally.
|
|
40
|
+
* **Usage inspection:** Open an exact definition's paginated page list and copy the visible usage table in one click.
|
|
39
41
|
* **Template discovery:** See the template path associated with every component.
|
|
40
42
|
|
|
41
43
|
## Installation
|
|
42
44
|
|
|
43
|
-
Compatibility: Python 3.9+ and Wagtail 4.1 through 7.4.2 LTS
|
|
44
|
-
|
|
45
|
+
Compatibility: Python 3.9+ and Wagtail 4.1 through 7.4.2 LTS. Wagtail 7.4
|
|
46
|
+
itself requires Python 3.10+.
|
|
45
47
|
|
|
46
48
|
1. Add \`wagtail_developer_reference\` to your \`INSTALLED_APPS\`:
|
|
47
49
|
\`\`\`python
|
|
@@ -70,5 +72,24 @@ By default, no account can see the menu item or access the URLs. Superusers are
|
|
|
70
72
|
granted access automatically by this package; assign the permission directly or
|
|
71
73
|
through a group.
|
|
72
74
|
|
|
75
|
+
## Settings
|
|
76
|
+
|
|
77
|
+
### `WAGTAIL_DEVELOPER_REFERENCE_ALLOW_ALL_ADMIN_USERS`
|
|
78
|
+
|
|
79
|
+
Default: `False`.
|
|
80
|
+
|
|
81
|
+
Set this to `True` in your Django settings to allow all active, authenticated
|
|
82
|
+
users with access to the Wagtail admin (`wagtailadmin.access_admin`) to see the
|
|
83
|
+
**System Registry** menu item and access the Developer Reference URLs, without
|
|
84
|
+
assigning the additional Developer Reference permission:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
WAGTAIL_DEVELOPER_REFERENCE_ALLOW_ALL_ADMIN_USERS = True
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This includes active superusers. When omitted or set to `False`, the explicit
|
|
91
|
+
user or group permission described above is still required, including for
|
|
92
|
+
superusers.
|
|
93
|
+
|
|
73
94
|
## License
|
|
74
95
|
**Internal Use Only** - Copyright © 2026 Fourdigits. All rights reserved.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Wagtail Developer Reference
|
|
2
|
+
|
|
3
|
+
**A visual auditing tool for Wagtail developers.** This package provides a live registry of your project's architecture, making it easy to track Page models and StreamField blocks across complex, multi-site setups.
|
|
4
|
+
|
|
5
|
+
## Why use this?
|
|
6
|
+
In large Wagtail projects, it’s easy to lose track of where specific blocks are used or which templates are being rendered. This tool gives you a "God-view" of your components without needing to dig through the database or shell.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
* **Fast registry:** Code definitions are cached per worker; the normal registry response stays small and paginates at 20 rows.
|
|
10
|
+
* **On-demand exact usage:** PAGE and BLOCK counts are calculated only for a usage range, scoped to the exact page model, StreamField and nested definition path.
|
|
11
|
+
* **Scalable StreamField inspection:** Usage scans read raw StreamField JSON, avoiding chooser-object queries while keeping totals current.
|
|
12
|
+
* **Searchable block picker:** An accessible, collapsed picker searches all definitions server-side and returns at most 30 choices at once.
|
|
13
|
+
* **Multi-site aware:** Site root pages are included, nested subsites are excluded from parent-site counts, and an empty site filter aggregates globally.
|
|
14
|
+
* **Usage inspection:** Open an exact definition's paginated page list and copy the visible usage table in one click.
|
|
15
|
+
* **Template discovery:** See the template path associated with every component.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Compatibility: Python 3.9+ and Wagtail 4.1 through 7.4.2 LTS. Wagtail 7.4
|
|
20
|
+
itself requires Python 3.10+.
|
|
21
|
+
|
|
22
|
+
1. Add \`wagtail_developer_reference\` to your \`INSTALLED_APPS\`:
|
|
23
|
+
\`\`\`python
|
|
24
|
+
INSTALLED_APPS = [
|
|
25
|
+
...,
|
|
26
|
+
"wagtail_developer_reference",
|
|
27
|
+
]
|
|
28
|
+
\`\`\`
|
|
29
|
+
|
|
30
|
+
2. Run migrations so Django creates the package permission:
|
|
31
|
+
```bash
|
|
32
|
+
python manage.py migrate
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Grant access in the Django admin:
|
|
36
|
+
* Open a user or group in the Django admin.
|
|
37
|
+
* Assign **Wagtail developer reference | developer reference access | Can access the
|
|
38
|
+
Wagtail developer reference**.
|
|
39
|
+
|
|
40
|
+
4. **Usage:**
|
|
41
|
+
* Login to the Wagtail Admin with an account that has that permission explicitly
|
|
42
|
+
assigned.
|
|
43
|
+
* Find the **"System Registry"** item in the sidebar menu.
|
|
44
|
+
|
|
45
|
+
By default, no account can see the menu item or access the URLs. Superusers are not
|
|
46
|
+
granted access automatically by this package; assign the permission directly or
|
|
47
|
+
through a group.
|
|
48
|
+
|
|
49
|
+
## Settings
|
|
50
|
+
|
|
51
|
+
### `WAGTAIL_DEVELOPER_REFERENCE_ALLOW_ALL_ADMIN_USERS`
|
|
52
|
+
|
|
53
|
+
Default: `False`.
|
|
54
|
+
|
|
55
|
+
Set this to `True` in your Django settings to allow all active, authenticated
|
|
56
|
+
users with access to the Wagtail admin (`wagtailadmin.access_admin`) to see the
|
|
57
|
+
**System Registry** menu item and access the Developer Reference URLs, without
|
|
58
|
+
assigning the additional Developer Reference permission:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
WAGTAIL_DEVELOPER_REFERENCE_ALLOW_ALL_ADMIN_USERS = True
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This includes active superusers. When omitted or set to `False`, the explicit
|
|
65
|
+
user or group permission described above is still required, including for
|
|
66
|
+
superusers.
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
**Internal Use Only** - Copyright © 2026 Fourdigits. All rights reserved.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "wagtail-developer-reference"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.5"
|
|
4
4
|
description = "A visual auditing tool for Wagtail developers to track Page models and StreamField blocks."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{name = "Four Digits", email = "info@fourdigits.nl" }]
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
<div class="breadcrumb">
|
|
130
130
|
<a href="{% url 'dev_reference' %}">← Back to System Registry</a>
|
|
131
131
|
</div>
|
|
132
|
-
<h1>
|
|
132
|
+
<h1>Usage: <strong>{{ component_name }}</strong></h1>
|
|
133
133
|
<p>{{ page_obj.paginator.count }} location{{ page_obj.paginator.count|pluralize }} found</p>
|
|
134
134
|
</div>
|
|
135
135
|
</header>
|
|
@@ -159,14 +159,14 @@
|
|
|
159
159
|
<tr class="usage-row">
|
|
160
160
|
<td>
|
|
161
161
|
<strong class="page-title">{{ page.title }}</strong>
|
|
162
|
-
<small class="page-type">Type: {{ page.
|
|
162
|
+
<small class="page-type">Type: {{ page.content_type.name|title }}</small>
|
|
163
163
|
</td>
|
|
164
164
|
<td class="td-status" data-sort="{% if page.live %}1{% else %}0{% endif %}">
|
|
165
165
|
<span class="status-tag status-tag--{% if page.live %}live{% else %}draft{% endif %}">
|
|
166
166
|
{% if page.live %}Live{% else %}Draft{% endif %}
|
|
167
167
|
</span>
|
|
168
168
|
</td>
|
|
169
|
-
<td><code class="url-code">{{ page.
|
|
169
|
+
<td><code class="url-code">{{ page.url_path|default:"/(no path)/" }}</code></td>
|
|
170
170
|
<td class="col-actions">
|
|
171
171
|
<a href="{% url 'wagtailadmin_pages:edit' page.id %}" class="button button-small edit-button" target="_blank">Edit Page</a>
|
|
172
172
|
</td>
|
|
@@ -39,6 +39,24 @@
|
|
|
39
39
|
}
|
|
40
40
|
.quick-search-item .type-tag { margin-right: 0.35rem; }
|
|
41
41
|
.registry-controls label { display: block; font-weight: bold; color: white; margin-bottom: 0.5rem; }
|
|
42
|
+
.registry-controls .usage-label { display: flex; align-items: center; gap: 0.35rem; margin-bottom: 0.5rem; }
|
|
43
|
+
.registry-controls .usage-label label { margin-bottom: 0; }
|
|
44
|
+
.usage-help { position: relative; display: inline-flex; }
|
|
45
|
+
.usage-help-icon {
|
|
46
|
+
background: transparent; border: 1px solid currentColor; border-radius: 50%;
|
|
47
|
+
color: white; cursor: help; font: bold 0.8rem serif;
|
|
48
|
+
width: 1.25rem; height: 1.25rem; padding: 0;
|
|
49
|
+
}
|
|
50
|
+
.usage-help-icon:focus-visible { outline: 2px solid #dcbd3d; outline-offset: 2px; }
|
|
51
|
+
.usage-help-tooltip {
|
|
52
|
+
display: none; position: absolute; top: 100%; right: 0; z-index: 20;
|
|
53
|
+
width: 16rem; max-width: calc(100vw - 3rem); padding: 0.75rem;
|
|
54
|
+
background: #222; border: 1px solid #dcbd3d; color: white;
|
|
55
|
+
font-size: 0.875rem; font-weight: normal; line-height: 1.5;
|
|
56
|
+
}
|
|
57
|
+
.usage-help:hover .usage-help-tooltip,
|
|
58
|
+
.usage-help:focus-within .usage-help-tooltip { display: block; }
|
|
59
|
+
.usage-help-tooltip[hidden] { display: none; }
|
|
42
60
|
.registry-controls input, .registry-controls select {
|
|
43
61
|
box-sizing: border-box; height: 2.5rem; width: 100%;
|
|
44
62
|
border: 1px solid #dcbd3d; background: #333; color: white; border-radius: 0;
|
|
@@ -120,19 +138,29 @@
|
|
|
120
138
|
<input id="quick-search-filter" class="quick-search-filter" type="search" placeholder="Search pages and blocks..." autocomplete="off">
|
|
121
139
|
<span id="quick-search-status" class="quick-search-status" aria-live="polite"></span>
|
|
122
140
|
<div class="quick-search-list" aria-label="Quick registry search">
|
|
123
|
-
{% for
|
|
124
|
-
<button type="button" class="quick-search-item" data-search="{{
|
|
125
|
-
<span class="type-tag type-tag--{{ component_type|lower }}">{{ component_type }}</span>{{
|
|
141
|
+
{% for item in quick_search_items %}
|
|
142
|
+
<button type="button" class="quick-search-item" data-search="{{ item.hierarchy }}" data-type="{{ item.component_type }}" data-definition="{{ item.definition_id }}">
|
|
143
|
+
<span class="type-tag type-tag--{{ item.component_type|lower }}">{{ item.component_type }}</span>{{ item.hierarchy }}
|
|
126
144
|
</button>
|
|
127
145
|
{% endfor %}
|
|
128
146
|
</div>
|
|
129
147
|
</details>
|
|
130
148
|
<form id="registry-filter-form" method="get" class="registry-controls">
|
|
131
149
|
<input type="hidden" name="apply" value="1">
|
|
150
|
+
<input id="definition-filter" type="hidden" name="definition" value="{{ filters.definition }}">
|
|
132
151
|
<div class="registry-search"><label for="reg-search">Search Registry</label><input type="search" id="reg-search" name="q" value="{{ filters.q }}" placeholder="Name, field or site..."></div>
|
|
133
152
|
<div><label for="reg-filter">Component Type</label><select id="reg-filter" name="type"><option value="ALL" {% if filters.type == "ALL" %}selected{% endif %}>All</option><option value="PAGE" {% if filters.type == "PAGE" %}selected{% endif %}>Pages</option><option value="BLOCK" {% if filters.type == "BLOCK" %}selected{% endif %}>Blocks</option></select></div>
|
|
134
153
|
<div><label for="reg-site-filter">Site</label><select id="reg-site-filter" name="site"><option value="">All sites</option>{% for site in sites %}<option value="{{ site.id }}" {% if filters.site == site.id|stringformat:"s" %}selected{% endif %}>{{ site.site_name }}</option>{% endfor %}</select></div>
|
|
135
|
-
<div class="usage-range"
|
|
154
|
+
<div class="usage-range">
|
|
155
|
+
<span class="usage-label">
|
|
156
|
+
<label for="min-usage">Min usage</label>
|
|
157
|
+
<span class="usage-help">
|
|
158
|
+
<button id="min-usage-help" class="usage-help-icon" type="button" aria-label="About Min usage" aria-describedby="min-usage-tooltip"><span aria-hidden="true">i</span></button>
|
|
159
|
+
<span id="min-usage-tooltip" class="usage-help-tooltip" role="tooltip">Usage counts for pages, blocks, images and documents are only shown when Min usage is filled in. Enter 0 to include items with no usage, then apply the filters.</span>
|
|
160
|
+
</span>
|
|
161
|
+
</span>
|
|
162
|
+
<input id="min-usage" type="number" min="0" name="min_usage" value="{{ filters.min_usage }}" aria-describedby="min-usage-tooltip">
|
|
163
|
+
</div>
|
|
136
164
|
<div class="usage-range"><label for="max-usage">Max usage</label><input id="max-usage" type="number" min="0" name="max_usage" value="{{ filters.max_usage }}"></div>
|
|
137
165
|
<div class="filter-actions"><button type="submit" class="button">Apply Filters</button><a class="button button-secondary" href="{% url 'dev_reference' %}">Clear</a></div>
|
|
138
166
|
</form>
|
|
@@ -179,21 +207,15 @@
|
|
|
179
207
|
{% endfor %}
|
|
180
208
|
</div>
|
|
181
209
|
</td>
|
|
182
|
-
|
|
183
|
-
<span class="{% if item.usage.draft > 0 %}count-draft{% else %}zero-usage{% endif %}">{{ item.usage.draft }}</span>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{{ item.usage.total }}
|
|
192
|
-
</a>
|
|
193
|
-
{% else %}
|
|
194
|
-
<span class="zero-usage">0</span>
|
|
195
|
-
{% endif %}
|
|
196
|
-
</td>
|
|
210
|
+
{% if usage_requested %}
|
|
211
|
+
<td class="col-centered" data-sort="{{ item.usage.draft }}"><span class="{% if item.usage.draft > 0 %}count-draft{% else %}zero-usage{% endif %}">{{ item.usage.draft }}</span></td>
|
|
212
|
+
<td class="col-centered" data-sort="{{ item.usage.live }}"><span class="{% if item.usage.live > 0 %}count-live{% else %}zero-usage{% endif %}">{{ item.usage.live }}</span></td>
|
|
213
|
+
<td class="col-centered" data-sort="{{ item.usage.total }}">{% if item.usage.total > 0 %}<a href="{% url 'dev_definition_usage' item.definition_id %}{% if item.site_id %}?site={{ item.site_id }}{% endif %}" class="button button-small usage-link">{{ item.usage.total }}</a>{% else %}<span class="zero-usage">0</span>{% endif %}</td>
|
|
214
|
+
{% else %}
|
|
215
|
+
<td class="col-centered"><span class="template-empty">—</span></td>
|
|
216
|
+
<td class="col-centered"><span class="template-empty">—</span></td>
|
|
217
|
+
<td class="col-centered"><span class="template-empty">Add min or max</span></td>
|
|
218
|
+
{% endif %}
|
|
197
219
|
<td>
|
|
198
220
|
{% if item.template %}
|
|
199
221
|
<span class="template-label" title="{{ item.template }}">{{ item.template_label }}</span>
|
|
@@ -206,12 +228,36 @@
|
|
|
206
228
|
{% endfor %}
|
|
207
229
|
</tbody>
|
|
208
230
|
</table>
|
|
231
|
+
{% if page_obj.paginator.num_pages > 1 %}
|
|
232
|
+
<nav class="pagination" aria-label="Registry result pages">
|
|
233
|
+
{% if page_obj.has_previous %}<a class="button" href="?apply=1&q={{ filters.q|urlencode }}&type={{ filters.type|urlencode }}&site={{ filters.site|urlencode }}&min_usage={{ filters.min_usage|urlencode }}&max_usage={{ filters.max_usage|urlencode }}&definition={{ filters.definition|urlencode }}&page={{ page_obj.previous_page_number }}">Previous</a>{% endif %}
|
|
234
|
+
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} ({{ page_obj.paginator.count }} results)</span>
|
|
235
|
+
{% if page_obj.has_next %}<a class="button" href="?apply=1&q={{ filters.q|urlencode }}&type={{ filters.type|urlencode }}&site={{ filters.site|urlencode }}&min_usage={{ filters.min_usage|urlencode }}&max_usage={{ filters.max_usage|urlencode }}&definition={{ filters.definition|urlencode }}&page={{ page_obj.next_page_number }}">Next</a>{% endif %}
|
|
236
|
+
</nav>
|
|
237
|
+
{% endif %}
|
|
209
238
|
{% else %}
|
|
210
239
|
<p class="help-block">Choose filters and select <strong>Apply Filters</strong> to load registry usage.</p>
|
|
211
240
|
{% endif %}
|
|
212
241
|
</div>
|
|
213
242
|
|
|
214
243
|
<script>
|
|
244
|
+
const usageHelp = document.getElementById("min-usage-help");
|
|
245
|
+
const usageTooltip = document.getElementById("min-usage-tooltip");
|
|
246
|
+
["mouseenter", "focus", "click"].forEach(eventName => {
|
|
247
|
+
usageHelp.addEventListener(eventName, () => {
|
|
248
|
+
usageTooltip.hidden = false;
|
|
249
|
+
usageTooltip.style.transform = "";
|
|
250
|
+
const tooltipLeft = usageTooltip.getBoundingClientRect().left;
|
|
251
|
+
if (tooltipLeft < 16) {
|
|
252
|
+
usageTooltip.style.transform = `translateX(${16 - tooltipLeft}px)`;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
window.addEventListener("resize", () => { usageTooltip.hidden = true; });
|
|
257
|
+
document.addEventListener("keydown", event => {
|
|
258
|
+
if (event.key === "Escape") usageTooltip.hidden = true;
|
|
259
|
+
});
|
|
260
|
+
|
|
215
261
|
let currentSortColumn = -1;
|
|
216
262
|
let isSortAscending = true;
|
|
217
263
|
|
|
@@ -273,41 +319,76 @@
|
|
|
273
319
|
|
|
274
320
|
const quickSearchInput = document.getElementById("quick-search-filter");
|
|
275
321
|
const quickSearchStatus = document.getElementById("quick-search-status");
|
|
276
|
-
const
|
|
277
|
-
const
|
|
322
|
+
const quickSearchList = document.querySelector(".quick-search-list");
|
|
323
|
+
const definitionFilter = document.getElementById("definition-filter");
|
|
324
|
+
let quickSearchTimer;
|
|
325
|
+
let quickSearchController;
|
|
326
|
+
|
|
327
|
+
function selectQuickItem(button) {
|
|
328
|
+
document.getElementById("reg-search").value = button.dataset.search;
|
|
329
|
+
document.getElementById("reg-filter").value = button.dataset.type;
|
|
330
|
+
definitionFilter.value = button.dataset.definition;
|
|
331
|
+
document.getElementById("registry-filter-form").submit();
|
|
332
|
+
}
|
|
278
333
|
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
quickSearchItems.forEach(button => {
|
|
283
|
-
const matchesQuery = button.dataset.quickSearch.toLowerCase().includes(query);
|
|
284
|
-
const withinInitialLimit = query || Number(button.dataset.quickIndex) < initialQuickSearchLimit;
|
|
285
|
-
const isVisible = matchesQuery && withinInitialLimit;
|
|
286
|
-
button.hidden = !isVisible;
|
|
287
|
-
if (matchesQuery) matches += 1;
|
|
334
|
+
function bindQuickItems() {
|
|
335
|
+
quickSearchList.querySelectorAll(".quick-search-item").forEach(button => {
|
|
336
|
+
button.addEventListener("click", () => selectQuickItem(button));
|
|
288
337
|
});
|
|
289
|
-
quickSearchStatus.textContent = query
|
|
290
|
-
? `${matches} matching component${matches === 1 ? "" : "s"}`
|
|
291
|
-
: `Showing the first ${Math.min(initialQuickSearchLimit, matches)} of ${matches} components. Type to narrow the list.`;
|
|
292
338
|
}
|
|
293
339
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
document.
|
|
298
|
-
|
|
340
|
+
function renderQuickItems(results, total) {
|
|
341
|
+
quickSearchList.replaceChildren();
|
|
342
|
+
results.forEach(item => {
|
|
343
|
+
const button = document.createElement("button");
|
|
344
|
+
button.type = "button";
|
|
345
|
+
button.className = "quick-search-item";
|
|
346
|
+
button.dataset.search = item.name;
|
|
347
|
+
button.dataset.type = item.type;
|
|
348
|
+
button.dataset.definition = item.id;
|
|
349
|
+
const badge = document.createElement("span");
|
|
350
|
+
badge.className = `type-tag type-tag--${item.type.toLowerCase()}`;
|
|
351
|
+
badge.textContent = item.type;
|
|
352
|
+
button.append(badge, document.createTextNode(item.name));
|
|
353
|
+
quickSearchList.appendChild(button);
|
|
299
354
|
});
|
|
355
|
+
quickSearchStatus.textContent = total > results.length
|
|
356
|
+
? `Showing ${results.length} of ${total} matching components.`
|
|
357
|
+
: `${total} matching component${total === 1 ? "" : "s"}.`;
|
|
358
|
+
bindQuickItems();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function loadQuickItems() {
|
|
362
|
+
if (quickSearchController) quickSearchController.abort();
|
|
363
|
+
quickSearchController = new AbortController();
|
|
364
|
+
const query = quickSearchInput.value.trim();
|
|
365
|
+
const params = new URLSearchParams({q: query, limit: "30"});
|
|
366
|
+
try {
|
|
367
|
+
const response = await fetch(`{% url 'dev_catalog_search' %}?${params}`, {
|
|
368
|
+
headers: {"X-Requested-With": "XMLHttpRequest"},
|
|
369
|
+
signal: quickSearchController.signal,
|
|
370
|
+
});
|
|
371
|
+
if (!response.ok) throw new Error("Catalog search failed");
|
|
372
|
+
const data = await response.json();
|
|
373
|
+
renderQuickItems(data.results, data.total);
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (error.name !== "AbortError") {
|
|
376
|
+
quickSearchStatus.textContent = "Could not load components.";
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
bindQuickItems();
|
|
382
|
+
quickSearchStatus.textContent = "Showing the first 30 components. Type to search all definitions.";
|
|
383
|
+
quickSearchInput.addEventListener("input", () => {
|
|
384
|
+
window.clearTimeout(quickSearchTimer);
|
|
385
|
+
quickSearchTimer = window.setTimeout(loadQuickItems, 200);
|
|
386
|
+
});
|
|
387
|
+
document.getElementById("reg-search").addEventListener("input", () => {
|
|
388
|
+
definitionFilter.value = "";
|
|
300
389
|
});
|
|
301
|
-
quickSearchInput.addEventListener("input", runQuickSearch);
|
|
302
390
|
document.querySelector(".quick-search").addEventListener("toggle", event => {
|
|
303
391
|
if (event.target.open) quickSearchInput.focus();
|
|
304
392
|
});
|
|
305
|
-
runQuickSearch();
|
|
306
|
-
|
|
307
|
-
if (document.getElementById("registry-table")) {
|
|
308
|
-
document.getElementById("reg-search").addEventListener("input", runFilters);
|
|
309
|
-
document.getElementById("reg-filter").addEventListener("change", runFilters);
|
|
310
|
-
document.getElementById("reg-site-filter").addEventListener("change", runFilters);
|
|
311
|
-
}
|
|
312
393
|
</script>
|
|
313
394
|
{% endblock %}
|
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from django.test import SimpleTestCase, TestCase
|
|
2
|
+
from wagtail.blocks import CharBlock, StreamBlock, StructBlock
|
|
3
|
+
from wagtail.models import Page, Site
|
|
4
|
+
|
|
5
|
+
from wagtail_developer_reference.wagtail_hooks import WagtailAuditor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TimelineBlock(StructBlock):
|
|
9
|
+
title = CharBlock(required=False)
|
|
10
|
+
content = StreamBlock(
|
|
11
|
+
[
|
|
12
|
+
("logo", CharBlock(required=False)),
|
|
13
|
+
("image", CharBlock(required=False)),
|
|
14
|
+
],
|
|
15
|
+
required=False,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DefinitionPathTests(SimpleTestCase):
|
|
20
|
+
def setUp(self):
|
|
21
|
+
self.auditor = object.__new__(WagtailAuditor)
|
|
22
|
+
self.root_block = StreamBlock([("timeline", TimelineBlock())])
|
|
23
|
+
|
|
24
|
+
def test_raw_nested_stream_paths_are_exact_and_unique(self):
|
|
25
|
+
value = [
|
|
26
|
+
{
|
|
27
|
+
"type": "timeline",
|
|
28
|
+
"value": {
|
|
29
|
+
"title": "First",
|
|
30
|
+
"content": [
|
|
31
|
+
{"type": "logo", "value": "A"},
|
|
32
|
+
{"type": "image", "value": "B"},
|
|
33
|
+
{"type": "image", "value": "C"},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
present = set()
|
|
39
|
+
|
|
40
|
+
self.auditor._collect_present_paths(self.root_block, value, (), present)
|
|
41
|
+
|
|
42
|
+
self.assertIn(("timeline",), present)
|
|
43
|
+
self.assertIn(("timeline", "title"), present)
|
|
44
|
+
self.assertIn(("timeline", "content", "logo"), present)
|
|
45
|
+
self.assertIn(("timeline", "content", "image"), present)
|
|
46
|
+
self.assertEqual(
|
|
47
|
+
len([path for path in present if path[-1] == "image"]), 1
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def test_definition_id_includes_model_field_and_path(self):
|
|
51
|
+
first = WagtailAuditor._definition_id(
|
|
52
|
+
"BLOCK", "demo.articlepage", "body", ("timeline", "title")
|
|
53
|
+
)
|
|
54
|
+
same = WagtailAuditor._definition_id(
|
|
55
|
+
"BLOCK", "demo.articlepage", "body", ("timeline", "title")
|
|
56
|
+
)
|
|
57
|
+
other = WagtailAuditor._definition_id(
|
|
58
|
+
"BLOCK", "demo.homepage", "body", ("timeline", "title")
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.assertEqual(first, same)
|
|
62
|
+
self.assertNotEqual(first, other)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SiteScopeTests(TestCase):
|
|
66
|
+
def test_site_root_is_included_and_nested_site_is_excluded(self):
|
|
67
|
+
tree_root = Page.get_first_root_node()
|
|
68
|
+
site_root = tree_root.add_child(
|
|
69
|
+
instance=Page(title="Package test site", slug="package-test-site")
|
|
70
|
+
)
|
|
71
|
+
included_child = site_root.add_child(
|
|
72
|
+
instance=Page(title="Included", slug="included")
|
|
73
|
+
)
|
|
74
|
+
nested_root = site_root.add_child(
|
|
75
|
+
instance=Page(title="Nested site", slug="nested-site")
|
|
76
|
+
)
|
|
77
|
+
nested_child = nested_root.add_child(
|
|
78
|
+
instance=Page(title="Nested child", slug="nested-child")
|
|
79
|
+
)
|
|
80
|
+
site = Site.objects.create(
|
|
81
|
+
hostname="package-test.example",
|
|
82
|
+
port=8123,
|
|
83
|
+
root_page=site_root,
|
|
84
|
+
)
|
|
85
|
+
Site.objects.create(
|
|
86
|
+
hostname="nested-package-test.example",
|
|
87
|
+
port=8124,
|
|
88
|
+
root_page=nested_root,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
page_ids = set(
|
|
92
|
+
WagtailAuditor()._scope_page_queryset(site).values_list(
|
|
93
|
+
"pk", flat=True
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.assertIn(site_root.pk, page_ids)
|
|
98
|
+
self.assertIn(included_child.pk, page_ids)
|
|
99
|
+
self.assertNotIn(nested_root.pk, page_ids)
|
|
100
|
+
self.assertNotIn(nested_child.pk, page_ids)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from django.contrib.auth import get_user_model
|
|
2
|
+
from django.contrib.auth.models import Permission
|
|
3
|
+
from django.test import TestCase
|
|
4
|
+
from django.urls import reverse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RegistryHelpTests(TestCase):
|
|
8
|
+
def test_min_usage_help_is_available_without_applying_filters(self):
|
|
9
|
+
user = get_user_model().objects.create_superuser(
|
|
10
|
+
username="registry-admin", email="admin@example.com", password="password"
|
|
11
|
+
)
|
|
12
|
+
user.user_permissions.add(
|
|
13
|
+
Permission.objects.get(
|
|
14
|
+
content_type__app_label="wagtail_developer_reference",
|
|
15
|
+
codename="access_developer_reference",
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
self.client.force_login(user)
|
|
19
|
+
|
|
20
|
+
response = self.client.get(reverse("dev_reference"))
|
|
21
|
+
|
|
22
|
+
self.assertContains(response, '<label for="min-usage">Min usage</label>')
|
|
23
|
+
self.assertContains(
|
|
24
|
+
response,
|
|
25
|
+
'<button id="min-usage-help" class="usage-help-icon" type="button" '
|
|
26
|
+
'aria-label="About Min usage" aria-describedby="min-usage-tooltip">'
|
|
27
|
+
'<span aria-hidden="true">i</span></button>',
|
|
28
|
+
html=True,
|
|
29
|
+
)
|
|
30
|
+
self.assertContains(
|
|
31
|
+
response,
|
|
32
|
+
'<span id="min-usage-tooltip" class="usage-help-tooltip" role="tooltip">'
|
|
33
|
+
"Usage counts for pages, blocks, images and documents are only shown "
|
|
34
|
+
"when Min usage is filled in. Enter 0 to include items with no usage, "
|
|
35
|
+
"then apply the filters.</span>",
|
|
36
|
+
html=True,
|
|
37
|
+
)
|