django-unfold 0.67.0__py3-none-any.whl → 0.68.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.67.0.dist-info → django_unfold-0.68.0.dist-info}/METADATA +8 -5
- {django_unfold-0.67.0.dist-info → django_unfold-0.68.0.dist-info}/RECORD +39 -36
- unfold/admin.py +73 -13
- unfold/components.py +2 -2
- unfold/contrib/filters/admin/choice_filters.py +13 -1
- unfold/contrib/filters/admin/mixins.py +3 -3
- unfold/contrib/filters/admin/numeric_filters.py +6 -6
- unfold/contrib/forms/widgets.py +5 -5
- unfold/contrib/inlines/admin.py +3 -3
- unfold/contrib/inlines/forms.py +5 -4
- unfold/dataclasses.py +13 -13
- unfold/datasets.py +69 -0
- unfold/decorators.py +19 -19
- unfold/fields.py +3 -5
- unfold/forms.py +19 -7
- unfold/mixins/action_model_admin.py +11 -10
- unfold/mixins/base_model_admin.py +6 -6
- unfold/sites.py +14 -17
- unfold/static/unfold/css/styles.css +1 -1
- unfold/static/unfold/js/app.js +3 -1
- unfold/styles.css +21 -16
- unfold/templates/admin/change_form.html +5 -1
- unfold/templates/admin/change_list_results.html +10 -62
- unfold/templates/admin/edit_inline/stacked.html +1 -1
- unfold/templates/admin/search_form.html +5 -3
- unfold/templates/unfold/helpers/change_list_headers.html +65 -0
- unfold/templates/unfold/helpers/dataset.html +19 -0
- unfold/templates/unfold/helpers/edit_inline/tabular_field.html +1 -1
- unfold/templates/unfold/helpers/empty_results.html +6 -4
- unfold/templates/unfold/helpers/field_readonly_value_file.html +1 -1
- unfold/templates/unfold/helpers/tab_items.html +6 -0
- unfold/templatetags/unfold.py +18 -13
- unfold/templatetags/unfold_list.py +64 -8
- unfold/typing.py +5 -6
- unfold/utils.py +9 -9
- unfold/views.py +15 -1
- unfold/widgets.py +30 -29
- {django_unfold-0.67.0.dist-info → django_unfold-0.68.0.dist-info}/WHEEL +0 -0
- {django_unfold-0.67.0.dist-info → django_unfold-0.68.0.dist-info}/licenses/LICENSE.md +0 -0
unfold/static/unfold/js/app.js
CHANGED
@@ -17,7 +17,9 @@ const sortRecords = (e) => {
|
|
17
17
|
const orderingField = e.from.dataset.orderingField;
|
18
18
|
|
19
19
|
const weightInputs = Array.from(
|
20
|
-
e.from.querySelectorAll(
|
20
|
+
e.from.querySelectorAll(
|
21
|
+
`.has_original input[name$=-${orderingField}], td.field-${orderingField} input[name$=-${orderingField}]`
|
22
|
+
)
|
21
23
|
);
|
22
24
|
|
23
25
|
weightInputs.forEach((input, index) => {
|
unfold/styles.css
CHANGED
@@ -420,6 +420,10 @@ td .inline-deletelink {
|
|
420
420
|
@apply py-1;
|
421
421
|
}
|
422
422
|
|
423
|
+
.select2-container .select2-results__option--highlighted[aria-selected] {
|
424
|
+
@apply !bg-base-100 dark:!bg-base-800;
|
425
|
+
}
|
426
|
+
|
423
427
|
.select2-container.select2-container--open .select2-dropdown {
|
424
428
|
@apply bg-white border border-base-200 rounded-default shadow-xs dark:border-base-700 dark:bg-base-900;
|
425
429
|
}
|
@@ -505,13 +509,17 @@ fieldset details[open] > summary:after {
|
|
505
509
|
/*******************************************************
|
506
510
|
Calendar
|
507
511
|
*******************************************************/
|
512
|
+
.calendarbox .calendar {
|
513
|
+
@apply min-h-[320px];
|
514
|
+
}
|
515
|
+
|
508
516
|
.calendarbox,
|
509
517
|
.clockbox {
|
510
|
-
@apply bg-white border border-base-200 rounded-default shadow-xs
|
518
|
+
@apply bg-white border border-base-200 rounded-default shadow-xs w-80 z-50 dark:bg-base-800 dark:border-base-700 fixed! left-1/2! top-1/2! -translate-x-1/2 -translate-y-1/2;
|
511
519
|
}
|
512
520
|
|
513
521
|
.calendar caption {
|
514
|
-
@apply font-
|
522
|
+
@apply font-semibold mb-3 py-3 text-important;
|
515
523
|
}
|
516
524
|
|
517
525
|
.calendar table {
|
@@ -519,7 +527,7 @@ fieldset details[open] > summary:after {
|
|
519
527
|
}
|
520
528
|
|
521
529
|
.calendar table th {
|
522
|
-
@apply font-
|
530
|
+
@apply font-semibold text-center text-important text-xs;
|
523
531
|
}
|
524
532
|
|
525
533
|
.calendar table td {
|
@@ -527,7 +535,7 @@ fieldset details[open] > summary:after {
|
|
527
535
|
}
|
528
536
|
|
529
537
|
.calendar table td a {
|
530
|
-
@apply block flex h-8 items-center justify-center rounded-full
|
538
|
+
@apply block flex h-8 items-center justify-center rounded-full w-8 dark:text-base-300;
|
531
539
|
}
|
532
540
|
|
533
541
|
.calendar table td a:hover {
|
@@ -543,7 +551,7 @@ fieldset details[open] > summary:after {
|
|
543
551
|
}
|
544
552
|
|
545
553
|
.calendar-shortcuts a {
|
546
|
-
@apply border border-base-200
|
554
|
+
@apply border border-base-200 font-medium leading-none mx-1 px-2 py-2 rounded-default shadow-xs text-center text-base-500 text-xs w-1/3 dark:border-base-700 dark:text-base-300 ;
|
547
555
|
}
|
548
556
|
|
549
557
|
.calendar-cancel {
|
@@ -554,28 +562,25 @@ fieldset details[open] > summary:after {
|
|
554
562
|
@apply absolute block ml-2 mt-2 left-0 text-[0px] top-0;
|
555
563
|
}
|
556
564
|
|
557
|
-
.calendarnav-next
|
558
|
-
.calendarnav-previous
|
559
|
-
@apply border border-base-200 flex h-7 items-center justify-center material-symbols-outlined rounded-full
|
565
|
+
.calendarnav-next,
|
566
|
+
.calendarnav-previous {
|
567
|
+
@apply after:border after:border-base-200 after:flex after:h-7 after:items-center after:justify-center after:material-symbols-outlined after:rounded-full after:transition-all after:w-7 dark:after:bg-base-800 dark:after:border-base-700;
|
568
|
+
}
|
560
569
|
|
570
|
+
.calendarnav-previous:after {
|
561
571
|
content: "navigate_before";
|
562
572
|
display: flex;
|
563
573
|
}
|
564
574
|
|
565
|
-
.calendarnav-next:
|
566
|
-
|
567
|
-
|
575
|
+
.calendarnav-next:after {
|
576
|
+
content: "navigate_next";
|
577
|
+
display: flex;
|
568
578
|
}
|
569
579
|
|
570
580
|
.calendarnav-next {
|
571
581
|
@apply absolute block mr-2 mt-2 right-0 text-[0px] top-0;
|
572
582
|
}
|
573
583
|
|
574
|
-
.calendarnav-next:after {
|
575
|
-
content: "navigate_next";
|
576
|
-
display: flex;
|
577
|
-
}
|
578
|
-
|
579
584
|
/*******************************************************
|
580
585
|
Timepicker
|
581
586
|
*******************************************************/
|
@@ -34,7 +34,7 @@
|
|
34
34
|
{% include adminform.model_admin.change_form_outer_before_template %}
|
35
35
|
{% endif %}
|
36
36
|
|
37
|
-
<form {% if adminform.model_admin.conditional_fields %}x-data='{{ adminform|changeform_data }}'{% endif %} {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}"{%
|
37
|
+
<form {% if adminform.model_admin.conditional_fields %}x-data='{{ adminform|changeform_data }}'{% endif %} {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}"{% endif %} x-bind:action="activeTab && activeTab !== 'general' ? window.location.pathname + window.location.search + '#' + activeTab : window.location.pathname + window.location.search" method="post" id="{{ opts.model_name }}_form" {% if adminform.model_admin.warn_unsaved_form %}class="warn-unsaved-form"{% endif %} novalidate>
|
38
38
|
{% csrf_token %}
|
39
39
|
|
40
40
|
{% if adminform.model_admin.change_form_before_template %}
|
@@ -99,6 +99,10 @@
|
|
99
99
|
{% endif %}
|
100
100
|
</form>
|
101
101
|
|
102
|
+
{% for dataset in datasets %}
|
103
|
+
{{ dataset.contents }}
|
104
|
+
{% endfor %}
|
105
|
+
|
102
106
|
{% if adminform.model_admin.change_form_outer_after_template %}
|
103
107
|
{% include adminform.model_admin.change_form_outer_after_template %}
|
104
108
|
{% endif %}
|
@@ -8,69 +8,11 @@
|
|
8
8
|
|
9
9
|
{% if results %}
|
10
10
|
<div class="{% if cl.search_fields or cl.has_filters %}lg:rounded-b-default{% else %}lg:rounded-default{% endif %} -mx-1 px-1 overflow-x-auto lg:border lg:border-base-200 lg:mx-0 lg:px-0 lg:shadow-xs 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
|
-
<table id="result_list" class="block border-base-200 border-spacing-none border-separate w-full lg:table">
|
12
|
-
|
13
|
-
<tr>
|
14
|
-
{% if cl.model_admin.list_sections|length > 0 %}
|
15
|
-
<th></th>
|
16
|
-
{% endif %}
|
17
|
-
|
18
|
-
{% for header in result_headers %}
|
19
|
-
<th class="align-middle font-semibold py-2 text-left text-font-important-light dark:text-font-important-dark whitespace-nowrap {{ header.class_attrib }} {% if "action-toggle" in header.text and forloop.counter == 1 %}lg:px-3 lg:w-10{% else %}hidden px-3 lg:table-cell{% endif %}" scope="col">
|
20
|
-
<div class="flex items-center">
|
21
|
-
<div class="text">
|
22
|
-
{% if header.sortable %}
|
23
|
-
<a href="{{ header.url_primary }}">
|
24
|
-
{{ header.text|capfirst }}
|
25
|
-
</a>
|
26
|
-
{% else %}
|
27
|
-
{% if "action-toggle" in header.text and forloop.counter == 1 %}
|
28
|
-
<label class="flex flex-row items-center gap-2">
|
29
|
-
{{ header.text|capfirst }}
|
30
|
-
|
31
|
-
<span class="block font-normal lg:hidden">
|
32
|
-
{% trans "Select all rows"%}
|
33
|
-
</span>
|
34
|
-
</label>
|
35
|
-
{% else %}
|
36
|
-
<span>
|
37
|
-
{{ header.text|capfirst }}
|
38
|
-
</span>
|
39
|
-
{% endif %}
|
40
|
-
{% endif %}
|
41
|
-
</div>
|
42
|
-
|
43
|
-
{% if header.sortable %}
|
44
|
-
{% if header.sort_priority > 0 %}
|
45
|
-
<div class="sortoptions flex items-center ml-2">
|
46
|
-
<a href="{{ header.url_toggle }}" class="flex items-center leading-none text-base-400 hover:text-base-500 dark:text-base-500 dark:hover:text-base-400 toggle {% if header.ascending %}ascending{% else %}descending{% endif %}" title="{% translate "Toggle sorting" %}">
|
47
|
-
{% if header.ascending %}
|
48
|
-
<span class="block material-symbols-outlined">arrow_circle_down</span>
|
49
|
-
{% else %}
|
50
|
-
<span class="block material-symbols-outlined">arrow_circle_up</span>
|
51
|
-
{% endif %}
|
52
|
-
</a>
|
53
|
-
|
54
|
-
<a class="sortremove flex items-center leading-none ml-1 text-base-400 dark:text-base-500 transition-all hover:text-red-700 dark:hover:text-red-500" href="{{ header.url_remove }}" title="{% translate "Remove from sorting" %}">
|
55
|
-
<span class="block material-symbols-outlined">cancel</span>
|
56
|
-
</a>
|
57
|
-
</div>
|
58
|
-
|
59
|
-
{% if num_sorted_fields > 1 %}
|
60
|
-
<span class="sortpriority font-medium ml-2 text-xs" title="{% blocktranslate with priority_number=header.sort_priority %}Sorting priority: {{ priority_number }}{% endblocktranslate %}">
|
61
|
-
{{ header.sort_priority }}
|
62
|
-
</span>
|
63
|
-
{% endif %}
|
64
|
-
{% endif %}
|
65
|
-
{% endif %}
|
66
|
-
</div>
|
67
|
-
</th>
|
68
|
-
{% endfor %}
|
69
|
-
</tr>
|
70
|
-
</thead>
|
11
|
+
<table id="result_list" class="block border-base-200 border-spacing-none border-separate w-full lg:table" {% if cl.model_admin.ordering_field %}x-sort.ghost x-on:end="sortRecords"{% endif %} data-ordering-field="{{ cl.model_admin.ordering_field }}">
|
12
|
+
{% include 'unfold/helpers/change_list_headers.html' %}
|
71
13
|
|
72
14
|
{% for result in results %}
|
73
|
-
<tbody class="block relative lg:table-row-group lg:hover:shadow-raised lg:dark:hover:shadow-raised-dark lg:hover:z-20 {% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}" x-data="{rowOpen: false}">
|
15
|
+
<tbody class="block relative lg:table-row-group lg:hover:shadow-raised lg:dark:hover:shadow-raised-dark lg:hover:z-20 {% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}" x-data="{rowOpen: false}" {% if cl.model_admin.ordering_field %}x-sort:item{% endif %}>
|
74
16
|
{% if result.form and result.form.non_field_errors %}
|
75
17
|
<tr>
|
76
18
|
<td class="text-left" colspan="{{ result|length }}">
|
@@ -80,6 +22,12 @@
|
|
80
22
|
{% endif %}
|
81
23
|
|
82
24
|
<tr class="block border border-base-200 mb-3 relative rounded-default shadow-xs lg:table-row lg:border-none lg:mb-0 lg:rounded-none lg:shadow-none dark:border-base-800">
|
25
|
+
{% if cl.model_admin.ordering_field %}
|
26
|
+
<td class="align-middle cursor-move flex border-b border-base-200 font-normal px-2.5 py-2 relative text-left before:font-semibold before:text-font-important-light before:block before:capitalize before:content-[attr(data-label)] before:mr-auto lg:before:hidden lg:border-b-0 lg:border-t lg:pl-3 lg:pr-0 lg:py-3 lg:table-cell dark:border-base-800 dark:lg:border-base-800 dark:before:text-font-important-dark lg:w-px" x-sort:handle>
|
27
|
+
<span class="material-symbols-outlined align-middle cursor-move">drag_indicator</span>
|
28
|
+
</td>
|
29
|
+
{% endif %}
|
30
|
+
|
83
31
|
{% if cl.model_admin.list_sections|length > 0 %}
|
84
32
|
<td class="align-middle cursor-pointer flex border-b border-base-200 font-normal px-2.5 py-2 relative text-left before:font-semibold before:text-font-important-light before:block before:capitalize before:content-[attr(data-label)] before:mr-auto lg:before:hidden lg:border-b-0 lg:border-t lg:pl-3 lg:pr-0 lg:py-3 lg:table-cell dark:border-base-800 dark:lg:border-base-800 dark:before:text-font-important-dark lg:w-px"
|
85
33
|
data-label="{% trans "Expand row" %}"
|
@@ -100,7 +48,7 @@
|
|
100
48
|
|
101
49
|
{% if cl.model_admin.list_sections|length > 0 %}
|
102
50
|
<tr class="block mb-3 relative z-30 lg:table-row" x-show="rowOpen">
|
103
|
-
<td colspan="
|
51
|
+
<td colspan="100%" class="border bg-base-200/10 block border-base-200 relative rounded-default p-3 lg:shadow-inner lg:border-0 lg:border-t lg:rounded-none lg:table-cell dark:border-base-800">
|
104
52
|
<div class="absolute bg-primary-600 h-full hidden left-0 top-0 w-0.5 lg:block"></div>
|
105
53
|
|
106
54
|
<div class="grid gap-3 {{ cl.model_admin.list_sections_classes }}">
|
@@ -34,7 +34,7 @@
|
|
34
34
|
|
35
35
|
{% if inline_admin_formset.opts.ordering_field %}
|
36
36
|
{% if inline_admin_form.original %}
|
37
|
-
<span class="material-symbols-outlined cursor-
|
37
|
+
<span class="material-symbols-outlined cursor-move" x-sort:handle>drag_indicator</span>
|
38
38
|
{% else %}
|
39
39
|
<span class="-mr-2" x-sort:handle></span>
|
40
40
|
{% endif %}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
{% if cl.search_fields %}
|
4
4
|
<div id="toolbar">
|
5
|
-
<form id="changelist-search" method="get" role="search"
|
5
|
+
<form {% if not cl.is_dataset %}id="changelist-search" x-data="searchForm()"{% endif %} method="get" role="search">
|
6
6
|
<div class="bg-white border border-base-200 flex flex-row items-center px-3 rounded-default relative shadow-xs w-full focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-primary-600 lg:w-96 dark:bg-base-900 dark:border-base-700">
|
7
7
|
<button type="submit" class="flex items-center focus:outline-hidden" id="searchbar-submit">
|
8
8
|
<span class="material-symbols-outlined md-18 text-base-400 dark:text-base-500">search</span>
|
@@ -14,10 +14,12 @@
|
|
14
14
|
class="grow font-medium min-w-0 overflow-hidden p-2 placeholder-font-subtle-light truncate focus:outline-hidden dark:bg-base-900 dark:placeholder-font-subtle-dark dark:text-font-default-dark"
|
15
15
|
name="{{ search_var }}"
|
16
16
|
value="{{ cl.query }}"
|
17
|
-
id="searchbar"
|
17
|
+
{% if not cl.is_dataset %}id="searchbar"{% endif %}
|
18
18
|
placeholder="{% if cl.search_help_text %}{{ cl.search_help_text }}{% else %}{% trans "Type to search" %}{% endif %}" />
|
19
19
|
|
20
|
-
{%
|
20
|
+
{% if not cl.is_dataset %}
|
21
|
+
{% include "unfold/helpers/shortcut.html" with shortcut="/" %}
|
22
|
+
{% endif %}
|
21
23
|
</div>
|
22
24
|
|
23
25
|
{% for pair in cl.filter_params.items %}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
{% load i18n %}
|
2
|
+
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
{% if cl.model_admin.ordering_field %}
|
6
|
+
<th></th>
|
7
|
+
{% endif %}
|
8
|
+
|
9
|
+
{% if cl.model_admin.list_sections|length > 0 %}
|
10
|
+
<th></th>
|
11
|
+
{% endif %}
|
12
|
+
|
13
|
+
{% for header in result_headers %}
|
14
|
+
<th class="align-middle font-semibold py-2 text-left text-font-important-light dark:text-font-important-dark whitespace-nowrap {{ header.class_attrib }} {% if "action-toggle" in header.text and forloop.counter == 1 %}lg:px-3 lg:w-10{% else %}hidden px-3 lg:table-cell{% endif %}" scope="col">
|
15
|
+
<div class="flex items-center">
|
16
|
+
<div class="text">
|
17
|
+
{% if header.sortable %}
|
18
|
+
<a href="{{ header.url_primary }}">
|
19
|
+
{{ header.text|capfirst }}
|
20
|
+
</a>
|
21
|
+
{% else %}
|
22
|
+
{% if "action-toggle" in header.text and forloop.counter == 1 %}
|
23
|
+
<label class="flex flex-row items-center gap-2">
|
24
|
+
{{ header.text|capfirst }}
|
25
|
+
|
26
|
+
<span class="block font-normal lg:hidden">
|
27
|
+
{% trans "Select all rows"%}
|
28
|
+
</span>
|
29
|
+
</label>
|
30
|
+
{% else %}
|
31
|
+
<span>
|
32
|
+
{{ header.text|capfirst }}
|
33
|
+
</span>
|
34
|
+
{% endif %}
|
35
|
+
{% endif %}
|
36
|
+
</div>
|
37
|
+
|
38
|
+
{% if header.sortable %}
|
39
|
+
{% if header.sort_priority > 0 %}
|
40
|
+
<div class="sortoptions flex items-center ml-2">
|
41
|
+
<a href="{{ header.url_toggle }}" class="flex items-center leading-none text-base-400 hover:text-base-500 dark:text-base-500 dark:hover:text-base-400 toggle {% if header.ascending %}ascending{% else %}descending{% endif %}" title="{% translate "Toggle sorting" %}">
|
42
|
+
{% if header.ascending %}
|
43
|
+
<span class="block material-symbols-outlined">arrow_circle_down</span>
|
44
|
+
{% else %}
|
45
|
+
<span class="block material-symbols-outlined">arrow_circle_up</span>
|
46
|
+
{% endif %}
|
47
|
+
</a>
|
48
|
+
|
49
|
+
<a class="sortremove flex items-center leading-none ml-1 text-base-400 dark:text-base-500 transition-all hover:text-red-700 dark:hover:text-red-500" href="{{ header.url_remove }}" title="{% translate "Remove from sorting" %}">
|
50
|
+
<span class="block material-symbols-outlined">cancel</span>
|
51
|
+
</a>
|
52
|
+
</div>
|
53
|
+
|
54
|
+
{% if num_sorted_fields > 1 %}
|
55
|
+
<span class="sortpriority font-medium ml-2 text-xs" title="{% blocktranslate with priority_number=header.sort_priority %}Sorting priority: {{ priority_number }}{% endblocktranslate %}">
|
56
|
+
{{ header.sort_priority }}
|
57
|
+
</span>
|
58
|
+
{% endif %}
|
59
|
+
{% endif %}
|
60
|
+
{% endif %}
|
61
|
+
</div>
|
62
|
+
</th>
|
63
|
+
{% endfor %}
|
64
|
+
</tr>
|
65
|
+
</thead>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{% load admin_list unfold_list %}
|
2
|
+
|
3
|
+
<div {% if cl.model_admin.tab %}x-show="activeTab == 'dataset-{{ dataset.model_name }}'"{% endif %}>
|
4
|
+
{% if not cl.model_admin.tab %}
|
5
|
+
<h2 class="inline-heading bg-base-100 font-semibold mb-6 px-4 py-3 rounded-default text-font-important-light @min-[1570px]:-mx-4 dark:bg-white/[.02] dark:text-font-important-dark">
|
6
|
+
{{ dataset.model_verbose_name|capfirst }}
|
7
|
+
</h2>
|
8
|
+
{% endif %}
|
9
|
+
|
10
|
+
{% if cl.search_fields %}
|
11
|
+
<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-default">
|
12
|
+
{% unfold_search_form cl %}
|
13
|
+
</div>
|
14
|
+
{% endif %}
|
15
|
+
|
16
|
+
{% unfold_result_list cl %}
|
17
|
+
|
18
|
+
{% pagination cl %}
|
19
|
+
</div>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
{% if forloop.parentloop.counter == 1 and forloop.counter == 1 %}
|
3
3
|
{% if inline_admin_formset.opts.ordering_field %}
|
4
4
|
{% if inline_admin_form.original %}
|
5
|
-
<span class="material-symbols-outlined cursor-
|
5
|
+
<span class="material-symbols-outlined cursor-move" x-sort:handle>drag_indicator</span>
|
6
6
|
{% else %}
|
7
7
|
<span class="-mr-3" x-sort:handle></span>
|
8
8
|
{% endif %}
|
@@ -3,10 +3,12 @@
|
|
3
3
|
{% url cl.opts|admin_urlname:"add" as add_url %}
|
4
4
|
{% blocktranslate with name=cl.opts.verbose_name asvar title %}Add {{ name }}{% endblocktranslate %}
|
5
5
|
|
6
|
-
<div class="bg-white border border-base-200 flex flex-col items-center px-8
|
7
|
-
|
8
|
-
<
|
9
|
-
|
6
|
+
<div class="bg-white border border-base-200 flex flex-col items-center px-8 shadow-xs dark:bg-base-900 dark:border-base-800 {% if cl.search_fields %}rounded-b-default{% else %}rounded-default{% endif %} {% if cl.is_dataset %}py-16{% else %}py-24{% endif %}">
|
7
|
+
{% if not cl.is_dataset %}
|
8
|
+
<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">
|
9
|
+
<span class="material-symbols-outlined text-base-500 text-5xl! dark:text-base-400">inbox</span>
|
10
|
+
</div>
|
11
|
+
{% endif %}
|
10
12
|
|
11
13
|
<h2 class="font-semibold mb-1 text-xl text-font-important-light tracking-tight dark:text-font-important-dark">
|
12
14
|
{% trans "No results found" %}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
{% with url=field.url %}
|
4
4
|
{% if not url %}
|
5
|
-
<span class="flex items-center"
|
5
|
+
<span class="flex items-center">-</span>
|
6
6
|
{% elif field.is_image %}
|
7
7
|
<a href="{{ url }}" target="_blank" class="block max-w-48">
|
8
8
|
<img src="{{ url }}" alt="{% trans 'Image preview' %}" class="block rounded-default" />
|
@@ -24,6 +24,12 @@
|
|
24
24
|
{% endif %}
|
25
25
|
</a>
|
26
26
|
{% endfor %}
|
27
|
+
|
28
|
+
{% for dataset in datasets_list %}
|
29
|
+
<a href="#dataset-{{ dataset.model_name }}" x-on:click="activeTab = 'dataset-{{ dataset.model_name }}'" x-bind:class="{'active': activeTab == 'dataset-{{ dataset.model_name }}'}">
|
30
|
+
{{ dataset.model_verbose_name|capfirst }}
|
31
|
+
</a>
|
32
|
+
{% endfor %}
|
27
33
|
{% endif %}
|
28
34
|
</nav>
|
29
35
|
{% endif %}
|
unfold/templatetags/unfold.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
from collections.abc import Iterable, Mapping
|
3
|
-
from typing import Any
|
3
|
+
from typing import Any
|
4
4
|
|
5
5
|
from django import template
|
6
6
|
from django.contrib.admin.helpers import AdminForm, Fieldset
|
@@ -27,7 +27,7 @@ register = Library()
|
|
27
27
|
|
28
28
|
|
29
29
|
def _get_tabs_list(
|
30
|
-
context: RequestContext, page: str, opts:
|
30
|
+
context: RequestContext, page: str, opts: Options | None = None
|
31
31
|
) -> list:
|
32
32
|
tabs_list = []
|
33
33
|
page_id = None
|
@@ -63,9 +63,9 @@ def _get_tabs_list(
|
|
63
63
|
|
64
64
|
|
65
65
|
@register.simple_tag(name="tab_list", takes_context=True)
|
66
|
-
def tab_list(context: RequestContext, page: str, opts:
|
66
|
+
def tab_list(context: RequestContext, page: str, opts: Options | None = None) -> str:
|
67
67
|
inlines_list = []
|
68
|
-
|
68
|
+
datasets_list = []
|
69
69
|
data = {
|
70
70
|
"nav_global": context.get("nav_global"),
|
71
71
|
"actions_detail": context.get("actions_detail"),
|
@@ -85,6 +85,13 @@ def tab_list(context: RequestContext, page: str, opts: Optional[Options] = None)
|
|
85
85
|
if len(inlines_list) > 0:
|
86
86
|
data["inlines_list"] = inlines_list
|
87
87
|
|
88
|
+
for dataset in context.get("datasets", []):
|
89
|
+
if dataset and hasattr(dataset, "tab"):
|
90
|
+
datasets_list.append(dataset)
|
91
|
+
|
92
|
+
if len(datasets_list) > 0:
|
93
|
+
data["datasets_list"] = datasets_list
|
94
|
+
|
88
95
|
return render_to_string(
|
89
96
|
"unfold/helpers/tab_list.html",
|
90
97
|
request=context["request"],
|
@@ -150,7 +157,7 @@ class CaptureNode(Node):
|
|
150
157
|
self.varname = varname
|
151
158
|
self.silent = silent
|
152
159
|
|
153
|
-
def render(self, context: dict[str, Any]) ->
|
160
|
+
def render(self, context: dict[str, Any]) -> str | SafeText:
|
154
161
|
output = self.nodelist.render(context)
|
155
162
|
context[self.varname] = output
|
156
163
|
if self.silent:
|
@@ -219,7 +226,7 @@ class RenderComponentNode(template.Node):
|
|
219
226
|
self,
|
220
227
|
template_name: str,
|
221
228
|
nodelist: NodeList,
|
222
|
-
extra_context:
|
229
|
+
extra_context: dict | None = None,
|
223
230
|
include_context: bool = False,
|
224
231
|
*args,
|
225
232
|
**kwargs,
|
@@ -308,7 +315,7 @@ def do_component(parser: Parser, token: Token) -> str:
|
|
308
315
|
|
309
316
|
|
310
317
|
@register.filter
|
311
|
-
def add_css_class(field: Field, classes:
|
318
|
+
def add_css_class(field: Field, classes: list | tuple) -> Field:
|
312
319
|
if type(classes) in (list, tuple):
|
313
320
|
classes = " ".join(classes)
|
314
321
|
|
@@ -329,8 +336,8 @@ def preserve_changelist_filters(context: Context) -> dict[str, dict[str, str]]:
|
|
329
336
|
"""
|
330
337
|
Generate hidden input fields to preserve filters for POST forms.
|
331
338
|
"""
|
332
|
-
request:
|
333
|
-
changelist:
|
339
|
+
request: HttpRequest | None = context.get("request")
|
340
|
+
changelist: ChangeList | None = context.get("cl")
|
334
341
|
|
335
342
|
if not request or not changelist:
|
336
343
|
return {"params": {}}
|
@@ -348,7 +355,7 @@ def preserve_changelist_filters(context: Context) -> dict[str, dict[str, str]]:
|
|
348
355
|
@register.simple_tag(takes_context=True)
|
349
356
|
def element_classes(context: Context, key: str) -> str:
|
350
357
|
if key in context.get("element_classes", {}):
|
351
|
-
if isinstance(context["element_classes"][key],
|
358
|
+
if isinstance(context["element_classes"][key], list | tuple):
|
352
359
|
return " ".join(context["element_classes"][key])
|
353
360
|
|
354
361
|
return context["element_classes"][key]
|
@@ -585,9 +592,7 @@ def infinite_paginator_url(cl, i):
|
|
585
592
|
|
586
593
|
|
587
594
|
@register.simple_tag
|
588
|
-
def elided_page_range(
|
589
|
-
paginator: Paginator, number: int
|
590
|
-
) -> Optional[list[Union[int, str]]]:
|
595
|
+
def elided_page_range(paginator: Paginator, number: int) -> list[int | str] | None:
|
591
596
|
if not paginator or not number:
|
592
597
|
return None
|
593
598
|
|