django-unfold 0.58.0__py3-none-any.whl → 0.60.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.58.0.dist-info → django_unfold-0.60.0.dist-info}/METADATA +3 -2
- {django_unfold-0.58.0.dist-info → django_unfold-0.60.0.dist-info}/RECORD +60 -50
- unfold/admin.py +45 -13
- unfold/checks.py +24 -2
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +2 -2
- unfold/contrib/inlines/admin.py +11 -6
- unfold/contrib/inlines/forms.py +3 -1
- unfold/contrib/location_field/__init__.py +0 -0
- unfold/contrib/location_field/apps.py +6 -0
- unfold/contrib/location_field/templates/location_field/map_widget.html +5 -0
- unfold/decorators.py +27 -13
- unfold/fields.py +18 -20
- unfold/forms.py +99 -3
- unfold/layout.py +16 -0
- unfold/mixins/action_model_admin.py +22 -14
- unfold/mixins/base_model_admin.py +15 -1
- unfold/paginator.py +12 -1
- unfold/settings.py +8 -1
- unfold/sites.py +27 -29
- unfold/static/admin/js/admin/RelatedObjectLookups.js +9 -3
- unfold/static/unfold/css/styles.css +1 -1
- unfold/styles.css +12 -16
- unfold/templates/admin/app_list.html +4 -1
- unfold/templates/admin/base.html +1 -1
- unfold/templates/admin/change_form.html +2 -1
- unfold/templates/admin/edit_inline/stacked.html +12 -8
- unfold/templates/admin/edit_inline/tabular.html +2 -0
- unfold/templates/admin/includes/fieldset.html +9 -3
- unfold/templates/admin/login.html +45 -88
- unfold/templates/admin/pagination.html +1 -1
- unfold/templates/unfold/components/button.html +10 -1
- unfold/templates/unfold/helpers/account_links.html +14 -6
- unfold/templates/unfold/helpers/app_list.html +5 -2
- unfold/templates/unfold/helpers/app_list_default.html +2 -1
- unfold/templates/unfold/helpers/change_list_actions.html +2 -2
- unfold/templates/unfold/helpers/change_list_filter_actions.html +1 -1
- unfold/templates/unfold/helpers/edit_inline/tabular_heading.html +1 -1
- unfold/templates/unfold/helpers/empty_results.html +2 -2
- unfold/templates/unfold/helpers/field.html +5 -3
- unfold/templates/unfold/helpers/header.html +1 -1
- unfold/templates/unfold/helpers/language_form.html +10 -0
- unfold/templates/unfold/helpers/language_switch.html +17 -19
- unfold/templates/unfold/helpers/navigation.html +1 -1
- unfold/templates/unfold/helpers/pagination_infinite.html +3 -3
- unfold/templates/unfold/helpers/pagination_inline.html +28 -0
- unfold/templates/unfold/helpers/theme_switch.html +29 -27
- unfold/templates/unfold/helpers/unauthenticated_header.html +15 -0
- unfold/templates/unfold/helpers/unauthenticated_title.html +11 -0
- unfold/templates/unfold/helpers/userlinks.html +2 -6
- unfold/templates/unfold/helpers/welcomemsg.html +9 -7
- unfold/templates/unfold/layouts/unauthenticated.html +37 -0
- unfold/templates/unfold/widgets/select.html +1 -1
- unfold/templates/unfold/widgets/text.html +28 -0
- unfold/templates/unfold_crispy/layout/fieldset.html +3 -1
- unfold/templates/unfold_crispy/layout/fieldset_subheader.html +3 -0
- unfold/templatetags/unfold.py +37 -2
- unfold/utils.py +2 -2
- unfold/widgets.py +49 -3
- {django_unfold-0.58.0.dist-info → django_unfold-0.60.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.58.0.dist-info → django_unfold-0.60.0.dist-info}/WHEEL +0 -0
@@ -1,32 +1,34 @@
|
|
1
1
|
{% load i18n %}
|
2
2
|
|
3
|
-
|
4
|
-
<
|
5
|
-
<
|
6
|
-
<span
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
<nav class="absolute bg-white border border-base-200 flex flex-col leading-none py-1 -right-2 rounded-default shadow-lg top-7 w-40 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openTheme" x-transition x-on:click.outside="openTheme = false">
|
11
|
-
<a class="cursor-pointer flex flex-row leading-none mx-1 px-3 py-1.5 rounded-default hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"
|
12
|
-
x-on:click="adminTheme = 'dark'"
|
13
|
-
x-bind:class="adminTheme == 'dark' && 'text-primary-600 dark:text-primary-500 dark:hover:text-primary-500! hover:text-primary-600!'">
|
14
|
-
<span class="material-symbols-outlined mr-2">dark_mode</span>
|
15
|
-
<span class="leading-none self-center">{% trans "Dark" %}</span>
|
3
|
+
{% if not theme %}
|
4
|
+
<div class="relative" x-data="{ openTheme: false }">
|
5
|
+
<a class="block cursor-pointer h-[18px] leading-none hover:text-base-700 dark:hover:text-base-200" x-on:click="openTheme = !openTheme">
|
6
|
+
<span class="material-symbols-outlined">
|
7
|
+
<span x-text="adminTheme == 'dark' && 'dark_mode' || adminTheme == 'light' && 'light_mode' || 'computer'"></span>
|
8
|
+
</span>
|
16
9
|
</a>
|
17
10
|
|
18
|
-
<
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
11
|
+
<nav class="absolute bg-white border border-base-200 flex flex-col leading-none py-1 -right-2 rounded-default shadow-lg top-7 w-40 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openTheme" x-transition x-on:click.outside="openTheme = false">
|
12
|
+
<a class="cursor-pointer flex flex-row leading-none mx-1 px-3 py-1.5 rounded-default hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"
|
13
|
+
x-on:click="adminTheme = 'dark'"
|
14
|
+
x-bind:class="adminTheme == 'dark' && 'text-primary-600 dark:text-primary-500 dark:hover:text-primary-500! hover:text-primary-600!'">
|
15
|
+
<span class="material-symbols-outlined mr-2">dark_mode</span>
|
16
|
+
<span class="leading-none self-center">{% trans "Dark" %}</span>
|
17
|
+
</a>
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
<a class="cursor-pointer flex flex-row mx-1 px-3 py-1.5 rounded-default hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"
|
20
|
+
x-on:click="adminTheme = 'light'"
|
21
|
+
x-bind:class="adminTheme == 'light' && 'text-primary-600 dark:text-primary-500 dark:hover:text-primary-500! hover:text-primary-600!'">
|
22
|
+
<span class="material-symbols-outlined mr-2">light_mode</span>
|
23
|
+
<span class="leading-none self-center">{% trans "Light" %}</span>
|
24
|
+
</a>
|
25
|
+
|
26
|
+
<a class="cursor-pointer flex flex-row mx-1 px-3 py-1.5 rounded-default hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"
|
27
|
+
x-on:click="adminTheme = 'auto'"
|
28
|
+
x-bind:class="adminTheme == 'auto' && 'text-primary-600 dark:text-primary-500 dark:hover:text-primary-500! hover:text-primary-600!'">
|
29
|
+
<span class="material-symbols-outlined mr-2">computer</span>
|
30
|
+
<span class="leading-none self-center">{% trans "System" %}</span>
|
31
|
+
</a>
|
32
|
+
</nav>
|
33
|
+
</div>
|
34
|
+
{% endif %}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{% load i18n %}
|
2
|
+
|
3
|
+
<div class="absolute flex flex-row items-center justify-between left-0 m-4 right-0 top-0">
|
4
|
+
{% if site_url %}
|
5
|
+
<a href="{{ site_url }}" class="flex font-medium items-center text-sm text-primary-600 dark:text-primary-500">
|
6
|
+
<span class="material-symbols-outlined mr-2">arrow_back</span> {% trans 'Return to site' %}
|
7
|
+
</a>
|
8
|
+
{% endif %}
|
9
|
+
|
10
|
+
{% if not theme %}
|
11
|
+
<div class="ml-auto">
|
12
|
+
{% include "unfold/helpers/theme_switch.html" %}
|
13
|
+
</div>
|
14
|
+
{% endif %}
|
15
|
+
</div>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{% load i18n %}
|
2
|
+
|
3
|
+
<div class="border-b border-base-200 mb-8 pb-6 dark:border-base-800">
|
4
|
+
<h1 class="font-semibold">
|
5
|
+
{% if subtitle %}
|
6
|
+
<span class="block text-font-important-light dark:text-font-important-dark">{{ subtitle }} </span>
|
7
|
+
{% endif %}
|
8
|
+
|
9
|
+
<span class="block text-primary-600 text-xl dark:text-primary-500">{{ title }}</span>
|
10
|
+
</h1>
|
11
|
+
</div>
|
@@ -11,13 +11,9 @@
|
|
11
11
|
{{ extra_userlinks }}
|
12
12
|
{% endif %}
|
13
13
|
|
14
|
-
{%
|
15
|
-
{% include "unfold/helpers/language_switch.html" %}
|
16
|
-
{% endif %}
|
14
|
+
{% include "unfold/helpers/language_switch.html" %}
|
17
15
|
|
18
|
-
{%
|
19
|
-
{% include "unfold/helpers/theme_switch.html" %}
|
20
|
-
{% endif %}
|
16
|
+
{% include "unfold/helpers/theme_switch.html" %}
|
21
17
|
|
22
18
|
{% include "unfold/helpers/account_links.html" %}
|
23
19
|
</div>
|
@@ -1,15 +1,17 @@
|
|
1
1
|
{% load unfold i18n %}
|
2
2
|
|
3
3
|
<div class="flex flex-row grow font-semibold items-center min-w-0 mr-3">
|
4
|
-
|
5
|
-
<span class="
|
6
|
-
|
7
|
-
|
4
|
+
{% if is_nav_sidebar_enabled %}
|
5
|
+
<span class="cursor-pointer flex flex-row items-center">
|
6
|
+
<span class="material-symbols-outlined hidden! xl:block!" hx-get="{% url "admin:toggle_sidebar" %}" hx-swap="none" x-on:click="sidebarDesktopOpen = !sidebarDesktopOpen">
|
7
|
+
dock_to_right
|
8
|
+
</span>
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
<span class="material-symbols-outlined block! xl:hidden!" x-on:click="sidebarMobileOpen = !sidebarMobileOpen">
|
11
|
+
dock_to_right
|
12
|
+
</span>
|
11
13
|
</span>
|
12
|
-
|
14
|
+
{% endif %}
|
13
15
|
|
14
16
|
<span class="block bg-base-200 h-5 mx-3 w-px dark:bg-base-700"></span>
|
15
17
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
{% extends 'unfold/layouts/skeleton.html' %}
|
2
|
+
|
3
|
+
{% load i18n static unfold %}
|
4
|
+
|
5
|
+
|
6
|
+
{% block bodyclass %}{{ block.super }}bg-base-50 login dark:bg-base-900{% endblock %}
|
7
|
+
|
8
|
+
{% block usertools %}{% endblock %}
|
9
|
+
|
10
|
+
{% block nav-global %}{% endblock %}
|
11
|
+
|
12
|
+
{% block nav-sidebar %}{% endblock %}
|
13
|
+
|
14
|
+
{% block content_title %}{% endblock %}
|
15
|
+
|
16
|
+
{% block breadcrumbs %}{% endblock %}
|
17
|
+
|
18
|
+
{% block title %}
|
19
|
+
{{ title }} | {{ site_title }}
|
20
|
+
{% endblock %}
|
21
|
+
|
22
|
+
{% block base %}
|
23
|
+
<div id="page" class="bg-white flex min-h-screen dark:bg-base-900 ">
|
24
|
+
<div class="flex grow items-center justify-center mx-auto px-4 relative">
|
25
|
+
<div class="w-full sm:w-96">
|
26
|
+
{% block content %}{% endblock %}
|
27
|
+
</div>
|
28
|
+
|
29
|
+
{% include "unfold/helpers/unauthenticated_header.html" %}
|
30
|
+
</div>
|
31
|
+
|
32
|
+
{% if login_image %}
|
33
|
+
<div class="bg-cover grow hidden max-w-3xl xl:max-w-4xl xl:block" style="background-image: url('{{ login_image }}')">
|
34
|
+
</div>
|
35
|
+
{% endif %}
|
36
|
+
</div>
|
37
|
+
{% endblock %}
|
@@ -5,5 +5,5 @@
|
|
5
5
|
</optgroup>{% endif %}{% endfor %}
|
6
6
|
</select>
|
7
7
|
|
8
|
-
<span class="material-symbols-outlined absolute group-[.primary]:text-white -
|
8
|
+
<span class="material-symbols-outlined absolute group-[.primary]:text-white pointer-events-none mr-[12px] right-0 text-base-400 top-0 top-1/2 hover:text-base-700 dark:text-base-500 dark:hover:text-base-200 -translate-y-1/2">expand_more</span>
|
9
9
|
</div>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
<div class="max-w-2xl relative">
|
3
|
+
{% if widget.prefix %}
|
4
|
+
<span class="absolute left-3 top-0 bottom-0 flex items-center justify-center">
|
5
|
+
{{ widget.prefix }}
|
6
|
+
</span>
|
7
|
+
{% endif %}
|
8
|
+
|
9
|
+
{% if widget.prefix_icon %}
|
10
|
+
<span class="material-symbols-outlined absolute left-3 top-0 bottom-0 flex items-center justify-center text-base-400 dark:text-base-500">
|
11
|
+
{{ widget.prefix_icon }}
|
12
|
+
</span>
|
13
|
+
{% endif %}
|
14
|
+
|
15
|
+
{% include "django/forms/widgets/input.html" %}
|
16
|
+
|
17
|
+
{% if widget.suffix %}
|
18
|
+
<span class="absolute right-3 top-0 bottom-0 flex items-center justify-center">
|
19
|
+
{{ widget.suffix }}
|
20
|
+
</span>
|
21
|
+
{% endif %}
|
22
|
+
|
23
|
+
{% if widget.suffix_icon %}
|
24
|
+
<span class="material-symbols-outlined absolute right-3 top-0 bottom-0 flex items-center justify-center text-base-400 dark:text-base-500">
|
25
|
+
{{ widget.suffix_icon }}
|
26
|
+
</span>
|
27
|
+
{% endif %}
|
28
|
+
</div>
|
unfold/templatetags/unfold.py
CHANGED
@@ -4,12 +4,13 @@ from typing import Any, Optional, Union
|
|
4
4
|
|
5
5
|
from django import template
|
6
6
|
from django.contrib.admin.helpers import AdminForm, Fieldset
|
7
|
-
from django.contrib.admin.views.main import ChangeList
|
7
|
+
from django.contrib.admin.views.main import PAGE_VAR, ChangeList
|
8
8
|
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
|
9
|
+
from django.core.paginator import Paginator
|
9
10
|
from django.db.models import Model
|
10
11
|
from django.db.models.options import Options
|
11
12
|
from django.forms import BoundField, CheckboxSelectMultiple, Field
|
12
|
-
from django.http import HttpRequest
|
13
|
+
from django.http import HttpRequest, QueryDict
|
13
14
|
from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
|
14
15
|
from django.template.base import NodeList, Parser, Token, token_kwargs
|
15
16
|
from django.template.loader import render_to_string
|
@@ -569,3 +570,37 @@ def changeform_condition(field: BoundField) -> BoundField:
|
|
569
570
|
field.field.field.widget.attrs["x-model.fill"] = field.field.name
|
570
571
|
|
571
572
|
return field
|
573
|
+
|
574
|
+
|
575
|
+
@register.simple_tag
|
576
|
+
def infinite_paginator_url(cl, i):
|
577
|
+
return cl.get_query_string({PAGE_VAR: i})
|
578
|
+
|
579
|
+
|
580
|
+
@register.simple_tag
|
581
|
+
def elided_page_range(
|
582
|
+
paginator: Paginator, number: int
|
583
|
+
) -> Optional[list[Union[int, str]]]:
|
584
|
+
if not paginator or not number:
|
585
|
+
return None
|
586
|
+
|
587
|
+
return paginator.get_elided_page_range(number=number)
|
588
|
+
|
589
|
+
|
590
|
+
@register.simple_tag(takes_context=True)
|
591
|
+
def querystring_params(
|
592
|
+
context: RequestContext, query_key: str, query_value: str
|
593
|
+
) -> str:
|
594
|
+
request = context.get("request")
|
595
|
+
result = QueryDict(mutable=True)
|
596
|
+
|
597
|
+
for key, values in request.GET.lists():
|
598
|
+
if key == query_key:
|
599
|
+
continue
|
600
|
+
|
601
|
+
for value in values:
|
602
|
+
result[key] = value
|
603
|
+
|
604
|
+
result[query_key] = query_value
|
605
|
+
|
606
|
+
return result.urlencode()
|
unfold/utils.py
CHANGED
@@ -150,7 +150,7 @@ def hex_to_rgb(hex_color: str) -> list[int]:
|
|
150
150
|
return (r, g, b)
|
151
151
|
|
152
152
|
|
153
|
-
def prettify_json(data: Any) -> Optional[str]:
|
153
|
+
def prettify_json(data: Any, encoder: Any) -> Optional[str]:
|
154
154
|
try:
|
155
155
|
from pygments import highlight
|
156
156
|
from pygments.formatters import HtmlFormatter
|
@@ -167,7 +167,7 @@ def prettify_json(data: Any) -> Optional[str]:
|
|
167
167
|
)
|
168
168
|
return highlight(response, JsonLexer(), formatter)
|
169
169
|
|
170
|
-
response = json.dumps(data, sort_keys=True, indent=4)
|
170
|
+
response = json.dumps(data, sort_keys=True, indent=4, cls=encoder)
|
171
171
|
|
172
172
|
return mark_safe(
|
173
173
|
f'<div class="block dark:hidden">{format_response(response, "colorful")}</div>'
|
unfold/widgets.py
CHANGED
@@ -123,9 +123,10 @@ TEXTAREA_EXPANDABLE_CLASSES = [
|
|
123
123
|
|
124
124
|
SELECT_CLASSES = [
|
125
125
|
*BASE_INPUT_CLASSES,
|
126
|
-
"pr-8",
|
126
|
+
"pr-8!",
|
127
127
|
"max-w-2xl",
|
128
128
|
"appearance-none",
|
129
|
+
"truncate",
|
129
130
|
]
|
130
131
|
|
131
132
|
PROSE_CLASSES = [
|
@@ -287,7 +288,47 @@ FILE_CLASSES = [
|
|
287
288
|
]
|
288
289
|
|
289
290
|
|
290
|
-
class
|
291
|
+
class UnfoldPrefixSuffixMixin:
|
292
|
+
def get_context(self, name, value, attrs):
|
293
|
+
widget = {}
|
294
|
+
|
295
|
+
if "prefix" in self.attrs:
|
296
|
+
widget["prefix"] = self.attrs["prefix"]
|
297
|
+
del self.attrs["prefix"]
|
298
|
+
|
299
|
+
if "prefix_icon" in self.attrs:
|
300
|
+
widget["prefix_icon"] = self.attrs["prefix_icon"]
|
301
|
+
self.attrs["class"] = " ".join([self.attrs["class"], "pl-9"])
|
302
|
+
del self.attrs["prefix_icon"]
|
303
|
+
|
304
|
+
if "suffix" in self.attrs:
|
305
|
+
widget["suffix"] = self.attrs["suffix"]
|
306
|
+
del self.attrs["suffix"]
|
307
|
+
|
308
|
+
if "suffix_icon" in self.attrs:
|
309
|
+
widget["suffix_icon"] = self.attrs["suffix_icon"]
|
310
|
+
self.attrs["class"] = " ".join([self.attrs["class"], "pr-9"])
|
311
|
+
del self.attrs["suffix_icon"]
|
312
|
+
|
313
|
+
widget.update(
|
314
|
+
{
|
315
|
+
"name": name,
|
316
|
+
"is_hidden": self.is_hidden,
|
317
|
+
"required": self.is_required,
|
318
|
+
"value": self.format_value(value),
|
319
|
+
"attrs": self.build_attrs(self.attrs, attrs),
|
320
|
+
"template_name": self.template_name,
|
321
|
+
}
|
322
|
+
)
|
323
|
+
|
324
|
+
return {
|
325
|
+
"widget": widget,
|
326
|
+
}
|
327
|
+
|
328
|
+
|
329
|
+
class UnfoldAdminTextInputWidget(UnfoldPrefixSuffixMixin, AdminTextInputWidget):
|
330
|
+
template_name = "unfold/widgets/text.html"
|
331
|
+
|
291
332
|
def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
|
292
333
|
super().__init__(
|
293
334
|
attrs={
|
@@ -759,8 +800,13 @@ try:
|
|
759
800
|
template_name = "unfold/widgets/split_money.html"
|
760
801
|
|
761
802
|
def __init__(self, *args, **kwargs):
|
803
|
+
if "attrs" in kwargs:
|
804
|
+
attrs = kwargs.pop("attrs")
|
805
|
+
else:
|
806
|
+
attrs = {}
|
807
|
+
|
762
808
|
super().__init__(
|
763
|
-
amount_widget=UnfoldAdminTextInputWidget,
|
809
|
+
amount_widget=UnfoldAdminTextInputWidget(attrs=attrs),
|
764
810
|
currency_widget=UnfoldAdminSelectWidget(
|
765
811
|
choices=CURRENCY_CHOICES,
|
766
812
|
attrs={
|
File without changes
|
File without changes
|