django-smartbase-admin 1.0.14__py3-none-any.whl → 1.0.16__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 (53) hide show
  1. django_smartbase_admin/admin/admin_base.py +14 -14
  2. django_smartbase_admin/admin/site.py +7 -1
  3. django_smartbase_admin/admin/widgets.py +13 -1
  4. django_smartbase_admin/engine/admin_base_view.py +14 -0
  5. django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
  6. django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +22 -13
  7. django_smartbase_admin/migrations/0005_sbadminuserconfiguration.py +26 -0
  8. django_smartbase_admin/models.py +33 -0
  9. django_smartbase_admin/services/configuration.py +13 -0
  10. django_smartbase_admin/static/sb_admin/build/tailwind.config.js +1 -0
  11. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/colors.js +4 -0
  12. django_smartbase_admin/static/sb_admin/css/ckeditor/ckeditor_content_dark.css +208 -0
  13. django_smartbase_admin/static/sb_admin/dist/calendar.js +1 -1
  14. django_smartbase_admin/static/sb_admin/dist/calendar_style.css +1 -1
  15. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  16. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  17. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  18. django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
  19. django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
  20. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
  21. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
  22. django_smartbase_admin/static/sb_admin/src/css/_base.css +1 -1
  23. django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
  24. django_smartbase_admin/static/sb_admin/src/css/_components.css +1 -1
  25. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +2 -2
  26. django_smartbase_admin/static/sb_admin/src/css/calendar.css +3 -3
  27. django_smartbase_admin/static/sb_admin/src/css/components/_button.css +1 -1
  28. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +25 -7
  29. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +33 -2
  30. django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
  31. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +1 -7
  32. django_smartbase_admin/static/sb_admin/src/css/style.css +1 -1
  33. django_smartbase_admin/static/sb_admin/src/js/main.js +71 -15
  34. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +10 -19
  35. django_smartbase_admin/static/sb_admin/src/js/utils.js +47 -23
  36. django_smartbase_admin/templates/sb_admin/actions/change_form.html +2 -2
  37. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  38. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +1 -1
  39. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +2 -2
  40. django_smartbase_admin/templates/sb_admin/navigation.html +12 -8
  41. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +11 -0
  42. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +1 -1
  43. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  44. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +2 -1
  45. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  46. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +4 -4
  47. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  48. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  49. django_smartbase_admin/views/user_config_view.py +35 -0
  50. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.16.dist-info}/METADATA +1 -1
  51. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.16.dist-info}/RECORD +53 -46
  52. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.16.dist-info}/LICENSE.md +0 -0
  53. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.16.dist-info}/WHEEL +0 -0
@@ -27,12 +27,13 @@ import Range from "./range"
27
27
  import Sorting from "./sorting"
28
28
  import Autocomplete from "./autocomplete"
29
29
  import ChoicesJS from "./choices"
30
- import {setCookie} from "./utils"
30
+ import {setCookie, setDropdownLabel} from "./utils"
31
31
  import Multiselect from "./multiselect"
32
32
 
