django-unfold 0.59.0__py3-none-any.whl → 0.60.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 (33) hide show
  1. {django_unfold-0.59.0.dist-info → django_unfold-0.60.0.dist-info}/METADATA +3 -2
  2. {django_unfold-0.59.0.dist-info → django_unfold-0.60.0.dist-info}/RECORD +33 -25
  3. unfold/admin.py +45 -13
  4. unfold/contrib/inlines/admin.py +11 -6
  5. unfold/contrib/inlines/forms.py +3 -1
  6. unfold/contrib/location_field/__init__.py +0 -0
  7. unfold/contrib/location_field/apps.py +6 -0
  8. unfold/contrib/location_field/templates/location_field/map_widget.html +5 -0
  9. unfold/fields.py +17 -19
  10. unfold/forms.py +95 -1
  11. unfold/mixins/base_model_admin.py +15 -1
  12. unfold/sites.py +3 -29
  13. unfold/static/admin/js/admin/RelatedObjectLookups.js +2 -2
  14. unfold/static/unfold/css/styles.css +1 -1
  15. unfold/styles.css +3 -3
  16. unfold/templates/admin/app_list.html +4 -1
  17. unfold/templates/admin/edit_inline/stacked.html +12 -8
  18. unfold/templates/admin/edit_inline/tabular.html +2 -0
  19. unfold/templates/admin/includes/fieldset.html +1 -1
  20. unfold/templates/admin/login.html +45 -88
  21. unfold/templates/unfold/components/button.html +10 -1
  22. unfold/templates/unfold/helpers/app_list.html +5 -2
  23. unfold/templates/unfold/helpers/app_list_default.html +2 -1
  24. unfold/templates/unfold/helpers/field.html +5 -3
  25. unfold/templates/unfold/helpers/pagination_inline.html +28 -0
  26. unfold/templates/unfold/helpers/unauthenticated_header.html +15 -0
  27. unfold/templates/unfold/helpers/unauthenticated_title.html +11 -0
  28. unfold/templates/unfold/layouts/unauthenticated.html +37 -0
  29. unfold/templates/unfold/widgets/text.html +28 -0
  30. unfold/templatetags/unfold.py +31 -1
  31. unfold/widgets.py +41 -1
  32. {django_unfold-0.59.0.dist-info → django_unfold-0.60.0.dist-info}/LICENSE.md +0 -0
  33. {django_unfold-0.59.0.dist-info → django_unfold-0.60.0.dist-info}/WHEEL +0 -0
unfold/styles.css CHANGED
@@ -197,7 +197,7 @@ table tr.selected th {
197
197
  }
198
198
 
199
199
  .timezonewarning {
200
- @apply absolute block items-center right-20 top-2.5 text-base-500 truncate;
200
+ @apply absolute block items-center right-11 top-2 text-base-500 truncate;
201
201
  font-size: 0;
202
202
  }
203
203
 
@@ -218,7 +218,7 @@ table tr.selected th {
218
218
  Selector
219
219
  *******************************************************/
220
220
  .selector {
221
- @apply flex flex-col grow items-center md:flex-row;
221
+ @apply flex flex-col grow items-center max-w-5xl md:flex-row;
222
222
  }
223
223
 
224
224
  .selector .selector-available-title label,
@@ -319,7 +319,7 @@ table tr.selected th {
319
319
  }
320
320
 
321
321
  [data-inline-type="stacked"] .add-row {
322
- @apply border-t border-base-200 overflow-hidden dark:border-base-800;
322
+ @apply overflow-hidden;
323
323
  }
324
324
 
325
325
  .add-row td {
@@ -44,5 +44,8 @@
44
44
  {% endfor %}
45
45
  </div>
46
46
  {% else %}
47
- <p>{% translate 'You don’t have permission to view or edit anything.' %}</p>
47
+ <p>
48
+ {% trans "You don’t have permission to view or edit anything." as error_message %}
49
+ {% include "unfold/helpers/messages/error.html" with error=error_message %}
50
+ </p>
48
51
  {% endif %}
@@ -17,15 +17,15 @@
17
17
  {{ inline_admin_formset.formset.management_form }}
18
18
  {% include "unfold/helpers/messages/error.html" with errors=inline_admin_formset.formset.non_form_errors %}
19
19
 
20
- <div class="border border-base-200 mb-6 overflow-hidden rounded-default shadow-xs w-full dark:border-base-800 *:border-t first-of-type:border-t-0 *:border-base-200 dark:*:border-base-800"
20
+ <div class="border border-base-200 mb-6 overflow-hidden rounded-default shadow-xs w-full dark:border-base-800"
21
21
  {% if inline_admin_formset.opts.ordering_field %}
22
22
  data-ordering-field="{{ inline_admin_formset.opts.ordering_field }}" x-on:end="sortRecords" x-sort.ghost
23
23
  {% endif %}>
24
24
 
25
25
  {% for inline_admin_form in inline_admin_formset %}
26
- <div x-sort:item 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 %}">
26
+ <div x-sort:item 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 %}" {% if inline_admin_formset.opts.collapsible and inline_admin_form.original %}x-data="{ open: {% if inline_admin_form.errors %}true{% else %}false{% endif %} }"{% endif %}>
27
27
  {% if not inline_admin_formset.opts.hide_title or inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}
