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.
Files changed (80) hide show
  1. plain/admin/README.md +260 -0
  2. plain/admin/__init__.py +5 -0
  3. plain/admin/assets/admin/admin.css +108 -0
  4. plain/admin/assets/admin/admin.js +79 -0
  5. plain/admin/assets/admin/chart.js +19 -0
  6. plain/admin/assets/admin/jquery-3.6.1.slim.min.js +2 -0
  7. plain/admin/assets/admin/list.js +57 -0
  8. plain/admin/assets/admin/popper.min.js +5 -0
  9. plain/admin/assets/admin/tippy-bundle.umd.min.js +1 -0
  10. plain/admin/assets/toolbar/toolbar.js +51 -0
  11. plain/admin/cards/__init__.py +10 -0
  12. plain/admin/cards/base.py +86 -0
  13. plain/admin/cards/charts.py +153 -0
  14. plain/admin/cards/tables.py +26 -0
  15. plain/admin/config.py +21 -0
  16. plain/admin/dates.py +254 -0
  17. plain/admin/default_settings.py +4 -0
  18. plain/admin/impersonate/README.md +44 -0
  19. plain/admin/impersonate/__init__.py +3 -0
  20. plain/admin/impersonate/middleware.py +38 -0
  21. plain/admin/impersonate/models.py +0 -0
  22. plain/admin/impersonate/permissions.py +16 -0
  23. plain/admin/impersonate/settings.py +8 -0
  24. plain/admin/impersonate/urls.py +10 -0
  25. plain/admin/impersonate/views.py +23 -0
  26. plain/admin/middleware.py +12 -0
  27. plain/admin/querystats/README.md +191 -0
  28. plain/admin/querystats/__init__.py +3 -0
  29. plain/admin/querystats/core.py +153 -0
  30. plain/admin/querystats/middleware.py +99 -0
  31. plain/admin/querystats/urls.py +9 -0
  32. plain/admin/querystats/views.py +27 -0
  33. plain/admin/templates/admin/base.html +160 -0
  34. plain/admin/templates/admin/cards/base.html +30 -0
  35. plain/admin/templates/admin/cards/card.html +17 -0
  36. plain/admin/templates/admin/cards/chart.html +25 -0
  37. plain/admin/templates/admin/cards/table.html +35 -0
  38. plain/admin/templates/admin/delete.html +17 -0
  39. plain/admin/templates/admin/detail.html +24 -0
  40. plain/admin/templates/admin/form.html +13 -0
  41. plain/admin/templates/admin/index.html +5 -0
  42. plain/admin/templates/admin/list.html +194 -0
  43. plain/admin/templates/admin/page.html +3 -0
  44. plain/admin/templates/admin/search.html +27 -0
  45. plain/admin/templates/admin/values/UUID.html +1 -0
  46. plain/admin/templates/admin/values/bool.html +9 -0
  47. plain/admin/templates/admin/values/datetime.html +1 -0
  48. plain/admin/templates/admin/values/default.html +5 -0
  49. plain/admin/templates/admin/values/dict.html +1 -0
  50. plain/admin/templates/admin/values/get_display.html +1 -0
  51. plain/admin/templates/admin/values/img.html +4 -0
  52. plain/admin/templates/admin/values/list.html +1 -0
  53. plain/admin/templates/admin/values/model.html +15 -0
  54. plain/admin/templates/admin/values/queryset.html +7 -0
  55. plain/admin/templates/elements/admin/Checkbox.html +8 -0
  56. plain/admin/templates/elements/admin/CheckboxField.html +7 -0
  57. plain/admin/templates/elements/admin/FieldErrors.html +5 -0
  58. plain/admin/templates/elements/admin/Input.html +9 -0
  59. plain/admin/templates/elements/admin/InputField.html +5 -0
  60. plain/admin/templates/elements/admin/Label.html +3 -0
  61. plain/admin/templates/elements/admin/Select.html +11 -0
  62. plain/admin/templates/elements/admin/SelectField.html +5 -0
  63. plain/admin/templates/elements/admin/Submit.html +6 -0
  64. plain/admin/templates/querystats/querystats.html +78 -0
  65. plain/admin/templates/querystats/toolbar.html +79 -0
  66. plain/admin/templates/toolbar/toolbar.html +91 -0
  67. plain/admin/templates.py +25 -0
  68. plain/admin/toolbar.py +36 -0
  69. plain/admin/urls.py +45 -0
  70. plain/admin/views/__init__.py +41 -0
  71. plain/admin/views/base.py +140 -0
  72. plain/admin/views/models.py +254 -0
  73. plain/admin/views/objects.py +399 -0
  74. plain/admin/views/registry.py +117 -0
  75. plain/admin/views/types.py +6 -0
  76. plain/admin/views/viewsets.py +54 -0
  77. plain_admin-0.14.1.dist-info/METADATA +275 -0
  78. plain_admin-0.14.1.dist-info/RECORD +80 -0
  79. plain_admin-0.14.1.dist-info/WHEEL +4 -0
  80. plain_admin-0.14.1.dist-info/licenses/LICENSE +28 -0
