plain.admin 0.18.0__py3-none-any.whl → 0.19.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.
plain/admin/cards/base.py CHANGED
@@ -2,7 +2,6 @@ from enum import Enum
2
2
 
3
3
  from plain.http import HttpRequest
4
4
  from plain.templates import Template
5
- from plain.utils.text import slugify
6
5
  from plain.views import View
7
6
 
8
7
 
@@ -22,7 +21,6 @@ class Card:
22
21
  title: str
23
22
 
24
23
  # Optional fields
25
- slug: str = ""
26
24
  description: str = ""
27
25
  text: str = ""
28
26
  link: str = ""
@@ -60,8 +58,9 @@ class Card:
60
58
  def get_title(self) -> str:
61
59
  return self.title
62
60
 
63
- def get_slug(self) -> str:
64
- return self.slug or slugify(self.title)
61
+ @classmethod
62
+ def get_slug(cls) -> str:
63
+ return f"{cls.__module__}.{cls.__name__}".lower().replace(".", "_")
65
64
 
66
65
  def get_description(self) -> str:
67
66
  return self.description
@@ -78,7 +78,7 @@
78
78
  {% for view in views %}
79
79
  {% set url = view.get_view_url() %}
80
80
  <a
81
- {% if url == request.path %}data-active{% endif %}
81
+ {% if url == request.path or view in parent_view_classes %}data-active{% endif %}
82
82
  class="data-[active]:bg-white/20 data-[active]:text-white flex items-center px-2 py-1 mt-px -mx-2 text-sm rounded hover:text-stone-300 text-stone-400/80 hover:bg-white/5"
83
83
  href="{{ url }}">
84
84
  {{ view.get_nav_title() }}
@@ -3,5 +3,6 @@
3
3
  name="{{ field.html_name }}"
4
4
  class="block w-full text-sm border rounded border-white/10 bg-white/5"
5
5
  rows="{{ rows|default(3) }}"
6
+ spellcheck="{{ spellcheck|default('default') }}"
6
7
  {% if field.field.required %}required{% endif %}
7
8
  >{{ field.value() or "" }}</textarea>
plain/admin/urls.py CHANGED
@@ -10,7 +10,6 @@ from .views.registry import registry
10
10
  class AdminIndexView(AdminView):
11
11
  template_name = "admin/index.html"
12
12
  title = "Dashboard"
13
- slug = ""
14
13
 
15
14
  def get(self):
16
15
  # Slight hack to redirect to the first view that doesn't
@@ -24,7 +23,6 @@ class AdminIndexView(AdminView):
24
23
  class AdminSearchView(AdminView):
25
24
  template_name = "admin/search.html"
26
25
  title = "Search"
27
- slug = "search"
28
26
 
29
27
  def get_template_context(self):
30
28
  context = super().get_template_context()
plain/admin/views/base.py CHANGED
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
3
3
  from plain.auth.views import AuthViewMixin
4
4
  from plain.urls import reverse
5
5
  from plain.utils import timezone
6
- from plain.utils.text import slugify
7
6
  from plain.views import (
8
7
  TemplateView,
9
8
  )
@@ -22,7 +21,6 @@ class AdminView(AuthViewMixin, TemplateView):
22
21
  admin_required = True
23
22
 
24
23
  title: str = ""
25
- slug: str = ""
26
24
  path: str = ""
27
25
  description: str = ""
28
26
  image: Img | None = None
@@ -69,15 +67,7 @@ class AdminView(AuthViewMixin, TemplateView):
69
67
 
70
68
  @classmethod
71
69
  def get_slug(cls) -> str:
72
- if cls.slug:
73
- return cls.slug
74
-
75
- if cls.title:
76
- return slugify(cls.title)
77
-
78
- raise NotImplementedError(
79
- f"Please set a slug on the {cls} class or implement get_slug()."
80
- )
70
+ return f"{cls.__module__}.{cls.__qualname__}".lower().replace(".", "_")
81
71
 
82
72
  # Can actually use @classmethod, @staticmethod or regular method for these?
83
73
  def get_title(self) -> str:
@@ -91,15 +81,7 @@ class AdminView(AuthViewMixin, TemplateView):
91
81
 
92
82
  @classmethod
93
83
  def get_path(cls) -> str:
94
- if cls.path:
95
- return cls.path
96
-
97
- if slug := cls.get_slug():
98
- return slug
99
-
100
- raise NotImplementedError(
101
- f"Please set a path on the {cls} class or implement get_slug() or get_path()."
102
- )
84
+ return cls.path
103
85
 
104
86
  @classmethod
105
87
  def get_parent_view_classes(cls) -> list["AdminView"]:
@@ -68,8 +68,11 @@ class AdminModelListView(AdminListView):
68
68
  return cls.model._meta.model_name.capitalize() + "s"