28
- <h3 class="bg-base-50 border-b border-base-200 flex font-medium items-center gap-2 px-3 py-2 text-sm dark:bg-white/[.02] dark:border-base-800">
28
+ <h3 class="border-b border-base-200 flex font-medium items-center gap-2 px-3 py-2 text-sm dark:border-base-800 {% if inline_admin_formset.opts.collapsible and inline_admin_form.original %}cursor-pointer{% endif %}" {% if inline_admin_formset.opts.collapsible and inline_admin_form.original %}x-on:click="open = !open"{% endif %}>
29
29
  {% if inline_admin_formset.opts.ordering_field %}
30
30
  {% if inline_admin_form.original %}
31
31
  <span class="material-symbols-outlined cursor-pointer" x-sort:handle>drag_indicator</span>
@@ -82,11 +82,13 @@
82
82
 
83
83
  {% include "unfold/helpers/messages/error.html" with errors=inline_admin_form.form.non_field_errors %}
84
84
 
85
- {% for fieldset in inline_admin_form %}
86
- <div class="{% if inline_admin_formset.opts.hide_title %}{% if not inline_admin_formset.formset.can_delete or not inline_admin_formset.has_delete_permission %}pt-3{% endif %}{% endif %}">
87
- {% include 'admin/includes/fieldset.html' with stacked=1 %}
88
- </div>
89
- {% endfor %}
85
+ <div class="border-b border-base-200 dark:border-base-800" {% if inline_admin_formset.opts.collapsible and inline_admin_form.original %}x-show="open"{% endif %}>
86
+ {% for fieldset in inline_admin_form %}
87
+ <div class="{% if inline_admin_formset.opts.hide_title %}{% if not inline_admin_formset.formset.can_delete or not inline_admin_formset.has_delete_permission %}pt-3{% endif %}{% endif %}">
88
+ {% include 'admin/includes/fieldset.html' with stacked=1 %}
89
+ </div>
90
+ {% endfor %}
91
+ </div>
90
92
 
91
93
  {% if inline_admin_form.needs_explicit_pk_field %}
92
94
  {{ inline_admin_form.pk_field.field }}
@@ -99,4 +101,6 @@
99
101
 
100
102
  {% if inline_admin_formset.is_collapsible %}</details>{% endif %}
101
103
  </fieldset>
104
+
105
+ {% include "unfold/helpers/pagination_inline.html" %}
102
106
  </div>
@@ -36,5 +36,7 @@
36
36
 
37
37
  {% if inline_admin_formset.is_collapsible %}</details>{% endif %}
38
38
  </fieldset>
39
+
40
+ {% include "unfold/helpers/pagination_inline.html" %}
39
41
  </div>
40
42
  </div>
@@ -3,7 +3,7 @@
3
3
  <fieldset class="module{% if fieldset.classes %} {{ fieldset.classes }}{% endif %}" {% if stacked != 1 %}x-show="activeTab == 'general'"{% endif %}>
4
4
  {% if fieldset.name and "tab" not in fieldset.classes %}
