django-unfold 0.52.0__py3-none-any.whl → 0.54.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.
Files changed (42) hide show
  1. {django_unfold-0.52.0.dist-info → django_unfold-0.54.0.dist-info}/METADATA +1 -1
  2. {django_unfold-0.52.0.dist-info → django_unfold-0.54.0.dist-info}/RECORD +42 -38
  3. {django_unfold-0.52.0.dist-info → django_unfold-0.54.0.dist-info}/WHEEL +1 -1
  4. unfold/contrib/filters/admin/__init__.py +17 -0
  5. unfold/contrib/filters/admin/choice_filters.py +134 -0
  6. unfold/contrib/filters/admin/dropdown_filters.py +3 -3
  7. unfold/contrib/filters/admin/mixins.py +1 -1
  8. unfold/contrib/filters/admin/text_filters.py +2 -2
  9. unfold/contrib/filters/forms.py +41 -3
  10. unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
  11. unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
  12. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
  13. unfold/decorators.py +10 -4
  14. unfold/forms.py +2 -1
  15. unfold/paginator.py +10 -0
  16. unfold/sites.py +8 -6
  17. unfold/static/admin/js/inlines.js +23 -3
  18. unfold/static/unfold/css/styles.css +1 -1
  19. unfold/styles.css +2 -1
  20. unfold/templates/admin/auth/user/add_form.html +1 -1
  21. unfold/templates/admin/change_form.html +9 -8
  22. unfold/templates/admin/change_list.html +15 -13
  23. unfold/templates/admin/change_list_results.html +1 -1
  24. unfold/templates/admin/edit_inline/tabular.html +1 -2
  25. unfold/templates/admin/pagination.html +4 -20
  26. unfold/templates/unfold/components/chart/cohort.html +2 -2
  27. unfold/templates/unfold/helpers/change_list_filter.html +2 -2
  28. unfold/templates/unfold/helpers/change_list_filter_actions.html +28 -26
  29. unfold/templates/unfold/helpers/edit_inline/tabular_field.html +10 -8
  30. unfold/templates/unfold/helpers/field.html +4 -2
  31. unfold/templates/unfold/helpers/fieldset_row.html +19 -7
  32. unfold/templates/unfold/helpers/form_label.html +1 -1
  33. unfold/templates/unfold/helpers/pagination_default.html +23 -0
  34. unfold/templates/unfold/helpers/pagination_infinite.html +11 -0
  35. unfold/templates/unfold/helpers/site_icon.html +14 -14
  36. unfold/templates/unfold/helpers/tab_action.html +18 -3
  37. unfold/templates/unfold/helpers/welcomemsg.html +2 -2
  38. unfold/templates/unfold/widgets/radio_option.html +1 -1
  39. unfold/templatetags/unfold.py +66 -29
  40. unfold/utils.py +11 -0
  41. unfold/widgets.py +2 -0
  42. {django_unfold-0.52.0.dist-info → django_unfold-0.54.0.dist-info}/LICENSE.md +0 -0
@@ -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",
@@ -451,41 +438,46 @@ def action_item_classes(context: Context, action: UnfoldAction) -> str:
451
438
  if variant == ActionVariant.PRIMARY:
452
439
  classes.extend(
453
440
  [
454
- "border-primary-600",
441
+ "border-primary-700",
455
442
  "bg-primary-600",
456
443
  "text-white",
444
+ "dark:border-primary-500",
457
445
  ]
458
446
  )
459
447
  elif variant == ActionVariant.DANGER:
460
448
  classes.extend(
461
449
  [
462
- "border-red-600",
450
+ "border-red-700",
463
451
  "bg-red-600",
464
452
  "text-white",
453
+ "dark:border-red-500",
465
454
  ]
466
455
  )
467
456
  elif variant == ActionVariant.SUCCESS:
468
457
  classes.extend(
469
458
  [
470
- "border-green-600",
459
+ "border-green-700",
471
460
  "bg-green-600",
472
461
  "text-white",
462
+ "dark:border-green-500",
473
463
  ]
474
464
  )
475
465
  elif variant == ActionVariant.INFO:
476
466
  classes.extend(
477
467
  [
478
- "border-blue-600",
468
+ "border-blue-700",
479
469
  "bg-blue-600",
480
470
  "text-white",
471
+ "dark:border-blue-500",
481
472
  ]
482
473
  )
483
474
  elif variant == ActionVariant.WARNING:
484
475
  classes.extend(
485
476
  [
486
- "border-orange-600",
477
+ "border-orange-700",
487
478
  "bg-orange-600",
488
479
  "text-white",
480
+ "dark:border-orange-500",
489
481
  ]
490
482
  )
491
483
  else:
@@ -493,8 +485,53 @@ def action_item_classes(context: Context, action: UnfoldAction) -> str:
493
485
  [
494
486
  "border-base-200",
495
487
  "hover:text-primary-600",
488
+ "dark:hover:text-primary-500",
496
489
  "dark:border-base-700",
497
490
  ]
498
491
  )
499
492
 
500
493
  return " ".join(set(classes))
494
+
495
+
496
+ @register.filter
497
+ def changeform_data(adminform: AdminForm) -> str:
498
+ fields = []
499
+
500
+ for fieldset in adminform:
501
+ for line in fieldset:
502
+ for field in line:
503
+ if isinstance(field.field, dict):
504
+ continue
505
+
506
+ if isinstance(field.field.field.widget, UnfoldAdminSplitDateTimeWidget):
507
+ for index, _widget in enumerate(field.field.field.widget.widgets):
508
+ fields.append(
509
+ f"{field.field.name}{field.field.field.widget.widgets_names[index]}"
510
+ )
511
+ else:
512
+ fields.append(field.field.name)
513
+
514
+ return mark_safe(json.dumps(dict.fromkeys(fields, "")))
515
+
516
+
517
+ @register.filter(takes_context=True)
518
+ def changeform_condition(field: BoundField) -> BoundField:
519
+ if isinstance(field.field, dict):
520
+ return field
521
+
522
+ if isinstance(field.field.field.widget, RelatedFieldWidgetWrapper):
523
+ field.field.field.widget.widget.attrs["x-model.fill"] = field.field.name
524
+ field.field.field.widget.widget.attrs["x-init"] = mark_safe(
525
+ f"const $ = django.jQuery; $(function () {{ const select = $('#{field.field.auto_id}'); select.on('change', (ev) => {{ {field.field.name} = select.val(); }}); }});"
526
+ )
527
+ elif isinstance(field.field.field.widget, UnfoldAdminSplitDateTimeWidget):
528
+ for index, widget in enumerate(field.field.field.widget.widgets):
529
+ field_name = (
530
+ f"{field.field.name}{field.field.field.widget.widgets_names[index]}"
531
+ )
532
+
533
+ widget.attrs["x-model.fill"] = field_name
534
+ else:
535
+ field.field.field.widget.attrs["x-model.fill"] = field.field.name
536
+
537
+ 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)):
unfold/widgets.py CHANGED
@@ -146,6 +146,7 @@ CHECKBOX_CLASSES = [
146
146
  "border-base-300",
147
147
  "cursor-pointer",
148
148
  "h-4",
149
+ "min-w-4",
149
150
  "relative",
150
151
  "rounded-[4px]",
151
152
  "shadow-sm",
@@ -189,6 +190,7 @@ RADIO_CLASSES = [
189
190
  "border-base-300",
190
191
  "cursor-pointer",
191
192
  "h-4",
193
+ "min-w-4",
192
194
  "relative",
193
195
  "rounded-full",
194
196
  "w-4",