django-smartbase-admin 1.0.7__py3-none-any.whl → 1.0.9__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 (26) hide show
  1. django_smartbase_admin/admin/admin_base.py +0 -3
  2. django_smartbase_admin/admin/widgets.py +28 -20
  3. django_smartbase_admin/engine/dashboard.py +15 -1
  4. django_smartbase_admin/engine/filter_widgets.py +3 -2
  5. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  6. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  7. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  8. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +1 -1
  9. django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +32 -0
  10. django_smartbase_admin/static/sb_admin/src/js/chart.js +4 -3
  11. django_smartbase_admin/static/sb_admin/src/js/main.js +1 -1
  12. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +2 -2
  13. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +1 -1
  14. django_smartbase_admin/templates/sb_admin/actions/list.html +43 -29
  15. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +1 -1
  16. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +1 -0
  17. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -36
  18. django_smartbase_admin/templates/sb_admin/widgets/date.html +1 -1
  19. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +23 -18
  20. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  21. django_smartbase_admin/templates/sb_admin/widgets/time.html +1 -1
  22. django_smartbase_admin/views/dashboard_view.py +6 -0
  23. {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/METADATA +1 -1
  24. {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/RECORD +26 -26
  25. {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/LICENSE.md +0 -0
  26. {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/WHEEL +0 -0
@@ -33,7 +33,7 @@
33
33
  }
34
34
 
35
35
  .dropdown-menu-link {
36
- display: flex;
36
+ @apply flex items-center;
37
37
  @apply py-12 px-16;
38
38
  @apply text-dark-700;
39
39
  white-space: nowrap;
@@ -94,6 +94,9 @@ export default class Autocomplete {
94
94
  }, 200))
95
95
 
96
96
  choiceInput.addEventListener('selectItem', (event) => {
97
+ if(!event.target.hasAttribute('multiple')) {
98
+ this.updateEditButtonUrl(event)
99
+ }
97
100
  choicesJSListeners.selectItem(event.detail, inputEl)
98
101
  })
99
102
  choiceInput.addEventListener('addItem', () => {
@@ -105,6 +108,7 @@ export default class Autocomplete {
105
108
  choiceInput.addEventListener('change', (e) => {
106
109
  if(!e.target.hasAttribute('multiple')) {
107
110
  choicesJS.SBwrapperElButton.click()
111
+ this.updateEditButtonUrl(e)
108
112
  }
109
113
  inputEl.dispatchEvent(new CustomEvent('SBAutocompleteChange'))
110
114
  })
@@ -295,4 +299,32 @@ export default class Autocomplete {
295
299
  document.getElementById(`${field}-value`).textContent = label
296
300
  }
297
301
  }
302
+
303
+ updateEditButtonUrl(event) {
304
+ const group = event.srcElement.dataset.autocompleteDataId?.replace("_data", "-wrapper")
305
+ if (!group) return
306
+
307
+ const wrapper = document.getElementById(group)
308
+ const editButton = wrapper?.querySelector('.edit-button')
309
+ const value = event.detail?.value
310
+
311
+ if (editButton && value) {
312
+ let originalUrl = editButton.getAttribute('hx-get') || editButton.dataset.addUrl || ''
313
+ if (!originalUrl) return
314
+
315
+ const newUrl = originalUrl
316
+ .replace(/\/add\/\?/, `/${value}/change/?`)
317
+ .replace(/\/\d+\/change\/\?/, `/${value}/change/?`)
318
+
319
+ const newEditButton = editButton.cloneNode(true)
320
+ newEditButton.setAttribute('hx-get', newUrl)
321
+ newEditButton.classList.remove('hidden')
322
+
323
+ editButton.replaceWith(newEditButton)
324
+ window.htmx.process(newEditButton)
325
+ }
326
+ }
327
+
328
+
329
+
298
330
  }
@@ -24,6 +24,7 @@ class SBAdminChart {
24
24
  }
25
25
 
26
26
  initChart() {
27
+ this.options.formId = this.options.formId || `${this.options.widgetId}-filter-form`
27
28
  const ctx = document.getElementById(`${this.options.widgetId}-chart`)
28
29
  this.chart = new Chart(ctx, {
29
30
  type: this.options.chartType,
@@ -47,7 +48,7 @@ class SBAdminChart {
47
48
  }
48
49
 
49
50
  refreshData() {
50
- const filterForm = document.querySelector(`#${this.options.widgetId}-filter-form`)
51
+ const filterForm = document.getElementById(this.options.formId)
51
52
  const filterData = new FormData(filterForm).entries()
52
53
  const filterDataNotEmpty = {}
53
54
  for (const [key, value] of filterData) {
@@ -65,7 +66,7 @@ class SBAdminChart {
65
66
  .then(res => {
66
67
  this.chart.data.labels = res.data.main.labels
67
68
  this.chart.data.datasets = this.processDatasets(res.data.main.datasets)
68
- if (this.chart.data.labels.length > 1) {
69
+ if (this.chart.data.labels.length >= 1) {
69
70
  this.chart.canvas.classList.remove('!hidden')
70
71
  } else {
71
72
  this.chart.canvas.classList.add('!hidden')
@@ -101,7 +102,7 @@ class SBAdminChart {
101
102
  }
102
103
 
103
104
  initFilters() {
104
- filterInputValueChangeListener(`[form="${this.options.widgetId}-filter-form"]`, (event) => {
105
+ filterInputValueChangeListener(`[form="${this.options.formId}"]`, (event) => {
105
106
  this.refreshData()
106
107
  filterInputValueChangedUtil(event.target)
107
108
  })
@@ -284,7 +284,7 @@ class Main {
284
284
  const id = textarea.id
285
285
  if (!id) return
286
286
  if (window.CKEDITOR.instances[id]) {
287
- return
287
+ window.CKEDITOR.instances[id].destroy(true)
288
288
  }
289
289
  window.CKEDITOR.replace(id)
290
290
  })
@@ -10,7 +10,7 @@ export default class Multiselect {
10
10
  const wrapperEl = e.target.closest(this.wrapperSelector)
11
11
  const multiselectInput = wrapperEl?.querySelector(selector)
12
12
  const isCheckboxClicked = e.target.type === 'checkbox'
13
- const selectAllEl = wrapperEl.querySelector(`.${selectAllClass}`)
13
+ const selectAllEl = wrapperEl?.querySelector(`.${selectAllClass}`)
14
14
  if(multiselectInput && isCheckboxClicked) {
15
15
  const checkboxes = Array.from(this.getCheckboxes(multiselectInput))
16
16
  if(e.target.classList.contains(selectAllClass)) {
@@ -20,7 +20,7 @@ export default class Multiselect {
20
20
  } else {
21
21
  selectAllEl.checked = false
22
22
  }
23
- if(!checkboxes.some(el => el.checked)) {
23
+ if(!checkboxes.some(el => el.checked) && selectAllEl) {
24
24
  selectAllEl.checked = true
25
25
  }
26
26
  let checked = []
@@ -3,7 +3,7 @@
3
3
 
4
4
  {% block content %}
5
5
  <div class="w-full max-w-1180 mx-auto">
6
- {% for widget in sub_views %}
6
+ {% for widget in direct_sub_views %}
7
7
  {% render_widget widget request %}
8
8
  {% endfor %}
9
9
  </div>
@@ -5,34 +5,48 @@
5
5
 
6
6
  {% block content %}
7
7
  {% block page_header %}
8
- <div class="list-view-header py-16 md:pb-32 flex items-center max-xs:px-20">
8
+ <div class="list-view-header py-16 md:pb-32 flex flex-wrap items-center max-xs:px-20 gap-x-16 gap-y-8">
9
9
  {% block heading %}
10
10
  <h1 class="text-24 md:text-30 text-dark-900 font-bold font-heading line-clamp-1 first-letter:uppercase">{{ list_title|capfirst }}</h1>
11
11
  {% endblock %}
12
- <ul class="ml-auto flex gap-8 md:gap-16">
13
- <li class="relative md:hidden">
14
- <button
15
- data-bs-toggle="dropdown"
16
- aria-expanded="false"
17
- data-bs-popper-placement="bottom-end"
18
- class="btn btn-empty btn-icon">
19
- <svg class="w-16 h-16">
20
- <use xlink:href="#More"></use>
21
- </svg>
22
- <span></span>
23
- </button>
24
- <div class="dropdown-menu w-248 max-h-432">
25
- <ul>
26
- {% block dropdown_actions %}
27
- {% for list_action in content_context.list_actions %}
28
- <li>
29
- {% include "sb_admin/actions/partials/action_link.html" with action=list_action view_id=view_id extra_classes="dropdown-menu-link" %}
30
- </li>
31
- {% endfor %}
32
- {% endblock %}
33
- </ul>
34
- </div>
35
- </li>
12
+ <ul class="ml-auto flex flex-wrap gap-8 md:gap-16">
13
+ {% if content_context.list_actions %}
14
+ <li class="relative md:hidden">
15
+ <button
16
+ data-bs-toggle="dropdown"
17
+ aria-expanded="false"
18
+ data-bs-popper-placement="bottom-end"
19
+ class="btn btn-empty btn-icon">
20
+ <svg class="w-16 h-16">
21
+ <use xlink:href="#More"></use>
22
+ </svg>
23
+ <span></span>
24
+ </button>
25
+ <div class="dropdown-menu w-248 max-h-432">
26
+ <ul>
27
+ {% block dropdown_actions %}
28
+ {% for list_action in content_context.list_actions %}
29
+ {% if not list_action.sub_actions %}
30
+ <li>
31
+ {% include "sb_admin/actions/partials/action_link.html" with action=list_action view_id=view_id extra_classes="dropdown-menu-link items-center" %}
32
+ </li>
33
+ {% endif %}
34
+ {% endfor %}
35
+ <script>
36
+ (function(){
37
+ if(document.currentScript && !document.currentScript.previousElementSibling) {
38
+ const li = document.currentScript.closest('li');
39
+ if(li) {
40
+ li.classList.add('hidden');
41
+ }
42
+ }
43
+ }());
44
+ </script>
45
+ {% endblock %}
46
+ </ul>
47
+ </div>
48
+ </li>
49
+ {% endif %}
36
50
 
37
51
  {% block actions %}
38
52
  {% for list_action in content_context.list_actions %}
@@ -40,15 +54,15 @@
40
54
  <button
41
55
  data-bs-toggle="dropdown"
42
56
  aria-expanded="false"
43
- data-bs-popper-placement="bottom"
44
- class="btn">
57
+ data-bs-popper-placement="bottom-end"
58
+ class="btn btn-icon">
45
59
  {% if list_action.icon %}
46
60
  <svg class="w-16 h-16 md:mr-8">
47
61
  <use xlink:href="#{{ list_action.icon }}"></use>
48
62
  </svg>
49
63
  {% endif %}
50
64
  <span>{{ list_action.title }}</span>
51
- <svg class="w-16 h-16 ml-4">
65
+ <svg class="w-16 h-16 ml-4 max-sm:hidden">
52
66
  <use xlink:href="#Down"></use>
53
67
  </svg>
54
68
  </button>
@@ -56,7 +70,7 @@
56
70
  <ul>
57
71
  {% for list_sub_action in list_action.sub_actions %}
58
72
  <li>
59
- {% include "sb_admin/actions/partials/action_link.html" with action=list_sub_action view_id=view_id extra_classes="dropdown-menu-link" %}
73
+ {% include "sb_admin/actions/partials/action_link.html" with action=list_sub_action view_id=view_id extra_classes="dropdown-menu-link" %}
60
74
  </li>
61
75
  {% endfor %}
62
76
  </ul>
@@ -6,7 +6,7 @@
6
6
  {% endif %}
7
7
  class="{{ extra_classes|default:'' }} {{ action.css_class|default_if_none:'' }}">
8
8
  {% if action.icon %}
9
- <svg class="w-16 h-16 md:mr-8">
9
+ <svg class="w-16 h-16 mr-8">
10
10
  <use xlink:href="#{{ action.icon }}"></use>
11
11
  </svg>
12
12
  {% endif %}
@@ -17,6 +17,7 @@
17
17
  <script>
18
18
  window.SBAdminChart = window.SBAdminChart || {};
19
19
  window.SBAdminChart["{{ widget_id }}"] = new window.SBAdminChartClass({
20
+ "formId": "{{ widget_id }}-filter-form",
20
21
  "widgetId": "{{ widget_id }}",
21
22
  "ajaxUrl": "{{ ajax_url }}",
22
23
  "chartType": "{{ chart_type }}",
@@ -1,46 +1,58 @@
1
1
  {% load sb_admin_tags %}
2
2
 
3
- {% if fieldset.name %}
4
- <header class="mb-24">
5
- <span class="text-dark-900">{{ fieldset.name }}</span>
6
- <div class="ml-auto flex items-center">
7
- {% for action in fieldset_context.actions %}
8
- <a href="{{ action.url }}" class="btn btn-small">{{ action.title }}</a>
9
- {% endfor %}
10
- {% if fieldset.description %}
11
- <div class="js-tooltip ml-8" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{ fieldset.description|safe }}">
12
- <svg class="w-20 h-20">
13
- <use xlink:href="#Help"></use>
14
- </svg>
3
+ {% block content %}
4
+ {% block header %}
5
+ {% if fieldset.name %}
6
+ <header class="mb-24">
7
+ <span class="text-dark-900">{{ fieldset.name }}</span>
8
+ <div class="ml-auto flex items-center">
9
+ {% block actions %}
10
+ {% for action in fieldset_context.actions %}
11
+ <a href="{{ action.url }}" class="btn btn-small">{{ action.title }}</a>
12
+ {% endfor %}
13
+ {% endblock %}
14
+ {% block description %}
15
+ {% if fieldset.description %}
16
+ <div class="js-tooltip ml-8" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{ fieldset.description|safe }}">
17
+ <svg class="w-20 h-20">
18
+ <use xlink:href="#Help"></use>
19
+ </svg>
20
+ </div>
21
+ {% endif %}
22
+ {% endblock %}
15
23
  </div>
16
- {% endif %}
17
- </div>
18
- </header>
19
- {% endif %}
20
- {% for line in fieldset %}
21
- <div class="field{% if forloop.last %} -mb-24{% endif %}{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
22
- {% if line.fields|length > 1 %}
23
- <div class="flex max-xs:flex-wrap gap-x-16">{% endif %}
24
+ </header>
25
+ {% endif %}
26
+ {% endblock %}
27
+
28
+ {% block lines %}
29
+ {% for line in fieldset %}
30
+ <div class="field{% if forloop.last %} -mb-24{% endif %}{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
31
+ {% if line.fields|length > 1 %}
32
+ <div class="flex max-xs:flex-wrap gap-x-16">{% endif %}
33
+
34
+ {% for field in line %}
35
+ <div class="mb-16 sm:mb-24 max-xs:w-full sm:flex-1{% if field.field.is_hidden or field.field|is_row_class_field %} hidden{% endif %}">
36
+ {% if field.is_readonly %}
37
+ {% call_method field "contents" request %}
38
+ {% else %}
39
+ {{ field.field }}
40
+ {% if not line.fields|length == 1 %}{{ field.errors }}{% endif %}
41
+ {% endif %}
42
+ </div>
43
+ {% endfor %}
24
44
 
25
- {% for field in line %}
26
- <div class="mb-16 sm:mb-24 max-xs:w-full sm:flex-1{% if field.field.is_hidden or field.field|is_row_class_field %} hidden{% endif %}">
27
- {% if field.is_readonly %}
28
- {% call_method field "contents" request %}
29
- {% else %}
30
- {{ field.field }}
31
- {% if not line.fields|length == 1 %}{{ field.errors }}{% endif %}
45
+ {% if line.fields|length == 1 and line.errors %}
46
+ <div class="-mt-16 mb-24">
47
+ {{ line.errors }}
48
+ </div>
32
49
  {% endif %}
50
+ {% if line.fields|length > 1 %}</div>{% endif %}
33
51
  </div>
34
52
  {% endfor %}
35
53
 
36
- {% if line.fields|length == 1 and line.errors %}
37
- <div class="-mt-16 mb-24">
38
- {{ line.errors }}
39
- </div>
40
- {% endif %}
41
- {% if line.fields|length > 1 %}</div>{% endif %}
42
- </div>
43
- {% endfor %}
44
- {% block additional_fields %}
54
+ {% endblock %}
55
+ {% block additional_fields %}
45
56
 
57
+ {% endblock %}
46
58
  {% endblock %}
@@ -1,5 +1,5 @@
1
1
  {% include 'sb_admin/widgets/includes/field_label.html' %}
2
- <div class="relative inline-flex">
2
+ <div class="relative flex">
3
3
  {% include "sb_admin/widgets/input.html" %}
4
4
  <label class="absolute right-10 top-0 bottom-0 h-full flex items-center pointer-events-none" for="{{ widget.attrs.id }}">
5
5
  <svg class="w-20 h-20">
@@ -1,28 +1,33 @@
1
1
  {% load i18n %}
2
- <div class="flex items-center gap-4 flex-shrink-0">
3
- {% if widget.attrs.related_edit_url %}
4
- <a href="{{ widget.attrs.related_edit_url }}"
5
- class="btn btn-icon"
6
- title="{% trans 'Edit' %}"
7
- data-bs-toggle="modal"
8
- data-bs-target="#sb-admin-modal"
9
- hx-get="{{ widget.attrs.related_edit_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
10
- hx-target="#sb-admin-modal .modal-content"
11
- hx-swap="innerHTML">
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 }}"
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 .modal-content"
16
+ hx-swap="innerHTML">
12
17
  <svg class="w-20 h-20">
13
18
  <use xlink:href="#Preview-open"></use>
14
19
  </svg>
15
20
  </a>
16
21
  {% endif %}
17
22
  {% if widget.attrs.related_add_url %}
18
- <a href="{{ widget.attrs.related_add_url }}"
19
- class="btn btn-icon"
20
- title="{% trans 'Add' %}"
21
- data-bs-toggle="modal"
22
- data-bs-target="#sb-admin-modal"
23
- hx-get="{{ widget.attrs.related_add_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
24
- hx-target="#sb-admin-modal .modal-content"
25
- hx-swap="innerHTML">
23
+ <a
24
+ class="btn btn-icon"
25
+ title="{% trans 'Add' %}"
26
+ data-bs-toggle="modal"
27
+ data-bs-target="#sb-admin-modal"
28
+ hx-get="{{ widget.attrs.related_add_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
29
+ hx-target="#sb-admin-modal .modal-content"
30
+ hx-swap="innerHTML">
26
31
  <svg class="w-20 h-20">
27
32
  <use xlink:href="#Add-one"></use>
28
33
  </svg>
@@ -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,5 +1,5 @@
1
1
  {% include 'sb_admin/widgets/includes/field_label.html' %}
2
- <div class="relative inline-flex time-wrapper">
2
+ <div class="relative flex time-wrapper">
3
3
  {% include "sb_admin/widgets/input.html" %}
4
4
  <label class="absolute right-10 top-0 bottom-0 h-full flex items-center pointer-events-none" for="{{ widget.attrs.id }}">
5
5
  <svg class="w-20 h-20">
@@ -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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-smartbase-admin
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary:
5
5
  Home-page: https://smartbase-sk.github.io/django-smartbase-admin-docs/
6
6
  License: MIT