django-smartbase-admin 0.2.54__py3-none-any.whl → 1.0.42__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 (184) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +79 -38
  2. django_smartbase_admin/actions/advanced_filters.py +24 -1
  3. django_smartbase_admin/admin/admin_base.py +402 -97
  4. django_smartbase_admin/admin/site.py +93 -35
  5. django_smartbase_admin/admin/widgets.py +636 -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 +252 -115
  9. django_smartbase_admin/engine/configuration.py +186 -4
  10. django_smartbase_admin/engine/const.py +6 -0
  11. django_smartbase_admin/engine/dashboard.py +49 -24
  12. django_smartbase_admin/engine/fake_inline.py +15 -11
  13. django_smartbase_admin/engine/field.py +42 -12
  14. django_smartbase_admin/engine/field_formatter.py +38 -14
  15. django_smartbase_admin/engine/filter_widgets.py +348 -24
  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 +80 -13
  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/tree_widget.js +1 -0
  47. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
  48. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
  49. django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +1 -0
  50. django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
  51. django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
  52. django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
  53. django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
  54. django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
  55. django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
  56. django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
  57. django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
  58. django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
  59. django_smartbase_admin/static/sb_admin/images/flags/de-at.png +0 -0
  60. django_smartbase_admin/static/sb_admin/images/flags/de-ch.png +0 -0
  61. django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
  62. django_smartbase_admin/static/sb_admin/js/coloris/coloris.min.js +6 -0
  63. django_smartbase_admin/static/sb_admin/js/fullcalendar.min.js +14804 -0
  64. django_smartbase_admin/static/sb_admin/js/sbadmin_prepopulated_fields_init.js +25 -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 +61 -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 +31 -7
  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 +69 -11
  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 +306 -31
  102. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
  103. django_smartbase_admin/static/sb_admin/src/js/radio.js +31 -0
  104. django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
  105. django_smartbase_admin/static/sb_admin/src/js/table.js +34 -5
  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/detail_view_module.js +50 -1
  109. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +10 -3
  110. django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
  111. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
  112. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  113. django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +6 -0
  114. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
  115. django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
  116. django_smartbase_admin/templates/sb_admin/actions/change_form.html +176 -116
  117. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
  118. django_smartbase_admin/templates/sb_admin/actions/list.html +79 -39
  119. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
  120. django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
  121. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  122. django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
  123. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  124. django_smartbase_admin/templates/sb_admin/components/filters.html +1 -0
  125. django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
  126. django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
  127. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
  128. django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
  129. django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
  130. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
  131. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  132. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  133. django_smartbase_admin/templates/sb_admin/filter_widgets/boolean_field.html +1 -14
  134. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
  135. django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
  136. django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +12 -6
  137. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +5 -3
  138. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  139. django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
  140. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
  141. django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
  142. django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
  143. django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
  144. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  145. django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
  146. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +76 -34
  147. django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
  148. django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
  149. django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
  150. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
  151. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +27 -11
  152. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
  153. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  154. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
  155. django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
  156. django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
  157. django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
  158. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  159. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  160. django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
  161. django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
  162. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
  163. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
  164. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  165. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  166. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  167. django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
  168. django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
  169. django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
  170. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
  171. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  172. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  173. django_smartbase_admin/templatetags/sb_admin_tags.py +115 -4
  174. django_smartbase_admin/utils.py +22 -3
  175. django_smartbase_admin/views/dashboard_view.py +6 -0
  176. django_smartbase_admin/views/global_filter_view.py +8 -2
  177. django_smartbase_admin/views/translations_view.py +12 -5
  178. django_smartbase_admin/views/user_config_view.py +52 -0
  179. django_smartbase_admin-1.0.42.dist-info/METADATA +166 -0
  180. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/RECORD +182 -118
  181. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/WHEEL +1 -1
  182. django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
  183. django_smartbase_admin-0.2.54.dist-info/METADATA +0 -25
  184. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.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,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,13 +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
7
11
  from django.utils.safestring import mark_safe
8
12
  from django.utils.text import get_text_list
9
13
  from django.utils.translation import gettext
14
+ from easy_thumbnails.exceptions import InvalidImageFormatError
10
15
 
16
+ from django_smartbase_admin.engine.const import (
17
+ ROW_CLASS_FIELD,
18
+ SUPPORTED_FILE_TYPE_ICONS,
19
+ )
11
20
  from django_smartbase_admin.templatetags.base import InclusionSBAdminNode
12
21
 
13
22
  register = template.Library()
@@ -41,6 +50,36 @@ def get_item(dictionary, key):
41
50
  return dictionary.get(key, None) if dictionary else None
42
51
 
43
52
 
