django-unfold 0.25.0__py3-none-any.whl → 0.27.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 (45) hide show
  1. {django_unfold-0.25.0.dist-info → django_unfold-0.27.0.dist-info}/METADATA +61 -7
  2. {django_unfold-0.25.0.dist-info → django_unfold-0.27.0.dist-info}/RECORD +44 -31
  3. unfold/admin.py +13 -157
  4. unfold/contrib/forms/templates/unfold/forms/array.html +31 -0
  5. unfold/contrib/forms/widgets.py +58 -3
  6. unfold/contrib/import_export/forms.py +17 -0
  7. unfold/contrib/import_export/templates/admin/import_export/change_form.html +10 -0
  8. unfold/contrib/import_export/templates/admin/import_export/export.html +38 -3
  9. unfold/contrib/import_export/templates/admin/import_export/import_form.html +8 -12
  10. unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +1 -1
  11. unfold/contrib/inlines/__init__.py +0 -0
  12. unfold/contrib/inlines/admin.py +141 -0
  13. unfold/contrib/inlines/apps.py +6 -0
  14. unfold/contrib/inlines/checks.py +18 -0
  15. unfold/contrib/inlines/forms.py +43 -0
  16. unfold/decorators.py +3 -0
  17. unfold/fields.py +200 -0
  18. unfold/forms.py +6 -0
  19. unfold/static/unfold/css/simplebar.css +230 -0
  20. unfold/static/unfold/css/styles.css +1 -1
  21. unfold/static/unfold/js/simplebar.js +10 -0
  22. unfold/styles.css +9 -1
  23. unfold/templates/admin/app_list.html +1 -1
  24. unfold/templates/admin/change_form.html +11 -11
  25. unfold/templates/admin/change_list_results.html +2 -2
  26. unfold/templates/admin/edit_inline/stacked.html +6 -6
  27. unfold/templates/admin/edit_inline/tabular.html +7 -9
  28. unfold/templates/admin/includes/fieldset.html +2 -32
  29. unfold/templates/unfold/helpers/app_list.html +1 -1
  30. unfold/templates/unfold/helpers/display_header.html +11 -8
  31. unfold/templates/unfold/helpers/field.html +20 -6
  32. unfold/templates/unfold/helpers/field_readonly.html +1 -3
  33. unfold/templates/unfold/helpers/field_readonly_value.html +1 -0
  34. unfold/templates/unfold/helpers/fieldset_row.html +53 -0
  35. unfold/templates/unfold/helpers/fieldsets_tabs.html +4 -4
  36. unfold/templates/unfold/helpers/form_label.html +1 -1
  37. unfold/templates/unfold/layouts/skeleton.html +2 -0
  38. unfold/templates/unfold/widgets/clearable_file_input.html +1 -1
  39. unfold/templates/unfold/widgets/foreign_key_raw_id.html +15 -0
  40. unfold/templates/unfold/widgets/textarea.html +1 -7
  41. unfold/templates/unfold/widgets/textarea_expandable.html +7 -0
  42. unfold/widgets.py +36 -3
  43. unfold/contrib/import_export/admin.py +0 -37
  44. {django_unfold-0.25.0.dist-info → django_unfold-0.27.0.dist-info}/LICENSE.md +0 -0
  45. {django_unfold-0.25.0.dist-info → django_unfold-0.27.0.dist-info}/WHEEL +0 -0
@@ -11,19 +11,19 @@
11
11
  </h2>
12
12
 
13
13
  {{ inline_admin_formset.formset.management_form }}
14
- {{ inline_admin_formset.formset.non_form_errors }}
14
+ {% include "unfold/helpers/messages/error.html" with errors=inline_admin_formset.formset.non_form_errors %}
15
15
 
16
16
  <div class="border border-gray-200 mb-6 overflow-hidden rounded-md shadow-sm text-gray-700 w-full dark:border-gray-800">
