django-unfold 0.41.0__py3-none-any.whl → 0.43.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.
- {django_unfold-0.41.0.dist-info → django_unfold-0.43.0.dist-info}/METADATA +1 -1
- {django_unfold-0.41.0.dist-info → django_unfold-0.43.0.dist-info}/RECORD +63 -55
- unfold/admin.py +8 -1
- unfold/components.py +47 -0
- unfold/contrib/filters/admin.py +23 -33
- unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +1 -1
- unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +1 -1
- unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +1 -1
- unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
- unfold/contrib/import_export/templates/admin/import_export/import.html +1 -1
- unfold/contrib/import_export/templates/admin/import_export/import_validation.html +26 -21
- unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +5 -3
- unfold/contrib/simple_history/templates/simple_history/object_history_form.html +1 -1
- unfold/contrib/simple_history/templates/simple_history/submit_line.html +1 -1
- unfold/settings.py +1 -0
- unfold/sites.py +25 -5
- unfold/static/unfold/css/styles.css +1 -1
- unfold/static/unfold/js/app.js +21 -15
- unfold/styles.css +14 -1
- unfold/templates/admin/base.html +3 -3
- unfold/templates/admin/change_form.html +9 -1
- unfold/templates/admin/change_list.html +1 -1
- unfold/templates/admin/date_hierarchy.html +5 -5
- unfold/templates/admin/delete_confirmation.html +1 -1
- unfold/templates/admin/delete_selected_confirmation.html +1 -1
- unfold/templates/admin/edit_inline/stacked.html +8 -2
- unfold/templates/admin/edit_inline/tabular.html +8 -2
- unfold/templates/admin/nav_sidebar.html +1 -10
- unfold/templates/admin/object_history.html +1 -1
- unfold/templates/admin/submit_line.html +2 -2
- unfold/templates/unfold/change_list_filter.html +5 -13
- unfold/templates/unfold/components/card.html +1 -1
- unfold/templates/unfold/components/chart/cohort.html +59 -0
- unfold/templates/unfold/components/navigation.html +1 -1
- unfold/templates/unfold/components/tracker.html +5 -0
- unfold/templates/unfold/helpers/account_links.html +1 -1
- unfold/templates/unfold/helpers/actions_row.html +9 -7
- unfold/templates/unfold/helpers/field_readonly.html +1 -1
- unfold/templates/unfold/helpers/form_label.html +1 -1
- unfold/templates/unfold/helpers/header.html +1 -1
- unfold/templates/unfold/helpers/label.html +1 -1
- unfold/templates/unfold/helpers/language_switch.html +27 -0
- unfold/templates/unfold/helpers/messages/debug.html +3 -0
- unfold/templates/unfold/helpers/messages/error.html +5 -3
- unfold/templates/unfold/helpers/messages/info.html +2 -2
- unfold/templates/unfold/helpers/messages/success.html +3 -0
- unfold/templates/unfold/helpers/messages/warning.html +3 -0
- unfold/templates/unfold/helpers/messages.html +13 -14
- unfold/templates/unfold/helpers/tab_list.html +3 -3
- unfold/templates/unfold/helpers/theme_switch.html +1 -1
- unfold/templates/unfold/helpers/userlinks.html +4 -0
- unfold/templates/unfold/helpers/welcomemsg.html +9 -1
- unfold/templates/unfold/layouts/base_simple.html +1 -1
- unfold/templates/unfold/layouts/skeleton.html +1 -1
- unfold/templates/unfold/templatetags/preserve_changelist_filters.html +3 -0
- unfold/templates/unfold/widgets/clearable_file_input.html +1 -1
- unfold/templates/unfold/widgets/clearable_file_input_small.html +1 -1
- unfold/templates/unfold/widgets/split_datetime_vertical.html +1 -1
- unfold/templatetags/unfold.py +55 -9
- unfold/utils.py +17 -0
- unfold/widgets.py +6 -2
- {django_unfold-0.41.0.dist-info → django_unfold-0.43.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.41.0.dist-info → django_unfold-0.43.0.dist-info}/WHEEL +0 -0
@@ -8,7 +8,7 @@
|
|
8
8
|
{% for item in tabs_list %}
|
9
9
|
{% if item.has_permission %}
|
10
10
|
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-gray-800">
|
11
|
-
<a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}" class="block px-3 py-2 md:py-4 md:px-0 dark:border-gray-800 {% if item.active %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-
|
11
|
+
<a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}" class="block px-3 py-2 md:py-4 md:px-0 dark:border-gray-800 {% if item.active %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}">
|
12
12
|
{{ item.title }}
|
13
13
|
</a>
|
14
14
|
</li>
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
|
21
21
|
href="#general"
|
22
22
|
x-on:click="activeTab = 'general'"
|
23
|
-
x-bind:class="{'border-b border-gray-200 dark:border-gray-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == 'general', 'hover:text-
|
23
|
+
x-bind:class="{'border-b border-gray-200 dark:border-gray-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == 'general', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-gray-800': activeTab != 'general'}">
|
24
24
|
{% trans "General" %}
|
25
25
|
</a>
|
26
26
|
</li>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
<a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
|
31
31
|
href="#{{ inline.opts.verbose_name|slugify }}"
|
32
32
|
x-on:click="activeTab = '{{ inline.opts.verbose_name|slugify }}'"
|
33
|
-
x-bind:class="{'border-b border-gray-200 dark:border-gray-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ inline.opts.verbose_name|slugify }}', 'hover:text-
|
33
|
+
x-bind:class="{'border-b border-gray-200 dark:border-gray-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ inline.opts.verbose_name|slugify }}', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-gray-800': activeTab != '{{ inline.opts.verbose_name|slugify }}'}">
|
34
34
|
{% if inline.formset.max_num == 1 %}
|
35
35
|
{{ inline.opts.verbose_name|capfirst }}
|
36
36
|
{% else %}
|
@@ -7,7 +7,7 @@
|
|
7
7
|
</span>
|
8
8
|
</a>
|
9
9
|
|
10
|
-
<nav class="absolute bg-white border flex flex-col leading-none
|
10
|
+
<nav class="absolute bg-white border flex flex-col leading-none py-1 -right-2 rounded shadow-lg top-7 w-40 z-50 dark:bg-gray-800 dark:border-gray-700" x-cloak x-show="openTheme" @click.outside="openTheme = false">
|
11
11
|
<a class="cursor-pointer flex flex-row leading-none mx-1 px-3 py-1.5 rounded hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200"
|
12
12
|
x-on:click="adminTheme = 'dark'"
|
13
13
|
x-bind:class="adminTheme == 'dark' && 'text-primary-600 dark:text-primary-500 dark:hover:!text-primary-500 hover:!text-primary-600'">
|
@@ -7,6 +7,10 @@
|
|
7
7
|
{% include "unfold/helpers/label.html" with text=environment.0 type=environment.1 %}
|
8
8
|
{% endif %}
|
9
9
|
|
10
|
+
{% if show_languages %}
|
11
|
+
{% include "unfold/helpers/language_switch.html" %}
|
12
|
+
{% endif %}
|
13
|
+
|
10
14
|
{% if not theme %}
|
11
15
|
{% include "unfold/helpers/theme_switch.html" %}
|
12
16
|
{% endif %}
|
@@ -1,6 +1,14 @@
|
|
1
1
|
{% load i18n %}
|
2
2
|
|
3
|
-
<div class="flex-grow font-semibold min-w-0 mr-3">
|
3
|
+
<div class="flex flex-row flex-grow font-semibold items-center min-w-0 mr-3">
|
4
|
+
<span class="cursor-pointer hidden flex-row items-center text-sm xl:flex">
|
5
|
+
<span class="material-symbols-outlined" hx-get="{% url "admin:toggle_sidebar" %}" hx-swap="none" x-on:click="sidebarDesktopOpen = !sidebarDesktopOpen">
|
6
|
+
dock_to_right
|
7
|
+
</span>
|
8
|
+
</span>
|
9
|
+
|
10
|
+
<span class="hidden bg-gray-200 h-5 mx-3 w-px xl:block dark:bg-gray-700"></span>
|
11
|
+
|
4
12
|
<h1 class="overflow-hidden text-ellipsis text-sm whitespace-nowrap xl:text-base text-font-important-light dark:text-font-important-dark">
|
5
13
|
{% if content_title %}
|
6
14
|
<span class="tracking-tight">
|
@@ -21,7 +21,7 @@
|
|
21
21
|
</div>
|
22
22
|
{% endif %}
|
23
23
|
|
24
|
-
<input type="text" aria-label="{% trans 'Choose file to upload' %}" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium px-3 py-2 text-ellipsis dark:bg-gray-900">
|
24
|
+
<input type="text" aria-label="{% trans 'Choose file to upload' %}" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium min-w-0 px-3 py-2 text-ellipsis dark:bg-gray-900">
|
25
25
|
|
26
26
|
<div class="flex flex-none items-center leading-none self-stretch">
|
27
27
|
<div class="hidden">
|
@@ -14,7 +14,7 @@
|
|
14
14
|
</div>
|
15
15
|
{% endif %}
|
16
16
|
|
17
|
-
<input type="text" aria-label="{% trans 'Choose file to upload' %}" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium px-3 py-2 text-ellipsis dark:bg-gray-900">
|
17
|
+
<input type="text" aria-label="{% trans 'Choose file to upload' %}" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium min-w-0 px-3 py-2 text-ellipsis dark:bg-gray-900">
|
18
18
|
|
19
19
|
<div class="flex flex-none items-center leading-none self-stretch">
|
20
20
|
<div class="hidden">
|
unfold/templatetags/unfold.py
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
-
from typing import Any, Dict, List, Mapping, Optional, Union
|
1
|
+
from typing import Any, Dict, List, Mapping, Optional, Set, Union
|
2
2
|
|
3
3
|
from django import template
|
4
4
|
from django.contrib.admin.helpers import AdminForm, Fieldset
|
5
|
+
from django.contrib.admin.views.main import ChangeList
|
5
6
|
from django.forms import Field
|
6
|
-
from django.
|
7
|
+
from django.http import HttpRequest
|
8
|
+
from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
|
7
9
|
from django.template.base import NodeList, Parser, Token, token_kwargs
|
8
10
|
from django.template.loader import render_to_string
|
9
11
|
from django.utils.safestring import SafeText
|
10
12
|
|
13
|
+
from unfold.components import ComponentRegistry
|
14
|
+
|
11
15
|
register = Library()
|
12
16
|
|
13
17
|
|
@@ -152,24 +156,37 @@ class RenderComponentNode(template.Node):
|
|
152
156
|
template_name: str,
|
153
157
|
nodelist: NodeList,
|
154
158
|
extra_context: Optional[Dict] = None,
|
159
|
+
include_context: bool = False,
|
155
160
|
*args,
|
156
161
|
**kwargs,
|
157
162
|
):
|
158
163
|
self.template_name = template_name
|
159
164
|
self.nodelist = nodelist
|
160
165
|
self.extra_context = extra_context or {}
|
166
|
+
self.include_context = include_context
|
161
167
|
super().__init__(*args, **kwargs)
|
162
168
|
|
163
169
|
def render(self, context: RequestContext) -> str:
|
164
|
-
|
170
|
+
values = {
|
171
|
+
name: var.resolve(context) for name, var in self.extra_context.items()
|
172
|
+
}
|
173
|
+
|
174
|
+
values.update(
|
175
|
+
{
|
176
|
+
"children": self.nodelist.render(context),
|
177
|
+
}
|
178
|
+
)
|
165
179
|
|
166
|
-
|
167
|
-
|
180
|
+
if "component_class" in values:
|
181
|
+
values = ComponentRegistry.create_instance(
|
182
|
+
values["component_class"], request=context.request
|
183
|
+
).get_context_data(**values)
|
184
|
+
|
185
|
+
if self.include_context:
|
186
|
+
values.update(context.flatten())
|
168
187
|
|
169
188
|
return render_to_string(
|
170
|
-
self.template_name,
|
171
|
-
request=context.request,
|
172
|
-
context=ctx,
|
189
|
+
self.template_name, request=context.request, context=values
|
173
190
|
)
|
174
191
|
|
175
192
|
|
@@ -200,17 +217,21 @@ def do_component(parser: Parser, token: Token) -> str:
|
|
200
217
|
raise TemplateSyntaxError(
|
201
218
|
'"with" in {bits[0]} tag needs at least one keyword argument.'
|
202
219
|
)
|
220
|
+
elif option == "include_context":
|
221
|
+
value = True
|
203
222
|
else:
|
204
223
|
raise TemplateSyntaxError(f"Unknown argument for {bits[0]} tag: {option}.")
|
205
224
|
|
206
225
|
options[option] = value
|
207
226
|
|
227
|
+
include_context = options.get("include_context", False)
|
208
228
|
nodelist = parser.parse(("endcomponent",))
|
209
229
|
template_name = bits[1][1:-1]
|
230
|
+
|
210
231
|
extra_context = options.get("with", {})
|
211
232
|
parser.next_token()
|
212
233
|
|
213
|
-
return RenderComponentNode(template_name, nodelist, extra_context)
|
234
|
+
return RenderComponentNode(template_name, nodelist, extra_context, include_context)
|
214
235
|
|
215
236
|
|
216
237
|
@register.filter
|
@@ -224,3 +245,28 @@ def add_css_class(field: Field, classes: Union[list, tuple]) -> Field:
|
|
224
245
|
field.field.widget.attrs["class"] = classes
|
225
246
|
|
226
247
|
return field
|
248
|
+
|
249
|
+
|
250
|
+
@register.inclusion_tag(
|
251
|
+
"unfold/templatetags/preserve_changelist_filters.html",
|
252
|
+
takes_context=True,
|
253
|
+
name="preserve_filters",
|
254
|
+
)
|
255
|
+
def preserve_changelist_filters(context: Context) -> Dict[str, Dict[str, str]]:
|
256
|
+
"""
|
257
|
+
Generate hidden input fields to preserve filters for POST forms.
|
258
|
+
"""
|
259
|
+
request: Optional[HttpRequest] = context.get("request")
|
260
|
+
changelist: Optional[ChangeList] = context.get("cl")
|
261
|
+
|
262
|
+
if not request or not changelist:
|
263
|
+
return {"params": {}}
|
264
|
+
|
265
|
+
used_params: Set[str] = {
|
266
|
+
param for spec in changelist.filter_specs for param in spec.used_parameters
|
267
|
+
}
|
268
|
+
preserved_params: Dict[str, str] = {
|
269
|
+
param: value for param, value in request.GET.items() if param not in used_params
|
270
|
+
}
|
271
|
+
|
272
|
+
return {"params": preserved_params}
|
unfold/utils.py
CHANGED
@@ -3,6 +3,7 @@ import decimal
|
|
3
3
|
import json
|
4
4
|
from typing import Any, Iterable, List, Optional
|
5
5
|
|
6
|
+
from django.conf import settings
|
6
7
|
from django.db import models
|
7
8
|
from django.template.loader import render_to_string
|
8
9
|
from django.utils import formats, timezone
|
@@ -146,3 +147,19 @@ def prettify_json(data: Any) -> Optional[str]:
|
|
146
147
|
f'<div class="block dark:hidden">{format_response(response, "colorful")}</div>'
|
147
148
|
f'<div class="hidden dark:block">{format_response(response, "monokai")}</div>'
|
148
149
|
)
|
150
|
+
|
151
|
+
|
152
|
+
def parse_date_str(value: str) -> Optional[datetime.date]:
|
153
|
+
for format in settings.DATE_INPUT_FORMATS:
|
154
|
+
try:
|
155
|
+
return datetime.datetime.strptime(value, format).date()
|
156
|
+
except (ValueError, TypeError):
|
157
|
+
continue
|
158
|
+
|
159
|
+
|
160
|
+
def parse_datetime_str(value: str) -> Optional[datetime.datetime]:
|
161
|
+
for format in settings.DATETIME_INPUT_FORMATS:
|
162
|
+
try:
|
163
|
+
return datetime.datetime.strptime(value, format)
|
164
|
+
except (ValueError, TypeError):
|
165
|
+
continue
|
unfold/widgets.py
CHANGED
@@ -35,7 +35,7 @@ from .exceptions import UnfoldException
|
|
35
35
|
|
36
36
|
LABEL_CLASSES = [
|
37
37
|
"block",
|
38
|
-
"font-
|
38
|
+
"font-semibold",
|
39
39
|
"mb-2",
|
40
40
|
"text-font-important-light",
|
41
41
|
"text-sm",
|
@@ -43,7 +43,7 @@ LABEL_CLASSES = [
|
|
43
43
|
]
|
44
44
|
|
45
45
|
CHECKBOX_LABEL_CLASSES = [
|
46
|
-
"font-
|
46
|
+
"font-semibold",
|
47
47
|
"ml-2",
|
48
48
|
"text-sm",
|
49
49
|
"text-font-important-light",
|
@@ -332,6 +332,8 @@ class UnfoldAdminImageSmallFieldWidget(FileFieldMixin, AdminFileWidget):
|
|
332
332
|
|
333
333
|
|
334
334
|
class UnfoldAdminDateWidget(AdminDateWidget):
|
335
|
+
template_name = "unfold/widgets/date.html"
|
336
|
+
|
335
337
|
def __init__(
|
336
338
|
self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
|
337
339
|
) -> None:
|
@@ -358,6 +360,8 @@ class UnfoldAdminSingleDateWidget(AdminDateWidget):
|
|
358
360
|
|
359
361
|
|
360
362
|
class UnfoldAdminTimeWidget(AdminTimeWidget):
|
363
|
+
template_name = "unfold/widgets/time.html"
|
364
|
+
|
361
365
|
def __init__(
|
362
366
|
self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
|
363
367
|
) -> None:
|
File without changes
|
File without changes
|