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.
@@ -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>Document</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
- <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>
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">&nbsp; duplicated {{ query.duplicate_count }} times</span>
52
- {% endif %}
25
+ </div>
53
26
 
54
- {#
55
- <div>many {{ query.many }}</div>
56
- <div>result {{ query.result }}</div>
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
- <pre><code class="font-mono whitespace-pre-wrap text-zinc-100">{{ query.sql_display }}</code></pre>
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="mt-3 text-zinc-400">
63
- <span class="font-medium">Parameters</span>
64
- <pre><code class="font-mono">{{ query.params|pprint }}</code></pre>
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
- <details class="mt-3">
67
- <summary>Traceback</summary>
68
- <pre><code class="block overflow-x-auto font-mono text-xs">{{ query.tb }}</code></pre>
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">&nbsp; 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
- <div>No queries...</div>
73
- {% endfor %}
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
- <form
2
- data-querystats
3
- action="."
4
- method="get"
5
- target="querystats"
6
- class="relative group/querystats"
7
- style="display: none;">
8
- <input type="hidden" name="querystats" value="store">
9
- <button type="submit" class="px-2 py-px text-xs rounded-full bg-stone-700 text-stone-300 whitespace-nowrap" data-querystats-summary></button>
10
- <div data-querystats-list style="display: none;" class="absolute right-0 z-50 hidden translate-y-full -bottom-1 group/querystats-hover:block">
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 most recent timing call
19
- const latestTiming = querystatsTimings[querystatsTimings.length - 1];
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
- </form>
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.TOOLBAR_CLASS, str):
16
- cls = import_string(settings.TOOLBAR_CLASS)
15
+ if isinstance(settings.ADMIN_TOOLBAR_CLASS, str):
16
+ cls = import_string(settings.ADMIN_TOOLBAR_CLASS)
17
17
  else:
18
- cls = settings.TOOLBAR_CLASS
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.TOOLBAR_VERSION
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
+ ![Plain Admin user example](https://assets.plainframework.com/docs/plain-pageviews-user.png)
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=ln0toiRsIQ_BbHRNlz1OfC31km1DBHRdgAvfHSlujXo,7353
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=j7RdgGqksCmCgPO7zCcFiVV9f8yW-EULvqDcFOhQap8,127
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=jLhJkuvqnPMBQTP-kzojFaqmFi50GZHvrVzuZCLc3rk,836
8
- plain/admin/toolbar.py,sha256=dsZa_I-tTbaeOluCbvHGEqy4_Suw6Q_JSrKl8Eu08qY,973
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=GLhKwWwO2OwN2wneAgfbKRQzIIjZqegZYb1fMVwiljY,4281
33
- plain/admin/querystats/middleware.py,sha256=M1EVdX11H545IdZlppbSIL_h8hzBIrMELrYrcAb4aq0,3192
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=58UpxaBp_H80Tf7azi4QcphgHbXgP5iqLDf-qZJfzRI,788
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=CMH3TDBXXxoxrICMIxiLfo4cN7ae9DMCg3WNmZR8M_o,3504
70
- plain/admin/templates/querystats/toolbar.html,sha256=dePs614akVWUD8IlgzvQ0TREThv1ttKPj-yOPzJxmXM,3574
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.25.0.dist-info/METADATA,sha256=eIkWBWIZCXM3Y2otclnUElQI8jDUxOLt_bcBKG3h4_c,7790
80
- plain_admin-0.25.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
81
- plain_admin-0.25.0.dist-info/licenses/LICENSE,sha256=cvKM3OlqHx3ijD6e34zsSUkPvzl-ya3Dd63A6EHL94U,1500
82
- plain_admin-0.25.0.dist-info/RECORD,,
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,,