17
17
  {% for inline_admin_form in inline_admin_formset %}
18
18
  <div class="inline-related group inline-stacked {% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
19
- <h3 class="border-b {% if not forloop.first %}border-t{% endif %} border-gray-200 flex font-medium items-center mb-3 px-3 py-2 text-gray-400 text-sm dark:border-gray-800">
19
+ <h3 class="bg-gray-50 border-b {% if not forloop.first %}border-t{% endif %} border-gray-200 flex font-medium items-center mb-3 px-3 py-2 text-gray-400 text-sm dark:bg-white/[.02] dark:border-gray-800">
20
20
  <span class="mr-2">
21
21
  {{ inline_admin_formset.opts.verbose_name|capfirst }}:
22
22
  </span>
23
23
 
24
- <span class="inline_label font-semibold text-gray-900">
24
+ <span class="inline_label font-semibold text-gray-900 dark:text-gray-200">
25
25
  {% if inline_admin_form.original and inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}
26
- <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="{% if inline_admin_formset.has_change_permission %}inlinechangelink{% else %}inlineviewlink{% endif %} font-medium ml-1 text-primary-600 underline text-xs">
26
+ <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="{% if inline_admin_formset.has_change_permission %}inlinechangelink{% else %}inlineviewlink{% endif %} font-medium text-primary-600 underline">
27
27
  {{ inline_admin_form.original }}
28
28
  </a>
29
29
  {% else %}
@@ -42,7 +42,7 @@
42
42
  {% endif %}
43
43
 
44
44
  {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}
45
- <span class="delete flex items-center ml-auto text-gray-500">
45
+ <span class="delete flex gap-2 items-center ml-auto text-gray-500">
46
46
  {{ inline_admin_form.deletion_field.field|add_css_class:form_classes.checkbox }} {{ inline_admin_form.deletion_field.label_tag }}
47
47
  </span>
48
48
  {% endif %}
@@ -52,7 +52,7 @@
52
52
 
53
53
  {% for fieldset in inline_admin_form %}
54
54
  <div class="px-3 -mb-5">
55
- {% include 'admin/includes/fieldset.html' %}
55
+ {% include 'admin/includes/fieldset.html' with stacked=1 %}
56
56
  </div>
57
57
  {% endfor %}
58
58
 
@@ -16,18 +16,18 @@
16
16
  {{ inline_admin_formset.formset.non_form_errors }}
17
17
 
18
18
  <div>
19
- {% if not inline_admin_formset.has_add_permission and inline_admin_formset.forms|length_is:0 %}
19
+ {% if not inline_admin_formset.has_add_permission and inline_admin_formset.forms|length == 0 %}
20
20
  <p class="mb-6 text-gray-500 text-sm dark:text-gray-400">
21
21
  {% trans "No records found." %}
22
22
  </p>
23
23
  {% else %}
24
- <div class="border border-gray-200 mb-6 overflow-x-auto rounded-md shadow-sm dark:border-gray-800">
24
+ <div class="border border-gray-200 mb-6 overflow-x-auto rounded-md shadow-sm dark:border-gray-800" data-simplebar data-simplebar-auto-hide="false">
25
25
  <table class="border-spacing-none border-separate text-gray-700 w-full">
26
26
  <thead class="hidden lg:table-header-group">
27
27
  <tr>
28
28
  {% for field in inline_admin_formset.fields %}
29
29
  {% if not field.widget.is_hidden %}
30
- <th class="column-{{ field.name }}{% if field.required %} required{% endif %} align-middle border-b border-gray-200 font-medium px-3 py-2 text-left text-gray-400 text-sm dark:border-gray-800">
30
+ <th class="column-{{ field.name }}{% if field.required %} required{% endif %} align-middle border-b border-gray-200 font-medium px-3 py-2 text-left text-gray-400 text-sm whitespace-nowrap dark:border-gray-800">
31
31
  <span class="flex flex-row items-center">
