django-smartbase-admin 0.2.87__py3-none-any.whl → 0.2.89__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 (25) hide show
  1. django_smartbase_admin/admin/admin_base.py +0 -1
  2. django_smartbase_admin/engine/admin_base_view.py +1 -0
  3. django_smartbase_admin/engine/filter_widgets.py +18 -13
  4. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  5. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  6. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  7. django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
  8. django_smartbase_admin/static/sb_admin/src/css/_components.css +26 -0
  9. django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +3 -0
  10. django_smartbase_admin/static/sb_admin/src/css/_inlines.css +1 -0
  11. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +2 -1
  12. django_smartbase_admin/static/sb_admin/src/js/datepicker.js +71 -9
  13. django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +72 -46
  14. django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +16 -9
  15. django_smartbase_admin/static/sb_admin/src/js/utils.js +5 -0
  16. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +9 -2
  17. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +15 -5
  18. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +1 -1
  19. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  20. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +21 -3
  21. django_smartbase_admin/views/translations_view.py +1 -1
  22. {django_smartbase_admin-0.2.87.dist-info → django_smartbase_admin-0.2.89.dist-info}/METADATA +1 -1
  23. {django_smartbase_admin-0.2.87.dist-info → django_smartbase_admin-0.2.89.dist-info}/RECORD +25 -25
  24. {django_smartbase_admin-0.2.87.dist-info → django_smartbase_admin-0.2.89.dist-info}/LICENSE.md +0 -0
  25. {django_smartbase_admin-0.2.87.dist-info → django_smartbase_admin-0.2.89.dist-info}/WHEEL +0 -0
@@ -467,4 +467,30 @@
467
467
  .dropdown-menu {
468
468
  @apply py-0;
469
469
  }
470
+
471
+ .sticky-table-head,
472
+ .sticky-table-col {
473
+ position: sticky !important;
474
+ right: 0;
475
+ z-index: 1;
476
+ @apply overflow-hidden !border-l-0 pl-8;
477
+ &:before {
478
+ content: '';
479
+ box-shadow: 0 0 8px 0 rgba(17, 24, 39, 0.16);
480
+ @apply absolute inset-0 transition-colors ml-8;
481
+ }
482
+ }
483
+
484
+ .sticky-table-head {
485
+ @apply bg-transparent !important;
486
+ &:before {
487
+ @apply bg-dark-50;
488
+ }
489
+ }
490
+
491
+ .sticky-table-col {
492
+ &:before {
493
+ @apply bg-light;
494
+ }
495
+ }
470
496
  }