69
69
 
70
70
  @classmethod
71
- def get_slug(cls) -> str:
72
- return cls.model._meta.model_name
71
+ def get_path(cls) -> str:
72
+ if path := super().get_path():
73
+ return path
74
+
75
+ return f"{cls.model._meta.model_name}/"
73
76
 
74
77
  def get_template_context(self):
75
78
  context = super().get_template_context()
@@ -137,12 +140,11 @@ class AdminModelDetailView(AdminDetailView):
137
140
  def get_title(self) -> str:
138
141
  return str(self.object)
139
142
 
140
- @classmethod
141
- def get_slug(cls) -> str:
142
- return f"{cls.model._meta.model_name}_detail"
143
-
144
143
  @classmethod
145
144
  def get_path(cls) -> str:
145
+ if path := super().get_path():
146
+ return path
147
+
146
148
  return f"{cls.model._meta.model_name}/<int:pk>/"
147
149
 
148
150
  def get_fields(self):
@@ -182,12 +184,11 @@ class AdminModelCreateView(AdminCreateView):
182
184
 
183
185
  return f"New {self.model._meta.model_name}"
184
186
 
185
- @classmethod
186
- def get_slug(cls) -> str:
187
- return f"{cls.model._meta.model_name}_create"
188
-
189
187
  @classmethod
190
188
  def get_path(cls) -> str:
189
+ if path := super().get_path():
190
+ return path
191
+
191
192
  return f"{cls.model._meta.model_name}/create/"
192
193
 
193
194
  def get_template_names(self):
@@ -213,12 +214,11 @@ class AdminModelUpdateView(AdminUpdateView):
213
214
 
214
215
  return f"Update {self.object}"
215
216
 
216
- @classmethod
217
- def get_slug(cls) -> str:
218
- return f"{cls.model._meta.model_name}_update"
219
-
220
217
  @classmethod
221
218
  def get_path(cls) -> str:
219
+ if path := super().get_path():
220
+ return path
221
+
222
222
  return f"{cls.model._meta.model_name}/<int:pk>/update/"
223
223
 
224
224
  def get_object(self):
@@ -242,12 +242,11 @@ class AdminModelDeleteView(AdminDeleteView):
242
242
  def get_title(self) -> str:
243
243
  return f"Delete {self.object}"
244
244
 
245
- @classmethod
246
- def get_slug(cls) -> str:
247
- return f"{cls.model._meta.model_name}_delete"
248
-
249
245
  @classmethod
250
246
  def get_path(cls) -> str:
247
+ if path := super().get_path():
248
+ return path
249
+
251
250
  return f"{cls.model._meta.model_name}/<int:pk>/delete/"
252
251
 
253
252
  def get_object(self):
@@ -306,18 +306,6 @@ class AdminDetailView(AdminView, DetailView):
306
306
 
307
307
  return links
308
308
 
309
- def get_success_url(self, form):
310
- if detail_url := self.get_detail_url(self.object):
311
- return detail_url
312
-
313
- if list_url := self.get_list_url():
314
- return list_url
315
-
316
- if update_url := self.get_update_url(self.object):
317
- return update_url
318
-
319
- return super().get_success_url(form)
320
-
321
309
 
322
310
  class AdminUpdateView(AdminView, UpdateView):
323
311
  template_name = None
@@ -355,6 +343,18 @@ class AdminUpdateView(AdminView, UpdateView):
355
343
 
356
344
  return links
357
345
 
346
+ def get_success_url(self, form):
347
+ if detail_url := self.get_detail_url(self.object):
348
+ return detail_url
349
+
350
+ if list_url := self.get_list_url():
351
+ return list_url
352
+
353
+ if update_url := self.get_update_url(self.object):
354
+ return update_url
355
+
356
+ return super().get_success_url(form)
357
+
358
358
 
359
359
  class AdminDeleteView(AdminView, DeleteView):
360
360
  template_name = "admin/delete.html"
@@ -67,18 +67,26 @@ class AdminViewRegistry:
67
67
  def get_urls(self):
68
68
  urls = []
69
69
 
70
- paths_seen = set()
71
-
72
- def add_view_path(view, _path):
73
- if _path in paths_seen:
74
- raise ValueError(f"Path {_path} already registered")
75
- paths_seen.add(_path)
76
- if not _path.endswith("/"):
77
- _path += "/"
78
- urls.append(path(_path, view, name=view.view_name()))
70
+ paths_seen = {}
79
71
 
80
72
  for view in self.registered_views:
