django-unfold 0.49.1__py3-none-any.whl → 0.50.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.50.0.dist-info/METADATA +81 -0
- {django_unfold-0.49.1.dist-info → django_unfold-0.50.0.dist-info}/RECORD +29 -26
- {django_unfold-0.49.1.dist-info → django_unfold-0.50.0.dist-info}/WHEEL +1 -1
- unfold/admin.py +4 -0
- unfold/contrib/filters/admin/dropdown_filters.py +1 -0
- unfold/contrib/filters/admin/mixins.py +10 -4
- unfold/contrib/filters/forms.py +2 -2
- unfold/dataclasses.py +3 -0
- unfold/decorators.py +8 -0
- unfold/enums.py +10 -0
- unfold/mixins/action_model_admin.py +4 -0
- unfold/settings.py +1 -0
- unfold/sites.py +3 -0
- unfold/static/admin/js/inlines.js +18 -5
- unfold/static/unfold/css/styles.css +1 -1
- unfold/static/unfold/js/select2.init.js +29 -0
- unfold/styles.css +1 -1
- unfold/templates/admin/change_list.html +16 -16
- unfold/templates/admin/change_list_results.html +2 -34
- unfold/templates/admin/filter.html +1 -1
- unfold/templates/unfold/helpers/empty_results.html +35 -0
- unfold/templates/unfold/helpers/tab_action.html +24 -19
- unfold/templates/unfold/helpers/tab_actions.html +19 -0
- unfold/templates/unfold/helpers/tab_items.html +47 -0
- unfold/templates/unfold/helpers/tab_list.html +3 -66
- unfold/templates/unfold/layouts/skeleton.html +1 -2
- unfold/templatetags/unfold.py +70 -0
- unfold/views.py +14 -1
- django_unfold-0.49.1.dist-info/METADATA +0 -75
- unfold/contrib/filters/admin.py +0 -619
- {django_unfold-0.49.1.dist-info → django_unfold-0.50.0.dist-info}/LICENSE.md +0 -0
@@ -6,10 +6,39 @@
|
|
6
6
|
$.each(this, function (i, element) {
|
7
7
|
$(element).select2();
|
8
8
|
});
|
9
|
+
|
10
|
+
return this;
|
11
|
+
};
|
12
|
+
|
13
|
+
$.fn.djangoAdminSelect2 = function () {
|
14
|
+
$.each(this, function (i, element) {
|
15
|
+
$(element).select2({
|
16
|
+
ajax: {
|
17
|
+
data: (params) => {
|
18
|
+
return {
|
19
|
+
term: params.term,
|
20
|
+
page: params.page,
|
21
|
+
app_label: element.dataset.appLabel,
|
22
|
+
model_name: element.dataset.modelName,
|
23
|
+
field_name: element.dataset.fieldName,
|
24
|
+
};
|
25
|
+
},
|
26
|
+
},
|
27
|
+
});
|
28
|
+
});
|
9
29
|
return this;
|
10
30
|
};
|
11
31
|
|
12
32
|
$(function () {
|
13
33
|
$(".unfold-admin-autocomplete.admin-autocomplete").djangoCustomSelect2();
|
34
|
+
|
35
|
+
$(".admin-autocomplete")
|
36
|
+
.not(".unfold-admin-autocomplete")
|
37
|
+
.not("[name*=__prefix__]")
|
38
|
+
.djangoAdminSelect2();
|
39
|
+
});
|
40
|
+
|
41
|
+
document.addEventListener("formset:added", (event) => {
|
42
|
+
$(event.target).find(".admin-autocomplete").djangoAdminSelect2();
|
14
43
|
});
|
15
44
|
}
|
unfold/styles.css
CHANGED
@@ -181,7 +181,7 @@ table tr.selected th {
|
|
181
181
|
Selector
|
182
182
|
*******************************************************/
|
183
183
|
.selector {
|
184
|
-
@apply flex flex-col flex-grow items-center
|
184
|
+
@apply flex flex-col flex-grow items-center md:flex-row;
|
185
185
|
}
|
186
186
|
|
187
187
|
.selector select {
|
@@ -61,22 +61,6 @@
|
|
61
61
|
{{ cl.formset.non_form_errors }}
|
62
62
|
{% endif %}
|
63
63
|
|
64
|
-
<div class="flex flex-col gap-4 mb-4 sm:flex-row">
|
65
|
-
{% block search %}
|
66
|
-
{% search_form cl %}
|
67
|
-
{% endblock %}
|
68
|
-
|
69
|
-
{% block filters %}
|
70
|
-
{% if cl.has_filters %}
|
71
|
-
<a class="{% if cl.has_active_filters %}bg-primary-600 border-primary-600 text-white{% else %}bg-white border-base-200 dark:bg-base-900 dark:border-base-700{% endif %} border cursor-pointer flex font-medium group items-center px-3 py-2 rounded shadow-sm text-sm lg:ml-auto md:mt-0 {% if not cl.model_admin.list_filter_sheet %}2xl:hidden{% endif %}" x-on:click="filterOpen = true" x-on:keydown.escape.window="filterOpen = false">
|
72
|
-
{% trans "Filters" %}
|
73
|
-
|
74
|
-
<span class="material-symbols-outlined md-18 ml-auto -mr-1 pl-4 {% if cl.has_active_filters %}text-white{% else %}text-base-400 group-hover:text-base-500 dark:group-hover:text-base-400 dark:text-base-500{% endif %}">filter_list</span>
|
75
|
-
</a>
|
76
|
-
{% endif %}
|
77
|
-
{% endblock %}
|
78
|
-
</div>
|
79
|
-
|
80
64
|
<div class="flex -mx-4 module{% if cl.has_filters %} filtered{% endif %}" id="changelist" x-data="{ changeListWidth: 0 }">
|
81
65
|
<div class="changelist-form-container flex flex-row flex-grow gap-6 min-w-0 px-4">
|
82
66
|
<div class="flex-grow min-w-0 lg:pb-16" x-resize="changeListWidth = $width">
|
@@ -90,6 +74,22 @@
|
|
90
74
|
{% include cl.model_admin.list_before_template %}
|
91
75
|
{% endif %}
|
92
76
|
|
77
|
+
<div class="flex flex-col gap-4 mb-4 sm:flex-row empty:hidden lg:border lg:border-base-200 lg:dark:border-base-800 lg:-mb-8 lg:p-3 lg:pb-11 lg:rounded-t">
|
78
|
+
{% block search %}
|
79
|
+
{% search_form cl %}
|
80
|
+
{% endblock %}
|
81
|
+
|
82
|
+
{% block filters %}
|
83
|
+
{% if cl.has_filters %}
|
84
|
+
<a class="{% if cl.has_active_filters %}bg-primary-600 border-primary-600 text-white{% else %}bg-white border-base-200 hover:text-primary-600 dark:bg-base-900 dark:border-base-700 dark:hover:text-primary-500{% endif %} border cursor-pointer flex font-medium gap-2 group items-center px-3 py-2 rounded shadow-sm text-sm lg:ml-auto md:mt-0 {% if not cl.model_admin.list_filter_sheet %}2xl:hidden{% endif %}" x-on:click="filterOpen = true" x-on:keydown.escape.window="filterOpen = false">
|
85
|
+
{% trans "Filters" %}
|
86
|
+
|
87
|
+
<span class="material-symbols-outlined md-18 ml-auto">filter_list</span>
|
88
|
+
</a>
|
89
|
+
{% endif %}
|
90
|
+
{% endblock %}
|
91
|
+
</div>
|
92
|
+
|
93
93
|
<form id="changelist-form" class="group" method="post"{% if cl.formset and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>
|
94
94
|
{% csrf_token %}
|
95
95
|
|
@@ -7,7 +7,7 @@
|
|
7
7
|
{% endif %}
|
8
8
|
|
9
9
|
{% if results %}
|
10
|
-
<div class="-mx-1 px-1 overflow-x-auto lg:border lg:border-base-200 lg:mx-0 lg:px-0 lg:rounded lg:shadow-sm lg:dark:border-base-800 {% if cl.model_admin.list_horizontal_scrollbar_top %}simplebar-horizontal-scrollbar-top{% endif %}" data-simplebar data-simplebar-auto-hide="false">
|
10
|
+
<div class="-mx-1 px-1 overflow-x-auto lg:border lg:border-base-200 lg:mx-0 lg:px-0 lg:rounded lg:shadow-sm lg:dark:border-base-800 lg:bg-white lg:dark:bg-base-900 {% if cl.model_admin.list_horizontal_scrollbar_top %}simplebar-horizontal-scrollbar-top{% endif %}" data-simplebar data-simplebar-auto-hide="false">
|
11
11
|
<table id="result_list" class="block border-base-200 border-spacing-none border-separate w-full lg:table">
|
12
12
|
<thead>
|
13
13
|
<tr>
|
@@ -86,37 +86,5 @@
|
|
86
86
|
</table>
|
87
87
|
</div>
|
88
88
|
{% else %}
|
89
|
-
{%
|
90
|
-
{% blocktranslate with name=cl.opts.verbose_name asvar title %}Add {{ name }}{% endblocktranslate %}
|
91
|
-
|
92
|
-
<div class="border border-base-200 flex flex-col items-center px-8 py-24 rounded shadow-sm dark:border-base-800">
|
93
|
-
<div class="bg-base-100 flex h-24 items-center justify-center mb-8 rounded-full w-24 dark:bg-base-800">
|
94
|
-
<span class="material-symbols-outlined text-base-500 !text-5xl dark:text-base-400">inbox</span>
|
95
|
-
</div>
|
96
|
-
|
97
|
-
<h2 class="font-semibold mb-1 text-xl text-font-important-light dark:text-font-important-dark">
|
98
|
-
{% trans "No results found" %}
|
99
|
-
</h2>
|
100
|
-
|
101
|
-
<p class="mb-6 text-center">
|
102
|
-
{% trans "This page yielded into no results. Create a new item or reset your filters." %}
|
103
|
-
</p>
|
104
|
-
|
105
|
-
{% if has_add_permission or cl.has_filters %}
|
106
|
-
<div class="flex flex-col gap-4 justify-center w-full lg:flex-row">
|
107
|
-
{% if has_add_permission %}
|
108
|
-
<a href="{% add_preserved_filters add_url is_popup to_field %}" class="bg-primary-600 flex flex-row font-medium gap-2 items-center h-9.5 justify-center px-3 py-2 rounded text-white w-full lg:w-auto">
|
109
|
-
<span class="material-symbols-outlined text-white">add</span> {{ title }}
|
110
|
-
</a>
|
111
|
-
{% endif %}
|
112
|
-
|
113
|
-
|
114
|
-
{% if cl.has_filters %}
|
115
|
-
<a href="{{ cl.clear_all_filters_qs }}" class="border border-base-200 flex flex-row font-medium gap-2 group/button h-9.5 items-center justify-center px-3 py-2 rounded transition-all w-full hover:text-primary-600 lg:w-auto dark:border-base-700 dark:hover:bg-base-900 dark:hover:text-primary-500">
|
116
|
-
<span class="material-symbols-outlined group-hover/button:text-primary-600 dark:group-hover/button:text-base-500 text-base-400 dark:text-base-500">filter_list_off</span> {% trans "Reset filters" %}
|
117
|
-
</a>
|
118
|
-
{% endif %}
|
119
|
-
</div>
|
120
|
-
{% endif %}
|
121
|
-
</div>
|
89
|
+
{% include 'unfold/helpers/empty_results.html' %}
|
122
90
|
{% endif %}
|
@@ -11,7 +11,7 @@
|
|
11
11
|
{% endif %}
|
12
12
|
{% endfor %}
|
13
13
|
|
14
|
-
{% if spec|class_name == "BooleanFieldListFilter" %}
|
14
|
+
{% if spec|class_name == "BooleanFieldListFilter" or spec.horizontal %}
|
15
15
|
<ul class="dark:bg-base-900 border border-base-200 flex min-w-20 rounded shadow-sm text-font-default-light dark:border-base-700 dark:text-font-default-dark w-full">
|
16
16
|
{% for choice in choices %}
|
17
17
|
<li class="basis-1/3 border-r border-base-200 flex-grow truncate last:border-r-0 dark:border-base-700 {% if choice.selected %}font-semibold text-primary-600 dark:text-primary-500 {% else %}hover:text-base-700 dark:hover:text-base-200{% endif %}">
|
@@ -0,0 +1,35 @@
|
|
1
|
+
{% load admin_urls i18n %}
|
2
|
+
|
3
|
+
{% url cl.opts|admin_urlname:"add" as add_url %}
|
4
|
+
{% blocktranslate with name=cl.opts.verbose_name asvar title %}Add {{ name }}{% endblocktranslate %}
|
5
|
+
|
6
|
+
<div class="bg-white border border-base-200 flex flex-col items-center px-8 py-24 rounded shadow-sm dark:bg-base-900 dark:border-base-800">
|
7
|
+
<div class="border border-base-300 border-dashed flex h-24 items-center justify-center mb-8 rounded-full w-24 dark:border-base-700">
|
8
|
+
<span class="material-symbols-outlined text-base-500 !text-5xl dark:text-base-400">inbox</span>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<h2 class="font-semibold mb-1 text-xl text-font-important-light tracking-tight dark:text-font-important-dark">
|
12
|
+
{% trans "No results found" %}
|
13
|
+
</h2>
|
14
|
+
|
15
|
+
<p class="mb-6 text-center">
|
16
|
+
{% trans "This page yielded into no results. Create a new item or reset your filters." %}
|
17
|
+
</p>
|
18
|
+
|
19
|
+
{% if has_add_permission or cl.has_filters %}
|
20
|
+
<div class="flex flex-col gap-4 justify-center w-full lg:flex-row">
|
21
|
+
{% if has_add_permission %}
|
22
|
+
<a href="{% add_preserved_filters add_url is_popup to_field %}" class="bg-primary-600 flex flex-row font-medium gap-2 items-center h-9.5 justify-center px-3 py-2 rounded text-white w-full lg:w-auto">
|
23
|
+
<span class="material-symbols-outlined text-white">add</span> {{ title }}
|
24
|
+
</a>
|
25
|
+
{% endif %}
|
26
|
+
|
27
|
+
|
28
|
+
{% if cl.has_filters %}
|
29
|
+
<a href="{{ cl.clear_all_filters_qs }}" class="border border-base-200 flex flex-row font-medium gap-2 group/button h-9.5 items-center justify-center px-3 py-2 rounded w-full hover:text-primary-600 lg:w-auto dark:border-base-700 dark:hover:bg-base-900 dark:hover:text-primary-500">
|
30
|
+
<span class="material-symbols-outlined ml-1">filter_list_off</span> {% trans "Reset filters" %}
|
31
|
+
</a>
|
32
|
+
{% endif %}
|
33
|
+
</div>
|
34
|
+
{% endif %}
|
35
|
+
</div>
|
@@ -1,10 +1,13 @@
|
|
1
1
|
{% load unfold %}
|
2
2
|
|
3
|
-
<li class="
|
4
|
-
<a {% if link %}href="{{ link }}"{% endif %}class="cursor-pointer flex items-center gap-2 px-
|
5
|
-
|
3
|
+
<li class="{% action_item_classes action %}" {% if not link %}x-data="{actionDropdownOpen: false}" x-ref="actionDropdown{{ action.method_name }}"{% endif %}>
|
4
|
+
<a {% if link %}href="{{ link }}"{% endif %}class="cursor-pointer flex items-center gap-2 px-3 py-2 text-left whitespace-nowrap" {% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=action.attrs %}
|
5
|
+
{% if not link %}
|
6
|
+
x-on:click="actionDropdownOpen = !actionDropdownOpen"
|
7
|
+
x-bind:class="{'{% if action.variant.value == "default" %}text-primary-600 dark:text-primary-500{% endif %}': actionDropdownOpen }"
|
8
|
+
{% endif %}>
|
6
9
|
{% if action.icon %}
|
7
|
-
<span class="material-symbols-outlined
|
10
|
+
<span class="material-symbols-outlined">
|
8
11
|
{{ action.icon }}
|
9
12
|
</span>
|
10
13
|
{% endif %}
|
@@ -12,27 +15,29 @@
|
|
12
15
|
{{ title }}
|
13
16
|
|
14
17
|
{% if not link %}
|
15
|
-
<span class="material-symbols-outlined -
|
18
|
+
<span class="material-symbols-outlined ml-auto rotate-90">
|
16
19
|
chevron_right
|
17
20
|
</span>
|
18
21
|
{% endif %}
|
19
22
|
</a>
|
20
23
|
|
21
24
|
{% if not link %}
|
22
|
-
<
|
23
|
-
{
|
24
|
-
|
25
|
-
{% if
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
<template x-teleport="body">
|
26
|
+
<nav x-anchor.bottom-end.offset.4="$refs.actionDropdown{{ action.method_name }}" class="absolute bg-white border flex flex-col -mr-px py-1 right-0 rounded shadow-lg top-10 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-transition x-show="actionDropdownOpen" x-on:click.outside="actionDropdownOpen = false">
|
27
|
+
{% for item in action.items %}
|
28
|
+
<a href="{{ item.path }}" class="flex items-center font-normal gap-2 max-h-[30px] mx-1 px-3 py-2 rounded text-left hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"{% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=action.attrs %}>
|
29
|
+
{% if item.icon %}
|
30
|
+
<span class="material-symbols-outlined">
|
31
|
+
{{ item.icon }}
|
32
|
+
</span>
|
33
|
+
{% endif %}
|
30
34
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
<span class="grow truncate">
|
36
|
+
{{ item.title }}
|
37
|
+
</span>
|
38
|
+
</a>
|
39
|
+
{% endfor %}
|
40
|
+
</nav>
|
41
|
+
</template>
|
37
42
|
{% endif %}
|
38
43
|
</li>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{% if actions_list or actions_detail or actions_items or nav_global %}
|
2
|
+
<ul class="flex flex-col font-medium mb-4 ml-auto mt-2 shadow-sm md:flex-row md:mb-2 md:mt-0 max-md:w-full">
|
3
|
+
{% if actions_items %}
|
4
|
+
{{ actions_items }}
|
5
|
+
{% endif %}
|
6
|
+
|
7
|
+
{% if nav_global %}
|
8
|
+
{{ nav_global }}
|
9
|
+
{% endif %}
|
10
|
+
|
11
|
+
{% for action in actions_list %}
|
12
|
+
{% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
|
13
|
+
{% endfor %}
|
14
|
+
|
15
|
+
{% for action in actions_detail %}
|
16
|
+
{% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
|
17
|
+
{% endfor %}
|
18
|
+
</ul>
|
19
|
+
{% endif %}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
{% load i18n %}
|
2
|
+
|
3
|
+
{% if inlines_list or tabs_list %}
|
4
|
+
<ul class="border rounded flex flex-col max-md:w-full md:flex-row md:border-b-0 md:border-t-0 md:border-l-0 md:border-r-0 dark:border-base-800">
|
5
|
+
{% for item in tabs_list %}
|
6
|
+
{% if item.has_permission %}
|
7
|
+
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
|
8
|
+
<a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}{% if item.inline %}#{{ item.inline }}{% endif %}"
|
9
|
+
class="block px-3 py-2 md:py-4 md:px-0 dark:border-base-800 {% if item.active and not item.inline %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}"
|
10
|
+
{% if item.inline %}
|
11
|
+
x-on:click="activeTab = '{{ item.inline }}'"
|
12
|
+
x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ item.inline }}'}"
|
13
|
+
{% endif %}
|
14
|
+
>
|
15
|
+
{{ item.title }}
|
16
|
+
</a>
|
17
|
+
</li>
|
18
|
+
{% endif %}
|
19
|
+
{% endfor %}
|
20
|
+
|
21
|
+
{% if inlines_list %}
|
22
|
+
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
|
23
|
+
<a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
|
24
|
+
href="#general"
|
25
|
+
x-on:click="activeTab = 'general'"
|
26
|
+
x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == 'general', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != 'general'}">
|
27
|
+
{% trans "General" %}
|
28
|
+
</a>
|
29
|
+
</li>
|
30
|
+
|
31
|
+
{% for inline in inlines_list %}
|
32
|
+
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
|
33
|
+
<a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
|
34
|
+
href="#{{ inline.opts.verbose_name|slugify }}"
|
35
|
+
x-on:click="activeTab = '{{ inline.opts.verbose_name|slugify }}'"
|
36
|
+
x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ inline.opts.verbose_name|slugify }}', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != '{{ inline.opts.verbose_name|slugify }}'}">
|
37
|
+
{% if inline.formset.max_num == 1 %}
|
38
|
+
{{ inline.opts.verbose_name|capfirst }}
|
39
|
+
{% else %}
|
40
|
+
{{ inline.opts.verbose_name_plural|capfirst }}
|
41
|
+
{% endif %}
|
42
|
+
</a>
|
43
|
+
</li>
|
44
|
+
{% endfor %}
|
45
|
+
{% endif %}
|
46
|
+
</ul>
|
47
|
+
{% endif %}
|
@@ -2,73 +2,10 @@
|
|
2
2
|
|
3
3
|
{% if not is_popup %}
|
4
4
|
{% if tabs_list or inlines_list or actions_list or actions_detail or actions_items or nav_global %}
|
5
|
-
<div class="flex items-start flex-col mb-4
|
6
|
-
{%
|
7
|
-
<ul class="border rounded flex flex-col w-full md:flex-row md:border-b-0 md:border-t-0 md:border-l-0 md:border-r-0 dark:border-base-800">
|
8
|
-
{% for item in tabs_list %}
|
9
|
-
{% if item.has_permission %}
|
10
|
-
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
|
11
|
-
<a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}"
|
12
|
-
class="block px-3 py-2 md:py-4 md:px-0 dark:border-base-800 {% if item.active and not item.inline %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}"
|
13
|
-
{% if item.inline %}
|
14
|
-
x-on:click="activeTab = '{{ item.inline }}'"
|
15
|
-
x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ item.inline }}'}"
|
16
|
-
{% endif %}
|
17
|
-
>
|
18
|
-
{{ item.title }}
|
19
|
-
</a>
|
20
|
-
</li>
|
21
|
-
{% endif %}
|
22
|
-
{% endfor %}
|
5
|
+
<div class="flex items-start flex-col mb-4 md:border-b dark:md:border-base-800 md:border-l-0 md:flex-row md:items-center">
|
6
|
+
{% include "unfold/helpers/tab_items.html" %}
|
23
7
|
|
24
|
-
|
25
|
-
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
|
26
|
-
<a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
|
27
|
-
href="#general"
|
28
|
-
x-on:click="activeTab = 'general'"
|
29
|
-
x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == 'general', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != 'general'}">
|
30
|
-
{% trans "General" %}
|
31
|
-
</a>
|
32
|
-
</li>
|
33
|
-
|
34
|
-
{% for inline in inlines_list %}
|
35
|
-
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
|
36
|
-
<a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
|
37
|
-
href="#{{ inline.opts.verbose_name|slugify }}"
|
38
|
-
x-on:click="activeTab = '{{ inline.opts.verbose_name|slugify }}'"
|
39
|
-
x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ inline.opts.verbose_name|slugify }}', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != '{{ inline.opts.verbose_name|slugify }}'}">
|
40
|
-
{% if inline.formset.max_num == 1 %}
|
41
|
-
{{ inline.opts.verbose_name|capfirst }}
|
42
|
-
{% else %}
|
43
|
-
{{ inline.opts.verbose_name_plural|capfirst }}
|
44
|
-
{% endif %}
|
45
|
-
</a>
|
46
|
-
</li>
|
47
|
-
{% endfor %}
|
48
|
-
{% endif %}
|
49
|
-
</ul>
|
50
|
-
{% endif %}
|
51
|
-
|
52
|
-
{% if actions_list or actions_detail or actions_items or nav_global %}
|
53
|
-
<ul class="border flex flex-col font-medium mb-4 mt-2 rounded shadow-sm md:flex-row md:mb-2 md:mt-0 dark:border-base-700 max-md:w-full">
|
54
|
-
{% for action in actions_list %}
|
55
|
-
{% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
|
56
|
-
{% endfor %}
|
57
|
-
|
58
|
-
{% for action in actions_detail %}
|
59
|
-
{% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
|
60
|
-
{% endfor %}
|
61
|
-
|
62
|
-
{% if actions_items %}
|
63
|
-
{{ actions_items }}
|
64
|
-
{% endif %}
|
65
|
-
|
66
|
-
{% if nav_global %}
|
67
|
-
{{ nav_global }}
|
68
|
-
{% endif %}
|
69
|
-
</ul>
|
70
|
-
{% endif %}
|
8
|
+
{% include "unfold/helpers/tab_actions.html" %}
|
71
9
|
</div>
|
72
10
|
{% endif %}
|
73
|
-
|
74
11
|
{% endif %}
|
@@ -14,9 +14,8 @@
|
|
14
14
|
|
15
15
|
<!DOCTYPE html>
|
16
16
|
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" dir="{{ LANGUAGE_BIDI|yesno:"rtl,ltr,auto" }}" {% if theme %}class="{{ theme }}"{% endif %} x-data="{ adminTheme: {% if theme %}'{{ theme }}'{% else %}$persist('auto').as('adminTheme'){% endif %} }" x-bind:class="{'dark': adminTheme === 'dark' || (adminTheme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)}" x-cloak>
|
17
|
-
|
18
17
|
<head>
|
19
|
-
<title>{% block title %}{% endblock %}</title>
|
18
|
+
<title>{{ environment_title_prefix|default:"" }} {% block title %}{% endblock %}</title>
|
20
19
|
|
21
20
|
<link href="{% static "unfold/fonts/inter/styles.css" %}" rel="stylesheet">
|
22
21
|
<link href="{% static "unfold/fonts/material-symbols/styles.css" %}" rel="stylesheet">
|
unfold/templatetags/unfold.py
CHANGED
@@ -13,6 +13,8 @@ from django.template.loader import render_to_string
|
|
13
13
|
from django.utils.safestring import SafeText
|
14
14
|
|
15
15
|
from unfold.components import ComponentRegistry
|
16
|
+
from unfold.dataclasses import UnfoldAction
|
17
|
+
from unfold.enums import ActionVariant
|
16
18
|
|
17
19
|
register = Library()
|
18
20
|
|
@@ -416,3 +418,71 @@ def fieldset_line_classes(context: Context) -> str:
|
|
416
418
|
)
|
417
419
|
|
418
420
|
return " ".join(set(classes))
|
421
|
+
|
422
|
+
|
423
|
+
@register.simple_tag(takes_context=True)
|
424
|
+
def action_item_classes(context: Context, action: UnfoldAction) -> str:
|
425
|
+
classes = [
|
426
|
+
"border",
|
427
|
+
"-ml-px",
|
428
|
+
"max-md:first:rounded-t",
|
429
|
+
"max-md:last:rounded-b",
|
430
|
+
"md:first:rounded-l",
|
431
|
+
"md:last:rounded-r",
|
432
|
+
]
|
433
|
+
|
434
|
+
if "variant" not in action:
|
435
|
+
variant = ActionVariant.DEFAULT
|
436
|
+
else:
|
437
|
+
variant = action["variant"]
|
438
|
+
|
439
|
+
if variant == ActionVariant.PRIMARY:
|
440
|
+
classes.extend(
|
441
|
+
[
|
442
|
+
"border-primary-600",
|
443
|
+
"bg-primary-600",
|
444
|
+
"text-white",
|
445
|
+
]
|
446
|
+
)
|
447
|
+
elif variant == ActionVariant.DANGER:
|
448
|
+
classes.extend(
|
449
|
+
[
|
450
|
+
"border-red-600",
|
451
|
+
"bg-red-600",
|
452
|
+
"text-white",
|
453
|
+
]
|
454
|
+
)
|
455
|
+
elif variant == ActionVariant.SUCCESS:
|
456
|
+
classes.extend(
|
457
|
+
[
|
458
|
+
"border-green-600",
|
459
|
+
"bg-green-600",
|
460
|
+
"text-white",
|
461
|
+
]
|
462
|
+
)
|
463
|
+
elif variant == ActionVariant.INFO:
|
464
|
+
classes.extend(
|
465
|
+
[
|
466
|
+
"border-blue-600",
|
467
|
+
"bg-blue-600",
|
468
|
+
"text-white",
|
469
|
+
]
|
470
|
+
)
|
471
|
+
elif variant == ActionVariant.WARNING:
|
472
|
+
classes.extend(
|
473
|
+
[
|
474
|
+
"border-orange-600",
|
475
|
+
"bg-orange-600",
|
476
|
+
"text-white",
|
477
|
+
]
|
478
|
+
)
|
479
|
+
else:
|
480
|
+
classes.extend(
|
481
|
+
[
|
482
|
+
"border-base-200",
|
483
|
+
"hover:text-primary-600",
|
484
|
+
"dark:border-base-700",
|
485
|
+
]
|
486
|
+
)
|
487
|
+
|
488
|
+
return " ".join(set(classes))
|
unfold/views.py
CHANGED
@@ -1,8 +1,21 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
+
import django
|
4
|
+
from django.contrib.admin.views.main import ERROR_FLAG, PAGE_VAR
|
5
|
+
from django.contrib.admin.views.main import ChangeList as BaseChangeList
|
3
6
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
4
7
|
|
5
|
-
from .exceptions import UnfoldException
|
8
|
+
from unfold.exceptions import UnfoldException
|
9
|
+
|
10
|
+
|
11
|
+
class ChangeList(BaseChangeList):
|
12
|
+
def __init__(self, request, *args, **kwargs):
|
13
|
+
super().__init__(request, *args, **kwargs)
|
14
|
+
|
15
|
+
if django.VERSION < (5, 0):
|
16
|
+
self.filter_params = dict(request.GET.lists())
|
17
|
+
self.filter_params.pop(PAGE_VAR, None)
|
18
|
+
self.filter_params.pop(ERROR_FLAG, None)
|
6
19
|
|
7
20
|
|
8
21
|
class UnfoldModelAdminViewMixin(PermissionRequiredMixin):
|
@@ -1,75 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: django-unfold
|
3
|
-
Version: 0.49.1
|
4
|
-
Summary: Modern Django admin theme for seamless interface development
|
5
|
-
License: MIT
|
6
|
-
Keywords: django,admin,tailwind,theme
|
7
|
-
Requires-Python: >=3.9
|
8
|
-
Classifier: Environment :: Web Environment
|
9
|
-
Classifier: Framework :: Django
|
10
|
-
Classifier: Intended Audience :: Developers
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
12
|
-
Classifier: Operating System :: OS Independent
|
13
|
-
Classifier: Programming Language :: Python
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
20
|
-
Requires-Dist: django (>=3.2)
|
21
|
-
Project-URL: Homepage, https://unfoldadmin.com
|
22
|
-
Project-URL: Repository, https://github.com/unfoldadmin/django-unfold
|
23
|
-
Description-Content-Type: text/markdown
|
24
|
-
|
25
|
-

|
26
|
-
|
27
|
-
## Unfold Django Admin Theme
|
28
|
-
|
29
|
-
[](https://pypi.org/project/django-unfold/)
|
30
|
-
[](https://github.com/unfoldadmin/django-unfold/actions?query=workflow%3Arelease)
|
31
|
-

|
32
|
-

|
33
|
-
|
34
|
-
Unfold is a theme for Django admin that incorporates common best practices for building full-fledged admin areas. It is designed to work on top of the default administration provided by Django.
|
35
|
-
|
36
|
-
- **Documentation:** Full docs are available at [unfoldadmin.com](https://unfoldadmin.com?utm_medium=github&utm_source=unfold).
|
37
|
-
- **Unfold:** Demo site is available at [unfoldadmin.com](https://unfoldadmin.com?utm_medium=github&utm_source=unfold).
|
38
|
-
- **Formula:** Repository with demo implementation at [github.com/unfoldadmin/formula](https://github.com/unfoldadmin/formula?utm_medium=github&utm_source=unfold).
|
39
|
-
- **Turbo:** Django & Next.js boilerplate implementing Unfold at [github.com/unfoldadmin/turbo](https://github.com/unfoldadmin/turbo?utm_medium=github&utm_source=unfold).
|
40
|
-
- **Discord:** Join the Unfold community on [Discord](https://discord.gg/9sQj9MEbNz).
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
## Are you using Unfold and need help?
|
45
|
-
|
46
|
-
Have you decided to start using Unfold but don’t have time to make the switch from the native Django admin? [Get in touch with us](https://unfoldadmin.com/consulting?utm_medium=github&utm_source=unfold) and let’s supercharge your development with our expertise.
|
47
|
-
|
48
|
-
## Features
|
49
|
-
|
50
|
-
- **Visual**: Provides a new user interface based on the Tailwind CSS framework.
|
51
|
-
- **Sidebar:** Simplifies the creation of sidebar navigation with icons, collapsibles, and more.
|
52
|
-
- **Dark mode:** Supports both light and dark mode versions.
|
53
|
-
- **Actions:** Offers multiple ways to define actions within different parts of the admin interface.
|
54
|
-
- **Filters:** Custom dropdowns, autocomplete, numeric, datetime, and text fields.
|
55
|
-
- **Dashboard:** Includes helpers for creating custom dashboard pages.
|
56
|
-
- **Components:** Reusable UI components such as cards, buttons, and charts.
|
57
|
-
- **WYSIWYG widget:** Built-in support for WYSIWYG (Trix).
|
58
|
-
- **Array widget:** Built-in widget for `django.contrib.postgres.fields.ArrayField`.
|
59
|
-
- **Inline tabs:** Groups inlines into tab navigation in the change form.
|
60
|
-
- **Model tabs:** Allows defining custom tab navigation for models.
|
61
|
-
- **Fieldset tabs:** Merges multiple fieldsets into tabs in the change form.
|
62
|
-
- **Sortable inlines:** Allows sorting inlines by dragging and dropping.
|
63
|
-
- **Environment label**: Distinguishes between environments by displaying a label.
|
64
|
-
- **Nonrelated inlines**: Displays nonrelated models as inlines in the change form.
|
65
|
-
- **Favicons**: Built-in support for configuring various site favicons.
|
66
|
-
- **Themes:** Allows customization of color scheme, background color, border radius, and more.
|
67
|
-
- **Font colors:** Adjusts font colors for better readability.
|
68
|
-
- **Changeform modes:** Displays fields in compressed mode in the change form.
|
69
|
-
- **Language switcher:** Change language directly from the admin area.
|
70
|
-
- **Parallel admin**: Supports [running the default admin](https://unfoldadmin.com/blog/migrating-django-admin-unfold/?utm_medium=github&utm_source=unfold) alongside Unfold.
|
71
|
-
- **Third party packages:** Default support for multiple popular applications.
|
72
|
-
- **Configuration:** Most basic options can be changed in `settings.py`.
|
73
|
-
- **Dependencies:** Fully based on `django.contrib.admin`.
|
74
|
-
- **VS Code**: Project configuration and development container included.
|
75
|
-
|