django-subadmin 3.2.3__tar.gz → 5.2.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-5.2.0/.github/workflows/ci.yml +62 -0
- django_subadmin-5.2.0/.github/workflows/publish.yml +69 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/.github/workflows/publish_test.yml +15 -4
- django_subadmin-5.2.0/PKG-INFO +165 -0
- django_subadmin-5.2.0/README.md +136 -0
- django_subadmin-5.2.0/django_subadmin.egg-info/PKG-INFO +165 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/django_subadmin.egg-info/SOURCES.txt +19 -1
- django_subadmin-5.2.0/django_subadmin.egg-info/requires.txt +1 -0
- django_subadmin-5.2.0/django_subadmin.egg-info/scm_file_list.json +36 -0
- django_subadmin-5.2.0/django_subadmin.egg-info/scm_version.json +8 -0
- django_subadmin-5.2.0/docs/images/child-add.png +0 -0
- django_subadmin-5.2.0/docs/images/child-list.png +0 -0
- django_subadmin-5.2.0/docs/images/parent-change.png +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/pyproject.toml +9 -6
- django_subadmin-5.2.0/subadmin/__init__.py +25 -0
- django_subadmin-5.2.0/subadmin/base.py +577 -0
- django_subadmin-5.2.0/subadmin/changelist.py +13 -0
- django_subadmin-5.2.0/subadmin/forms.py +39 -0
- django_subadmin-5.2.0/subadmin/helpers.py +58 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templates/subadmin/change_form.html +5 -4
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templates/subadmin/change_list.html +3 -3
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templates/subadmin/delete_confirmation.html +2 -2
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templates/subadmin/delete_selected_confirmation.html +2 -2
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templates/subadmin/object_history.html +2 -2
- django_subadmin-5.2.0/subadmin/templates/subadmin/submit_line.html +17 -0
- django_subadmin-5.2.0/subadmin/templatetags/subadmin_tags.py +79 -0
- django_subadmin-5.2.0/tests/__init__.py +0 -0
- django_subadmin-5.2.0/tests/settings.py +35 -0
- django_subadmin-5.2.0/tests/test_admin.py +360 -0
- django_subadmin-5.2.0/tests/testapp/__init__.py +0 -0
- django_subadmin-5.2.0/tests/testapp/admin.py +48 -0
- django_subadmin-5.2.0/tests/testapp/models.py +66 -0
- django_subadmin-5.2.0/tests/urls.py +4 -0
- django-subadmin-3.2.3/.github/workflows/publish.yml +0 -41
- django-subadmin-3.2.3/PKG-INFO +0 -136
- django-subadmin-3.2.3/README.md +0 -111
- django-subadmin-3.2.3/django_subadmin.egg-info/PKG-INFO +0 -136
- django-subadmin-3.2.3/subadmin/__init__.py +0 -578
- django-subadmin-3.2.3/subadmin/templates/subadmin/submit_line.html +0 -11
- django-subadmin-3.2.3/subadmin/templatetags/subadmin_tags.py +0 -57
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/.gitignore +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/LICENSE +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/django_subadmin.egg-info/dependency_links.txt +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/django_subadmin.egg-info/top_level.txt +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/setup.cfg +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templates/subadmin/breadcrumbs.html +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templates/subadmin/parent_change_form.html +0 -0
- {django-subadmin-3.2.3 → django_subadmin-5.2.0}/subadmin/templatetags/__init__.py +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_call:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: Django ${{ matrix.django }} / Python ${{ matrix.python }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
matrix:
|
|
18
|
+
include:
|
|
19
|
+
- django: "5.2.0"
|
|
20
|
+
python: "3.10"
|
|
21
|
+
django_spec: "Django==5.2.0"
|
|
22
|
+
- django: "5.2.x (latest)"
|
|
23
|
+
python: "3.10"
|
|
24
|
+
django_spec: "Django>=5.2,<5.3"
|
|
25
|
+
- django: "5.2.x (latest)"
|
|
26
|
+
python: "3.14"
|
|
27
|
+
django_spec: "Django>=5.2,<5.3"
|
|
28
|
+
- django: "6.0.0"
|
|
29
|
+
python: "3.12"
|
|
30
|
+
django_spec: "Django==6.0.0"
|
|
31
|
+
- django: "6.0.x (latest)"
|
|
32
|
+
python: "3.12"
|
|
33
|
+
django_spec: "Django>=6.0,<6.1"
|
|
34
|
+
- django: "6.0.x (latest)"
|
|
35
|
+
python: "3.14"
|
|
36
|
+
django_spec: "Django>=6.0,<6.1"
|
|
37
|
+
steps:
|
|
38
|
+
- name: Checkout
|
|
39
|
+
uses: actions/checkout@v7
|
|
40
|
+
with:
|
|
41
|
+
fetch-depth: 0
|
|
42
|
+
- name: Set up Python
|
|
43
|
+
uses: actions/setup-python@v7
|
|
44
|
+
with:
|
|
45
|
+
python-version: ${{ matrix.python }}
|
|
46
|
+
- name: Install package and Django
|
|
47
|
+
env:
|
|
48
|
+
DJANGO_SPEC: ${{ matrix.django_spec }}
|
|
49
|
+
run: python -m pip install -e . "$DJANGO_SPEC"
|
|
50
|
+
- name: Run integration tests
|
|
51
|
+
run: python -m django test tests --settings=tests.settings
|
|
52
|
+
|
|
53
|
+
passed:
|
|
54
|
+
name: CI passed
|
|
55
|
+
needs: test
|
|
56
|
+
if: always()
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
steps:
|
|
59
|
+
- name: Require every test job to pass
|
|
60
|
+
env:
|
|
61
|
+
TEST_RESULT: ${{ needs.test.result }}
|
|
62
|
+
run: test "$TEST_RESULT" = success
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Build and publish to PyPI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags: ["5.2.*"]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
uses: ./.github/workflows/ci.yml
|
|
12
|
+
|
|
13
|
+
build:
|
|
14
|
+
needs: test
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v7
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v7
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.10"
|
|
25
|
+
- name: Install pypa/build
|
|
26
|
+
run: python3 -m pip install --upgrade build
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: python3 -m build --sdist --wheel --outdir dist/ .
|
|
29
|
+
- name: Upload dist as artifact
|
|
30
|
+
uses: actions/upload-artifact@v7
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: ./dist
|
|
34
|
+
if-no-files-found: error
|
|
35
|
+
retention-days: 3
|
|
36
|
+
|
|
37
|
+
pypi-publish:
|
|
38
|
+
needs: ['build']
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
environment:
|
|
41
|
+
name: pypi
|
|
42
|
+
url: https://pypi.org/p/django-subadmin
|
|
43
|
+
permissions:
|
|
44
|
+
id-token: write
|
|
45
|
+
steps:
|
|
46
|
+
- name: Download artifact as dist
|
|
47
|
+
uses: actions/download-artifact@v8
|
|
48
|
+
with:
|
|
49
|
+
name: dist
|
|
50
|
+
path: dist
|
|
51
|
+
- name: Publish package
|
|
52
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
53
|
+
|
|
54
|
+
github-release:
|
|
55
|
+
needs: pypi-publish
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
permissions:
|
|
58
|
+
contents: write
|
|
59
|
+
steps:
|
|
60
|
+
- name: Download distribution files
|
|
61
|
+
uses: actions/download-artifact@v8
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist
|
|
65
|
+
- name: Create GitHub Release
|
|
66
|
+
env:
|
|
67
|
+
GH_TOKEN: ${{ github.token }}
|
|
68
|
+
GH_REPO: ${{ github.repository }}
|
|
69
|
+
run: gh release create "$GITHUB_REF_NAME" dist/* --verify-tag --notes-from-tag
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
name: Build and publish to Test PyPI
|
|
2
2
|
on: workflow_dispatch
|
|
3
3
|
|
|
4
|
+
permissions:
|
|
5
|
+
contents: read
|
|
6
|
+
|
|
4
7
|
jobs:
|
|
8
|
+
test:
|
|
9
|
+
uses: ./.github/workflows/ci.yml
|
|
10
|
+
|
|
5
11
|
build:
|
|
12
|
+
needs: test
|
|
6
13
|
runs-on: ubuntu-latest
|
|
7
14
|
steps:
|
|
8
15
|
- name: Checkout
|
|
9
|
-
uses: actions/checkout@
|
|
16
|
+
uses: actions/checkout@v7
|
|
10
17
|
with:
|
|
11
18
|
fetch-depth: 0
|
|
12
19
|
- name: Set up Python
|
|
13
|
-
uses: actions/setup-python@
|
|
20
|
+
uses: actions/setup-python@v7
|
|
14
21
|
with:
|
|
15
22
|
python-version: "3.10"
|
|
16
23
|
- name: Install pypa/build
|
|
@@ -18,10 +25,11 @@ jobs:
|
|
|
18
25
|
- name: Build package
|
|
19
26
|
run: python3 -m build --sdist --wheel --outdir dist/ .
|
|
20
27
|
- name: Upload dist as artifact
|
|
21
|
-
uses: actions/upload-artifact@
|
|
28
|
+
uses: actions/upload-artifact@v7
|
|
22
29
|
with:
|
|
23
30
|
name: dist
|
|
24
31
|
path: ./dist
|
|
32
|
+
if-no-files-found: error
|
|
25
33
|
retention-days: 1
|
|
26
34
|
|
|
27
35
|
pypi-publish:
|
|
@@ -34,7 +42,10 @@ jobs:
|
|
|
34
42
|
id-token: write
|
|
35
43
|
steps:
|
|
36
44
|
- name: Download artifact as dist
|
|
37
|
-
uses: actions/download-artifact@
|
|
45
|
+
uses: actions/download-artifact@v8
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist
|
|
38
49
|
- name: Publish package
|
|
39
50
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
40
51
|
with:
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-subadmin
|
|
3
|
+
Version: 5.2.0
|
|
4
|
+
Summary: A special kind of ModelAdmin that allows it to be nested within another ModelAdmin
|
|
5
|
+
Author-email: Mitja Pagon <mitja@inueni.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/inueni/django-subadmin/
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/inueni/django-subadmin/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/inueni/django-subadmin/releases
|
|
10
|
+
Keywords: django,admin,modeladmin,foreignkey,related field
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Framework :: Django
|
|
22
|
+
Classifier: Framework :: Django :: 5.2
|
|
23
|
+
Classifier: Framework :: Django :: 6.0
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: Django<6.1,>=5.2
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# django-subadmin
|
|
31
|
+
|
|
32
|
+
`django-subadmin` lets a `ModelAdmin` live under another `ModelAdmin`. When
|
|
33
|
+
related objects have outgrown an inline, a `SubAdmin` gives them their own list
|
|
34
|
+
and change pages, with search, filters, and pagination scoped to a parent
|
|
35
|
+
object. Subadmins can be nested several levels deep.
|
|
36
|
+
|
|
37
|
+
## Compatibility
|
|
38
|
+
|
|
39
|
+
| django-subadmin | Django | Python |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| 5.2.x | 5.2, 6.0 | 3.10+ |
|
|
42
|
+
| 3.2.x | 3.2, 4.x, 5.x | 3.6+ |
|
|
43
|
+
|
|
44
|
+
The package version follows the oldest Django version supported by that line.
|
|
45
|
+
The 5.2 release drops support for Django before 5.2 and Python before 3.10.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```console
|
|
50
|
+
pip install django-subadmin
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Add `subadmin` to `INSTALLED_APPS` so Django can find its templates:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
INSTALLED_APPS = [
|
|
57
|
+
# Django's contrib apps and your own apps...
|
|
58
|
+
"subadmin",
|
|
59
|
+
]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
The test app has a `Parent` model with related `Child` objects. Django's inline
|
|
65
|
+
admin would put the children on the parent's form; a `SubAdmin` gives each
|
|
66
|
+
parent its own child changelist instead.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# models.py
|
|
70
|
+
from django.db import models
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Parent(models.Model):
|
|
74
|
+
name = models.CharField(max_length=100)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Child(models.Model):
|
|
78
|
+
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
|
|
79
|
+
name = models.CharField(max_length=100)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# admin.py
|
|
84
|
+
from django.contrib import admin
|
|
85
|
+
from subadmin import RootSubAdmin, SubAdmin
|
|
86
|
+
|
|
87
|
+
from .models import Child, Parent
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ChildAdmin(SubAdmin):
|
|
91
|
+
model = Child
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@admin.register(Parent)
|
|
95
|
+
class ParentAdmin(RootSubAdmin):
|
|
96
|
+
subadmins = (ChildAdmin,)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Open a parent in the admin and follow the link to its child admin. The child
|
|
100
|
+
pages show only records for that parent. The parent foreign key is set
|
|
101
|
+
automatically when adding a child and omitted from the nested form.
|
|
102
|
+
|
|
103
|
+
The [test app models](tests/testapp/models.py) and
|
|
104
|
+
[admin configuration](tests/testapp/admin.py) also show deeper nesting. Their
|
|
105
|
+
workflows are covered by [integration tests](tests/test_admin.py), which you
|
|
106
|
+
can run from a source checkout with:
|
|
107
|
+
|
|
108
|
+
```console
|
|
109
|
+
python -m django test tests --settings=tests.settings
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Screenshots
|
|
113
|
+
|
|
114
|
+
The parent change page links to its child admin.
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
The child changelist contains only that parent's children.
|
|
119
|
+
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+
The child add form omits the parent foreign key, which is set automatically.
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
## Labels
|
|
127
|
+
|
|
128
|
+
Set `subadmin_label` to change a subadmin link and its collection breadcrumbs
|
|
129
|
+
without renaming the model:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
class ChildAdmin(SubAdmin):
|
|
133
|
+
model = Child
|
|
134
|
+
subadmin_label = "Members"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The default is the model's `verbose_name_plural`. Object breadcrumbs and other
|
|
138
|
+
model names keep their usual Django wording. Override
|
|
139
|
+
`get_subadmin_label(request)` if the label needs to vary by request.
|
|
140
|
+
|
|
141
|
+
## Upgrading from 3.2
|
|
142
|
+
|
|
143
|
+
Parent objects are now loaded through the parent admin's `get_object()` method,
|
|
144
|
+
so custom `get_queryset()` filters affect nested pages. Each parent in the URL
|
|
145
|
+
must also pass that admin's `has_view_or_change_permission(request, obj)` check.
|
|
146
|
+
A parent hidden by the queryset returns 404; a visible parent without permission
|
|
147
|
+
returns 403. Child permissions alone no longer grant access through a parent.
|
|
148
|
+
|
|
149
|
+
If you need the previous direct model lookup while adapting a project, set:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
SUBADMIN_USE_DIRECT_PARENT_LOOKUP = True
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This changes how parents are loaded, but does not skip the parent permission
|
|
156
|
+
check.
|
|
157
|
+
|
|
158
|
+
Custom overrides of `get_parent_instance()` and `get_subadmin_helper()` need to
|
|
159
|
+
accept `request` as their first argument after `self`. Their signatures are now
|
|
160
|
+
`get_parent_instance(self, request, parent_id)` and
|
|
161
|
+
`get_subadmin_helper(self, request, view_args, object_id=None)`.
|
|
162
|
+
|
|
163
|
+
`SubAdmin` wraps forms to validate parent-scoped fields. If you override
|
|
164
|
+
`get_form()` or `get_changelist_form()`, call `super()` so that wrapping still
|
|
165
|
+
runs.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# django-subadmin
|
|
2
|
+
|
|
3
|
+
`django-subadmin` lets a `ModelAdmin` live under another `ModelAdmin`. When
|
|
4
|
+
related objects have outgrown an inline, a `SubAdmin` gives them their own list
|
|
5
|
+
and change pages, with search, filters, and pagination scoped to a parent
|
|
6
|
+
object. Subadmins can be nested several levels deep.
|
|
7
|
+
|
|
8
|
+
## Compatibility
|
|
9
|
+
|
|
10
|
+
| django-subadmin | Django | Python |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| 5.2.x | 5.2, 6.0 | 3.10+ |
|
|
13
|
+
| 3.2.x | 3.2, 4.x, 5.x | 3.6+ |
|
|
14
|
+
|
|
15
|
+
The package version follows the oldest Django version supported by that line.
|
|
16
|
+
The 5.2 release drops support for Django before 5.2 and Python before 3.10.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```console
|
|
21
|
+
pip install django-subadmin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Add `subadmin` to `INSTALLED_APPS` so Django can find its templates:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
INSTALLED_APPS = [
|
|
28
|
+
# Django's contrib apps and your own apps...
|
|
29
|
+
"subadmin",
|
|
30
|
+
]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Example
|
|
34
|
+
|
|
35
|
+
The test app has a `Parent` model with related `Child` objects. Django's inline
|
|
36
|
+
admin would put the children on the parent's form; a `SubAdmin` gives each
|
|
37
|
+
parent its own child changelist instead.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# models.py
|
|
41
|
+
from django.db import models
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Parent(models.Model):
|
|
45
|
+
name = models.CharField(max_length=100)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Child(models.Model):
|
|
49
|
+
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
|
|
50
|
+
name = models.CharField(max_length=100)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# admin.py
|
|
55
|
+
from django.contrib import admin
|
|
56
|
+
from subadmin import RootSubAdmin, SubAdmin
|
|
57
|
+
|
|
58
|
+
from .models import Child, Parent
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ChildAdmin(SubAdmin):
|
|
62
|
+
model = Child
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@admin.register(Parent)
|
|
66
|
+
class ParentAdmin(RootSubAdmin):
|
|
67
|
+
subadmins = (ChildAdmin,)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Open a parent in the admin and follow the link to its child admin. The child
|
|
71
|
+
pages show only records for that parent. The parent foreign key is set
|
|
72
|
+
automatically when adding a child and omitted from the nested form.
|
|
73
|
+
|
|
74
|
+
The [test app models](tests/testapp/models.py) and
|
|
75
|
+
[admin configuration](tests/testapp/admin.py) also show deeper nesting. Their
|
|
76
|
+
workflows are covered by [integration tests](tests/test_admin.py), which you
|
|
77
|
+
can run from a source checkout with:
|
|
78
|
+
|
|
79
|
+
```console
|
|
80
|
+
python -m django test tests --settings=tests.settings
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Screenshots
|
|
84
|
+
|
|
85
|
+
The parent change page links to its child admin.
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
The child changelist contains only that parent's children.
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
The child add form omits the parent foreign key, which is set automatically.
|
|
94
|
+
|
|
95
|
+

|
|
96
|
+
|
|
97
|
+
## Labels
|
|
98
|
+
|
|
99
|
+
Set `subadmin_label` to change a subadmin link and its collection breadcrumbs
|
|
100
|
+
without renaming the model:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
class ChildAdmin(SubAdmin):
|
|
104
|
+
model = Child
|
|
105
|
+
subadmin_label = "Members"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The default is the model's `verbose_name_plural`. Object breadcrumbs and other
|
|
109
|
+
model names keep their usual Django wording. Override
|
|
110
|
+
`get_subadmin_label(request)` if the label needs to vary by request.
|
|
111
|
+
|
|
112
|
+
## Upgrading from 3.2
|
|
113
|
+
|
|
114
|
+
Parent objects are now loaded through the parent admin's `get_object()` method,
|
|
115
|
+
so custom `get_queryset()` filters affect nested pages. Each parent in the URL
|
|
116
|
+
must also pass that admin's `has_view_or_change_permission(request, obj)` check.
|
|
117
|
+
A parent hidden by the queryset returns 404; a visible parent without permission
|
|
118
|
+
returns 403. Child permissions alone no longer grant access through a parent.
|
|
119
|
+
|
|
120
|
+
If you need the previous direct model lookup while adapting a project, set:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
SUBADMIN_USE_DIRECT_PARENT_LOOKUP = True
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This changes how parents are loaded, but does not skip the parent permission
|
|
127
|
+
check.
|
|
128
|
+
|
|
129
|
+
Custom overrides of `get_parent_instance()` and `get_subadmin_helper()` need to
|
|
130
|
+
accept `request` as their first argument after `self`. Their signatures are now
|
|
131
|
+
`get_parent_instance(self, request, parent_id)` and
|
|
132
|
+
`get_subadmin_helper(self, request, view_args, object_id=None)`.
|
|
133
|
+
|
|
134
|
+
`SubAdmin` wraps forms to validate parent-scoped fields. If you override
|
|
135
|
+
`get_form()` or `get_changelist_form()`, call `super()` so that wrapping still
|
|
136
|
+
runs.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-subadmin
|
|
3
|
+
Version: 5.2.0
|
|
4
|
+
Summary: A special kind of ModelAdmin that allows it to be nested within another ModelAdmin
|
|
5
|
+
Author-email: Mitja Pagon <mitja@inueni.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/inueni/django-subadmin/
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/inueni/django-subadmin/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/inueni/django-subadmin/releases
|
|
10
|
+
Keywords: django,admin,modeladmin,foreignkey,related field
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Framework :: Django
|
|
22
|
+
Classifier: Framework :: Django :: 5.2
|
|
23
|
+
Classifier: Framework :: Django :: 6.0
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: Django<6.1,>=5.2
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# django-subadmin
|
|
31
|
+
|
|
32
|
+
`django-subadmin` lets a `ModelAdmin` live under another `ModelAdmin`. When
|
|
33
|
+
related objects have outgrown an inline, a `SubAdmin` gives them their own list
|
|
34
|
+
and change pages, with search, filters, and pagination scoped to a parent
|
|
35
|
+
object. Subadmins can be nested several levels deep.
|
|
36
|
+
|
|
37
|
+
## Compatibility
|
|
38
|
+
|
|
39
|
+
| django-subadmin | Django | Python |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| 5.2.x | 5.2, 6.0 | 3.10+ |
|
|
42
|
+
| 3.2.x | 3.2, 4.x, 5.x | 3.6+ |
|
|
43
|
+
|
|
44
|
+
The package version follows the oldest Django version supported by that line.
|
|
45
|
+
The 5.2 release drops support for Django before 5.2 and Python before 3.10.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```console
|
|
50
|
+
pip install django-subadmin
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Add `subadmin` to `INSTALLED_APPS` so Django can find its templates:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
INSTALLED_APPS = [
|
|
57
|
+
# Django's contrib apps and your own apps...
|
|
58
|
+
"subadmin",
|
|
59
|
+
]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
The test app has a `Parent` model with related `Child` objects. Django's inline
|
|
65
|
+
admin would put the children on the parent's form; a `SubAdmin` gives each
|
|
66
|
+
parent its own child changelist instead.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# models.py
|
|
70
|
+
from django.db import models
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Parent(models.Model):
|
|
74
|
+
name = models.CharField(max_length=100)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Child(models.Model):
|
|
78
|
+
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
|
|
79
|
+
name = models.CharField(max_length=100)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# admin.py
|
|
84
|
+
from django.contrib import admin
|
|
85
|
+
from subadmin import RootSubAdmin, SubAdmin
|
|
86
|
+
|
|
87
|
+
from .models import Child, Parent
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ChildAdmin(SubAdmin):
|
|
91
|
+
model = Child
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@admin.register(Parent)
|
|
95
|
+
class ParentAdmin(RootSubAdmin):
|
|
96
|
+
subadmins = (ChildAdmin,)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Open a parent in the admin and follow the link to its child admin. The child
|
|
100
|
+
pages show only records for that parent. The parent foreign key is set
|
|
101
|
+
automatically when adding a child and omitted from the nested form.
|
|
102
|
+
|
|
103
|
+
The [test app models](tests/testapp/models.py) and
|
|
104
|
+
[admin configuration](tests/testapp/admin.py) also show deeper nesting. Their
|
|
105
|
+
workflows are covered by [integration tests](tests/test_admin.py), which you
|
|
106
|
+
can run from a source checkout with:
|
|
107
|
+
|
|
108
|
+
```console
|
|
109
|
+
python -m django test tests --settings=tests.settings
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Screenshots
|
|
113
|
+
|
|
114
|
+
The parent change page links to its child admin.
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
The child changelist contains only that parent's children.
|
|
119
|
+
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+
The child add form omits the parent foreign key, which is set automatically.
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
## Labels
|
|
127
|
+
|
|
128
|
+
Set `subadmin_label` to change a subadmin link and its collection breadcrumbs
|
|
129
|
+
without renaming the model:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
class ChildAdmin(SubAdmin):
|
|
133
|
+
model = Child
|
|
134
|
+
subadmin_label = "Members"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The default is the model's `verbose_name_plural`. Object breadcrumbs and other
|
|
138
|
+
model names keep their usual Django wording. Override
|
|
139
|
+
`get_subadmin_label(request)` if the label needs to vary by request.
|
|
140
|
+
|
|
141
|
+
## Upgrading from 3.2
|
|
142
|
+
|
|
143
|
+
Parent objects are now loaded through the parent admin's `get_object()` method,
|
|
144
|
+
so custom `get_queryset()` filters affect nested pages. Each parent in the URL
|
|
145
|
+
must also pass that admin's `has_view_or_change_permission(request, obj)` check.
|
|
146
|
+
A parent hidden by the queryset returns 404; a visible parent without permission
|
|
147
|
+
returns 403. Child permissions alone no longer grant access through a parent.
|
|
148
|
+
|
|
149
|
+
If you need the previous direct model lookup while adapting a project, set:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
SUBADMIN_USE_DIRECT_PARENT_LOOKUP = True
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This changes how parents are loaded, but does not skip the parent permission
|
|
156
|
+
check.
|
|
157
|
+
|
|
158
|
+
Custom overrides of `get_parent_instance()` and `get_subadmin_helper()` need to
|
|
159
|
+
accept `request` as their first argument after `self`. Their signatures are now
|
|
160
|
+
`get_parent_instance(self, request, parent_id)` and
|
|
161
|
+
`get_subadmin_helper(self, request, view_args, object_id=None)`.
|
|
162
|
+
|
|
163
|
+
`SubAdmin` wraps forms to validate parent-scoped fields. If you override
|
|
164
|
+
`get_form()` or `get_changelist_form()`, call `super()` so that wrapping still
|
|
165
|
+
runs.
|
|
@@ -2,13 +2,24 @@
|
|
|
2
2
|
LICENSE
|
|
3
3
|
README.md
|
|
4
4
|
pyproject.toml
|
|
5
|
+
.github/workflows/ci.yml
|
|
5
6
|
.github/workflows/publish.yml
|
|
6
7
|
.github/workflows/publish_test.yml
|
|
7
8
|
django_subadmin.egg-info/PKG-INFO
|
|
8
9
|
django_subadmin.egg-info/SOURCES.txt
|
|
9
10
|
django_subadmin.egg-info/dependency_links.txt
|
|
11
|
+
django_subadmin.egg-info/requires.txt
|
|
12
|
+
django_subadmin.egg-info/scm_file_list.json
|
|
13
|
+
django_subadmin.egg-info/scm_version.json
|
|
10
14
|
django_subadmin.egg-info/top_level.txt
|
|
15
|
+
docs/images/child-add.png
|
|
16
|
+
docs/images/child-list.png
|
|
17
|
+
docs/images/parent-change.png
|
|
11
18
|
subadmin/__init__.py
|
|
19
|
+
subadmin/base.py
|
|
20
|
+
subadmin/changelist.py
|
|
21
|
+
subadmin/forms.py
|
|
22
|
+
subadmin/helpers.py
|
|
12
23
|
subadmin/templates/subadmin/breadcrumbs.html
|
|
13
24
|
subadmin/templates/subadmin/change_form.html
|
|
14
25
|
subadmin/templates/subadmin/change_list.html
|
|
@@ -18,4 +29,11 @@ subadmin/templates/subadmin/object_history.html
|
|
|
18
29
|
subadmin/templates/subadmin/parent_change_form.html
|
|
19
30
|
subadmin/templates/subadmin/submit_line.html
|
|
20
31
|
subadmin/templatetags/__init__.py
|
|
21
|
-
subadmin/templatetags/subadmin_tags.py
|
|
32
|
+
subadmin/templatetags/subadmin_tags.py
|
|
33
|
+
tests/__init__.py
|
|
34
|
+
tests/settings.py
|
|
35
|
+
tests/test_admin.py
|
|
36
|
+
tests/urls.py
|
|
37
|
+
tests/testapp/__init__.py
|
|
38
|
+
tests/testapp/admin.py
|
|
39
|
+
tests/testapp/models.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Django<6.1,>=5.2
|