81
- add_view_path(view, f"p/{view.get_path()}")
73
+ view_path = view.get_path()
74
+
75
+ if not view_path:
76
+ raise ValueError(f"Path for {view} is empty")
77
+
78
+ if existing_view := paths_seen.get(view_path, None):
79
+ raise ValueError(
80
+ f"Duplicate admin path {view_path}\n{existing_view}\n{view}"
81
+ )
82
+
83
+ paths_seen[view_path] = view
84
+
85
+ # Append trailing slashes automatically
86
+ if not view_path.endswith("/"):
87
+ view_path += "/"
88
+
89
+ urls.append(path(f"p/{view_path}", view, name=view.view_name()))
82
90
 
83
91
  return urls
84
92
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.admin
3
- Version: 0.18.0
3
+ Version: 0.19.1
4
4
  Summary: Admin dashboard and tools for Plain.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-Expression: BSD-3-Clause
@@ -6,7 +6,7 @@ plain/admin/default_settings.py,sha256=j7RdgGqksCmCgPO7zCcFiVV9f8yW-EULvqDcFOhQa
6
6
  plain/admin/middleware.py,sha256=k3yP1o3CzvLiZZSoxqq-DvAZlp4sICRauaT-kD3FJKM,398
7
7
  plain/admin/templates.py,sha256=jLhJkuvqnPMBQTP-kzojFaqmFi50GZHvrVzuZCLc3rk,836
8
8
  plain/admin/toolbar.py,sha256=dsZa_I-tTbaeOluCbvHGEqy4_Suw6Q_JSrKl8Eu08qY,973
9
- plain/admin/urls.py,sha256=xGZDs103SStrlny806pqfvuA-UBUI2Xy_eXtq1VtoBI,1365
9
+ plain/admin/urls.py,sha256=Q44_HpivSHJfDQWJLtm_OQVWm0VQlqAg8sa1M1NdxFM,1331
10
10
  plain/admin/assets/admin/admin.css,sha256=qSM3Q99I2YbWxNmRjSORz9yCZ-H7oeGppTFE4QzByvk,2580
11
11
  plain/admin/assets/admin/admin.js,sha256=AWD6UqPxGqJFaUhYTDWe4niTgk0thzU4gRl7qK41KNc,2759
12
12
  plain/admin/assets/admin/chart.js,sha256=GZiCYXjL6SmyuSCGE0Df80QvOUkw6H2YD-zsVID05lo,205089
@@ -16,7 +16,7 @@ plain/admin/assets/admin/popper.min.js,sha256=SgCxkjQZdrt2puqn62YUu9hknpCBGBEAy9
16
16
  plain/admin/assets/admin/tippy-bundle.umd.min.js,sha256=oVWBpeGTKMG_iBWGkQF02JnGIMFPYuFqTjUWeJY3pZ0,25668
17
17
  plain/admin/assets/toolbar/toolbar.js,sha256=kRCQ37iQNklzBjjBeHSeBU39mLpQ4Q0pnC3cdbQAy28,1636
18
18
  plain/admin/cards/__init__.py,sha256=8NfWrguyJRriJFUc3_QeGaDILhgeU3d1aXktzIuAR1E,172
19
- plain/admin/cards/base.py,sha256=IUqHOWB0t7sM1vVUrbHT76alP-7Jwga3FfagNgKTJww,2250
19
+ plain/admin/cards/base.py,sha256=g9t-pQq8O8gqMbTVdTujacfDQguFMq_aoRIKZkme_SA,2238
20
20
  plain/admin/cards/charts.py,sha256=fbCypn4_2uhFnNgj7z1T7bhSjQVtlxODnctynI6yrqI,5017
21
21
  plain/admin/cards/tables.py,sha256=lGUBeSaBsNVuzINVH8qU-1XF0PfPY03gcUKtN-462zE,599
22
22
  plain/admin/impersonate/README.md,sha256=GT7ubMxyB2RhUh-gDg_yYqWSm7oMp0hy1LepXyDRMo8,1012
@@ -33,7 +33,7 @@ plain/admin/querystats/core.py,sha256=GLhKwWwO2OwN2wneAgfbKRQzIIjZqegZYb1fMVwilj
33
33
  plain/admin/querystats/middleware.py,sha256=M1EVdX11H545IdZlppbSIL_h8hzBIrMELrYrcAb4aq0,3192
34
34
  plain/admin/querystats/urls.py,sha256=SHYbWvxjg6kFHR-e6T4vFmh4VqyPG7gYHXxDKdB4qz0,230
35
35
  plain/admin/querystats/views.py,sha256=58UpxaBp_H80Tf7azi4QcphgHbXgP5iqLDf-qZJfzRI,788