53
+ @register.simple_tag(takes_context=True)
54
+ def sb_admin_prepopulated_fields_init(context):
55
+ adminform = context.get("adminform")
56
+ if not adminform:
57
+ return ""
58
+
59
+ data = []
60
+ for f in adminform.prepopulated_fields:
61
+ bf = f["field"]
62
+ deps = f["dependencies"]
63
+ data.append(
64
+ {
65
+ "id": f"#{bf.id_for_label}",
66
+ "name": bf.name,
67
+ "dependency_ids": [f"#{d.id_for_label}" for d in deps],
68
+ "dependency_list": [d.name for d in deps],
69
+ "maxLength": getattr(bf.field, "max_length", None) or 50,
70
+ "allowUnicode": bool(getattr(bf.field, "allow_unicode", False)),
71
+ }
72
+ )
73
+
74
+ opts = context.get("opts")
75
+ element_id = (
76
+ f"sbadmin_prepopulated_fields_{opts.app_label}_{opts.model_name}"
77
+ if opts
78
+ else "sbadmin_prepopulated_fields"
79
+ )
80
+ return mark_safe(get_json_script(data, element_id))
81
+
82
+
44
83
  @register.tag(name="submit_row")
45
84
  def submit_row_tag(parser, token):
46
85
  return InclusionSBAdminNode(
@@ -78,7 +117,7 @@ def sb_admin_render_form_field(context, form_field, label_as_placeholder=False):
78
117
  @register.simple_tag
79
118
  def get_tabular_context(fieldsets, inlines, tabs):
80
119
  default_tabs = False
81
- has_error = False
120
+ any_error = False
82
121
  first_error_tab = True
83
122
  tabular_context = {}
84
123
  inlines_map = {inline.opts.__class__: inline for inline in inlines}
@@ -90,6 +129,7 @@ def get_tabular_context(fieldsets, inlines, tabs):
90
129
  default_tabs = True
91
130
  for key, values in tabs.items():
92
131
  for value in values:
132
+ has_error = False
93
133
  fieldset_value = fieldsets_map.get(value)
94
134
  inline_value = inlines_map.get(value)
95
135
  tabular_context[key] = tabular_context.get(
@@ -99,7 +139,15 @@ def get_tabular_context(fieldsets, inlines, tabs):
99
139
  tabular_context[key]["content"].append(
100
140
  {"type": "fieldset", "value": fieldset_value}
101
141
  )
102
- error_present = bool(fieldset_value.form.errors)
142
+ fieldset_fields = set()
143
+ for field in fieldset_value.fields:
144
+ if isinstance(field, (list, tuple)):
145
+ fieldset_fields.update(set(field))
146
+ else:
147
+ fieldset_fields.add(field)
148
+ error_present = bool(
149
+ set(fieldset_value.form.errors.keys()).intersection(fieldset_fields)
150
+ )
103
151
  has_error = has_error or error_present
104
152
  tabular_context[key]["error"] = (
105
153
  tabular_context[key]["error"] or error_present
@@ -114,18 +162,19 @@ def get_tabular_context(fieldsets, inlines, tabs):
114
162
  tabular_context[key]["error"] or error_present
115
163
  )
116
164
  if has_error:
165
+ any_error = True
117
166
  tabular_context[key]["classes"].add("error")
118
167
  if first_error_tab:
119
168
  tabular_context[key]["classes"].update(["active", "show"])
120
169
  first_error_tab = False
121
- if not has_error:
170
+ if not any_error:
122
171
  tabular_context[list(tabular_context.keys())[0]]["classes"].update(
123
172
  ["active", "show"]
124
173
  )
125
174
  return {
126
175
  "context": tabular_context,
127
176
  "default_tabs": default_tabs,
128
- "has_error": has_error,
177
+ "has_error": any_error,
129
178
  }
130
179
 
131
180
 
@@ -202,3 +251,65 @@ def get_log_entry_message(log_entry):
202
251
  return get_change_message_legacy(log_entry)
203
252
  except Exception as e:
204
253
  return ""
254
+
255
+
256
+ @register.simple_tag
257
+ def call_method(obj, method_name, *args):
258
+ method = getattr(obj, method_name, None)
259
+ if method:
260
+ return method(*args)
261
+ return False
262
+
263
+
264
+ @register.simple_tag
265
+ def get_row_class(inline_admin_form):
266
+ try:
267
+ f, attr, value = lookup_field(
268
+ ROW_CLASS_FIELD, inline_admin_form.original, inline_admin_form.model_admin
269
+ )
270
+ except (AttributeError, ValueError, ObjectDoesNotExist):
271
+ return ""
272
+ return value
273
+
274
+
275
+ @register.filter
276
+ def is_row_class_field(field):
277
+ return isinstance(field, dict) and field["name"] == ROW_CLASS_FIELD
278
+
279
+
280
+ @register.simple_tag
281
+ def get_file_extension(file):
282
+ if not file:
283
+ return ""
284
+ name, extension = os.path.splitext(file.name)
285
+ return extension.lower().replace(".", "")
286
+
287
+
288
+ @register.simple_tag
289
+ def get_file_preview_image(
290
+ file, file_extension=None, thumbnail_width=64, thumbnail_height=64
291
+ ):
292
+ file_extension = file_extension or get_file_extension(file)
293
+ if file_extension in ["jpg", "jpeg", "png"]:
294
+ from easy_thumbnails.files import get_thumbnailer
295
+
296
+ thumbnailer = get_thumbnailer(file)
297
+ try:
298
+ thumb = thumbnailer.get_thumbnail(
299
+ {
300
+ "size": (thumbnail_width, thumbnail_height),
301
+ "crop": True,
302
+ "replace_alpha": "#fff",
303
+ }
304
+ )
305
+ return thumb.url
306
+ except ValueError:
307
+ return file.url
308
+ except InvalidImageFormatError:
309
+ return static("sb_admin/images/file_types/file-other.svg")
310
+ if file_extension == "svg" or file_extension == "webp":
311
+ return file.url
312
+ for extension in SUPPORTED_FILE_TYPE_ICONS:
313
+ if file_extension == extension:
314
+ return static(f"sb_admin/images/file_types/file-{extension}.svg")
315
+ 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
@@ -9,6 +9,7 @@ from django.forms import modelform_factory
9
9
  from django.http import HttpResponse, HttpResponseRedirect
10
10
  from django.template.response import TemplateResponse
11
11
  from django.utils.html import format_html
12
+ from django.utils.safestring import mark_safe
12
13
  from django.utils.translation import gettext_lazy as _
13
14
 
14
15
  from django_smartbase_admin.actions.admin_action_list import SBAdminListAction
@@ -125,7 +126,7 @@ class ModelTranslationView(SBAdminView, SBAdminBaseListView):
125
126
  lang_codes.extend(selected_lang_codes)
126
127
  return lang_codes
127
128
 
128
- def init_fields_cache(self, fields_source=None, configuration=None):
129
+ def init_fields_cache(self, fields_source=None, configuration=None, force=False):
129
130
  return
130
131
 
131
132
  def get_field_map(self, request):
@@ -161,7 +162,7 @@ class ModelTranslationView(SBAdminView, SBAdminBaseListView):
161
162
  annotate=Case(
162
163
  When(
163
164
  **{
164
- f"{annotate_name}_count": F(
165
+ f"{annotate_name}_count__gte": F(
165
166
  f"{main_lang_annotate_name}_count"
166
167
  ),
167
168
  "then": Value(self.TRANSLATION_TRANSLATED),
@@ -207,11 +208,17 @@ class ModelTranslationView(SBAdminView, SBAdminBaseListView):
207
208
 
208
209
  def format_translation_status(self, object_id, value):
209
210
  if value == self.TRANSLATION_NOT_TRANSLATED:
210
- return f'<span class="badge badge-simple badge-neutral"><svg class="w-16 h-16 mr-4 text-invisible"><use xlink:href="#Add-one"></use></svg>{_("Not Translated")}</span>'
211
+ return mark_safe(
212
+ f'<span class="badge badge-simple badge-neutral"><svg class="w-16 h-16 mr-4 text-invisible"><use xlink:href="#Add-one"></use></svg>{_("Not Translated")}</span>'
213
+ )
211
214
  if value == self.TRANSLATION_INCOMPLETE:
212
- return f'<span class="badge badge-simple badge-warning"><svg class="w-16 h-16 mr-4 text-warning"><use xlink:href="#Attention"></use></svg>{_("Incomplete")}</span>'
215
+ return mark_safe(
216
+ f'<span class="badge badge-simple badge-warning"><svg class="w-16 h-16 mr-4 text-warning"><use xlink:href="#Attention"></use></svg>{_("Incomplete")}</span>'
217
+ )
213
218
  if value == self.TRANSLATION_TRANSLATED:
214
- return f'<span class="badge badge-simple badge-positive"><svg class="w-16 h-16 mr-4 text-success"><use xlink:href="#Check"></use></svg>{_("Translated")}</span>'
219
+ return mark_safe(
220
+ f'<span class="badge badge-simple badge-positive"><svg class="w-16 h-16 mr-4 text-success"><use xlink:href="#Check"></use></svg>{_("Translated")}</span>'
221
+ )
215
222
 
216
223
  def get_translated_fields(self):
217
224
  return SBAdminTranslationsService.get_translated_fields_for_model(self.model)