django-admin-inline-controls 0.1.0__py3-none-any.whl

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 (33) hide show
  1. django_admin_inline_controls/__init__.py +1 -0
  2. django_admin_inline_controls/actions.py +176 -0
  3. django_admin_inline_controls/apps.py +7 -0
  4. django_admin_inline_controls/checks.py +212 -0
  5. django_admin_inline_controls/contrib/__init__.py +4 -0
  6. django_admin_inline_controls/contrib/filters.py +69 -0
  7. django_admin_inline_controls/contrib/nested.py +77 -0
  8. django_admin_inline_controls/contrib/unfold.py +85 -0
  9. django_admin_inline_controls/controls.py +684 -0
  10. django_admin_inline_controls/locale/es/LC_MESSAGES/django.mo +0 -0
  11. django_admin_inline_controls/locale/es/LC_MESSAGES/django.po +232 -0
  12. django_admin_inline_controls/mixins.py +932 -0
  13. django_admin_inline_controls/py.typed +0 -0
  14. django_admin_inline_controls/row_actions.py +173 -0
  15. django_admin_inline_controls/static/django_admin_inline_controls/css/contrib/unfold.css +111 -0
  16. django_admin_inline_controls/static/django_admin_inline_controls/css/core.css +294 -0
  17. django_admin_inline_controls/static/django_admin_inline_controls/js/actions.js +330 -0
  18. django_admin_inline_controls/static/django_admin_inline_controls/js/contrib/unfold.js +21 -0
  19. django_admin_inline_controls/static/django_admin_inline_controls/js/core.js +570 -0
  20. django_admin_inline_controls/static/django_admin_inline_controls/js/save.js +109 -0
  21. django_admin_inline_controls/templates/django_admin_inline_controls/footer.html +99 -0
  22. django_admin_inline_controls/templates/django_admin_inline_controls/inline.html +34 -0
  23. django_admin_inline_controls/templates/django_admin_inline_controls/inline_response.html +14 -0
  24. django_admin_inline_controls/templates/django_admin_inline_controls/row_actions.html +19 -0
  25. django_admin_inline_controls/templates/django_admin_inline_controls/tfoot.html +36 -0
  26. django_admin_inline_controls/templates/django_admin_inline_controls/toolbar.html +93 -0
  27. django_admin_inline_controls/templatetags/__init__.py +0 -0
  28. django_admin_inline_controls/templatetags/inline_controls.py +72 -0
  29. django_admin_inline_controls/types.py +40 -0
  30. django_admin_inline_controls-0.1.0.dist-info/METADATA +823 -0
  31. django_admin_inline_controls-0.1.0.dist-info/RECORD +33 -0
  32. django_admin_inline_controls-0.1.0.dist-info/WHEEL +4 -0
  33. django_admin_inline_controls-0.1.0.dist-info/licenses/LICENSE +21 -0
