django-adminflow 1.0.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.
- adminflow/__init__.py +1 -0
- adminflow/apps.py +22 -0
- adminflow/context_processors.py +72 -0
- adminflow/forms.py +89 -0
- adminflow/import_export.py +224 -0
- adminflow/static/adminflow/img/logo.png +0 -0
- adminflow/templates/admin/actions.html +79 -0
- adminflow/templates/admin/base.html +1263 -0
- adminflow/templates/admin/base_site.html +5 -0
- adminflow/templates/admin/change_form.html +315 -0
- adminflow/templates/admin/change_list.html +216 -0
- adminflow/templates/admin/change_list_results.html +211 -0
- adminflow/templates/admin/delete_confirmation.html +55 -0
- adminflow/templates/admin/delete_selected_confirmation.html +118 -0
- adminflow/templates/admin/edit_inline/stacked.html +85 -0
- adminflow/templates/admin/edit_inline/tabular.html +81 -0
- adminflow/templates/admin/filter.html +17 -0
- adminflow/templates/admin/import_export/change_list_export_item.html +8 -0
- adminflow/templates/admin/import_export/change_list_import_item.html +8 -0
- adminflow/templates/admin/import_export/export.html +199 -0
- adminflow/templates/admin/includes/fieldset.html +73 -0
- adminflow/templates/admin/includes/toast.html +102 -0
- adminflow/templates/admin/index.html +227 -0
- adminflow/templates/admin/logged_out.html +38 -0
- adminflow/templates/admin/login.html +144 -0
- adminflow/templates/admin/object_history.html +60 -0
- adminflow/templates/admin/pagination.html +30 -0
- adminflow/templates/admin/search_form.html +28 -0
- adminflow/templates/admin/submit_line.html +48 -0
- adminflow/templates/admin/verify_2fa.html +280 -0
- adminflow/templates/otp/email/token.html +64 -0
- adminflow/templates/otp/email/token.txt +11 -0
- adminflow/templates/registration/logged_out.html +38 -0
- adminflow/templates/registration/password_change_form.html +272 -0
- adminflow/templates/simple_history/object_history.html +186 -0
- adminflow/templates/simple_history/object_history_form.html +103 -0
- adminflow/templates/simple_history/submit_line.html +17 -0
- adminflow/templatetags/__init__.py +1 -0
- adminflow/templatetags/__pycache__/__init__.cpython-314.pyc +0 -0
- adminflow/templatetags/__pycache__/adminflow_tags.cpython-314.pyc +0 -0
- adminflow/templatetags/adminflow_tags.py +233 -0
- adminflow/user_admin.py +662 -0
- adminflow/views.py +207 -0
- django_adminflow-1.0.0.dist-info/METADATA +398 -0
- django_adminflow-1.0.0.dist-info/RECORD +47 -0
- django_adminflow-1.0.0.dist-info/WHEEL +5 -0
- django_adminflow-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
{% load i18n admin_urls adminflow_tags %}
|
|
2
|
+
{% if result_hidden_fields %}
|
|
3
|
+
<div class="hiddenfields">
|
|
4
|
+
{% for item in result_hidden_fields %}{{ item }}{% endfor %}
|
|
5
|
+
</div>
|
|
6
|
+
{% endif %}
|
|
7
|
+
|
|
8
|
+
{% if results %}
|
|
9
|
+
<div class="overflow-x-auto custom-scrollbar">
|
|
10
|
+
<table id="result_list" class="w-full text-left border-collapse">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr class="bg-surface-low border-b border-outline-variant">
|
|
13
|
+
{% for header in result_headers %}
|
|
14
|
+
<th scope="col" class="px-4 py-3 font-semibold text-on-surface-variant uppercase tracking-wider text-[11px]" {{ header.class_attrib }}>
|
|
15
|
+
<div class="flex items-center gap-1">
|
|
16
|
+
{% if header.sortable %}
|
|
17
|
+
<a href="{{ header.url_primary }}" class="text-on-surface-variant hover:text-primary transition-colors flex items-center gap-1">
|
|
18
|
+
{{ header.text|capfirst }}
|
|
19
|
+
{% if header.sortpriority > 0 %}
|
|
20
|
+
<span class="material-symbols-outlined text-xs">
|
|
21
|
+
{% if header.ascending %}arrow_upward{% else %}arrow_downward{% endif %}
|
|
22
|
+
</span>
|
|
23
|
+
{% endif %}
|
|
24
|
+
</a>
|
|
25
|
+
{% else %}
|
|
26
|
+
<span class="text-on-surface-variant">{{ header.text|capfirst }}</span>
|
|
27
|
+
{% endif %}
|
|
28
|
+
</div>
|
|
29
|
+
</th>
|
|
30
|
+
{% endfor %}
|
|
31
|
+
<th scope="col" class="px-6 py-3 font-semibold text-on-surface-variant uppercase tracking-wider text-[11px] text-right">Actions</th>
|
|
32
|
+
</tr>
|
|
33
|
+
</thead>
|
|
34
|
+
<tbody class="divide-y divide-outline-variant/40">
|
|
35
|
+
{% for result, obj in results|zip_results:cl %}
|
|
36
|
+
{% if result.form and result.form.non_field_errors %}
|
|
37
|
+
<tr class="bg-rose-50 dark:bg-rose-950/20"><td colspan="{{ result|length|add:1 }}" class="px-4 py-2 text-xs text-danger font-semibold">{{ result.form.non_field_errors }}</td></tr>
|
|
38
|
+
{% endif %}
|
|
39
|
+
<tr class="hover:bg-surface-low/30 transition-all">
|
|
40
|
+
{% for item in result %}
|
|
41
|
+
{{ item }}
|
|
42
|
+
{% endfor %}
|
|
43
|
+
<td class="px-6 py-2.5 text-right align-middle shrink-0 w-24">
|
|
44
|
+
<div class="flex items-center justify-end gap-2">
|
|
45
|
+
<a href="{% url cl.opts|admin_urlname:'change' obj.pk|admin_urlquote %}" class="p-1.5 hover:bg-primary/10 rounded-lg text-primary transition-all hover:text-primary-dim inline-flex items-center" title="{% translate 'Edit' %}">
|
|
46
|
+
<span class="material-symbols-outlined text-sm">edit</span>
|
|
47
|
+
</a>
|
|
48
|
+
{% if has_delete_permission %}
|
|
49
|
+
<a href="{% url cl.opts|admin_urlname:'delete' obj.pk|admin_urlquote %}" class="p-1.5 hover:bg-rose-50 rounded-lg text-rose-500 transition-all hover:text-rose-600 inline-flex items-center" title="{% translate 'Delete' %}">
|
|
50
|
+
<span class="material-symbols-outlined text-sm">delete</span>
|
|
51
|
+
</a>
|
|
52
|
+
{% endif %}
|
|
53
|
+
</div>
|
|
54
|
+
</td>
|
|
55
|
+
</tr>
|
|
56
|
+
{% endfor %}
|
|
57
|
+
</tbody>
|
|
58
|
+
</table>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
/* Styling elements dynamically outputted by Django python code */
|
|
63
|
+
table#result_list {
|
|
64
|
+
width: 100% !important;
|
|
65
|
+
border-collapse: collapse !important;
|
|
66
|
+
}
|
|
67
|
+
table#result_list td {
|
|
68
|
+
padding: 12px 16px !important;
|
|
69
|
+
font-size: 13px !important;
|
|
70
|
+
color: var(--tw-prose-body);
|
|
71
|
+
vertical-align: middle !important;
|
|
72
|
+
}
|
|
73
|
+
table#result_list td.action-checkbox,
|
|
74
|
+
table#result_list th.action-checkbox-column {
|
|
75
|
+
width: 48px !important;
|
|
76
|
+
padding-left: 20px !important;
|
|
77
|
+
padding-right: 20px !important;
|
|
78
|
+
}
|
|
79
|
+
table#result_list td.action-checkbox input,
|
|
80
|
+
table#result_list th.action-checkbox-column input {
|
|
81
|
+
border-radius: 4px !important;
|
|
82
|
+
border-color: #c3c6d7 !important;
|
|
83
|
+
color: #2563eb !important;
|
|
84
|
+
width: 16px !important;
|
|
85
|
+
height: 16px !important;
|
|
86
|
+
cursor: pointer !important;
|
|
87
|
+
}
|
|
88
|
+
table#result_list td a {
|
|
89
|
+
color: #2563eb !important;
|
|
90
|
+
font-weight: 600 !important;
|
|
91
|
+
}
|
|
92
|
+
table#result_list td a:hover {
|
|
93
|
+
text-decoration: underline !important;
|
|
94
|
+
}
|
|
95
|
+
.dark table#result_list td.action-checkbox input {
|
|
96
|
+
border-color: #475569 !important;
|
|
97
|
+
background-color: #0f172a !important;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ── Mobile Stacked Card View ── */
|
|
101
|
+
@media (max-width: 767px) {
|
|
102
|
+
div.overflow-x-auto {
|
|
103
|
+
overflow-x: visible !important;
|
|
104
|
+
}
|
|
105
|
+
table#result_list,
|
|
106
|
+
table#result_list tbody,
|
|
107
|
+
table#result_list tr,
|
|
108
|
+
table#result_list th,
|
|
109
|
+
table#result_list td {
|
|
110
|
+
display: block !important;
|
|
111
|
+
width: 100% !important;
|
|
112
|
+
box-sizing: border-box !important;
|
|
113
|
+
}
|
|
114
|
+
table#result_list thead {
|
|
115
|
+
display: none !important;
|
|
116
|
+
}
|
|
117
|
+
table#result_list tr {
|
|
118
|
+
margin: 12px 0 !important;
|
|
119
|
+
background: var(--surface-lowest, #ffffff) !important;
|
|
120
|
+
border: 1px solid rgba(226, 232, 240, 0.9) !important;
|
|
121
|
+
border-radius: 16px !important;
|
|
122
|
+
padding: 16px !important;
|
|
123
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04) !important;
|
|
124
|
+
position: relative !important;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Top line: Checkbox on left */
|
|
128
|
+
table#result_list td.action-checkbox,
|
|
129
|
+
table#result_list th.action-checkbox-column {
|
|
130
|
+
width: 100% !important;
|
|
131
|
+
padding: 0 0 12px 0 !important;
|
|
132
|
+
margin-bottom: 8px !important;
|
|
133
|
+
border-bottom: 1px solid rgba(226, 232, 240, 0.6) !important;
|
|
134
|
+
display: flex !important;
|
|
135
|
+
align-items: center !important;
|
|
136
|
+
min-height: 28px !important;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Top line: Edit / Delete Actions on right (same line as checkbox) */
|
|
140
|
+
table#result_list td:last-child {
|
|
141
|
+
position: absolute !important;
|
|
142
|
+
top: 12px !important;
|
|
143
|
+
right: 16px !important;
|
|
144
|
+
width: auto !important;
|
|
145
|
+
border-top: none !important;
|
|
146
|
+
border-bottom: none !important;
|
|
147
|
+
padding: 0 !important;
|
|
148
|
+
margin: 0 !important;
|
|
149
|
+
display: flex !important;
|
|
150
|
+
justify-content: flex-end !important;
|
|
151
|
+
align-items: center !important;
|
|
152
|
+
z-index: 10 !important;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
table#result_list th[data-label],
|
|
156
|
+
table#result_list td[data-label] {
|
|
157
|
+
display: flex !important;
|
|
158
|
+
justify-content: space-between !important;
|
|
159
|
+
align-items: center !important;
|
|
160
|
+
padding: 9px 0 !important;
|
|
161
|
+
border-bottom: 1px solid rgba(226, 232, 240, 0.4) !important;
|
|
162
|
+
text-align: right !important;
|
|
163
|
+
font-size: 13px !important;
|
|
164
|
+
font-weight: 500 !important;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
table#result_list th[data-label]:last-of-type,
|
|
168
|
+
table#result_list td[data-label]:last-of-type {
|
|
169
|
+
border-bottom: none !important;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
table#result_list [data-label]::before {
|
|
173
|
+
content: attr(data-label);
|
|
174
|
+
font-weight: 700 !important;
|
|
175
|
+
font-size: 10px !important;
|
|
176
|
+
text-transform: uppercase !important;
|
|
177
|
+
letter-spacing: 0.05em !important;
|
|
178
|
+
color: #64748b !important;
|
|
179
|
+
margin-right: 12px !important;
|
|
180
|
+
text-align: left !important;
|
|
181
|
+
flex-shrink: 0 !important;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
</style>
|
|
185
|
+
|
|
186
|
+
<script>
|
|
187
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
188
|
+
// Enhance checkbox styling
|
|
189
|
+
const checkboxes = document.querySelectorAll('table#result_list input[type="checkbox"]');
|
|
190
|
+
checkboxes.forEach(cb => {
|
|
191
|
+
cb.classList.add("rounded", "border-outline-variant", "text-primary", "focus:ring-primary", "w-4", "h-4", "cursor-pointer", "dark:bg-slate-800", "dark:border-slate-700");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Add mobile card field labels accurately using both th and td row cells
|
|
195
|
+
const table = document.getElementById('result_list');
|
|
196
|
+
if (table) {
|
|
197
|
+
const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim());
|
|
198
|
+
table.querySelectorAll('tbody tr').forEach(row => {
|
|
199
|
+
const cells = row.querySelectorAll('th, td');
|
|
200
|
+
cells.forEach((cell, idx) => {
|
|
201
|
+
const isCheckbox = cell.classList.contains('action-checkbox') || cell.classList.contains('action-checkbox-column') || cell.querySelector('input[type="checkbox"]');
|
|
202
|
+
const isActions = idx === cells.length - 1;
|
|
203
|
+
if (headers[idx] && !isCheckbox && !isActions) {
|
|
204
|
+
cell.setAttribute('data-label', headers[idx]);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
</script>
|
|
211
|
+
{% endif %}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{% extends "admin/base_site.html" %}
|
|
2
|
+
{% load i18n admin_urls static %}
|
|
3
|
+
|
|
4
|
+
{% block bodyclass %}{{ opts.app_label }}-{{ opts.model_name }} delete-confirmation{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block breadcrumbs %}
|
|
7
|
+
<nav class="flex items-center gap-1.5 text-xs text-on-surface-variant/70 mb-4">
|
|
8
|
+
<a href="{% url 'admin:index' %}" class="hover:text-primary transition-colors flex items-center"><span class="material-symbols-outlined text-[16px]">home</span></a>
|
|
9
|
+
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
|
|
10
|
+
<a href="{% url 'admin:app_list' app_label=opts.app_label %}" class="hover:text-primary transition-colors capitalize font-medium">{{ opts.app_label }}</a>
|
|
11
|
+
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
|
|
12
|
+
<a href="{% url opts|admin_urlname:'changelist' %}" class="hover:text-primary transition-colors font-bold">{{ opts.verbose_name_plural|capfirst }}</a>
|
|
13
|
+
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
|
|
14
|
+
<span class="text-primary font-bold">{% translate 'Delete' %}</span>
|
|
15
|
+
</nav>
|
|
16
|
+
{% endblock %}
|
|
17
|
+
|
|
18
|
+
{% block content %}
|
|
19
|
+
<div class="space-y-6 max-w-2xl mx-auto w-full border border-outline-variant bg-surface-lowest p-8 rounded-xl shadow-md my-8">
|
|
20
|
+
<!-- Danger Icon & Warning title -->
|
|
21
|
+
<div class="text-center space-y-3">
|
|
22
|
+
<div class="inline-flex items-center justify-center p-4 bg-danger/10 text-danger rounded-full">
|
|
23
|
+
<span class="material-symbols-outlined text-4xl">warning</span>
|
|
24
|
+
</div>
|
|
25
|
+
<h2 class="text-xl md:text-2xl font-black text-on-surface">{% translate 'Confirm Deletion' %}</h2>
|
|
26
|
+
<p class="text-xs text-on-surface-variant max-w-md mx-auto">
|
|
27
|
+
{% blocktranslate with name=opts.verbose_name obj=object %}Are you sure you want to delete the {{ name }} "<strong>{{ obj }}</strong>"? All of the following objects and their related items will be deleted permanently.{% endblocktranslate %}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Related deletions list -->
|
|
32
|
+
<div class="bg-surface-low border border-outline-variant p-4 rounded-lg space-y-2 max-h-[300px] overflow-y-auto custom-scrollbar">
|
|
33
|
+
<h3 class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">{% translate 'Objects to be deleted:' %}</h3>
|
|
34
|
+
<div class="text-xs text-on-surface-variant space-y-1 font-mono pl-2 border-l-2 border-danger/40">
|
|
35
|
+
{{ deleted_objects|safe }}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- Confirmation form -->
|
|
40
|
+
<form method="post" class="flex flex-col sm:flex-row items-center justify-center gap-3 pt-4 border-t border-outline-variant">
|
|
41
|
+
{% csrf_token %}
|
|
42
|
+
<input type="hidden" name="post" value="yes"/>
|
|
43
|
+
{% if is_popup %}<input type="hidden" name="_popup" value="1"/>{% endif %}
|
|
44
|
+
|
|
45
|
+
<button type="submit" class="w-full sm:w-auto px-6 py-2.5 bg-danger text-white rounded-lg text-xs font-bold hover:bg-danger/95 shadow-md active:scale-[0.98] transition-all">
|
|
46
|
+
{% translate "Yes, I’m sure" %}
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<a href="{% if cancel_url %}{{ cancel_url }}{% else %}../{% endif %}"
|
|
50
|
+
class="w-full sm:w-auto px-6 py-2.5 bg-surface-lowest border border-outline-variant text-on-surface-variant hover:bg-surface-low text-center rounded-lg text-xs font-bold shadow-sm transition-all">
|
|
51
|
+
{% translate "No, take me back" %}
|
|
52
|
+
</a>
|
|
53
|
+
</form>
|
|
54
|
+
</div>
|
|
55
|
+
{% endblock %}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{% extends "admin/base_site.html" %}
|
|
2
|
+
{% load i18n l10n admin_urls static %}
|
|
3
|
+
|
|
4
|
+
{% block extrahead %}
|
|
5
|
+
{{ block.super }}
|
|
6
|
+
{{ media }}
|
|
7
|
+
{% endblock %}
|
|
8
|
+
|
|
9
|
+
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
|
|
10
|
+
|
|
11
|
+
{% block breadcrumbs %}
|
|
12
|
+
<nav class="flex items-center gap-1.5 text-xs text-on-surface-variant/70 mb-4">
|
|
13
|
+
<a href="{% url 'admin:index' %}" class="hover:text-primary transition-colors flex items-center"><span class="material-symbols-outlined text-[16px]">home</span></a>
|
|
14
|
+
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
|
|
15
|
+
<a href="{% url 'admin:app_list' app_label=opts.app_label %}" class="hover:text-primary transition-colors capitalize font-medium">{{ opts.app_config.verbose_name }}</a>
|
|
16
|
+
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
|
|
17
|
+
<a href="{% url opts|admin_urlname:'changelist' %}" class="hover:text-primary transition-colors font-bold">{{ opts.verbose_name_plural|capfirst }}</a>
|
|
18
|
+
<span class="material-symbols-outlined text-[14px]">chevron_right</span>
|
|
19
|
+
<span class="text-danger font-bold">{% translate 'Confirm Bulk Deletion' %}</span>
|
|
20
|
+
</nav>
|
|
21
|
+
{% endblock %}
|
|
22
|
+
|
|
23
|
+
{% block content %}
|
|
24
|
+
<div class="space-y-6 max-w-2xl mx-auto w-full border border-outline-variant bg-surface-lowest p-8 rounded-xl shadow-md my-8">
|
|
25
|
+
|
|
26
|
+
{% if perms_lacking %}
|
|
27
|
+
<!-- Permission Error -->
|
|
28
|
+
<div class="text-center space-y-3">
|
|
29
|
+
<div class="inline-flex items-center justify-center p-4 bg-warning/10 text-warning rounded-full">
|
|
30
|
+
<span class="material-symbols-outlined text-4xl">gpp_bad</span>
|
|
31
|
+
</div>
|
|
32
|
+
<h2 class="text-xl md:text-2xl font-black text-on-surface">{% translate "Permission Denied" %}</h2>
|
|
33
|
+
<p class="text-xs text-on-surface-variant max-w-md mx-auto">
|
|
34
|
+
{% blocktranslate %}Deleting the selected {{ objects_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktranslate %}
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="bg-warning/5 border border-warning/20 p-4 rounded-lg space-y-1">
|
|
38
|
+
<ul class="text-xs text-on-surface-variant space-y-1 list-disc pl-5">{{ perms_lacking|unordered_list }}</ul>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{% elif protected %}
|
|
42
|
+
<!-- Protected Objects Error -->
|
|
43
|
+
<div class="text-center space-y-3">
|
|
44
|
+
<div class="inline-flex items-center justify-center p-4 bg-warning/10 text-warning rounded-full">
|
|
45
|
+
<span class="material-symbols-outlined text-4xl">shield</span>
|
|
46
|
+
</div>
|
|
47
|
+
<h2 class="text-xl md:text-2xl font-black text-on-surface">{% translate "Protected Objects" %}</h2>
|
|
48
|
+
<p class="text-xs text-on-surface-variant max-w-md mx-auto">
|
|
49
|
+
{% blocktranslate %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktranslate %}
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="bg-warning/5 border border-warning/20 p-4 rounded-lg space-y-1">
|
|
53
|
+
<ul class="text-xs text-on-surface-variant space-y-1 list-disc pl-5">{{ protected|unordered_list }}</ul>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{% else %}
|
|
57
|
+
<!-- Danger Icon & Warning Title -->
|
|
58
|
+
<div class="text-center space-y-3">
|
|
59
|
+
<div class="inline-flex items-center justify-center p-4 bg-danger/10 text-danger rounded-full">
|
|
60
|
+
<span class="material-symbols-outlined text-4xl">delete_forever</span>
|
|
61
|
+
</div>
|
|
62
|
+
<h2 class="text-xl md:text-2xl font-black text-on-surface">{% translate 'Confirm Bulk Deletion' %}</h2>
|
|
63
|
+
<p class="text-xs text-on-surface-variant max-w-md mx-auto">
|
|
64
|
+
{% blocktranslate %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted permanently.{% endblocktranslate %}
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Summary -->
|
|
69
|
+
<div class="bg-surface-low border border-outline-variant p-4 rounded-lg">
|
|
70
|
+
<h3 class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2 flex items-center gap-1.5">
|
|
71
|
+
<span class="material-symbols-outlined text-[14px]">summarize</span>
|
|
72
|
+
{% translate "Summary" %}
|
|
73
|
+
</h3>
|
|
74
|
+
<div class="flex flex-wrap gap-2">
|
|
75
|
+
{% for model_name, object_count in model_count %}
|
|
76
|
+
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[11px] font-bold border" style="background:color-mix(in srgb, var(--danger) 5%, transparent);border-color:color-mix(in srgb, var(--danger) 15%, transparent);color:var(--danger);">
|
|
77
|
+
<span class="material-symbols-outlined text-[14px]">folder</span>
|
|
78
|
+
{{ model_name|capfirst }}: {{ object_count }}
|
|
79
|
+
</span>
|
|
80
|
+
{% endfor %}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<!-- Objects list -->
|
|
85
|
+
<div class="bg-surface-low border border-outline-variant p-4 rounded-lg space-y-2 max-h-[300px] overflow-y-auto custom-scrollbar">
|
|
86
|
+
<h3 class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider flex items-center gap-1.5">
|
|
87
|
+
<span class="material-symbols-outlined text-[14px]">list</span>
|
|
88
|
+
{% translate "Objects to be deleted" %}
|
|
89
|
+
</h3>
|
|
90
|
+
{% for deletable_object in deletable_objects %}
|
|
91
|
+
<div class="text-xs text-on-surface-variant space-y-1 font-mono pl-2 border-l-2 border-danger/40">
|
|
92
|
+
<ul class="space-y-0.5">{{ deletable_object|unordered_list }}</ul>
|
|
93
|
+
</div>
|
|
94
|
+
{% endfor %}
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- Confirmation form -->
|
|
98
|
+
<form method="post" class="flex flex-col sm:flex-row items-center justify-center gap-3 pt-4 border-t border-outline-variant">
|
|
99
|
+
{% csrf_token %}
|
|
100
|
+
{% for obj in queryset %}
|
|
101
|
+
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
|
|
102
|
+
{% endfor %}
|
|
103
|
+
<input type="hidden" name="action" value="delete_selected">
|
|
104
|
+
<input type="hidden" name="post" value="yes">
|
|
105
|
+
|
|
106
|
+
<button type="submit" class="w-full sm:w-auto flex items-center justify-center gap-1.5 px-6 py-2.5 bg-danger text-white rounded-lg text-xs font-bold hover:bg-danger/90 shadow-md active:scale-[0.98] transition-all">
|
|
107
|
+
<span class="material-symbols-outlined text-sm">delete</span>
|
|
108
|
+
{% translate "Yes, I'm sure" %}
|
|
109
|
+
</button>
|
|
110
|
+
|
|
111
|
+
<a href="#" class="cancel-link w-full sm:w-auto flex items-center justify-center gap-1.5 px-6 py-2.5 bg-surface-lowest border border-outline-variant text-on-surface-variant hover:bg-surface-low text-center rounded-lg text-xs font-bold shadow-sm transition-all">
|
|
112
|
+
<span class="material-symbols-outlined text-sm">arrow_back</span>
|
|
113
|
+
{% translate "No, take me back" %}
|
|
114
|
+
</a>
|
|
115
|
+
</form>
|
|
116
|
+
{% endif %}
|
|
117
|
+
</div>
|
|
118
|
+
{% endblock %}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{% load i18n admin_urls static adminflow_tags %}
|
|
2
|
+
<div class="js-inline-admin-formset inline-group space-y-4 mt-6"
|
|
3
|
+
id="{{ inline_admin_formset.formset.prefix }}-group"
|
|
4
|
+
data-inline-type="stacked"
|
|
5
|
+
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
|
|
6
|
+
|
|
7
|
+
<fieldset class="module {{ inline_admin_formset.classes }} space-y-4">
|
|
8
|
+
{{ inline_admin_formset.formset.management_form }}
|
|
9
|
+
{{ inline_admin_formset.formset.non_form_errors }}
|
|
10
|
+
|
|
11
|
+
{% for inline_admin_form in inline_admin_formset %}
|
|
12
|
+
<div class="inline-related bg-surface-lowest border border-outline-variant rounded-xl p-5 shadow-sm {% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form last-related{% endif %}"
|
|
13
|
+
id="{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}">
|
|
14
|
+
|
|
15
|
+
{% if inline_admin_form.original %}
|
|
16
|
+
<!-- Always show header + delete button for existing records -->
|
|
17
|
+
<div class="flex items-center justify-between border-b border-outline-variant/60 pb-3 mb-4">
|
|
18
|
+
<h4 class="text-xs font-bold text-on-surface flex items-center gap-2">
|
|
19
|
+
<span class="material-symbols-outlined text-sm text-primary">security</span>
|
|
20
|
+
{{ inline_admin_formset.opts.verbose_name|capfirst }}:
|
|
21
|
+
<span class="inline_label font-semibold text-on-surface-variant">
|
|
22
|
+
{{ inline_admin_form.original }}
|
|
23
|
+
</span>
|
|
24
|
+
</h4>
|
|
25
|
+
<div class="flex items-center gap-3">
|
|
26
|
+
{% if inline_admin_form.original and inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}
|
|
27
|
+
<a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="text-[11px] font-bold text-primary hover:underline">
|
|
28
|
+
{% if inline_admin_formset.has_change_permission %}{% translate "Edit" %}{% else %}{% translate "View" %}{% endif %}
|
|
29
|
+
</a>
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if inline_admin_form.show_url %}
|
|
32
|
+
<a href="{{ inline_admin_form.absolute_url }}" class="text-[11px] font-bold text-on-surface-variant hover:text-on-surface">
|
|
33
|
+
{% translate "View on site" %}
|
|
34
|
+
</a>
|
|
35
|
+
{% endif %}
|
|
36
|
+
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}
|
|
37
|
+
<span class="delete flex items-center gap-1.5 text-xs text-rose-500 font-semibold cursor-pointer">
|
|
38
|
+
{{ inline_admin_form.deletion_field.field|add_class:"w-4 h-4 rounded border-rose-300 text-rose-600 focus:ring-rose-500" }}
|
|
39
|
+
{{ inline_admin_form.deletion_field.label_tag }}
|
|
40
|
+
</span>
|
|
41
|
+
{% endif %}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
{% elif inline_admin_formset.formset.max_num > 1 %}
|
|
45
|
+
<!-- Show numbered header when multiple items allowed -->
|
|
46
|
+
<div class="flex items-center justify-between border-b border-outline-variant/60 pb-3 mb-4">
|
|
47
|
+
<h4 class="text-xs font-bold text-on-surface flex items-center gap-2">
|
|
48
|
+
<span class="material-symbols-outlined text-sm text-primary">security</span>
|
|
49
|
+
{{ inline_admin_formset.opts.verbose_name|capfirst }}:
|
|
50
|
+
<span class="inline_label font-semibold text-on-surface-variant">#{{ forloop.counter }}</span>
|
|
51
|
+
</h4>
|
|
52
|
+
<div class="flex items-center gap-3">
|
|
53
|
+
{% if inline_admin_form.show_url %}
|
|
54
|
+
<a href="{{ inline_admin_form.absolute_url }}" class="text-[11px] font-bold text-on-surface-variant hover:text-on-surface">
|
|
55
|
+
{% translate "View on site" %}
|
|
56
|
+
</a>
|
|
57
|
+
{% endif %}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
{% else %}
|
|
61
|
+
<!-- Render hidden relation keys for new empty forms -->
|
|
62
|
+
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
|
63
|
+
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
|
|
64
|
+
{% endif %}
|
|
65
|
+
|
|
66
|
+
{% if inline_admin_form.form.non_field_errors %}
|
|
67
|
+
<div class="p-3 mb-4 bg-danger/10 border border-danger/20 text-danger rounded-lg text-xs font-semibold">
|
|
68
|
+
{{ inline_admin_form.form.non_field_errors }}
|
|
69
|
+
</div>
|
|
70
|
+
{% endif %}
|
|
71
|
+
|
|
72
|
+
{% with parent_counter=forloop.counter0 %}
|
|
73
|
+
{% for fieldset in inline_admin_form %}
|
|
74
|
+
{% include "admin/includes/fieldset.html" with heading_level=4 prefix=fieldset.formset.prefix id_prefix=parent_counter id_suffix=forloop.counter0 is_inline=True %}
|
|
75
|
+
{% endfor %}
|
|
76
|
+
{% endwith %}
|
|
77
|
+
|
|
78
|
+
{% if inline_admin_formset.formset.max_num > 1 or inline_admin_form.original %}
|
|
79
|
+
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
|
80
|
+
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
|
|
81
|
+
{% endif %}
|
|
82
|
+
</div>
|
|
83
|
+
{% endfor %}
|
|
84
|
+
</fieldset>
|
|
85
|
+
</div>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{% load i18n admin_urls static adminflow_tags %}
|
|
2
|
+
<div class="js-inline-admin-formset inline-group space-y-3 mt-6 border border-outline-variant p-5 bg-surface-lowest rounded-xl shadow-sm"
|
|
3
|
+
id="{{ inline_admin_formset.formset.prefix }}-group"
|
|
4
|
+
data-inline-type="tabular"
|
|
5
|
+
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
|
|
6
|
+
|
|
7
|
+
<h3 class="text-xs font-black text-on-surface uppercase tracking-wider px-1">
|
|
8
|
+
{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
|
|
9
|
+
</h3>
|
|
10
|
+
|
|
11
|
+
{{ inline_admin_formset.formset.management_form }}
|
|
12
|
+
{{ inline_admin_formset.formset.non_form_errors }}
|
|
13
|
+
|
|
14
|
+
<div class="overflow-x-auto custom-scrollbar tabular inline-related">
|
|
15
|
+
<table class="w-full text-left border-collapse text-xs">
|
|
16
|
+
<thead>
|
|
17
|
+
<tr class="bg-surface-low border-b border-outline-variant">
|
|
18
|
+
<th class="original hidden"></th>
|
|
19
|
+
{% for field in inline_admin_formset.fields %}
|
|
20
|
+
{% if not field.widget.is_hidden %}
|
|
21
|
+
<th class="px-4 py-3 font-semibold text-on-surface-variant uppercase tracking-wider text-[10px]">
|
|
22
|
+
{{ field.label|capfirst }}
|
|
23
|
+
</th>
|
|
24
|
+
{% endif %}
|
|
25
|
+
{% endfor %}
|
|
26
|
+
{% if inline_admin_formset.formset.can_delete %}
|
|
27
|
+
<th class="px-4 py-3 text-[10px] font-semibold text-on-surface-variant uppercase tracking-wider text-center">Delete?</th>
|
|
28
|
+
{% endif %}
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody class="divide-y divide-outline-variant/40">
|
|
32
|
+
{% for inline_admin_form in inline_admin_formset %}
|
|
33
|
+
{% if inline_admin_form.form.non_field_errors %}
|
|
34
|
+
<tr class="row-form-errors"><td colspan="{{ inline_admin_formset.fields|length|add:2 }}" class="px-4 py-2 text-xs text-danger font-semibold">{{ inline_admin_form.form.non_field_errors }}</td></tr>
|
|
35
|
+
{% endif %}
|
|
36
|
+
<tr class="form-row {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form{% endif %} hover:bg-surface-low/20 transition-all {% if inline_admin_form.errors %}bg-rose-50/50{% endif %}"
|
|
37
|
+
id="{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}">
|
|
38
|
+
|
|
39
|
+
<td class="original hidden">
|
|
40
|
+
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
|
41
|
+
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
|
|
42
|
+
</td>
|
|
43
|
+
|
|
44
|
+
{% for fieldset in inline_admin_form %}
|
|
45
|
+
{% for line in fieldset %}
|
|
46
|
+
{% for field in line %}
|
|
47
|
+
{% if not field.field.is_hidden %}
|
|
48
|
+
<td class="px-4 py-2.5 align-middle {% if field.field.name %}field-{{ field.field.name }}{% endif %}">
|
|
49
|
+
{{ field.field.errors }}
|
|
50
|
+
{% if field.field|is_checkbox %}
|
|
51
|
+
<div class="flex justify-center items-center h-full">
|
|
52
|
+
{{ field.field|add_class:"w-4 h-4 rounded border-outline-variant text-primary focus:ring-primary cursor-pointer dark:bg-slate-800" }}
|
|
53
|
+
</div>
|
|
54
|
+
{% elif field.field|is_textarea %}
|
|
55
|
+
{{ field.field|add_class:"w-full px-3 py-1.5 rounded-lg border border-outline-variant bg-surface-lowest text-xs text-on-surface focus:ring-2 focus:ring-primary/10 focus:border-primary transition-all outline-none dark:bg-slate-900" }}
|
|
56
|
+
{% elif field.field|is_select %}
|
|
57
|
+
{{ field.field|add_class:"w-full pl-3 pr-8 py-1.5 bg-surface-lowest border border-outline-variant rounded-lg text-xs focus:ring-2 focus:ring-primary/10 focus:border-primary transition-all outline-none dark:bg-slate-900" }}
|
|
58
|
+
{% else %}
|
|
59
|
+
{{ field.field|add_class:"w-full h-9 px-3 rounded-lg border border-outline-variant bg-surface-lowest text-xs text-on-surface focus:ring-2 focus:ring-primary/10 focus:border-primary transition-all outline-none dark:bg-slate-900" }}
|
|
60
|
+
{% endif %}
|
|
61
|
+
</td>
|
|
62
|
+
{% else %}
|
|
63
|
+
{{ field.field }}
|
|
64
|
+
{% endif %}
|
|
65
|
+
{% endfor %}
|
|
66
|
+
{% endfor %}
|
|
67
|
+
{% endfor %}
|
|
68
|
+
|
|
69
|
+
{% if inline_admin_formset.formset.can_delete %}
|
|
70
|
+
<td class="px-4 py-2.5 align-middle text-center delete">
|
|
71
|
+
{% if inline_admin_form.original %}
|
|
72
|
+
{{ inline_admin_form.deletion_field.field|add_class:"w-4 h-4 rounded border-rose-300 text-rose-600 focus:ring-rose-500 cursor-pointer" }}
|
|
73
|
+
{% endif %}
|
|
74
|
+
</td>
|
|
75
|
+
{% endif %}
|
|
76
|
+
</tr>
|
|
77
|
+
{% endfor %}
|
|
78
|
+
</tbody>
|
|
79
|
+
</table>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
<div class="space-y-1.5 pt-3">
|
|
3
|
+
<h4 class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">
|
|
4
|
+
By {{ title }}
|
|
5
|
+
</h4>
|
|
6
|
+
<ul class="space-y-1 max-h-[160px] overflow-y-auto custom-scrollbar">
|
|
7
|
+
{% for choice in choices %}
|
|
8
|
+
<li class="{% if choice.selected %}font-semibold text-primary{% else %}text-on-surface-variant{% endif %} text-xs">
|
|
9
|
+
<a href="{{ choice.query_string|stringformat:'s' }}"
|
|
10
|
+
class="block px-2.5 py-1.5 rounded-lg hover:bg-surface-low transition-all truncate {% if choice.selected %}bg-primary/10 text-primary font-bold{% else %}hover:text-on-surface{% endif %}"
|
|
11
|
+
title="{{ choice.display }}">
|
|
12
|
+
{{ choice.display }}
|
|
13
|
+
</a>
|
|
14
|
+
</li>
|
|
15
|
+
{% endfor %}
|
|
16
|
+
</ul>
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% load i18n admin_urls %}
|
|
2
|
+
{% if has_export_permission %}
|
|
3
|
+
<a href="{% url opts|admin_urlname:'export' %}{{ cl.get_query_string }}"
|
|
4
|
+
class="flex items-center gap-1.5 px-3.5 py-2 bg-surface-lowest border border-outline-variant text-on-surface-variant hover:text-on-surface hover:bg-surface-low text-xs font-bold rounded-lg shadow-sm transition-all">
|
|
5
|
+
<span class="material-symbols-outlined text-[15px] text-primary">download</span>
|
|
6
|
+
{% translate "Export" %}
|
|
7
|
+
</a>
|
|
8
|
+
{% endif %}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% load i18n admin_urls %}
|
|
2
|
+
{% if has_import_permission %}
|
|
3
|
+
<a href="{% url opts|admin_urlname:"import" %}"
|
|
4
|
+
class="flex items-center gap-1.5 px-3.5 py-2 bg-surface-lowest border border-outline-variant text-on-surface-variant hover:text-on-surface hover:bg-surface-low text-xs font-bold rounded-lg shadow-sm transition-all">
|
|
5
|
+
<span class="material-symbols-outlined text-[15px] text-primary">upload</span>
|
|
6
|
+
{% translate "Import" %}
|
|
7
|
+
</a>
|
|
8
|
+
{% endif %}
|