plain.admin 0.24.0__tar.gz → 0.25.1__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.
Files changed (91) hide show
  1. {plain_admin-0.24.0 → plain_admin-0.25.1}/.gitignore +2 -0
  2. plain_admin-0.25.1/PKG-INFO +178 -0
  3. plain_admin-0.25.1/plain/admin/README.md +163 -0
  4. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/admin/admin.js +6 -1
  5. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/views/objects.py +52 -52
  6. {plain_admin-0.24.0 → plain_admin-0.25.1}/pyproject.toml +1 -1
  7. plain_admin-0.24.0/PKG-INFO +0 -316
  8. plain_admin-0.24.0/plain/admin/README.md +0 -301
  9. {plain_admin-0.24.0 → plain_admin-0.25.1}/LICENSE +0 -0
  10. {plain_admin-0.24.0 → plain_admin-0.25.1}/README.md +0 -0
  11. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/__init__.py +0 -0
  12. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/admin/admin.css +0 -0
  13. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/admin/chart.js +0 -0
  14. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/admin/jquery-3.6.1.slim.min.js +0 -0
  15. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/admin/list.js +0 -0
  16. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/admin/popper.min.js +0 -0
  17. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/admin/tippy-bundle.umd.min.js +0 -0
  18. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/assets/toolbar/toolbar.js +0 -0
  19. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/cards/__init__.py +0 -0
  20. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/cards/base.py +0 -0
  21. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/cards/charts.py +0 -0
  22. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/cards/tables.py +0 -0
  23. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/config.py +0 -0
  24. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/dates.py +0 -0
  25. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/default_settings.py +0 -0
  26. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/README.md +0 -0
  27. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/__init__.py +0 -0
  28. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/middleware.py +0 -0
  29. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/models.py +0 -0
  30. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/permissions.py +0 -0
  31. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/settings.py +0 -0
  32. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/urls.py +0 -0
  33. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/impersonate/views.py +0 -0
  34. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/middleware.py +0 -0
  35. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/querystats/README.md +0 -0
  36. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/querystats/__init__.py +0 -0
  37. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/querystats/core.py +0 -0
  38. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/querystats/middleware.py +0 -0
  39. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/querystats/urls.py +0 -0
  40. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/querystats/views.py +0 -0
  41. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/base.html +0 -0
  42. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/cards/base.html +0 -0
  43. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/cards/card.html +0 -0
  44. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/cards/chart.html +0 -0
  45. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/cards/table.html +0 -0
  46. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/delete.html +0 -0
  47. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/detail.html +0 -0
  48. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/index.html +0 -0
  49. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/list.html +0 -0
  50. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/page.html +0 -0
  51. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/search.html +0 -0
  52. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/UUID.html +0 -0
  53. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/bool.html +0 -0
  54. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/datetime.html +0 -0
  55. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/default.html +0 -0
  56. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/dict.html +0 -0
  57. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/get_display.html +0 -0
  58. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/img.html +0 -0
  59. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/list.html +0 -0
  60. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/model.html +0 -0
  61. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/admin/values/queryset.html +0 -0
  62. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/Checkbox.html +0 -0
  63. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/CheckboxField.html +0 -0
  64. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/FieldErrors.html +0 -0
  65. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/Help.html +0 -0
  66. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/Input.html +0 -0
  67. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/InputField.html +0 -0
  68. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/Label.html +0 -0
  69. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/Select.html +0 -0
  70. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/SelectField.html +0 -0
  71. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/Submit.html +0 -0
  72. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/Textarea.html +0 -0
  73. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/elements/admin/TextareaField.html +0 -0
  74. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/querystats/querystats.html +0 -0
  75. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/querystats/toolbar.html +0 -0
  76. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates/toolbar/toolbar.html +0 -0
  77. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/templates.py +0 -0
  78. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/toolbar.py +0 -0
  79. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/urls.py +0 -0
  80. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/views/__init__.py +0 -0
  81. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/views/base.py +0 -0
  82. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/views/models.py +0 -0
  83. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/views/registry.py +0 -0
  84. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/views/types.py +0 -0
  85. {plain_admin-0.24.0 → plain_admin-0.25.1}/plain/admin/views/viewsets.py +0 -0
  86. {plain_admin-0.24.0 → plain_admin-0.25.1}/tests/app/settings.py +0 -0
  87. {plain_admin-0.24.0 → plain_admin-0.25.1}/tests/app/urls.py +0 -0
  88. {plain_admin-0.24.0 → plain_admin-0.25.1}/tests/app/users/migrations/0001_initial.py +0 -0
  89. {plain_admin-0.24.0 → plain_admin-0.25.1}/tests/app/users/migrations/__init__.py +0 -0
  90. {plain_admin-0.24.0 → plain_admin-0.25.1}/tests/app/users/models.py +0 -0
  91. {plain_admin-0.24.0 → plain_admin-0.25.1}/tests/test_admin.py +0 -0