5
5
  {% if stacked == 1 %}
6
- <h2 class="bg-base-50 border-b border-base-200 border-t border-dashed font-semibold text-font-important-light px-3 py-2 dark:text-font-important-dark dark:bg-white/[.02] dark:border-base-800">
6
+ <h2 class="border-b border-base-200 border-t border-dashed font-semibold text-font-important-light px-3 py-4.5 dark:text-font-important-dark dark:border-base-800">
7
7
  {{ fieldset.name }}
8
8
  </h2>
9
9
  {% else %}
@@ -1,105 +1,62 @@
1
- {% extends 'unfold/layouts/skeleton.html' %}
1
+ {% extends 'unfold/layouts/unauthenticated.html' %}
2
2
 
3
- {% load i18n static %}
3
+ {% load i18n static unfold %}
4
4
 
5
5
  {% block extrastyle %}
6
6
  {{ block.super }}
7
7
  {{ form.media }}
8
8
  {% endblock %}
9
9
 
10
- {% block bodyclass %}{{ block.super }}bg-base-50 login dark:bg-base-900{% endblock %}
10
+ {% block title %}
11
+ {{ title }} | {{ site_title }}
12
+ {% endblock %}
11
13
 
12
- {% block usertools %}{% endblock %}
14
+ {% block content %}
15
+ {% include "unfold/helpers/unauthenticated_title.html" with title=site_title|default:_('Django site admin') subtitle=_('Welcome back to') %}
13
16
 
14
- {% block nav-global %}{% endblock %}
17
+ {% include "unfold/helpers/messages.html" %}
15
18
 
16
- {% block nav-sidebar %}{% endblock %}
19
+ {% if form.errors or form.non_field_errors %}
20
+ <div class="flex flex-col gap-4 mb-8 *:mb-0">
21
+ {% include "unfold/helpers/messages/errornote.html" with errors=form.errors %}
17
22
 
18
- {% block content_title %}{% endblock %}
23
+ {% include "unfold/helpers/messages/error.html" with errors=form.non_field_errors %}
19
24
 
20
- {% block breadcrumbs %}{% endblock %}
25
+ {% if user.is_authenticated %}
26
+ {% blocktranslate trimmed asvar message %}
27
+ You are authenticated as {{ username }}, but are not authorized to
28
+ access this page. Would you like to login to a different account?
29
+ {% endblocktranslate %}
21
30
 
22
- {% block title %}
23
- {{ title }} | {{ site_title }}
24
- {% endblock %}
31
+ {% include "unfold/helpers/messages/error.html" with error=message %}
32
+ {% endif %}
33
+ </div>
34
+ {% endif %}
35
+
36
+ {% block login_before %}{% endblock %}
37
+
38
+ <form action="{{ app_path }}" method="post" id="login-form">
39
+ {% csrf_token %}
40
+
41
+ {% include "unfold/helpers/field.html" with field=form.username %}
42
+
43
+ {% include "unfold/helpers/field.html" with field=form.password %}
44
+
45
+ <div class="flex flex-col gap-3 mt-6">
46
+ {% component "unfold/components/button.html" with submit=1 variant="primary" class="submit-row w-full" %}
47
+ {% translate 'Log in' %} <span class="material-symbols-outlined ml-2 relative right-0 transition-all group-hover:-right-1">arrow_forward</span>
48
+ {% endcomponent %}
49
+
50
+ {% url 'admin_password_reset' as password_reset_url %}
51
+ {% url 'admin:admin_password_reset' as unfold_password_reset_url %}
25
52
 