32
32
  {{ field.label|capfirst }}
33
33
 
@@ -40,7 +40,7 @@
40
40
  {% endfor %}
41
41
 
42
42
  {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}
43
- <th class="align-middle border-b border-gray-200 font-medium px-3 py-2 text-left text-gray-400 text-sm lg:w-px dark:border-gray-800">
43
+ <th class="align-middle border-b border-gray-200 font-medium px-3 py-2 text-left text-gray-400 text-sm whitespace-nowrap lg:w-px dark:border-gray-800">
44
44
  {% translate "Delete?" %}
45
45
  </th>
46
46
  {% endif %}
@@ -59,12 +59,12 @@
59
59
 
60
60
  {% if inline_admin_form.original or inline_admin_form.show_url %}
61
61
  <tr>
62
- <td class="original" colspan="10">
62
+ <td class="original {% if inline_admin_form.original or inline_admin_form.show_url %}{% if inline_admin_form.original and inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model and inline_admin_formset.has_change_permission %}bg-gray-50 border-b border-gray-200 dark:bg-white/[.02] dark:border-gray-800{% endif %}{% endif %}" colspan="10">
63
63
  {% if inline_admin_form.original or inline_admin_form.show_url %}
64
64
  <p class="align-middle flex font-normal items-center leading-none px-3 text-gray-500 text-left text-sm whitespace-nowrap">
65
65
  {% if inline_admin_form.original %}
66
66
  {% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}
67
- <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="{% if inline_admin_formset.has_change_permission %}inlinechangelink{% else %}inlineviewlink{% endif %} font-medium mt-3 text-primary-600 underline text-xs">
67
+ <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="{% if inline_admin_formset.has_change_permission %}inlinechangelink py-1.5{% else %}inlineviewlink mt-3{% endif %} font-medium text-primary-600 underline text-xs">
68
68
  {{ inline_admin_form.original }}
69
69
  </a>
70
70
  {% endif %}
@@ -112,9 +112,7 @@
112
112
  {% if field.is_readonly or not field.field.is_hidden %}
113
113
  <td{% if field.field.name %} class="field-{{ field.field.name }}{% if field.field.errors|length > 0 %} errors{% endif %}{% if inline_admin_form.original %} p-3 lg:py-3{% else %} py-3{% endif %}{% if field.is_checkbox %} align-middle{% else %} align-top{% endif %} {% if is_last_row and not inline_admin_formset.has_add_permission %}{% if is_last_col %}border-0 {% else %}border-b lg:border-0{% endif %}{% else %}border-b{% endif %} border-gray-200 flex items-center before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 before:w-72 lg:before:hidden font-normal px-3 text-left text-sm lg:table-cell dark:border-gray-800 {% if field.field.is_hidden %} !hidden{% endif %}"{% endif %} data-label="{{ field.field.label }}">
114
114
  {% if field.is_readonly %}
115
- <div class="bg-gray-50 border font-medium max-w-lg px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm truncate whitespace-nowrap dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800">
116
- {{ field.contents }}
117
- </div>
115
+ {% include "unfold/helpers/field_readonly_value.html" with tabular=1 %}
118
116
  {% else %}
119
117
  {{ field.field }}
120
118
 
@@ -13,39 +13,9 @@
13
13
  </div>
14
14
  {% endif %}
15
15
 
16
- <div class="aligned border border-gray-200 mb-8 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800">
16
+ <div class="aligned {% if stacked %}mb-5{% else %}border border-gray-200 mb-8 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800{% endif %}">
17
17
  {% for line in fieldset %}