@@ -11,3 +11,5 @@ plain*/tests/.plain
11
11
 
12
12
  # Ottobot
13
13
  .aider*
14
+
15
+ /llms-full.txt
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: plain.admin
3
+ Version: 0.25.1
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
@@ -0,0 +1,163 @@
1
+ # plain.admin
2
+
3
+ **Manage your app with a backend interface.**
4
+
5
+ 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.
6
+
7
+ ![Plain Admin user example](https://assets.plainframework.com/docs/plain-pageviews-user.png)
8
+
9
+ ## Installation
10
+
11
+ Install the `plain.admin` package and its dependencies.
12
+
13
+ ```console
14
+ uv add plain.admin
15
+ ```
16
+
17
+ 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:
18
+
19
+ ```python
20
+ # app/settings.py
21
+ INSTALLED_PACKAGES = [
22
+ "plain.models",
23
+ "plain.tailwind",
24
+ "plain.auth",
25
+ "plain.sessions",
26
+ "plain.htmx",
27
+ "plain.admin",
28
+ "plain.elements",
29
+ # other packages...
30
+ ]
31
+
32
+ AUTH_USER_MODEL = "users.User"
33
+ AUTH_LOGIN_URL = "login"
34
+
35
+ MIDDLEWARE = [
36
+ "plain.sessions.middleware.SessionMiddleware",
37
+ "plain.auth.middleware.AuthenticationMiddleware",
38
+ "plain.admin.AdminMiddleware",
39
+ ]
40
+ ```
41
+
42
+ Your User model is expected to have an `is_admin` field (or attribute) for checking who has permission to access the admin.
43
+
44
+ ```python
45
+ # app/users/models.py
46
+ from plain import models
47
+
48
+
49
+ @models.register_model
50
+ class User(models.Model):
51
+ is_admin = models.BooleanField(default=False)
52
+ # other fields...
53
+ ```
54
+
55
+ To make the admin accessible, add the `AdminRouter` to your root URLs.
56
+
57
+ ```python
58
+ # app/urls.py
59
+ from plain.admin.urls import AdminRouter
60
+ from plain.urls import Router, include, path
61
+
62
+ from . import views
63
+
64
+
65
+ class AppRouter(Router):
66
+ namespace = ""
67
+ urls = [
68
+ include("admin/", AdminRouter),
69
+ path("login/", views.LoginView, name="login"),
70
+ path("logout/", LogoutView, name="logout"),
71
+ # other urls...
72
+ ]
73
+
74
+ ```
75
+
76
+ 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!).
77
+
78
+ ```html
79
+ <!-- app/templates/base.html -->
80
+ <!DOCTYPE html>
81
+ <html lang="en">
82
+ <head>
83
+ <meta charset="UTF-8">
84
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
85
+ <title>{{ html_title|default("My App") }}</title>
86
+ {% tailwind_css %}
87
+ </head>
88
+ <body>
89
+ {% block content required %}{% endblock %}
90
+
91
+ {% toolbar %}
92
+ </body>
93
+ </html>
94
+ ```
95
+
96
+ ## Admin viewsets
97
+
98
+ 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.
99
+
100
+ ```python
101
+ # app/users/admin.py
102
+ from plain.admin.views import (
103
+ AdminModelDetailView,
104
+ AdminModelListView,
105
+ AdminModelUpdateView,
106
+ AdminViewset,
107
+ register_viewset,
108
+ )
109
+ from plain.models.forms import ModelForm
110
+
111
+ from .models import User
112
+
113
+
114
+ class UserForm(ModelForm):
115
+ class Meta:
116
+ model = User
117
+ fields = ["email"]
118
+
119
+
120
+ @register_viewset
121
+ class UserAdmin(AdminViewset):
122
+ class ListView(AdminModelListView):
123
+ model = User
124
+ fields = [
125
+ "id",
126
+ "email",
127
+ "created_at__date",
128
+ ]
129
+ queryset_order = ["-created_at"]
130
+ search_fields = [
131
+ "email",
132
+ ]
133
+
134
+ class DetailView(AdminModelDetailView):
135
+ model = User
136
+
137
+ class UpdateView(AdminModelUpdateView):
138
+ template_name = "admin/users/user_form.html"
139
+ model = User
140
+ form_class = UserForm
141
+ ```
142
+
143
+ 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.
144
+
145
+ ## Admin cards
146
+
147
+ TODO
148
+
149
+ ## Admin forms
150
+
151
+ TODO
152
+
153
+ ## Toolbar
154
+
155
+ TODO
156
+
157
+ ## Impersonate
158
+
159
+ TODO
160
+
161
+ ## Querystats
162
+
163
+ TODO
@@ -58,8 +58,13 @@ jQuery(function($) {
58
58
  // Column already has a link, so don't add another
59
59
  return;
60
60
  }
61
+ var autolinkUrl = $this.data("column-autolink");
62
+ if (!autolinkUrl) {
63
+ // No URL, so don't add a link
64
+ return;
65
+ }
61
66
  var $link = $(document.createElement("a"));
62
- $link.attr("href", $this.data("column-autolink"));
67
+ $link.attr("href", autolinkUrl);
63
68
  $link.addClass("flex p-2 -m-2 text-white/80 hover:no-underline");
64
69
  $(this).wrapInner($link);
65
70
  })