36
- plain/admin/templates/admin/base.html,sha256=pLv6gKOSwTaNumhzl2ThLD--lq3u-aBVnSt64f-lkdI,8307
36
+ plain/admin/templates/admin/base.html,sha256=NvhAxtYQSTjkX9RZbp6mQ-uh_RZN6Bb02texjEKY5es,8338
37
37
  plain/admin/templates/admin/delete.html,sha256=lNuU2G-BR6TH6NUmh7VcvjnEuFeI84rwwk_oO1jkUq0,431
38
38
  plain/admin/templates/admin/detail.html,sha256=NOEUvZlHM5lvpq6ZnfMYS6UUBCMSya4CmAHxZr-joJQ,787
39
39
  plain/admin/templates/admin/index.html,sha256=b65tcZhv9QfvmjePySU7MmzUlpMECIXP8dBH-a3Eyxw,69
@@ -64,19 +64,19 @@ plain/admin/templates/elements/admin/Label.html,sha256=RVZZjrv4ff_ujSmKu2M7HngIy
64
64
  plain/admin/templates/elements/admin/Select.html,sha256=CUJD4cHno_bc0g_SQN0IV0a5sZL7Gx4fFB44xBse-Ic,458
65
65
  plain/admin/templates/elements/admin/SelectField.html,sha256=P2-vXifOs2-ie20AgLyni6Oy0iz0dAeOxBBGrZV4nIg,221
66
66
  plain/admin/templates/elements/admin/Submit.html,sha256=1Lgn3Du9rXplbM3V12z2JckSaiWPlPGLP48xIZ887AA,150
67
- plain/admin/templates/elements/admin/Textarea.html,sha256=zi9A_NAsXdZXpVTAS1KXNjQyhtVrqjyQvTHUPtf64uM,275
67
+ plain/admin/templates/elements/admin/Textarea.html,sha256=fOTZlLrcXlyuxkksZfBzgYf_IN4L59slfhQ6iJnjIbI,328
68
68
  plain/admin/templates/elements/admin/TextareaField.html,sha256=4IOJapBNEfhUpMkkLW-gliIefZCEMn5aKyW4QagfcNw,223
69
69
  plain/admin/templates/querystats/querystats.html,sha256=CMH3TDBXXxoxrICMIxiLfo4cN7ae9DMCg3WNmZR8M_o,3504
70
70
  plain/admin/templates/querystats/toolbar.html,sha256=dePs614akVWUD8IlgzvQ0TREThv1ttKPj-yOPzJxmXM,3574
71
71
  plain/admin/templates/toolbar/toolbar.html,sha256=KcGAG6kRmx60wfqEsdD5C4nDMilH-JvPjHoU6EktfaY,5985
72
72
  plain/admin/views/__init__.py,sha256=nF6AENZ3Xxyi08OTRrF6e-HYBkZSFj7XBK2mVzMYqN4,846
73
- plain/admin/views/base.py,sha256=ueMH1kvLInceIqKeR2w8HSiIT0MlAtv6i73k7ijsNUU,3943
74
- plain/admin/views/models.py,sha256=0zSrxvZa1ARmqUeSzKqXNS5fT0bVwydsNdSOK5AUhk8,7287
75
- plain/admin/views/objects.py,sha256=5jipTCJBnypM8nyD9YF-i2MLT9E4aWJbIK2GNMdtel0,11319
76
- plain/admin/views/registry.py,sha256=aGLu9GMraLJBl60Kpfa6PXgno1vFsQZZI3YlXHClPYg,3504
73
+ plain/admin/views/base.py,sha256=S1oaMUXnMOwRozbn2K-tk9tL4BMimemfMagZD9QxrJw,3512
74
+ plain/admin/views/models.py,sha256=M4qoz3IpZuD_9DG25vKUNnfijAJhj33sXSciw2edkGE,7205
75
+ plain/admin/views/objects.py,sha256=Wcw7_k4BDz4h49e4BGtq6g-3jo5-T0fJU9CfMcvMQK4,11319
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.18.0.dist-info/METADATA,sha256=QlTNTD1FhrMs46kGdaSMvnq-tHexDY395oCw-4PtjCs,6821
80
- plain_admin-0.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
81
- plain_admin-0.18.0.dist-info/licenses/LICENSE,sha256=cvKM3OlqHx3ijD6e34zsSUkPvzl-ya3Dd63A6EHL94U,1500
82
- plain_admin-0.18.0.dist-info/RECORD,,
79
+ plain_admin-0.19.1.dist-info/METADATA,sha256=_zR8_z_dDwMuv_3cUpjSPdnuFxWagbm-jZ0pSRo4rdM,6821
80
+ plain_admin-0.19.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
81
+ plain_admin-0.19.1.dist-info/licenses/LICENSE,sha256=cvKM3OlqHx3ijD6e34zsSUkPvzl-ya3Dd63A6EHL94U,1500
82
+ plain_admin-0.19.1.dist-info/RECORD,,