18
- <div class="form-row block {% if not line.fields|length_is:'1' %}flex flex-row flex-wrap gap-x-8{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
19
- {% for field in line %}
20
- <div class="flex group {% if field.errors %}errors {% endif %}{% if not line.fields|length_is:1 %}lg:max-w-xs w-full {% endif %}{% if not forloop.parentloop.last %} mb-6 {% else %} mb-3{% endif %} {% if row %}flex-row items-center{% else %}flex-col{% endif %}">
21
- {% if field.is_checkbox %}
22
- <div class="flex flex-row items-center">
23
- {{ field.field }}{{ field.label_tag }}
24
- </div>
25
- {% else %}
26
- <div class="{% if row %} w-48{% endif %}">
27
- {{ field.label_tag }}
28
- </div>
29
-
30
- {% if field.is_readonly %}
31
- <div class="readonly bg-gray-50 border font-medium max-w-2xl px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 {% if field.is_json %}truncate whitespace-pre-wrap{% endif %}">{% if field.contents %}{{ field.contents }}{% else %}-{% endif %}</div>
32
- {% else %}
33
- {{ field.field }}
34
- {% endif %}
35
- {% endif %}
36
-
37
- {% if field.errors %}
38
- <span class="mt-1 text-red-600 text-sm dark:text-red-500">
39
- {{ field.errors }}
40
- </span>
41
- {% endif %}
42
-
43
- {% if field.field.help_text %}
44
- {% include "unfold/helpers/help_text.html" with help_text=field.field.help_text %}
45
- {% endif %}
46
- </div>
47
- {% endfor %}
48
- </div>
18
+ {% include "unfold/helpers/fieldset_row.html" %}
49
19
  {% endfor %}
50
20
  </div>
51
21
  </fieldset>
@@ -1,7 +1,7 @@
1
1
  {% load i18n %}
2
2
 
3
3
  {% if sidebar_navigation %}
4
- <div class="overflow-auto">
4
+ <div class="h-0 flex-grow overflow-auto" data-simplebar>
5
5
  {% for group in sidebar_navigation %}
6
6
  {% if group.items %}
7
7
  {% if group.separator %}
@@ -1,20 +1,23 @@
1
- <div class="flex gap-4 items-center">
1
+ <span class="flex gap-4 items-center">
2
2
  {% if value.3 and value.3.path %}
3
- <div class="bg-center bg-cover bg-white border flex font-medium h-8 w-8 dark:bg-gray-900 dark:border-gray-700 {% if value.3.squared %}rounded-sm{% else %}rounded-full{% endif %}" style="background-image: url('{{ value.3.path }}')">
4
- </div>
3
+ <span class="bg-center bg-cover bg-white flex font-medium justify-center overflow-hidden dark:bg-gray-900 dark:border-gray-700 {% if value.3.squared %}rounded-sm{% else %}rounded-full{% endif %}{% if not value.3.borderless %} border{% endif %}{% if not value.3.width or not value.3.height %} h-8 max-w-8 min-w-8{% endif %}">
4
+ <img loading="lazy" src="{{ value.3.path }}" class="object-cover" {% if value.3.width %}width="{{ value.3.width }}"{% endif %} {% if value.3.height %}height="{{ value.3.height }}"{% endif %}/>
5
+ </span>
5
6
  {% elif value.2 %}
6
- <div class="bg-white border flex font-medium h-8 justify-center items-center rounded-full text-xs uppercase w-8 dark:bg-gray-900 dark:border-gray-700">
7
+ <span class="bg-white border flex font-medium h-8 justify-center items-center rounded-full text-xs uppercase w-8 dark:bg-gray-900 dark:border-gray-700">
7
8
  {{ value.2 }}
8
- </div>
9
+ </span>
9
10
  {% endif %}
10
11
 
11
- <div class="flex flex-col text-right lg:text-left">
12
- <h3>{{ value.0 }}</h3>
12
+ <span class="flex flex-col text-right lg:text-left">
13
+ {% if value.0 %}
14
+ <h3>{{ value.0 }}</h3>
15
+ {% endif %}
13
16
 
14
17
  {% if value.1 %}
15
18
  <p class="text-gray-500">
16
19
  {{ value.1 }}
17
20
  </p>
18
21
  {% endif %}
