django-subadmin-unfold 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.
- django_subadmin_unfold-1.0.0/LICENSE +21 -0
- django_subadmin_unfold-1.0.0/MANIFEST.in +2 -0
- django_subadmin_unfold-1.0.0/PKG-INFO +197 -0
- django_subadmin_unfold-1.0.0/README.md +178 -0
- django_subadmin_unfold-1.0.0/django_subadmin_unfold.egg-info/PKG-INFO +197 -0
- django_subadmin_unfold-1.0.0/django_subadmin_unfold.egg-info/SOURCES.txt +27 -0
- django_subadmin_unfold-1.0.0/django_subadmin_unfold.egg-info/dependency_links.txt +1 -0
- django_subadmin_unfold-1.0.0/django_subadmin_unfold.egg-info/requires.txt +3 -0
- django_subadmin_unfold-1.0.0/django_subadmin_unfold.egg-info/top_level.txt +1 -0
- django_subadmin_unfold-1.0.0/pyproject.toml +31 -0
- django_subadmin_unfold-1.0.0/setup.cfg +4 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/__init__.py +0 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/admin.py +182 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/apps.py +5 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/datasets.py +35 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/change_form.html +28 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/change_list.html +14 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/delete_confirmation.html +2 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/delete_selected_confirmation.html +2 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/header.html +52 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/object_history.html +2 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/parent_change_form.html +9 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/subadmin_links.html +3 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/subadmin_unfold/submit_line.html +35 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templates/unfold/helpers/actions_row.html +56 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templatetags/__init__.py +0 -0
- django_subadmin_unfold-1.0.0/subadmin_unfold/templatetags/subadmin_unfold.py +70 -0
- django_subadmin_unfold-1.0.0/tests/test_datasets.py +156 -0
- django_subadmin_unfold-1.0.0/tests/test_pages.py +334 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mitja Pagon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-subadmin-unfold
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Django Unfold integration for django-subadmin
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Framework :: Django :: 5.2
|
|
7
|
+
Classifier: Framework :: Django :: 6.0
|
|
8
|
+
Classifier: Framework :: Django :: 6.1
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: Django>=5.2
|
|
16
|
+
Requires-Dist: django-subadmin>=5.2
|
|
17
|
+
Requires-Dist: django-unfold>=0.102
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# django-subadmin-unfold
|
|
21
|
+
|
|
22
|
+
`django-subadmin-unfold` makes
|
|
23
|
+
[django-subadmin](https://github.com/inueni/django-subadmin)'s nested model
|
|
24
|
+
admins feel at home in [Django Unfold](https://unfoldadmin.com/), from
|
|
25
|
+
navigation and breadcrumbs to custom actions.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```console
|
|
30
|
+
pip install django-subadmin-unfold
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The package requires Python 3.12+, Django 5.2+, django-subadmin 5.2+, and
|
|
34
|
+
django-unfold 0.102+. It has been tested with Django 5.2, 6.0, and 6.1. Add
|
|
35
|
+
the apps in this order so the adapter's templates are used:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# settings.py
|
|
39
|
+
INSTALLED_APPS = [
|
|
40
|
+
"subadmin_unfold",
|
|
41
|
+
"unfold",
|
|
42
|
+
"subadmin",
|
|
43
|
+
"django.contrib.admin",
|
|
44
|
+
# Django's other contrib apps and your own apps...
|
|
45
|
+
]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Using it
|
|
49
|
+
|
|
50
|
+
Start with the [django-subadmin example](https://github.com/inueni/django-subadmin#example).
|
|
51
|
+
The model relationships are the same. Use `UnfoldSubAdmin` for nested admins
|
|
52
|
+
and `UnfoldRootSubAdmin` for the registered parent admin:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from django.contrib import admin
|
|
56
|
+
from subadmin_unfold.admin import UnfoldRootSubAdmin, UnfoldSubAdmin
|
|
57
|
+
|
|
58
|
+
from .models import Child, Parent
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
62
|
+
model = Child
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@admin.register(Parent)
|
|
66
|
+
class ParentAdmin(UnfoldRootSubAdmin):
|
|
67
|
+
subadmins = (ChildAdmin,)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The nested list and change pages use Unfold's templates and breadcrumbs.
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
## Grouping subadmin links
|
|
75
|
+
|
|
76
|
+
Django admin has no subadmin links, and django-subadmin displays each link
|
|
77
|
+
separately. This adapter lets you group links into dropdowns, following
|
|
78
|
+
Unfold's dropdown actions pattern. Here **Children** stays as a direct link,
|
|
79
|
+
while **Notes** and **Documents** sit under **Related**:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
subadmins = (
|
|
83
|
+
ChildAdmin,
|
|
84
|
+
{
|
|
85
|
+
"title": "Related",
|
|
86
|
+
"icon": "folder",
|
|
87
|
+
"items": (NoteAdmin, DocumentAdmin),
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+

|
|
93
|
+
|
|
94
|
+
`NoteAdmin` and `DocumentAdmin` are defined like `ChildAdmin`. Groups work on
|
|
95
|
+
root and nested admins. A group with no permitted links is hidden; one
|
|
96
|
+
permitted link still appears in a dropdown.
|
|
97
|
+
|
|
98
|
+
## Icons
|
|
99
|
+
|
|
100
|
+
Set `subadmin_icon` on a nested admin to give its link a
|
|
101
|
+
[Material Symbols](https://fonts.google.com/icons) icon:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
105
|
+
model = Child
|
|
106
|
+
subadmin_icon = "groups"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The default link icon is `view_list`. To change it for all subadmins, set
|
|
110
|
+
`SUBADMIN_UNFOLD_DEFAULT_ICON` in your Django settings:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
SUBADMIN_UNFOLD_DEFAULT_ICON = "list_alt"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A class's `subadmin_icon` takes precedence over this setting. Both work for
|
|
117
|
+
direct links and links inside groups. Set a dropdown's icon with the group's
|
|
118
|
+
`icon` key, as above.
|
|
119
|
+
|
|
120
|
+
## Datasets
|
|
121
|
+
|
|
122
|
+
[Unfold datasets](https://unfoldadmin.com/docs/configuration/datasets/) display
|
|
123
|
+
a changelist inside a change form. Regular `BaseDataset` works on subadmin
|
|
124
|
+
pages. Use `SubAdminDataset` when the dataset's rows should link to a direct
|
|
125
|
+
child subadmin's change pages.
|
|
126
|
+
|
|
127
|
+
The following setup shows Children on a Parent change form. It replaces the
|
|
128
|
+
short `admin.py` example above. `ChildAdmin` provides the full nested pages,
|
|
129
|
+
`ChildDatasetAdmin` configures and filters the embedded table, and
|
|
130
|
+
`ChildDataset` points table rows to those nested pages:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from django.contrib import admin
|
|
134
|
+
from django.contrib.admin.utils import unquote
|
|
135
|
+
from subadmin_unfold.admin import UnfoldRootSubAdmin, UnfoldSubAdmin
|
|
136
|
+
from subadmin_unfold.datasets import SubAdminDataset
|
|
137
|
+
from unfold.admin import ModelAdmin
|
|
138
|
+
|
|
139
|
+
from .models import Child, Parent
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
143
|
+
model = Child
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ChildDatasetAdmin(ModelAdmin):
|
|
147
|
+
list_display = ("name",)
|
|
148
|
+
|
|
149
|
+
def get_queryset(self, request):
|
|
150
|
+
queryset = super().get_queryset(request)
|
|
151
|
+
parent_id = (self.extra_context or {}).get("object")
|
|
152
|
+
if not parent_id or not self.has_view_or_change_permission(request):
|
|
153
|
+
return queryset.none()
|
|
154
|
+
return queryset.filter(parent_id=unquote(parent_id))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ChildDataset(SubAdminDataset):
|
|
158
|
+
model = Child
|
|
159
|
+
model_admin = ChildDatasetAdmin
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@admin.register(Parent)
|
|
163
|
+
class ParentAdmin(UnfoldRootSubAdmin):
|
|
164
|
+
subadmins = (ChildAdmin,)
|
|
165
|
+
change_form_datasets = (ChildDataset,)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`ChildDatasetAdmin` is a separate, unregistered admin used by Unfold to render
|
|
169
|
+
the table. Its queryset must enforce parent scope and any child visibility
|
|
170
|
+
rules. `SubAdminDataset` only changes row links; it does not create detail pages
|
|
171
|
+
or inherit settings from `ChildAdmin`.
|
|
172
|
+
|
|
173
|
+
## Unfold actions
|
|
174
|
+
|
|
175
|
+
Define [Unfold actions](https://unfoldadmin.com/docs/actions/introduction/) on
|
|
176
|
+
`UnfoldSubAdmin` as you would on an Unfold `ModelAdmin`. List, detail, row, and
|
|
177
|
+
submit-line actions are supported. For example, a list action can use
|
|
178
|
+
`request.subadmin` to access the current parent:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from django.http import HttpResponse
|
|
182
|
+
from unfold.decorators import action
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
186
|
+
model = Child
|
|
187
|
+
actions_list = ("show_parent",)
|
|
188
|
+
|
|
189
|
+
@action(description="Show parent", icon="account_tree")
|
|
190
|
+
def show_parent(self, request):
|
|
191
|
+
return HttpResponse(request.subadmin.parent_instance.name)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The adapter includes the parent IDs in nested action URLs. Nested admins can
|
|
195
|
+
also define `get_custom_urls()`; those views receive `model_admin` and
|
|
196
|
+
`request.subadmin`. Reverse a nested custom URL with
|
|
197
|
+
`model_admin.reverse_url(name, *parent_ids, *custom_args)`.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# django-subadmin-unfold
|
|
2
|
+
|
|
3
|
+
`django-subadmin-unfold` makes
|
|
4
|
+
[django-subadmin](https://github.com/inueni/django-subadmin)'s nested model
|
|
5
|
+
admins feel at home in [Django Unfold](https://unfoldadmin.com/), from
|
|
6
|
+
navigation and breadcrumbs to custom actions.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```console
|
|
11
|
+
pip install django-subadmin-unfold
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The package requires Python 3.12+, Django 5.2+, django-subadmin 5.2+, and
|
|
15
|
+
django-unfold 0.102+. It has been tested with Django 5.2, 6.0, and 6.1. Add
|
|
16
|
+
the apps in this order so the adapter's templates are used:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
# settings.py
|
|
20
|
+
INSTALLED_APPS = [
|
|
21
|
+
"subadmin_unfold",
|
|
22
|
+
"unfold",
|
|
23
|
+
"subadmin",
|
|
24
|
+
"django.contrib.admin",
|
|
25
|
+
# Django's other contrib apps and your own apps...
|
|
26
|
+
]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Using it
|
|
30
|
+
|
|
31
|
+
Start with the [django-subadmin example](https://github.com/inueni/django-subadmin#example).
|
|
32
|
+
The model relationships are the same. Use `UnfoldSubAdmin` for nested admins
|
|
33
|
+
and `UnfoldRootSubAdmin` for the registered parent admin:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from django.contrib import admin
|
|
37
|
+
from subadmin_unfold.admin import UnfoldRootSubAdmin, UnfoldSubAdmin
|
|
38
|
+
|
|
39
|
+
from .models import Child, Parent
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
43
|
+
model = Child
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@admin.register(Parent)
|
|
47
|
+
class ParentAdmin(UnfoldRootSubAdmin):
|
|
48
|
+
subadmins = (ChildAdmin,)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The nested list and change pages use Unfold's templates and breadcrumbs.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
## Grouping subadmin links
|
|
56
|
+
|
|
57
|
+
Django admin has no subadmin links, and django-subadmin displays each link
|
|
58
|
+
separately. This adapter lets you group links into dropdowns, following
|
|
59
|
+
Unfold's dropdown actions pattern. Here **Children** stays as a direct link,
|
|
60
|
+
while **Notes** and **Documents** sit under **Related**:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
subadmins = (
|
|
64
|
+
ChildAdmin,
|
|
65
|
+
{
|
|
66
|
+
"title": "Related",
|
|
67
|
+
"icon": "folder",
|
|
68
|
+
"items": (NoteAdmin, DocumentAdmin),
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
`NoteAdmin` and `DocumentAdmin` are defined like `ChildAdmin`. Groups work on
|
|
76
|
+
root and nested admins. A group with no permitted links is hidden; one
|
|
77
|
+
permitted link still appears in a dropdown.
|
|
78
|
+
|
|
79
|
+
## Icons
|
|
80
|
+
|
|
81
|
+
Set `subadmin_icon` on a nested admin to give its link a
|
|
82
|
+
[Material Symbols](https://fonts.google.com/icons) icon:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
86
|
+
model = Child
|
|
87
|
+
subadmin_icon = "groups"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The default link icon is `view_list`. To change it for all subadmins, set
|
|
91
|
+
`SUBADMIN_UNFOLD_DEFAULT_ICON` in your Django settings:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
SUBADMIN_UNFOLD_DEFAULT_ICON = "list_alt"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A class's `subadmin_icon` takes precedence over this setting. Both work for
|
|
98
|
+
direct links and links inside groups. Set a dropdown's icon with the group's
|
|
99
|
+
`icon` key, as above.
|
|
100
|
+
|
|
101
|
+
## Datasets
|
|
102
|
+
|
|
103
|
+
[Unfold datasets](https://unfoldadmin.com/docs/configuration/datasets/) display
|
|
104
|
+
a changelist inside a change form. Regular `BaseDataset` works on subadmin
|
|
105
|
+
pages. Use `SubAdminDataset` when the dataset's rows should link to a direct
|
|
106
|
+
child subadmin's change pages.
|
|
107
|
+
|
|
108
|
+
The following setup shows Children on a Parent change form. It replaces the
|
|
109
|
+
short `admin.py` example above. `ChildAdmin` provides the full nested pages,
|
|
110
|
+
`ChildDatasetAdmin` configures and filters the embedded table, and
|
|
111
|
+
`ChildDataset` points table rows to those nested pages:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from django.contrib import admin
|
|
115
|
+
from django.contrib.admin.utils import unquote
|
|
116
|
+
from subadmin_unfold.admin import UnfoldRootSubAdmin, UnfoldSubAdmin
|
|
117
|
+
from subadmin_unfold.datasets import SubAdminDataset
|
|
118
|
+
from unfold.admin import ModelAdmin
|
|
119
|
+
|
|
120
|
+
from .models import Child, Parent
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
124
|
+
model = Child
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ChildDatasetAdmin(ModelAdmin):
|
|
128
|
+
list_display = ("name",)
|
|
129
|
+
|
|
130
|
+
def get_queryset(self, request):
|
|
131
|
+
queryset = super().get_queryset(request)
|
|
132
|
+
parent_id = (self.extra_context or {}).get("object")
|
|
133
|
+
if not parent_id or not self.has_view_or_change_permission(request):
|
|
134
|
+
return queryset.none()
|
|
135
|
+
return queryset.filter(parent_id=unquote(parent_id))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ChildDataset(SubAdminDataset):
|
|
139
|
+
model = Child
|
|
140
|
+
model_admin = ChildDatasetAdmin
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@admin.register(Parent)
|
|
144
|
+
class ParentAdmin(UnfoldRootSubAdmin):
|
|
145
|
+
subadmins = (ChildAdmin,)
|
|
146
|
+
change_form_datasets = (ChildDataset,)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`ChildDatasetAdmin` is a separate, unregistered admin used by Unfold to render
|
|
150
|
+
the table. Its queryset must enforce parent scope and any child visibility
|
|
151
|
+
rules. `SubAdminDataset` only changes row links; it does not create detail pages
|
|
152
|
+
or inherit settings from `ChildAdmin`.
|
|
153
|
+
|
|
154
|
+
## Unfold actions
|
|
155
|
+
|
|
156
|
+
Define [Unfold actions](https://unfoldadmin.com/docs/actions/introduction/) on
|
|
157
|
+
`UnfoldSubAdmin` as you would on an Unfold `ModelAdmin`. List, detail, row, and
|
|
158
|
+
submit-line actions are supported. For example, a list action can use
|
|
159
|
+
`request.subadmin` to access the current parent:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from django.http import HttpResponse
|
|
163
|
+
from unfold.decorators import action
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
167
|
+
model = Child
|
|
168
|
+
actions_list = ("show_parent",)
|
|
169
|
+
|
|
170
|
+
@action(description="Show parent", icon="account_tree")
|
|
171
|
+
def show_parent(self, request):
|
|
172
|
+
return HttpResponse(request.subadmin.parent_instance.name)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The adapter includes the parent IDs in nested action URLs. Nested admins can
|
|
176
|
+
also define `get_custom_urls()`; those views receive `model_admin` and
|
|
177
|
+
`request.subadmin`. Reverse a nested custom URL with
|
|
178
|
+
`model_admin.reverse_url(name, *parent_ids, *custom_args)`.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-subadmin-unfold
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Django Unfold integration for django-subadmin
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Framework :: Django :: 5.2
|
|
7
|
+
Classifier: Framework :: Django :: 6.0
|
|
8
|
+
Classifier: Framework :: Django :: 6.1
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: Django>=5.2
|
|
16
|
+
Requires-Dist: django-subadmin>=5.2
|
|
17
|
+
Requires-Dist: django-unfold>=0.102
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# django-subadmin-unfold
|
|
21
|
+
|
|
22
|
+
`django-subadmin-unfold` makes
|
|
23
|
+
[django-subadmin](https://github.com/inueni/django-subadmin)'s nested model
|
|
24
|
+
admins feel at home in [Django Unfold](https://unfoldadmin.com/), from
|
|
25
|
+
navigation and breadcrumbs to custom actions.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```console
|
|
30
|
+
pip install django-subadmin-unfold
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The package requires Python 3.12+, Django 5.2+, django-subadmin 5.2+, and
|
|
34
|
+
django-unfold 0.102+. It has been tested with Django 5.2, 6.0, and 6.1. Add
|
|
35
|
+
the apps in this order so the adapter's templates are used:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# settings.py
|
|
39
|
+
INSTALLED_APPS = [
|
|
40
|
+
"subadmin_unfold",
|
|
41
|
+
"unfold",
|
|
42
|
+
"subadmin",
|
|
43
|
+
"django.contrib.admin",
|
|
44
|
+
# Django's other contrib apps and your own apps...
|
|
45
|
+
]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Using it
|
|
49
|
+
|
|
50
|
+
Start with the [django-subadmin example](https://github.com/inueni/django-subadmin#example).
|
|
51
|
+
The model relationships are the same. Use `UnfoldSubAdmin` for nested admins
|
|
52
|
+
and `UnfoldRootSubAdmin` for the registered parent admin:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from django.contrib import admin
|
|
56
|
+
from subadmin_unfold.admin import UnfoldRootSubAdmin, UnfoldSubAdmin
|
|
57
|
+
|
|
58
|
+
from .models import Child, Parent
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
62
|
+
model = Child
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@admin.register(Parent)
|
|
66
|
+
class ParentAdmin(UnfoldRootSubAdmin):
|
|
67
|
+
subadmins = (ChildAdmin,)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The nested list and change pages use Unfold's templates and breadcrumbs.
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
## Grouping subadmin links
|
|
75
|
+
|
|
76
|
+
Django admin has no subadmin links, and django-subadmin displays each link
|
|
77
|
+
separately. This adapter lets you group links into dropdowns, following
|
|
78
|
+
Unfold's dropdown actions pattern. Here **Children** stays as a direct link,
|
|
79
|
+
while **Notes** and **Documents** sit under **Related**:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
subadmins = (
|
|
83
|
+
ChildAdmin,
|
|
84
|
+
{
|
|
85
|
+
"title": "Related",
|
|
86
|
+
"icon": "folder",
|
|
87
|
+
"items": (NoteAdmin, DocumentAdmin),
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+

|
|
93
|
+
|
|
94
|
+
`NoteAdmin` and `DocumentAdmin` are defined like `ChildAdmin`. Groups work on
|
|
95
|
+
root and nested admins. A group with no permitted links is hidden; one
|
|
96
|
+
permitted link still appears in a dropdown.
|
|
97
|
+
|
|
98
|
+
## Icons
|
|
99
|
+
|
|
100
|
+
Set `subadmin_icon` on a nested admin to give its link a
|
|
101
|
+
[Material Symbols](https://fonts.google.com/icons) icon:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
105
|
+
model = Child
|
|
106
|
+
subadmin_icon = "groups"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The default link icon is `view_list`. To change it for all subadmins, set
|
|
110
|
+
`SUBADMIN_UNFOLD_DEFAULT_ICON` in your Django settings:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
SUBADMIN_UNFOLD_DEFAULT_ICON = "list_alt"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A class's `subadmin_icon` takes precedence over this setting. Both work for
|
|
117
|
+
direct links and links inside groups. Set a dropdown's icon with the group's
|
|
118
|
+
`icon` key, as above.
|
|
119
|
+
|
|
120
|
+
## Datasets
|
|
121
|
+
|
|
122
|
+
[Unfold datasets](https://unfoldadmin.com/docs/configuration/datasets/) display
|
|
123
|
+
a changelist inside a change form. Regular `BaseDataset` works on subadmin
|
|
124
|
+
pages. Use `SubAdminDataset` when the dataset's rows should link to a direct
|
|
125
|
+
child subadmin's change pages.
|
|
126
|
+
|
|
127
|
+
The following setup shows Children on a Parent change form. It replaces the
|
|
128
|
+
short `admin.py` example above. `ChildAdmin` provides the full nested pages,
|
|
129
|
+
`ChildDatasetAdmin` configures and filters the embedded table, and
|
|
130
|
+
`ChildDataset` points table rows to those nested pages:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from django.contrib import admin
|
|
134
|
+
from django.contrib.admin.utils import unquote
|
|
135
|
+
from subadmin_unfold.admin import UnfoldRootSubAdmin, UnfoldSubAdmin
|
|
136
|
+
from subadmin_unfold.datasets import SubAdminDataset
|
|
137
|
+
from unfold.admin import ModelAdmin
|
|
138
|
+
|
|
139
|
+
from .models import Child, Parent
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
143
|
+
model = Child
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ChildDatasetAdmin(ModelAdmin):
|
|
147
|
+
list_display = ("name",)
|
|
148
|
+
|
|
149
|
+
def get_queryset(self, request):
|
|
150
|
+
queryset = super().get_queryset(request)
|
|
151
|
+
parent_id = (self.extra_context or {}).get("object")
|
|
152
|
+
if not parent_id or not self.has_view_or_change_permission(request):
|
|
153
|
+
return queryset.none()
|
|
154
|
+
return queryset.filter(parent_id=unquote(parent_id))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ChildDataset(SubAdminDataset):
|
|
158
|
+
model = Child
|
|
159
|
+
model_admin = ChildDatasetAdmin
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@admin.register(Parent)
|
|
163
|
+
class ParentAdmin(UnfoldRootSubAdmin):
|
|
164
|
+
subadmins = (ChildAdmin,)
|
|
165
|
+
change_form_datasets = (ChildDataset,)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`ChildDatasetAdmin` is a separate, unregistered admin used by Unfold to render
|
|
169
|
+
the table. Its queryset must enforce parent scope and any child visibility
|
|
170
|
+
rules. `SubAdminDataset` only changes row links; it does not create detail pages
|
|
171
|
+
or inherit settings from `ChildAdmin`.
|
|
172
|
+
|
|
173
|
+
## Unfold actions
|
|
174
|
+
|
|
175
|
+
Define [Unfold actions](https://unfoldadmin.com/docs/actions/introduction/) on
|
|
176
|
+
`UnfoldSubAdmin` as you would on an Unfold `ModelAdmin`. List, detail, row, and
|
|
177
|
+
submit-line actions are supported. For example, a list action can use
|
|
178
|
+
`request.subadmin` to access the current parent:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from django.http import HttpResponse
|
|
182
|
+
from unfold.decorators import action
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ChildAdmin(UnfoldSubAdmin):
|
|
186
|
+
model = Child
|
|
187
|
+
actions_list = ("show_parent",)
|
|
188
|
+
|
|
189
|
+
@action(description="Show parent", icon="account_tree")
|
|
190
|
+
def show_parent(self, request):
|
|
191
|
+
return HttpResponse(request.subadmin.parent_instance.name)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The adapter includes the parent IDs in nested action URLs. Nested admins can
|
|
195
|
+
also define `get_custom_urls()`; those views receive `model_admin` and
|
|
196
|
+
`request.subadmin`. Reverse a nested custom URL with
|
|
197
|
+
`model_admin.reverse_url(name, *parent_ids, *custom_args)`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
django_subadmin_unfold.egg-info/PKG-INFO
|
|
6
|
+
django_subadmin_unfold.egg-info/SOURCES.txt
|
|
7
|
+
django_subadmin_unfold.egg-info/dependency_links.txt
|
|
8
|
+
django_subadmin_unfold.egg-info/requires.txt
|
|
9
|
+
django_subadmin_unfold.egg-info/top_level.txt
|
|
10
|
+
subadmin_unfold/__init__.py
|
|
11
|
+
subadmin_unfold/admin.py
|
|
12
|
+
subadmin_unfold/apps.py
|
|
13
|
+
subadmin_unfold/datasets.py
|
|
14
|
+
subadmin_unfold/templates/subadmin_unfold/change_form.html
|
|
15
|
+
subadmin_unfold/templates/subadmin_unfold/change_list.html
|
|
16
|
+
subadmin_unfold/templates/subadmin_unfold/delete_confirmation.html
|
|
17
|
+
subadmin_unfold/templates/subadmin_unfold/delete_selected_confirmation.html
|
|
18
|
+
subadmin_unfold/templates/subadmin_unfold/header.html
|
|
19
|
+
subadmin_unfold/templates/subadmin_unfold/object_history.html
|
|
20
|
+
subadmin_unfold/templates/subadmin_unfold/parent_change_form.html
|
|
21
|
+
subadmin_unfold/templates/subadmin_unfold/subadmin_links.html
|
|
22
|
+
subadmin_unfold/templates/subadmin_unfold/submit_line.html
|
|
23
|
+
subadmin_unfold/templates/unfold/helpers/actions_row.html
|
|
24
|
+
subadmin_unfold/templatetags/__init__.py
|
|
25
|
+
subadmin_unfold/templatetags/subadmin_unfold.py
|
|
26
|
+
tests/test_datasets.py
|
|
27
|
+
tests/test_pages.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
subadmin_unfold
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-subadmin-unfold"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Django Unfold integration for django-subadmin"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"Django>=5.2",
|
|
15
|
+
"django-subadmin>=5.2",
|
|
16
|
+
"django-unfold>=0.102",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Framework :: Django :: 5.2",
|
|
20
|
+
"Framework :: Django :: 6.0",
|
|
21
|
+
"Framework :: Django :: 6.1",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.package-data]
|
|
28
|
+
subadmin_unfold = ["templates/**/*.html"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
line-length = 88
|
|
File without changes
|