django-unfold 0.45.0__py3-none-any.whl → 0.46.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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
  )