django-unfold 0.52.0__py3-none-any.whl → 0.53.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.52.0.dist-info → django_unfold-0.53.0.dist-info}/METADATA +1 -1
- {django_unfold-0.52.0.dist-info → django_unfold-0.53.0.dist-info}/RECORD +24 -21
- unfold/contrib/filters/admin/dropdown_filters.py +3 -3
- unfold/contrib/filters/admin/mixins.py +1 -1
- unfold/contrib/filters/admin/text_filters.py +2 -2
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
- unfold/decorators.py +10 -4
- unfold/forms.py +2 -1
- unfold/paginator.py +10 -0
- unfold/sites.py +8 -6
- unfold/static/unfold/css/styles.css +1 -1
- unfold/templates/admin/auth/user/add_form.html +1 -1
- unfold/templates/admin/change_form.html +9 -8
- unfold/templates/admin/pagination.html +4 -20
- unfold/templates/unfold/helpers/fieldset_row.html +19 -7
- unfold/templates/unfold/helpers/pagination_default.html +23 -0
- unfold/templates/unfold/helpers/pagination_infinite.html +11 -0
- unfold/templates/unfold/helpers/site_icon.html +14 -14
- unfold/templates/unfold/helpers/tab_action.html +1 -1
- unfold/templates/unfold/helpers/welcomemsg.html +2 -2
- unfold/templatetags/unfold.py +55 -24
- unfold/utils.py +11 -0
- {django_unfold-0.52.0.dist-info → django_unfold-0.53.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.52.0.dist-info → django_unfold-0.53.0.dist-info}/WHEEL +0 -0
@@ -6,7 +6,7 @@
|
|
6
6
|
{% if not is_popup %}
|
7
7
|
{% translate 'First, enter a username and password. Then, you’ll be able to edit more user options.' %}
|
8
8
|
{% else %}
|
9
|
-
{% translate "Enter a username and password." %}
|
9
|
+
{% translate "Enter a username and password." %}
|
10
10
|
{% endif %}
|
11
11
|
<p>
|
12
12
|
{% endblock %}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{% extends "admin/base_site.html" %}
|
2
2
|
|
3
|
-
{% load i18n admin_urls static admin_modify %}
|
3
|
+
{% load i18n admin_urls static admin_modify unfold %}
|
4
4
|
|
5
5
|
{% block extrahead %}{{ block.super }}
|
6
6
|
<script src="{% url 'admin:jsi18n' %}"></script>
|
@@ -65,7 +65,7 @@
|
|
65
65
|
{% include adminform.model_admin.change_form_outer_before_template %}
|
66
66
|
{% endif %}
|
67
67
|
|
68
|
-
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}" {% endif %}method="post" id="{{ opts.model_name }}_form" {% if adminform.model_admin.warn_unsaved_form %}class="warn-unsaved-form"{% endif %} novalidate>
|
68
|
+
<form {% if adminform.model_admin.conditional_fields %}x-data='{{ adminform|changeform_data }}'{% endif %} {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}" {% endif %}method="post" id="{{ opts.model_name }}_form" {% if adminform.model_admin.warn_unsaved_form %}class="warn-unsaved-form"{% endif %} novalidate>
|
69
69
|
{% csrf_token %}
|
70
70
|
|
71
71
|
{% if adminform.model_admin.change_form_before_template %}
|
@@ -87,12 +87,13 @@
|
|
87
87
|
{% include "unfold/helpers/messages/error.html" with errors=adminform.form.non_field_errors %}
|
88
88
|
|
89
89
|
{% block field_sets %}
|
90
|
-
{%
|
91
|
-
{%
|
92
|
-
{%
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
{% with has_conditional_display=adminform.model_admin.conditional_fields %}
|
91
|
+
{% for fieldset in adminform %}
|
92
|
+
{% if "tab" not in fieldset.classes %}
|
93
|
+
{% include 'admin/includes/fieldset.html' %}
|
94
|
+
{% endif %}
|
95
|
+
{% endfor %}
|
96
|
+
{% endwith %}
|
96
97
|
{% include "unfold/helpers/fieldsets_tabs.html" %}
|
97
98
|
{% endblock %}
|
98
99
|
|
@@ -4,28 +4,12 @@
|
|
4
4
|
<div class="{% if not is_popup %}max-w-full lg:bottom-0 lg:fixed lg:left-0 lg:right-0{% endif %}" {% if not is_popup %}x-bind:class="{'xl:left-0': !sidebarDesktopOpen, 'xl:left-72': sidebarDesktopOpen}"{% endif %} x-bind:style="'width: ' + mainWidth + 'px'">
|
5
5
|
<div class="lg:backdrop-blur-sm lg:bg-white/80 lg:flex lg:items-center lg:dark:bg-base-900/80 {% if not is_popup %}lg:border-t lg:border-base-200 lg:h-[71px] lg:py-2 lg:relative lg:scrollable-top lg:px-8 lg:dark:border-base-800{% endif %}">
|
6
6
|
<div class="flex flex-row items-center {% if not cl.model_admin.list_fullwidth %}lg:mx-auto{% endif %}" x-bind:style="'width: ' + changeListWidth + 'px'">
|
7
|
-
{% if
|
8
|
-
{%
|
9
|
-
|
10
|
-
|
11
|
-
</div>
|
12
|
-
{% endfor %}
|
7
|
+
{% if cl.paginator.template_name %}
|
8
|
+
{% include cl.paginator.template_name %}
|
9
|
+
{% else %}
|
10
|
+
{% include "unfold/helpers/pagination_default.html" %}
|
13
11
|
{% endif %}
|
14
12
|
|
15
|
-
<div class="py-4">
|
16
|
-
{% if pagination_required %}
|
17
|
-
-
|
18
|
-
{% endif %}
|
19
|
-
|
20
|
-
{{ cl.result_count }}
|
21
|
-
|
22
|
-
{% if cl.result_count == 1 %}
|
23
|
-
{{ cl.opts.verbose_name }}
|
24
|
-
{% else %}
|
25
|
-
{{ cl.opts.verbose_name_plural }}
|
26
|
-
{% endif %}
|
27
|
-
</div>
|
28
|
-
|
29
13
|
{% if show_all_url %}
|
30
14
|
<a href="{{ show_all_url }}" class="showall ml-4 text-primary-600 dark:text-primary-500">
|
31
15
|
{% translate 'Show all' %}
|
@@ -2,12 +2,24 @@
|
|
2
2
|
|
3
3
|
<div class="{% fieldset_row_classes %} {% if forloop.last %}last{% endif %}">
|
4
4
|
{% for field in line %}
|
5
|
-
|
6
|
-
{% if
|
7
|
-
{%
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
{% with adminform.model_admin.conditional_fields|index:field.field.name as conditional_display %}
|
6
|
+
<div class="{% fieldset_line_classes %}" {% if has_conditional_display and conditional_display %}x-show="{{ conditional_display }}"{% endif %}>
|
7
|
+
{% if has_conditional_display %}
|
8
|
+
{% with field|changeform_condition as field %}
|
9
|
+
{% if field.is_checkbox %}
|
10
|
+
{% include "unfold/helpers/fieldset_row_checkbox.html" %}
|
11
|
+
{% else %}
|
12
|
+
{% include "unfold/helpers/fieldset_row_field.html" %}
|
13
|
+
{% endif %}
|
14
|
+
{% endwith %}
|
15
|
+
{% else %}
|
16
|
+
{% if field.is_checkbox %}
|
17
|
+
{% include "unfold/helpers/fieldset_row_checkbox.html" %}
|
18
|
+
{% else %}
|
19
|
+
{% include "unfold/helpers/fieldset_row_field.html" %}
|
20
|
+
{% endif %}
|
21
|
+
{% endif %}
|
22
|
+
</div>
|
23
|
+
{% endwith %}
|
12
24
|
{% endfor %}
|
13
25
|
</div>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
{% load unfold_list %}
|
2
|
+
|
3
|
+
{% if pagination_required %}
|
4
|
+
{% for i in page_range %}
|
5
|
+
<div class="{% if forloop.last %}pr-2{% else %}pr-4{% endif %}">
|
6
|
+
{% paginator_number cl i %}
|
7
|
+
</div>
|
8
|
+
{% endfor %}
|
9
|
+
{% endif %}
|
10
|
+
|
11
|
+
<div class="py-4">
|
12
|
+
{% if pagination_required %}
|
13
|
+
-
|
14
|
+
{% endif %}
|
15
|
+
|
16
|
+
{{ cl.result_count }}
|
17
|
+
|
18
|
+
{% if cl.result_count == 1 %}
|
19
|
+
{{ cl.opts.verbose_name }}
|
20
|
+
{% else %}
|
21
|
+
{{ cl.opts.verbose_name_plural }}
|
22
|
+
{% endif %}
|
23
|
+
</div>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{% load unfold_list i18n %}
|
2
|
+
|
3
|
+
<div class="flex flex-row gap-4">
|
4
|
+
<a {% if cl.page_num != 1 %}href="?p={{ cl.page_num|add:-1 }}"{% endif %} class="{% if cl.page_num != 1 %}hover:text-primary-600 dark:hover:text-primary-500{% endif %}">
|
5
|
+
{% trans "Previous" %}
|
6
|
+
</a>
|
7
|
+
|
8
|
+
<a href="?p={{ cl.page_num|add:1 }}" class="hover:text-primary-600 dark:hover:text-primary-500">
|
9
|
+
{% trans "Next" %}
|
10
|
+
</a>
|
11
|
+
</div>
|
@@ -2,29 +2,29 @@
|
|
2
2
|
|
3
3
|
{% if site_icon %}
|
4
4
|
<div class="shrink-0">
|
5
|
-
<a href="{% url "admin:index" %}">
|
6
|
-
|
7
|
-
|
5
|
+
<{% if site_dropdown %}span{% else %}a href="{% url "admin:index" %}"{% endif %}>
|
6
|
+
{% if site_icon.light and site_icon.dark %}
|
7
|
+
<img src="{{ site_icon.dark }}" alt="{% trans 'Home' %}" class="h-8 hidden dark:block"/>
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
<img src="{{ site_icon.light }}" alt="{% trans 'Home' %}" class="block h-8 dark:hidden" />
|
10
|
+
{% else %}
|
11
|
+
<img src="{{ site_icon }}" class="h-8" alt="{% trans 'Home' %}" />
|
12
|
+
{% endif %}
|
13
|
+
</{% if site_dropdown %}span{% else %}a{% endif %}>
|
14
14
|
</div>
|
15
15
|
{% else %}
|
16
|
-
<a href="{% url "admin:index" %}" class="bg-primary-600 flex h-8 items-center justify-center rounded text-white text-xs w-8">
|
17
|
-
|
18
|
-
</a>
|
16
|
+
<{% if site_dropdown %}span{% else %}a href="{% url "admin:index" %}"{% endif %} class="bg-primary-600 flex h-8 items-center justify-center rounded shrink-0 text-white text-xs w-8">
|
17
|
+
<span class="material-symbols-outlined md-18">{% if site_symbol %}{{ site_symbol }}{% else %}settings{% endif %}</span>
|
18
|
+
</{% if site_dropdown %}span{% else %}a{% endif %}>
|
19
19
|
{% endif %}
|
20
20
|
|
21
|
-
<div class="flex flex-col grow min-w-0">
|
22
|
-
<div class="text-font-important-light leading-normal tracking-tight dark:text-font-important-dark *:leading-none {% if site_subheader %}xl:text-sm{% else %}xl:text-base{% endif %}">
|
21
|
+
<div class="flex flex-col gap-0.5 grow justify-center min-w-0">
|
22
|
+
<div class="text-font-important-light leading-normal tracking-tight dark:text-font-important-dark *:leading-none *:truncate {% if site_subheader %}xl:text-sm{% else %}xl:text-base{% endif %}">
|
23
23
|
{{ branding }}
|
24
24
|
</div>
|
25
25
|
|
26
26
|
{% if site_subheader %}
|
27
|
-
<div class="font-normal
|
27
|
+
<div class="font-normal text-font-subtle-light text-xs leading-none dark:text-font-subtle-dark">
|
28
28
|
{{ site_subheader }}
|
29
29
|
</div>
|
30
30
|
{% endif %}
|
@@ -25,7 +25,7 @@
|
|
25
25
|
<template x-teleport="body">
|
26
26
|
<nav x-anchor.bottom-end.offset.4="$refs.actionDropdown{{ action.method_name }}" class="absolute bg-white border flex flex-col -mr-px py-1 right-0 rounded shadow-lg top-10 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-transition x-show="actionDropdownOpen" x-on:click.outside="actionDropdownOpen = false">
|
27
27
|
{% for item in action.items %}
|
28
|
-
<a href="{{ item.path }}" class="flex items-center font-normal gap-2 max-h-[30px] mx-1 px-3 py-2 rounded text-left hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"{% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=
|
28
|
+
<a href="{{ item.path }}" class="flex items-center font-normal gap-2 max-h-[30px] mx-1 px-3 py-2 rounded text-left hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"{% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=item.attrs %}>
|
29
29
|
{% if item.icon %}
|
30
30
|
<span class="material-symbols-outlined">
|
31
31
|
{{ item.icon }}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
{% load i18n %}
|
1
|
+
{% load unfold i18n %}
|
2
2
|
|
3
3
|
<div class="flex flex-row flex-grow font-semibold items-center min-w-0 mr-3">
|
4
4
|
<span class="cursor-pointer flex flex-row items-center">
|
@@ -23,7 +23,7 @@
|
|
23
23
|
{{ content_title }}
|
24
24
|
</span>
|
25
25
|
|
26
|
-
{% if cl and cl.full_result_count != cl.result_count %}
|
26
|
+
{% if cl and cl.full_result_count != cl.result_count and cl.paginator|class_name != "InfinitePaginator" %}
|
27
27
|
<span class="font-medium ml-2 text-font-subtle-light text-sm dark:text-font-subtle-dark">
|
28
28
|
{% blocktranslate count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktranslate %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% if cl.show_full_result_count %}{% blocktranslate with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktranslate %}{% else %}{% translate "Show all" %}{% endif %}</a>)
|
29
29
|
</span>
|
unfold/templatetags/unfold.py
CHANGED
@@ -1,21 +1,24 @@
|
|
1
|
+
import json
|
1
2
|
from collections.abc import Mapping
|
2
3
|
from typing import Any, Optional, Union
|
3
4
|
|
4
5
|
from django import template
|
5
6
|
from django.contrib.admin.helpers import AdminForm, Fieldset
|
6
7
|
from django.contrib.admin.views.main import ChangeList
|
8
|
+
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
|
7
9
|
from django.db.models import Model
|
8
10
|
from django.db.models.options import Options
|
9
|
-
from django.forms import Field
|
11
|
+
from django.forms import BoundField, Field
|
10
12
|
from django.http import HttpRequest
|
11
13
|
from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
|
12
14
|
from django.template.base import NodeList, Parser, Token, token_kwargs
|
13
15
|
from django.template.loader import render_to_string
|
14
|
-
from django.utils.safestring import SafeText
|
16
|
+
from django.utils.safestring import SafeText, mark_safe
|
15
17
|
|
16
18
|
from unfold.components import ComponentRegistry
|
17
19
|
from unfold.dataclasses import UnfoldAction
|
18
20
|
from unfold.enums import ActionVariant
|
21
|
+
from unfold.widgets import UnfoldAdminSplitDateTimeWidget
|
19
22
|
|
20
23
|
register = Library()
|
21
24
|
|
@@ -107,7 +110,10 @@ def class_name(value: Any) -> str:
|
|
107
110
|
|
108
111
|
@register.filter
|
109
112
|
def index(indexable: Mapping[int, Any], i: int) -> Any:
|
110
|
-
|
113
|
+
try:
|
114
|
+
return indexable[i]
|
115
|
+
except (KeyError, TypeError):
|
116
|
+
return None
|
111
117
|
|
112
118
|
|
113
119
|
@register.filter
|
@@ -346,7 +352,6 @@ def fieldset_row_classes(context: Context) -> str:
|
|
346
352
|
]
|
347
353
|
|
348
354
|
formset = context.get("inline_admin_formset", None)
|
349
|
-
adminform = context.get("adminform", None)
|
350
355
|
line = context.get("line")
|
351
356
|
|
352
357
|
# Hide the field in case of ordering field for sorting
|
@@ -359,28 +364,11 @@ def fieldset_row_classes(context: Context) -> str:
|
|
359
364
|
):
|
360
365
|
classes.append("hidden")
|
361
366
|
|
362
|
-
# Compressed fields special styling
|
363
|
-
if (
|
364
|
-
adminform
|
365
|
-
and hasattr(adminform.model_admin, "compressed_fields")
|
366
|
-
and adminform.model_admin.compressed_fields
|
367
|
-
):
|
368
|
-
classes.extend(
|
369
|
-
[
|
370
|
-
"lg:border-b",
|
371
|
-
"lg:border-base-200",
|
372
|
-
"lg:border-dashed",
|
373
|
-
"dark:lg:border-base-800",
|
374
|
-
"last:lg:border-b-0",
|
375
|
-
]
|
376
|
-
)
|
377
|
-
|
378
367
|
if len(line.fields) > 1:
|
379
368
|
classes.extend(
|
380
369
|
[
|
381
|
-
"
|
382
|
-
"
|
383
|
-
"flex-wrap",
|
370
|
+
"grid",
|
371
|
+
f"lg:grid-cols-{len(line.fields)}",
|
384
372
|
]
|
385
373
|
)
|
386
374
|
|
@@ -421,7 +409,6 @@ def fieldset_line_classes(context: Context) -> str:
|
|
421
409
|
"border-base-200",
|
422
410
|
"border-dashed",
|
423
411
|
"group-[.last]/row:border-b-0",
|
424
|
-
"lg:border-b-0",
|
425
412
|
"lg:border-l",
|
426
413
|
"lg:flex-row",
|
427
414
|
"dark:border-base-800",
|
@@ -498,3 +485,47 @@ def action_item_classes(context: Context, action: UnfoldAction) -> str:
|
|
498
485
|
)
|
499
486
|
|
500
487
|
return " ".join(set(classes))
|
488
|
+
|
489
|
+
|
490
|
+
@register.filter
|
491
|
+
def changeform_data(adminform: AdminForm) -> str:
|
492
|
+
fields = []
|
493
|
+
|
494
|
+
for fieldset in adminform:
|
495
|
+
for line in fieldset:
|
496
|
+
for field in line:
|
497
|
+
if isinstance(field.field, dict):
|
498
|
+
continue
|
499
|
+
|
500
|
+
if isinstance(field.field.field.widget, UnfoldAdminSplitDateTimeWidget):
|
501
|
+
for index, _widget in enumerate(field.field.field.widget.widgets):
|
502
|
+
fields.append(
|
503
|
+
f"{field.field.name}{field.field.field.widget.widgets_names[index]}"
|
504
|
+
)
|
505
|
+
else:
|
506
|
+
fields.append(field.field.name)
|
507
|
+
|
508
|
+
return mark_safe(json.dumps(dict.fromkeys(fields, "")))
|
509
|
+
|
510
|
+
|
511
|
+
@register.filter(takes_context=True)
|
512
|
+
def changeform_condition(field: BoundField) -> BoundField:
|
513
|
+
if isinstance(field.field, dict):
|
514
|
+
return field
|
515
|
+
|
516
|
+
if isinstance(field.field.field.widget, RelatedFieldWidgetWrapper):
|
517
|
+
field.field.field.widget.widget.attrs["x-model.fill"] = field.field.name
|
518
|
+
field.field.field.widget.widget.attrs["x-init"] = mark_safe(
|
519
|
+
f"const $ = django.jQuery; $(function () {{ const select = $('#{field.field.auto_id}'); select.on('change', (ev) => {{ {field.field.name} = select.val(); }}); }});"
|
520
|
+
)
|
521
|
+
elif isinstance(field.field.field.widget, UnfoldAdminSplitDateTimeWidget):
|
522
|
+
for index, widget in enumerate(field.field.field.widget.widgets):
|
523
|
+
field_name = (
|
524
|
+
f"{field.field.name}{field.field.field.widget.widgets_names[index]}"
|
525
|
+
)
|
526
|
+
|
527
|
+
widget.attrs["x-model.fill"] = field_name
|
528
|
+
else:
|
529
|
+
field.field.field.widget.attrs["x-model.fill"] = field.field.name
|
530
|
+
|
531
|
+
return field
|
unfold/utils.py
CHANGED
@@ -15,6 +15,13 @@ from django.utils.safestring import SafeText, mark_safe
|
|
15
15
|
|
16
16
|
from .exceptions import UnfoldException
|
17
17
|
|
18
|
+
try:
|
19
|
+
from djmoney.models.fields import MoneyField
|
20
|
+
from djmoney.money import Money
|
21
|
+
except ImportError:
|
22
|
+
MoneyField = None
|
23
|
+
Money = None
|
24
|
+
|
18
25
|
|
19
26
|
def _boolean_icon(field_val: Any) -> str:
|
20
27
|
return render_to_string("unfold/helpers/boolean.html", {"value": field_val})
|
@@ -89,6 +96,8 @@ def display_for_value(
|
|
89
96
|
return formats.localize(timezone.template_localtime(value))
|
90
97
|
elif isinstance(value, (datetime.date, datetime.time)):
|
91
98
|
return formats.localize(value)
|
99
|
+
elif Money is not None and isinstance(value, Money):
|
100
|
+
return str(value)
|
92
101
|
elif isinstance(value, (int, decimal.Decimal, float)):
|
93
102
|
return formats.number_format(value)
|
94
103
|
elif isinstance(value, (list, tuple)):
|
@@ -114,6 +123,8 @@ def display_for_field(value: Any, field: Any, empty_value_display: str) -> str:
|
|
114
123
|
return formats.localize(timezone.template_localtime(value))
|
115
124
|
elif isinstance(field, (models.DateField, models.TimeField)):
|
116
125
|
return formats.localize(value)
|
126
|
+
elif MoneyField is not None and isinstance(field, MoneyField):
|
127
|
+
return str(value)
|
117
128
|
elif isinstance(field, models.DecimalField):
|
118
129
|
return formats.number_format(value, field.decimal_places)
|
119
130
|
elif isinstance(field, (models.IntegerField, models.FloatField)):
|
File without changes
|
File without changes
|