26
- {% block base %}
27
- <div id="page" class="bg-white flex min-h-screen dark:bg-base-900 ">
28
- <div class="flex grow items-center justify-center mx-auto px-4 relative">
29
- <div class="w-full sm:w-96">
30
- <h1 class="font-semibold mb-10">
31
- <span class="block text-font-important-light dark:text-font-important-dark">{% trans 'Welcome back to' %}</span>
32
- <span class="block text-primary-600 text-xl dark:text-primary-500">{{ site_title|default:_('Django site admin') }}</span>
33
- </h1>
34
-
35
- {% include "unfold/helpers/messages.html" %}
36
-
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 %}
40
-
41
- {% include "unfold/helpers/messages/error.html" with errors=form.non_field_errors %}
42
-
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 %}
48
-
49
- {% include "unfold/helpers/messages/error.html" with error=message %}
50
- {% endif %}
51
- </div>
52
- {% endif %}
53
-
54
- {% block login_before %}{% endblock %}
55
-
56
- <form action="{{ app_path }}" method="post" id="login-form">
57
- {% csrf_token %}
58
-
59
- {% include "unfold/helpers/field.html" with field=form.username %}
60
-
61
- {% include "unfold/helpers/field.html" with field=form.password %}
62
-
63
- {% url 'admin_password_reset' as password_reset_url %}
64
-
65
- <div class="submit-row">
66
- <button type="submit" class="bg-primary-600 border border-transparent cursor-pointer flex flex-row font-semibold group items-center justify-center py-2 rounded-default text-sm text-white w-full">
67
- {% translate 'Log in' %}
68
-
69
- <span class="material-symbols-outlined ml-2 relative right-0 text-base transition-all group-hover:-right-1">arrow_forward</span>
70
- </button>
71
- </div>
72
-
73
- {% if password_reset_url %}
74
- <div class="password-reset-link">
75
- <a href="{{ password_reset_url }}" class="border border-base-200 font-medium hidden mt-4 px-3 py-2 rounded-default text-center text-sm text-base-500 transition-all w-full hover:bg-base-50 lg:block lg:w-auto dark:border-base-700 dark:text-font-default-dark dark:hover:text-base-200 dark:hover:bg-base-900">
76
- {% translate 'Forgotten your password or username?' %}
77
- </a>
78
- </div>
79
- {% endif %}
80
- </form>
81
-
82
- {% block login_after %}{% endblock %}
83
- </div>
84
-
85
- <div class="absolute flex flex-row items-center justify-between left-0 m-4 right-0 top-0">
86
- {% if site_url %}
87
- <a href="{{ site_url }}" class="flex font-medium items-center text-sm text-primary-600 dark:text-primary-500">
88
- <span class="material-symbols-outlined mr-2">arrow_back</span> {% trans 'Return to site' %}
89
- </a>
90
- {% endif %}
91
-
92
- {% if not theme %}
93
- <div class="ml-auto">
94
- {% include "unfold/helpers/theme_switch.html" %}
95
- </div>
96
- {% endif %}
97
- </div>
53
+ {% if password_reset_url or unfold_password_reset_url %}
54
+ {% component "unfold/components/button.html" with href=password_reset_url|default:unfold_password_reset_url variant="secondary" class="password-reset-link w-full" %}
55
+ {% translate 'Forgotten your password or username?' %}
56
+ {% endcomponent %}
57
+ {% endif %}
98
58
  </div>
59
+ </form>
99
60
 
100
- {% if image %}
101
- <div class="bg-cover grow hidden max-w-3xl xl:max-w-4xl xl:block" style="background-image: url('{{ image }}')">
102
- </div>
103
- {% endif %}
104
- </div>
61
+ {% block login_after %}{% endblock %}
105
62
  {% endblock %}
@@ -1,4 +1,13 @@
1
1
  <{% if href %}a href="{{ href }}"{% else %}button{% endif%} {% if submit %}type="submit"{% endif%} {% if name %}name="{{ name }}"{% endif %}
2
- class="border border-base-200 font-medium flex items-center gap-2 px-3 py-2 rounded-default justify-center whitespace-nowrap {% if attrs.disabled %}cursor-not-allowed opacity-50{% endif %} {% if variant == "default" %}bg-white text-font-important-light dark:border-base-700 dark:bg-transparent dark:text-font-important-dark{% else %}bg-primary-600 border-transparent text-white{% endif %}{% if class %} {{ class }}{% endif %}" {% include "unfold/helpers/attrs.html" %}>
2
+ class="font-medium flex group items-center gap-2 px-3 py-2 rounded-default justify-center whitespace-nowrap {% if attrs.disabled %}cursor-not-allowed opacity-50{% else %}cursor-pointer{% endif %}
3
+ {% if variant == "default" %}
4
+ border border-base-200 bg-white text-font-important-light dark:border-base-700 dark:bg-transparent dark:text-font-important-dark
5
+ {% elif variant == "secondary" %}
6
+ bg-gray-100 border border-transparent dark:bg-base-800
7
+ {% elif variant == "ghost" %}
8
+ bg-transparent text-font-important-light dark:text-font-important-dark
9
+ {% else %}
10
+ border border-base-200 bg-primary-600 border-transparent text-white
11
+ {% endif %}{% if class %} {{ class }}{% endif %}" {% include "unfold/helpers/attrs.html" %}>
3
12
  {{ children }}