@@ -144,29 +144,29 @@ class AdminListView(HTMXViewMixin, AdminView):
144
144
  "admin/values/default.html",
145
145
  ]
146
146
 
147
- def get_list_url(self) -> str | None:
148
- return None
147
+ def get_list_url(self) -> str:
148
+ return ""
149
149
 
150
- def get_create_url(self) -> str | None:
151
- return None
150
+ def get_create_url(self) -> str:
151
+ return ""
152
152
 
153
- def get_detail_url(self, obj) -> str | None:
154
- return None
153
+ def get_detail_url(self, obj) -> str:
154
+ return ""
155
155
 
156
- def get_update_url(self, obj) -> str | None:
157
- return None
156
+ def get_update_url(self, obj) -> str:
157
+ return ""
158
158
 
159
- def get_delete_url(self, obj) -> str | None:
160
- return None
159
+ def get_delete_url(self, obj) -> str:
160
+ return ""
161
161
 
162
- def get_object_url(self, obj) -> str | None:
162
+ def get_object_url(self, obj) -> str:
163
163
  if url := self.get_detail_url(obj):
164
164
  return url
165
165
  if url := self.get_update_url(obj):
166
166
  return url
167
167
  if url := self.get_delete_url(obj):
168
168
  return url
169
- return None
169
+ return ""
170
170
 
171
171
  def get_object_links(self, obj) -> dict[str]:
172
172
  links = {}
@@ -191,20 +191,20 @@ class AdminListView(HTMXViewMixin, AdminView):
191
191
  class AdminCreateView(AdminView, CreateView):
192
192
  template_name = None
193
193
 
194
- def get_list_url(self) -> str | None:
195
- return None
194
+ def get_list_url(self) -> str:
195
+ return ""
196
196
 
