django-unfold 0.26.0__py3-none-any.whl → 0.28.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.
- {django_unfold-0.26.0.dist-info → django_unfold-0.28.0.dist-info}/METADATA +54 -5
- {django_unfold-0.26.0.dist-info → django_unfold-0.28.0.dist-info}/RECORD +33 -26
- unfold/admin.py +6 -157
- unfold/contrib/forms/templates/unfold/forms/array.html +31 -0
- unfold/contrib/forms/widgets.py +58 -3
- unfold/contrib/import_export/forms.py +21 -7
- unfold/contrib/import_export/templates/admin/import_export/change_list_export.html +5 -0
- unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
- unfold/dataclasses.py +2 -0
- unfold/decorators.py +3 -0
- unfold/fields.py +208 -0
- unfold/static/unfold/css/styles.css +1 -1
- unfold/templates/admin/change_form.html +0 -2
- unfold/templates/admin/edit_inline/tabular.html +4 -6
- unfold/templates/admin/includes/fieldset.html +2 -32
- unfold/templates/admin/login.html +4 -0
- unfold/templates/admin/submit_line.html +1 -1
- unfold/templates/unfold/helpers/attrs.html +1 -0
- unfold/templates/unfold/helpers/display_header.html +1 -1
- unfold/templates/unfold/helpers/field_readonly.html +1 -3
- unfold/templates/unfold/helpers/field_readonly_value.html +1 -0
- unfold/templates/unfold/helpers/fieldset_row.html +53 -0
- unfold/templates/unfold/helpers/search_results.html +10 -10
- unfold/templates/unfold/layouts/base.html +11 -0
- unfold/templates/unfold/layouts/base_simple.html +7 -1
- unfold/templates/unfold/widgets/clearable_file_input.html +1 -1
- unfold/templates/unfold/widgets/clearable_file_input_small.html +1 -1
- unfold/templates/unfold/widgets/foreign_key_raw_id.html +7 -13
- unfold/utils.py +21 -1
- unfold/views.py +18 -6
- unfold/widgets.py +12 -1
- {django_unfold-0.26.0.dist-info → django_unfold-0.28.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.26.0.dist-info → django_unfold-0.28.0.dist-info}/WHEEL +0 -0
@@ -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 dark:border-gray-800 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>
|
@@ -1,13 +1,13 @@
|
|
1
1
|
{% if results %}
|
2
|
-
<ul class="absolute bg-white border left-0 mt-12 right-0 rounded top-0 shadow-sm text-sm dark:bg-gray-800 dark:border-gray-700">
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
<ul class="absolute bg-white border left-0 mt-12 right-0 rounded top-0 shadow-sm text-sm z-10 dark:bg-gray-800 dark:border-gray-700">
|
3
|
+
{% for app in results %}
|
4
|
+
{% for model in app.models %}
|
5
|
+
<li>
|
6
|
+
<a href="{{ model.admin_url }}" class="block group overflow-hidden px-3 py-2 text-gray-500 text-ellipsis whitespace-nowrap hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
7
|
+
{{ app.name }} <span class="align-text-top material-symbols-outlined md-18 text-gray-300 group-hover:text-gray-400 dark:text-gray-600">arrow_right_alt</span> {{ model.name }}
|
8
|
+
</a>
|
9
|
+
</li>
|
10
|
+
{% endfor %}
|
10
11
|
{% endfor %}
|
11
|
-
|
12
|
-
</ul>
|
12
|
+
</ul>
|
13
13
|
{% endif %}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{% extends "unfold/layouts/base_simple.html" %}
|
2
|
+
|
3
|
+
{% block branding %}
|
4
|
+
<h1 id="site-name">
|
5
|
+
<a href="{% url 'admin:index' %}">
|
6
|
+
{{ site_header|default:_('Django administration') }}
|
7
|
+
</a>
|
8
|
+
</h1>
|
9
|
+
{% endblock %}
|
10
|
+
|
11
|
+
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
@@ -15,7 +15,13 @@
|
|
15
15
|
{% include "unfold/helpers/header.html" %}
|
16
16
|
{% endblock %}
|
17
17
|
|
18
|
-
|
18
|
+
{% if not is_popup %}
|
19
|
+
{% spaceless %}
|
20
|
+
{% block breadcrumbs %}{% endblock %}
|
21
|
+
{% endspaceless %}
|
22
|
+
{% endif %}
|
23
|
+
|
24
|
+
<div class="px-4 lg:px-12">
|
19
25
|
<div id="content" class="container mx-auto {% block coltype %}colM{% endblock %}">
|
20
26
|
{% block content %}
|
21
27
|
{% block object-tools %}{% endblock %}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{% load i18n %}
|
2
2
|
|
3
3
|
{% if widget.attrs.accept == 'image/*' and widget.is_initial %}
|
4
|
-
<div class="
|
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>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
<input type="text" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium px-3 py-2 text-ellipsis dark:bg-gray-900 {% if widget.value %}text-gray-500 dark:text-gray-400{% else %}text-gray-300 dark:text-gray-400{% endif %}">
|
18
18
|
|
19
19
|
<div class="flex flex-none items-center leading-none self-stretch">
|
20
|
-
<input id="{{ widget.name }}" type="{{ widget.type }}" name="{{ widget.name }}" class="
|
20
|
+
<input id="{{ widget.name }}" type="{{ widget.type }}" name="{{ widget.name }}" class="{{ widget.file_input_class }}" {% include "django/forms/widgets/attrs.html" %} />
|
21
21
|
|
22
22
|
<label for="{{ widget.name }}" class="cursor-pointer text-gray-400 px-3 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200">
|
23
23
|
<span class="material-symbols-outlined">file_upload</span>
|
@@ -2,20 +2,14 @@
|
|
2
2
|
{% include 'django/forms/widgets/input.html' %}
|
3
3
|
|
4
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 }}"
|
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
6
|
<span class="material-symbols-outlined text-sm">search</span>
|
7
7
|
</a>
|
8
8
|
{% endif %}
|
9
|
-
</div>
|
10
9
|
|
11
|
-
{% if link_label %}
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
{% else %}
|
18
|
-
{{ link_label }}
|
19
|
-
{% endif %}
|
20
|
-
</strong>
|
21
|
-
{% endif %}
|
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>
|
unfold/utils.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import datetime
|
2
2
|
import decimal
|
3
3
|
import json
|
4
|
-
from typing import Any, Iterable, List
|
4
|
+
from typing import Any, Iterable, List, Optional
|
5
5
|
|
6
6
|
from django.db import models
|
7
7
|
from django.template.loader import render_to_string
|
@@ -121,3 +121,23 @@ def hex_to_rgb(hex_color: str) -> List[int]:
|
|
121
121
|
b = int(hex_color[4:6], 16)
|
122
122
|
|
123
123
|
return (r, g, b)
|
124
|
+
|
125
|
+
|
126
|
+
def prettify_json(data: Any) -> Optional[str]:
|
127
|
+
try:
|
128
|
+
from pygments import highlight
|
129
|
+
from pygments.formatters import HtmlFormatter
|
130
|
+
from pygments.lexers import JsonLexer
|
131
|
+
except ImportError:
|
132
|
+
return None
|
133
|
+
|
134
|
+
def format_response(response: str, theme: str) -> str:
|
135
|
+
formatter = HtmlFormatter(style=theme, noclasses=True, nobackground=True)
|
136
|
+
return highlight(response, JsonLexer(), formatter)
|
137
|
+
|
138
|
+
response = json.dumps(data, sort_keys=True, indent=4)
|
139
|
+
|
140
|
+
return mark_safe(
|
141
|
+
f'<div class="block dark:hidden">{format_response(response, "colorful")}</div>'
|
142
|
+
f'<div class="hidden dark:block">{format_response(response, "monokai")}</div>'
|
143
|
+
)
|
unfold/views.py
CHANGED
@@ -10,13 +10,25 @@ class UnfoldModelAdminViewMixin(PermissionRequiredMixin):
|
|
10
10
|
Prepares views to be displayed in admin
|
11
11
|
"""
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
model_admin = None
|
14
|
+
|
15
|
+
def __init__(self, model_admin, **kwargs):
|
16
|
+
self.model_admin = model_admin
|
17
|
+
super().__init__(**kwargs)
|
18
|
+
|
19
|
+
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
20
|
+
if not hasattr(self, "model_admin"):
|
15
21
|
raise UnfoldException(
|
16
22
|
"UnfoldModelAdminViewMixin was not provided with 'model_admin' argument"
|
17
23
|
)
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
|
25
|
+
if not hasattr(self, "title"):
|
26
|
+
raise UnfoldException(
|
27
|
+
"UnfoldModelAdminViewMixin was not provided with 'title' attribute"
|
28
|
+
)
|
29
|
+
|
30
|
+
return super().get_context_data(
|
31
|
+
**kwargs,
|
32
|
+
**self.model_admin.admin_site.each_context(self.request),
|
33
|
+
**{"title": self.title},
|
21
34
|
)
|
22
|
-
return context_data
|
unfold/widgets.py
CHANGED
@@ -288,7 +288,18 @@ class FileFieldMixin:
|
|
288
288
|
def get_context(self, name, value, attrs):
|
289
289
|
widget = super().get_context(name, value, attrs)
|
290
290
|
widget["widget"].update(
|
291
|
-
{
|
291
|
+
{
|
292
|
+
"class": " ".join([*CHECKBOX_CLASSES, *["form-check-input"]]),
|
293
|
+
"file_input_class": " ".join(
|
294
|
+
[
|
295
|
+
self.attrs.get("class", ""),
|
296
|
+
*[
|
297
|
+
"opacity-0",
|
298
|
+
"pointer-events-none",
|
299
|
+
],
|
300
|
+
]
|
301
|
+
),
|
302
|
+
}
|
292
303
|
)
|
293
304
|
return widget
|
294
305
|
|
File without changes
|
File without changes
|