4
13
  </{% if href %}a{% else %}button{% endif%}>
@@ -57,7 +57,7 @@
57
57
  {% endfor %}
58
58
  </div>
59
59
 
60
- {% if sidebar_show_all_applications %}
60
+ {% if sidebar_show_all_applications and app_list|length > 0 %}
61
61
  <div class="mt-auto" x-data="{ openAllApplications: false }">
62
62
  <a class="cursor-pointer flex items-center h-[64px] px-6 py-3 text-sm dark:text-font-default-dark hover:text-primary-600 dark:hover:text-primary-500" x-on:click="openAllApplications = !openAllApplications">
63
63
  <span class="material-symbols-outlined md-18 mr-3">
@@ -95,5 +95,8 @@
95
95
  </div>
96
96
  {% endif %}
97
97
  {% else %}
98
- <p>{% translate "You don’t have permission to view or edit anything." %}</p>
98
+ <p>
99
+ {% trans "You don’t have permission to view or edit anything." as error_message %}
100
+ {% include "unfold/helpers/messages/error.html" with error=error_message %}
101
+ </p>
99
102
  {% endif %}
@@ -56,6 +56,7 @@
56
56
  {% endfor %}
57
57
  {% else %}
58
58
  <p>
59
- {% translate 'You don’t have permission to view or edit anything.' %}
59
+ {% trans "You don’t have permission to view or edit anything." as error_message %}
60
+ {% include "unfold/helpers/messages/error.html" with error=error_message %}
60
61
  </p>
61
62
  {% endif %}
@@ -13,10 +13,12 @@
13
13
  {% include "unfold/helpers/help_text.html" with help_text=field.help_text %}
14
14
  </div>
15
15
  {% else %}
16
- <div class="{% if field.errors %}errors {% endif %}flex flex-col gap-2 group mb-5 last:mb-4">
17
- {% include "unfold/helpers/form_label.html" with field=field %}
16
+ <div class="{% if field.errors %}errors {% endif %}flex flex-col group mb-5 last:mb-4">
17
+ <div class="flex flex-col gap-2">
18
+ {% include "unfold/helpers/form_label.html" with field=field %}
18
19
 
19
- {{ field }}
20
+ {{ field }}
21
+ </div>
20
22
 
21
23
  {% include "unfold/helpers/form_errors.html" with errors=field.errors %}
22
24
 