@@ -617,6 +617,9 @@ span.flatpickr-weekday {
617
617
  .flatpickr-shortcuts {
618
618
  @apply pt-4 mb-16;
619
619
 
620
+ input[type="radio"] {
621
+ @apply hidden;
622
+ }
620
623
  & > label {
621
624
  @apply p-0;
622
625
  }
@@ -9,6 +9,7 @@
9
9
  min-width: 100%;
10
10
  @apply h-full;
11
11
  > .djn-thead {
12
+ @apply bg-dark-50;
12
13
  th {
13
14
  @apply p-8 h-48;
14
15
  @apply transition-colors;
@@ -1,5 +1,6 @@
1
1
  .query-builder {
2
- select:not(.select2-hidden-accessible), input:not(.checkbox):not(.choices__input) {
2
+ select:not(.select2-hidden-accessible),
3
+ input:not(.checkbox):not(.radio):not(.choices__input) {
3
4
  @apply !w-full input;
4
5
  }
5
6
 
@@ -1,6 +1,10 @@
1
1
  import flatpickr from "flatpickr"
2
2
  import {createIcon} from "./utils"
3
- import {customActionsPlugin, HIDE_CALENDAR_CLASS, monthYearViewsPlugin} from "./datepicker_plugins"
3
+ import {
4
+ createRadioInput,
5
+ customActionsPlugin,
6
+ monthYearViewsPlugin
7
+ } from "./datepicker_plugins"
4
8
 
5
9
 
6
10
  export default class Datepicker {
@@ -28,20 +32,38 @@ export default class Datepicker {
28
32
  if(datePickerEl.dataset.sbadminDatepicker) {
29
33
  sbadminDatepickerData = JSON.parse(datePickerEl.dataset.sbadminDatepicker)
30
34
  }
35
+
31
36
  flatpickr(datePickerEl, {
32
37
  onReady: (selectedDates, dateStr, instance) => {
33
- const isInTable = datePickerEl.closest('[data-filter-input-name]')
34
- if(!isInTable) {
35
- this.createClear(instance)
36
- }
37
38
  instance.nextMonthNav?.replaceChildren(createIcon('Right-small'))
38
39
  instance.prevMonthNav?.replaceChildren(createIcon('Left-small'))
39
- datePickerEl.addEventListener('clear', () => {
40
+
41
+ const isInTable = datePickerEl.closest('[data-filter-input-name]')
42
+
43
+ // real input element should be present only in filters
44
+ const realInput = document.getElementById(datePickerEl.dataset.sbadminDatepickerRealInputId)
45
+ const mainInput = realInput || datePickerEl
46
+ mainInput.addEventListener('clear', () => {
40
47
  instance.clear()
41
48
  })
42
- if(datePickerEl.classList.contains(HIDE_CALENDAR_CLASS)) {
43
- instance.monthNav.classList.add('!hidden')
44
- instance.innerContainer.classList.add('!hidden')
49
+
50
+
51
+ if(!isInTable){
52
+ this.createClear(instance)
53
+ }
54
+
55
+ if(isInTable && realInput) {
56
+ // set initial value from real input to flatpickr
57
+ realInput.addEventListener('SBTableFilterFormLoad', () => {
58
+ if(!datePickerEl.value) {
59
+ instance.setDate(realInput.value, false, instance.config.dateFormat)
60
+ }
61
+ })
62
+ return
63
+ }
64
+ if(realInput) {
65
+ // advanced filters
66
+ instance.setDate(realInput.value, false, instance.config.dateFormat)
45
67
  }
46
68
  },
47
69
  onClose: function(selectedDates, dateStr, instance) {
@@ -50,12 +72,52 @@ export default class Datepicker {
50
72
  instance.setDate([selectedDates[0],selectedDates[0]], true)
51
73
  }
52
74
  },
75
+ onChange: function(selectedDates, dateStr) {
76
+ const realInput = document.getElementById(datePickerEl.dataset.sbadminDatepickerRealInputId)
77
+ if(realInput) {
78
+ realInput.value = dateStr
79
+ realInput.removeAttribute('data-label')
80
+ realInput.dispatchEvent(new Event('change'))
81
+ }
82
+ },
53
83
  ...options,
54
84
  ...sbadminDatepickerData.flatpickrOptions,
55
85
  ...optionsOverride
56
86
  })
57
87
  }
58
88
 
89
+ initShortcutsDropdown(datePickerEl) {
90
+ const realInput = document.getElementById(datePickerEl.dataset.sbadminDatepickerRealInputId)
91
+ const baseId = realInput.id
92
+ const baseValue = realInput.value
93
+ const el = document.createElement('div')
94
+ const shortcuts = JSON.parse(datePickerEl.dataset.sbadminDatepickerShortcuts)
95
+ el.classList.add('flatpickr-shortcuts', 'dropdown-menu')
96
+ el.addEventListener('change', (e) => {
97
+ realInput.value = e.target.value
98
+ datePickerEl.value = e.target.nextElementSibling.innerText
99
+ })
100
+
101
+ shortcuts.forEach((shortcut, idx) => {
102
+ const checked = JSON.stringify(shortcut.value) === baseValue
103
+ if(checked) {
104
+ datePickerEl.value = shortcut.label
105
+ }
106
+ el.append(createRadioInput(
107
+ `${baseId}_range${idx}`,
108
+ `${baseId}_shortcut`,
109
+ JSON.stringify(shortcut.value),
110
+ shortcut.label,
111
+ checked,
112
+ idx
113
+ ))
114
+ })
115
+ datePickerEl.parentElement.append(el)
116
+ datePickerEl.readOnly = true
117
+ datePickerEl.dataset['bsToggle'] = "dropdown"
118
+ new window.bootstrap5.Dropdown(datePickerEl)
119
+ }
120
+
59
121
  initWidgets(parentEl=null) {
60
122
  const datePickerSelector = {
61
123
  '.js-datepicker': {
@@ -249,74 +249,100 @@ export const monthYearViewsPlugin = (fp) => {
249
249
  }
250
250
  }
251
251
 
252
+ export const createRadioInput = (id, name, value, label, checked, index) => {
253
+ const inputWrapperEl = document.createElement('label')
254
+ inputWrapperEl.classList.add('relative', 'block', 'px-12', 'py-8')
255
+ inputWrapperEl.setAttribute('for', id)
256
+ const labelEl = document.createElement('label')
257
+ labelEl.innerText = label
258
+ labelEl.setAttribute('for', id)
259
+ const inputEl = document.createElement('input')
260
+ inputEl.id = id
261
+ inputEl.name = name
262
+ inputEl.value = value
263
+ inputEl.type = 'radio'
264
+ inputEl.classList.add('radio', 'flatpickr-shortcut')
265
+ inputEl.checked = checked
266
+ inputEl.dataset["index"] = index
267
+ inputWrapperEl.append(inputEl)
268
+ inputWrapperEl.append(labelEl)
269
+ return inputWrapperEl
270
+ }
271
+
252
272
  // eslint-disable-next-line no-unused-vars
253
273
  export const customActionsPlugin = (fp) => {
254
-
255
- const createRadioInput = (id, name, value, label, checked) => {
256
- const inputWrapperEl = document.createElement('label')
257
- inputWrapperEl.classList.add('relative', 'block', 'px-12', 'py-8')
258
- inputWrapperEl.setAttribute('for', id)
259
- const labelEl = document.createElement('label')
260
- labelEl.innerText = label
261
- labelEl.setAttribute('for', id)
262
- const inputEl = document.createElement('input')
263
- inputEl.id = id
264
- inputEl.name = name
265
- inputEl.value = value
266
- inputEl.type = 'radio'
267
- inputEl.classList.add('radio')
268
- inputEl.checked = checked
269
- inputWrapperEl.append(inputEl)
270
- inputWrapperEl.append(labelEl)
271
- return inputWrapperEl
272
- }
273
-
274
- const dateTimeReviver = (key, value) => {
275
- if (key === 'value') {
276
- const newValue = []
277
- value.forEach((val) => {
278
- newValue.push(new Date(val))
279
- })
280
- return newValue
281
- }
282
- return value
283
- }
284
-
285
274
  const createShortcuts = () => {
286
- const baseId = fp.element.id
287
- const baseValue = fp.element.value
275
+ const realInput = document.getElementById(fp.element.dataset.sbadminDatepickerRealInputId)
276
+ const baseId = realInput.id
277
+ const baseValue = realInput.value
288
278
  const el = document.createElement('div')
289
- const shortcuts = JSON.parse(fp.element.dataset.sbadminDatepickerShortcuts, dateTimeReviver)
279
+ const shortcuts = JSON.parse(fp.element.dataset.sbadminDatepickerShortcuts)
290
280
  el.classList.add('flatpickr-shortcuts')
291
281
  el.addEventListener('change', (e) => {
282
+ // on shortcut click/change set new value and label to real input element
292
283
  const value = e.target.value
293
- fp.setDate(shortcuts[value].value, true, fp.config.dateFormat)
284
+ realInput.value = value
285
+ realInput.dataset['label'] = e.target.nextElementSibling.innerText
286
+ realInput.dispatchEvent(new Event('change'))
287
+
288
+ // parse new value and set it to flatpicker
289
+ const shortcutValue = shortcuts[e.target.dataset.index].value
290
+ const from = new Date()
291
+ from.setDate(from.getDate() + shortcutValue[0])
292
+
293
+ const to = new Date()
294
+ to.setDate(to.getDate() + shortcutValue[1])
295
+
296
+ fp.setDate([from, to], false, fp.config.dateFormat)
294
297
  })
295
298
 
296
299
  shortcuts.forEach((shortcut, idx) => {
297
- const fromDateFormatted = fp.formatDate(shortcut.value[0], fp.config.dateFormat)
298
- const toDateFormatted = fp.formatDate(shortcut.value[1], fp.config.dateFormat)
299
- let shortcutValueStr
300
- if (fromDateFormatted === toDateFormatted) {
301
- shortcutValueStr = fromDateFormatted
302
- } else {
303
- shortcutValueStr = fromDateFormatted + fp.config.locale.rangeSeparator + toDateFormatted
300
+ const checked = JSON.stringify(shortcut.value) === baseValue
301
+ if(checked) {
302
+ realInput.dataset['label'] = shortcut.label
304
303
  }
305
304
  el.append(createRadioInput(
306
305
  `${baseId}_range${idx}`,
307
306
  `${baseId}_shortcut`,
308
- idx,
307
+ JSON.stringify(shortcut.value),
309
308
  shortcut.label,
310
- baseValue === shortcutValueStr
309
+ checked,
310
+ idx
311
311
  ))
312
312
  })
313
313
  fp.calendarContainer.prepend(el)
314
+
315
+ realInput.addEventListener('SBTableFilterFormLoad', () => {
316
+ try {
317
+ const shortcutValue = JSON.parse(realInput.value)
318
+ const from = new Date()
319
+ from.setDate(from.getDate() + shortcutValue[0])
320
+
321
+ const to = new Date()
322
+ to.setDate(to.getDate() + shortcutValue[1])
323
+ fp.setDate([from, to], false, fp.config.dateFormat)
324
+
325
+ shortcuts.forEach((shortcut, idx) => {
326
+ if(JSON.stringify(shortcut.value) === realInput.value) {
327
+ document.getElementById(`${baseId}_range${idx}`).checked = true
328
+ realInput.dataset['label'] = shortcut.label
329
+ }
330
+ })
331
+ }
332
+ catch {
333
+ fp.setDate(realInput.value, false, fp.config.dateFormat)
334
+ }
335
+ })
314
336
  }
315
337
 
316
338
 
317
339
  return {
318
340
  onReady: createShortcuts,
341
+ onChange: (selectedDates, dateStr, instance) => {
342
+ const checkedShortcut = instance.element.parentElement.querySelector('input.flatpickr-shortcut:checked')
343
+ if(checkedShortcut){
344
+ checkedShortcut.checked = false
345
+ }
346
+ }
319
347
  }
320
348
  }
321
-
322
- export const HIDE_CALENDAR_CLASS = 'hide-calendar' // used for hiding calendar and only use shortcuts
@@ -1,6 +1,6 @@
1
1
  import { SBAdminTableModule } from "./base_module"
2
2
  import { filterInputValueChangedUtil, filterInputValueChangeListener } from "../utils"
3
- import {customActionsPlugin, HIDE_CALENDAR_CLASS, monthYearViewsPlugin} from "../datepicker_plugins"
3
+ import {customActionsPlugin, monthYearViewsPlugin} from "../datepicker_plugins"
4
4
 
5
5
  export class AdvancedFilterModule extends SBAdminTableModule {
6
6
  constructor(table) {
@@ -104,6 +104,16 @@ export class AdvancedFilterModule extends SBAdminTableModule {
104
104
  widgetEl._flatpickr.calendarContainer?.remove()
105
105
  widgetEl._flatpickr.clear()
106
106
  widgetEl._flatpickr.destroy()
107
+ widgetEl._flatpickr = undefined
108
+ return
109
+ }
110
+ const dropdownInstance = window.bootstrap5.Dropdown.getInstance(widgetEl)
111
+ if(dropdownInstance) {
112
+ dropdownInstance.dispose()
113
+ widgetEl.removeAttribute('data-bs-toggle')
114
+ widgetEl.nextElementSibling?.remove()
115
+ widgetEl.readOnly = false
116
+ widgetEl.value = ""
107
117
  }
108
118
  }
109
119
 
@@ -120,17 +130,14 @@ export class AdvancedFilterModule extends SBAdminTableModule {
120
130
  if (["between", "not_between", "in_the_last", "in_the_next"].includes(rule.operator.type)) {
121
131
  optionsOverride["mode"] = "range"
122
132
  }
133
+ this.destroyDatePicker(widgetEl)
134
+ const shortcuts = JSON.parse(widgetEl.dataset.sbadminDatepickerShortcutsDict)
135
+ widgetEl.dataset.sbadminDatepickerShortcuts = JSON.stringify(shortcuts[rule.operator.type] || [])
123
136
  if (["in_the_last", "in_the_next"].includes(rule.operator.type)) {
124
- // this option does work but there is a bug in certain cases
125
- // optionsOverride["noCalendar"] = true
126
- widgetEl.classList.add(HIDE_CALENDAR_CLASS)
137
+ window.SBAdmin.datepicker.initShortcutsDropdown(widgetEl, {}, optionsOverride)
127
138
  }
128
139
  else {
129
- widgetEl.classList.remove(HIDE_CALENDAR_CLASS)
140
+ window.SBAdmin.datepicker.initFlatPickr(widgetEl, {}, optionsOverride)
130
141
  }
131
- this.destroyDatePicker(widgetEl)
132
- const shortcuts = JSON.parse(widgetEl.dataset.sbadminDatepickerShortcutsDict)
133
- widgetEl.dataset.sbadminDatepickerShortcuts = JSON.stringify(shortcuts[rule.operator.type] || [])
134
- window.SBAdmin.datepicker.initFlatPickr(widgetEl, {}, optionsOverride)
135
142
  }
136
143
  }
@@ -73,6 +73,11 @@ export const filterInputValueChangedUtil = (field) => {
73
73
  if(!valueElem) {
74
74
  return
75
75
  }
76
+ const label = field.dataset.label
77
+ if(label) {
78
+ valueElem.innerHTML = label
79
+ return valueElem
80
+ }
76
81
  const valueOrObject = getObjectOrValue(field.value)
77
82
  if ((field.value === "" || field.value === "[]")) {
78
83
  if(field.dataset.emptyLabel) {
@@ -1,10 +1,17 @@
1
1
  <div class="relative">
2
2
  <input
3
- type="text"
3
+ type="hidden"
4
4
  autocomplete="off"
5
- class="input pl-10 js-datepicker-dynamic"
6
5
  id="{{ filter_widget.input_id }}"
7
6
  name="{{ filter_widget.input_name }}"
7
+ >
8
+ <input
9
+ type="text"
10
+ autocomplete="off"
11
+ class="input pl-10 js-datepicker-dynamic"
12
+ id="{{ filter_widget.input_id }}_picker"
13
+ name="{{ filter_widget.input_name }}_picker"
14
+ data-sbadmin-datepicker-real-input-id="{{ filter_widget.input_id }}"
8
15
  data-sbadmin-datepicker="{{ filter_widget.get_data }}"
9
16
  data-sbadmin-datepicker-shortcuts-dict="{{ filter_widget.get_shortcuts_dict_data }}"
10
17
  >
@@ -1,12 +1,22 @@
1
1
  <div class="dropdown-menu dropdown-menu-datepicker"
2
2
  style="max-width: none; width: auto; height: auto; max-height: none;">
3
3
  <div class="px-12 py-8">
4
- <input form="{{ filter_widget.view_id }}-filter-form" type="text" class="input pl-10 js-datepicker-range"
5
- id="{{ filter_widget.input_id }}" name="{{ filter_widget.input_name }}"
4
+ <input
5
+ type="hidden"
6
+ autocomplete="off"
7
+ id="{{ filter_widget.input_id }}"
8
+ name="{{ filter_widget.input_name }}"
9
+ form="{{ filter_widget.view_id }}-filter-form"
10
+ {% if filter_widget.get_default_value %}value="{{ filter_widget.get_default_value }}"{% endif %}
11
+ {% if not all_filters_visible %}disabled{% endif %}
12
+ >
13
+ <input type="text"
14
+ class="input pl-10 js-datepicker-range"
15
+ id="{{ filter_widget.input_id }}_picker"
16
+ name="{{ filter_widget.input_name }}_picker"
17
+ data-sbadmin-datepicker-real-input-id="{{ filter_widget.input_id }}"
6
18
  data-sbadmin-datepicker="{{ filter_widget.get_data }}"
7
- data-sbadmin-datepicker-shortcuts="{{ filter_widget.get_shortcuts_data }}"
8
- {% if not all_filters_visible %}disabled{% endif %}{% if filter_widget.get_default_value %}
9
- value="{{ filter_widget.get_default_value }}"{% endif %}>
19
+ data-sbadmin-datepicker-shortcuts="{{ filter_widget.get_shortcuts_data }}">
10
20
  </div>
11
21
  {% include "sb_admin/filter_widgets/partials/clear.html" %}
12
22
  </div>
@@ -23,7 +23,7 @@
23
23
  <div class="flex max-xs:flex-wrap gap-x-16">{% endif %}
24
24
 
25
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 %} hidden{% endif %}">
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
27
  {% if field.is_readonly %}
28
28
  {% call_method field "contents" request %}
29
29
  {% else %}
@@ -1,7 +1,7 @@
1
1
  {% load widget_tweaks %}
2
2
  {% if inline_admin_formset.formset.can_delete %}
3
3
  {% if inline_admin_form.original %}
4
- <td class="w-40">
4
+ <td class="w-40{% if sticky %} sticky-table-col{% endif %}">
5
5
  <div class="relative min-h-40 flex items-center delete djn-delete-handler {{ inline_admin_formset.handler_classes|join:" " }}">
6
6
  {% render_field inline_admin_form.deletion_field.field class="checkbox checkbox-delete" %}
7
7
  <label for="{{ inline_admin_form.deletion_field.field.auto_id }}">
@@ -12,10 +12,9 @@
12
12
  </div>
13
13
  </td>
14
14
  {% else %}
15
- <td class="delete w-40">
16
- <a
17
- class="flex-center h-40 inline-deletelink djn-remove-handler {{ inline_admin_formset.handler_classes|join:" " }}"
18
- href="javascript:void(0)">
15
+ <td class="delete w-40{% if sticky %} sticky-table-col{% endif %}">
16
+ <a class="flex-center h-40 inline-deletelink djn-remove-handler {{ inline_admin_formset.handler_classes|join:" " }}"
17
+ href="javascript:void(0)">
19
18
  <svg class="w-20 h-20 text-dark-300 hover:text-dark-400">
20
19
  <use xlink:href="#Delete"></use>
21
20
  </svg>
@@ -70,8 +70,11 @@
70
70
  </th>
71
71
  {% endif %}
72
72
  {% endfor %}
73
- {% if inline_admin_formset.formset.can_delete %}
74
- <th class="djn-th" style="min-width: 54px; width: 54px;"></th>{% endif %}
73
+ {% block table_inline_delete_table_head %}
74
+ {% if inline_admin_formset.formset.can_delete %}
75
+ <th class="djn-th sticky-table-head" style="min-width: 54px; width: 54px;"></th>
76
+ {% endif %}
77
+ {% endblock %}
75
78
  </tr>
76
79
  </thead>
77
80
 
@@ -163,7 +166,7 @@
163
166
  {% endfor %}
164
167
  {% endfor %}
165
168
  {% block table_inline_delete_button %}
166
- {% include "sb_admin/includes/table_inline_delete_button.html" %}
169
+ {% include "sb_admin/includes/table_inline_delete_button.html" with sticky=True %}
167
170
  {% endblock %}
168
171
  </tr>
169
172
  {% block table_inline_after_line %}{% endblock %}
@@ -183,6 +186,21 @@
183
186
  {% endblock %}
184
187
  </table>
185
188
 
189
+ {% block table_script %}
190
+ <script>
191
+ {# move all non initial lines to the top of the table to maintain original order #}
192
+ {# this should happen only during validation error #}
193
+ (function(){
194
+ const group = document.getElementById('{{ inline_admin_formset.formset.prefix }}-group');
195
+ const table = group.querySelector('.djn-table');
196
+ const bodies_to_move = Array.from(group.querySelectorAll('.djn-tbody[data-is-initial="false"]'));
197
+ if(bodies_to_move) {
198
+ table.prepend(...bodies_to_move.reverse());
199
+ }
200
+ }());
201
+ </script>
202
+ {% endblock %}
203
+
186
204
  </fieldset>
187
205
  </div>
188
206
 
@@ -161,7 +161,7 @@ class ModelTranslationView(SBAdminView, SBAdminBaseListView):
161
161
  annotate=Case(
162
162
  When(
163
163
  **{
164
- f"{annotate_name}_count": F(
164
+ f"{annotate_name}_count__gte": F(
165
165
  f"{main_lang_annotate_name}_count"
166
166
  ),
167
167
  "then": Value(self.TRANSLATION_TRANSLATED),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-smartbase-admin
3
- Version: 0.2.87
3
+ Version: 0.2.89
4
4
  Summary:
5
5
  Author: SmartBase
6
6
  Author-email: info@smartbase.sk