plain.admin 0.28.0__tar.gz → 0.29.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.
- {plain_admin-0.28.0 → plain_admin-0.29.0}/PKG-INFO +1 -1
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/toolbar/toolbar.js +6 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/querystats/core.py +2 -1
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/querystats/middleware.py +8 -7
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/querystats/views.py +0 -5
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/querystats/querystats.html +4 -2
- plain_admin-0.29.0/plain/admin/templates/toolbar/exception.html +16 -0
- plain_admin-0.29.0/plain/admin/templates/toolbar/querystats.html +28 -0
- plain_admin-0.29.0/plain/admin/templates/toolbar/request.html +60 -0
- plain_admin-0.29.0/plain/admin/templates/toolbar/toolbar.html +94 -0
- plain_admin-0.29.0/plain/admin/toolbar.py +109 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/pyproject.toml +1 -1
- plain_admin-0.28.0/plain/admin/templates/toolbar/toolbar.html +0 -245
- plain_admin-0.28.0/plain/admin/toolbar.py +0 -34
- {plain_admin-0.28.0 → plain_admin-0.29.0}/.gitignore +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/LICENSE +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/README.md +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/README.md +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/__init__.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/admin/admin.css +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/admin/admin.js +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/admin/chart.js +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/admin/jquery-3.6.1.slim.min.js +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/admin/list.js +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/admin/popper.min.js +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/assets/admin/tippy-bundle.umd.min.js +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/cards/__init__.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/cards/base.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/cards/charts.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/cards/tables.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/config.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/dates.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/default_settings.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/README.md +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/__init__.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/middleware.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/models.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/permissions.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/settings.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/urls.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/impersonate/views.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/middleware.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/querystats/README.md +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/querystats/__init__.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/querystats/urls.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/base.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/cards/base.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/cards/card.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/cards/chart.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/cards/table.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/delete.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/detail.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/index.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/list.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/page.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/search.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/UUID.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/bool.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/datetime.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/default.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/dict.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/get_display.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/img.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/list.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/model.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/queryset.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Checkbox.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/CheckboxField.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/FieldErrors.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Help.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Input.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/InputField.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Label.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Select.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/SelectField.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Submit.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Textarea.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/TextareaField.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/querystats/toolbar.html +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/urls.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/views/__init__.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/views/base.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/views/models.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/views/objects.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/views/registry.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/views/types.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/views/viewsets.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/tests/app/settings.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/tests/app/urls.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/tests/app/users/migrations/0001_initial.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/tests/app/users/migrations/__init__.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/tests/app/users/models.py +0 -0
- {plain_admin-0.28.0 → plain_admin-0.29.0}/tests/test_admin.py +0 -0
@@ -50,6 +50,12 @@ var plainToolbar = {
|
|
50
50
|
var toolbar = document.querySelector("#plaintoolbar");
|
51
51
|
var tab = toolbar.querySelector("div[data-toolbar-tab=" + tabName + "]");
|
52
52
|
|
53
|
+
// If the tab doesn't exist for some reason, quit
|
54
|
+
if (!tab) {
|
55
|
+
console.warn("Toolbar tab " + tabName + " does not exist");
|
56
|
+
return;
|
57
|
+
}
|
58
|
+
|
53
59
|
// Hide all children in the tab parent
|
54
60
|
for (var i = 0; i < tab.parentNode.children.length; i++) {
|
55
61
|
var child = tab.parentNode.children[i];
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import datetime
|
1
2
|
import time
|
2
3
|
import traceback
|
3
4
|
from collections import Counter
|
@@ -143,7 +144,7 @@ class QueryStats:
|
|
143
144
|
"method": request.method,
|
144
145
|
"unique_id": request.unique_id,
|
145
146
|
},
|
146
|
-
"timestamp":
|
147
|
+
"timestamp": datetime.datetime.now().isoformat(),
|
147
148
|
"total_time_display": self.total_time_display,
|
148
149
|
"queries": self.queries,
|
149
150
|
}
|
@@ -55,14 +55,15 @@ class QueryStatsMiddleware:
|
|
55
55
|
if self.should_ignore_request(request):
|
56
56
|
return self.get_response(request)
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
def is_tracking():
|
59
|
+
return "querystats" in request.session
|
60
|
+
|
61
|
+
querystats = QueryStats(include_tracebacks=is_tracking())
|
62
|
+
|
63
|
+
with connection.execute_wrapper(querystats):
|
64
|
+
is_admin = self.is_admin_request(request)
|
62
65
|
|
63
66
|
if settings.DEBUG or is_admin:
|
64
|
-
tracking = "querystats" in request.session
|
65
|
-
querystats = QueryStats(include_tracebacks=tracking)
|
66
67
|
with connection.execute_wrapper(querystats):
|
67
68
|
response = self.get_response(request)
|
68
69
|
|
@@ -74,7 +75,7 @@ class QueryStatsMiddleware:
|
|
74
75
|
# by using the server timing API which can be parsed client-side
|
75
76
|
response.headers["Server-Timing"] = querystats.as_server_timing()
|
76
77
|
|
77
|
-
if
|
78
|
+
if is_tracking() and querystats.num_queries > 0:
|
78
79
|
request.session["querystats"][request.unique_id] = json.dumps(
|
79
80
|
querystats.as_context_dict(request), cls=QueryStatsJSONEncoder
|
80
81
|
)
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import datetime
|
2
1
|
import json
|
3
2
|
|
4
3
|
from plain.auth.views import AuthViewMixin
|
@@ -56,10 +55,6 @@ class QuerystatsView(AuthViewMixin, TemplateView):
|
|
56
55
|
)
|
57
56
|
)
|
58
57
|
|
59
|
-
# Convert the timestamps back to a python datetime object
|
60
|
-
for data in querystats.values():
|
61
|
-
data["timestamp"] = datetime.datetime.fromtimestamp(data["timestamp"])
|
62
|
-
|
63
58
|
context["querystats"] = querystats
|
64
59
|
context["querystats_enabled"] = "querystats" in self.request.session
|
65
60
|
|
@@ -41,7 +41,7 @@
|
|
41
41
|
<span class="text-sm">{{ qs.request.path }}</span>
|
42
42
|
<span class="font-semibold bg-white/5 rounded-sm px-1 py-0.5 text-xs">{{ qs.request.method }}</span>
|
43
43
|
<div class="text-xs text-stone-400">{{ qs.summary }}</div>
|
44
|
-
<div class="text-xs text-stone-500">{{ qs.timestamp|timesince }} ago</div>
|
44
|
+
<div class="text-xs text-stone-500">{{ qs.timestamp|fromisoformat|timesince }} ago</div>
|
45
45
|
</button>
|
46
46
|
</li>
|
47
47
|
{% endfor %}
|
@@ -58,7 +58,7 @@
|
|
58
58
|
</div>
|
59
59
|
<div class="text-right">
|
60
60
|
<div class="text-xs text-white/60">Request ID <code>{{ qs.request.unique_id }}</code></div>
|
61
|
-
<div class="text-xs text-white/60"><code>{{ qs.timestamp }}</code></div>
|
61
|
+
<div class="text-xs text-white/60"><code>{{ qs.timestamp|fromisoformat }}</code></div>
|
62
62
|
</div>
|
63
63
|
</div>
|
64
64
|
|
@@ -92,10 +92,12 @@
|
|
92
92
|
<span class="font-medium">Parameters</span>
|
93
93
|
<pre><code class="font-mono">{{ query.params|pprint }}</code></pre>
|
94
94
|
</div>
|
95
|
+
{% if query.tb|default(false) %}
|
95
96
|
<details>
|
96
97
|
<summary>Traceback</summary>
|
97
98
|
<pre><code class="block overflow-x-auto font-mono text-xs">{{ query.tb }}</code></pre>
|
98
99
|
</details>
|
100
|
+
{% endif %}
|
99
101
|
</div>
|
100
102
|
</details>
|
101
103
|
{% else %}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<div class="px-6 py-4">
|
2
|
+
<div class="p-2 mb-5 border-amber-500 border rounded">
|
3
|
+
<div class="text-amber-500 text-lg flex justify-between items-center">
|
4
|
+
<div>
|
5
|
+
<span class="font-bold">Exception</span>
|
6
|
+
{{ exception }}
|
7
|
+
</div>
|
8
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-5 h-5 bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
|
9
|
+
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
|
10
|
+
</svg>
|
11
|
+
</div>
|
12
|
+
<div class="text-amber-400 text-xs mt-3 bg-white/5 p-2 rounded overflow-auto">
|
13
|
+
<pre><code>{{ exception._traceback_string }}</code></pre>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<div id="querystats-container" class="h-full">
|
2
|
+
<div class="px-6 py-4 text-center">
|
3
|
+
<p>Loading querystats...</p>
|
4
|
+
</div>
|
5
|
+
</div>
|
6
|
+
<script>
|
7
|
+
(function() {
|
8
|
+
var container = document.getElementById('querystats-container');
|
9
|
+
var loaded = false;
|
10
|
+
var parent = container.parentNode;
|
11
|
+
var observer = new IntersectionObserver(function(entries) {
|
12
|
+
entries.forEach(function(entry) {
|
13
|
+
if (entry.isIntersecting && !loaded) {
|
14
|
+
loaded = true;
|
15
|
+
var iframe = document.createElement('iframe');
|
16
|
+
iframe.src = "{{ url('admin:querystats:querystats') }}";
|
17
|
+
iframe.frameBorder = "0";
|
18
|
+
iframe.style.width = "100%";
|
19
|
+
iframe.style.height = "100%";
|
20
|
+
container.innerHTML = '';
|
21
|
+
container.appendChild(iframe);
|
22
|
+
observer.disconnect();
|
23
|
+
}
|
24
|
+
});
|
25
|
+
}, { root: parent, threshold: 0 });
|
26
|
+
observer.observe(container);
|
27
|
+
})();
|
28
|
+
</script>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<dl class="text-sm grid grid-cols-1 sm:grid-cols-[max-content_1fr] sm:gap-y-2 gap-x-8 px-6 py-4">
|
2
|
+
<dt>Request ID</dt>
|
3
|
+
<dd class="text-sm text-white/50">{{ request.unique_id }}</dd>
|
4
|
+
|
5
|
+
<dt>Query params</dt>
|
6
|
+
<dd class="text-sm text-white/50">{{ request.query_params }}</dd>
|
7
|
+
|
8
|
+
<dt>Method</dt>
|
9
|
+
<dd class="text-sm text-white/50">{{ request.method }}</dd>
|
10
|
+
|
11
|
+
{% if request.resolver_match %}
|
12
|
+
<dt>View</dt>
|
13
|
+
<dd class="text-sm text-white/50">{{ request.resolver_match.view.view_class|pprint }}</dd>
|
14
|
+
|
15
|
+
<dt>URL pattern</dt>
|
16
|
+
<dd class="text-sm text-white/50">
|
17
|
+
<pre><code>{{ request.resolver_match.route }}</code></pre>
|
18
|
+
</dd>
|
19
|
+
|
20
|
+
<dt>URL name</dt>
|
21
|
+
<dd class="text-sm text-white/50">
|
22
|
+
<pre><code>{{ request.resolver_match.namespaced_url_name }}</code></pre>
|
23
|
+
</dd>
|
24
|
+
|
25
|
+
<dt>URL args</dt>
|
26
|
+
<dd class="text-sm text-white/50">
|
27
|
+
<pre><code>{{ request.resolver_match.args }}</code></pre>
|
28
|
+
</dd>
|
29
|
+
|
30
|
+
<dt>URL kwargs</dt>
|
31
|
+
<dd class="text-sm text-white/50">
|
32
|
+
<pre><code>{{ request.resolver_match.kwargs }}</code></pre>
|
33
|
+
</dd>
|
34
|
+
{% endif %}
|
35
|
+
|
36
|
+
{% if template_names is defined %}
|
37
|
+
<dt>Template names</dt>
|
38
|
+
<dd class="text-sm text-white/50">
|
39
|
+
<pre><code>{{ template_names }}</code></pre>
|
40
|
+
</dd>
|
41
|
+
{% endif %}
|
42
|
+
|
43
|
+
{% if object|default(false) %}
|
44
|
+
<dt>Primary object</dt>
|
45
|
+
<dd class="text-sm text-white/50 inline-flex items-center" title="PK: {{ object.pk|default('unknown') }}">
|
46
|
+
<pre><code>{{ object.__repr__() }}</code></pre>
|
47
|
+
{% if object|get_admin_model_detail_url %}
|
48
|
+
<a class="ml-2 inline-flex items-center p-1 text-blue-500 hover:text-blue-400" href="{{ object|get_admin_model_detail_url }}">
|
49
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-3 h-3 bi bi-database-fill" viewBox="0 0 16 16">
|
50
|
+
<path d="M3.904 1.777C4.978 1.289 6.427 1 8 1s3.022.289 4.096.777C13.125 2.245 14 2.993 14 4s-.875 1.755-1.904 2.223C11.022 6.711 9.573 7 8 7s-3.022-.289-4.096-.777C2.875 5.755 2 5.007 2 4s.875-1.755 1.904-2.223"/>
|
51
|
+
<path d="M2 6.161V7c0 1.007.875 1.755 1.904 2.223C4.978 9.71 6.427 10 8 10s3.022-.289 4.096-.777C13.125 8.755 14 8.007 14 7v-.839c-.457.432-1.004.751-1.49.972C11.278 7.693 9.682 8 8 8s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
|
52
|
+
<path d="M2 9.161V10c0 1.007.875 1.755 1.904 2.223C4.978 12.711 6.427 13 8 13s3.022-.289 4.096-.777C13.125 11.755 14 11.007 14 10v-.839c-.457.432-1.004.751-1.49.972-1.232.56-2.828.867-4.51.867s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
|
53
|
+
<path d="M2 12.161V13c0 1.007.875 1.755 1.904 2.223C4.978 15.711 6.427 16 8 16s3.022-.289 4.096-.777C13.125 14.755 14 14.007 14 13v-.839c-.457.432-1.004.751-1.49.972-1.232.56-2.828.867-4.51.867s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
|
54
|
+
</svg>
|
55
|
+
</a>
|
56
|
+
{% endif %}
|
57
|
+
</dd>
|
58
|
+
{% endif %}
|
59
|
+
|
60
|
+
</dl>
|
@@ -0,0 +1,94 @@
|
|
1
|
+
{% if toolbar.should_render() %}
|
2
|
+
{% set panels=toolbar.get_panels() %}
|
3
|
+
<script defer src="{{ asset('toolbar/toolbar.js') }}"></script>
|
4
|
+
<div id="plaintoolbar" class="print:hidden text-stone-300 fixed bottom-0 w-full z-30 hidden sm:flex sm:flex-col">
|
5
|
+
|
6
|
+
<div id="plaintoolbar-details" class="hidden relative text-sm border-white/5 shadow-xl border-t inset-shadow-xs inset-shadow-stone-800 rounded-t-xl bg-stone-950/95 backdrop-blur-sm">
|
7
|
+
|
8
|
+
<div class="flex border-b border-white/5 px-2">
|
9
|
+
<div class="flex flex-grow">
|
10
|
+
{% for panel in panels %}
|
11
|
+
<button {% if loop.first %}data-active{% endif %} data-toolbar-tab="{{ panel.name }}" class="data-active:border-yellow-500 px-4 py-2.5 -mb-px cursor-pointer border-b border-transparent hover:border-yellow-600" type="button">{{ panel.name }}</button>
|
12
|
+
{% endfor %}
|
13
|
+
<button data-plaintoolbar-expand class="flex-grow cursor-pointer inline-flex h-full" type="button"></button>
|
14
|
+
</div>
|
15
|
+
<div class="px-4 flex items-center space-x-4">
|
16
|
+
<button title="Hide toolbar for 1 hour" class="cursor-pointer hover:text-white text-white/50" type="button" data-plaintoolbar-hideuntil>
|
17
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4" viewBox="0 0 16 16">
|
18
|
+
<path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022zm2.004.45a7 7 0 0 0-.985-.299l.219-.976q.576.129 1.126.342zm1.37.71a7 7 0 0 0-.439-.27l.493-.87a8 8 0 0 1 .979.654l-.615.789a7 7 0 0 0-.418-.302zm1.834 1.79a7 7 0 0 0-.653-.796l.724-.69q.406.429.747.91zm.744 1.352a7 7 0 0 0-.214-.468l.893-.45a8 8 0 0 1 .45 1.088l-.95.313a7 7 0 0 0-.179-.483m.53 2.507a7 7 0 0 0-.1-1.025l.985-.17q.1.58.116 1.17zm-.131 1.538q.05-.254.081-.51l.993.123a8 8 0 0 1-.23 1.155l-.964-.267q.069-.247.12-.501m-.952 2.379q.276-.436.486-.908l.914.405q-.24.54-.555 1.038zm-.964 1.205q.183-.183.35-.378l.758.653a8 8 0 0 1-.401.432z"/>
|
19
|
+
<path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0z"/>
|
20
|
+
<path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5"/>
|
21
|
+
</svg>
|
22
|
+
</button>
|
23
|
+
<button class="cursor-pointer hover:text-white text-white/50" type="button" data-plaintoolbar-expand>
|
24
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4" viewBox="0 0 16 16">
|
25
|
+
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708"/>
|
26
|
+
</svg>
|
27
|
+
</button>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<div data-resizer class="cursor-grab w-20 h-1.5 top-1 bg-white/15 rounded-full absolute top-0 left-1/2 -translate-x-1/2"></div>
|
32
|
+
|
33
|
+
<div class="overflow-auto h-[30vh] flex flex-col">
|
34
|
+
{% for panel in panels %}
|
35
|
+
<div data-toolbar-tab="{{ panel.name }}" {% if not loop.first %}style="display: none;"{% endif %}>
|
36
|
+
{{ panel.render() }}
|
37
|
+
</div>
|
38
|
+
{% endfor %}
|
39
|
+
</div>
|
40
|
+
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div class="flex px-3 text-xs border-t border-white/5 space-x-4 py-2 bg-stone-950 shadow-xl">
|
44
|
+
<div class="flex items-center">
|
45
|
+
<code class="ml-1.5 bg-white/10 px-1.5 rounded-sm whitespace-nowrap text-mono">{{ toolbar.version }}</code>
|
46
|
+
|
47
|
+
{% if request.impersonator is defined %}
|
48
|
+
<div class="flex items-center ml-1 font-light">
|
49
|
+
Impersonating <span class="font-medium">{{ request.user }}</span>
|
50
|
+
</span>
|
51
|
+
<a href="{{ url('admin:impersonate:stop') }}" title="Stop impersonating" class="flex items-center px-1 ml-1 text-red-300 hover:text-white">
|
52
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4 bi bi-x-octagon-fill" viewBox="0 0 16 16">
|
53
|
+
<path d="M11.46.146A.5.5 0 0 0 11.107 0H4.893a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146zm-6.106 4.5L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 1 1 .708-.708z"/>
|
54
|
+
</svg>
|
55
|
+
</a>
|
56
|
+
</div>
|
57
|
+
{% endif %}
|
58
|
+
</div>
|
59
|
+
<button type="button" data-plaintoolbar-expand class="flex-grow cursor-pointer"></button>
|
60
|
+
<div class="flex items-center space-x-4">
|
61
|
+
{% include "querystats/toolbar.html" %}
|
62
|
+
|
63
|
+
<div class="flex items-center space-x-3 transition-all">
|
64
|
+
|
65
|
+
{% if toolbar.request_exception() %}
|
66
|
+
<button class="cursor-pointer text-amber-500 hover:text-amber-400" type="button" data-toolbar-tab="Exception">
|
67
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4 bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
|
68
|
+
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
|
69
|
+
</svg>
|
70
|
+
</button>
|
71
|
+
{% endif %}
|
72
|
+
|
73
|
+
<a href="{{ url('admin:index') }}" class="hover:underline">Admin</a>
|
74
|
+
|
75
|
+
{% include "toolbar/links.html" ignore missing %}
|
76
|
+
|
77
|
+
<button data-plaintoolbar-expand class="hover:text-white cursor-pointer" type="button" title="Expand toolbar">
|
78
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4" viewBox="0 0 16 16">
|
79
|
+
<path fill-rule="evenodd" d="M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708z"/>
|
80
|
+
</svg>
|
81
|
+
</button>
|
82
|
+
|
83
|
+
<button data-plaintoolbar-hide class="hover:text-red-500 cursor-pointer" type="button" title="Hide toolbar">
|
84
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x w-4 h-4" viewBox="0 0 16 16">
|
85
|
+
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
|
86
|
+
</svg>
|
87
|
+
</button>
|
88
|
+
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
|
93
|
+
</div>
|
94
|
+
{% endif %}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import sys
|
2
|
+
import traceback
|
3
|
+
|
4
|
+
from plain.runtime import settings
|
5
|
+
from plain.templates import Template
|
6
|
+
from plain.urls.exceptions import Resolver404
|
7
|
+
from plain.utils.safestring import mark_safe
|
8
|
+
|
9
|
+
|
10
|
+
class Toolbar:
|
11
|
+
def __init__(self, request):
|
12
|
+
self.request = request
|
13
|
+
self.version = settings.ADMIN_TOOLBAR_VERSION
|
14
|
+
|
15
|
+
def should_render(self):
|
16
|
+
if settings.DEBUG:
|
17
|
+
return True
|
18
|
+
|
19
|
+
if hasattr(self.request, "impersonator"):
|
20
|
+
return self.request.impersonator.is_admin
|
21
|
+
|
22
|
+
if self.request.user:
|
23
|
+
return self.request.user.is_admin
|
24
|
+
|
25
|
+
return False
|
26
|
+
|
27
|
+
def request_exception(self):
|
28
|
+
# We can capture the exception currently being handled here, if any.
|
29
|
+
exception = sys.exception()
|
30
|
+
|
31
|
+
if exception and not isinstance(exception, Resolver404):
|
32
|
+
exception._traceback_string = "".join(
|
33
|
+
traceback.format_tb(exception.__traceback__)
|
34
|
+
)
|
35
|
+
return exception
|
36
|
+
|
37
|
+
def get_panels(self):
|
38
|
+
panels = [panel(self.request) for panel in _toolbar_panel_registry.get_panels()]
|
39
|
+
|
40
|
+
if self.request_exception():
|
41
|
+
exception = self.request_exception()
|
42
|
+
panels = [
|
43
|
+
_ExceptionToolbarPanel(self.request, exception),
|
44
|
+
] + panels
|
45
|
+
|
46
|
+
return panels
|
47
|
+
|
48
|
+
|
49
|
+
class ToolbarPanel:
|
50
|
+
name: str
|
51
|
+
template_name: str
|
52
|
+
|
53
|
+
def __init__(self, request):
|
54
|
+
self.request = request
|
55
|
+
|
56
|
+
def get_template_context(self):
|
57
|
+
return {
|
58
|
+
"request": self.request,
|
59
|
+
}
|
60
|
+
|
61
|
+
def render(self):
|
62
|
+
template = Template(self.template_name)
|
63
|
+
context = self.get_template_context()
|
64
|
+
return mark_safe(template.render(context))
|
65
|
+
|
66
|
+
|
67
|
+
class _ToolbarPanelRegistry:
|
68
|
+
def __init__(self):
|
69
|
+
self._panels = {}
|
70
|
+
|
71
|
+
def register_panel(self, panel_class):
|
72
|
+
self._panels[panel_class.name] = panel_class
|
73
|
+
|
74
|
+
def get_panels(self):
|
75
|
+
return self._panels.values()
|
76
|
+
|
77
|
+
|
78
|
+
_toolbar_panel_registry = _ToolbarPanelRegistry()
|
79
|
+
|
80
|
+
|
81
|
+
def register_toolbar_panel(panel_class):
|
82
|
+
_toolbar_panel_registry.register_panel(panel_class)
|
83
|
+
return panel_class
|
84
|
+
|
85
|
+
|
86
|
+
class _ExceptionToolbarPanel(ToolbarPanel):
|
87
|
+
name = "Exception"
|
88
|
+
template_name = "toolbar/exception.html"
|
89
|
+
|
90
|
+
def __init__(self, request, exception):
|
91
|
+
super().__init__(request)
|
92
|
+
self.exception = exception
|
93
|
+
|
94
|
+
def get_template_context(self):
|
95
|
+
context = super().get_template_context()
|
96
|
+
context["exception"] = self.exception
|
97
|
+
return context
|
98
|
+
|
99
|
+
|
100
|
+
@register_toolbar_panel
|
101
|
+
class _RequestToolbarPanel(ToolbarPanel):
|
102
|
+
name = "Request"
|
103
|
+
template_name = "toolbar/request.html"
|
104
|
+
|
105
|
+
|
106
|
+
@register_toolbar_panel
|
107
|
+
class _QuerystatsToolbarPanel(ToolbarPanel):
|
108
|
+
name = "Querystats"
|
109
|
+
template_name = "toolbar/querystats.html"
|
@@ -1,245 +0,0 @@
|
|
1
|
-
{% if toolbar.should_render() %}
|
2
|
-
{% set exception=toolbar.request_exception() %}
|
3
|
-
<script defer src="{{ asset('toolbar/toolbar.js') }}"></script>
|
4
|
-
<div id="plaintoolbar" class="print:hidden text-stone-300 fixed bottom-0 w-full z-30 hidden sm:flex sm:flex-col">
|
5
|
-
|
6
|
-
<div id="plaintoolbar-details" class="{% if not exception %}hidden{% endif %} relative text-sm border-white/5 shadow-xl border-t inset-shadow-xs inset-shadow-stone-800 rounded-t-xl bg-stone-950/95 backdrop-blur-sm">
|
7
|
-
|
8
|
-
<div class="flex items-center border-b border-white/5 px-2 justify-between">
|
9
|
-
<div class="flex items-center">
|
10
|
-
<button data-active data-toolbar-tab="request" class="data-active:border-yellow-500 px-4 py-2.5 -mb-px cursor-pointer border-b border-transparent hover:border-yellow-600" type="button">Request</button>
|
11
|
-
{% if request.session is defined %}
|
12
|
-
<button data-toolbar-tab="session" class="data-active:border-yellow-500 px-4 py-2.5 -mb-px cursor-pointer border-b border-transparent hover:border-yellow-600" type="button">Session</button>
|
13
|
-
{% endif %}
|
14
|
-
<button data-toolbar-tab="querystats" class="data-active:border-yellow-500 px-4 py-2.5 -mb-px cursor-pointer border-b border-transparent hover:border-yellow-600" type="button">Querystats</button>
|
15
|
-
{% if toolbar.metadata %}
|
16
|
-
<button data-toolbar-tab="metadata" class="data-active:border-yellow-500 px-4 py-2.5 -mb-px cursor-pointer border-b border-transparent hover:border-yellow-600" type="button">Metadata</button>
|
17
|
-
{% endif %}
|
18
|
-
</div>
|
19
|
-
<div class="px-4 flex items-center space-x-4">
|
20
|
-
<button title="Hide toolbar for 1 hour" class="cursor-pointer hover:text-white text-white/50" type="button" data-plaintoolbar-hideuntil>
|
21
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4" viewBox="0 0 16 16">
|
22
|
-
<path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022zm2.004.45a7 7 0 0 0-.985-.299l.219-.976q.576.129 1.126.342zm1.37.71a7 7 0 0 0-.439-.27l.493-.87a8 8 0 0 1 .979.654l-.615.789a7 7 0 0 0-.418-.302zm1.834 1.79a7 7 0 0 0-.653-.796l.724-.69q.406.429.747.91zm.744 1.352a7 7 0 0 0-.214-.468l.893-.45a8 8 0 0 1 .45 1.088l-.95.313a7 7 0 0 0-.179-.483m.53 2.507a7 7 0 0 0-.1-1.025l.985-.17q.1.58.116 1.17zm-.131 1.538q.05-.254.081-.51l.993.123a8 8 0 0 1-.23 1.155l-.964-.267q.069-.247.12-.501m-.952 2.379q.276-.436.486-.908l.914.405q-.24.54-.555 1.038zm-.964 1.205q.183-.183.35-.378l.758.653a8 8 0 0 1-.401.432z"/>
|
23
|
-
<path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0z"/>
|
24
|
-
<path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5"/>
|
25
|
-
</svg>
|
26
|
-
</button>
|
27
|
-
<button class="cursor-pointer hover:text-white text-white/50" type="button" data-plaintoolbar-expand>
|
28
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4" viewBox="0 0 16 16">
|
29
|
-
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708"/>
|
30
|
-
</svg>
|
31
|
-
</button>
|
32
|
-
</div>
|
33
|
-
</div>
|
34
|
-
|
35
|
-
<div data-resizer class="cursor-grab w-20 h-1.5 top-1 bg-white/15 rounded-full absolute top-0 left-1/2 -translate-x-1/2"></div>
|
36
|
-
|
37
|
-
<div class="overflow-auto h-[30vh] flex flex-col">
|
38
|
-
<div data-toolbar-tab="request" class="px-6 py-4">
|
39
|
-
|
40
|
-
{% if exception %}
|
41
|
-
<div class="p-2 mb-5 border-amber-500 border rounded">
|
42
|
-
<div class="text-amber-500 text-lg flex justify-between items-center">
|
43
|
-
<div>
|
44
|
-
<span class="font-bold">Exception</span>
|
45
|
-
{{ exception }}
|
46
|
-
</div>
|
47
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-5 h-5 bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
|
48
|
-
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
|
49
|
-
</svg>
|
50
|
-
</div>
|
51
|
-
<div class="text-amber-400 text-xs mt-3 bg-white/5 p-2 rounded overflow-auto">
|
52
|
-
<pre><code>{{ exception._traceback_string }}</code></pre>
|
53
|
-
</div>
|
54
|
-
</div>
|
55
|
-
{% endif %}
|
56
|
-
|
57
|
-
<dl class="text-sm grid grid-cols-1 sm:grid-cols-[max-content_1fr] sm:gap-y-2 gap-x-8">
|
58
|
-
<dt>Request ID</dt>
|
59
|
-
<dd class="text-sm text-white/50">{{ request.unique_id }}</dd>
|
60
|
-
|
61
|
-
<dt>Query params</dt>
|
62
|
-
<dd class="text-sm text-white/50">{{ request.query_params }}</dd>
|
63
|
-
|
64
|
-
<dt>Method</dt>
|
65
|
-
<dd class="text-sm text-white/50">{{ request.method }}</dd>
|
66
|
-
|
67
|
-
{% if request.resolver_match %}
|
68
|
-
<dt>View</dt>
|
69
|
-
<dd class="text-sm text-white/50">{{ request.resolver_match.view.view_class|pprint }}</dd>
|
70
|
-
|
71
|
-
<dt>URL pattern</dt>
|
72
|
-
<dd class="text-sm text-white/50">
|
73
|
-
<pre><code>{{ request.resolver_match.route }}</code></pre>
|
74
|
-
</dd>
|
75
|
-
|
76
|
-
<dt>URL name</dt>
|
77
|
-
<dd class="text-sm text-white/50">
|
78
|
-
<pre><code>{{ request.resolver_match.namespaced_url_name }}</code></pre>
|
79
|
-
</dd>
|
80
|
-
|
81
|
-
<dt>URL args</dt>
|
82
|
-
<dd class="text-sm text-white/50">
|
83
|
-
<pre><code>{{ request.resolver_match.args }}</code></pre>
|
84
|
-
</dd>
|
85
|
-
|
86
|
-
<dt>URL kwargs</dt>
|
87
|
-
<dd class="text-sm text-white/50">
|
88
|
-
<pre><code>{{ request.resolver_match.kwargs }}</code></pre>
|
89
|
-
</dd>
|
90
|
-
{% endif %}
|
91
|
-
|
92
|
-
{% if template_names is defined %}
|
93
|
-
<dt>Template names</dt>
|
94
|
-
<dd class="text-sm text-white/50">
|
95
|
-
<pre><code>{{ template_names }}</code></pre>
|
96
|
-
</dd>
|
97
|
-
{% endif %}
|
98
|
-
|
99
|
-
{% if object|default(false) %}
|
100
|
-
<dt>Primary object</dt>
|
101
|
-
<dd class="text-sm text-white/50 inline-flex items-center" title="PK: {{ object.pk|default('unknown') }}">
|
102
|
-
<pre><code>{{ object.__repr__() }}</code></pre>
|
103
|
-
{% if object|get_admin_model_detail_url %}
|
104
|
-
<a class="ml-2 inline-flex items-center p-1 text-blue-500 hover:text-blue-400" href="{{ object|get_admin_model_detail_url }}">
|
105
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-3 h-3 bi bi-database-fill" viewBox="0 0 16 16">
|
106
|
-
<path d="M3.904 1.777C4.978 1.289 6.427 1 8 1s3.022.289 4.096.777C13.125 2.245 14 2.993 14 4s-.875 1.755-1.904 2.223C11.022 6.711 9.573 7 8 7s-3.022-.289-4.096-.777C2.875 5.755 2 5.007 2 4s.875-1.755 1.904-2.223"/>
|
107
|
-
<path d="M2 6.161V7c0 1.007.875 1.755 1.904 2.223C4.978 9.71 6.427 10 8 10s3.022-.289 4.096-.777C13.125 8.755 14 8.007 14 7v-.839c-.457.432-1.004.751-1.49.972C11.278 7.693 9.682 8 8 8s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
|
108
|
-
<path d="M2 9.161V10c0 1.007.875 1.755 1.904 2.223C4.978 12.711 6.427 13 8 13s3.022-.289 4.096-.777C13.125 11.755 14 11.007 14 10v-.839c-.457.432-1.004.751-1.49.972-1.232.56-2.828.867-4.51.867s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
|
109
|
-
<path d="M2 12.161V13c0 1.007.875 1.755 1.904 2.223C4.978 15.711 6.427 16 8 16s3.022-.289 4.096-.777C13.125 14.755 14 14.007 14 13v-.839c-.457.432-1.004.751-1.49.972-1.232.56-2.828.867-4.51.867s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
|
110
|
-
</svg>
|
111
|
-
</a>
|
112
|
-
{% endif %}
|
113
|
-
</dd>
|
114
|
-
{% endif %}
|
115
|
-
|
116
|
-
</dl>
|
117
|
-
</div>
|
118
|
-
|
119
|
-
{% if request.session is defined %}
|
120
|
-
<div data-toolbar-tab="session" style="display: none;">
|
121
|
-
<div class="px-6 py-4">
|
122
|
-
<dl class="text-sm grid grid-cols-1 sm:grid-cols-[max-content_1fr] sm:gap-y-2 gap-x-8">
|
123
|
-
<dt>Session ID</dt>
|
124
|
-
<dd class="text-sm text-white/50">{{ request.session.session_key }}</dd>
|
125
|
-
|
126
|
-
{% for k, v in request.session.items() %}
|
127
|
-
<dt>session["{{ k }}"]</dt>
|
128
|
-
<dd class="text-sm text-white/50 max-h-32 overflow-auto">
|
129
|
-
{% if v is iterable and not v|string %}
|
130
|
-
<pre><code>{{ v }}</code></pre>
|
131
|
-
{% else %}
|
132
|
-
{{ v }}
|
133
|
-
{% endif %}
|
134
|
-
</dd>
|
135
|
-
{% endfor %}
|
136
|
-
</dl>
|
137
|
-
</div>
|
138
|
-
</div>
|
139
|
-
{% endif %}
|
140
|
-
|
141
|
-
<div data-toolbar-tab="querystats" style="display: none;" class="h-full">
|
142
|
-
<div id="querystats-container" class="h-full">
|
143
|
-
<div class="px-6 py-4 text-center">
|
144
|
-
<p>Loading querystats...</p>
|
145
|
-
</div>
|
146
|
-
</div>
|
147
|
-
<script>
|
148
|
-
(function() {
|
149
|
-
var container = document.getElementById('querystats-container');
|
150
|
-
var loaded = false;
|
151
|
-
var parent = container.parentNode;
|
152
|
-
var observer = new IntersectionObserver(function(entries) {
|
153
|
-
entries.forEach(function(entry) {
|
154
|
-
if (entry.isIntersecting && !loaded) {
|
155
|
-
loaded = true;
|
156
|
-
var iframe = document.createElement('iframe');
|
157
|
-
iframe.src = "{{ url('admin:querystats:querystats') }}";
|
158
|
-
iframe.frameBorder = "0";
|
159
|
-
iframe.style.width = "100%";
|
160
|
-
iframe.style.height = "100%";
|
161
|
-
container.innerHTML = '';
|
162
|
-
container.appendChild(iframe);
|
163
|
-
observer.disconnect();
|
164
|
-
}
|
165
|
-
});
|
166
|
-
}, { root: parent, threshold: 0 });
|
167
|
-
observer.observe(container);
|
168
|
-
})();
|
169
|
-
</script>
|
170
|
-
</div>
|
171
|
-
|
172
|
-
{% if toolbar.metadata %}
|
173
|
-
<div data-toolbar-tab="metadata" style="display: none;">
|
174
|
-
<div class="px-6 py-4">
|
175
|
-
<dl class="text-sm grid grid-cols-1 sm:grid-cols-[max-content_1fr] sm:gap-y-2 gap-x-8">
|
176
|
-
{% for k, v in toolbar.metadata.items() %}
|
177
|
-
<dt>{{ k }}</dt>
|
178
|
-
<dd class="text-sm text-white/50 max-h-32 overflow-auto">
|
179
|
-
{% if v is iterable and not v|string %}
|
180
|
-
<pre><code>{{ v }}</code></pre>
|
181
|
-
{% else %}
|
182
|
-
{{ v }}
|
183
|
-
{% endif %}
|
184
|
-
</dd>
|
185
|
-
{% endfor %}
|
186
|
-
</dl>
|
187
|
-
</div>
|
188
|
-
</div>
|
189
|
-
{% endif %}
|
190
|
-
</div>
|
191
|
-
|
192
|
-
</div>
|
193
|
-
|
194
|
-
<div class="flex px-3 text-xs border-t border-white/5 space-x-4 py-2 bg-stone-950 shadow-xl">
|
195
|
-
<div class="flex items-center">
|
196
|
-
<code class="ml-1.5 bg-white/10 px-1.5 rounded-sm whitespace-nowrap text-mono">{{ toolbar.version }}</code>
|
197
|
-
|
198
|
-
{% if request.impersonator is defined %}
|
199
|
-
<div class="flex items-center ml-1 font-light">
|
200
|
-
Impersonating <span class="font-medium">{{ request.user }}</span>
|
201
|
-
</span>
|
202
|
-
<a href="{{ url('admin:impersonate:stop') }}" title="Stop impersonating" class="flex items-center px-1 ml-1 text-red-300 hover:text-white">
|
203
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4 bi bi-x-octagon-fill" viewBox="0 0 16 16">
|
204
|
-
<path d="M11.46.146A.5.5 0 0 0 11.107 0H4.893a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146zm-6.106 4.5L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 1 1 .708-.708z"/>
|
205
|
-
</svg>
|
206
|
-
</a>
|
207
|
-
</div>
|
208
|
-
{% endif %}
|
209
|
-
</div>
|
210
|
-
<button type="button" data-plaintoolbar-expand class="flex-grow cursor-pointer"></button>
|
211
|
-
<div class="flex items-center space-x-4">
|
212
|
-
{% include "querystats/toolbar.html" %}
|
213
|
-
|
214
|
-
<div class="flex items-center space-x-3 transition-all">
|
215
|
-
|
216
|
-
{% if exception %}
|
217
|
-
<button class="cursor-pointer text-amber-500 hover:text-amber-400" type="button" data-toolbar-tab="request">
|
218
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4 bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
|
219
|
-
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
|
220
|
-
</svg>
|
221
|
-
</button>
|
222
|
-
{% endif %}
|
223
|
-
|
224
|
-
<a href="{{ url('admin:index') }}" class="hover:underline">Admin</a>
|
225
|
-
|
226
|
-
{% include "toolbar/links.html" ignore missing %}
|
227
|
-
|
228
|
-
<button data-plaintoolbar-expand class="hover:text-white cursor-pointer" type="button" title="Expand toolbar">
|
229
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="w-4 h-4" viewBox="0 0 16 16">
|
230
|
-
<path fill-rule="evenodd" d="M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708z"/>
|
231
|
-
</svg>
|
232
|
-
</button>
|
233
|
-
|
234
|
-
<button data-plaintoolbar-hide class="hover:text-red-500 cursor-pointer" type="button" title="Hide toolbar">
|
235
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x w-4 h-4" viewBox="0 0 16 16">
|
236
|
-
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
|
237
|
-
</svg>
|
238
|
-
</button>
|
239
|
-
|
240
|
-
</div>
|
241
|
-
</div>
|
242
|
-
</div>
|
243
|
-
|
244
|
-
</div>
|
245
|
-
{% endif %}
|
@@ -1,34 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import traceback
|
3
|
-
|
4
|
-
from plain.runtime import settings
|
5
|
-
from plain.urls.exceptions import Resolver404
|
6
|
-
|
7
|
-
|
8
|
-
class Toolbar:
|
9
|
-
def __init__(self, request):
|
10
|
-
self.request = request
|
11
|
-
self.version = settings.ADMIN_TOOLBAR_VERSION
|
12
|
-
self.metadata = {}
|
13
|
-
|
14
|
-
def should_render(self):
|
15
|
-
if settings.DEBUG:
|
16
|
-
return True
|
17
|
-
|
18
|
-
if hasattr(self.request, "impersonator"):
|
19
|
-
return self.request.impersonator.is_admin
|
20
|
-
|
21
|
-
if self.request.user:
|
22
|
-
return self.request.user.is_admin
|
23
|
-
|
24
|
-
return False
|
25
|
-
|
26
|
-
def request_exception(self):
|
27
|
-
# We can capture the exception currently being handled here, if any.
|
28
|
-
exception = sys.exception()
|
29
|
-
|
30
|
-
if exception and not isinstance(exception, Resolver404):
|
31
|
-
exception._traceback_string = "".join(
|
32
|
-
traceback.format_tb(exception.__traceback__)
|
33
|
-
)
|
34
|
-
return exception
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/admin/values/get_display.html
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Checkbox.html
RENAMED
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/CheckboxField.html
RENAMED
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/FieldErrors.html
RENAMED
File without changes
|
File without changes
|
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/InputField.html
RENAMED
File without changes
|
File without changes
|
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/SelectField.html
RENAMED
File without changes
|
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/Textarea.html
RENAMED
File without changes
|
{plain_admin-0.28.0 → plain_admin-0.29.0}/plain/admin/templates/elements/admin/TextareaField.html
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|