django-smartbase-admin 0.2.54__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 (179) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +74 -38
  2. django_smartbase_admin/actions/advanced_filters.py +24 -1
  3. django_smartbase_admin/admin/admin_base.py +401 -96
  4. django_smartbase_admin/admin/site.py +93 -35
  5. django_smartbase_admin/admin/widgets.py +589 -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 +44 -23
  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 +22 -8
  15. django_smartbase_admin/engine/filter_widgets.py +309 -20
  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/sprites/sb_admin/Bolt-one.svg +3 -0
  65. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Calendar.svg +3 -0
  66. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Caution.svg +3 -0
  67. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Electric-drill.svg +3 -0
  68. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Fire-extinguisher.svg +3 -0
  69. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Gas.svg +3 -0
  70. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Lightning-fill.svg +3 -0
  71. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
  72. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Phone-telephone.svg +3 -0
  73. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Printer.svg +3 -0
  74. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.svg +3 -0
  75. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
  76. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Time.svg +3 -0
  77. django_smartbase_admin/static/sb_admin/src/css/_base.css +5 -1
  78. django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
  79. django_smartbase_admin/static/sb_admin/src/css/_components.css +61 -13
  80. django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +8 -1
  81. django_smartbase_admin/static/sb_admin/src/css/_filer.css +60 -0
  82. django_smartbase_admin/static/sb_admin/src/css/_inlines.css +51 -10
  83. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +8 -2
  84. django_smartbase_admin/static/sb_admin/src/css/calendar.css +162 -0
  85. django_smartbase_admin/static/sb_admin/src/css/components/_button.css +41 -1
  86. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +26 -8
  87. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +62 -20
  88. django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
  89. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +21 -2
  90. django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +12 -1
  91. django_smartbase_admin/static/sb_admin/src/css/components/_tooltip.css +8 -22
  92. django_smartbase_admin/static/sb_admin/src/css/style.css +17 -0
  93. django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +411 -0
  94. django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +63 -5
  95. django_smartbase_admin/static/sb_admin/src/js/calendar.js +56 -0
  96. django_smartbase_admin/static/sb_admin/src/js/chart.js +8 -22
  97. django_smartbase_admin/static/sb_admin/src/js/choices.js +18 -8
  98. django_smartbase_admin/static/sb_admin/src/js/datepicker.js +97 -336
  99. django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +357 -0
  100. django_smartbase_admin/static/sb_admin/src/js/main.js +304 -31
  101. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
  102. django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
  103. django_smartbase_admin/static/sb_admin/src/js/table.js +34 -5
  104. django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +43 -20
  105. django_smartbase_admin/static/sb_admin/src/js/table_modules/data_edit_module.js +8 -10
  106. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -3
  107. django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
  108. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
  109. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  110. django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +6 -0
  111. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
  112. django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
  113. django_smartbase_admin/templates/sb_admin/actions/change_form.html +169 -114
  114. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
  115. django_smartbase_admin/templates/sb_admin/actions/list.html +79 -39
  116. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
  117. django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
  118. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  119. django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
  120. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  121. django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
  122. django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
  123. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
  124. django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
  125. django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
  126. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
  127. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  128. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  129. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
  130. django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
  131. django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +10 -5
  132. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +2 -2
  133. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  134. django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
  135. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
  136. django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
  137. django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
  138. django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
  139. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  140. django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
  141. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +76 -34
  142. django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
  143. django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
  144. django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
  145. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
  146. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +27 -11
  147. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
  148. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  149. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
  150. django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
  151. django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
  152. django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
  153. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  154. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  155. django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
  156. django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
  157. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
  158. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
  159. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  160. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  161. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  162. django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
  163. django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
  164. django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
  165. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
  166. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  167. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  168. django_smartbase_admin/templatetags/sb_admin_tags.py +85 -4
  169. django_smartbase_admin/utils.py +22 -3
  170. django_smartbase_admin/views/dashboard_view.py +6 -0
  171. django_smartbase_admin/views/global_filter_view.py +8 -2
  172. django_smartbase_admin/views/translations_view.py +12 -5
  173. django_smartbase_admin/views/user_config_view.py +52 -0
  174. django_smartbase_admin-1.0.38.dist-info/METADATA +166 -0
  175. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/RECORD +177 -115
  176. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/WHEEL +1 -1
  177. django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
  178. django_smartbase_admin-0.2.54.dist-info/METADATA +0 -25
  179. {django_smartbase_admin-0.2.54.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,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()
@@ -78,7 +87,7 @@ def sb_admin_render_form_field(context, form_field, label_as_placeholder=False):
78
87
  @register.simple_tag
79
88
  def get_tabular_context(fieldsets, inlines, tabs):
80
89
  default_tabs = False
81
- has_error = False
90
+ any_error = False
82
91
  first_error_tab = True
83
92
  tabular_context = {}
84
93
  inlines_map = {inline.opts.__class__: inline for inline in inlines}
@@ -90,6 +99,7 @@ def get_tabular_context(fieldsets, inlines, tabs):
90
99
  default_tabs = True
91
100
  for key, values in tabs.items():
92
101
  for value in values:
102
+ has_error = False
93
103
  fieldset_value = fieldsets_map.get(value)
94
104
  inline_value = inlines_map.get(value)
95
105
  tabular_context[key] = tabular_context.get(
@@ -99,7 +109,15 @@ def get_tabular_context(fieldsets, inlines, tabs):
99
109
  tabular_context[key]["content"].append(
100
110
  {"type": "fieldset", "value": fieldset_value}
101
111
  )
102
- 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
+ )
103
121
  has_error = has_error or error_present
104
122
  tabular_context[key]["error"] = (
105
123
  tabular_context[key]["error"] or error_present
@@ -114,18 +132,19 @@ def get_tabular_context(fieldsets, inlines, tabs):
114
132
  tabular_context[key]["error"] or error_present
115
133
  )
116
134
  if has_error:
135
+ any_error = True
117
136
  tabular_context[key]["classes"].add("error")
118
137
  if first_error_tab:
119
138
  tabular_context[key]["classes"].update(["active", "show"])
120
139
  first_error_tab = False
121
- if not has_error:
140
+ if not any_error:
122
141
  tabular_context[list(tabular_context.keys())[0]]["classes"].update(
123
142
  ["active", "show"]
124
143
  )
125
144
  return {
126
145
  "context": tabular_context,
127
146
  "default_tabs": default_tabs,
128
- "has_error": has_error,
147
+ "has_error": any_error,
129
148
  }
130
149
 
131
150
 
@@ -202,3 +221,65 @@ def get_log_entry_message(log_entry):
202
221
  return get_change_message_legacy(log_entry)
203
222
  except Exception as e:
204
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
@@ -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)
@@ -0,0 +1,52 @@
1
+ from django import forms
2
+ from django.http import HttpResponse
3
+ from django.utils.html import format_html
4
+ from django.views.generic import FormView
5
+
6
+ from django_smartbase_admin.admin.admin_base import SBAdminBaseFormInit
7
+ from django_smartbase_admin.admin.widgets import (
8
+ SBAdminRadioDropdownWidget,
9
+ )
10
+ from django_smartbase_admin.models import SBAdminUserConfiguration, ColorScheme
11
+ from django_smartbase_admin.services.configuration import (
12
+ SBAdminUserConfigurationService,
13
+ )
14
+
15
+
16
+ class ColorSchemeForm(SBAdminBaseFormInit, forms.ModelForm):
17
+ color_scheme_icons = {
18
+ ColorScheme.AUTO.value: "Translation",
19
+ ColorScheme.DARK.value: "Moon",
20
+ ColorScheme.LIGHT.value: "Sun-one",
21
+ }
22
+
23
+ class Meta:
24
+ model = SBAdminUserConfiguration
25
+ fields = ("color_scheme",)
26
+ widgets = {
27
+ "color_scheme": SBAdminRadioDropdownWidget(),
28
+ }
29
+ required = []
30
+
31
+ def __init__(self, *args, **kwargs):
32
+ super().__init__(*args, **kwargs)
33
+ choices_formatted = []
34
+ for choice in self.fields["color_scheme"].choices:
35
+ choice_label = format_html(
36
+ f'<span class="flex gap-8"><svg class="w-20 h-20"><use href="#{self.color_scheme_icons.get(choice[0])}"></use></svg><span>{choice[1]}</span></span>'
37
+ )
38
+ choices_formatted.append((choice[0], choice_label))
39
+ self.fields["color_scheme"].choices = choices_formatted
40
+
41
+
42
+ class ColorSchemeView(FormView):
43
+ form_class = ColorSchemeForm
44
+
45
+ def form_valid(self, form):
46
+ instance = form.save(commit=False)
47
+ sb_admin_user_config = SBAdminUserConfigurationService.get_user_config(
48
+ self.request
49
+ )
50
+ sb_admin_user_config.color_scheme = instance.color_scheme
51
+ sb_admin_user_config.save(update_fields=["color_scheme"])
52
+ return HttpResponse(status=200)