django-subadmin 3.2.4__tar.gz → 6.1.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 (53) hide show
  1. django_subadmin-6.1.0/.github/workflows/ci.yml +53 -0
  2. django_subadmin-6.1.0/.github/workflows/publish.yml +77 -0
  3. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/.github/workflows/publish_test.yml +16 -5
  4. django_subadmin-6.1.0/PKG-INFO +166 -0
  5. django_subadmin-6.1.0/README.md +138 -0
  6. django_subadmin-6.1.0/django_subadmin.egg-info/PKG-INFO +166 -0
  7. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/django_subadmin.egg-info/SOURCES.txt +20 -1
  8. django_subadmin-6.1.0/django_subadmin.egg-info/requires.txt +1 -0
  9. django_subadmin-6.1.0/django_subadmin.egg-info/scm_file_list.json +37 -0
  10. django_subadmin-6.1.0/django_subadmin.egg-info/scm_version.json +8 -0
  11. django_subadmin-6.1.0/docs/images/child-add.png +0 -0
  12. django_subadmin-6.1.0/docs/images/child-list.png +0 -0
  13. django_subadmin-6.1.0/docs/images/parent-change.png +0 -0
  14. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/pyproject.toml +8 -6
  15. django_subadmin-6.1.0/scripts/verify_release_version.py +29 -0
  16. django_subadmin-6.1.0/subadmin/__init__.py +25 -0
  17. django_subadmin-6.1.0/subadmin/base.py +581 -0
  18. django_subadmin-6.1.0/subadmin/changelist.py +13 -0
  19. django_subadmin-6.1.0/subadmin/forms.py +39 -0
  20. django_subadmin-6.1.0/subadmin/helpers.py +58 -0
  21. django_subadmin-6.1.0/subadmin/templates/subadmin/breadcrumbs.html +7 -0
  22. django_subadmin-6.1.0/subadmin/templates/subadmin/change_form.html +25 -0
  23. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/subadmin/templates/subadmin/change_list.html +5 -5
  24. django_subadmin-6.1.0/subadmin/templates/subadmin/delete_confirmation.html +11 -0
  25. django_subadmin-6.1.0/subadmin/templates/subadmin/delete_selected_confirmation.html +10 -0
  26. django_subadmin-6.1.0/subadmin/templates/subadmin/object_history.html +12 -0
  27. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/subadmin/templates/subadmin/submit_line.html +3 -3
  28. django_subadmin-6.1.0/subadmin/templatetags/subadmin_tags.py +79 -0
  29. django_subadmin-6.1.0/tests/__init__.py +0 -0
  30. django_subadmin-6.1.0/tests/settings.py +35 -0
  31. django_subadmin-6.1.0/tests/test_admin.py +377 -0
  32. django_subadmin-6.1.0/tests/testapp/__init__.py +0 -0
  33. django_subadmin-6.1.0/tests/testapp/admin.py +54 -0
  34. django_subadmin-6.1.0/tests/testapp/models.py +66 -0
  35. django_subadmin-6.1.0/tests/urls.py +4 -0
  36. django-subadmin-3.2.4/.github/workflows/publish.yml +0 -41
  37. django-subadmin-3.2.4/PKG-INFO +0 -136
  38. django-subadmin-3.2.4/README.md +0 -111
  39. django-subadmin-3.2.4/django_subadmin.egg-info/PKG-INFO +0 -136
  40. django-subadmin-3.2.4/subadmin/__init__.py +0 -578
  41. django-subadmin-3.2.4/subadmin/templates/subadmin/breadcrumbs.html +0 -7
  42. django-subadmin-3.2.4/subadmin/templates/subadmin/change_form.html +0 -24
  43. django-subadmin-3.2.4/subadmin/templates/subadmin/delete_confirmation.html +0 -11
  44. django-subadmin-3.2.4/subadmin/templates/subadmin/delete_selected_confirmation.html +0 -10
  45. django-subadmin-3.2.4/subadmin/templates/subadmin/object_history.html +0 -12
  46. django-subadmin-3.2.4/subadmin/templatetags/subadmin_tags.py +0 -57
  47. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/.gitignore +0 -0
  48. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/LICENSE +0 -0
  49. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/django_subadmin.egg-info/dependency_links.txt +0 -0
  50. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/django_subadmin.egg-info/top_level.txt +0 -0
  51. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/setup.cfg +0 -0
  52. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/subadmin/templates/subadmin/parent_change_form.html +0 -0
  53. {django-subadmin-3.2.4 → django_subadmin-6.1.0}/subadmin/templatetags/__init__.py +0 -0
