plain.admin 0.14.1__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.
- plain/admin/README.md +260 -0
- plain/admin/__init__.py +5 -0
- plain/admin/assets/admin/admin.css +108 -0
- plain/admin/assets/admin/admin.js +79 -0
- plain/admin/assets/admin/chart.js +19 -0
- plain/admin/assets/admin/jquery-3.6.1.slim.min.js +2 -0
- plain/admin/assets/admin/list.js +57 -0
- plain/admin/assets/admin/popper.min.js +5 -0
- plain/admin/assets/admin/tippy-bundle.umd.min.js +1 -0
- plain/admin/assets/toolbar/toolbar.js +51 -0
- plain/admin/cards/__init__.py +10 -0
- plain/admin/cards/base.py +86 -0
- plain/admin/cards/charts.py +153 -0
- plain/admin/cards/tables.py +26 -0
- plain/admin/config.py +21 -0
- plain/admin/dates.py +254 -0
- plain/admin/default_settings.py +4 -0
- plain/admin/impersonate/README.md +44 -0
- plain/admin/impersonate/__init__.py +3 -0
- plain/admin/impersonate/middleware.py +38 -0
- plain/admin/impersonate/models.py +0 -0
- plain/admin/impersonate/permissions.py +16 -0
- plain/admin/impersonate/settings.py +8 -0
- plain/admin/impersonate/urls.py +10 -0
- plain/admin/impersonate/views.py +23 -0
- plain/admin/middleware.py +12 -0
- plain/admin/querystats/README.md +191 -0
- plain/admin/querystats/__init__.py +3 -0
- plain/admin/querystats/core.py +153 -0
- plain/admin/querystats/middleware.py +99 -0
- plain/admin/querystats/urls.py +9 -0
- plain/admin/querystats/views.py +27 -0
- plain/admin/templates/admin/base.html +160 -0
- plain/admin/templates/admin/cards/base.html +30 -0
- plain/admin/templates/admin/cards/card.html +17 -0
- plain/admin/templates/admin/cards/chart.html +25 -0
- plain/admin/templates/admin/cards/table.html +35 -0
- plain/admin/templates/admin/delete.html +17 -0
- plain/admin/templates/admin/detail.html +24 -0
- plain/admin/templates/admin/form.html +13 -0
- plain/admin/templates/admin/index.html +5 -0
- plain/admin/templates/admin/list.html +194 -0
- plain/admin/templates/admin/page.html +3 -0
- plain/admin/templates/admin/search.html +27 -0
- plain/admin/templates/admin/values/UUID.html +1 -0
- plain/admin/templates/admin/values/bool.html +9 -0
- plain/admin/templates/admin/values/datetime.html +1 -0
- plain/admin/templates/admin/values/default.html +5 -0
- plain/admin/templates/admin/values/dict.html +1 -0
- plain/admin/templates/admin/values/get_display.html +1 -0
- plain/admin/templates/admin/values/img.html +4 -0
- plain/admin/templates/admin/values/list.html +1 -0
- plain/admin/templates/admin/values/model.html +15 -0
- plain/admin/templates/admin/values/queryset.html +7 -0
- plain/admin/templates/elements/admin/Checkbox.html +8 -0
- plain/admin/templates/elements/admin/CheckboxField.html +7 -0
- plain/admin/templates/elements/admin/FieldErrors.html +5 -0
- plain/admin/templates/elements/admin/Input.html +9 -0
- plain/admin/templates/elements/admin/InputField.html +5 -0
- plain/admin/templates/elements/admin/Label.html +3 -0
- plain/admin/templates/elements/admin/Select.html +11 -0
- plain/admin/templates/elements/admin/SelectField.html +5 -0
- plain/admin/templates/elements/admin/Submit.html +6 -0
- plain/admin/templates/querystats/querystats.html +78 -0
- plain/admin/templates/querystats/toolbar.html +79 -0
- plain/admin/templates/toolbar/toolbar.html +91 -0
- plain/admin/templates.py +25 -0
- plain/admin/toolbar.py +36 -0
- plain/admin/urls.py +45 -0
- plain/admin/views/__init__.py +41 -0
- plain/admin/views/base.py +140 -0
- plain/admin/views/models.py +254 -0
- plain/admin/views/objects.py +399 -0
- plain/admin/views/registry.py +117 -0
- plain/admin/views/types.py +6 -0
- plain/admin/views/viewsets.py +54 -0
- plain_admin-0.14.1.dist-info/METADATA +275 -0
- plain_admin-0.14.1.dist-info/RECORD +80 -0
- plain_admin-0.14.1.dist-info/WHEEL +4 -0
- plain_admin-0.14.1.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
{% extends "admin/cards/base.html" %}
|
2
|
+
|
3
|
+
{% block content %}
|
4
|
+
|
5
|
+
{% if number is not none %}
|
6
|
+
<div class="text-4xl font-bold">
|
7
|
+
{{ number }}
|
8
|
+
</div>
|
9
|
+
{% endif %}
|
10
|
+
|
11
|
+
{% if link %}
|
12
|
+
<a href="{{ link }}">{{ text }}</a>
|
13
|
+
{% elif text %}
|
14
|
+
{{ text }}
|
15
|
+
{% endif %}
|
16
|
+
|
17
|
+
{% endblock %}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
{% extends "admin/cards/base.html" %}
|
2
|
+
|
3
|
+
{% block content %}
|
4
|
+
<canvas id="card-chart-{{ slug }}"></canvas>
|
5
|
+
{{ chart_data|json_script(slug) }}
|
6
|
+
<script>
|
7
|
+
(function() {
|
8
|
+
function renderChart() {
|
9
|
+
const ctx = document.getElementById('card-chart-{{ slug }}');
|
10
|
+
|
11
|
+
const data = JSON.parse(document.getElementById('{{ slug }}').textContent);
|
12
|
+
|
13
|
+
if (ctx._chart) {
|
14
|
+
ctx._chart.destroy();
|
15
|
+
}
|
16
|
+
|
17
|
+
const chart = new Chart(ctx, data);
|
18
|
+
ctx._chart = chart;
|
19
|
+
}
|
20
|
+
|
21
|
+
document.addEventListener('DOMContentLoaded', renderChart);
|
22
|
+
document.addEventListener('htmx:afterSwap', renderChart);
|
23
|
+
})();
|
24
|
+
</script>
|
25
|
+
{% endblock %}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
{% extends "admin/cards/base.html" %}
|
2
|
+
|
3
|
+
{% block content %}
|
4
|
+
<div class="flex-grow w-full">
|
5
|
+
<table>
|
6
|
+
{% if headers %}
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
{% for header in headers %}
|
10
|
+
<th>{{ header }}</th>
|
11
|
+
{% endfor %}
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
{% endif %}
|
15
|
+
<tbody>
|
16
|
+
{% for row in rows %}
|
17
|
+
<tr>
|
18
|
+
{% for cell in row %}
|
19
|
+
<td>{{ cell }}</td>
|
20
|
+
{% endfor %}
|
21
|
+
</tr>
|
22
|
+
{% endfor %}
|
23
|
+
</tbody>
|
24
|
+
{% if footers %}
|
25
|
+
<tfoot>
|
26
|
+
<tr>
|
27
|
+
{% for footer in footers %}
|
28
|
+
<td>{{ footer }}</td>
|
29
|
+
{% endfor %}
|
30
|
+
</tr>
|
31
|
+
</tfoot>
|
32
|
+
{% endif %}
|
33
|
+
</table>
|
34
|
+
</div>
|
35
|
+
{% endblock %}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
{% extends "admin/base.html" %}
|
2
|
+
|
3
|
+
{% block content %}
|
4
|
+
|
5
|
+
<div class="border border-red-500 rounded">
|
6
|
+
<form method="post">
|
7
|
+
{{ csrf_input }}
|
8
|
+
<div class="p-4">
|
9
|
+
<p>Are you sure you want to delete this?</p>
|
10
|
+
<button class="px-5 py-2 mt-6 text-white bg-red-600 rounded hover:bg-red-700" type="submit">
|
11
|
+
Delete
|
12
|
+
</button>
|
13
|
+
</div>
|
14
|
+
</form>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
{% endblock %}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{% extends "admin/base.html" %}
|
2
|
+
|
3
|
+
{% block content %}
|
4
|
+
|
5
|
+
<button
|
6
|
+
class="text-xs"
|
7
|
+
data-toggle=".raw-value, .pretty-value"
|
8
|
+
type="button">
|
9
|
+
Toggle raw values
|
10
|
+
</button>
|
11
|
+
|
12
|
+
<dl class="text-sm mt-3 w-full grid grid-cols-[max-content,1fr] gap-y-2 gap-x-8" style="grid-template-columns: max-content 1fr;">
|
13
|
+
{% for field in fields %}
|
14
|
+
<dt class="font-semibold"><code>{{ field }}</code></dt>
|
15
|
+
<dd class="flex items-center">
|
16
|
+
{% with value=get_field_value(object, field) %}
|
17
|
+
<div class="raw-value hidden"><code>{{ value }}</code></div>
|
18
|
+
<div class="pretty-value">{% include get_field_value_template(object, field, value) with context %}</div>
|
19
|
+
{% endwith %}
|
20
|
+
</dd>
|
21
|
+
{% endfor %}
|
22
|
+
</dl>
|
23
|
+
|
24
|
+
{% endblock %}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
{% extends "admin/base.html" %}
|
2
|
+
|
3
|
+
{% block content %}
|
4
|
+
|
5
|
+
<form method="post">
|
6
|
+
{{ csrf_input }}
|
7
|
+
{% block form_content %}{% endblock %}
|
8
|
+
<button class="px-5 py-2 mt-6 text-white bg-blue-600 rounded hover:bg-blue-700" type="submit">
|
9
|
+
Save
|
10
|
+
</button>
|
11
|
+
</form>
|
12
|
+
|
13
|
+
{% endblock %}
|
@@ -0,0 +1,194 @@
|
|
1
|
+
{% extends "admin/base.html" %}
|
2
|
+
|
3
|
+
{% block header_scripts %}
|
4
|
+
<script src="{{ asset('admin/list.js') }}" defer></script>
|
5
|
+
{% endblock %}
|
6
|
+
|
7
|
+
{% block content %}
|
8
|
+
|
9
|
+
{% htmxfragment "list" %}
|
10
|
+
|
11
|
+
<header class="flex items-center justify-between">
|
12
|
+
<div class="text-sm text-white/50">
|
13
|
+
{% if table_style == "simple" %}
|
14
|
+
<div class="mb-3">{{ title }}</div>
|
15
|
+
{% else %}
|
16
|
+
{% if page.has_other_pages() %}
|
17
|
+
Page {{ page.number }} of {{ page.paginator.num_pages }} ({{ page.paginator.count }} results)
|
18
|
+
{% else %}
|
19
|
+
Showing all {{ page.paginator.count }} results
|
20
|
+
{% endif %}
|
21
|
+
{% endif %}
|
22
|
+
</div>
|
23
|
+
<div class="flex space-x-5">
|
24
|
+
{% if table_style == "simple" %}
|
25
|
+
<a class="text-sm" href="{{ request.get_full_path() }}">View {{ page.paginator.count }} result{{ "s" if page.paginator.count != 1 else "" }}</a>
|
26
|
+
{% else %}
|
27
|
+
{% if actions %}
|
28
|
+
<form method="POST" data-actions-form>
|
29
|
+
{{ csrf_input }}
|
30
|
+
<select name="action_name" class="text-sm border-gray-200 rounded-md">
|
31
|
+
<option value="">Actions</option>
|
32
|
+
{% for action in actions %}
|
33
|
+
<option>{{ action }}</option>
|
34
|
+
{% endfor %}
|
35
|
+
</select>
|
36
|
+
<input type="hidden" name="action_pks" value="" />
|
37
|
+
<button type="submit" disabled>Apply</button>
|
38
|
+
</form>
|
39
|
+
{% endif %}
|
40
|
+
|
41
|
+
<form method="GET" class="inline-flex space-x-5">
|
42
|
+
{% if displays %}
|
43
|
+
<select data-autosubmit name="display" class="text-sm border-gray-200 rounded-md">
|
44
|
+
<option value="">Displays</option>
|
45
|
+
{% for display in displays %}
|
46
|
+
<option {% if display == current_display %}selected{% endif %}>{{ display }}</option>
|
47
|
+
{% endfor %}
|
48
|
+
</select>
|
49
|
+
{% endif %}
|
50
|
+
|
51
|
+
{% if show_search %}
|
52
|
+
<div class="flex justify-end">
|
53
|
+
<div class="relative max-w-xs">
|
54
|
+
<label for="search" class="sr-only">Search</label>
|
55
|
+
<input
|
56
|
+
{% if search_query %}value="{{ search_query }}"{% endif %}
|
57
|
+
type="text"
|
58
|
+
name="search"
|
59
|
+
id="search"
|
60
|
+
class="block w-full px-3 pl-10 text-sm border-white/10 bg-white/5 rounded-md focus:border-blue-500 focus:ring-blue-500"
|
61
|
+
placeholder="Search"
|
62
|
+
>
|
63
|
+
<div class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
64
|
+
<svg class="h-3.5 w-3.5 text-gray-400" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
65
|
+
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"></path>
|
66
|
+
</svg>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
{% endif %}
|
71
|
+
</form>
|
72
|
+
{% endif %}
|
73
|
+
</div>
|
74
|
+
</header>
|
75
|
+
|
76
|
+
<div class="w-full overflow-auto mt-2">
|
77
|
+
<table class="text-sm table-auto">
|
78
|
+
<thead>
|
79
|
+
<tr class="bg-white/5 [&>th]:py-2 [&>:first-child]:rounded-l-md [&>:last-child]:rounded-r-md">
|
80
|
+
{% if table_style != "simple" and actions %}<th></th>{% endif %}
|
81
|
+
|
82
|
+
{% for field in fields %}
|
83
|
+
{% if order_by_field is defined %}
|
84
|
+
<th>
|
85
|
+
{% if table_style == "simple" %}
|
86
|
+
<div class="font-mono text-xs font-normal text-white/60">
|
87
|
+
{{ field }}
|
88
|
+
</div>
|
89
|
+
{% else %}
|
90
|
+
<a
|
91
|
+
data-merge-params
|
92
|
+
class="font-mono text-xs font-normal text-white/60"
|
93
|
+
href="?page=1&order_by={{ '-' if not order_by_direction else '' }}{{ field }}">
|
94
|
+
{{ field }}
|
95
|
+
{% if field == order_by_field %}
|
96
|
+
{% if order_by_direction == "-" %}
|
97
|
+
<span class="text-xs">▲</span>
|
98
|
+
{% else %}
|
99
|
+
<span class="text-xs">▼</span>
|
100
|
+
{% endif %}
|
101
|
+
{% endif %}
|
102
|
+
</a>
|
103
|
+
{% endif %}
|
104
|
+
</th>
|
105
|
+
{% else %}
|
106
|
+
<th class="font-mono text-xs font-normal text-white/60">{{ field }}</th>
|
107
|
+
{% endif %}
|
108
|
+
{% endfor %}
|
109
|
+
|
110
|
+
{# A th that spans any links columns that will be added #}
|
111
|
+
<th colspan="99"></th>
|
112
|
+
</tr>
|
113
|
+
</thead>
|
114
|
+
<tbody>
|
115
|
+
{% for object in objects %}
|
116
|
+
<tr>
|
117
|
+
|
118
|
+
{% set row_url = get_object_url(object) %}
|
119
|
+
|
120
|
+
{% if table_style != "simple" and actions %}
|
121
|
+
<td class="p-0 pl-1">
|
122
|
+
<input data-action-checkbox class="rounded-sm" type="checkbox" name="{{ get_object_pk(object) }}" />
|
123
|
+
</td>
|
124
|
+
{% endif %}
|
125
|
+
|
126
|
+
{% for field in fields %}
|
127
|
+
{# Make every column clickable if it doesn't contain a link already #}
|
128
|
+
<td data-column-autolink="{{ row_url }}">
|
129
|
+
<div class="flex">
|
130
|
+
{% set value = get_field_value(object, field) %}
|
131
|
+
{% include get_field_value_template(object, field, value) with context %}
|
132
|
+
</div>
|
133
|
+
</td>
|
134
|
+
{% endfor %}
|
135
|
+
|
136
|
+
{% set object_links = get_object_links(object) %}
|
137
|
+
{% if object_links %}
|
138
|
+
<td class="py-0">
|
139
|
+
<button data-dropdown class="inline-flex rounded-md border border-transparent hover:border-white/10 hover:shadow-sm px-3 py-1.5 hover:bg-white/20 text-sm font-medium text-white/80 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
140
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-three-dots" viewBox="0 0 16 16">
|
141
|
+
<path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3m5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3"/>
|
142
|
+
</svg>
|
143
|
+
<template>
|
144
|
+
<div class="py-1">
|
145
|
+
{% for text, url in object_links.items() %}
|
146
|
+
<a href="{{ url }}" class="block px-4 py-2 text-sm text-white/80 hover:bg-white/10 rounded">
|
147
|
+
{{ text }}
|
148
|
+
</a>
|
149
|
+
{% endfor %}
|
150
|
+
</div>
|
151
|
+
</template>
|
152
|
+
</button>
|
153
|
+
</td>
|
154
|
+
{% endif %}
|
155
|
+
</tr>
|
156
|
+
{% endfor %}
|
157
|
+
</tbody>
|
158
|
+
</table>
|
159
|
+
</div>
|
160
|
+
|
161
|
+
{% if table_style != "simple" %}
|
162
|
+
<footer class="mt-4">
|
163
|
+
<div class="flex items-center justify-between">
|
164
|
+
<div class="py-2 text-sm text-gray-500">
|
165
|
+
{% if page.has_other_pages() %}
|
166
|
+
Page {{ page.number }} of {{ page.paginator.num_pages }} ({{ page.paginator.count }} results)
|
167
|
+
{% endif %}
|
168
|
+
</div>
|
169
|
+
{% if page.has_other_pages() %}
|
170
|
+
<div class="flex items-center justify-center space-x-2">
|
171
|
+
{% if page.has_previous() %}
|
172
|
+
<a data-merge-params href="?page={{ page.previous_page_number() }}">‹</a>
|
173
|
+
{% endif %}
|
174
|
+
<form data-autosubmit method="GET">
|
175
|
+
{% if show_search and search_query %}<input type="hidden" name="search" value="{{ search_query }}" />{% endif %}
|
176
|
+
{% if displays and current_display %}<input type="hidden" name="display" value="{{ current_display }}" />{% endif %}
|
177
|
+
<select name="page" class="text-xs border-white/10 bg-white/5 rounded-md">
|
178
|
+
{% for page_num in page.paginator.page_range %}
|
179
|
+
<option value="{{ page_num }}" {% if page_num == page.number %}selected{% endif %}>Page {{ page_num }}</option>
|
180
|
+
{% endfor %}
|
181
|
+
</select>
|
182
|
+
</form>
|
183
|
+
{% if page.has_next() %}
|
184
|
+
<a data-merge-params href="?page={{ page.next_page_number() }}">›</a>
|
185
|
+
{% endif %}
|
186
|
+
</div>
|
187
|
+
{% endif %}
|
188
|
+
</div>
|
189
|
+
</footer>
|
190
|
+
{% endif %}
|
191
|
+
|
192
|
+
{% endhtmxfragment %}
|
193
|
+
|
194
|
+
{% endblock %}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
{% extends "admin/base.html" %}
|
2
|
+
|
3
|
+
{% block title %}
|
4
|
+
{%- if global_search_query -%}
|
5
|
+
Search results for "{{ global_search_query }}"
|
6
|
+
{%- else -%}
|
7
|
+
Search
|
8
|
+
{%- endif -%}
|
9
|
+
{% endblock %}
|
10
|
+
|
11
|
+
{% block content %}
|
12
|
+
|
13
|
+
{% if global_search_query %}
|
14
|
+
<div class="*:mt-14 *:empty:mt-0">
|
15
|
+
{% for view in searchable_views %}
|
16
|
+
<div
|
17
|
+
hx-get="{{ view.get_view_url() }}?search={{ global_search_query }}&page_size=5"
|
18
|
+
hx-trigger="plainhtmx:load from:body"
|
19
|
+
plain-hx-fragment="list">
|
20
|
+
</div>
|
21
|
+
{% endfor %}
|
22
|
+
</div>
|
23
|
+
{% else %}
|
24
|
+
<p class="text-stone-500">Enter a search query in the top bar</p>
|
25
|
+
{% endif %}
|
26
|
+
|
27
|
+
{% endblock %}
|
@@ -0,0 +1 @@
|
|
1
|
+
<code class="px-1 text-xs bg-white/10 rounded">{{ value }}</code>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
{%- if value -%}
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-3 h-3 text-green-400 bi bi-check-circle-fill" viewBox="0 0 16 16">
|
3
|
+
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
|
4
|
+
</svg>
|
5
|
+
{%- else -%}
|
6
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-3 h-3 text-red-400 bi bi-x-circle-fill" viewBox="0 0 16 16">
|
7
|
+
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
|
8
|
+
</svg>
|
9
|
+
{%- endif %}
|
@@ -0,0 +1 @@
|
|
1
|
+
<span class="tabular-nums" title="{{ value.isoformat() }}">{{ value|localtime|strftime("%Y-%m-%d %-I:%M:%S %p") }}</span>
|
@@ -0,0 +1 @@
|
|
1
|
+
<pre><code>{{ value|tojson(indent=2) }}</code></pre>
|
@@ -0,0 +1 @@
|
|
1
|
+
<span title="{{ value }}">{{ object['get_' + field + '_display']() }}</span>
|
@@ -0,0 +1 @@
|
|
1
|
+
<pre><code>{{ value|tojson(indent=2) }}</code></pre>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{% if value %}
|
2
|
+
|
3
|
+
{% with url = value|get_admin_model_detail_url %}
|
4
|
+
{% if url %}
|
5
|
+
<a href="{{ url }}" class="hover:underline">{{ value }}</a>
|
6
|
+
{% elif value.get_absolute_url is defined %}
|
7
|
+
<a href="{{ value.get_absolute_url() }}" class="hover:underline">{{ value }}</a>
|
8
|
+
{% else %}
|
9
|
+
<div>{{ value }}</div>
|
10
|
+
{% endif %}
|
11
|
+
{% endwith %}
|
12
|
+
|
13
|
+
{% else %}
|
14
|
+
<span class="italic">None</span>
|
15
|
+
{% endif %}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<input
|
2
|
+
id="{{ field.html_id }}"
|
3
|
+
type="{{ type|default('text') }}"
|
4
|
+
name="{{ field.html_name }}"
|
5
|
+
class="block w-full text-sm border rounded border-white/10 bg-white/5"
|
6
|
+
{% if list is defined %}list="{{ list }}"{% endif %}
|
7
|
+
{% if field.value() != None %}value="{{ field.value() }}"{% endif %}
|
8
|
+
{% if field.field.required %}required{% endif %}
|
9
|
+
>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<select
|
2
|
+
id="{{ field.html_id }}"
|
3
|
+
name="{{ field.html_name }}"
|
4
|
+
class="block w-full text-sm border rounded border-white/10 bg-white/5"
|
5
|
+
{% if field.value() != None %}value="{{ field.value() }}"{% endif %}
|
6
|
+
{% if field.field.required %}required{% endif %}
|
7
|
+
>
|
8
|
+
{% for value, label in field.field.choices %}
|
9
|
+
<option value="{{ value }}" {% if value == field.value() %}selected{% endif %}>{{ label }}</option>
|
10
|
+
{% endfor %}
|
11
|
+
</select>
|
@@ -0,0 +1,78 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Document</title>
|
7
|
+
{% tailwind_css %}
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<div class="px-6 py-4">
|
12
|
+
<div class="flex items-center justify-between">
|
13
|
+
<h2 class="text-xl font-medium">Query stats for {{ request.path }}</h2>
|
14
|
+
<div class="flex items-center">
|
15
|
+
<div class="pt-1">
|
16
|
+
{{ querystats.summary }}
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<form action="." method="get">
|
20
|
+
<input type="hidden" name="querystats" value="store">
|
21
|
+
<button type="submit" class="flex items-center px-3 py-2 ml-4 text-sm rounded-full bg-zinc-600 hover:bg-zinc-500">
|
22
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 mr-2" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-cw"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
23
|
+
reload
|
24
|
+
</button>
|
25
|
+
</form>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
{#
|
29
|
+
<div class="mt-2 font-mono text-xs">
|
30
|
+
{{ querystats_resolver_match }}
|
31
|
+
and template {{ querystats_template_name }}
|
32
|
+
</div>
|
33
|
+
#}
|
34
|
+
|
35
|
+
<div class="flex w-full mt-5 overflow-auto rounded-sm">
|
36
|
+
{% for query in querystats.queries %}
|
37
|
+
<a href="#query-{{ loop.index }}"
|
38
|
+
{{ loop.cycle('class=\"h-4 bg-amber-400\"', 'class="h-4 bg-amber-500"', 'class="h-4 bg-amber-600"')|safe }}
|
39
|
+
title="[{{ query.duration_display }}] {{ query.sql_display }}"
|
40
|
+
style="width: {{ query.duration / querystats.total_time * 100 }}%">
|
41
|
+
</a>
|
42
|
+
{% endfor %}
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div class="mt-4 space-y-4 text-sm">
|
46
|
+
{% for query in querystats.queries %}
|
47
|
+
<div id="query-{{ loop.index }}" class="p-2 rounded bg-zinc-800">
|
48
|
+
<div class="float-right px-2 py-px mb-px ml-2 text-xs rounded-full bg-zinc-700">
|
49
|
+
<span>{{ query.duration_display }}</span>
|
50
|
+
{% if query.duplicate_count is defined %}
|
51
|
+
<span class="text-red-500"> duplicated {{ query.duplicate_count }} times</span>
|
52
|
+
{% endif %}
|
53
|
+
|
54
|
+
{#
|
55
|
+
<div>many {{ query.many }}</div>
|
56
|
+
<div>result {{ query.result }}</div>
|
57
|
+
#}
|
58
|
+
</div>
|
59
|
+
<div>
|
60
|
+
<pre><code class="font-mono whitespace-pre-wrap text-zinc-100">{{ query.sql_display }}</code></pre>
|
61
|
+
</div>
|
62
|
+
<div class="mt-3 text-zinc-400">
|
63
|
+
<span class="font-medium">Parameters</span>
|
64
|
+
<pre><code class="font-mono">{{ query.params|pprint }}</code></pre>
|
65
|
+
</div>
|
66
|
+
<details class="mt-3">
|
67
|
+
<summary>Traceback</summary>
|
68
|
+
<pre><code class="block overflow-x-auto font-mono text-xs">{{ query.tb }}</code></pre>
|
69
|
+
</details>
|
70
|
+
</div>
|
71
|
+
{% else %}
|
72
|
+
<div>No queries...</div>
|
73
|
+
{% endfor %}
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
|
77
|
+
</body>
|
78
|
+
</html>
|