django-smartbase-admin 0.2.47__py3-none-any.whl → 1.0.38__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 (188) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +80 -51
  2. django_smartbase_admin/actions/advanced_filters.py +55 -20
  3. django_smartbase_admin/admin/admin_base.py +477 -89
  4. django_smartbase_admin/admin/site.py +104 -34
  5. django_smartbase_admin/admin/widgets.py +598 -26
  6. django_smartbase_admin/apps.py +2 -0
  7. django_smartbase_admin/engine/actions.py +34 -16
  8. django_smartbase_admin/engine/admin_base_view.py +253 -115
  9. django_smartbase_admin/engine/configuration.py +186 -4
  10. django_smartbase_admin/engine/const.py +7 -0
  11. django_smartbase_admin/engine/dashboard.py +44 -23
  12. django_smartbase_admin/engine/fake_inline.py +44 -7
  13. django_smartbase_admin/engine/field.py +54 -10
  14. django_smartbase_admin/engine/field_formatter.py +32 -9
  15. django_smartbase_admin/engine/filter_widgets.py +356 -21
  16. django_smartbase_admin/engine/menu_item.py +8 -5
  17. django_smartbase_admin/engine/modal_view.py +12 -7
  18. django_smartbase_admin/engine/request.py +2 -0
  19. django_smartbase_admin/integration/__init__.py +0 -0
  20. django_smartbase_admin/integration/django_cms.py +43 -0
  21. django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
  22. django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +268 -37
  23. django_smartbase_admin/migrations/0005_sbadminuserconfiguration.py +26 -0
  24. django_smartbase_admin/migrations/0006_alter_sbadminuserconfiguration_color_scheme.py +18 -0
  25. django_smartbase_admin/models.py +22 -0
  26. django_smartbase_admin/monkeypatch/admin_readonly_field_monkeypatch.py +96 -0
  27. django_smartbase_admin/monkeypatch/fake_inline_monkeypatch.py +1 -1
  28. django_smartbase_admin/querysets.py +3 -0
  29. django_smartbase_admin/services/configuration.py +30 -0
  30. django_smartbase_admin/services/thread_local.py +6 -19
  31. django_smartbase_admin/services/views.py +82 -27
  32. django_smartbase_admin/services/xlsx_export.py +6 -0
  33. django_smartbase_admin/static/sb_admin/build/tailwind.config.js +1 -0
  34. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/colors.js +4 -0
  35. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/spacing.js +1 -0
  36. django_smartbase_admin/static/sb_admin/build/webpack.common.js +11 -8
  37. django_smartbase_admin/static/sb_admin/css/ckeditor/ckeditor_content_dark.css +208 -0
  38. django_smartbase_admin/static/sb_admin/css/coloris/coloris.min.css +1 -0
  39. django_smartbase_admin/static/sb_admin/dist/calendar.js +1 -0
  40. django_smartbase_admin/static/sb_admin/dist/calendar_style.css +1 -0
  41. django_smartbase_admin/static/sb_admin/dist/calendar_style.js +0 -0
  42. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  43. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  44. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  45. django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
  46. django_smartbase_admin/static/sb_admin/dist/table.js.LICENSE.txt +9 -0
  47. django_smartbase_admin/static/sb_admin/dist/tree_widget.js +1 -0
  48. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
  49. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
  50. django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +1 -0
  51. django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
  52. django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
  53. django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
  54. django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
  55. django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
  56. django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
  57. django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
  58. django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
  59. django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
  60. django_smartbase_admin/static/sb_admin/images/flags/de-at.png +0 -0
  61. django_smartbase_admin/static/sb_admin/images/flags/de-ch.png +0 -0
  62. django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
  63. django_smartbase_admin/static/sb_admin/js/coloris/coloris.min.js +6 -0
  64. django_smartbase_admin/static/sb_admin/js/fullcalendar.min.js +14804 -0
  65. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Bolt-one.svg +3 -0
  66. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Calendar.svg +3 -0
  67. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Caution.svg +3 -0
  68. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Electric-drill.svg +3 -0
  69. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Fire-extinguisher.svg +3 -0
  70. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Gas.svg +3 -0
  71. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Lightning-fill.svg +3 -0
  72. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
  73. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Phone-telephone.svg +3 -0
  74. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Printer.svg +3 -0
  75. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.svg +3 -0
  76. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
  77. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Time.svg +3 -0
  78. django_smartbase_admin/static/sb_admin/src/css/_base.css +5 -1
  79. django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
  80. django_smartbase_admin/static/sb_admin/src/css/_components.css +66 -13
  81. django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +8 -1
  82. django_smartbase_admin/static/sb_admin/src/css/_filer.css +60 -0
  83. django_smartbase_admin/static/sb_admin/src/css/_inlines.css +51 -10
  84. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +8 -2
  85. django_smartbase_admin/static/sb_admin/src/css/calendar.css +162 -0
  86. django_smartbase_admin/static/sb_admin/src/css/components/_button.css +41 -1
  87. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +26 -8
  88. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +62 -20
  89. django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
  90. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +21 -2
  91. django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +12 -1
  92. django_smartbase_admin/static/sb_admin/src/css/components/_tooltip.css +8 -22
  93. django_smartbase_admin/static/sb_admin/src/css/style.css +17 -0
  94. django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +411 -0
  95. django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +63 -5
  96. django_smartbase_admin/static/sb_admin/src/js/calendar.js +56 -0
  97. django_smartbase_admin/static/sb_admin/src/js/chart.js +8 -22
  98. django_smartbase_admin/static/sb_admin/src/js/choices.js +18 -8
  99. django_smartbase_admin/static/sb_admin/src/js/datepicker.js +97 -336
  100. django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +357 -0
  101. django_smartbase_admin/static/sb_admin/src/js/main.js +307 -26
  102. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
  103. django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
  104. django_smartbase_admin/static/sb_admin/src/js/sb_ajax_params_tabulator_modifier.js +21 -0
  105. django_smartbase_admin/static/sb_admin/src/js/table.js +38 -13
  106. django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +43 -20
  107. django_smartbase_admin/static/sb_admin/src/js/table_modules/data_edit_module.js +8 -10
  108. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -3
  109. django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
  110. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
  111. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  112. django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +19 -3
  113. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
  114. django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
  115. django_smartbase_admin/templates/sb_admin/actions/change_form.html +169 -117
  116. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
  117. django_smartbase_admin/templates/sb_admin/actions/delete_selected_confirmation.html +56 -32
  118. django_smartbase_admin/templates/sb_admin/actions/list.html +79 -42
  119. django_smartbase_admin/templates/sb_admin/actions/object_history.html +2 -2
  120. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
  121. django_smartbase_admin/templates/sb_admin/actions/partials/selected_rows_actions.html +2 -2
  122. django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
  123. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  124. django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
  125. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  126. django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
  127. django_smartbase_admin/templates/sb_admin/config/view.html +0 -1
  128. django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
  129. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
  130. django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
  131. django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
  132. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
  133. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  134. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  135. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
  136. django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
  137. django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +10 -5
  138. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +2 -2
  139. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  140. django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
  141. django_smartbase_admin/templates/sb_admin/includes/components.html +5 -1
  142. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
  143. django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
  144. django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
  145. django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
  146. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  147. django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
  148. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +78 -36
  149. django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
  150. django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
  151. django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
  152. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
  153. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +35 -11
  154. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
  155. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  156. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
  157. django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
  158. django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
  159. django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
  160. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  161. django_smartbase_admin/templates/sb_admin/widgets/checkbox_group.html +15 -0
  162. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  163. django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
  164. django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
  165. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
  166. django_smartbase_admin/templates/sb_admin/widgets/html_read_only.html +1 -0
  167. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
  168. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  169. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  170. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  171. django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
  172. django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
  173. django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
  174. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
  175. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  176. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  177. django_smartbase_admin/templatetags/sb_admin_tags.py +163 -4
  178. django_smartbase_admin/utils.py +22 -3
  179. django_smartbase_admin/views/dashboard_view.py +6 -0
  180. django_smartbase_admin/views/global_filter_view.py +8 -2
  181. django_smartbase_admin/views/translations_view.py +12 -5
  182. django_smartbase_admin/views/user_config_view.py +52 -0
  183. django_smartbase_admin-1.0.38.dist-info/METADATA +166 -0
  184. {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/RECORD +186 -121
  185. {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/WHEEL +1 -1
  186. django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
  187. django_smartbase_admin-0.2.47.dist-info/METADATA +0 -25
  188. {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/LICENSE.md +0 -0
@@ -0,0 +1,84 @@
1
+ {% load i18n filer_admin_tags static %}
2
+
3
+ {% spaceless %}
4
+ <div class="clearfix"></div>
5
+
6
+ <div class="dz-preview dz-file-preview hidden js-filer-dropzone-template">
7
+ <span class="filerFile">
8
+ <div class="dz-thumbnail"><img class="quiet" data-dz-thumbnail></div>
9
+ <div>
10
+ <span data-dz-name class="dz-name"></span>
11
+ <span class="filerClearer filer-icon filer-icon-remove-selection" title="{% trans 'Clear' %}"
12
+ data-dz-remove data-no-icon-file="{% static 'filer/icons/file-unknown.svg' %}">
13
+ <button type="button" class="btn btn-tiny btn-destructive">{% trans 'Delete' %}</button>
14
+ </span>
15
+ </div>
16
+ <div class="dz-progress js-filer-dropzone-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
17
+ </span>
18
+ </div>
19
+
20
+ <div class="js-filer-dropzone filer-dropzone{% if object %} js-object-attached{% endif %}"
21
+ data-url="{% url 'admin:filer-ajax_upload' %}"
22
+ data-max-files="1"
23
+ {% if max_filesize %}}data-max-filesize="{{ max_filesize|safe }}"{% endif %}>
24
+ <div class="z-index-fix"></div>
25
+ <div class="dz-default dz-message js-filer-dropzone-message{% if object %} hidden{% endif %}">
26
+ <span class="w-64 h-64 flex items-center justify-center rounded-full mr-16 js-input-file-empty input-file-upload-icon">
27
+ <svg class="w-24 h-24">
28
+ <use xlink:href="#Upload"></use>
29
+ </svg>
30
+ </span>
31
+ <span>{% trans "drop your file here or" %}</span>
32
+ </div>
33
+
34
+ <span class="filerFile js-file-selector">
35
+ {% if object %}
36
+ {% if object.file.exists %}
37
+ <a href="{{ object.url }}" target="_blank">{% file_icon object size='64x64' %}</a>
38
+ {% else %}
39
+ {% file_icon object %}
40
+ {% endif %}
41
+ {% else %}
42
+ <img class="thumbnail_img hidden quiet" alt="{% trans 'No file selected' %}">
43
+ {% endif %}
44
+
45
+ <div>
46
+ {% if object %}
47
+ {% if object.file.exists %}
48
+ <span class="description_text">{{ object.label }}</span>
49
+ {% else %}
50
+ {% file_icon object %}
51
+ <span class="description_text">{% trans 'File is missing' %}</span>
52
+ {% endif %}
53
+ {% else %}
54
+ <span class="description_text"></span>
55
+ {% endif %}
56
+
57
+ <div class="flex items-center gap-4">
58
+ <a class="filerClearer {% if not object %}hidden{% endif %}" title="{% trans 'Clear' %}"
59
+ data-no-icon-file="{% static 'filer/icons/file-unknown.svg' %}">
60
+ <button type="button" class="btn btn-tiny btn-destructive">{% trans 'Delete' %}</button>
61
+ </a>
62
+
63
+ <a href="{{ lookup_url }}" class="js-related-lookup related-lookup {% if object %}related-lookup-change{% endif %} lookup" id="{{ id }}_lookup"
64
+ title="{% trans 'Lookup' %}">
65
+ <button type="button" class="btn btn-primary-light btn-tiny add-file-button">{% trans 'Choose File' %}</button>
66
+ </a>
67
+ </div>
68
+ </div>
69
+
70
+
71
+ <div class="hidden">{{ hidden_input }}</div>
72
+ <script type="text/javascript" id="{{id}}_javascript">
73
+ django.jQuery(document).ready(function(){
74
+ var plus = django.jQuery('#add_{{ id }}');
75
+ if (plus.length){
76
+ plus.remove();
77
+ }
78
+ // Delete this javascript once loaded to avoid the "add new" link duplicates it
79
+ django.jQuery('#{{id}}_javascript').remove();
80
+ });
81
+ </script>
82
+ </span>
83
+ </div>
84
+ {% endspaceless %}
@@ -0,0 +1 @@
1
+ {{ widget.value }}
@@ -0,0 +1,38 @@
1
+ {% load i18n %}
2
+ <div class="flex items-center gap-4 flex-shrink-0 related-item-buttons">
3
+ {% if widget.attrs.related_edit_url or widget.attrs.related_add_url %}
4
+ <a
5
+ class="btn btn-icon edit-button {% if not widget.attrs.related_edit_url %}hidden{% endif %}"
6
+ title="{% trans 'Edit' %}"
7
+ {% if widget.attrs.related_edit_url %}
8
+ hx-get="{{ widget.attrs.related_edit_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}{% if reload_on_save %}&sbadmin_reload_on_save=1{% endif %}"
9
+ {% endif %}
10
+ {% if widget.attrs.related_add_url and not widget.attrs.related_edit_url %}
11
+ data-add-url="{{ widget.attrs.related_add_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
12
+ {% endif %}
13
+ data-bs-toggle="modal"
14
+ data-bs-target="#sb-admin-modal"
15
+ hx-target="#sb-admin-modal"
16
+ hx-select="#modal-content"
17
+ hx-swap="innerHTML">
18
+ <svg class="w-20 h-20">
19
+ <use xlink:href="#Edit"></use>
20
+ </svg>
21
+ </a>
22
+ {% endif %}
23
+ {% if widget.attrs.related_add_url %}
24
+ <a
25
+ class="btn btn-icon"
26
+ title="{% trans 'Add' %}"
27
+ data-bs-toggle="modal"
28
+ data-bs-target="#sb-admin-modal"
29
+ hx-get="{{ widget.attrs.related_add_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
30
+ hx-target="#sb-admin-modal"
31
+ hx-select="#modal-content"
32
+ hx-swap="innerHTML">
33
+ <svg class="w-20 h-20">
34
+ <use xlink:href="#Add-one"></use>
35
+ </svg>
36
+ </a>
37
+ {% endif %}
38
+ </div>
@@ -1,7 +1,7 @@
1
1
  {% include 'sb_admin/widgets/includes/field_label.html' %}
2
2
  <div class="flex gap-16">
3
3
  {% for widget in widget.subwidgets %}
4
- <div class="relative flex-1">
4
+ <div class="relative">
5
5
  {% include widget.template_name with subwidget=True %}
6
6
  </div>
7
7
  {% endfor %}
@@ -1,12 +1,13 @@
1
+ {% include 'sb_admin/widgets/includes/field_label.html' %}
1
2
  {% with id=widget.attrs.id %}
2
3
  <ul{% if id %} id="{{ id }}"{% endif %}>
3
4
  {% for group, options, index in widget.optgroups %}
4
5
  {% for option in options %}
5
- <li>
6
+ <li class="relative py-8">
6
7
  {% include option.template_name with widget=option %}
7
8
  </li>
8
9
  {% endfor %}
9
10
  {% endfor %}
10
11
  </ul>
11
12
  {% endwith %}
12
-
13
+ {% include 'sb_admin/widgets/includes/help_text.html' %}
@@ -0,0 +1,30 @@
1
+ {% load i18n %}
2
+
3
+ <div class="relative">
4
+ {% include 'sb_admin/widgets/includes/simple_field_label.html' with label_classes="radio-dropdown-label inline-flex text-dark-900 mb-8 text-14 leading-18 font-medium" %}
5
+ <div id="{{ widget.attrs.id }}-wrapper" class="js-simple-multiselect-detail js-dropdown-wrapper">
6
+ <button
7
+ data-bs-toggle="dropdown"
8
+ aria-expanded="false"
9
+ data-bs-offset="[0, 8]"
10
+ class="btn px-10 font-normal w-full"
11
+ >
12
+ <span id="{{ widget.attrs.id }}-value" class="js-dropdown-label flex gap-8">{{ widget.form_field.empty_label }}</span>
13
+ <svg class="ml-8 no-rotate text-dark">
14
+ <use xlink:href="#Sort Alt"></use>
15
+ </svg>
16
+ </button>
17
+ <div id="{{ widget.attrs.id }}-dropdown" class="dropdown-menu">
18
+ <ul id="{{ widget.attrs.id }}" class="relative py-8">
19
+ {% for group, options, index in widget.optgroups %}
20
+ {% for option in options %}
21
+ <li>
22
+ {% include option.template_name with widget=option %}
23
+ </li>
24
+ {% endfor %}
25
+ {% endfor %}
26
+ </ul>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ {% include 'sb_admin/widgets/includes/help_text.html' %}
@@ -1,4 +1,7 @@
1
1
  {% include 'sb_admin/widgets/includes/field_label.html' %}
2
2
  <div {% include "sb_admin/widgets/attrs.html" %}>
3
3
  <p class="text-14 leading-18 read-only-password-hash">{{ widget.form_field.help_text|safe }}</p>
4
+ {% if button_label %}
5
+ <p class="mt-8"><a class="btn btn-small" href="{{ password_url|default:"../password/" }}">{{ button_label }}</a></p>
6
+ {% endif %}
4
7
  </div>
@@ -1,3 +1,10 @@
1
1
  {% include 'sb_admin/widgets/includes/field_label.html' %}
2
- {% include "sb_admin/widgets/input.html" %}
2
+ <div class="relative flex time-wrapper">
3
+ {% include "sb_admin/widgets/input.html" %}
4
+ <label class="absolute right-10 top-0 bottom-0 h-full flex items-center pointer-events-none" for="{{ widget.attrs.id }}">
5
+ <svg class="w-20 h-20">
6
+ <use xlink:href="#Time"></use>
7
+ </svg>
8
+ </label>
9
+ </div>
3
10
  {% include 'sb_admin/widgets/includes/help_text.html' %}
@@ -1,4 +1,4 @@
1
- <div class="relative">
1
+ <div class="relative min-h-24">
2
2
  {% include "sb_admin/widgets/input.html" %}
3
3
  <label for="{{ widget.attrs.id }}"></label>
4
4
  {% include 'sb_admin/widgets/includes/simple_field_label.html' %}
@@ -0,0 +1,59 @@
1
+ {% load static sb_admin_tags i18n %}
2
+
3
+ {% if not hide_tree_search %}
4
+ <div class="border-b border-dark-200 {{ search_wrapper_classes }}">
5
+ <div class="js-tree-widget-search relative">
6
+ {% trans 'Search' as search_string %}
7
+ <input id="{{ tree_component_id }}_search" name="search" placeholder="{{ tree_filter_placeholder|default:search_string }}" autocomplete="off" class="input pl-36" type="search">
8
+ <div class="absolute pl-10 left-0 top-0 bottom-0 flex items-center gap-8 rounded-r">
9
+ <svg class="w-20 h-20">
10
+ <use xlink:href="#Search"></use>
11
+ </svg>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ {% endif %}
16
+ <div class="{{ table_wrapper_classes }}">
17
+ <table style="width: 100%;" id="{{ tree_component_id }}_tree" class="js-tree-widget fancytree-ext-table" data-tree-data-id="{{ tree_component_id }}_data" data-tree-additional-columns-id="{{ tree_component_id }}_additional_columns" data-tree-strings-id="{{ tree_component_id }}_tree_strings">
18
+ <thead>
19
+ <tr>
20
+ {% if tree_show_checkbox %}
21
+ <th class="select-all-checkbox-wrapper"></th>
22
+ {% endif %}
23
+ <th class="text-left">{{ tree_main_column_name }}<span id="{{ tree_component_id }}_matches" class="ml-4"></span></th>
24
+ {% for column in tree_additional_columns %}
25
+ <th>{{ column.title }}</th>
26
+ {% endfor %}
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <tr>
31
+ {% if tree_show_checkbox %}
32
+ <td class="alignCenter fancytree-checkbox-column"></td>{% endif %}
33
+ <td></td>
34
+ {% for column in tree_additional_columns %}
35
+ <td></td>
36
+ {% endfor %}
37
+ </tr>
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+
42
+ <script id="{{ tree_component_id }}_data" type="application/json">
43
+ {
44
+ "filter": {% if tree_filter %}true{% else %}false{% endif %},
45
+ "input_id": "{{ tree_component_id }}",
46
+ "data_url": "{{ tree_data_url }}",
47
+ "checkbox": {% if tree_show_checkbox %}true{% else %}false{% endif %},
48
+ "multiselect": {% if tree_multiselect %}2{% else %}1{% endif %},
49
+ "detail_url":"{{ tree_detail_url }}",
50
+ "reorder_url":"{{ tree_reorder_url }}",
51
+ "filter_by_table_data": {% if filter_by_table_data %}true{% else %}false{% endif %},
52
+ "allow_select_all": {% if allow_select_all %}true{% else %}false{% endif %}
53
+ }
54
+ </script>
55
+ {{ fancytree_filter_settings|get_json_script:'fancytree_filter_settings' }}
56
+ <script id="{{ tree_component_id }}_additional_columns" type="application/json">{{ tree_additional_columns|get_json|safe }}</script>
57
+ <script id="{{ tree_component_id }}_tree_strings" type="application/json">
58
+ {{ tree_strings|get_json|safe }}
59
+ </script>
@@ -0,0 +1,24 @@
1
+ {% load sb_admin_tags %}
2
+
3
+ <div class="relative">
4
+ {% include 'sb_admin/widgets/includes/field_label.html' %}
5
+ <div id="{{ widget.attrs.id }}-wrapper" class="tree-widget-wrapper">
6
+ <input type="hidden" id="{{ widget.attrs.id }}" name="{{ widget.name }}"{% if widget.raw_value %} value="{{ widget.raw_value|get_json }}"{% endif %}>
7
+ <button
8
+ data-bs-toggle="dropdown"
9
+ aria-expanded="false"
10
+ data-bs-offset="[0, 8]"
11
+ class="btn px-10 font-normal w-full"
12
+ >
13
+ <span id="{{ widget.attrs.id }}_label">{% get_item widget.value_dict widget.raw_value widget.form_field.empty_label %}</span>
14
+ <svg class="ml-8">
15
+ <use xlink:href="#Down"></use>
16
+ </svg>
17
+ </button>
18
+ <div class="dropdown-menu max-h-none w-248">
19
+ <div class="py-8">
20
+ {% include "sb_admin/widgets/tree_base.html" with tree_additional_columns=widget.additional_columns tree_strings=widget.tree_strings tree_value=widget.raw_value tree_multiselect=filter_widget.multiselect tree_component_id=widget.attrs.id tree_data_url=filter_widget.to_json.autocomplete_url tree_show_checkbox=True table_wrapper_classes="max-h-432 overflow-auto custom-scrollbar" %}
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
@@ -0,0 +1,12 @@
1
+ {% load sb_admin_tags %}
2
+
3
+ <div class="relative -mb-2">
4
+ <script>
5
+ document.currentScript.closest('.djn-table').querySelector('.djn-thead').classList.add('hidden');
6
+ </script>
7
+ <div id="{{ widget.attrs.id }}-wrapper" class="tree-widget-wrapper">
8
+ <input type="hidden" id="{{ widget.attrs.id }}" name="{{ widget.name }}"{% if widget.raw_value %}
9
+ value="{{ widget.raw_value|get_json }}"{% endif %}>
10
+ {% include "sb_admin/widgets/tree_base.html" with tree_main_column_name=filter_widget.form_field.label tree_strings=widget.tree_strings tree_additional_columns=widget.additional_columns tree_value=widget.raw_value tree_multiselect=filter_widget.multiselect tree_component_id=widget.attrs.id tree_data_url=filter_widget.to_json.autocomplete_url tree_show_checkbox=True table_wrapper_classes="max-h-432 overflow-auto custom-scrollbar" search_wrapper_classes="pb-16 px-16" allow_select_all=True %}
11
+ </div>
12
+ </div>
@@ -1,10 +1,22 @@
1
1
  import json
2
+ import os
2
3
 
3
4
  from django import template
4
5
  from django.contrib.admin.templatetags.admin_modify import submit_row
6
+ from django.contrib.admin.utils import lookup_field
7
+ from django.core.exceptions import ObjectDoesNotExist
5
8
  from django.core.serializers.json import DjangoJSONEncoder
6
9
  from django.template.defaultfilters import json_script
10
+ from django.templatetags.static import static
11
+ from django.utils.safestring import mark_safe
12
+ from django.utils.text import get_text_list
13
+ from django.utils.translation import gettext
14
+ from easy_thumbnails.exceptions import InvalidImageFormatError
7
15
 
16
+ from django_smartbase_admin.engine.const import (
17
+ ROW_CLASS_FIELD,
18
+ SUPPORTED_FILE_TYPE_ICONS,
19
+ )
8
20
  from django_smartbase_admin.templatetags.base import InclusionSBAdminNode
9
21
 
10
22
  register = template.Library()
@@ -75,7 +87,7 @@ def sb_admin_render_form_field(context, form_field, label_as_placeholder=False):
75
87
  @register.simple_tag
76
88
  def get_tabular_context(fieldsets, inlines, tabs):
77
89
  default_tabs = False
78
- has_error = False
90
+ any_error = False
79
91
  first_error_tab = True
80
92
  tabular_context = {}
81
93
  inlines_map = {inline.opts.__class__: inline for inline in inlines}
@@ -87,6 +99,7 @@ def get_tabular_context(fieldsets, inlines, tabs):
87
99
  default_tabs = True
88
100
  for key, values in tabs.items():
89
101
  for value in values:
102
+ has_error = False
90
103
  fieldset_value = fieldsets_map.get(value)
91
104
  inline_value = inlines_map.get(value)
92
105
  tabular_context[key] = tabular_context.get(
@@ -96,7 +109,15 @@ def get_tabular_context(fieldsets, inlines, tabs):
96
109
  tabular_context[key]["content"].append(
97
110
  {"type": "fieldset", "value": fieldset_value}
98
111
  )
99
- error_present = bool(fieldset_value.form.errors)
112
+ fieldset_fields = set()
113
+ for field in fieldset_value.fields:
114
+ if isinstance(field, (list, tuple)):
115
+ fieldset_fields.update(set(field))
116
+ else:
117
+ fieldset_fields.add(field)
118
+ error_present = bool(
119
+ set(fieldset_value.form.errors.keys()).intersection(fieldset_fields)
120
+ )
100
121
  has_error = has_error or error_present
101
122
  tabular_context[key]["error"] = (
102
123
  tabular_context[key]["error"] or error_present
@@ -111,16 +132,154 @@ def get_tabular_context(fieldsets, inlines, tabs):
111
132
  tabular_context[key]["error"] or error_present
112
133
  )
113
134
  if has_error:
135
+ any_error = True
114
136
  tabular_context[key]["classes"].add("error")
115
137
  if first_error_tab:
116
138
  tabular_context[key]["classes"].update(["active", "show"])
117
139
  first_error_tab = False
118
- if not has_error:
140
+ if not any_error:
119
141
  tabular_context[list(tabular_context.keys())[0]]["classes"].update(
120
142
  ["active", "show"]
121
143
  )
122
144
  return {
123
145
  "context": tabular_context,
124
146
  "default_tabs": default_tabs,
125
- "has_error": has_error,
147
+ "has_error": any_error,
126
148
  }
149
+
150
+
151
+ @register.filter
152
+ def to_class_name(value):
153
+ return value.__class__.__name__
154
+
155
+
156
+ def get_change_message_legacy(log_entry):
157
+ change_message = log_entry.change_message
158
+ if change_message and change_message[0] == "[":
159
+ try:
160
+ change_message = json.loads(change_message)
161
+ except json.JSONDecodeError:
162
+ return change_message
163
+ messages = []
164
+ for sub_message in change_message:
165
+ if "added" in sub_message:
166
+ if sub_message["added"]:
167
+ sub_message["added"]["name"] = gettext(sub_message["added"]["name"])
168
+ messages.append(
169
+ gettext("Added {name} “{object}”.").format(
170
+ **sub_message["added"]
171
+ )
172
+ )
173
+ else:
174
+ messages.append(gettext("Added."))
175
+
176
+ elif "changed" in sub_message:
177
+ sub_message["changed"]["fields"] = get_text_list(
178
+ [
179
+ gettext(field_name)
180
+ for field_name in sub_message["changed"]["fields"]
181
+ ],
182
+ gettext("and"),
183
+ )
184
+ if "name" in sub_message["changed"]:
185
+ sub_message["changed"]["name"] = gettext(
186
+ sub_message["changed"]["name"]
187
+ )
188
+ messages.append(
189
+ gettext("Changed {fields} for {name} “{object}”.").format(
190
+ **sub_message["changed"]
191
+ )
192
+ )
193
+ else:
194
+ messages.append(
195
+ gettext("Changed {fields}.").format(**sub_message["changed"])
196
+ )
197
+
198
+ elif "deleted" in sub_message:
199
+ sub_message["deleted"]["name"] = gettext(sub_message["deleted"]["name"])
200
+ messages.append(
201
+ gettext("Deleted {name} “{object}”.").format(
202
+ **sub_message["deleted"]
203
+ )
204
+ )
205
+
206
+ change_message = mark_safe(
207
+ "<br/>".join(msg[0].upper() + msg[1:] for msg in messages)
208
+ )
209
+ return change_message or gettext("No fields changed.")
210
+ else:
211
+ return change_message
212
+
213
+
214
+ @register.simple_tag
215
+ def get_log_entry_message(log_entry):
216
+ """
217
+ If change_message is a JSON structure, interpret it as a change
218
+ string, properly translated.
219
+ """
220
+ try:
221
+ return get_change_message_legacy(log_entry)
222
+ except Exception as e:
223
+ return ""
224
+
225
+
226
+ @register.simple_tag
227
+ def call_method(obj, method_name, *args):
228
+ method = getattr(obj, method_name, None)
229
+ if method:
230
+ return method(*args)
231
+ return False
232
+
233
+
234
+ @register.simple_tag
235
+ def get_row_class(inline_admin_form):
236
+ try:
237
+ f, attr, value = lookup_field(
238
+ ROW_CLASS_FIELD, inline_admin_form.original, inline_admin_form.model_admin
239
+ )
240
+ except (AttributeError, ValueError, ObjectDoesNotExist):
241
+ return ""
242
+ return value
243
+
244
+
245
+ @register.filter
246
+ def is_row_class_field(field):
247
+ return isinstance(field, dict) and field["name"] == ROW_CLASS_FIELD
248
+
249
+
250
+ @register.simple_tag
251
+ def get_file_extension(file):
252
+ if not file:
253
+ return ""
254
+ name, extension = os.path.splitext(file.name)
255
+ return extension.lower().replace(".", "")
256
+
257
+
258
+ @register.simple_tag
259
+ def get_file_preview_image(
260
+ file, file_extension=None, thumbnail_width=64, thumbnail_height=64
261
+ ):
262
+ file_extension = file_extension or get_file_extension(file)
263
+ if file_extension in ["jpg", "jpeg", "png"]:
264
+ from easy_thumbnails.files import get_thumbnailer
265
+
266
+ thumbnailer = get_thumbnailer(file)
267
+ try:
268
+ thumb = thumbnailer.get_thumbnail(
269
+ {
270
+ "size": (thumbnail_width, thumbnail_height),
271
+ "crop": True,
272
+ "replace_alpha": "#fff",
273
+ }
274
+ )
275
+ return thumb.url
276
+ except ValueError:
277
+ return file.url
278
+ except InvalidImageFormatError:
279
+ return static("sb_admin/images/file_types/file-other.svg")
280
+ if file_extension == "svg" or file_extension == "webp":
281
+ return file.url
282
+ for extension in SUPPORTED_FILE_TYPE_ICONS:
283
+ if file_extension == extension:
284
+ return static(f"sb_admin/images/file_types/file-{extension}.svg")
285
+ return static("sb_admin/images/file_types/file-other.svg")
@@ -1,8 +1,13 @@
1
+ import logging
2
+ from collections.abc import Iterable
3
+
1
4
  from django import forms
2
5
  from django.conf import settings
3
6
  from django.contrib.admin.helpers import Fieldset
4
7
  from django.template.loader import render_to_string
5
8
 
9
+ logger = logging.getLogger(__name__)
10
+
6
11
 
7
12
  class JSONSerializableMixin(object):
8
13
  def to_json(self):
@@ -35,15 +40,29 @@ def render_notifications(request):
35
40
  return render_to_string("sb_admin/includes/notifications.html", request=request)
36
41
 
37
42
 
43
+ def is_modal(request):
44
+ from django_smartbase_admin.engine.admin_base_view import SBADMIN_IS_MODAL_VAR
45
+
46
+ return request and (
47
+ SBADMIN_IS_MODAL_VAR in request.GET or SBADMIN_IS_MODAL_VAR in request.POST
48
+ )
49
+
50
+
38
51
  class FormFieldsetMixin(forms.Form):
39
- def fieldsets(self):
52
+ def get_fieldsets(self) -> Iterable[tuple[str | None, dict]]:
40
53
  meta = getattr(self, "Meta", None)
54
+ return getattr(meta, "fieldsets", tuple())
41
55
 
42
- if not meta or not meta.fieldsets:
56
+ def fieldsets(self) -> Iterable[Fieldset]:
57
+ if not (fieldsets := self.get_fieldsets()):
58
+ logger.warning(
59
+ "No fieldsets defined for form %s. Using form fields as fallback.",
60
+ self.__class__.__name__,
61
+ )
43
62
  yield Fieldset(form=self, fields=self.fields)
44
63
  return
45
64
 
46
- for name, data in meta.fieldsets:
65
+ for name, data in fieldsets:
47
66
  yield Fieldset(
48
67
  form=self,
49
68
  name=name,
@@ -11,6 +11,7 @@ class SBAdminDashboardView(SBAdminView):
11
11
  menu_action = Action.DASHBOARD.value
12
12
  widgets = None
13
13
  title = None
14
+ direct_sub_views = None
14
15
 
15
16
  def __init__(self, title=None, widgets=None) -> None:
16
17
  super().__init__()
@@ -27,16 +28,21 @@ class SBAdminDashboardView(SBAdminView):
27
28
  view.init_view_dynamic(request, request_data, **kwargs)
28
29
 
29
30
  def get_sub_views(self, configuration):
31
+ self.direct_sub_views = []
30
32
  self.sub_views = []
31
33
  for idx, widget_view in enumerate(self.widgets):
32
34
  widget_view.widget_id = f"{self.get_id()}_{idx}"
33
35
  widget_view.init_widget_static(configuration)
36
+ widget_view_sub_views = widget_view.get_sub_views(configuration) or []
34
37
  self.sub_views.append(widget_view)
38
+ self.direct_sub_views.append(widget_view)
39
+ self.sub_views.extend(widget_view_sub_views)
35
40
  return self.sub_views
36
41
 
37
42
  def dashboard(self, request, modifier):
38
43
  context = self.get_global_context(request)
39
44
  context["sub_views"] = self.sub_views
45
+ context["direct_sub_views"] = self.direct_sub_views
40
46
  context["title"] = self.get_title()
41
47
  return TemplateResponse(
42
48
  request,
@@ -4,7 +4,10 @@ from django.http import HttpResponse
4
4
  from django.shortcuts import redirect
5
5
  from django.views import View
6
6
 
7
- from django_smartbase_admin.engine.const import GLOBAL_FILTER_DATA_KEY
7
+ from django_smartbase_admin.engine.const import (
8
+ GLOBAL_FILTER_DATA_KEY,
9
+ TABLE_RELOAD_DATA_EVENT_NAME,
10
+ )
8
11
  from django_smartbase_admin.utils import is_htmx_request, querydict_to_dict
9
12
 
10
13
 
@@ -13,10 +16,13 @@ class GlobalFilterView(View):
13
16
  response = redirect(request.headers.get("referer", ""))
14
17
  if is_htmx_request(request.META):
15
18
  response = HttpResponse()
16
- response["HX-Trigger"] = json.dumps({"SBAdminReloadTableData": ""})
19
+ response["HX-Trigger"] = json.dumps({TABLE_RELOAD_DATA_EVENT_NAME: ""})
17
20
  new_global_filter_data = querydict_to_dict(request.POST)
18
21
  request.request_data.global_filter = new_global_filter_data
19
22
  request.request_data.configuration.init_global_filter_form_instance(request)
20
23
  if request.request_data.global_filter_instance.is_valid():
21
24
  request.session[GLOBAL_FILTER_DATA_KEY] = new_global_filter_data
25
+ response = request.request_data.configuration.process_global_filter_response(
26
+ response, request
27
+ )
22
28
  return response