19
- </div>
22
+ </span>
20
23
  </div>
@@ -1,9 +1,23 @@
1
- <div class="{% if field.errors %}errors {% endif %}flex group mb-6 flex-col last:mb-4">
2
- {% include "unfold/helpers/form_label.html" with field=field %}
1
+ {% if field.field.widget.input_type == "checkbox" %}
2
+ <div class="{% if field.errors %}errors {% endif %}flex flex-col group mb-6 last:mb-4">
3
+ <div class="flex flex-row gap-2 items-center">
4
+ {{ field }}
3
5
 
4
- {{ field }}
6
+ {% include "unfold/helpers/form_label.html" with field=field %}
7
+ </div>
5
8
 
6
- {% include "unfold/helpers/form_errors.html" with errors=field.errors %}
9
+ {% include "unfold/helpers/form_errors.html" with errors=field.errors %}
7
10
 
8
- {% include "unfold/helpers/help_text.html" with help_text=field.help_text %}
9
- </div>
11
+ {% include "unfold/helpers/help_text.html" with help_text=field.help_text %}
12
+ </div>
13
+ {% else %}
14
+ <div class="{% if field.errors %}errors {% endif %}flex flex-col group mb-6 last:mb-4">
15
+ {% include "unfold/helpers/form_label.html" with field=field %}
16
+
17
+ {{ field }}
18
+
19
+ {% include "unfold/helpers/form_errors.html" with errors=field.errors %}
20
+
21
+ {% include "unfold/helpers/help_text.html" with help_text=field.help_text %}
22
+ </div>
23
+ {% endif %}
@@ -3,7 +3,5 @@
3
3
  {{ title }}
4
4
  </label>
5
5
 
6
- <div class="readonly bg-gray-50 border font-medium max-w-2xl px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400">
7
- {{ value }}
8
- </div>
6
+ {% include "unfold/helpers/field_readonly_value.html" %}
9
7
  </div>