@@ -0,0 +1,28 @@
1
+ {% load i18n admin_urls unfold %}
2
+
3
+ {% with page_obj=inline_admin_formset.formset.page has_tab=inline_admin_formset.opts.tab pagination_key=inline_admin_formset.formset.get_pagination_key %}
4
+ {% elided_page_range page_obj.paginator page_obj.number as elided_page_range %}
5
+ <input type="hidden" name="{{ inline_admin_formset.formset.get_pagination_key }}" value="{{ page_obj.number }}" />
6
+
7
+ {% if page_obj.paginator.count and page_obj.paginator.num_pages > 1%}
8
+ <div class="flex items-center gap-2 mb-6">
9
+ {% for i in elided_page_range %}
10
+ {% if i == page_obj.paginator.ELLIPSIS %}
11
+ <span>{{ page_obj.paginator.ELLIPSIS }}</span>
12
+ {% elif i == page_obj.number %}
13
+ <span class="font-medium text-primary-600">{{ i }}</span>
14
+ {% else %}
15
+ <a href="?{% querystring_params pagination_key i %}{% if has_tab %}#{{ inline_admin_formset.formset.prefix|slugify }}{% endif %}">
16
+ {{ i }}
17
+ </a>
18
+ {% endif %}
19
+ {% endfor %}
20
+
21
+ <div>-</div>
22
+
23
+ <div>
24
+ {{ page_obj.paginator.count }} {% if page_obj.paginator.count == 1 %}{{ inline_admin_formset.opts.verbose_name }}{% else %}{{ inline_admin_formset.opts.verbose_name_plural }}{% endif %}
25
+ </div>
26
+ </div>
27
+ {% endif %}
28
+ {% endwith %}
@@ -0,0 +1,15 @@
1
+ {% load i18n %}
2
+
3
+ <div class="absolute flex flex-row items-center justify-between left-0 m-4 right-0 top-0">
4
+ {% if site_url %}
5
+ <a href="{{ site_url }}" class="flex font-medium items-center text-sm text-primary-600 dark:text-primary-500">
6
+ <span class="material-symbols-outlined mr-2">arrow_back</span> {% trans 'Return to site' %}
7
+ </a>
8
+ {% endif %}
9
+
10
+ {% if not theme %}
11
+ <div class="ml-auto">
12
+ {% include "unfold/helpers/theme_switch.html" %}
13
+ </div>
14
+ {% endif %}
15
+ </div>
@@ -0,0 +1,11 @@
1
+ {% load i18n %}
2
+
3
+ <div class="border-b border-base-200 mb-8 pb-6 dark:border-base-800">
4
+ <h1 class="font-semibold">
5
+ {% if subtitle %}
6
+ <span class="block text-font-important-light dark:text-font-important-dark">{{ subtitle }} </span>
7
+ {% endif %}
8
+
9
+ <span class="block text-primary-600 text-xl dark:text-primary-500">{{ title }}</span>
10
+ </h1>
11
+ </div>
@@ -0,0 +1,37 @@
1
+ {% extends 'unfold/layouts/skeleton.html' %}
2
+
3
+ {% load i18n static unfold %}
4
+
5
+
6
+ {% block bodyclass %}{{ block.super }}bg-base-50 login dark:bg-base-900{% endblock %}
7
+
8
+ {% block usertools %}{% endblock %}
9
+
10
+ {% block nav-global %}{% endblock %}
11
+
12
+ {% block nav-sidebar %}{% endblock %}
13
+
14
+ {% block content_title %}{% endblock %}
15
+
16
+ {% block breadcrumbs %}{% endblock %}
17
+
18
+ {% block title %}
19
+ {{ title }} | {{ site_title }}
20
+ {% endblock %}
21
+
22
+ {% block base %}
23
+ <div id="page" class="bg-white flex min-h-screen dark:bg-base-900 ">
24
+ <div class="flex grow items-center justify-center mx-auto px-4 relative">
25
+ <div class="w-full sm:w-96">
26
+ {% block content %}{% endblock %}
27
+ </div>
28
+
29
+ {% include "unfold/helpers/unauthenticated_header.html" %}
30
+ </div>
31
+
32
+ {% if login_image %}
33
+ <div class="bg-cover grow hidden max-w-3xl xl:max-w-4xl xl:block" style="background-image: url('{{ login_image }}')">
34
+ </div>
35
+ {% endif %}
36
+ </div>
37
+ {% endblock %}
@@ -0,0 +1,28 @@
1
+
2
+ <div class="max-w-2xl relative">
3
+ {% if widget.prefix %}
4
+ <span class="absolute left-3 top-0 bottom-0 flex items-center justify-center">
5
+ {{ widget.prefix }}
6
+ </span>
7
+ {% endif %}
8
+
9
+ {% if widget.prefix_icon %}
10
+ <span class="material-symbols-outlined absolute left-3 top-0 bottom-0 flex items-center justify-center text-base-400 dark:text-base-500">
11
+ {{ widget.prefix_icon }}
12
+ </span>
13
+ {% endif %}
14
+
15
+ {% include "django/forms/widgets/input.html" %}
16
+
17
+ {% if widget.suffix %}
18
+ <span class="absolute right-3 top-0 bottom-0 flex items-center justify-center">
19
+ {{ widget.suffix }}
20
+ </span>
21
+ {% endif %}
22
+
23
+ {% if widget.suffix_icon %}
24
+ <span class="material-symbols-outlined absolute right-3 top-0 bottom-0 flex items-center justify-center text-base-400 dark:text-base-500">
25
+ {{ widget.suffix_icon }}
26
+ </span>
27
+ {% endif %}
28
+ </div>
@@ -6,10 +6,11 @@ from django import template
6
6
  from django.contrib.admin.helpers import AdminForm, Fieldset
