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.
@@ -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." %}x
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
- {% for fieldset in adminform %}
91
- {% if "tab" not in fieldset.classes %}
92
- {% include 'admin/includes/fieldset.html' %}
93
- {% endif %}
94
- {% endfor %}
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 pagination_required %}
8
- {% for i in page_range %}
9
- <div class="{% if forloop.last %}pr-2{% else %}pr-4{% endif %}">
10
- {% paginator_number cl i %}
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
- <div class="{% fieldset_line_classes %}">
6
- {% if field.is_checkbox %}
7
- {% include "unfold/helpers/fieldset_row_checkbox.html" %}
8
- {% else %}
9
- {% include "unfold/helpers/fieldset_row_field.html" %}
10
- {% endif %}
11
- </div>
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
- {% if site_icon.light and site_icon.dark %}
7
- <img src="{{ site_icon.dark }}" alt="{% trans 'Home' %}" class="h-8 hidden dark:block"/>
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
- <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
- </a>
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
- <span class="material-symbols-outlined md-18">{% if site_symbol %}{{ site_symbol }}{% else %}settings{% endif %}</span>
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 leading-normal text-font-subtle-light text-xs truncate dark:text-font-subtle-dark">
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=action.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>
@@ -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
- return indexable[i]
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
- "flex",
382
- "flex-row",
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)):