@@ -0,0 +1,53 @@
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: "6.1.0"
20
+ python: "3.12"
21
+ django_spec: "Django==6.1.0"
22
+ - django: "latest"
23
+ python: "3.12"
24
+ django_spec: "Django>=6.1"
25
+ - django: "latest"
26
+ python: "3.14"
27
+ django_spec: "Django>=6.1"
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v7
31
+ with:
32
+ fetch-depth: 0
33
+ - name: Set up Python
34
+ uses: actions/setup-python@v7
35
+ with:
36
+ python-version: ${{ matrix.python }}
37
+ - name: Install package and Django
38
+ env:
39
+ DJANGO_SPEC: ${{ matrix.django_spec }}
40
+ run: python -m pip install -e . "$DJANGO_SPEC"
41
+ - name: Run integration tests
42
+ run: python -m django test tests --settings=tests.settings
43
+
44
+ passed:
45
+ name: CI passed
46
+ needs: test
47
+ if: always()
48
+ runs-on: ubuntu-latest
49
+ steps:
50
+ - name: Require every test job to pass
51
+ env:
52
+ TEST_RESULT: ${{ needs.test.result }}
53
+ run: test "$TEST_RESULT" = success
@@ -0,0 +1,77 @@
1
+ name: Build and publish to PyPI
2
+ on:
3
+ push:
4
+ tags: ["*"]
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.12"
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: Verify package version matches tag
30
+ env:
31
+ RELEASE_TAG: ${{ github.ref_name }}
32
+ run: python3 scripts/verify_release_version.py "$RELEASE_TAG"
33
+ - name: Upload dist as artifact
34
+ uses: actions/upload-artifact@v7
35
+ with:
36
+ name: dist
37
+ path: ./dist
38
+ if-no-files-found: error
39
+ retention-days: 3
40
+
41
+ pypi-publish:
42
+ needs: ['build']
43
+ runs-on: ubuntu-latest
44
+ environment:
45
+ name: pypi
46
+ url: https://pypi.org/p/django-subadmin
47
+ permissions:
48
+ id-token: write
49
+ steps:
50
+ - name: Download artifact as dist
51
+ uses: actions/download-artifact@v8
52
+ with:
53
+ name: dist
54
+ path: dist
55
+ - name: Publish package
56
+ uses: pypa/gh-action-pypi-publish@release/v1
57
+
58
+ github-release:
59
+ needs: pypi-publish
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ contents: write
63
+ steps:
64
+ - name: Checkout tags for release notes
65
+ uses: actions/checkout@v7
66
+ with:
67
+ fetch-depth: 0
68
+ - name: Download distribution files
69
+ uses: actions/download-artifact@v8
70
+ with:
71
+ name: dist
72
+ path: dist
73
+ - name: Create GitHub Release
74
+ env:
75
+ GH_TOKEN: ${{ github.token }}
76
+ GH_REPO: ${{ github.repository }}
77
+ run: gh release create "$GITHUB_REF_NAME" dist/* --verify-tag --notes-from-tag
@@ -1,27 +1,35 @@
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
- python-version: "3.10"
22
+ python-version: "3.12"
16
23
  - name: Install pypa/build
17
24
  run: python3 -m pip install --upgrade build
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,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-subadmin
3
+ Version: 6.1.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.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Framework :: Django
22
+ Classifier: Framework :: Django :: 6.1
23
+ Requires-Python: >=3.12
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: Django>=6.1
27
+ Dynamic: license-file
28
+
29
+ # django-subadmin
30
+
31
+ `django-subadmin` lets a `ModelAdmin` live under another `ModelAdmin`. When
32
+ related objects have outgrown an inline, a `SubAdmin` gives them their own list
33
+ and change pages, with search, filters, and pagination scoped to a parent
34
+ object. Subadmins can be nested several levels deep.
35
+
36
+ ## Compatibility
37
+
38
+ | django-subadmin | Django | Python |
39
+ | --- | --- | --- |
40
+ | 6.1.x | 6.1+ | 3.12+ |
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
+ Projects staying on Django 5.2 or 6.0 should constrain `django-subadmin` to
46
+ `>=5.2,<6.1`.
47
+
48
+ ## Installation
49
+
50
+ ```console
51
+ pip install django-subadmin
52
+ ```
53
+
54
+ Add `subadmin` to `INSTALLED_APPS` so Django can find its templates:
55
+
56
+ ```python
57
+ INSTALLED_APPS = [
58
+ # Django's contrib apps and your own apps...
59
+ "subadmin",
60
+ ]
61
+ ```
62
+
63
+ ## Example
64
+
65
+ The test app has a `Parent` model with related `Child` objects. Django's inline
66
+ admin would put the children on the parent's form; a `SubAdmin` gives each
67
+ parent its own child changelist instead.
68
+
69
+ ```python
70
+ # models.py
71
+ from django.db import models
72
+
73
+
74
+ class Parent(models.Model):
75
+ name = models.CharField(max_length=100)
76
+
77
+
78
+ class Child(models.Model):
79
+ parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
80
+ name = models.CharField(max_length=100)
81
+ ```
82
+
83
+ ```python
84
+ # admin.py
85
+ from django.contrib import admin
86
+ from subadmin import RootSubAdmin, SubAdmin
87
+
88
+ from .models import Child, Parent
89
+
90
+
91
+ class ChildAdmin(SubAdmin):
92
+ model = Child
93
+
94
+
95
+ @admin.register(Parent)
96
+ class ParentAdmin(RootSubAdmin):
97
+ subadmins = (ChildAdmin,)
98
+ ```
99
+
100
+ Open a parent in the admin and follow the link to its child admin. The child
101
+ pages show only records for that parent. The parent foreign key is set
102
+ automatically when adding a child and omitted from the nested form.
103
+
104
+ The [test app models](tests/testapp/models.py) and
105
+ [admin configuration](tests/testapp/admin.py) also show deeper nesting. Their
106
+ workflows are covered by [integration tests](tests/test_admin.py), which you
107
+ can run from a source checkout with:
108
+
109
+ ```console
110
+ python -m django test tests --settings=tests.settings
111
+ ```
112
+
113
+ ## Screenshots
114
+
115
+ The parent change page links to its child admin.
116
+
117
+ ![Parent change page with a Children link](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/parent-change.png)
118
+
119
+ The child changelist contains only that parent's children.
120
+
121
+ ![Child changelist scoped to Example parent](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-list.png)
122
+
123
+ The child add form omits the parent foreign key, which is set automatically.
124
+
125
+ ![Child add form without a parent field](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-add.png)
126
+
127
+ ## Labels
128
+
129
+ Set `subadmin_label` to change a subadmin link and its collection breadcrumbs
130
+ without renaming the model:
131
+
132
+ ```python
133
+ class ChildAdmin(SubAdmin):
134
+ model = Child
135
+ subadmin_label = "Members"
136
+ ```
137
+
138
+ The default is the model's `verbose_name_plural`. Object breadcrumbs and other
139
+ model names keep their usual Django wording. Override
140
+ `get_subadmin_label(request)` if the label needs to vary by request.
141
+
142
+ ## Upgrading from 3.2
143
+
144
+ Parent objects are now loaded through the parent admin's `get_object()` method,
145
+ so custom `get_queryset()` filters affect nested pages. Each parent in the URL
146
+ must also pass that admin's `has_view_or_change_permission(request, obj)` check.
147
+ A parent hidden by the queryset returns 404; a visible parent without permission
148
+ returns 403. Child permissions alone no longer grant access through a parent.
149
+
150
+ If you need the previous direct model lookup while adapting a project, set:
151
+
152
+ ```python
153
+ SUBADMIN_USE_DIRECT_PARENT_LOOKUP = True
154
+ ```
155
+
156
+ This changes how parents are loaded, but does not skip the parent permission
157
+ check.
158
+
159
+ Custom overrides of `get_parent_instance()` and `get_subadmin_helper()` need to
160
+ accept `request` as their first argument after `self`. Their signatures are now
161
+ `get_parent_instance(self, request, parent_id)` and
162
+ `get_subadmin_helper(self, request, view_args, object_id=None)`.
163
+
164
+ `SubAdmin` wraps forms to validate parent-scoped fields. If you override
165
+ `get_form()` or `get_changelist_form()`, call `super()` so that wrapping still
166
+ runs.
@@ -0,0 +1,138 @@
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
+ | 6.1.x | 6.1+ | 3.12+ |
13
+ | 5.2.x | 5.2, 6.0 | 3.10+ |
14
+ | 3.2.x | 3.2, 4.x, 5.x | 3.6+ |
15
+
16
+ The package version follows the oldest Django version supported by that line.
17
+ Projects staying on Django 5.2 or 6.0 should constrain `django-subadmin` to
18
+ `>=5.2,<6.1`.
19
+
20
+ ## Installation
21
+
22
+ ```console
23
+ pip install django-subadmin
24
+ ```
25
+
26
+ Add `subadmin` to `INSTALLED_APPS` so Django can find its templates:
27
+
28
+ ```python
29
+ INSTALLED_APPS = [
30
+ # Django's contrib apps and your own apps...
31
+ "subadmin",
32
+ ]
33
+ ```
34
+
35
+ ## Example
36
+
37
+ The test app has a `Parent` model with related `Child` objects. Django's inline
38
+ admin would put the children on the parent's form; a `SubAdmin` gives each
39
+ parent its own child changelist instead.
40
+
41
+ ```python
42
+ # models.py
43
+ from django.db import models
44
+
45
+
46
+ class Parent(models.Model):
47
+ name = models.CharField(max_length=100)
48
+
49
+
50
+ class Child(models.Model):
51
+ parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
52
+ name = models.CharField(max_length=100)
53
+ ```
54
+
55
+ ```python
56
+ # admin.py
57
+ from django.contrib import admin
58
+ from subadmin import RootSubAdmin, SubAdmin
59
+
60
+ from .models import Child, Parent
61
+
62
+
63
+ class ChildAdmin(SubAdmin):
64
+ model = Child
65
+
66
+
67
+ @admin.register(Parent)
68
+ class ParentAdmin(RootSubAdmin):
69
+ subadmins = (ChildAdmin,)
70
+ ```
71
+
72
+ Open a parent in the admin and follow the link to its child admin. The child
73
+ pages show only records for that parent. The parent foreign key is set
74
+ automatically when adding a child and omitted from the nested form.
75
+
76
+ The [test app models](tests/testapp/models.py) and
77
+ [admin configuration](tests/testapp/admin.py) also show deeper nesting. Their
78
+ workflows are covered by [integration tests](tests/test_admin.py), which you
79
+ can run from a source checkout with:
80
+
81
+ ```console
82
+ python -m django test tests --settings=tests.settings
83
+ ```
84
+
85
+ ## Screenshots
86
+
87
+ The parent change page links to its child admin.
88
+
89
+ ![Parent change page with a Children link](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/parent-change.png)
90
+
91
+ The child changelist contains only that parent's children.
92
+
93
+ ![Child changelist scoped to Example parent](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-list.png)
94
+
95
+ The child add form omits the parent foreign key, which is set automatically.
96
+
97
+ ![Child add form without a parent field](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-add.png)
98
+
99
+ ## Labels
100
+
101
+ Set `subadmin_label` to change a subadmin link and its collection breadcrumbs
102
+ without renaming the model:
103
+
104
+ ```python
105
+ class ChildAdmin(SubAdmin):
106
+ model = Child
107
+ subadmin_label = "Members"
108
+ ```
109
+
110
+ The default is the model's `verbose_name_plural`. Object breadcrumbs and other
111
+ model names keep their usual Django wording. Override
112
+ `get_subadmin_label(request)` if the label needs to vary by request.
113
+
114
+ ## Upgrading from 3.2
115
+
116
+ Parent objects are now loaded through the parent admin's `get_object()` method,
117
+ so custom `get_queryset()` filters affect nested pages. Each parent in the URL
118
+ must also pass that admin's `has_view_or_change_permission(request, obj)` check.
119
+ A parent hidden by the queryset returns 404; a visible parent without permission
120
+ returns 403. Child permissions alone no longer grant access through a parent.
121
+
122
+ If you need the previous direct model lookup while adapting a project, set:
123
+
124
+ ```python
125
+ SUBADMIN_USE_DIRECT_PARENT_LOOKUP = True
126
+ ```
127
+
128
+ This changes how parents are loaded, but does not skip the parent permission
129
+ check.
130
+
131
+ Custom overrides of `get_parent_instance()` and `get_subadmin_helper()` need to
132
+ accept `request` as their first argument after `self`. Their signatures are now
133
+ `get_parent_instance(self, request, parent_id)` and
134
+ `get_subadmin_helper(self, request, view_args, object_id=None)`.
135
+
136
+ `SubAdmin` wraps forms to validate parent-scoped fields. If you override
137
+ `get_form()` or `get_changelist_form()`, call `super()` so that wrapping still
138
+ runs.
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-subadmin
3
+ Version: 6.1.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.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Framework :: Django
22
+ Classifier: Framework :: Django :: 6.1
23
+ Requires-Python: >=3.12
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: Django>=6.1
27
+ Dynamic: license-file
28
+
29
+ # django-subadmin
30
+
31
+ `django-subadmin` lets a `ModelAdmin` live under another `ModelAdmin`. When
32
+ related objects have outgrown an inline, a `SubAdmin` gives them their own list
33
+ and change pages, with search, filters, and pagination scoped to a parent
34
+ object. Subadmins can be nested several levels deep.
35
+
36
+ ## Compatibility
37
+
38
+ | django-subadmin | Django | Python |
39
+ | --- | --- | --- |
40
+ | 6.1.x | 6.1+ | 3.12+ |
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
+ Projects staying on Django 5.2 or 6.0 should constrain `django-subadmin` to
46
+ `>=5.2,<6.1`.
47
+
48
+ ## Installation
49
+
50
+ ```console
51
+ pip install django-subadmin
52
+ ```
53
+
54
+ Add `subadmin` to `INSTALLED_APPS` so Django can find its templates:
55
+
56
+ ```python
57
+ INSTALLED_APPS = [
58
+ # Django's contrib apps and your own apps...
59
+ "subadmin",
60
+ ]
61
+ ```
62
+
63
+ ## Example
64
+
65
+ The test app has a `Parent` model with related `Child` objects. Django's inline
66
+ admin would put the children on the parent's form; a `SubAdmin` gives each
67
+ parent its own child changelist instead.
68
+
69
+ ```python
70
+ # models.py
71
+ from django.db import models
72
+
73
+
74
+ class Parent(models.Model):
75
+ name = models.CharField(max_length=100)
76
+
77
+
78
+ class Child(models.Model):
79
+ parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
80
+ name = models.CharField(max_length=100)
81
+ ```
82
+
83
+ ```python
84
+ # admin.py
85
+ from django.contrib import admin
86
+ from subadmin import RootSubAdmin, SubAdmin
87
+
88
+ from .models import Child, Parent
89
+
90
+
91
+ class ChildAdmin(SubAdmin):
92
+ model = Child
93
+
94
+
95
+ @admin.register(Parent)
96
+ class ParentAdmin(RootSubAdmin):
97
+ subadmins = (ChildAdmin,)
98
+ ```
99
+
100
+ Open a parent in the admin and follow the link to its child admin. The child
101
+ pages show only records for that parent. The parent foreign key is set
102
+ automatically when adding a child and omitted from the nested form.
103
+
104
+ The [test app models](tests/testapp/models.py) and
105
+ [admin configuration](tests/testapp/admin.py) also show deeper nesting. Their
106
+ workflows are covered by [integration tests](tests/test_admin.py), which you
107
+ can run from a source checkout with:
108
+
109
+ ```console
110
+ python -m django test tests --settings=tests.settings
111
+ ```
112
+
113
+ ## Screenshots
114
+
115
+ The parent change page links to its child admin.
116
+
117
+ ![Parent change page with a Children link](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/parent-change.png)
118
+
119
+ The child changelist contains only that parent's children.
120
+
121
+ ![Child changelist scoped to Example parent](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-list.png)
122
+
123
+ The child add form omits the parent foreign key, which is set automatically.
124
+
125
+ ![Child add form without a parent field](https://raw.githubusercontent.com/inueni/django-subadmin/574123d0571a47b20190b6eb5b8b924983df7150/docs/images/child-add.png)
126
+
127
+ ## Labels
128
+
129
+ Set `subadmin_label` to change a subadmin link and its collection breadcrumbs
130
+ without renaming the model:
131
+
132
+ ```python
133
+ class ChildAdmin(SubAdmin):
134
+ model = Child
135
+ subadmin_label = "Members"
136
+ ```
137
+
138
+ The default is the model's `verbose_name_plural`. Object breadcrumbs and other
139
+ model names keep their usual Django wording. Override
140
+ `get_subadmin_label(request)` if the label needs to vary by request.
141
+
142
+ ## Upgrading from 3.2
143
+
144
+ Parent objects are now loaded through the parent admin's `get_object()` method,
145
+ so custom `get_queryset()` filters affect nested pages. Each parent in the URL
146
+ must also pass that admin's `has_view_or_change_permission(request, obj)` check.
147
+ A parent hidden by the queryset returns 404; a visible parent without permission
148
+ returns 403. Child permissions alone no longer grant access through a parent.
149
+
150
+ If you need the previous direct model lookup while adapting a project, set:
151
+
152
+ ```python
153
+ SUBADMIN_USE_DIRECT_PARENT_LOOKUP = True
154
+ ```
155
+
156
+ This changes how parents are loaded, but does not skip the parent permission
157
+ check.
158
+
159
+ Custom overrides of `get_parent_instance()` and `get_subadmin_helper()` need to
160
+ accept `request` as their first argument after `self`. Their signatures are now
161
+ `get_parent_instance(self, request, parent_id)` and
162
+ `get_subadmin_helper(self, request, view_args, object_id=None)`.
163
+
164
+ `SubAdmin` wraps forms to validate parent-scoped fields. If you override
165
+ `get_form()` or `get_changelist_form()`, call `super()` so that wrapping still
166
+ runs.
@@ -2,13 +2,25 @@
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
18
+ scripts/verify_release_version.py
11
19
  subadmin/__init__.py
20
+ subadmin/base.py
21
+ subadmin/changelist.py
22
+ subadmin/forms.py
23
+ subadmin/helpers.py
12
24
  subadmin/templates/subadmin/breadcrumbs.html
13
25
  subadmin/templates/subadmin/change_form.html
14
26
  subadmin/templates/subadmin/change_list.html
@@ -18,4 +30,11 @@ subadmin/templates/subadmin/object_history.html
18
30
  subadmin/templates/subadmin/parent_change_form.html
19
31
  subadmin/templates/subadmin/submit_line.html
20
32
  subadmin/templatetags/__init__.py
21
- subadmin/templatetags/subadmin_tags.py
33
+ subadmin/templatetags/subadmin_tags.py
34
+ tests/__init__.py
35
+ tests/settings.py
36
+ tests/test_admin.py
37
+ tests/urls.py
38
+ tests/testapp/__init__.py
39
+ tests/testapp/admin.py
40
+ tests/testapp/models.py
@@ -0,0 +1 @@
1
+ Django>=6.1