django-unfold 0.25.0__py3-none-any.whl → 0.26.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/METADATA +51 -6
  2. {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/RECORD +36 -27
  3. unfold/admin.py +11 -3
  4. unfold/contrib/import_export/forms.py +17 -0
  5. unfold/contrib/import_export/templates/admin/import_export/change_form.html +10 -0
  6. unfold/contrib/import_export/templates/admin/import_export/export.html +38 -3
  7. unfold/contrib/import_export/templates/admin/import_export/import_form.html +8 -12
  8. unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +1 -1
  9. unfold/contrib/inlines/__init__.py +0 -0
  10. unfold/contrib/inlines/admin.py +141 -0
  11. unfold/contrib/inlines/apps.py +6 -0
  12. unfold/contrib/inlines/checks.py +18 -0
  13. unfold/contrib/inlines/forms.py +43 -0
  14. unfold/forms.py +6 -0
  15. unfold/static/unfold/css/simplebar.css +230 -0
  16. unfold/static/unfold/css/styles.css +1 -1
  17. unfold/static/unfold/js/simplebar.js +10 -0
  18. unfold/styles.css +9 -1
  19. unfold/templates/admin/app_list.html +1 -1
  20. unfold/templates/admin/change_form.html +11 -9
  21. unfold/templates/admin/change_list_results.html +2 -2
  22. unfold/templates/admin/edit_inline/stacked.html +6 -6
  23. unfold/templates/admin/edit_inline/tabular.html +3 -3
  24. unfold/templates/admin/includes/fieldset.html +1 -1
  25. unfold/templates/unfold/helpers/app_list.html +1 -1
  26. unfold/templates/unfold/helpers/display_header.html +11 -8
  27. unfold/templates/unfold/helpers/field.html +20 -6
  28. unfold/templates/unfold/helpers/fieldsets_tabs.html +4 -4
  29. unfold/templates/unfold/helpers/form_label.html +1 -1
  30. unfold/templates/unfold/layouts/skeleton.html +2 -0
  31. unfold/templates/unfold/widgets/foreign_key_raw_id.html +21 -0
  32. unfold/templates/unfold/widgets/textarea.html +1 -7
  33. unfold/templates/unfold/widgets/textarea_expandable.html +7 -0
  34. unfold/widgets.py +36 -3
  35. unfold/contrib/import_export/admin.py +0 -37
  36. {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/LICENSE.md +0 -0
  37. {django_unfold-0.25.0.dist-info → django_unfold-0.26.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
 
@@ -21,13 +21,13 @@
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 %}
@@ -13,7 +13,7 @@
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 mb-8 {% if not stacked %}border border-gray-200 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800{% endif %}">
17
17
  {% for line in fieldset %}
18
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
19
  {% for field in line %}
@@ -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 %}
@@ -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>
@@ -0,0 +1,21 @@
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 }}" title="{{ link_title }}">
6
+ <span class="material-symbols-outlined text-sm">search</span>
7
+ </a>
8
+ {% endif %}
9
+ </div>
10
+
11
+ {% if link_label %}
12
+ <strong class="mt-2 font-medium text-xs">
13
+ {% if link_url %}
14
+ <a href="{{ link_url }}" class="underline text-primary-500">
15
+ {{ link_label }}
16
+ </a>
17
+ {% else %}
18
+ {{ link_label }}
19
+ {% endif %}
20
+ </strong>
21
+ {% endif %}
@@ -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)