File without changes
@@ -0,0 +1,173 @@
1
+ """Row actions: buttons on each saved row of an inline.
2
+
3
+ An action is a method of the inline (or a callable) in one of two forms:
4
+
5
+ - for one row, ``action(inline, request, obj, parent_obj=None)`` — the
6
+ signature of django-inline-actions, so its actions can be reused as they
7
+ are;
8
+ - for a queryset, ``action(inline, request, queryset)`` — an inline action
9
+ (``@inline_action``), run on a queryset with only that row.
10
+
11
+ Both return ``None`` to re-render the inline with the messages sent through
12
+ ``inline.message_user()``, or an ``HttpResponse`` (a file download or a
13
+ redirect). Per-row label, CSS classes and HTML attributes come from the
14
+ action's ``short_description`` / ``css_classes`` / ``attribute_properties``
15
+ or from ``get_<action>_label(obj)`` / ``get_<action>_css(obj)`` /
16
+ ``get_<action>_attr(obj)`` on the inline, as in django-inline-actions.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import inspect
22
+ from collections.abc import Callable
23
+ from dataclasses import dataclass
24
+ from typing import Any
25
+
26
+ from django.contrib import messages
27
+ from django.contrib.admin.utils import model_format_dict, quote
28
+ from django.http import HttpRequest, HttpResponseRedirect
29
+ from django.urls import NoReverseMatch, reverse
30
+ from django.utils.html import format_html_join
31
+ from django.utils.safestring import SafeString, mark_safe
32
+ from django.utils.text import capfirst
33
+ from django.utils.translation import gettext
34
+ from django.utils.translation import gettext_lazy as _
35
+
36
+ from django_admin_inline_controls.actions import delete_selected, inline_action
37
+
38
+
39
+ def set_render_context(inline: Any, request: HttpRequest, parent: Any) -> None:
40
+ """Remember the request and parent object the inline is rendered for: the
41
+ actions' column only gets the row. On the instance, which the admin
42
+ creates for each request (``get_inline_instances()``)."""
43
+ inline._inline_controls_render = (request, parent)
44
+
45
+
46
+ def get_render_context(inline: Any) -> tuple[HttpRequest | None, Any]:
47
+ return getattr(inline, "_inline_controls_render", (None, None))
48
+
49
+
50
+ @inline_action(
51
+ permissions=["delete"],
52
+ description=_("Delete"),
53
+ confirmation=_("Delete “%(object)s”? This cannot be undone."),
54
+ )
55
+ def delete(inline: Any, request: HttpRequest, queryset: Any) -> None:
56
+ """Delete the row (``delete()`` overrides and signals run), recorded in
57
+ the parent's history, like ``delete_selected``."""
58
+ return delete_selected(inline, request, queryset)
59
+
60
+
61
+ @inline_action(permissions=["view"], description=_("View"))
62
+ def view(inline: Any, request: HttpRequest, obj: Any, parent_obj: Any = None) -> Any:
63
+ """Open the row's change view (its own ``ModelAdmin``)."""
64
+ url = change_url(inline, obj)
65
+ if url is None: # pragma: no cover - only offered when it has one
66
+ inline.message_user(request, gettext("This item has no page."), messages.ERROR)
67
+ return None
68
+ return HttpResponseRedirect(url)
69
+
70
+
71
+ #: Rendered as a link to the row's change view, not a button.
72
+ view.inline_actions_link = True
73
+
74
+
75
+ #: Row actions that can be referenced by name in ``inline_actions``.
76
+ BUILTIN_ROW_ACTIONS: dict[str, Callable[..., Any]] = {"delete": delete, "view": view}
77
+
78
+
79
+ def change_url(inline: Any, obj: Any) -> str | None:
80
+ """The change view of ``obj`` in the inline's admin site, if registered."""
81
+ opts = obj._meta
82
+ try:
83
+ return reverse(
84
+ f"{inline.admin_site.name}:{opts.app_label}_{opts.model_name}_change",
85
+ args=[quote(obj.pk)],
86
+ )
87
+ except NoReverseMatch:
88
+ return None
89
+
90
+
91
+ def takes_one_row(function: Callable[..., Any]) -> bool:
92
+ """Whether ``function`` acts on one row (``obj``) rather than a queryset."""
93
+ try:
94
+ return "obj" in inspect.signature(function).parameters
95
+ except (TypeError, ValueError): # pragma: no cover - builtins, C functions
96
+ return False
97
+
98
+
99
+ @dataclass(frozen=True)
100
+ class RowActionSpec:
101
+ """A row action as offered for one row."""
102
+
103
+ name: str
104
+ function: Callable[..., Any]
105
+ label: str
106
+ css_classes: str
107
+ #: Extra HTML attributes of the button.
108
+ attrs: SafeString
109
+ confirmation: str | None
110
+ #: A link instead of a button (``view``).
111
+ url: str | None = None
112
+
113
+
114
+ def row_action_spec(
115
+ inline: Any, name: str, function: Callable[..., Any], obj: Any
116
+ ) -> RowActionSpec:
117
+ """``function`` as ``name``'s action for the row ``obj``."""
118
+ names = {**model_format_dict(inline.model._meta), "object": obj, "count": 1}
119
+
120
+ def hook(kind: str) -> Any:
121
+ method = getattr(inline, f"get_{name}_{kind}", None)
122
+ return method(obj=obj) if callable(method) else None
123
+
124
+ label = hook("label")
125
+ if label is None:
126
+ label = getattr(function, "short_description", None)
127
+ label = (
128
+ str(label) % names
129
+ if label is not None
130
+ else capfirst(name.replace("_", " "))
131
+ )
132
+ css = hook("css")
133
+ if css is None:
134
+ css = getattr(function, "css_classes", "")
135
+ attrs = hook("attr")
136
+ if attrs is None:
137
+ attrs = getattr(function, "attribute_properties", "")
138
+ if isinstance(attrs, dict):
139
+ attrs = format_html_join(" ", '{}="{}"', attrs.items())
140
+ else:
141
+ # A string is used as it is, as in django-inline-actions.
142
+ attrs = mark_safe(attrs) # noqa: S308
143
+ confirmation = getattr(function, "confirmation", None)
144
+ return RowActionSpec(
145
+ name=name,
146
+ function=function,
147
+ label=str(label),
148
+ css_classes=str(css),
149
+ attrs=attrs,
150
+ confirmation=None if confirmation is None else str(confirmation) % names,
151
+ url=change_url(inline, obj)
152
+ if getattr(function, "inline_actions_link", False)
153
+ else None,
154
+ )
155
+
156
+
157
+ def run_row_action(
158
+ inline: Any, request: HttpRequest, action: RowActionSpec, obj: Any, parent: Any
159
+ ) -> Any:
160
+ """Call the action on the row ``obj`` in the form it was written for."""
161
+ function = action.function
162
+ if takes_one_row(function):
163
+ return function(inline, request, obj, parent_obj=parent)
164
+ queryset = type(obj)._default_manager.filter(pk=obj.pk)
165
+ return function(inline, request, queryset)
166
+
167
+
168
+ __all__ = [
169
+ "BUILTIN_ROW_ACTIONS",
170
+ "RowActionSpec",
171
+ "delete",
172
+ "view",
173
+ ]
@@ -0,0 +1,111 @@
1
+ /* django-admin-inline-controls: django-unfold's colors, light and dark. */
2
+ /* The tfoot also on its own: the add view has no controls around it. */
3
+ .inline-controls,
4
+ .inline-controls-tfoot {
5
+ --body-bg: #fff;
6
+ --body-fg: var(--color-font-default-light);
7
+ --body-quiet-color: var(--color-font-subtle-light);
8
+ --border-color: var(--color-base-200);
9
+ --darkened-bg: var(--color-base-50);
10
+ --error-fg: var(--color-red-600);
11
+ --hairline-color: var(--color-base-200);
12
+ --selected-bg: var(--color-base-100);
13
+ }
14
+
15
+ .dark .inline-controls,
16
+ .dark .inline-controls-tfoot {
17
+ --body-bg: var(--color-base-900);
18
+ --body-fg: var(--color-font-default-dark);
19
+ --body-quiet-color: var(--color-font-subtle-dark);
20
+ --border-color: var(--color-base-700);
21
+ --darkened-bg: var(--color-base-800);
22
+ --error-fg: var(--color-red-500);
23
+ --hairline-color: var(--color-base-800);
24
+ --selected-bg: var(--color-base-800);
25
+ }
26
+
27
+ .inline-controls-toolbar {
28
+ margin-bottom: 1rem;
29
+ border: 1px solid var(--hairline-color);
30
+ border-radius: var(--border-radius, 6px);
31
+ color: var(--body-fg);
32
+ }
33
+
34
+ .inline-controls-footer {
35
+ color: var(--body-fg);
36
+ }
37
+
38
+ .inline-controls-toolbar select,
39
+ .inline-controls-toolbar input:not([type="checkbox"], [type="radio"]) {
40
+ box-sizing: border-box;
41
+ height: 30px;
42
+ padding: 0 8px;
43
+ border: 1px solid var(--border-color);
44
+ border-radius: var(--border-radius, 6px);
45
+ color: var(--body-fg);
46
+ background-color: var(--body-bg);
47
+ font-size: 0.8125rem;
48
+ }
49
+
50
+ .inline-controls .button {
51
+ border: 1px solid var(--border-color);
52
+ border-radius: var(--border-radius, 6px);
53
+ color: var(--body-fg);
54
+ background: var(--body-bg);
55
+ font-weight: 500;
56
+ }
57
+
58
+ .inline-controls .button:hover {
59
+ background: var(--darkened-bg);
60
+ }
61
+
62
+ .inline-controls a:not(.button) {
63
+ color: var(--color-primary-600);
64
+ }
65
+
66
+ .dark .inline-controls a:not(.button) {
67
+ color: var(--color-primary-500);
68
+ }
69
+
70
+ .inline-controls-current {
71
+ color: var(--body-fg);
72
+ }
73
+
74
+ table tfoot.inline-controls-tfoot.inline-controls-tfoot th,
75
+ table tfoot.inline-controls-tfoot.inline-controls-tfoot td {
76
+ padding: 0.5rem 0.375rem;
77
+ border-top: 1px solid var(--hairline-color);
78
+ background: var(--darkened-bg);
79
+ color: var(--body-fg);
80
+ font-size: 0.8125rem;
81
+ text-align: left;
82
+ }
83
+
84
+ table
85
+ tfoot.inline-controls-tfoot.inline-controls-tfoot
86
+ :is(th, td):first-child {
87
+ padding-left: 0.75rem;
88
+ }
89
+
90
+ table tfoot.inline-controls-tfoot.inline-controls-tfoot th {
91
+ text-align: right;
92
+ }
93
+
94
+ table
95
+ tfoot.inline-controls-tfoot.inline-controls-tfoot
96
+ .inline-controls-footer-label {
97
+ margin-right: 0.5rem;
98
+ color: var(--body-quiet-color);
99
+ }
100
+
101
+ table
102
+ tfoot.inline-controls-tfoot.inline-controls-tfoot
103
+ .inline-controls-footer-value {
104
+ font-weight: 600;
105
+ }
106
+
107
+ .inline-controls .inline-controls-row-action.button {
108
+ padding: 0.25rem 0.625rem;
109
+ font-size: 0.75rem;
110
+ text-decoration: none;
111
+ }
@@ -0,0 +1,294 @@
1
+ .inline-controls-toolbar {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ align-items: flex-end;
5
+ gap: 8px 24px;
6
+ padding: 8px 10px;
7
+ border-bottom: 1px solid var(--hairline-color, #e8e8e8);
8
+ background: var(--darkened-bg, #f8f8f8);
9
+ }
10
+
11
+ .inline-controls-actions {
12
+ display: flex;
13
+ flex-wrap: wrap;
14
+ flex-basis: 100%;
15
+ align-items: center;
16
+ gap: 6px 10px;
17
+ font-size: 0.8125rem;
18
+ }
19
+
20
+ .inline-controls-actions label {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 6px;
24
+ float: none;
25
+ width: auto;
26
+ min-width: 0;
27
+ padding: 0;
28
+ font-weight: normal;
29
+ }
30
+
31
+ .inline-controls-actions select,
32
+ .inline-controls-actions .button {
33
+ box-sizing: border-box;
34
+ height: 30px;
35
+ margin: 0;
36
+ font-size: 0.8125rem;
37
+ }
38
+
39
+ .inline-controls-actions .button {
40
+ padding: 0 12px;
41
+ border-radius: 4px;
42
+ line-height: 1;
43
+ cursor: pointer;
44
+ }
45
+
46
+ .inline-controls-selection {
47
+ color: var(--body-quiet-color, #666);
48
+ }
49
+
50
+ .inline-controls-select-across[hidden] {
51
+ display: none;
52
+ }
53
+
54
+ .inline-controls-select,
55
+ .inline-controls-select-all {
56
+ margin: 0 6px 0 0;
57
+ vertical-align: middle;
58
+ }
59
+
60
+ /* The tabular label line is short and clips; let the checkbox fit. */
61
+ .inline-group .tabular td.original p:has(> .inline-controls-select) {
62
+ height: auto;
63
+ overflow: visible;
64
+ }
65
+
66
+ .inline-controls-filters {
67
+ display: flex;
68
+ flex-wrap: wrap;
69
+ align-items: flex-end;
70
+ gap: 8px 12px;
71
+ }
72
+
73
+ .inline-controls-filter {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 2px;
77
+ }
78
+
79
+ .inline-controls-filter label {
80
+ float: none;
81
+ width: auto;
82
+ min-width: 0;
83
+ padding: 0;
84
+ font-size: 0.75rem;
85
+ font-weight: normal;
86
+ color: var(--body-quiet-color, #666);
87
+ }
88
+
89
+ .inline-controls-filter .errorlist {
90
+ margin: 0;
91
+ }
92
+
93
+ /* The admin styles text inputs and selects but not date/number inputs or
94
+ <button>: give every single-line control and the buttons one height. */
95
+ .inline-controls-filter input:not([type="checkbox"], [type="radio"]),
96
+ .inline-controls-filter select:not([multiple]),
97
+ .inline-controls-filter-buttons .button {
98
+ box-sizing: border-box;
99
+ height: 30px;
100
+ margin: 0;
101
+ font-size: 0.8125rem;
102
+ vertical-align: middle;
103
+ }
104
+
105
+ .inline-controls-filter input:not([type="checkbox"], [type="radio"]) {
106
+ padding: 5px 6px;
107
+ border: 1px solid var(--border-color, #ccc);
108
+ border-radius: 4px;
109
+ color: var(--body-fg, #333);
110
+ background-color: var(--body-bg, #fff);
111
+ }
112
+
113
+ .inline-controls-filter-buttons {
114
+ display: flex;
115
+ gap: 6px;
116
+ }
117
+
118
+ .inline-controls-filter-buttons .button {
119
+ padding: 0 12px;
120
+ border-radius: 4px;
121
+ line-height: 1;
122
+ cursor: pointer;
123
+ }
124
+
125
+ .inline-controls-ordering {
126
+ display: flex;
127
+ flex-wrap: wrap;
128
+ align-items: center;
129
+ gap: 4px 12px;
130
+ font-size: 0.8125rem;
131
+ }
132
+
133
+ .inline-controls-ordering[hidden] {
134
+ display: none;
135
+ }
136
+
137
+ .inline-controls-ordering-label,
138
+ .inline-controls-sort-priority,
139
+ .inline-controls-sort-remove,
140
+ .inline-controls-count {
141
+ color: var(--body-quiet-color, #666);
142
+ }
143
+
144
+ .inline-controls-sort-priority {
145
+ font-size: 0.6875rem;
146
+ }
147
+
148
+ .inline-controls-sort-remove {
149
+ margin-left: 2px;
150
+ text-decoration: none;
151
+ }
152
+
153
+ .inline-controls-ascending .inline-controls-sort-toggle::after {
154
+ content: " \25B2";
155
+ font-size: 0.7em;
156
+ }
157
+
158
+ .inline-controls-descending .inline-controls-sort-toggle::after {
159
+ content: " \25BC";
160
+ font-size: 0.7em;
161
+ }
162
+
163
+ th.inline-controls-sortable .inline-controls-sort-toggle {
164
+ color: inherit;
165
+ }
166
+
167
+ .inline-controls-footer {
168
+ display: flex;
169
+ flex-wrap: wrap;
170
+ align-items: center;
171
+ gap: 4px 12px;
172
+ padding: 8px 10px;
173
+ font-size: 0.8125rem;
174
+ }
175
+
176
+ .inline-controls-pagination {
177
+ display: flex;
178
+ flex-wrap: wrap;
179
+ align-items: center;
180
+ gap: 4px;
181
+ }
182
+
183
+ .inline-controls-page,
184
+ .inline-controls-ellipsis {
185
+ min-width: 1.5em;
186
+ padding: 2px 6px;
187
+ text-align: center;
188
+ }
189
+
190
+ .inline-controls-current {
191
+ font-weight: bold;
192
+ border-radius: 4px;
193
+ background: var(--selected-bg, #e4e4e4);
194
+ }
195
+
196
+ .inline-controls-footer:not(.inline-controls-infinite) .inline-controls-count {
197
+ margin-left: auto;
198
+ }
199
+
200
+ .inline-controls-infinite {
201
+ justify-content: center;
202
+ }
203
+
204
+ .inline-controls-save {
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 8px;
208
+ }
209
+
210
+ .inline-controls-infinite .inline-controls-save,
211
+ .inline-controls-footer > .inline-controls-save:first-child {
212
+ margin-left: auto;
213
+ }
214
+
215
+ .inline-controls-save .button {
216
+ box-sizing: border-box;
217
+ height: 30px;
218
+ margin: 0;
219
+ padding: 0 12px;
220
+ border-radius: 4px;
221
+ font-size: 0.8125rem;
222
+ line-height: 1;
223
+ cursor: pointer;
224
+ }
225
+
226
+ .inline-controls-status-invalid,
227
+ .inline-controls-status-failed,
228
+ .inline-controls-status-error {
229
+ color: var(--error-fg, #ba2121);
230
+ }
231
+
232
+ .inline-controls.inline-controls-loading {
233
+ opacity: 0.6;
234
+ transition: opacity 0.2s;
235
+ }
236
+
237
+ .inline-controls-footer-rows {
238
+ display: flex;
239
+ flex-direction: column;
240
+ flex-basis: 100%;
241
+ gap: 2px;
242
+ }
243
+
244
+ .inline-controls-footer-row {
245
+ display: flex;
246
+ flex-wrap: wrap;
247
+ gap: 4px 16px;
248
+ }
249
+
250
+ .inline-controls-footer-label {
251
+ font-weight: bold;
252
+ }
253
+
254
+ .inline-controls-footer-column {
255
+ color: var(--body-quiet-color, #666);
256
+ }
257
+
258
+ /* Specific enough to beat the admin's own `tfoot td` and inline rules. */
259
+ .inline-group table tfoot.inline-controls-tfoot th,
260
+ .inline-group table tfoot.inline-controls-tfoot td {
261
+ border-top: 1px solid var(--hairline-color, #e8e8e8);
262
+ background: var(--darkened-bg, #f8f8f8);
263
+ font-size: 0.8125rem;
264
+ font-weight: normal;
265
+ text-transform: none;
266
+ }
267
+
268
+ .inline-group table tfoot.inline-controls-tfoot th {
269
+ text-align: right;
270
+ }
271
+
272
+ .inline-group table tfoot.inline-controls-tfoot .inline-controls-footer-label {
273
+ color: var(--body-quiet-color, #666);
274
+ font-weight: bold;
275
+ }
276
+
277
+ .inline-group table tfoot.inline-controls-tfoot .inline-controls-footer-value {
278
+ font-weight: bold;
279
+ }
280
+
281
+ .inline-controls-row-actions:not([hidden]) {
282
+ display: inline-flex;
283
+ gap: 4px;
284
+ }
285
+
286
+ .inline-controls-row-action.button {
287
+ margin: 0;
288
+ padding: 4px 8px;
289
+ font-size: 0.75rem;
290
+ line-height: 1.2;
291
+ white-space: nowrap;
292
+ text-decoration: none;
293
+ cursor: pointer;
294
+ }