django-unfold 0.45.0__py3-none-any.whl → 0.46.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.
unfold/styles.css CHANGED
@@ -637,7 +637,7 @@ trix-toolbar[id^="trix-toolbar-"] {
637
637
  }
638
638
 
639
639
  .ui-tabs.ui-widget .ui-tabs-nav {
640
- @apply bg-base-100 border-0 flex gap-2 m-0 mb-4 mr-auto p-1 rounded text-sm dark:bg-white/[.04] after:hidden;
640
+ @apply bg-base-100 border-0 flex gap-2 m-0 ml-3 mb-0 mt-3 mr-auto p-1 rounded text-sm dark:bg-white/[.04] after:hidden;
641
641
  }
642
642
 
643
643
  .ui-tabs.ui-widget .ui-tabs-nav li {
@@ -34,20 +34,22 @@
34
34
 
35
35
  {% include "unfold/helpers/messages.html" %}
36
36
 
37
- <div class="flex flex-col gap-4 mb-8 *:mb-0">
38
- {% include "unfold/helpers/messages/errornote.html" with errors=form.errors %}
37
+ {% if form.errors or form.non_field_errors %}
38
+ <div class="flex flex-col gap-4 mb-8 *:mb-0">
39
+ {% include "unfold/helpers/messages/errornote.html" with errors=form.errors %}
39
40
 
40
- {% include "unfold/helpers/messages/error.html" with errors=form.non_field_errors %}
41
+ {% include "unfold/helpers/messages/error.html" with errors=form.non_field_errors %}
41
42
 
42
- {% if user.is_authenticated %}
43
- {% blocktranslate trimmed asvar message %}
44
- You are authenticated as {{ username }}, but are not authorized to
45
- access this page. Would you like to login to a different account?
46
- {% endblocktranslate %}
43
+ {% if user.is_authenticated %}
44
+ {% blocktranslate trimmed asvar message %}
45
+ You are authenticated as {{ username }}, but are not authorized to
46
+ access this page. Would you like to login to a different account?
47
+ {% endblocktranslate %}
47
48
 
48
- {% include "unfold/helpers/messages/error.html" with error=message %}
49
- {% endif %}
50
- </div>
49
+ {% include "unfold/helpers/messages/error.html" with error=message %}
50
+ {% endif %}
51
+ </div>
52
+ {% endif %}
51
53
 
52
54
  {% block login_before %}{% endblock %}
53
55
 
@@ -1,7 +1,7 @@
1
1
  {% load i18n admin_urls %}
2
2
 
3
- <div {% if not is_popup %}id="submit-row"{% endif %} class="relative lg:mt-16 z-20">
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'">
3
+ <div {% if not is_popup %}id="submit-row"{% endif %} class="relative {% if not is_popup %}lg:mt-16{% endif %} z-20">
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}" x-bind:style="'width: ' + mainWidth + 'px'"{% endif %}>
5
5
  <div class="backdrop-blur-sm bg-white/80 pb-4 dark:bg-base-900/80 {% if not is_popup %}lg:border-t lg:border-base-200 lg:py-4 relative lg:scrollable-top lg:px-8 dark:border-base-800{% endif %}">
6
6
  <div class="flex flex-col-reverse gap-3 items-center mx-auto lg:flex-row-reverse">
7
7
  {% block submit-row %}
@@ -27,14 +27,6 @@
27
27
  </button>
28
28
  {% endif %}
29
29
 
30
- {% if show_close %}
31
- {% url opts|admin_urlname:'changelist' as changelist_url %}
32
-
33
- <a href="{% add_preserved_filters changelist_url %}" class="border border-base-200 font-medium px-3 py-2 rounded transition-all w-full hover:bg-base-50 lg:block lg:w-auto dark:border-base-700 dark:hover:text-base-200 dark:hover:bg-base-900">
34
- {% translate 'Close' %}
35
- </a>
36
- {% endif %}
37
-
38
30
  {% if show_save_and_add_another %}
39
31
  <button type="submit" name="_addanother" class="border border-base-200 font-medium px-3 py-2 rounded transition-all w-full hover:bg-base-50 lg:block lg:w-auto dark:border-base-700 dark:hover:text-base-200 dark:hover:bg-base-900">