@@ -0,0 +1 @@
1
+ <div class="readonly font-medium max-w-2xl py-2 text-gray-500 text-sm dark:text-gray-400 *:rounded-md {% if not adminform.model_admin.compressed_fields or tabular and not field.is_image %}bg-gray-50 border px-3 rounded-md shadow-sm dark:border-gray-700 dark:bg-gray-800{% endif %} {% if field.is_image %}inline-block{% endif %} {% if field.is_json %}truncate whitespace-pre-wrap{% endif %}">{% if value %}{{ value }}{% elif field.contents %}{{ field.contents }}{% else %}-{% endif %}</div>
@@ -0,0 +1,53 @@
1
+ <div class="form-row
2
+ {% if adminform.model_admin.compressed_fields %} border-b border-gray-200 -mx-3 px-3 pt-3 first:pt-0 last:border-b-0{% endif %}
3
+ {% if not line.fields|length == 1 %} flex flex-row flex-wrap gap-x-8{% endif %}
4
+ {% if not line.has_visible_field %} hidden{% endif %}
5
+ {% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
6
+ {% for field in line %}
7
+ <div class="flex group {% if not line.fields|length == 1 and not adminform.model_admin.compressed_fields %} lg:max-w-xs flex-grow{% endif %}{% if field.errors %} errors {% endif %}{% if not forloop.parentloop.last %} {% if adminform.model_admin.compressed_fields %}mb-3{% else %}mb-6{% endif %}{% else %} pb-3{% endif %} {% if adminform.model_admin.compressed_fields %}flex-col lg:flex-row lg:gap-2 {% else %}flex-col{% endif %}">
8
+ {% if field.is_checkbox %}
9
+ <div class="flex flex-row">
10
+ {{ field.field }}
11
+
12
+ <div class="flex flex-col">
13
+ {{ field.label_tag }}
14
+
15
+ {% if field.field.help_text %}
16
+ <div class="ml-2 -mt-1">
17
+ {% include "unfold/helpers/help_text.html" with help_text=field.field.help_text %}
18
+ </div>
19
+ {% endif %}
20
+
21
+ {% if field.errors %}
22
+ <span class="mt-1 text-red-600 text-sm dark:text-red-500">
23
+ {{ field.errors }}
24
+ </span>
25
+ {% endif %}
26
+ </div>
27
+ </div>
28
+ {% else %}
29
+ <div class="{% if adminform.model_admin.compressed_fields %} min-w-48 mt-2 w-48{% endif %}">
30
+ {{ field.label_tag }}
31
+ </div>
32
+
33
+ <div class="flex-grow">
34
+ {% if field.is_readonly %}
35
+ {% include "unfold/helpers/field_readonly_value.html" %}
36
+ {% else %}
37
+ {{ field.field }}
38
+ {% endif %}
39
+
40
+ {% if field.field.help_text and not field.is_checkbox %}
41
+ {% include "unfold/helpers/help_text.html" with help_text=field.field.help_text %}
42
+ {% endif %}
43
+
44
+ {% if field.errors %}
45
+ <span class="mt-1 text-red-600 text-sm dark:text-red-500">
46
+ {{ field.errors }}
47
+ </span>
48
+ {% endif %}
49
+ </div>
50
+ {% endif %}
51
+ </div>
52
+ {% endfor %}
53
+ </div>
@@ -7,8 +7,8 @@
7
7
  {% for fieldset in tabs %}
8
8
  <li>
9
9
  <a class="cursor-pointer font-semibold hover:text-gray-700 dark:hover:text-white"
10
- x-on:click="openTab = '{{ fieldset.name|slugify }}'"
11
- x-bind:class="openTab == '{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %} ? 'text-gray-700 dark:text-white' : ''">
10
+ x-on:click="openTab = '{{ forloop.counter0 }}-{{ fieldset.name|slugify }}'"
11
+ x-bind:class="openTab == '{{ forloop.counter0 }}-{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %} ? 'text-gray-700 dark:text-white' : ''">
12
12
  {{ fieldset.name }}
13
13
  </a>
14
14
  </li>
@@ -16,8 +16,8 @@
16
16
  </ul>
17
17
 
18
18
  {% for fieldset in tabs %}
19
- <div class="tab-wrapper{% if fieldset.name %} fieldset-{{ fieldset.name|slugify }}{% endif %}"
20
- x-show="openTab == '{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %}">
19
+ <div class="tab-wrapper{% if fieldset.name %} fieldset-{{ fieldset.name|slugify }} fieldset-{{ forloop.counter0 }}-{{ fieldset.name|slugify }}{% endif %}"
20
+ x-show="openTab == '{{ forloop.counter0 }}-{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %}">
21
21
  {% include 'admin/includes/fieldset.html' %}
22
22
  </div>
23
23
  {% endfor %}
@@ -1,4 +1,4 @@
1
- <label for="{{ field.id_for_label }}" class="block font-medium mb-2 text-gray-900 text-sm dark:text-gray-200">
1
+ <label for="{{ field.id_for_label }}" class="block text-gray-900 text-sm dark:text-gray-200{% if field.field.widget.input_type == "checkbox" %}{% else %} font-medium mb-2{% endif %}">
2
2
  {{ field.label }}
3
3
 
4
4
  {% if field.field.required %}
@@ -25,6 +25,7 @@
25
25
  {% endfor %}
26
26
 
27
27
  <link href="{% static 'unfold/css/styles.css' %}" rel="stylesheet">
28
+ <link href="{% static 'unfold/css/simplebar.css' %}" rel="stylesheet">
28
29
 
29
30
  <script src="{% static 'unfold/js/alpine.persist.js' %}" defer></script>
30
31
  <script src="{% static 'unfold/js/alpine.js' %}" defer></script>
@@ -66,6 +67,7 @@
66
67
  {% block base %}{% endblock %}
67
68
 
68
69
  <div id="modal-overlay" class="backdrop-blur-sm bg-opacity-80 bg-gray-900 bottom-0 fixed hidden left-0 mr-1 right-0 top-0 z-50"></div>
70
+ <script src="{% static 'unfold/js/simplebar.js' %}"></script>
69
71
  </body>
70
72
 
71
73
  </html>
@@ -1,7 +1,7 @@
1
1
  {% load i18n %}
2
2
 
3
3
  {% if widget.attrs.accept == 'image/*' and widget.is_initial %}
4
- <div class="bg-gray-50 border flex items-center justify-center mb-4 min-h-32 p-1 rounded-md w-48 dark:bg-white/[.02] dark:border-gray-700">
4
+ <div class="mb-4 max-w-48">
5
5
  <a href="{{ widget.value.url }}" target="_blank">
6
6
  <img src="{{ widget.value.url }}" alt="{% trans 'Image preview' %}" class="block rounded" />
7
7
  </a>
@@ -0,0 +1,15 @@
1
+ <div class="flex flex-row">
2
+ {% include 'django/forms/widgets/input.html' %}
3
+
4
+ {% if related_url %}
5
+ <a href="{{ related_url }}" class="related-lookup related-widget-wrapper-link view-related bg-white border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm w-9.5 hover:text-gray-700 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200" id="lookup_id_{{ widget.name }}">
6
+ <span class="material-symbols-outlined text-sm">search</span>
7
+ </a>
8
+ {% endif %}
9
+
10
+ {% if link_label and link_url %}
11
+ <a href="{{ link_url }}" title="{{ link_label }}" class="bg-white border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm w-9.5 hover:text-gray-700 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200">
12
+ <span class="material-symbols-outlined text-sm">visibility</span>
13
+ </a>
14
+ {% endif %}
15
+ </div>
@@ -1,7 +1 @@
1
- <div class="relative">
2
- <div class="border break-words font-medium invisible max-w-4xl px-3 py-2 text-sm" style="min-height: 64px">
3
- {% if widget.value %}{{ widget.value|linebreaks }}{% endif %}
4
- </div>
5
-
6
- <textarea onInput="this.previousElementSibling.innerText = this.value + String.fromCharCode(10)" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
7
- </div>
1
+ <textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
@@ -0,0 +1,7 @@
1
+ <div class="relative">
2
+ <div class="border break-words font-medium invisible max-w-4xl px-3 py-2 text-sm" style="min-height: 64px">
3
+ {% if widget.value %}{{ widget.value|linebreaks }}{% endif %}
4
+ </div>
5
+
6
+ <textarea onInput="this.previousElementSibling.innerText = this.value + String.fromCharCode(10)" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
7
+ </div>
unfold/widgets.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from typing import Any, Callable, Dict, Optional, Tuple, Union
2
2
 
3
3
  from django.contrib.admin.options import VERTICAL
4
+ from django.contrib.admin.sites import AdminSite
4
5
  from django.contrib.admin.widgets import (
5
6
  AdminBigIntegerFieldWidget,
6
7
  AdminDateWidget,
@@ -13,8 +14,10 @@ from django.contrib.admin.widgets import (
13
14
  AdminTextInputWidget,
14
15
  AdminTimeWidget,
15
16
  AdminUUIDInputWidget,
17
+ ForeignKeyRawIdWidget,
16
18
  RelatedFieldWidgetWrapper,
17
19
  )
20
+ from django.db.models.fields.reverse_related import ForeignObjectRel
18
21
  from django.forms import (
19
22
  CheckboxInput,
20
23
  MultiWidget,
@@ -106,7 +109,6 @@ SELECT_CLASSES = [
106
109
  "pr-8",
107
110
  "max-w-2xl",
108
111
  "appearance-none",
109
- "truncate",
110
112
  ]
111
113
 
112
114
  PROSE_CLASSES = [
@@ -358,6 +360,20 @@ class UnfoldAdminSingleTimeWidget(AdminTimeWidget):
358
360
  class UnfoldAdminTextareaWidget(AdminTextareaWidget):
359
361
  template_name = "unfold/widgets/textarea.html"
360
362
 
363
+ def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
364
+ attrs = attrs or {}
365
+
366
+ super().__init__(
367
+ attrs={
368
+ "class": "vLargeTextField " + " ".join(TEXTAREA_CLASSES),
369
+ **(attrs or {}),
370
+ }
371
+ )
372
+
373
+
374
+ class UnfoldAdminExpandableTextareaWidget(AdminTextareaWidget):
375
+ template_name = "unfold/widgets/textarea_expandable.html"
376
+
361
377
  def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
362
378
  attrs = attrs or {}
363
379
 
@@ -499,7 +515,7 @@ class UnfoldBooleanWidget(CheckboxInput):
499
515
  if attrs is None:
500
516
  attrs = {}
501
517
 
502
- return super().__init__(
518
+ super().__init__(
503
519
  {
504
520
  **(attrs or {}),
505
521
  "class": " ".join(CHECKBOX_CLASSES + [attrs.get("class", "")]),
@@ -512,10 +528,27 @@ class UnfoldBooleanSwitchWidget(CheckboxInput):
512
528
  def __init__(
513
529
  self, attrs: Optional[Dict[str, Any]] = None, check_test: Callable = None
514
530
  ) -> None:
515
- return super().__init__(
531
+ super().__init__(
516
532
  attrs={"class": " ".join(SWITCH_CLASSES), **(attrs or {})}, check_test=None
517
533
  )
518
534
 
519
535
 
520
536
  class UnfoldRelatedFieldWidgetWrapper(RelatedFieldWidgetWrapper):
521
537
  template_name = "unfold/widgets/related_widget_wrapper.html"
538
+
539
+
540
+ class UnfoldForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
541
+ template_name = "unfold/widgets/foreign_key_raw_id.html"
542
+
543
+ def __init__(
544
+ self,
545
+ rel: ForeignObjectRel,
546
+ admin_site: AdminSite,
547
+ attrs: Optional[Dict] = None,
548
+ using: Optional[Any] = None,
549
+ ) -> None:
550
+ attrs = {
551
+ "class": " ".join(["vForeignKeyRawIdAdminField"] + INPUT_CLASSES),
552
+ **(attrs or {}),
553
+ }
554
+ super().__init__(rel, admin_site, attrs, using)
@@ -1,37 +0,0 @@
1
- from django import forms
2
- from django.utils.translation import gettext_lazy as _
3
- from import_export.admin import ExportActionModelAdmin as BaseExportActionModelAdmin
4
- from unfold.admin import ActionForm
5
- from unfold.widgets import SELECT_CLASSES
6
-
7
-
8
- def export_action_form_factory(formats):
9
- class _ExportActionForm(ActionForm):
10
- format = forms.ChoiceField(
11
- label=" ",
12
- choices=formats,
13
- required=False,
14
- widget=forms.Select(
15
- {"class": " ".join([*SELECT_CLASSES, "ml-3", "!w-auto", "lg:!w-40"])}
16
- ),
17
- )
18
-
19
- _ExportActionForm.__name__ = "ExportActionForm"
20
-
21
- return _ExportActionForm
22
-
23
-
24
- class ExportActionModelAdmin(BaseExportActionModelAdmin):
25
- def __init__(self, *args, **kwargs):
26
- super().__init__(*args, **kwargs)
27
-
28
- choices = []
29
- formats = self.get_export_formats()
30
- if formats:
31
- for i, f in enumerate(formats):
32
- choices.append((str(i), f().get_title()))
33
-
34
- if len(formats) > 1:
35
- choices.insert(0, ("", _("Select format")))
36
-
37
- self.action_form = export_action_form_factory(choices)