django-subadmin 3.2.4__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.
Files changed (47) hide show
  1. django_subadmin-5.2.0/.github/workflows/ci.yml +62 -0
  2. django_subadmin-5.2.0/.github/workflows/publish.yml +69 -0
  3. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/.github/workflows/publish_test.yml +15 -4
  4. django_subadmin-5.2.0/PKG-INFO +165 -0
  5. django_subadmin-5.2.0/README.md +136 -0
  6. django_subadmin-5.2.0/django_subadmin.egg-info/PKG-INFO +165 -0
  7. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/django_subadmin.egg-info/SOURCES.txt +19 -1
  8. django_subadmin-5.2.0/django_subadmin.egg-info/requires.txt +1 -0
  9. django_subadmin-5.2.0/django_subadmin.egg-info/scm_file_list.json +36 -0
  10. django_subadmin-5.2.0/django_subadmin.egg-info/scm_version.json +8 -0
  11. django_subadmin-5.2.0/docs/images/child-add.png +0 -0
  12. django_subadmin-5.2.0/docs/images/child-list.png +0 -0
  13. django_subadmin-5.2.0/docs/images/parent-change.png +0 -0
  14. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/pyproject.toml +9 -6
  15. django_subadmin-5.2.0/subadmin/__init__.py +25 -0
  16. django_subadmin-5.2.0/subadmin/base.py +577 -0
  17. django_subadmin-5.2.0/subadmin/changelist.py +13 -0
  18. django_subadmin-5.2.0/subadmin/forms.py +39 -0
  19. django_subadmin-5.2.0/subadmin/helpers.py +58 -0
  20. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/change_form.html +5 -4
  21. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/change_list.html +3 -3
  22. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/delete_confirmation.html +2 -2
  23. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/delete_selected_confirmation.html +2 -2
  24. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/object_history.html +2 -2
  25. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/submit_line.html +3 -3
  26. django_subadmin-5.2.0/subadmin/templatetags/subadmin_tags.py +79 -0
  27. django_subadmin-5.2.0/tests/__init__.py +0 -0
  28. django_subadmin-5.2.0/tests/settings.py +35 -0
  29. django_subadmin-5.2.0/tests/test_admin.py +360 -0
  30. django_subadmin-5.2.0/tests/testapp/__init__.py +0 -0
  31. django_subadmin-5.2.0/tests/testapp/admin.py +48 -0
  32. django_subadmin-5.2.0/tests/testapp/models.py +66 -0
  33. django_subadmin-5.2.0/tests/urls.py +4 -0
  34. django-subadmin-3.2.4/.github/workflows/publish.yml +0 -41
  35. django-subadmin-3.2.4/PKG-INFO +0 -136
  36. django-subadmin-3.2.4/README.md +0 -111
  37. django-subadmin-3.2.4/django_subadmin.egg-info/PKG-INFO +0 -136
  38. django-subadmin-3.2.4/subadmin/__init__.py +0 -578
  39. django-subadmin-3.2.4/subadmin/templatetags/subadmin_tags.py +0 -57
  40. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/.gitignore +0 -0
  41. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/LICENSE +0 -0
  42. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/django_subadmin.egg-info/dependency_links.txt +0 -0
  43. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/django_subadmin.egg-info/top_level.txt +0 -0
  44. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/setup.cfg +0 -0
  45. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/breadcrumbs.html +0 -0
  46. {django-subadmin-3.2.4 → django_subadmin-5.2.0}/subadmin/templates/subadmin/parent_change_form.html +0 -0
  47. {django-subadmin-3.2.4 → 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@v3
16
+ uses: actions/checkout@v7
10
17
  with:
11
18
  fetch-depth: 0
12
19
  - name: Set up Python
13
- uses: actions/setup-python@v4
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@v3
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@v3
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
+ ![Parent change page with a Children link](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/parent-change.png)
117
+
118
+ The child changelist contains only that parent's children.
119
+
120
+ ![Child changelist scoped to Example parent](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-list.png)
121
+
122
+ The child add form omits the parent foreign key, which is set automatically.
123
+
124
+ ![Child add form without a parent field](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-add.png)
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
+ ![Parent change page with a Children link](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/parent-change.png)
88
+
89
+ The child changelist contains only that parent's children.
90
+
91
+ ![Child changelist scoped to Example parent](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-list.png)
92
+
93
+ The child add form omits the parent foreign key, which is set automatically.
94
+
95
+ ![Child add form without a parent field](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-add.png)
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
+ ![Parent change page with a Children link](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/parent-change.png)
117
+
118
+ The child changelist contains only that parent's children.
119
+
120
+ ![Child changelist scoped to Example parent](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-list.png)
121
+
122
+ The child add form omits the parent foreign key, which is set automatically.
123
+
124
+ ![Child add form without a parent field](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-add.png)
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