plain/admin/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # Admin
2
+
3
+ An admin interface for admin users.
4
+
5
+ The Plain Admin is a new package built from the ground up.
6
+ It leverages class-based views and standard URLs and templates to provide a flexible admin where
7
+ you can quickly create your own pages and cards,
8
+ in addition to models.
9
+
10
+ - cards
11
+ - dashboards
12
+ - diy forms
13
+ - detached from login (do your own login (oauth, passkeys, etc))
14
+
15
+ ## Installation
16
+
17
+ - install plain.admin and plain.htmx, add plain.admin.admin and plain.htmx to installed packages
18
+ - add url
19
+
20
+ ## Models in the admin
21
+
22
+ ## Dashboards
23
+
24
+ <!-- # plain.querystats
25
+
26
+ On-page database query stats in development and production.
27
+
28
+ On each page, the query stats will display how many database queries were performed and how long they took.
29
+
30
+ [Watch on YouTube](https://www.youtube.com/watch?v=NX8VXxVJm08)
31
+
32
+ Clicking the stats in the toolbar will show the full SQL query log with tracebacks and timings.
33
+ This is even designed to work in production,
34
+ making it much easier to discover and debug performance issues on production data!
35
+
36
+ ![Django query stats](https://user-images.githubusercontent.com/649496/213781593-54197bb6-36a8-4c9d-8294-5b43bd86a4c9.png)
37
+
38
+ It will also point out duplicate queries,
39
+ which can typically be removed by using `select_related`,
40
+ `prefetch_related`, or otherwise refactoring your code.
41
+
42
+ ## Installation
43
+
44
+ ```python
45
+ # settings.py
46
+ INSTALLED_PACKAGES = [
47
+ # ...
48
+ "plain.admin.querystats",
49
+ ]
50
+
51
+ MIDDLEWARE = [
52
+ "plain.sessions.middleware.SessionMiddleware",
53
+ "plain.auth.middleware.AuthenticationMiddleware",
54
+
55
+ "plain.admin.querystats.QueryStatsMiddleware",
56
+ # Put additional middleware below querystats
57
+ # ...
58
+ ]
59
+ ```
60
+
61
+ We strongly recommend using the plain-toolbar along with this,
62
+ but if you aren't,
63
+ you can add the querystats to your frontend templates with this include:
64
+
65
+ ```html
66
+ {% include "querystats/button.html" %}
67
+ ```
68
+
69
+ *Note that you will likely want to surround this with an if `DEBUG` or `is_admin` check.*
70
+
71
+ To view querystats you need to send a POST request to `?querystats=store` (i.e. via a `<form>`),
72
+ and the template include is the easiest way to do that.
73
+
74
+ ## Tailwind CSS
75
+
76
+ This package is styled with [Tailwind CSS](https://tailwindcss.com/),
77
+ and pairs well with [`plain-tailwind`](https://github.com/plainpackages/plain-tailwind).
78
+
79
+ If you are using your own Tailwind implementation,
80
+ you can modify the "content" in your Tailwind config to include any Plain packages:
81
+
82
+ ```js
83
+ // tailwind.config.js
84
+ module.exports = {
85
+ content: [
86
+ // ...
87
+ ".venv/lib/python*/site-packages/plain*/**/*.{html,js}",
88
+ ],
89
+ // ...
90
+ }
91
+ ```
92
+
93
+ If you aren't using Tailwind, and don't intend to, open an issue to discuss other options.
94
+
95
+
96
+ # plain.toolbar
97
+
98
+ The admin toolbar is enabled for every user who `is_admin`.
99
+
100
+ ![Plain Admin toolbar](https://user-images.githubusercontent.com/649496/213781915-a2094f54-99b8-4a05-a36e-dee107405229.png)
101
+
102
+ ## Installation
103
+
104
+ Add `plaintoolbar` to your `INSTALLED_PACKAGES`,
105
+ and the `{% toolbar %}` to your base template:
106
+
107
+ ```python
108
+ # settings.py
109
+ INSTALLED_PACKAGES += [
110
+ "plaintoolbar",
111
+ ]
112
+ ```
113
+
114
+ ```html
115
+ <!-- base.template.html -->
116
+ {% load toolbar %}
117
+ <!doctype html>
118
+ <html lang="en">
119
+ <head>
120
+ ...
121
+ </head>
122
+ <body>
123
+ {% toolbar %}
124
+ ...
125
+ </body>
126
+ ```
127
+
128
+ More specific settings can be found below.
129
+
130
+ ## Tailwind CSS
131
+
132
+ This package is styled with [Tailwind CSS](https://tailwindcss.com/),
133
+ and pairs well with [`plain-tailwind`](https://github.com/plainpackages/plain-tailwind).
134
+
135
+ If you are using your own Tailwind implementation,
136
+ you can modify the "content" in your Tailwind config to include any Plain packages:
137
+
138
+ ```js
139
+ // tailwind.config.js
140
+ module.exports = {
141
+ content: [
142
+ // ...
143
+ ".venv/lib/python*/site-packages/plain*/**/*.{html,js}",
144
+ ],
145
+ // ...
146
+ }
147
+ ```
148
+
149
+ If you aren't using Tailwind, and don't intend to, open an issue to discuss other options.
150
+
151
+
152
+ # plain.requestlog
153
+
154
+ The request log stores a local history of HTTP requests and responses during `plain work` (Django runserver).
155
+
156
+ The request history will make it easy to see redirects,
157
+ 400 and 500 level errors,
158
+ form submissions,
159
+ API calls,
160
+ webhooks,
161
+ and more.
162
+
163
+ [Watch on YouTube](https://www.youtube.com/watch?v=AwI7Pt5oZnM)
164
+
165
+ Requests can be re-submitted by clicking the "replay" button.
166
+
167
+ [![Django request log](https://user-images.githubusercontent.com/649496/213781414-417ad043-de67-4836-9ef1-2b91404336c3.png)](https://user-images.githubusercontent.com/649496/213781414-417ad043-de67-4836-9ef1-2b91404336c3.png)
168
+
169
+ ## Installation
170
+
171
+ ```python
172
+ # settings.py
173
+ INSTALLED_PACKAGES += [
174
+ "plainrequestlog",
175
+ ]
176
+
177
+ MIDDLEWARE = MIDDLEWARE + [
178
+ # ...
179
+ "plainrequestlog.RequestLogMiddleware",
180
+ ]
181
+ ```
182
+
183
+ The default settings can be customized if needed:
184
+
185
+ ```python
186
+ # settings.py
187
+ DEV_REQUESTS_IGNORE_PATHS = [
188
+ "/sw.js",
189
+ "/favicon.ico",
190
+ "/admin/jsi18n/",
191
+ ]
192
+ DEV_REQUESTS_MAX = 50
193
+ ```
194
+
195
+ ## Tailwind CSS
196
+
197
+ This package is styled with [Tailwind CSS](https://tailwindcss.com/),
198
+ and pairs well with [`plain-tailwind`](https://github.com/plainpackages/plain-tailwind).
199
+
200
+ If you are using your own Tailwind implementation,
201
+ you can modify the "content" in your Tailwind config to include any Plain packages:
202
+
203
+ ```js
204
+ // tailwind.config.js
205
+ module.exports = {
206
+ content: [
207
+ // ...
208
+ ".venv/lib/python*/site-packages/plain*/**/*.{html,js}",
209
+ ],
210
+ // ...
211
+ }
212
+ ```
213
+
214
+ If you aren't using Tailwind, and don't intend to, open an issue to discuss other options.
215
+
216
+
217
+ # plain.impersonate
218
+
219
+ See what your users see.
220
+
221
+ A key feature for providing customer support is to be able to view the site through their account.
222
+ With `impersonate` installed, you can impersonate a user by finding them in the Django admin and clicking the "Impersonate" button.
223
+
224
+ ![](/docs/img/impersonate-admin.png)
225
+
226
+ Then with the [admin toolbar](/docs/plain-toolbar/) enabled, you'll get a notice of the impersonation and a button to exit:
227
+
228
+ ![](/docs/img/impersonate-bar.png)
229
+
230
+ ## Installation
231
+
232
+ To impersonate users, you need the app, middleware, and URLs:
233
+
234
+ ```python
235
+ # settings.py
236
+ INSTALLED_PACKAGES = INSTALLED_PACKAGES + [
237
+ "plain.admin.impersonate",
238
+ ]
239
+
240
+ MIDDLEWARE = MIDDLEWARE + [
241
+ "plain.admin.impersonate.ImpersonateMiddleware",
242
+ ]
243
+ ```
244
+
245
+ ```python
246
+ # urls.py
247
+ urlpatterns = [
248
+ # ...
249
+ path("impersonate/", include("plain.admin.impersonate.urls")),
250
+ ]
251
+ ```
252
+
253
+ ## Settings
254
+
255
+ By default, all admin users can impersonate other users.
256
+
257
+ ```python
258
+ # settings.py
259
+ IMPERSONATE_ALLOWED = lambda user: user.is_admin
260
+ ``` -->
@@ -0,0 +1,5 @@
1
+ from .middleware import AdminMiddleware
2
+
3
+ __all__ = [
4
+ "AdminMiddleware",
5
+ ]
@@ -0,0 +1,108 @@
1
+ /*
2
+ These standard CSS rules are intended to make it easier for the
3
+ end-user to drop in HTML elements and have a decent, consistent starting point.
4
+
5
+ We shouldn't use many custom classes in here (like .btn) because the user
6
+ could unintentionally overwrite those since our CSS is combined.
7
+ */
8
+ table {
9
+ width: 100%;
10
+ font-size: .875rem;
11
+ line-height: 1.25rem;
12
+ table-layout: auto;
13
+ }
14
+
15
+ th {
16
+ text-align: left;
17
+ padding: 0 0.5rem;
18
+ color: rgb(55, 65, 81);
19
+ }
20
+
21
+ table th a {
22
+ color: rgb(55, 65, 81);
23
+ }
24
+
25
+ tbody tr:hover {
26
+ background-color: rgb(255, 255, 255, 0.1);
27
+ }
28
+
29
+ td {
30
+ padding: 0.5rem 0.5rem;
31
+ white-space: nowrap;
32
+ border-bottom: 1px solid rgb(255, 255, 255, 0.1);
33
+ }
34
+ tr:last-child td {
35
+ border-bottom: none;
36
+ }
37
+
38
+ table img {
39
+ height: 1.2rem;
40
+ border-radius: 2px;
41
+ }
42
+
43
+ main a {
44
+ color: rgb(37, 99, 235);
45
+ }
46
+
47
+ main a:hover {
48
+ text-decoration: underline;
49
+ }
50
+
51
+ .actions a, .actions button, main button {
52
+ display: inline-block;
53
+ padding: 8px 16px;
54
+ font-size: 14px;
55
+ font-weight: 500;
56
+ color: #ffffff;
57
+ text-align: center;
58
+ text-decoration: none;
59
+ background-color: #2a2826;
60
+ border: 1px solid #3f3d3b;
61
+ border-radius: 6px;
62
+ transition: background-color 0.2s, border-color 0.2s, transform 0.2s;
63
+ cursor: pointer;
64
+
65
+ &:hover {
66
+ background-color: #2a2928;
67
+ border-color: #504e4c;
68
+ transform: translateY(-1px);
69
+ }
70
+
71
+ &:active {
72
+ background-color: #141312;
73
+ border-color: #3f3d3b;
74
+ transform: translateY(0);
75
+ }
76
+
77
+ &:focus {
78
+ outline: 2px solid #e5e7eb;
79
+ outline-offset: 2px;
80
+ }
81
+ }
82
+
83
+ /* Cards use these? */
84
+ section {
85
+ border: rgba(255, 255, 255, 0.1) 1px solid;
86
+ border-radius: 0.275rem;
87
+ overflow: auto;
88
+ background-color: rgba(255, 255, 255, 0.05);
89
+ color: rgba(255, 255, 255, 0.8);
90
+ display: flex;
91
+ flex-direction: column;
92
+ }
93
+
94
+ section > header {
95
+ padding: 0.5rem;
96
+ /* background-color: #FFFCF0; */
97
+ border-bottom: rgba(255, 255, 255, 0.05) 1px solid;
98
+ }
99
+
100
+ section > div {
101
+ padding: 0.75rem 0.5rem;
102
+ flex-grow: 1;
103
+ display: flex;
104
+ flex-direction: column;
105
+ align-items: center;
106
+ justify-content: center;
107
+ overflow: auto;
108
+ }
@@ -0,0 +1,79 @@
1
+ jQuery(function($) {
2
+ $("[data-toggle]").on("click", function(e) {
3
+ e.preventDefault();
4
+ var targets = $(this).data("toggle").split(",");
5
+ $.each(targets, function(index, target) {
6
+ var $target = $(target);
7
+ if ($target.data("toggle-class")) {
8
+ $target.toggleClass($target.data("toggle-class"));
9
+ } else {
10
+ $target.toggle();
11
+ }
12
+ });
13
+ });
14
+
15
+ $("[data-autosubmit]").on("change", function(e) {
16
+ $(this).closest("form").submit();
17
+ });
18
+
19
+ function createDropdowns(target) {
20
+ $(target).find("[data-dropdown]").each(function() {
21
+ var template = this.querySelector("template");
22
+ tippy(this, {
23
+ content: template.innerHTML,
24
+ trigger: "click",
25
+ allowHTML: true,
26
+ interactive: true,
27
+ duration: 100,
28
+ placement: "bottom-end",
29
+ offset: [0, 6],
30
+ arrow: false,
31
+ appendTo: () => document.body,
32
+ onCreate: (instance) => {
33
+ instance.popper.classList.add("*:bg-white/15")
34
+ instance.popper.classList.add("*:w-48")
35
+ instance.popper.classList.add("*:rounded-md")
36
+ instance.popper.classList.add("*:shadow-lg")
37
+ instance.popper.classList.add("*:ring-1")
38
+ instance.popper.classList.add("*:ring-white")
39
+ instance.popper.classList.add("*:ring-opacity-5")
40
+ },
41
+ });
42
+ });
43
+ }
44
+
45
+ function createTooltips(target) {
46
+ $(target).find("[data-tooltip]").each(function() {
47
+ tippy(this, {
48
+ content: this.dataset.tooltip,
49
+ duration: 100,
50
+ });
51
+ });
52
+ }
53
+
54
+ function autolinkColumns(target) {
55
+ $(target).find("[data-column-autolink]").each(function() {
56
+ var $this = $(this);
57
+ if ($this.find("a").length > 0) {
58
+ // Column already has a link, so don't add another
59
+ return;
60
+ }
61
+ var $link = $(document.createElement("a"));
62
+ $link.attr("href", $this.data("column-autolink"));
63
+ $link.addClass("flex p-2 -m-2 text-white/80 hover:no-underline");
64
+ $(this).wrapInner($link);
65
+ })
66
+ }
67
+
68
+ createDropdowns(document);
69
+ createTooltips(document);
70
+ autolinkColumns(document);
71
+
72
+ // Search uses htmx to load elements,
73
+ // so we need to hook those up too.
74
+ htmx.on("htmx:afterSwap", function(evt) {
75
+ createDropdowns(evt.detail.target);
76
+ createTooltips(evt.detail.target);
77
+ autolinkColumns(evt.detail.target);
78
+ });
79
+ });