197
- def get_create_url(self) -> str | None:
198
- return None
197
+ def get_create_url(self) -> str:
198
+ return ""
199
199
 
200
- def get_detail_url(self, obj) -> str | None:
201
- return None
200
+ def get_detail_url(self, obj) -> str:
201
+ return ""
202
202
 
203
- def get_update_url(self, obj) -> str | None:
204
- return None
203
+ def get_update_url(self, obj) -> str:
204
+ return ""
205
205
 
206
- def get_delete_url(self, obj) -> str | None:
207
- return None
206
+ def get_delete_url(self, obj) -> str:
207
+ return ""
208
208
 
209
209
  def get_success_url(self, form):
210
210
  if list_url := self.get_list_url():
@@ -274,20 +274,20 @@ class AdminDetailView(AdminView, DetailView):
274
274
 
275
275
  return templates
276
276
 
277
- def get_list_url(self) -> str | None:
278
- return None
277
+ def get_list_url(self) -> str:
278
+ return ""
279
279
 
280
- def get_create_url(self) -> str | None:
281
- return None
280
+ def get_create_url(self) -> str:
281
+ return ""
282
282
 
283
- def get_detail_url(self, obj) -> str | None:
284
- return None
283
+ def get_detail_url(self, obj) -> str:
284
+ return ""
285
285
 
286
- def get_update_url(self, obj) -> str | None:
287
- return None
286
+ def get_update_url(self, obj) -> str:
287
+ return ""
288
288
 
289
- def get_delete_url(self, obj) -> str | None:
290
- return None
289
+ def get_delete_url(self, obj) -> str:
290
+ return ""
291
291
 
292
292
  def get_fields(self):
293
293
  return self.fields.copy() # Avoid mutating the class attribute itself
@@ -311,20 +311,20 @@ class AdminUpdateView(AdminView, UpdateView):
311
311
  template_name = None
312
312
  nav_section = ""
313
313
 
314
- def get_list_url(self) -> str | None:
315
- return None
314
+ def get_list_url(self) -> str:
315
+ return ""
316
316
 
317
- def get_create_url(self) -> str | None:
318
- return None
317
+ def get_create_url(self) -> str:
318
+ return ""
319
319
 
320
- def get_detail_url(self, obj) -> str | None:
321
- return None
320
+ def get_detail_url(self, obj) -> str:
321
+ return ""
322
322
 
323
- def get_update_url(self, obj) -> str | None:
324
- return None
323
+ def get_update_url(self, obj) -> str:
324
+ return ""
325
325
 
326
- def get_delete_url(self, obj) -> str | None:
327
- return None
326
+ def get_delete_url(self, obj) -> str:
327
+ return ""
328
328
 
329
329
  def get_description(self):
330
330
  return repr(self.object)
@@ -363,20 +363,20 @@ class AdminDeleteView(AdminView, DeleteView):
363
363
  def get_description(self):
364
364
  return repr(self.object)
365
365
 
366
- def get_list_url(self) -> str | None:
367
- return None
366
+ def get_list_url(self) -> str:
367
+ return ""
368
368
 
369
- def get_create_url(self) -> str | None:
370
- return None
369
+ def get_create_url(self) -> str:
370
+ return ""
371
371
 
372
- def get_detail_url(self, obj) -> str | None:
373
- return None
372
+ def get_detail_url(self, obj) -> str:
373
+ return ""
374
374
 
375
- def get_update_url(self, obj) -> str | None:
376
- return None
375
+ def get_update_url(self, obj) -> str:
376
+ return ""
377
377
 
378
- def get_delete_url(self, obj) -> str | None:
379
- return None
378
+ def get_delete_url(self, obj) -> str:
379
+ return ""
380
380
 
381
381
  def get_links(self):
382
382
  links = super().get_links()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.admin"
3
- version = "0.24.0"
3
+ version = "0.25.1"
4
4
  description = "Admin dashboard and tools for Plain."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  license = "BSD-3-Clause"