40
32
  {% translate 'Save and add another' %}
@@ -47,15 +39,24 @@
47
39
  </button>
48
40
  {% endif %}
49
41
 
50
- {% if show_delete_link and original %}
51
- {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
42
+ <div class="flex flex-col gap-3 mr-auto w-full lg:flex-row lg:w-auto">
43
+ {% if show_close or adminform.model_admin.change_form_show_cancel_button %}
44
+ {% url opts|admin_urlname:'changelist' as changelist_url %}
45
+
46
+ <a href="{% add_preserved_filters changelist_url %}" class="border border-base-200 font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:block lg:w-auto dark:border-base-700 dark:hover:text-base-200 dark:hover:bg-base-900">
47
+ {% translate 'Close' %}
48
+ </a>
49
+ {% endif %}
50
+
51
+ {% if show_delete_link and original %}
52
+ {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
52
53
 
53
- <p class="deletelink-box mr-auto w-full lg:w-auto">
54
- <a href="{% add_preserved_filters delete_url %}" class="bg-red-600 flex items-center justify-center font-medium h-9.5 ml-auto px-3 py-2 rounded text-white dark:bg-red-500/20 dark:text-red-500">
54
+
55
+ <a href="{% add_preserved_filters delete_url %}" class="bg-red-600 flex items-center justify-center font-medium h-9.5 ml-auto px-3 py-2 rounded text-center text-white w-full lg:w-auto dark:bg-red-500/20 dark:text-red-500">
55
56
  {% translate "Delete" %} {{ opts.verbose_name }}
56
57
  </a>
57
- </p>
58
- {% endif %}
58
+ {% endif %}
59
+ </div>
59
60
  {% endblock %}
60
61
  </div>
61
62
  </div>
@@ -1,31 +1,25 @@
1
1
  {% load i18n admin_list unfold %}
2
2
 
3
3
  <div id="changelist-filter" class="backdrop-blur-sm bg-opacity-80 bg-base-900 flex inset-0 z-50 fixed {% if not cl.model_admin.list_filter_sheet %}2xl:pb-24 2xl:bg-transparent 2xl:relative 2xl:!block 2xl:z-10{% endif %}" hx-preserve x-show="filterOpen">
4
- <label for="show-filters" id="changelist-filter-close" class="flex-grow {% if not cl.model_admin.list_filter_sheet %}2xl:hidden{% endif %}" x-on:click="filterOpen = false"></label>
4
+ <div id="changelist-filter-close" class="flex-grow {% if not cl.model_admin.list_filter_sheet %}2xl:hidden{% endif %}" x-on:click="filterOpen = false"></div>
5
5
 
6
6
  <div class="bg-white flex m-4 overflow-hidden rounded shadow-sm w-80 dark:bg-base-800 {% if not cl.model_admin.list_filter_sheet %} 2xl:overflow-visible 2xl:border-0 2xl:shadow-none 2xl:sticky 2xl:top-4 2xl:dark:border-base-800 2xl:bg-transparent 2xl:dark:!bg-transparent 2xl:m-0{% endif %}">
7
7
  <div class="flex-grow h-full overflow-auto relative {% if not cl.model_admin.list_filter_sheet %} 2xl:overflow-visible{% endif %}">
8
- <div class="px-3 py-2.5 {% if not cl.model_admin.list_filter_sheet %}2xl:px-0{% endif %}">
9
- {% if cl.model_admin.list_filter_submit %}
10
- <form id="filter-form" method="get">
11
- {% preserve_filters %}
12
- {% endif %}
8
+ <{% if cl.model_admin.list_filter_submit %}form id="filter-form" method="get"{% else %}div{% endif %} class="flex flex-col h-full {% if not cl.model_admin.list_filter_sheet %}2xl:px-0{% endif %}">
9
+ {% if cl.model_admin.list_filter_submit %}
10
+ {% preserve_filters %}
11
+ {% endif %}
13
12
 
14
- <div class="flex flex-col gap-4 *:mb-0">
13
+ <div class="flex flex-col grow gap-4 overflow-auto *:mb-0" {% if cl.model_admin.list_filter_sheet %}data-simplebar data-simplebar-direction='rtl'{% endif %}>
14
+ <div class="flex flex-col gap-4 {% if cl.model_admin.list_filter_sheet %}px-3 py-2.5{% endif %} *:mb-0">
15
15
  {% for spec in cl.filter_specs %}
16
16
  {% admin_list_filter cl spec %}
17
17
  {% endfor %}
18
18
  </div>
19
-
20
- {% if cl.model_admin.list_filter_submit %}
21
- <button type="submit" class="bg-primary-600 block border border-transparent font-medium mt-6 px-3 py-2 rounded text-white w-full">
22
- {% trans "Apply Filters" %}
23
- </button>
24
- </form>
25
- {% endif %}
26
-
27
- {% include "unfold/helpers/change_list_filter_actions.html" %}
28
19
  </div>
29
- </div>
20
+
21
+ {% include "unfold/helpers/change_list_filter_actions.html" %}
22
+ </{% if cl.model_admin.list_filter_submit %}form{% else %}div{% endif %}>
30
23
  </div>
31
24
  </div>
25
+ </div>
@@ -1,23 +1,31 @@
1
1
  {% load i18n %}
2
2
 
3
- {% if cl.is_facets_optional or cl.has_active_filters %}
4
- <span id="changelist-filter-extra-actions" class="flex flex-row gap-2 items-center mt-2">
5
- {% if cl.is_facets_optional %}
6
- {% if cl.add_facets %}
7
- <a href="{{ cl.remove_facet_link }}" class="hidelink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:w-auto dark:border-base-700 dark:hover:text-base-200">
8
- {% trans "Hide counts" %}
9
- </a>
10
- {% else %}
11
- <a href="{{ cl.add_facet_link }}" class="viewlink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full lg:w-auto dark:border-base-700 dark:hover:text-base-200">
12
- {% trans "Show counts" %}
13
- </a>
3
+ <div class="{% if cl.model_admin.list_filter_sheet %}bg-white border-t border-base-200 p-3 py-2.5 dark:bg-base-800 dark:border-base-700{% else %}mt-6{% endif %}">
4
+ {% if cl.model_admin.list_filter_submit %}
5
+ <button type="submit" class="bg-primary-600 block border border-transparent font-medium px-3 py-2 rounded text-white w-full">
6
+ {% trans "Apply Filters" %}
7
+ </button>
8
+ {% endif %}
9
+
10
+ {% if cl.is_facets_optional or cl.has_active_filters %}
11
+ <span id="changelist-filter-extra-actions" class="flex flex-row gap-2 items-center mt-2">
12
+ {% if cl.is_facets_optional %}
13
+ {% if cl.add_facets %}
14
+ <a href="{{ cl.remove_facet_link }}" class="hidelink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:w-auto dark:border-base-700 dark:hover:text-base-200">
15
+ {% trans "Hide counts" %}
16
+ </a>
17
+ {% else %}
18
+ <a href="{{ cl.add_facet_link }}" class="viewlink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full lg:w-auto dark:border-base-700 dark:hover:text-base-200">
19
+ {% trans "Show counts" %}
20
+ </a>
21
+ {% endif %}
14
22
  {% endif %}
15
- {% endif %}
16
23
 
17
- {% if cl.has_active_filters %}
18
- <a href="{{ cl.clear_all_filters_qs }}" class="border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:w-auto dark:border-base-700 dark:hover:text-base-200">
19
- {% trans "Clear all filters" %}
20
- </a>
21
- {% endif %}
22
- </span>
23
- {% endif %}
24
+ {% if cl.has_active_filters %}
25
+ <a href="{{ cl.clear_all_filters_qs }}" class="border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:w-auto dark:border-base-700 dark:hover:text-base-200">
26
+ {% trans "Clear all filters" %}
27
+ </a>
28
+ {% endif %}
29
+ </span>
30
+ {% endif %}
31
+ </div>
@@ -1 +1 @@
1
- <div class="readonly break-all {% if field.is_json %}max-w-4xl{% else %}max-w-2xl{% endif %} py-2 text-sm *:rounded {% if not adminform.model_admin.compressed_fields and not field.is_image %}bg-base-50 border font-medium px-3 rounded shadow-sm dark:border-base-700 dark:bg-base-800{% endif %} {% if field.is_image %}inline-block [&_img]:rounded !py-0{% endif %}">{% if value %}{{ value }}{% elif field.contents %}{{ field.contents }}{% else %}-{% endif %}</div>
1
+ <div class="readonly break-words {% if field.is_json %}max-w-4xl{% else %}max-w-2xl{% endif %} py-2 text-sm *:rounded {% if not adminform.model_admin.compressed_fields and not field.is_image %}bg-base-50 border font-medium px-3 rounded shadow-sm dark:border-base-700 dark:bg-base-800{% endif %} {% if field.is_image %}inline-block [&_img]:rounded !py-0{% endif %}">{% if value %}{{ value }}{% elif field.contents %}{{ field.contents }}{% else %}-{% endif %}</div>
@@ -0,0 +1,13 @@
1
+ {% load admin_urls i18n %}
2
+
3
+ {% if show_back_button %}
4
+ {% if opts and adminform %}
5
+ {% url opts|admin_urlname:'changelist' as link %}
6
+
7
+ <a href="{{ link }}" class="block h-4.5 mr-3" title="{% trans "Go back" %}">
8
+ <span class="material-symbols-outlined">
9
+ arrow_back
10
+ </span>
11
+ </a>
12
+ {% endif %}
13
+ {% endif %}
@@ -16,6 +16,6 @@
16
16
  </a>
17
17
  {% endif %}
18
18
 
19
- <span class="text-font-important-light tracking-tight dark:text-font-important-dark xl:text-base">
19
+ <div class="text-font-important-light tracking-tight dark:text-font-important-dark xl:text-base">
20
20
  {{ branding }}
21
- </span>
21
+ </div>
@@ -13,6 +13,8 @@
13
13
 
14
14
  <span class="block bg-base-200 h-5 mx-3 w-px dark:bg-base-700"></span>
15
15
 
16
+ {% include "unfold/helpers/header_back_button.html" %}
17
+
16
18
  <h1 class="overflow-hidden text-ellipsis text-sm whitespace-nowrap xl:text-base text-font-important-light dark:text-font-important-dark">
17
19
  {% if content_title %}
18
20
  <span class="tracking-tight">
@@ -296,6 +296,7 @@ def fieldset_rows_classes(context: Context) -> str:
296
296
  @register.simple_tag(takes_context=True)
297
297
  def fieldset_row_classes(context: Context) -> str:
298
298
  classes = [
299
+ "form-row",
299
300
  "field-row",
300
301
  "group/row",
301
302
  ]
@@ -315,7 +316,11 @@ def fieldset_row_classes(context: Context) -> str:
315
316
  classes.append("hidden")
316
317
 
317
318
  # Compressed fields special styling
318
- if adminform and adminform.model_admin.compressed_fields:
319
+ if (
320
+ adminform
321
+ and hasattr(adminform.model_admin, "compressed_fields")
322
+ and adminform.model_admin.compressed_fields
323
+ ):
319
324
  classes.extend(
320
325
  [
321
326
  "lg:border-b",
@@ -359,7 +364,11 @@ def fieldset_line_classes(context: Context) -> str:
359
364
  if hasattr(field, "errors") and field.errors():
360
365
  classes.append("errors")
361
366
 
362
- if adminform.model_admin.compressed_fields:
367
+ if (
368
+ adminform
369
+ and hasattr(adminform.model_admin, "compressed_fields")
370
+ and adminform.model_admin.compressed_fields
371
+ ):
363
372
  classes.extend(
364
373
  [
365
374
  "border-b",
unfold/widgets.py CHANGED
@@ -256,26 +256,53 @@ SWITCH_CLASSES = [
256
256
 
257
257
  class UnfoldAdminTextInputWidget(AdminTextInputWidget):
258
258
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
259
- super().__init__(attrs={"class": " ".join(INPUT_CLASSES), **(attrs or {})})
259
+ super().__init__(
260
+ attrs={
261
+ **(attrs or {}),
262
+ "class": " ".join(
263
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
264
+ ),
265
+ }
266
+ )
260
267
 
261
268
 
262
269
  class UnfoldAdminURLInputWidget(AdminURLFieldWidget):
263
270
  template_name = "unfold/widgets/url.html"
264
271
 
265
272
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
266
- super().__init__(attrs={"class": " ".join(INPUT_CLASSES), **(attrs or {})})
273
+ super().__init__(
274
+ attrs={
275
+ **(attrs or {}),
276
+ "class": " ".join(
277
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
278
+ ),
279
+ }
280
+ )
267
281
 
268
282
 
269
283
  class UnfoldAdminColorInputWidget(AdminTextInputWidget):
270
284
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
271
285
  super().__init__(
272
- attrs={"type": "color", "class": " ".join(COLOR_CLASSES), **(attrs or {})}
286
+ attrs={
287
+ **(attrs or {}),
288
+ "type": "color",
289
+ "class": " ".join(
290
+ [*COLOR_CLASSES, attrs.get("class", "") if attrs else ""]
291
+ ),
292
+ }
273
293
  )
274
294
 
275
295
 
276
296
  class UnfoldAdminUUIDInputWidget(AdminUUIDInputWidget):
277
297
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
278
- super().__init__(attrs={"class": " ".join(INPUT_CLASSES), **(attrs or {})})
298
+ super().__init__(
299
+ attrs={
300
+ **(attrs or {}),
301
+ "class": " ".join(
302
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
303
+ ),
304
+ }
305
+ )
279
306
 
280
307
 
281
308
  class UnfoldAdminIntegerRangeWidget(MultiWidget):
@@ -285,7 +312,9 @@ class UnfoldAdminIntegerRangeWidget(MultiWidget):
285
312
  if attrs is None:
286
313
  attrs = {}
287
314
 
288
- attrs["class"] = " ".join(INPUT_CLASSES)
315
+ attrs["class"] = " ".join(
316
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
317
+ )
289
318
 
290
319
  _widgets = (NumberInput(attrs=attrs), NumberInput(attrs=attrs))
291
320
 
@@ -299,7 +328,14 @@ class UnfoldAdminIntegerRangeWidget(MultiWidget):
299
328
 
300
329
  class UnfoldAdminEmailInputWidget(AdminEmailInputWidget):
301
330
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
302
- super().__init__(attrs={"class": " ".join(INPUT_CLASSES), **(attrs or {})})
331
+ super().__init__(
332
+ attrs={
333
+ **(attrs or {}),
334
+ "class": " ".join(
335
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
336
+ ),
337
+ }
338
+ )
303
339
 
304
340
 
305
341
  class FileFieldMixin:
@@ -341,9 +377,15 @@ class UnfoldAdminDateWidget(AdminDateWidget):
341
377
  self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
342
378
  ) -> None:
343
379
  attrs = {
344
- "class": "vDateField " + " ".join(DATETIME_CLASSES),
345
- "size": "10",
346
380
  **(attrs or {}),
381
+ "class": " ".join(
382
+ [
383
+ "vDateField",
384
+ *DATETIME_CLASSES,
385
+ attrs.get("class", "") if attrs else "",
386
+ ]
387
+ ),
388
+ "size": "10",
347
389
  }
348
390
  super().__init__(attrs=attrs, format=format)
349
391
 
@@ -355,9 +397,15 @@ class UnfoldAdminSingleDateWidget(AdminDateWidget):
355
397
  self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
356
398
  ) -> None:
357
399
  attrs = {
358
- "class": "vDateField " + " ".join(DATETIME_CLASSES),
359
- "size": "10",
360
400
  **(attrs or {}),
401
+ "class": " ".join(
402
+ [
403
+ "vDateField",
404
+ *DATETIME_CLASSES,
405
+ attrs.get("class", "") if attrs else "",
406
+ ]
407
+ ),
408
+ "size": "10",
361
409
  }
362
410
  super().__init__(attrs=attrs, format=format)
363
411
 
@@ -369,9 +417,15 @@ class UnfoldAdminTimeWidget(AdminTimeWidget):
369
417
  self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
370
418
  ) -> None:
371
419
  attrs = {
372
- "class": "vTimeField " + " ".join(DATETIME_CLASSES),
373
- "size": "8",
374
420
  **(attrs or {}),
421
+ "class": " ".join(
422
+ [
423
+ "vTimeField",
424
+ *DATETIME_CLASSES,
425
+ attrs.get("class", "") if attrs else "",
426
+ ]
427
+ ),
428
+ "size": "8",
375
429
  }
376
430
  super().__init__(attrs=attrs, format=format)
377
431
 
@@ -383,9 +437,15 @@ class UnfoldAdminSingleTimeWidget(AdminTimeWidget):
383
437
  self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
384
438
  ) -> None:
385
439
  attrs = {
386
- "class": "vTimeField " + " ".join(DATETIME_CLASSES),
387
- "size": "8",
388
440
  **(attrs or {}),
441
+ "class": " ".join(
442
+ [
443
+ "vTimeField",
444
+ *DATETIME_CLASSES,
445
+ attrs.get("class", "") if attrs else "",
446
+ ]
447
+ ),
448
+ "size": "8",
389
449
  }
390
450
  super().__init__(attrs=attrs, format=format)
391
451
 
@@ -398,8 +458,14 @@ class UnfoldAdminTextareaWidget(AdminTextareaWidget):
398
458
 
399
459
  super().__init__(
400
460
  attrs={
401
- "class": "vLargeTextField " + " ".join(TEXTAREA_CLASSES),
402
461
  **(attrs or {}),
462
+ "class": " ".join(
463
+ [
464
+ "vLargeTextField",
465
+ *TEXTAREA_CLASSES,
466
+ attrs.get("class", "") if attrs else "",
467
+ ]
468
+ ),
403
469
  }
404
470
  )
405
471
 
@@ -414,11 +480,15 @@ class UnfoldAdminExpandableTextareaWidget(AdminTextareaWidget):
414
480
 
415
481
  super().__init__(
416
482
  attrs={
417
- "class": "vLargeTextField "
418
- + " ".join(TEXTAREA_CLASSES)
419
- + " "
420
- + " ".join(TEXTAREA_EXPANDABLE_CLASSES),
421
483
  **(attrs or {}),
484
+ "class": " ".join(
485
+ [
486
+ "vLargeTextField",
487
+ *TEXTAREA_CLASSES,
488
+ *TEXTAREA_EXPANDABLE_CLASSES,
489
+ attrs.get("class", "") if attrs else "",
490
+ ]
491
+ ),
422
492
  }
423
493
  )
424
494
 
@@ -471,17 +541,38 @@ class UnfoldAdminSplitDateTimeVerticalWidget(AdminSplitDateTime):
471
541
 
472
542
  class UnfoldAdminIntegerFieldWidget(AdminIntegerFieldWidget):
473
543
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
474
- super().__init__(attrs={"class": " ".join(INPUT_CLASSES), **(attrs or {})})
544
+ super().__init__(
545
+ attrs={
546
+ **(attrs or {}),
547
+ "class": " ".join(
548
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
549
+ ),
550
+ }
551
+ )
475
552
 
476
553
 
477
554
  class UnfoldAdminDecimalFieldWidget(AdminIntegerFieldWidget):
478
555
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
479
- super().__init__(attrs={"class": " ".join(INPUT_CLASSES), **(attrs or {})})
556
+ super().__init__(
557
+ attrs={
558
+ **(attrs or {}),
559
+ "class": " ".join(
560
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
561
+ ),
562
+ }
563
+ )
480
564
 
481
565
 
482
566
  class UnfoldAdminBigIntegerFieldWidget(AdminBigIntegerFieldWidget):
483
567
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
484
- super().__init__(attrs={"class": " ".join(INPUT_CLASSES), **(attrs or {})})
568
+ super().__init__(
569
+ attrs={
570
+ **(attrs or {}),
571
+ "class": " ".join(
572
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
573
+ ),
574
+ }
575
+ )
485
576
 
486
577
 
487
578
  class UnfoldAdminNullBooleanSelectWidget(NullBooleanSelect):
@@ -489,7 +580,9 @@ class UnfoldAdminNullBooleanSelectWidget(NullBooleanSelect):
489
580
  if attrs is None:
490
581
  attrs = {}
491
582
 
492
- attrs["class"] = " ".join(SELECT_CLASSES)
583
+ attrs["class"] = " ".join(
584
+ [*SELECT_CLASSES, attrs.get("class", "") if attrs else ""]
585
+ )
493
586
  super().__init__(attrs)
494
587
 
495
588
 
@@ -498,7 +591,9 @@ class UnfoldAdminSelectWidget(Select):
498
591
  if attrs is None:
499
592
  attrs = {}
500
593
 
501
- attrs["class"] = " ".join([*SELECT_CLASSES, attrs.get("class", "")])
594
+ attrs["class"] = " ".join(
595
+ [*SELECT_CLASSES, attrs.get("class", "") if attrs else ""]
596
+ )
502
597
  super().__init__(attrs, choices)
503
598
 
504
599
 
@@ -507,7 +602,9 @@ class UnfoldAdminSelectMultipleWidget(SelectMultiple):
507
602
  if attrs is None:
508
603
  attrs = {}
509
604
 
510
- attrs["class"] = " ".join([*SELECT_CLASSES, attrs.get("class", "")])
605
+ attrs["class"] = " ".join(
606
+ [*SELECT_CLASSES, attrs.get("class", "") if attrs else ""]
607
+ )
511
608
  super().__init__(attrs, choices)
512
609
 
513
610
 
@@ -537,7 +634,9 @@ class UnfoldAdminCheckboxSelectMultiple(CheckboxSelectMultiple):
537
634
  def __init__(self, *args, **kwargs):
538
635
  super().__init__(*args, **kwargs)
539
636
 
540
- self.attrs = {"class": " ".join(CHECKBOX_CLASSES)}
637
+ self.attrs = {
638
+ "class": " ".join([*CHECKBOX_CLASSES, self.attrs.get("class", "")])
639
+ }
541
640
 
542
641
 
543
642
  try:
@@ -575,7 +674,9 @@ class UnfoldBooleanWidget(CheckboxInput):
575
674
  super().__init__(
576
675
  {
577
676
  **(attrs or {}),
578
- "class": " ".join(CHECKBOX_CLASSES + [attrs.get("class", "")]),
677
+ "class": " ".join(
678
+ [*CHECKBOX_CLASSES, attrs.get("class", "") if attrs else ""]
679
+ ),
579
680
  },
580
681
  check_test,
581
682
  )
@@ -586,7 +687,13 @@ class UnfoldBooleanSwitchWidget(CheckboxInput):
586
687
  self, attrs: Optional[Dict[str, Any]] = None, check_test: Callable = None
587
688
  ) -> None:
588
689
  super().__init__(
589
- attrs={"class": " ".join(SWITCH_CLASSES), **(attrs or {})}, check_test=None
690
+ attrs={
691
+ **(attrs or {}),
692
+ "class": " ".join(
693
+ [*SWITCH_CLASSES, attrs.get("class", "") if attrs else ""]
694
+ ),
695
+ },
696
+ check_test=None,
590
697
  )
591
698
 
592
699
 
@@ -605,8 +712,14 @@ class UnfoldForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
605
712
  using: Optional[Any] = None,
606
713
  ) -> None:
607
714
  attrs = {
608
- "class": " ".join(["vForeignKeyRawIdAdminField"] + INPUT_CLASSES),
609
715
  **(attrs or {}),
716
+ "class": " ".join(
717
+ [
718
+ "vForeignKeyRawIdAdminField",
719
+ *INPUT_CLASSES,
720
+ attrs.get("class", "") if attrs else "",
721
+ ]
722
+ ),
610
723
  }
611
724
  super().__init__(rel, admin_site, attrs, using)
612
725
 
@@ -614,5 +727,11 @@ class UnfoldForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
614
727
  class UnfoldAdminPasswordInput(PasswordInput):
615
728
  def __init__(self, attrs=None, render_value=False):
616
729
  super().__init__(
617
- {"class": " ".join(INPUT_CLASSES), **(attrs or {})}, render_value
730
+ {
731
+ **(attrs or {}),
732
+ "class": " ".join(
733
+ [*INPUT_CLASSES, attrs.get("class", "") if attrs else ""]
734
+ ),
735
+ },
736
+ render_value,
618
737
  )