django-unfold 0.68.0__py3-none-any.whl → 0.69.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.68.0.dist-info → django_unfold-0.69.0.dist-info}/METADATA +31 -42
  2. {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/RECORD +33 -29
  3. unfold/admin.py +8 -37
  4. unfold/datasets.py +22 -1
  5. unfold/forms.py +22 -15
  6. unfold/mixins/__init__.py +2 -1
  7. unfold/mixins/dataset_model_admin.py +62 -0
  8. unfold/settings.py +1 -0
  9. unfold/sites.py +5 -1
  10. unfold/static/admin/js/actions.js +246 -0
  11. unfold/static/unfold/css/styles.css +2 -2
  12. unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
  13. unfold/templates/admin/actions.html +2 -2
  14. unfold/templates/admin/change_form.html +8 -4
  15. unfold/templates/admin/change_list.html +1 -1
  16. unfold/templates/admin/change_list_results.html +1 -1
  17. unfold/templates/admin/dataset_actions.html +50 -0
  18. unfold/templates/admin/edit_inline/stacked.html +1 -7
  19. unfold/templates/admin/edit_inline/tabular.html +1 -7
  20. unfold/templates/admin/includes/fieldset.html +1 -3
  21. unfold/templates/admin/search_form.html +1 -1
  22. unfold/templates/registration/password_change_done.html +3 -4
  23. unfold/templates/registration/password_change_form.html +10 -6
  24. unfold/templates/unfold/helpers/change_list_actions.html +1 -1
  25. unfold/templates/unfold/helpers/dataset.html +19 -7
  26. unfold/templates/unfold/helpers/fieldsets_tabs.html +9 -11
  27. unfold/templates/unfold/helpers/inline_heading.html +11 -0
  28. unfold/templates/unfold/helpers/tab_items.html +6 -4
  29. unfold/templatetags/unfold.py +47 -70
  30. unfold/templatetags/unfold_list.py +12 -0
  31. unfold/widgets.py +1 -2
  32. {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/WHEEL +0 -0
  33. {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -1,8 +1,8 @@
1
1
  {% load i18n %}
2
2
 
3
- <div id="changelist-actions" class="actions flex flex-col gap-3 text-white sm:flex-row sm:items-center lg:items-center {% if not cl.model_admin.list_fullwidth %}mx-auto{% endif %}" x-bind:style="'width: ' + changeListWidth + 'px'">
3
+ <div id="changelist-actions" class="actions flex flex-col gap-3 text-white sm:flex-row sm:items-center lg:items-center {% if not cl.model_admin.list_fullwidth %}mx-auto{% endif %}" x-bind:style="'width: ' + changeListWidth + 'px'">
4
4
  {% block actions %}
5
- <div class="group primary flex flex-row gap-2 lg:flex-row" x-data="{action: ''}">
5
+ <div class="group primary flex flex-row gap-2 lg:flex-row" x-data="{action: '', selectAcross: 0}">
6
6
  {% block actions-form %}
7
7
  {% for field in action_form %}
8
8
  {% if field.label %}
@@ -43,7 +43,7 @@
43
43
 
44
44
  {% block form_top %}{% endblock %}
45
45
 
46
- <div class="flex flex-col gap-8">
46
+ <div class="flex flex-col gap-6">
47
47
  {% if is_popup %}
48
48
  <input type="hidden" name="{{ is_popup_var }}" value="1">
49
49
  {% endif %}
@@ -99,9 +99,13 @@
99
99
  {% endif %}
100
100
  </form>
101
101
 
102
- {% for dataset in datasets %}
103
- {{ dataset.contents }}
104
- {% endfor %}
102
+ {% if datasets %}
103
+ <div class="flex flex-col">
104
+ {% for dataset in datasets %}
105
+ {{ dataset.contents }}
106
+ {% endfor %}
107
+ </div>
108
+ {% endif %}
105
109
 
106
110
  {% if adminform.model_admin.change_form_outer_after_template %}
107
111
  {% include adminform.model_admin.change_form_outer_after_template %}
@@ -45,7 +45,7 @@
45
45
  {% endif %}
46
46
 
47
47
  <div class="flex -mx-4 module{% if cl.has_filters %} filtered{% endif %}" id="changelist" x-data="{ changeListWidth: 0 }">
48
- <div class="changelist-form-container flex flex-row grow gap-6 min-w-0 px-4">
48
+ <div class="result-list-wrapper changelist-form-container flex flex-row grow gap-6 min-w-0 px-4">
49
49
  <div class="grow min-w-0" x-resize="changeListWidth = $width">
50
50
  {% block date_hierarchy %}
51
51
  {% if cl.date_hierarchy %}
@@ -8,7 +8,7 @@
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" {% if cl.model_admin.ordering_field %}x-sort.ghost x-on:end="sortRecords"{% endif %} data-ordering-field="{{ cl.model_admin.ordering_field }}">
11
+ <table id="result_list" class="result-list 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
12
  {% include 'unfold/helpers/change_list_headers.html' %}
13
13
 
14
14
  {% for result in results %}
@@ -0,0 +1,50 @@
1
+ {% load i18n %}
2
+
3
+ <div class="actions flex flex-col gap-3 order-last lg:order-none lg:flex-row lg:items-center">
4
+ {% if actions_selection_counter %}
5
+ <div class="bg-base-100 flex flex-row h-9.5 items-center px-3 rounded-md shadow-sm truncate dark:bg-white/[.04] lg:bg-transparent dark:lg:!bg-transparent lg:px-0 lg:shadow-none">
6
+ <span class="action-counter text-sm" data-actions-icnt="{{ cl.result_list|length }}">
7
+ {{ selection_note }}
8
+ </span>
9
+
10
+ {% if cl.result_count != cl.result_list|length %}
11
+ <span class="all hidden">
12
+ {{ selection_note_all }}
13
+ </span>
14
+
15
+ {% if not cl.model_admin.list_disable_select_all %}
16
+ <span class="question ml-2 hidden text-primary-600 dark:text-primary-500">
17
+ <a href="#" title="{% translate "Click here to select the objects across all pages" %}">
18
+ {% blocktranslate with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktranslate %}
19
+ </a>
20
+ </span>
21
+
22
+ <span class="clear hidden text-red-600">
23
+ <a href="#">
24
+ {% translate "Clear selection" %}
25
+ </a>
26
+ </span>
27
+ {% endif %}
28
+ {% endif %}
29
+ </div>
30
+ {% endif %}
31
+
32
+ <div class="flex flex-col gap-2 lg:flex-row">
33
+ {% for field in action_form %}
34
+ {% if field.label %}
35
+ <label>
36
+ {{ field.label }}
37
+ {% endif %}
38
+
39
+ {{ field }}
40
+
41
+ {% if field.label %}
42
+ </label>
43
+ {% endif %}
44
+ {% endfor %}
45
+
46
+ <button type="submit" form="dataset-{{ id }}" x-show="action" class="bg-primary-600 cursor-pointer flex font-medium items-center justify-center px-3 py-2 rounded-md text-sm text-white whitespace-nowrap" title="{% translate "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">
47
+ {% trans "Run" %}
48
+ </button>
49
+ </div>
50
+ </div>
@@ -4,13 +4,7 @@
4
4
  <fieldset class="module relative {{ inline_admin_formset.classes }}" aria-labelledby="{{ inline_admin_formset.formset.prefix }}-heading">
5
5
  {% if inline_admin_formset.is_collapsible %}<details><summary>{% endif %}
6
6
 
7
- <h2 id="{{ inline_admin_formset.formset.prefix }}-heading" 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 {% if inline_admin_formset.opts.tab %}hidden{% endif %} {% if inline_admin_formset.is_collapsible %} cursor-pointer{% endif %}">
8
- {% if inline_admin_formset.formset.max_num == 1 %}
9
- {{ inline_admin_formset.opts.verbose_name|capfirst }}
10
- {% else %}
11
- {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
12
- {% endif %}
13
- </h2>
7
+ {% include "unfold/helpers/inline_heading.html" %}
14
8
 
15
9
  {% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
16
10
 
@@ -7,13 +7,7 @@
7
7
  <fieldset class="module relative {{ inline_admin_formset.classes }} min-w-0" aria-labelledby="{{ inline_admin_formset.formset.prefix }}-heading">
8
8
  {% if inline_admin_formset.is_collapsible %}<details><summary>{% endif %}
9
9
 
10
- <h2 id="{{ inline_admin_formset.formset.prefix }}-heading" class="bg-base-100 border border-transparent font-semibold mb-6 px-4 py-3 rounded-default text-font-important-light text-sm @min-[1570px]:-mx-4 dark:bg-white/[.02] dark:text-font-important-dark {% if inline_admin_formset.opts.tab %}hidden{% endif %} {% if inline_admin_formset.is_collapsible %} cursor-pointer{% endif %}">
11
- {% if inline_admin_formset.formset.max_num == 1 %}
12
- {{ inline_admin_formset.opts.verbose_name|capfirst }}
13
- {% else %}
14
- {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
15
- {% endif %}
16
- </h2>
10
+ {% include "unfold/helpers/inline_heading.html" %}
17
11
 
18
12
  {% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
19
13
 
@@ -9,9 +9,7 @@
9
9
  {{ fieldset.name }}
10
10
  </h2>
11
11
  {% else %}
12
- <h2 class="bg-base-100 font-semibold mb-6 px-4 py-3 rounded-default text-font-important-light text-sm @min-[1570px]:-mx-4 dark:bg-white/[.02] dark:text-font-important-dark {% if fieldset.is_collapsible %}cursor-pointer{% endif %}">
13
- {{ fieldset.name }}
14
- </h2>
12
+ {% include "unfold/helpers/inline_heading.html" with title=fieldset.name clickable=fieldset.is_collapsible %}
15
13
  {% endif %}
16
14
  {% endif %}
17
15
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  <input type="text"
12
12
  x-ref="searchInput"
13
- x-on:keydown.window="applyShortcut($event)"
13
+ {% if not cl.is_dataset %}x-on:keydown.window="applyShortcut($event)"{% endif %}
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 }}"
@@ -1,12 +1,11 @@
1
1
  {% extends "admin/base_site.html" %}
2
2
  {% load admin_urls i18n static %}
3
3
 
4
- {% block extrastyle %}{{ block.super }}">{% endblock %}
4
+ {% block extrastyle %}{{ block.super }}{% endblock %}
5
5
 
6
6
  {% block content %}
7
7
  <div id="content-main">
8
- <p class="bg-primary-100 mb-8 text-primary-600 px-3 py-3 rounded-default text-sm">
9
- {% translate 'Your password was changed.' %}
10
- </p>
8
+ {% translate 'Your password was changed.' as message %}
9
+ {% include "unfold/helpers/messages/success.html" with message=message %}
11
10
  </div>
12
11
  {% endblock %}
@@ -19,15 +19,19 @@
19
19
 
20
20
  {% include "unfold/helpers/messages/info.html" with message=message %}
21
21
 
22
- {% include "unfold/helpers/field.html" with field=form.old_password %}
22
+ <fieldset class="fieldset group border border-base-200 mb-6 rounded-default shadow-xs p-3 dark:border-base-800 *:last:mb-0">
23
+ {% include "unfold/helpers/field.html" with field=form.old_password %}
23
24
 
24
- {% include "unfold/helpers/field.html" with field=form.new_password1 %}
25
+ {% include "unfold/helpers/field.html" with field=form.new_password1 %}
25
26
 
26
- {% include "unfold/helpers/field.html" with field=form.new_password2 %}
27
+ {% include "unfold/helpers/field.html" with field=form.new_password2 %}
28
+ </fieldset>
27
29
 
28
- <button type="submit" class="bg-primary-600 border border-transparent font-medium px-3 py-2 rounded-default text-sm text-white">
29
- {% translate 'Change password' %}
30
- </button>
30
+ <div class="flex justify-end">
31
+ <button type="submit" class="bg-primary-600 border border-transparent font-medium px-3 py-2 rounded-default text-sm text-white">
32
+ {% translate 'Change password' %}
33
+ </button>
34
+ </div>
31
35
  </div>
32
36
  </form>
33
37
  </div>
@@ -3,7 +3,7 @@
3
3
  {% if actions_on_top %}
4
4
  {% if cl.search_fields or action_form or cl.has_filters %}
5
5
  <div class="bottom-0 left-0 right-0 hidden bg-primary-600 group-has-[input.action-select:checked]:flex fixed gap-3 lg:h-[64px] items-center p-3 z-50 lg:flex-row dark:bg-primary-500">
6
- <div id="changelist-actions-wrapper" class="grow" {% if not is_popup %}x-bind:class="{'xl:ml-0': !sidebarDesktopOpen, 'xl:ml-72': sidebarDesktopOpen}"{% endif %}>
6
+ <div id="changelist-actions-wrapper" class="grow group changelist-actions" {% if not is_popup %}x-bind:class="{'xl:ml-0': !sidebarDesktopOpen, 'xl:ml-72': sidebarDesktopOpen}"{% endif %}>
7
7
  {% if action_form %}
8
8
  {% admin_actions %}
9
9
  {% endif %}
@@ -1,19 +1,31 @@
1
1
  {% load admin_list unfold_list %}
2
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>
3
+ <div class="result-list-wrapper {% if not tab %}mt-6{% endif %}" x-data="{action: '', selectAcross: '0'}" {% if tab %}x-show="activeTab == 'dataset-{{ dataset.model_name }}'"{% else %}x-show="activeTab == 'general'"{% endif %}>
4
+ {% if not tab %}
5
+ {% include "unfold/helpers/inline_heading.html" with title=dataset.model_verbose_name %}
8
6
  {% endif %}
9
7
 
10
8
  {% 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">
9
+ <div class="flex flex-col gap-4 justify-between 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
10
  {% unfold_search_form cl %}
11
+
12
+ {% unfold_admin_actions %}
13
13
  </div>
14
14
  {% endif %}
15
15
 
16
- {% unfold_result_list cl %}
16
+ <form id="dataset-{{ id }}" class="group" method="post"{% if cl.formset and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>
17
+ {% csrf_token %}
18
+
19
+ <input type="hidden" name="dataset" value="{{ id }}">
20
+ <input type="hidden" name="action" x-model="action">
21
+ <input type="hidden" name="select_across" x-model="selectAcross">
22
+
23
+ {% if cl.formset %}
24
+ {{ cl.formset.management_form }}
25
+ {% endif %}
26
+
27
+ {% unfold_result_list cl %}
28
+ </form>
17
29
 
18
30
  {% pagination cl %}
19
31
  </div>
@@ -2,23 +2,21 @@
2
2
 
3
3
  {% with tabs=adminform|tabs %}
4
4
  {% if tabs %}
5
- <div x-data="{openTab: null}" x-show="activeTab == 'general'">
6
- <ul class="bg-base-100 flex gap-1 mb-6 px-1.5 py-1.5 rounded-default text-font-default-light @min-[1570px]:-mx-4 dark:bg-white/[.02] dark:text-font-default-dark">
7
- {% for fieldset in tabs %}
8
- <li>
9
- <a class="block cursor-pointer font-semibold px-2.5 py-2 rounded-default hover:text-base-700 dark:hover: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 %} ? 'bg-white text-font-important-light shadow-xs dark:bg-base-900 dark:text-font-important-dark' : ''">
5
+ <div class="{% fieldset_rows_classes %}" x-data="{openTab: null}" x-show="activeTab == 'general'">
6
+ <div class="{% if adminform.model_admin.compressed_fields %}border-b border-base-200 border-dashed dark:border-base-800{% endif %}">
7
+ <nav class="bg-base-100 cursor-pointer flex flex-col font-medium gap-1 m-2 p-1 rounded-default text-important dark:border-base-700 md:inline-flex md:flex-row md:w-auto dark:bg-white/[.04] *:flex-inline *:flex-row *:items-center *:px-2.5 *:py-[5px] *:rounded-default *:transition-colors *:hover:bg-base-700/[.04] *:dark:hover:bg-white/[.04] [&>.active]:bg-white [&>.active]:shadow-xs [&>.active]:hover:bg-white [&>.active]:dark:bg-base-900 [&>.active]:dark:hover:bg-base-900">
8
+ {% for fieldset in tabs %}
9
+ <a x-on:click="openTab = '{{ forloop.counter0 }}-{{ fieldset.name|slugify }}'" x-bind:class="openTab == '{{ forloop.counter0 }}-{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %} ? 'active' : ''">
12
10
  {{ fieldset.name }}
13
11
  </a>
14
- </li>
15
- {% endfor %}
16
- </ul>
12
+ {% endfor %}
13
+ </nav>
14
+ </div>
17
15
 
18
16
  {% for fieldset in tabs %}
19
17
  <div class="tab-wrapper{% if fieldset.name %} fieldset-{{ fieldset.name|slugify }} fieldset-{{ forloop.counter0 }}-{{ fieldset.name|slugify }}{% endif %}"
20
18
  x-show="openTab == '{{ forloop.counter0 }}-{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %}">
21
- {% include 'admin/includes/fieldset.html' %}
19
+ {% include 'admin/includes/fieldset.html' with stacked=1 %}
22
20
  </div>
23
21
  {% endfor %}
24
22
  </div>
@@ -0,0 +1,11 @@
1
+ <h2 class="bg-base-100 flex flex-row font-semibold h-[38px] items-center mb-4 px-3 rounded-default text-important dark:bg-white/[.02] {% if inline_admin_formset.opts.tab %}hidden{% endif %} {% if clickable or inline_admin_formset.is_collapsible %}cursor-pointer{% endif %}" {% if inline_admin_formset %}id="{{ inline_admin_formset.formset.prefix }}-heading"{% endif %}>
2
+ {% if inline_admin_formset %}
3
+ {% if inline_admin_formset.formset.max_num == 1 %}
4
+ {{ inline_admin_formset.opts.verbose_name|capfirst }}
5
+ {% else %}
6
+ {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
7
+ {% endif %}
8
+ {% else %}
9
+ {{ title|capfirst }}
10
+ {% endif %}
11
+ </h2>
@@ -1,7 +1,7 @@
1
1
  {% load i18n %}
2
2
 
3
3
  {% if inlines_list or tabs_list %}
4
- <nav class="bg-base-100 flex flex-col font-medium gap-1 p-1 rounded-default w-full dark:border-base-700 md:flex-row md:w-auto dark:bg-white/[.04] *:flex *:flex-row *:font-medium *:items-center *:px-2.5 *:py-[5px] *:rounded-default *:transition-colors *:hover:bg-base-700/[.04] *:dark:hover:bg-white/[.04] [&>.active]:bg-white [&>.active]:shadow-xs [&>.active]:text-font-important-light [&>.active]:hover:bg-white [&>.active]:dark:bg-base-900 [&>.active]:dark:hover:bg-base-900 [&>.active]:dark:text-font-important-dark">
4
+ <nav class="bg-base-100 flex flex-col font-medium gap-1 p-1 rounded-default text-important w-full dark:border-base-700 md:flex-row md:w-auto dark:bg-white/[.04] *:flex *:flex-row *:font-medium *:items-center *:px-2.5 *:py-[5px] *:rounded-default *:transition-colors *:hover:bg-base-700/[.04] *:dark:hover:bg-white/[.04] [&>.active]:bg-white [&>.active]:shadow-xs [&>.active]:hover:bg-white [&>.active]:dark:bg-base-900 [&>.active]:dark:hover:bg-base-900">
5
5
  {% for item in tabs_list %}
6
6
  {% if item.has_permission %}
7
7
  <a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}{% if item.inline %}#{{ item.inline|slugify }}{% endif %}" class="{% if item.active and not item.inline %}active{% endif %}" {% if item.inline %}x-on:click="activeTab = '{{ item.inline|slugify }}'" x-bind:class="{'active': activeTab == '{{ item.inline|slugify }}'}"{% endif %}>
@@ -26,9 +26,11 @@
26
26
  {% endfor %}
27
27
 
28
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>
29
+ {% if dataset.tab %}
30
+ <a href="#dataset-{{ dataset.model_name }}" x-on:click="activeTab = 'dataset-{{ dataset.model_name }}'" x-bind:class="{'active': activeTab == 'dataset-{{ dataset.model_name }}'}">
31
+ {{ dataset.model_verbose_name|capfirst }}
32
+ </a>
33
+ {% endif %}
32
34
  {% endfor %}
33
35
  {% endif %}
34
36
  </nav>
@@ -151,76 +151,6 @@ def tabs(adminform: AdminForm) -> list[Fieldset]:
151
151
  return result
152
152
 
153
153
 
154
- class CaptureNode(Node):
155
- def __init__(self, nodelist: NodeList, varname: str, silent: bool) -> None:
156
- self.nodelist = nodelist
157
- self.varname = varname
158
- self.silent = silent
159
-
160
- def render(self, context: dict[str, Any]) -> str | SafeText:
161
- output = self.nodelist.render(context)
162
- context[self.varname] = output
163
- if self.silent:
164
- return ""
165
- else:
166
- return output
167
-
168
-
169
- @register.tag(name="capture")
170
- def do_capture(parser: Parser, token: Token) -> CaptureNode:
171
- """
172
- Capture the contents of a tag output.
173
- Usage:
174
- .. code-block:: html+django
175
- {% capture %}..{% endcapture %} # output in {{ capture }}
176
- {% capture silent %}..{% endcapture %} # output in {{ capture }} only
177
- {% capture as varname %}..{% endcapture %} # output in {{ varname }}
178
- {% capture as varname silent %}..{% endcapture %} # output in {{ varname }} only
179
- For example:
180
- .. code-block:: html+django
181
- {# Allow templates to override the page title/description #}
182
- <meta name="description" content="{% capture as meta_description %}
183
- {% block meta-description %}{% endblock %}{% endcapture %}" />
184
- <title>{% capture as meta_title %}{% block meta-title %}Untitled{% endblock %}{% endcapture %}</title>
185
- {# copy the values to the Social Media meta tags #}
186
- <meta property="og:description" content="{% block og-description %}{{ meta_description }}{% endblock %}" />
187
- <meta name="twitter:title" content="{% block twitter-title %}{{ meta_title }}{% endblock %}" />
188
- """
189
- bits = token.split_contents()
190
-
191
- # tokens
192
- t_as = "as"
193
- t_silent = "silent"
194
- var = "capture"
195
- silent = False
196
-
197
- num_bits = len(bits)
198
- if len(bits) > 4:
199
- raise TemplateSyntaxError(
200
- "'capture' node supports '[as variable] [silent]' parameters."
201
- )
202
- elif num_bits == 4:
203
- t_name, t_as, var, t_silent = bits
204
- silent = True
205
- elif num_bits == 3:
206
- t_name, t_as, var = bits
207
- elif num_bits == 2:
208
- t_name, t_silent = bits
209
- silent = True
210
- else:
211
- var = "capture"
212
- silent = False
213
-
214
- if t_silent != "silent" or t_as != "as":
215
- raise TemplateSyntaxError(
216
- "'capture' node expects 'as variable' or 'silent' syntax."
217
- )
218
-
219
- nodelist = parser.parse(("endcapture",))
220
- parser.delete_first_token()
221
- return CaptureNode(nodelist, var, silent)
222
-
223
-
224
154
  class RenderComponentNode(template.Node):
225
155
  def __init__(
226
156
  self,
@@ -799,3 +729,50 @@ def has_nested_tables(table: dict) -> bool:
799
729
  return any(
800
730
  isinstance(row, dict) and "table" in row for row in table.get("rows", [])
801
731
  )
732
+
733
+
734
+ class RenderCaptureNode(Node):
735
+ def __init__(self, nodelist: NodeList, variable_name: str, silent: bool) -> None:
736
+ self.nodelist = nodelist
737
+ self.variable_name = variable_name
738
+ self.silent = silent
739
+
740
+ def render(self, context: dict[str, Any]) -> str | SafeText:
741
+ content = self.nodelist.render(context)
742
+
743
+ if not self.silent:
744
+ return content
745
+
746
+ context.update(
747
+ {
748
+ self.variable_name: content,
749
+ }
750
+ )
751
+
752
+ return ""
753
+
754
+
755
+ @register.tag(name="capture")
756
+ def do_capture(parser: Parser, token: Token) -> RenderCaptureNode:
757
+ parts = token.split_contents()
758
+ variable_name = ""
759
+ silent = False
760
+
761
+ if len(parts) > 4:
762
+ raise TemplateSyntaxError("Too many arguments for 'capture' tag.")
763
+
764
+ if len(parts) >= 3:
765
+ if parts[1] != "as":
766
+ raise TemplateSyntaxError("'as' is required for 'capture' tag.")
767
+
768
+ variable_name = parts[2]
769
+
770
+ if len(parts) == 4:
771
+ if parts[3] != "silent":
772
+ raise TemplateSyntaxError("'silent' is required for 'capture' tag.")
773
+
774
+ silent = True
775
+
776
+ nodelist = parser.parse(("endcapture",))
777
+ parser.delete_first_token()
778
+ return RenderCaptureNode(nodelist, variable_name, silent)
@@ -5,6 +5,7 @@ from typing import Any
5
5
  from django.contrib.admin.templatetags.admin_list import (
6
6
  ResultList,
7
7
  _coerce_field_name,
8
+ admin_actions,
8
9
  result_hidden_fields,
9
10
  )
10
11
  from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
@@ -126,6 +127,7 @@ def result_headers(cl):
126
127
  "text": UnfoldBooleanWidget(
127
128
  {
128
129
  "id": "action-toggle",
130
+ "class": "action-toggle",
129
131
  "aria-label": _(
130
132
  "Select all objects on this page for an action"
131
133
  ),
@@ -471,3 +473,13 @@ def unfold_search_form_tag(parser, token):
471
473
  template_name="search_form.html",
472
474
  takes_context=False,
473
475
  )
476
+
477
+
478
+ @register.tag(name="unfold_admin_actions")
479
+ def unfold_admin_actions_tag(parser, token):
480
+ return InclusionAdminNode(
481
+ parser,
482
+ token,
483
+ func=admin_actions,
484
+ template_name="dataset_actions.html",
485
+ )
unfold/widgets.py CHANGED
@@ -176,7 +176,7 @@ CHECKBOX_CLASSES = [
176
176
  "focus:outline-offset-2",
177
177
  "focus:outline-primary-500",
178
178
  "after:absolute",
179
- "after:content-['done']",
179
+ r"after:content-['check\_small']",
180
180
  "after:flex!",
181
181
  "after:h-4",
182
182
  "after:items-center",
@@ -185,7 +185,6 @@ CHECKBOX_CLASSES = [
185
185
  "after:material-symbols-outlined",
186
186
  "after:-ml-px",
187
187
  "after:-mt-px",
188
- "after:text-sm!",
189
188
  "after:text-white",
190
189
  "after:transition-all",
191
190
  "after:w-4",