7
7
  from django.contrib.admin.views.main import PAGE_VAR, ChangeList
8
8
  from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
9
+ from django.core.paginator import Paginator
9
10
  from django.db.models import Model
10
11
  from django.db.models.options import Options
11
12
  from django.forms import BoundField, CheckboxSelectMultiple, Field
12
- from django.http import HttpRequest
13
+ from django.http import HttpRequest, QueryDict
13
14
  from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
14
15
  from django.template.base import NodeList, Parser, Token, token_kwargs
15
16
  from django.template.loader import render_to_string
@@ -574,3 +575,32 @@ def changeform_condition(field: BoundField) -> BoundField:
574
575
  @register.simple_tag
575
576
  def infinite_paginator_url(cl, i):
576
577
  return cl.get_query_string({PAGE_VAR: i})
578
+
579
+
580
+ @register.simple_tag
581
+ def elided_page_range(
582
+ paginator: Paginator, number: int
583
+ ) -> Optional[list[Union[int, str]]]:
584
+ if not paginator or not number:
585
+ return None
586
+
587
+ return paginator.get_elided_page_range(number=number)
588
+
589
+
590
+ @register.simple_tag(takes_context=True)
591
+ def querystring_params(
592
+ context: RequestContext, query_key: str, query_value: str
593
+ ) -> str:
594
+ request = context.get("request")
595
+ result = QueryDict(mutable=True)
596
+
597
+ for key, values in request.GET.lists():
598
+ if key == query_key:
599
+ continue
600
+
601
+ for value in values:
602
+ result[key] = value
603
+
604
+ result[query_key] = query_value
605
+
606
+ return result.urlencode()
unfold/widgets.py CHANGED
@@ -288,7 +288,47 @@ FILE_CLASSES = [
288
288
  ]
289
289
 
290
290
 
291
- class UnfoldAdminTextInputWidget(AdminTextInputWidget):
291
+ class UnfoldPrefixSuffixMixin:
292
+ def get_context(self, name, value, attrs):
293
+ widget = {}
294
+
295
+ if "prefix" in self.attrs:
296
+ widget["prefix"] = self.attrs["prefix"]
297
+ del self.attrs["prefix"]
298
+
299
+ if "prefix_icon" in self.attrs:
300
+ widget["prefix_icon"] = self.attrs["prefix_icon"]
301
+ self.attrs["class"] = " ".join([self.attrs["class"], "pl-9"])
302
+ del self.attrs["prefix_icon"]
303
+
304
+ if "suffix" in self.attrs:
305
+ widget["suffix"] = self.attrs["suffix"]
306
+ del self.attrs["suffix"]
307
+
308
+ if "suffix_icon" in self.attrs:
309
+ widget["suffix_icon"] = self.attrs["suffix_icon"]
310
+ self.attrs["class"] = " ".join([self.attrs["class"], "pr-9"])
311
+ del self.attrs["suffix_icon"]
312
+
313
+ widget.update(
314
+ {
315
+ "name": name,
316
+ "is_hidden": self.is_hidden,
317
+ "required": self.is_required,
318
+ "value": self.format_value(value),
319
+ "attrs": self.build_attrs(self.attrs, attrs),
320
+ "template_name": self.template_name,
321
+ }
322
+ )
323
+
324
+ return {
325
+ "widget": widget,
326
+ }
327
+
328
+
329
+ class UnfoldAdminTextInputWidget(UnfoldPrefixSuffixMixin, AdminTextInputWidget):
330
+ template_name = "unfold/widgets/text.html"
331
+
292
332
  def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
293
333
  super().__init__(
294
334
  attrs={