plain.admin 0.25.0__py3-none-any.whl → 0.26.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.
- plain/admin/README.md +103 -241
- plain/admin/default_settings.py +3 -3
- plain/admin/querystats/core.py +14 -10
- plain/admin/querystats/middleware.py +32 -28
- plain/admin/querystats/views.py +32 -11
- plain/admin/templates/querystats/querystats.html +89 -57
- plain/admin/templates/querystats/toolbar.html +31 -21
- plain/admin/templates.py +3 -3
- plain/admin/toolbar.py +1 -1
- plain_admin-0.26.0.dist-info/METADATA +178 -0
- {plain_admin-0.25.0.dist-info → plain_admin-0.26.0.dist-info}/RECORD +13 -13
- plain_admin-0.25.0.dist-info/METADATA +0 -316
- {plain_admin-0.25.0.dist-info → plain_admin-0.26.0.dist-info}/WHEEL +0 -0
- {plain_admin-0.25.0.dist-info → plain_admin-0.26.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,75 +3,107 @@
|
|
3
3
|
<head>
|
4
4
|
<meta charset="UTF-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
-
<title>
|
6
|
+
<title>Querystats</title>
|
7
7
|
{% tailwind_css %}
|
8
8
|
</head>
|
9
|
-
<body>
|
9
|
+
<body class="bg-stone-950 text-stone-300">
|
10
10
|
|
11
|
-
<div class="px-6 py-4">
|
12
|
-
<
|
13
|
-
|
14
|
-
<
|
15
|
-
|
16
|
-
|
17
|
-
</
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
</button>
|
25
|
-
</form>
|
26
|
-
</div>
|
11
|
+
<div class="flex items-center justify-between px-6 py-4">
|
12
|
+
<h1 class="text-lg font-semibold">Querystats</h1>
|
13
|
+
<div class="flex items-center space-x-2">
|
14
|
+
<form method="post" action=".">
|
15
|
+
{{ csrf_input }}
|
16
|
+
<input type="hidden" name="querystats_action" value="clear">
|
17
|
+
<button type="submit" class="px-2 py-px text-sm rounded-sm bg-stone-700 text-stone-300 hover:bg-stone-600 cursor-pointer whitespace-nowrap">Clear</button>
|
18
|
+
</form>
|
19
|
+
<form method="post" action=".">
|
20
|
+
{{ csrf_input }}
|
21
|
+
<input type="hidden" name="querystats_action" value="disable">
|
22
|
+
<button type="submit" class="px-2 py-px text-sm rounded-sm bg-stone-700 text-stone-300 hover:bg-stone-600 cursor-pointer whitespace-nowrap">Disable</button>
|
23
|
+
</form>
|
27
24
|
</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 %}
|
25
|
+
</div>
|
53
26
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
</div>
|
27
|
+
<div class="space-y-6 px-6 py-4">
|
28
|
+
{% for request_id, qs in querystats.items() %}
|
29
|
+
<div class="p-3 bg-white/5 rounded">
|
30
|
+
<div class="flex justify-between items-center">
|
59
31
|
<div>
|
60
|
-
<
|
32
|
+
<h2 class="font-medium"><span class="font-semibold">{{ qs.request.method }}</span> {{ qs.request.path }}</h2>
|
33
|
+
<p class="text-sm text-stone-400">{{ qs.summary }}</p>
|
61
34
|
</div>
|
62
|
-
<div class=
|
63
|
-
<
|
64
|
-
<
|
35
|
+
<div class=text-xs>
|
36
|
+
<p>Request ID <code>{{ qs.request.unique_id }}</code></p>
|
37
|
+
<p>Timestamp {{ qs.timestamp }}</p>
|
38
|
+
<details>
|
39
|
+
<summary>Headers</summary>
|
40
|
+
<pre><code>{{ qs.request.get("headers", {})|pprint }}</code></pre>
|
41
|
+
</details>
|
65
42
|
</div>
|
66
|
-
|
67
|
-
|
68
|
-
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div class="flex w-full mt-5 overflow-auto rounded-sm">
|
46
|
+
{% for query in qs.queries %}
|
47
|
+
<a href="#query-{{ loop.index }}"
|
48
|
+
{{ loop.cycle('class=\"h-4 bg-amber-300\"', 'class=\"h-4 bg-amber-400\"', 'class="h-4 bg-amber-500"', 'class="h-4 bg-amber-600"')|safe }}
|
49
|
+
title="[{{ query.duration_display }}] {{ query.sql_display }}"
|
50
|
+
style="width: {{ query.duration / qs.total_time * 100 }}%">
|
51
|
+
</a>
|
52
|
+
{% endfor %}
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<div class="mt-4 space-y-3 text-xs">
|
56
|
+
{% for query in qs.queries %}
|
57
|
+
<details id="query-{{ loop.index }}" class="p-2 rounded bg-zinc-800">
|
58
|
+
<summary class="truncate">
|
59
|
+
<div class="float-right px-2 py-px mb-px ml-2 text-xs rounded-full bg-zinc-700">
|
60
|
+
<span>{{ query.duration_display }}</span>
|
61
|
+
{% if query.duplicate_count is defined %}
|
62
|
+
<span class="text-red-500"> duplicated {{ query.duplicate_count }} times</span>
|
63
|
+
{% endif %}
|
64
|
+
|
65
|
+
{#
|
66
|
+
<div>many {{ query.many }}</div>
|
67
|
+
<div>result {{ query.result }}</div>
|
68
|
+
#}
|
69
|
+
</div>
|
70
|
+
<code class="font-mono">{{ query.sql }}</code>
|
71
|
+
</summary>
|
72
|
+
<div class="space-y-3 mt-3">
|
73
|
+
<div>
|
74
|
+
<pre><code class="font-mono whitespace-pre-wrap text-zinc-100">{{ query.sql_display }}</code></pre>
|
75
|
+
</div>
|
76
|
+
<div class="text-zinc-400">
|
77
|
+
<span class="font-medium">Parameters</span>
|
78
|
+
<pre><code class="font-mono">{{ query.params|pprint }}</code></pre>
|
79
|
+
</div>
|
80
|
+
<details>
|
81
|
+
<summary>Traceback</summary>
|
82
|
+
<pre><code class="block overflow-x-auto font-mono text-xs">{{ query.tb }}</code></pre>
|
83
|
+
</details>
|
84
|
+
</div>
|
69
85
|
</details>
|
86
|
+
{% else %}
|
87
|
+
<div>No queries...</div>
|
88
|
+
{% endfor %}
|
70
89
|
</div>
|
90
|
+
</div>
|
91
|
+
|
92
|
+
{% else %}
|
93
|
+
|
94
|
+
<div class="text-center">
|
95
|
+
{% if "querystats" in request.session %}
|
96
|
+
<div class="text-stone-500">Querystats are enabled but nothing has been tracked yet.</div>
|
71
97
|
{% else %}
|
72
|
-
<
|
73
|
-
|
98
|
+
<form method="post" action=".">
|
99
|
+
{{ csrf_input }}
|
100
|
+
<input type="hidden" name="querystats_action" value="enable">
|
101
|
+
<button type="submit" class="px-2 rounded-sm bg-stone-700 text-stone-300 hover:bg-stone-600 cursor-pointer whitespace-nowrap">Enable querystats</button>
|
102
|
+
</form>
|
103
|
+
{% endif %}
|
74
104
|
</div>
|
105
|
+
|
106
|
+
{% endfor %}
|
75
107
|
</div>
|
76
108
|
|
77
109
|
</body>
|
@@ -1,13 +1,25 @@
|
|
1
|
-
<
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
<div data-querystats class="relative group/querystats" style="display: none;">
|
2
|
+
{% if "querystats" in request.session %}
|
3
|
+
<a href="{{ url('admin:querystats:querystats') }}" target="querystats" class="inline-flex items-center cursor-pointer text-xs rounded-full px-2 py-px bg-stone-700 text-stone-300 whitespace-nowrap">
|
4
|
+
<span class="relative inline-flex size-2 mr-2">
|
5
|
+
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"></span>
|
6
|
+
<span class="relative inline-flex size-2 rounded-full bg-green-500"></span>
|
7
|
+
</span>
|
8
|
+
<span data-querystats-summary></span>
|
9
|
+
</a>
|
10
|
+
{% else %}
|
11
|
+
<form action="{{ url('admin:querystats:querystats') }}" method="post">
|
12
|
+
{{ csrf_input }}
|
13
|
+
<input type="hidden" name="redirect_url" value="{{ request.get_full_path() }}">
|
14
|
+
<input type="hidden" name="querystats_action" value="enable">
|
15
|
+
<button type="submit" class="cursor-pointer text-xs rounded-full px-2 py-px bg-stone-700 text-stone-300 whitespace-nowrap">
|
16
|
+
<span class="rounded-full bg-zinc-500 w-2 h-2 inline-block mr-1"></span>
|
17
|
+
<span data-querystats-summary></span>
|
18
|
+
</button>
|
19
|
+
</form>
|
20
|
+
{% endif %}
|
21
|
+
|
22
|
+
<div data-querystats-list style="display: none;" class="absolute z-50 -translate-x-1/2 hidden -translate-y-full left-1/2 -top-1 group-hover/querystats:block">
|
11
23
|
<div class="p-2 text-xs border rounded shadow-md bg-zinc-900 border-zinc-700"><table><tbody></tbody></table></div>
|
12
24
|
</div>
|
13
25
|
<script async defer>
|
@@ -15,9 +27,8 @@
|
|
15
27
|
// https://bugs.webkit.org/show_bug.cgi?id=209216
|
16
28
|
var querystatsTimings = [];
|
17
29
|
function renderQuerystats() {
|
18
|
-
// Render the
|
19
|
-
|
20
|
-
let summary = latestTiming.description;
|
30
|
+
// Render the original timing call
|
31
|
+
let summary = querystatsTimings[0].description;
|
21
32
|
if (querystatsTimings.length > 1) {
|
22
33
|
summary += ` *`;
|
23
34
|
}
|
@@ -47,7 +58,6 @@
|
|
47
58
|
}
|
48
59
|
}
|
49
60
|
try {
|
50
|
-
// Create the performance observer.
|
51
61
|
const po = new PerformanceObserver((list) => {
|
52
62
|
for (const entry of list.getEntries()) {
|
53
63
|
if (!entry.serverTiming) {
|
@@ -55,15 +65,15 @@
|
|
55
65
|
return;
|
56
66
|
}
|
57
67
|
for (const timing of entry.serverTiming) {
|
58
|
-
if (querystatsTimings.length > 0) {
|
59
|
-
if (querystatsTimings[querystatsTimings.length - 1] === timing) {
|
60
|
-
// Skip duplicate timings (happens on initial load...)
|
61
|
-
continue;
|
62
|
-
}
|
63
|
-
}
|
64
68
|
if (timing.name === "querystats") {
|
65
69
|
console.log("Querystats timing", entry)
|
66
70
|
timing.url = entry.name; // Store this for reference later
|
71
|
+
for (const existingTiming of querystatsTimings) {
|
72
|
+
if (existingTiming == timing) {
|
73
|
+
// Skip duplicate timings (happens on initial load...)
|
74
|
+
return;
|
75
|
+
}
|
76
|
+
}
|
67
77
|
querystatsTimings.push(timing);
|
68
78
|
renderQuerystats();
|
69
79
|
}
|
@@ -76,4 +86,4 @@
|
|
76
86
|
// Do nothing if the browser doesn't support this API.
|
77
87
|
}
|
78
88
|
</script>
|
79
|
-
</
|
89
|
+
</div>
|
plain/admin/templates.py
CHANGED
@@ -12,10 +12,10 @@ class ToolbarExtension(InclusionTagExtension):
|
|
12
12
|
template_name = "toolbar/toolbar.html"
|
13
13
|
|
14
14
|
def get_context(self, context, *args, **kwargs):
|
15
|
-
if isinstance(settings.
|
16
|
-
cls = import_string(settings.
|
15
|
+
if isinstance(settings.ADMIN_TOOLBAR_CLASS, str):
|
16
|
+
cls = import_string(settings.ADMIN_TOOLBAR_CLASS)
|
17
17
|
else:
|
18
|
-
cls = settings.
|
18
|
+
cls = settings.ADMIN_TOOLBAR_CLASS
|
19
19
|
context.vars["toolbar"] = cls(request=context["request"])
|
20
20
|
return context
|
21
21
|
|
plain/admin/toolbar.py
CHANGED
@@ -8,7 +8,7 @@ from plain.urls.exceptions import Resolver404
|
|
8
8
|
class Toolbar:
|
9
9
|
def __init__(self, request):
|
10
10
|
self.request = request
|
11
|
-
self.version = settings.
|
11
|
+
self.version = settings.ADMIN_TOOLBAR_VERSION
|
12
12
|
self.metadata = {
|
13
13
|
"Request ID": request.unique_id,
|
14
14
|
}
|
@@ -0,0 +1,178 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: plain.admin
|
3
|
+
Version: 0.26.0
|
4
|
+
Summary: Admin dashboard and tools for Plain.
|
5
|
+
Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
|
6
|
+
License-Expression: BSD-3-Clause
|
7
|
+
License-File: LICENSE
|
8
|
+
Requires-Python: >=3.11
|
9
|
+
Requires-Dist: plain-auth<1.0.0
|
10
|
+
Requires-Dist: plain-htmx<1.0.0
|
11
|
+
Requires-Dist: plain-tailwind<1.0.0
|
12
|
+
Requires-Dist: plain<1.0.0
|
13
|
+
Requires-Dist: sqlparse>=0.2.2
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
|
16
|
+
# plain.admin
|
17
|
+
|
18
|
+
**Manage your app with a backend interface.**
|
19
|
+
|
20
|
+
The Plain Admin provides a combination of built-in views and the flexibility to create your own. You can use it to quickly get visibility into your app's data and to manage it.
|
21
|
+
|
22
|
+

|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Install the `plain.admin` package and its dependencies.
|
27
|
+
|
28
|
+
```console
|
29
|
+
uv add plain.admin
|
30
|
+
```
|
31
|
+
|
32
|
+
The admin uses a combination of other Plain packages, most of which you will already have installed. Ultimately, your settings will look something like this:
|
33
|
+
|
34
|
+
```python
|
35
|
+
# app/settings.py
|
36
|
+
INSTALLED_PACKAGES = [
|
37
|
+
"plain.models",
|
38
|
+
"plain.tailwind",
|
39
|
+
"plain.auth",
|
40
|
+
"plain.sessions",
|
41
|
+
"plain.htmx",
|
42
|
+
"plain.admin",
|
43
|
+
"plain.elements",
|
44
|
+
# other packages...
|
45
|
+
]
|
46
|
+
|
47
|
+
AUTH_USER_MODEL = "users.User"
|
48
|
+
AUTH_LOGIN_URL = "login"
|
49
|
+
|
50
|
+
MIDDLEWARE = [
|
51
|
+
"plain.sessions.middleware.SessionMiddleware",
|
52
|
+
"plain.auth.middleware.AuthenticationMiddleware",
|
53
|
+
"plain.admin.AdminMiddleware",
|
54
|
+
]
|
55
|
+
```
|
56
|
+
|
57
|
+
Your User model is expected to have an `is_admin` field (or attribute) for checking who has permission to access the admin.
|
58
|
+
|
59
|
+
```python
|
60
|
+
# app/users/models.py
|
61
|
+
from plain import models
|
62
|
+
|
63
|
+
|
64
|
+
@models.register_model
|
65
|
+
class User(models.Model):
|
66
|
+
is_admin = models.BooleanField(default=False)
|
67
|
+
# other fields...
|
68
|
+
```
|
69
|
+
|
70
|
+
To make the admin accessible, add the `AdminRouter` to your root URLs.
|
71
|
+
|
72
|
+
```python
|
73
|
+
# app/urls.py
|
74
|
+
from plain.admin.urls import AdminRouter
|
75
|
+
from plain.urls import Router, include, path
|
76
|
+
|
77
|
+
from . import views
|
78
|
+
|
79
|
+
|
80
|
+
class AppRouter(Router):
|
81
|
+
namespace = ""
|
82
|
+
urls = [
|
83
|
+
include("admin/", AdminRouter),
|
84
|
+
path("login/", views.LoginView, name="login"),
|
85
|
+
path("logout/", LogoutView, name="logout"),
|
86
|
+
# other urls...
|
87
|
+
]
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
Optionally, you can add the admin toolbar to your base template. The toolbar will appear when `settings.DEBUG` or when `request.user.is_admin` (including in production!).
|
92
|
+
|
93
|
+
```html
|
94
|
+
<!-- app/templates/base.html -->
|
95
|
+
<!DOCTYPE html>
|
96
|
+
<html lang="en">
|
97
|
+
<head>
|
98
|
+
<meta charset="UTF-8">
|
99
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
100
|
+
<title>{{ html_title|default("My App") }}</title>
|
101
|
+
{% tailwind_css %}
|
102
|
+
</head>
|
103
|
+
<body>
|
104
|
+
{% block content required %}{% endblock %}
|
105
|
+
|
106
|
+
{% toolbar %}
|
107
|
+
</body>
|
108
|
+
</html>
|
109
|
+
```
|
110
|
+
|
111
|
+
## Admin viewsets
|
112
|
+
|
113
|
+
The most common use of the admin is to display and manage your `plain.models`. To do this, create a viewset with a set of inner views.
|
114
|
+
|
115
|
+
```python
|
116
|
+
# app/users/admin.py
|
117
|
+
from plain.admin.views import (
|
118
|
+
AdminModelDetailView,
|
119
|
+
AdminModelListView,
|
120
|
+
AdminModelUpdateView,
|
121
|
+
AdminViewset,
|
122
|
+
register_viewset,
|
123
|
+
)
|
124
|
+
from plain.models.forms import ModelForm
|
125
|
+
|
126
|
+
from .models import User
|
127
|
+
|
128
|
+
|
129
|
+
class UserForm(ModelForm):
|
130
|
+
class Meta:
|
131
|
+
model = User
|
132
|
+
fields = ["email"]
|
133
|
+
|
134
|
+
|
135
|
+
@register_viewset
|
136
|
+
class UserAdmin(AdminViewset):
|
137
|
+
class ListView(AdminModelListView):
|
138
|
+
model = User
|
139
|
+
fields = [
|
140
|
+
"id",
|
141
|
+
"email",
|
142
|
+
"created_at__date",
|
143
|
+
]
|
144
|
+
queryset_order = ["-created_at"]
|
145
|
+
search_fields = [
|
146
|
+
"email",
|
147
|
+
]
|
148
|
+
|
149
|
+
class DetailView(AdminModelDetailView):
|
150
|
+
model = User
|
151
|
+
|
152
|
+
class UpdateView(AdminModelUpdateView):
|
153
|
+
template_name = "admin/users/user_form.html"
|
154
|
+
model = User
|
155
|
+
form_class = UserForm
|
156
|
+
```
|
157
|
+
|
158
|
+
The [`AdminViewset`](./views/viewsets.py) will automatically recognize inner views named `ListView`, `CreateView`, `DetailView`, `UpdateView`, and `DeleteView`. It will interlink these views automatically in the UI and form success URLs. You can define additional views too, but you will need to implement a couple methods to hook them up.
|
159
|
+
|
160
|
+
## Admin cards
|
161
|
+
|
162
|
+
TODO
|
163
|
+
|
164
|
+
## Admin forms
|
165
|
+
|
166
|
+
TODO
|
167
|
+
|
168
|
+
## Toolbar
|
169
|
+
|
170
|
+
TODO
|
171
|
+
|
172
|
+
## Impersonate
|
173
|
+
|
174
|
+
TODO
|
175
|
+
|
176
|
+
## Querystats
|
177
|
+
|
178
|
+
TODO
|
@@ -1,11 +1,11 @@
|
|
1
|
-
plain/admin/README.md,sha256=
|
1
|
+
plain/admin/README.md,sha256=Ro2YkrKS-RXsmFBFN0QUpLh4OHIDvMkVgDIE6Wu4PMQ,3800
|
2
2
|
plain/admin/__init__.py,sha256=bPv9iftT8aLqBH6dDy-HTVXW66dQUhfIiEZ-LIUMC0Y,78
|
3
3
|
plain/admin/config.py,sha256=TDYmJe4UYmKw4bz0x5s9PkDa-X4V-9JoJlka162-J7M,676
|
4
4
|
plain/admin/dates.py,sha256=EEhcQhHt3-k6kE9yvPdH5X6EecmUQ259xywbDBec3Dg,10253
|
5
|
-
plain/admin/default_settings.py,sha256=
|
5
|
+
plain/admin/default_settings.py,sha256=S22r8JtwY-ArlNO4waBOrnRfb2qPbUQ5VSP6niJRzZw,145
|
6
6
|
plain/admin/middleware.py,sha256=k3yP1o3CzvLiZZSoxqq-DvAZlp4sICRauaT-kD3FJKM,398
|
7
|
-
plain/admin/templates.py,sha256=
|
8
|
-
plain/admin/toolbar.py,sha256=
|
7
|
+
plain/admin/templates.py,sha256=0xgMQmJEbh5U45ZlN2f15Xs42Y2A_lSS-_wdMp1BeD4,854
|
8
|
+
plain/admin/toolbar.py,sha256=doW1Eg9rYfLZulRTAyFACDaUDi2xkDlsdVABzCQKHG4,979
|
9
9
|
plain/admin/urls.py,sha256=HtYsTDyV6s-k6ClT2H2oZqUDIANLq-PACpZfrR538js,1292
|
10
10
|
plain/admin/assets/admin/admin.css,sha256=-KdI7geASBsSbTve26VeJ-wCrdHWyD3EdjDZ9o393Yc,2653
|
11
11
|
plain/admin/assets/admin/admin.js,sha256=8R4VestYByRd2THe5gg8I35Zu3rokm6TQTkEf2mEB1c,2919
|
@@ -29,10 +29,10 @@ plain/admin/impersonate/urls.py,sha256=s8bwi8qPueKCCYcLW75p-hPFkBKhm2AMi6AQKQcZs
|
|
29
29
|
plain/admin/impersonate/views.py,sha256=p8kEGC2ZNntAaLJRgwCaGSJABjLWoarpya9IuBpNW5A,789
|
30
30
|
plain/admin/querystats/README.md,sha256=ONscu4dQOVe20CPHFyI8vR8iL2kvo3cOM8iwVO-lDyM,4821
|
31
31
|
plain/admin/querystats/__init__.py,sha256=VmP1aQ5Pviq4Z3izCB8G9g0Weq-2SYR88UFNtwqAPpo,81
|
32
|
-
plain/admin/querystats/core.py,sha256=
|
33
|
-
plain/admin/querystats/middleware.py,sha256=
|
32
|
+
plain/admin/querystats/core.py,sha256=kh45lRPEv9lYiTDNI_srrfoJue48v3kcrBNbOIHYCmw,4480
|
33
|
+
plain/admin/querystats/middleware.py,sha256=g5Ld-Xx1eKq1AfED4oBHNkuhr5nUL1ILrzTv_tQVlPY,3528
|
34
34
|
plain/admin/querystats/urls.py,sha256=H8wMpqKBnXqA8ZsdwdxTKQguNYJ0JsMRqqMunccBm2I,198
|
35
|
-
plain/admin/querystats/views.py,sha256=
|
35
|
+
plain/admin/querystats/views.py,sha256=hCADEZMZSyIAqdDQU0J21HveWqAH_hX2RcRHNS7-I1k,1573
|
36
36
|
plain/admin/templates/admin/base.html,sha256=M3z5JwRPSS9fc3Rcg9YxPWTNL0wNo98oaEv3Ue3xlvs,8466
|
37
37
|
plain/admin/templates/admin/delete.html,sha256=lNuU2G-BR6TH6NUmh7VcvjnEuFeI84rwwk_oO1jkUq0,431
|
38
38
|
plain/admin/templates/admin/detail.html,sha256=AizpXs6HguFzwbk7JDbH8poJB5dM2CaVVaQ4FThAHaw,730
|
@@ -66,8 +66,8 @@ plain/admin/templates/elements/admin/SelectField.html,sha256=P2-vXifOs2-ie20AgLy
|
|
66
66
|
plain/admin/templates/elements/admin/Submit.html,sha256=1Lgn3Du9rXplbM3V12z2JckSaiWPlPGLP48xIZ887AA,150
|
67
67
|
plain/admin/templates/elements/admin/Textarea.html,sha256=nCSaGa9t5A5Oj6ZPWW-jSJiGqI1NLPahhXJblq62QME,363
|
68
68
|
plain/admin/templates/elements/admin/TextareaField.html,sha256=4IOJapBNEfhUpMkkLW-gliIefZCEMn5aKyW4QagfcNw,223
|
69
|
-
plain/admin/templates/querystats/querystats.html,sha256=
|
70
|
-
plain/admin/templates/querystats/toolbar.html,sha256=
|
69
|
+
plain/admin/templates/querystats/querystats.html,sha256=oeDswOjN_11weKsj_x1iOKyTucyU1X7aL1vvxqhIUMc,5088
|
70
|
+
plain/admin/templates/querystats/toolbar.html,sha256=JFuG97PackHuhRFxnOHEiKGMa_gmCsy3l4PotrwKt9Q,4369
|
71
71
|
plain/admin/templates/toolbar/toolbar.html,sha256=KcGAG6kRmx60wfqEsdD5C4nDMilH-JvPjHoU6EktfaY,5985
|
72
72
|
plain/admin/views/__init__.py,sha256=nF6AENZ3Xxyi08OTRrF6e-HYBkZSFj7XBK2mVzMYqN4,846
|
73
73
|
plain/admin/views/base.py,sha256=S1oaMUXnMOwRozbn2K-tk9tL4BMimemfMagZD9QxrJw,3512
|
@@ -76,7 +76,7 @@ plain/admin/views/objects.py,sha256=7BXrDpHbdZ0vpzTHoLbSNdXO-rYSRw5YOBTiTK12E1U,
|
|
76
76
|
plain/admin/views/registry.py,sha256=Lxib4YSQCMHb_zACnLKymJakV8jCZPWYll7J8-aV9Xw,3712
|
77
77
|
plain/admin/views/types.py,sha256=ONMMdUoapgMoUVYgSIe-4YCdfvaVMQ4jgPWYiMo0pDk,178
|
78
78
|
plain/admin/views/viewsets.py,sha256=dqMlQ6kLn9iqd9BwBWAZT1S271wH1FdfM5HXbOgBMEw,1655
|
79
|
-
plain_admin-0.
|
80
|
-
plain_admin-0.
|
81
|
-
plain_admin-0.
|
82
|
-
plain_admin-0.
|
79
|
+
plain_admin-0.26.0.dist-info/METADATA,sha256=4nnt50UwdYNs753lk8AfrS9RnF8I1YviI3KyF3GjlDs,4237
|
80
|
+
plain_admin-0.26.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
81
|
+
plain_admin-0.26.0.dist-info/licenses/LICENSE,sha256=cvKM3OlqHx3ijD6e34zsSUkPvzl-ya3Dd63A6EHL94U,1500
|
82
|
+
plain_admin-0.26.0.dist-info/RECORD,,
|