33
33
  class Main {
34
34
  constructor() {
35
35
  document.body.classList.add('js-ready')
36
+ this.handleColorSchemeChange()
36
37
 
37
38
  const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
38
39
  tooltipTriggerList.map((tooltipTriggerEl) => {
@@ -104,6 +105,38 @@ class Main {
104
105
  this.handleLocationHashFromTabs()
105
106
  }
106
107
 
108
+ isDarkMode(colorScheme) {
109
+ let isDark = colorScheme === 'dark'
110
+ if(colorScheme === 'auto') {
111
+ isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
112
+ }
113
+ if(isDark) {
114
+ document.body.classList.add('dark')
115
+ }
116
+ else {
117
+ document.body.classList.remove('dark')
118
+ }
119
+ return isDark
120
+ }
121
+
122
+ handleColorSchemeChange() {
123
+ const picker = document.querySelector('.js-color-scheme-picker')
124
+ if(!picker) {
125
+ return
126
+ }
127
+ picker.addEventListener('change', (e)=>{
128
+ if(e.target.value) {
129
+ document.documentElement.setAttribute('data-theme', e.target.value)
130
+ const isDarkMode = this.isDarkMode(e.target.value)
131
+ this.switchCKEditorTheme(isDarkMode)
132
+ return
133
+ }
134
+ document.documentElement.removeAttribute('data-theme')
135
+ })
136
+ const isDarkMode = this.isDarkMode(document.documentElement.dataset.theme)
137
+ this.switchCKEditorTheme(isDarkMode)
138
+ }
139
+
107
140
  initInlines(target) {
108
141
  target = target || document
109
142
  const inlineGroups = target.querySelectorAll('.inline-group')
@@ -173,7 +206,7 @@ class Main {
173
206
  } else {
174
207
  offset = [0, 8]
175
208
  }
176
- return new Dropdown(dropdownToggleEl, {
209
+ const dropdown = new Dropdown(dropdownToggleEl, {
177
210
  autoClose: 'outside',
178
211
  offset: offset,
179
212
  popperConfig(defaultBsPopperConfig) {
@@ -184,6 +217,14 @@ class Main {
184
217
  return {...defaultBsPopperConfig, ...elementConf, strategy: 'fixed'}
185
218
  }
186
219
  })
220
+ const dropdownWrapper = dropdownToggleEl.closest('.js-dropdown-wrapper')
221
+ if(dropdownWrapper) {
222
+ const dropdownLabelEl = dropdownWrapper.querySelector('.js-dropdown-label')
223
+ dropdown._menu.addEventListener('change', ()=>{
224
+ setDropdownLabel(dropdown._menu, dropdownLabelEl)
225
+ })
226
+ }
227
+ return dropdown
187
228
  })
188
229
  }
189
230
 
@@ -304,28 +345,43 @@ class Main {
304
345
  })
305
346
  }
306
347
 
307
- initCKEditor(target) {
348
+ initCKEditor(target, config, force=false) {
308
349
  if (!window.CKEDITOR) {
309
350
  return
310
351
  }
311
352
  target = target || document
312
353
  target.querySelectorAll('textarea[data-type="ckeditortype"]').forEach((textarea) => {
313
- const id = textarea.id
314
- if (!id) {
315
- return
316
- }
317
- if(
318
- textarea.getAttribute("data-processed") == "0" &&
319
- textarea.id.indexOf("__prefix__") == -1
320
- ) {
321
- if(window.CKEDITOR.instances[id]) {
322
- window.CKEDITOR.instances[id].destroy(true)
323
- }
324
- window.CKEDITOR.replace(id, JSON.parse(textarea.getAttribute("data-config")))
354
+ if( force || (textarea.getAttribute("data-processed") == "0" && textarea.id.indexOf("__prefix__") == -1)) {
355
+ this.reinitCKEditor(textarea, config)
325
356
  }
326
357
  })
327
358
  }
328
359
 
360
+ reinitCKEditor(textarea, config) {
361
+ const id = textarea.id
362
+ if (!id) {
363
+ return
364
+ }
365
+ if(window.CKEDITOR.instances[id]) {
366
+ window.CKEDITOR.instances[id].destroy(true)
367
+ }
368
+ config = config || {}
369
+ const new_config = {...JSON.parse(textarea.getAttribute("data-config")), ...config}
370
+ window.CKEDITOR.replace(id, new_config)
371
+ }
372
+
373
+ switchCKEditorTheme(isDarkMode) {
374
+ if(!window.CKEDITOR) {
375
+ return
376
+ }
377
+
378
+ if(isDarkMode) {
379
+ this.initCKEditor(document, {'contentsCss': '/static/sb_admin/css/ckeditor/ckeditor_content_dark.css', uiColor: '#000000'}, true)
380
+ return
381
+ }
382
+ this.initCKEditor(document, {'contentsCss':window.CKEDITOR.config.contentsCss}, true)
383
+ }
384
+
329
385
  clearFilter(inputId) {
330
386
  const fieldElem = document.querySelector(`#${inputId}`)
331
387
  fieldElem.value = ''
@@ -1,3 +1,5 @@
1
+ import {setDropdownLabel} from "./utils"
2
+
1
3
  export default class Multiselect {
2
4
  constructor(selector_override, options_override, target) {
3
5
  target = target || document
@@ -9,9 +11,13 @@ export default class Multiselect {
9
11
  document.addEventListener('change', e => {
10
12
  const wrapperEl = e.target.closest(this.wrapperSelector)
11
13
  const multiselectInput = wrapperEl?.querySelector(selector)
14
+ if(!wrapperEl || !multiselectInput) {
15
+ // not filter widget
16
+ return
17
+ }
12
18
  const isCheckboxClicked = e.target.type === 'checkbox'
13
19
  const selectAllEl = wrapperEl?.querySelector(`.${selectAllClass}`)
14
- if(multiselectInput && isCheckboxClicked) {
20
+ if(isCheckboxClicked) {
15
21
  const checkboxes = Array.from(this.getCheckboxes(multiselectInput))
16
22
  if(e.target.classList.contains(selectAllClass)) {
17
23
  checkboxes.forEach(el => {
@@ -79,35 +85,20 @@ export default class Multiselect {
79
85
  })
80
86
  }
81
87
 
82
-
83
- setLabel(wrapper, valueEl) {
84
- let labels = []
85
- wrapper.querySelectorAll('input[type="checkbox"]').forEach(el => {
86
- if (el.checked) {
87
- labels.push(document.querySelector(`label[for="${el.id}"]`).innerText)
88
- }
89
- })
90
- valueEl.innerHTML = labels.join(',')
91
- }
92
-
93
88
  clearAll(wrapper, valueEl) {
94
89
  wrapper.querySelectorAll('input[type="checkbox"]').forEach(el => {
95
90
  el.checked = false
96
91
  })
97
- this.setLabel(wrapper, valueEl)
92
+ setDropdownLabel(wrapper, valueEl)
98
93
  }
99
94
 
100
95
 
101
96
  initDetailMultiselect(wrapper) {
102
- const valueEl = wrapper.querySelector('.js-value')
103
97
  const clearEl = wrapper.querySelector('.js-clear')
104
98
 
105
- wrapper.addEventListener('change', () => {
106
- this.setLabel(wrapper, valueEl)
107
- })
108
99
  clearEl?.addEventListener('click', () => {
109
- this.clearAll(wrapper, valueEl)
100
+ this.clearAll(wrapper)
110
101
  })
111
- this.setLabel(wrapper, valueEl)
102
+ setDropdownLabel(wrapper)
112
103
  }
113
104
  }
@@ -66,6 +66,52 @@ export const filterInputValueChangeListener = (inputSelector, callbackFunction)
66
66
  })
67
67
  }
68
68
 
69
+ const getResultLabel = (valueOrObject, separator=', ') => {
70
+ const labelArray = []
71
+ const entries = Object.values(valueOrObject)
72
+ let hasMaxEntries = false
73
+ for (let [index, item] of entries.entries()) {
74
+ if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN) {
75
+ break
76
+ }
77
+ if(entries.length > 1 && item.value === window.sb_admin_const.SELECT_ALL_KEYWORD) {
78
+ continue
79
+ }
80
+ if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN - 2 && entries[index + 2]) {
81
+ labelArray.push(item.label)
82
+ hasMaxEntries = true
83
+ break
84
+ }
85
+ labelArray.push(item.label)
86
+ }
87
+ let resultLabel = labelArray.join(separator)
88
+ if(hasMaxEntries) {
89
+ resultLabel = resultLabel.substring(0, resultLabel.length)
90
+ resultLabel += `... +${entries.length - window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN + 1}`
91
+ }
92
+ return resultLabel
93
+ }
94
+
95
+ export const setDropdownLabel = (dropdownMenuEl, dropdownLabelEl) => {
96
+ if(!dropdownMenuEl) {
97
+ return
98
+ }
99
+ if(!dropdownLabelEl) {
100
+ dropdownLabelEl = dropdownMenuEl.querySelector('.js-dropdown-label')
101
+ }
102
+ if(!dropdownLabelEl) {
103
+ return
104
+ }
105
+ const fields = Array.from(dropdownMenuEl.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked')).map(el => {
106
+ const label = el.parentElement.querySelector(`label[for="${el.id}"]`)
107
+ return {
108
+ 'value': el.value,
109
+ 'label': label?label.innerHTML:''
110
+ }
111
+ })
112
+ dropdownLabelEl.innerHTML = getResultLabel(fields)
113
+ }
114
+
69
115
  export const filterInputValueChangedUtil = (field) => {
70
116
  const filterId = field.dataset.filterId || field.id
71
117
  const separator = field.dataset.labelSeparator || ', '
@@ -88,29 +134,7 @@ export const filterInputValueChangedUtil = (field) => {
88
134
  return valueElem
89
135
  }
90
136
  if (typeof valueOrObject === 'object') {
91
- const labelArray = []
92
- const entries = Object.values(valueOrObject)
93
- let hasMaxEntries = false
94
- for (let [index, item] of entries.entries()) {
95
- if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN) {
96
- break
97
- }
98
- if(entries.length > 1 && item.value === window.sb_admin_const.SELECT_ALL_KEYWORD) {
99
- continue
100
- }
101
- if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN - 2 && entries[index + 2]) {
102
- labelArray.push(item.label)
103
- hasMaxEntries = true
104
- break
105
- }
106
- labelArray.push(item.label)
107
- }
108
- let resultLabel = labelArray.join(separator)
109
- if(hasMaxEntries) {
110
- resultLabel = resultLabel.substring(0, resultLabel.length)
111
- resultLabel += `... +${entries.length - window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN + 1}`
112
- }
113
- valueElem.innerHTML = resultLabel
137
+ valueElem.innerHTML = getResultLabel(valueOrObject, separator)
114
138
  } else {
115
139
  try {
116
140
  // select
@@ -223,9 +223,9 @@
223
223
  <button
224
224
  form="{% if sbadmin_is_modal %}modal_{% endif %}{{ opts.model_name }}_form"
225
225
  hx-post="{{ post_url }}"
226
- hx-target="#sb-admin-modal .modal-content"
226
+ hx-target="#sb-admin-modal"
227
227
  hx-swap="innerHTML"
228
- hx-select="#content"
228
+ hx-select="#modal-content"
229
229
  hx-encoding="multipart/form-data"
230
230
  hx-vals='{
231
231
  "sbadmin_is_modal": 1,
@@ -7,7 +7,7 @@
7
7
  data-bs-auto-close="outside"
8
8
  class="btn !rounded px-10 font-normal w-full"
9
9
  >
10
- <span id="{{ filter_widget.input_id }}-value" class="js-value"></span>
10
+ <span id="{{ filter_widget.input_id }}-value"></span>
11
11
  <svg class="ml-8">
12
12
  <use xlink:href="#Down"></use>
13
13
  </svg>
@@ -7,7 +7,7 @@
7
7
  {% for choice in filter_widget.choices %}
8
8
  <li>
9
9
  <div class="relative">
10
- <input onchange="document.getElementById('{{ filter_widget.input_id }}').value = event.target.value;document.getElementById('{{ filter_widget.input_id }}').dispatchEvent(new Event('change'))" type="radio" value="{{ choice.0 }}" name="{{ filter_widget.input_name }}_dummy" class="radio" id="{{ filter_widget.input_id }}_{{ choice.0 }}" {% if filter_widget.get_default_value == choice.0 %} checked{% endif %} data-label="{{ choice.1 }}">
10
+ <input onchange="document.getElementById('{{ filter_widget.input_id }}').value = event.target.value;document.getElementById('{{ filter_widget.input_id }}').dispatchEvent(new Event('change'))" type="radio" value="{{ choice.0 }}" name="{{ filter_widget.input_name }}_dummy" class="radio radio-list" id="{{ filter_widget.input_id }}_{{ choice.0 }}" {% if filter_widget.get_default_value == choice.0 %} checked{% endif %} data-label="{{ choice.1 }}">
11
11
  <label for="{{ filter_widget.input_id }}_{{ choice.0 }}">
12
12
  {{ choice.1|safe }}
13
13
  </label>
@@ -30,8 +30,8 @@
30
30
  data-bs-toggle="modal"
31
31
  data-bs-target="#sb-admin-modal"
32
32
  hx-get="{{ context_data.add_url }}?_popup=1&sbadmin_is_modal=1&sbadmin_reload_on_save=1&sbadmin_parent_instance_field=modal_{{ context_data.parent_data.sbadmin_parent_instance_field }}&sbadmin_parent_instance_pk={{ context_data.parent_data.sbadmin_parent_instance_pk }}&sbadmin_parent_instance_label={{ context_data.parent_data.sbadmin_parent_instance_label }}"
33
- hx-target="#sb-admin-modal .modal-content"
34
- hx-select="#content"
33
+ hx-target="#sb-admin-modal"
34
+ hx-select="#modal-content"
35
35
  hx-swap="innerHTML"
36
36
  >
37
37
  <svg class="w-20 h-20 md:mr-8">
@@ -17,7 +17,9 @@
17
17
  <nav class="w-260 xl:fixed top-0 bottom-0 left-0 max-lg:sidebar border-r border-dark-200 overflow-hidden flex flex-col relative bg-dark-50 z-1"
18
18
  id="main-navigation">
19
19
  <header class="px-24 py-32 hidden xl:!block">
20
- <img src="{{ LAZY_LOAD_DEFAULT_IMAGE }}" data-src="{% static 'sb_admin/images/logo.svg' %}" class="lazyload h-32 w-auto"
20
+ <img src="{{ LAZY_LOAD_DEFAULT_IMAGE }}" data-src="{% static 'sb_admin/images/logo.svg' %}" class="lazyload h-32 w-auto dark:hidden"
21
+ alt="SBAdmin" width="133" height="20">
22
+ <img src="{{ LAZY_LOAD_DEFAULT_IMAGE }}" data-src="{% static 'sb_admin/images/logo_light.svg' %}" class="lazyload h-32 w-auto hidden dark:block"
21
23
  alt="SBAdmin" width="133" height="20">
22
24
  </header>
23
25
  <header class="px-16 py-8 flex items-center xl:hidden border-b border-dark-200 bg-light min-h-56">
@@ -148,16 +150,18 @@
148
150
  </svg>
149
151
  {% trans 'Change password' %}
150
152
  </li>
151
- {% if request_data.global_filter_instance %}
152
- <li class="p-12 text-dark-900">
153
- {% trans 'Choose domain' %}
153
+ {% block color_scheme %}
154
+ <li class="border-t border-dark-100 pt-8">
155
+ <form hx-post="{% url 'sb_admin:color_scheme' %}" hx-trigger="change" hx-swap="none" id="color-schema-form" class="js-color-scheme-picker">
156
+ {{ color_scheme_form.color_scheme }}
157
+ </form>
154
158
  </li>
155
- <li>
159
+ {% endblock %}
160
+ {% if request_data.global_filter_instance %}
161
+ <li class="border-t border-dark-100 pt-8">
156
162
  <form hx-post="{% url 'sb_admin:global_filter' %}" hx-trigger="change" hx-swap="none" id="global_filter_form">
157
163
  {% for field in request_data.global_filter_instance %}
158
- <div class="relative">
159
- {{ field }}
160
- </div>
164
+ {{ field }}
161
165
  {{ field.errors }}
162
166
  {% endfor %}
163
167
  </form>
@@ -5,6 +5,12 @@
5
5
  {% get_item request.META 'HTTP_SEC_FETCH_SITE' as sec_fetch_site %}
6
6
  <div class="w-full h-full min-h-full{% if not sec_fetch_site or request.META.HTTP_SEC_FETCH_DEST == 'document' %} xl:pl-260{% endif %}">
7
7
  {% include 'sb_admin/navigation.html' %}
8
+
9
+ {% if sbadmin_is_modal %}
10
+ <div id="modal-content" class="modal-dialog">
11
+ <div class="modal-content">
12
+ {% endif %}
13
+
8
14
  <div id="content" class="relative flex flex-col{% if sbadmin_is_modal %} min-h-0 h-full{% else %} min-h-screen sm:p-24 {% block view_class %}{% endblock %}{% endif %}">
9
15
  {% if sbadmin_is_modal %}
10
16
  {# load media which are missing #}
@@ -45,5 +51,10 @@
45
51
  {% endblock %}
46
52
  {{ inner_content }}
47
53
  </div>
54
+
55
+ {% if sbadmin_is_modal %}
56
+ </div>
57
+ </div>
58
+ {% endif %}
48
59
  </div>
49
60
  {% endblock %}
@@ -1,7 +1,7 @@
1
1
  {% load static %}
2
2
 
3
3
  <!DOCTYPE html>
4
- <html lang="{{ request.LANGUAGE_CODE }}" data-theme="light" data-color-scheme="light">
4
+ <html lang="{{ request.LANGUAGE_CODE }}" data-theme="{{ user_config.color_scheme }}">
5
5
  <head>
6
6
  {% include 'sb_admin/fonts.html' %}
7
7
  <meta name="robots" content="noindex"/>