accrete 0.0.99__py3-none-any.whl → 0.0.109__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.
- accrete/contrib/ui/__init__.py +6 -27
- accrete/contrib/ui/context.py +26 -294
- accrete/contrib/ui/filter.py +309 -280
- accrete/contrib/ui/middleware.py +44 -0
- accrete/contrib/ui/models.py +3 -0
- accrete/contrib/ui/static/bulma/README.md +4 -2
- accrete/contrib/ui/static/bulma/bulma.css +21551 -0
- accrete/contrib/ui/static/bulma/bulma.css.map +1 -0
- accrete/contrib/ui/static/bulma/bulma.scss +1 -1
- accrete/contrib/ui/static/bulma/css/bulma.css +1988 -2874
- accrete/contrib/ui/static/bulma/css/bulma.css.map +1 -1
- accrete/contrib/ui/static/bulma/css/bulma.min.css +2 -2
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.css +19648 -0
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.css.map +1 -0
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.min.css +2 -2
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.css +11136 -0
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.css.map +1 -0
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.min.css +2 -2
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.css +11136 -0
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.css.map +1 -0
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.min.css +2 -2
- accrete/contrib/ui/static/bulma/css/versions/bulma-prefixed.min.css +21550 -2
- accrete/contrib/ui/static/bulma/css/versions/bulma-prefixed.min.css.map +1 -1
- accrete/contrib/ui/static/bulma/css/versions/bulma-prefixed.min.min.css +3 -0
- accrete/contrib/ui/static/bulma/package.json +12 -11
- accrete/contrib/ui/static/bulma/sass/base/animations.css +15 -0
- accrete/contrib/ui/static/bulma/sass/base/animations.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/base/generic.css +196 -0
- accrete/contrib/ui/static/bulma/sass/base/generic.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/base/minireset.css +82 -0
- accrete/contrib/ui/static/bulma/sass/base/minireset.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/base/skeleton.css +113 -0
- accrete/contrib/ui/static/bulma/sass/base/skeleton.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/base/skeleton.scss +0 -12
- accrete/contrib/ui/static/bulma/sass/components/breadcrumb.css +108 -0
- accrete/contrib/ui/static/bulma/sass/components/breadcrumb.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/card.css +130 -0
- accrete/contrib/ui/static/bulma/sass/components/card.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/dropdown.css +119 -0
- accrete/contrib/ui/static/bulma/sass/components/dropdown.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/menu.css +119 -0
- accrete/contrib/ui/static/bulma/sass/components/menu.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/message.css +191 -0
- accrete/contrib/ui/static/bulma/sass/components/message.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/modal.css +194 -0
- accrete/contrib/ui/static/bulma/sass/components/modal.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/navbar.css +768 -0
- accrete/contrib/ui/static/bulma/sass/components/navbar.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/navbar.scss +41 -30
- accrete/contrib/ui/static/bulma/sass/components/pagination.css +302 -0
- accrete/contrib/ui/static/bulma/sass/components/pagination.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/panel.css +224 -0
- accrete/contrib/ui/static/bulma/sass/components/panel.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/components/panel.scss +2 -2
- accrete/contrib/ui/static/bulma/sass/components/tabs.css +192 -0
- accrete/contrib/ui/static/bulma/sass/components/tabs.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/block.css +17 -0
- accrete/contrib/ui/static/bulma/sass/elements/block.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/box.css +43 -0
- accrete/contrib/ui/static/bulma/sass/elements/box.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/button.css +685 -0
- accrete/contrib/ui/static/bulma/sass/elements/button.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/button.scss +9 -2
- accrete/contrib/ui/static/bulma/sass/elements/content.css +208 -0
- accrete/contrib/ui/static/bulma/sass/elements/content.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/content.scss +8 -2
- accrete/contrib/ui/static/bulma/sass/elements/delete.css +60 -0
- accrete/contrib/ui/static/bulma/sass/elements/delete.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/icon.css +51 -0
- accrete/contrib/ui/static/bulma/sass/elements/icon.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/image.css +253 -0
- accrete/contrib/ui/static/bulma/sass/elements/image.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/loader.css +14 -0
- accrete/contrib/ui/static/bulma/sass/elements/loader.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/notification.css +213 -0
- accrete/contrib/ui/static/bulma/sass/elements/notification.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/progress.css +119 -0
- accrete/contrib/ui/static/bulma/sass/elements/progress.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/table.css +294 -0
- accrete/contrib/ui/static/bulma/sass/elements/table.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/tag.css +252 -0
- accrete/contrib/ui/static/bulma/sass/elements/tag.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/elements/title.css +131 -0
- accrete/contrib/ui/static/bulma/sass/elements/title.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.css +72 -0
- accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.scss +7 -3
- accrete/contrib/ui/static/bulma/sass/form/file.css +374 -0
- accrete/contrib/ui/static/bulma/sass/form/file.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/form/input-textarea.css +284 -0
- accrete/contrib/ui/static/bulma/sass/form/input-textarea.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/form/input-textarea.scss +0 -10
- accrete/contrib/ui/static/bulma/sass/form/select.css +347 -0
- accrete/contrib/ui/static/bulma/sass/form/select.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/form/select.scss +1 -0
- accrete/contrib/ui/static/bulma/sass/form/shared.css +48 -0
- accrete/contrib/ui/static/bulma/sass/form/shared.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/form/shared.scss +5 -1
- accrete/contrib/ui/static/bulma/sass/form/tools.css +356 -0
- accrete/contrib/ui/static/bulma/sass/form/tools.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/form/tools.scss +23 -12
- accrete/contrib/ui/static/bulma/sass/grid/columns-v2.css +1635 -0
- accrete/contrib/ui/static/bulma/sass/grid/columns-v2.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/grid/columns.css +1652 -0
- accrete/contrib/ui/static/bulma/sass/grid/columns.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/grid/columns.scss +109 -25
- accrete/contrib/ui/static/bulma/sass/grid/grid.css +3011 -0
- accrete/contrib/ui/static/bulma/sass/grid/grid.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/grid/grid.scss +3 -3
- accrete/contrib/ui/static/bulma/sass/helpers/aspect-ratio.css +61 -0
- accrete/contrib/ui/static/bulma/sass/helpers/aspect-ratio.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/border.css +17 -0
- accrete/contrib/ui/static/bulma/sass/helpers/border.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/color.css +5582 -0
- accrete/contrib/ui/static/bulma/sass/helpers/color.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/color.scss +166 -186
- accrete/contrib/ui/static/bulma/sass/helpers/flexbox.css +217 -0
- accrete/contrib/ui/static/bulma/sass/helpers/flexbox.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/float.css +37 -0
- accrete/contrib/ui/static/bulma/sass/helpers/float.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/gap.css +209 -0
- accrete/contrib/ui/static/bulma/sass/helpers/gap.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/other.css +34 -0
- accrete/contrib/ui/static/bulma/sass/helpers/other.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/overflow.css +65 -0
- accrete/contrib/ui/static/bulma/sass/helpers/overflow.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/position.css +45 -0
- accrete/contrib/ui/static/bulma/sass/helpers/position.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/spacing.css +489 -0
- accrete/contrib/ui/static/bulma/sass/helpers/spacing.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/typography.css +423 -0
- accrete/contrib/ui/static/bulma/sass/helpers/typography.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/helpers/visibility.css +485 -0
- accrete/contrib/ui/static/bulma/sass/helpers/visibility.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/layout/container.scss +16 -8
- accrete/contrib/ui/static/bulma/sass/layout/footer.css +9 -0
- accrete/contrib/ui/static/bulma/sass/layout/footer.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/layout/hero.css +534 -0
- accrete/contrib/ui/static/bulma/sass/layout/hero.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/layout/level.css +102 -0
- accrete/contrib/ui/static/bulma/sass/layout/level.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/layout/media.css +90 -0
- accrete/contrib/ui/static/bulma/sass/layout/media.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/layout/section.css +23 -0
- accrete/contrib/ui/static/bulma/sass/layout/section.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/layout/section.scss +4 -0
- accrete/contrib/ui/static/bulma/sass/themes/dark.css +3 -0
- accrete/contrib/ui/static/bulma/sass/themes/dark.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/themes/light.css +3 -0
- accrete/contrib/ui/static/bulma/sass/themes/light.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/themes/light.scss +1 -0
- accrete/contrib/ui/static/bulma/sass/themes/setup.css +3 -0
- accrete/contrib/ui/static/bulma/sass/themes/setup.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/utilities/controls.css +13 -0
- accrete/contrib/ui/static/bulma/sass/utilities/controls.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/utilities/css-variables.css +3 -0
- accrete/contrib/ui/static/bulma/sass/utilities/css-variables.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/utilities/css-variables.scss +3 -2
- accrete/contrib/ui/static/bulma/sass/utilities/derived-variables.css +3 -0
- accrete/contrib/ui/static/bulma/sass/utilities/derived-variables.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/utilities/extends.css +13 -0
- accrete/contrib/ui/static/bulma/sass/utilities/extends.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/utilities/functions.scss +2 -2
- accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.css +3 -0
- accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.scss +4 -4
- accrete/contrib/ui/static/bulma/sass/utilities/mixins.css +3 -0
- accrete/contrib/ui/static/bulma/sass/utilities/mixins.css.map +1 -0
- accrete/contrib/ui/static/bulma/sass/utilities/mixins.scss +1 -1
- accrete/contrib/ui/static/bulma/versions/bulma-no-dark-mode.css +19648 -0
- accrete/contrib/ui/static/bulma/versions/bulma-no-dark-mode.css.map +1 -0
- accrete/contrib/ui/static/bulma/versions/bulma-no-dark-mode.scss +2 -1
- accrete/contrib/ui/static/bulma/versions/bulma-no-helpers-prefixed.css +11136 -0
- accrete/contrib/ui/static/bulma/versions/bulma-no-helpers-prefixed.css.map +1 -0
- accrete/contrib/ui/static/bulma/versions/bulma-no-helpers-prefixed.scss +1 -1
- accrete/contrib/ui/static/bulma/versions/bulma-no-helpers.css +11136 -0
- accrete/contrib/ui/static/bulma/versions/bulma-no-helpers.css.map +1 -0
- accrete/contrib/ui/static/bulma/versions/bulma-no-helpers.scss +1 -1
- accrete/contrib/ui/static/bulma/versions/bulma-prefixed.css +21551 -0
- accrete/contrib/ui/static/bulma/versions/bulma-prefixed.css.map +1 -0
- accrete/contrib/ui/static/bulma/versions/bulma-prefixed.scss +1 -1
- accrete/contrib/ui/static/css/accrete.css +20757 -19996
- accrete/contrib/ui/static/css/accrete.css.map +1 -1
- accrete/contrib/ui/static/css/accrete.scss +185 -461
- accrete/contrib/ui/static/js/filter.js +98 -679
- accrete/contrib/ui/static/js/htmx.min.js +1 -1
- accrete/contrib/ui/templates/django/forms/widgets/date.html +5 -0
- accrete/contrib/ui/templates/django/forms/widgets/input.html +1 -1
- accrete/contrib/ui/templates/django/forms/widgets/select.html +7 -5
- accrete/contrib/ui/templates/ui/content_right.html +7 -0
- accrete/contrib/ui/templates/ui/filter/filter.html +27 -0
- accrete/contrib/ui/templates/ui/filter/query_input.html +31 -0
- accrete/contrib/ui/templates/ui/filter/query_operator.html +11 -0
- accrete/contrib/ui/templates/ui/filter/query_params.html +106 -0
- accrete/contrib/ui/templates/ui/filter/query_tags.html +26 -0
- accrete/contrib/ui/templates/ui/layout.html +175 -232
- accrete/contrib/ui/templates/ui/list.html +39 -28
- accrete/contrib/ui/templates/ui/list_update.html +3 -0
- accrete/contrib/ui/templates/ui/message.html +13 -0
- accrete/contrib/ui/templates/ui/{partials/modal.html → modal.html} +9 -5
- accrete/contrib/ui/templates/ui/oob.html +3 -0
- accrete/contrib/ui/templates/ui/table.html +67 -71
- accrete/contrib/ui/templates/ui/table_row_update.html +14 -0
- accrete/contrib/ui/templates/ui/widgets/model_search_select.html +42 -0
- accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html +78 -0
- accrete/contrib/ui/templates/ui/widgets/model_search_select_options.html +3 -0
- accrete/contrib/ui/templatetags/{accrete_ui.py → ui.py} +69 -57
- accrete/contrib/ui/urls.py +3 -1
- accrete/contrib/ui/views.py +33 -3
- accrete/contrib/ui/widgets/__init__.py +1 -0
- accrete/contrib/ui/widgets/search_select.py +106 -0
- accrete/contrib/user/forms.py +1 -1
- accrete/contrib/user/templates/user/change_email.html +22 -27
- accrete/contrib/user/templates/user/change_password.html +24 -36
- accrete/contrib/user/templates/user/login.html +72 -23
- accrete/contrib/user/templates/user/user_form.html +50 -52
- accrete/contrib/user/urls.py +2 -3
- accrete/contrib/user/views.py +39 -46
- accrete/forms.py +0 -2
- accrete/managers.py +4 -4
- accrete/middleware.py +43 -68
- accrete/models.py +7 -1
- accrete/storage.py +4 -1
- accrete/tenant.py +11 -5
- accrete/utils/__init__.py +2 -0
- accrete/utils/models.py +9 -1
- accrete/utils/views.py +36 -20
- accrete/views.py +9 -5
- {accrete-0.0.99.dist-info → accrete-0.0.109.dist-info}/METADATA +2 -2
- accrete-0.0.109.dist-info/RECORD +369 -0
- {accrete-0.0.99.dist-info → accrete-0.0.109.dist-info}/WHEEL +1 -1
- accrete/contrib/ui/elements.py +0 -93
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.min.css.map +0 -1
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.min.css.map +0 -1
- accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.min.css.map +0 -1
- accrete/contrib/ui/static/bulma/sass/grid/columns-v2.scss +0 -957
- accrete/contrib/ui/static/js/ui.js +0 -30
- accrete/contrib/ui/templates/ui/dashboard.html +0 -7
- accrete/contrib/ui/templates/ui/detail.html +0 -19
- accrete/contrib/ui/templates/ui/form.html +0 -21
- accrete/contrib/ui/templates/ui/partials/filter.html +0 -143
- accrete/contrib/ui/templates/ui/partials/form_errors.html +0 -34
- accrete/contrib/ui/templates/ui/partials/header.html +0 -123
- accrete/contrib/ui/templates/ui/partials/modal_form.html +0 -45
- accrete/contrib/ui/templates/ui/partials/onchange_form.html +0 -1
- accrete/contrib/ui/templates/ui/partials/pagination_detail.html +0 -23
- accrete/contrib/ui/templates/ui/partials/pagination_list.html +0 -28
- accrete/contrib/ui/templates/ui/partials/table_field.html +0 -16
- accrete/contrib/ui/templates/ui/partials/table_field_float.html +0 -1
- accrete/contrib/ui/templates/ui/partials/table_field_monetary.html +0 -4
- accrete/contrib/ui/templates/ui/partials/table_field_string.html +0 -1
- accrete/contrib/user/templates/user/user_detail.html +0 -43
- accrete-0.0.99.dist-info/RECORD +0 -231
- {accrete-0.0.99.dist-info → accrete-0.0.109.dist-info}/licenses/LICENSE +0 -0
@@ -1,81 +1,77 @@
|
|
1
1
|
{% extends 'ui/layout.html' %}
|
2
|
-
{% load static %}
|
3
2
|
{% load i18n %}
|
4
|
-
{% load
|
3
|
+
{% load ui %}
|
4
|
+
{% load partials %}
|
5
5
|
|
6
|
-
{% block
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
{% for field in fields %}
|
16
|
-
<th data-name="{{ field.name }}" style="text-align: {% firstof field.header_alignment.value field.alignment.value %}">{{ field.label }}</th>
|
17
|
-
{% endfor %}
|
18
|
-
{% if unselect_button %}
|
19
|
-
<th></th>
|
20
|
-
{% endif %}
|
21
|
-
{% endblock %}
|
22
|
-
</tr>
|
23
|
-
{% endblock %}
|
6
|
+
{% block content_left %}
|
7
|
+
<table id="content-table" class="table can-compact is-fullwidth is-hoverable is-narrow my-0" hx-indicator=".htmx-indicator" x-data="">
|
8
|
+
<thead style="position: sticky; top: 0; z-index: 10; background-color: var(--bulma-scheme-main)">
|
9
|
+
<tr>
|
10
|
+
<th>{{ object_label }}</th>
|
11
|
+
{% for field in fields %}
|
12
|
+
<th style="text-align: {{ page.object_list|table_alignment:field }}">{{ page.object_list|verbose_field_name:field }}</th>
|
13
|
+
{% endfor %}
|
14
|
+
</tr>
|
24
15
|
</thead>
|
25
|
-
<tbody id="content-table-body" style="z-index: 9">
|
26
|
-
{% for
|
27
|
-
{%
|
28
|
-
<tr
|
29
|
-
{%
|
30
|
-
|
31
|
-
|
32
|
-
|
16
|
+
<tbody id="content-table-body" style="z-index: 9" x-ref="tbody">
|
17
|
+
{% for object in page.object_list %}
|
18
|
+
{% partialdef tr inline=True %}
|
19
|
+
<tr id="tr-{{ object.pk }}"
|
20
|
+
{% if detail_enabled %}
|
21
|
+
style="cursor: pointer;"
|
22
|
+
hx-get="{{ object.get_absolute_url }}{% querystring %}" hx-target="#content-right-header" hx-select="#content-right-header" hx-select-oob="#content-right" hx-swap="outerHTML"
|
23
|
+
x-data="{selected: false}" @unselect-tr.window="selected = false"
|
24
|
+
x-on:click="$dispatch('unselect-tr'); showContentRight = true; selected = true; $nextTick(() => { $el.scrollIntoView( {block: 'nearest'} ) });" x-bind:class="selected ? 'is-success' : ''"
|
25
|
+
{% endif %}
|
26
|
+
>
|
27
|
+
{% partialdef td inline=True %}
|
28
|
+
<td>{{ object }}</td>
|
33
29
|
{% for field in fields %}
|
34
|
-
{
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
<span class="responsive-heading has-text-weight-light" style="margin-right: .2rem">{{ field.label }}:</span>
|
41
|
-
{% if is_relation and val.get_absolute_url %}<a href="{{ obj|related_obj_url:field.name }}" class="is-underlined">{% endif %}
|
42
|
-
{{ field.prefix }}
|
43
|
-
{% block td_content %}
|
44
|
-
{% if field.truncate_after > 0 %}
|
45
|
-
{{ val|default_if_none:'---'|truncatechars:field.truncate_after }}
|
46
|
-
{% else %}
|
47
|
-
{{ val|default_if_none:'---' }}
|
48
|
-
{% endif %}
|
49
|
-
{% endblock %}
|
50
|
-
{{ field.suffix }}
|
51
|
-
{% if is_relation %}</a>{% endif %}
|
52
|
-
</span>
|
53
|
-
</td>
|
54
|
-
{% endwith %}
|
55
|
-
{% endif %}
|
30
|
+
<td style="text-align: {{ object|table_alignment:field }}">
|
31
|
+
<span class="">
|
32
|
+
<span class="responsive-heading has-text-weight-light" style="margin-right: .2rem">{{ object|verbose_field_name:field }}:</span>
|
33
|
+
{{ object|table_display:field|default_if_none:'' }}
|
34
|
+
</span>
|
35
|
+
</td>
|
56
36
|
{% endfor %}
|
57
|
-
|
58
|
-
<td><button class="button is-small has-icon p-0 is-pulled-right" onclick="unselectSingleObject({{ obj.pk }})"><i class="icon-delete-filter"></i></button></td>
|
59
|
-
{% endif %}
|
60
|
-
{% endblock %}
|
37
|
+
{% endpartialdef %}
|
61
38
|
</tr>
|
62
|
-
{%
|
39
|
+
{% endpartialdef %}
|
63
40
|
{% endfor %}
|
64
41
|
</tbody>
|
42
|
+
<tfoot style="position: sticky; bottom: 0; z-index: 10; background-color: var(--bulma-scheme-main);" class="has-text-weight-bold">
|
43
|
+
<tr id="tf-row">
|
44
|
+
{% partialdef tf inline=True %}
|
45
|
+
<td class="p-0"></td>
|
46
|
+
{% for field in fields %}
|
47
|
+
{% if field in footer %}
|
48
|
+
<td style="text-align: {{ queryset|table_alignment:field }}">
|
49
|
+
<span class="">
|
50
|
+
<span class="responsive-heading has-text-weight-light" style="margin-right: .2rem">{{ queryset|verbose_field_name:field }}:</span>
|
51
|
+
{{ footer|get_item:field }}
|
52
|
+
</span>
|
53
|
+
</td>
|
54
|
+
{% else %}
|
55
|
+
<td class="p-0"></td>
|
56
|
+
{% endif %}
|
57
|
+
{% endfor %}
|
58
|
+
{% endpartialdef %}
|
59
|
+
</tr>
|
60
|
+
</tfoot>
|
65
61
|
</table>
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
{% endblock %}
|
62
|
+
|
63
|
+
<div id="endless-scroller"
|
64
|
+
{% if page.has_next %}
|
65
|
+
hx-get="{% querystring page=page.next_page_number %}"
|
66
|
+
hx-trigger="intersect once"
|
67
|
+
hx-select="tbody > tr"
|
68
|
+
hx-target="#content-table-body"
|
69
|
+
hx-swap="beforeend"
|
70
|
+
hx-select-oob="#pagination-next-button,#pagination-end-index,#endless-scroller"
|
71
|
+
hx-indicator="#endless-scroll-indicator"
|
72
|
+
hx-replace-url="true"
|
73
|
+
{% endif %}
|
74
|
+
>
|
75
|
+
</div>
|
76
|
+
<progress id="endless-scroll-indicator" class="htmx-indicator progress is-small is-success" max="100">15%</progress>
|
77
|
+
{% endblock %}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<table>
|
2
|
+
<tbody>
|
3
|
+
<tr hx-swap-oob="innerHTML:#tr-{{ object.pk }}">
|
4
|
+
{% include 'ui/table.html#td' with object=object fields=fields queryset=queryset %}
|
5
|
+
</tr>
|
6
|
+
</tbody>
|
7
|
+
{% if footer %}
|
8
|
+
<tfoot>
|
9
|
+
<tr hx-swap-oob="innerHTML:#tf-row">
|
10
|
+
{% include 'ui/table.html#tf' with object=object fields=fields queryset=queryset %}
|
11
|
+
</tr>
|
12
|
+
</tfoot>
|
13
|
+
{% endif %}
|
14
|
+
</table>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
{% load static %}
|
2
|
+
{% load i18n %}
|
3
|
+
|
4
|
+
|
5
|
+
<div class="field select" x-data="{open: false, shouldClose(event) {return event.relatedTarget == $refs.dropdownContent || $refs.dropdownContent.contains(event.relatedTarget);} }"
|
6
|
+
@click.outside="open = false" hx-disinherit="*"
|
7
|
+
>
|
8
|
+
<input id="id_{{ widget.name }}_value" hidden="hidden" type="{{ widget.type }}" name="{{ widget.name }}" x-ref="inputValue" aria-label="inputValue"
|
9
|
+
value="{{ widget.value }}"
|
10
|
+
>
|
11
|
+
<input id="id_{{ widget.name }}_display" class="input" type="text" readonly x-ref="inputDisplay" value="{{ widget.value_display }}" aria-label="inputDisplay"
|
12
|
+
style="padding-right: 30px"
|
13
|
+
x-on:focus="open = !open;"
|
14
|
+
x-on:keydown="if ($event.keyCode == 27) {document.activeElement.blur()} else if ($event.keyCode != 9) {$refs.searchInput.focus();}"
|
15
|
+
x-on:focusout="open = shouldClose($event)"
|
16
|
+
>
|
17
|
+
<div style="position: relative; z-index: 90;">
|
18
|
+
<div x-show="open" x-cloak="" class="box pt-1 mb-2" x-ref="dropdownContent" tabindex="-1" x-transition style="position: absolute; width: 100%">
|
19
|
+
<input id="id_{{ widget.name }}_search" type="search" autocomplete="off" class="input" x-ref="searchInput" aria-label="search"
|
20
|
+
name="{{ widget.search_parameter }}"
|
21
|
+
placeholder="{% translate 'Type to search' %}"
|
22
|
+
x-on:keydown="if ($event.keyCode == 27) {document.activeElement.blur()}"
|
23
|
+
x-on:focusout="open = shouldClose($event)"
|
24
|
+
hx-get="{{ widget.search_url }}"
|
25
|
+
hx-trigger="input changed delay:500ms, search"
|
26
|
+
hx-target="#{{ widget.name }}_dropdown_items"
|
27
|
+
>
|
28
|
+
<div x-on:click="$refs.inputValue.setAttribute('value', $event.target.value); $refs.inputDisplay.value = $event.target.innerText; htmx.trigger($refs.inputValue, 'submit'); open = false; {% if widget.hx_trigger_on_change %}htmx.trigger('#id_{{ widget.name }}_value', 'change');{% endif %}"
|
29
|
+
x-on:keydown="if ($event.keyCode == 27) {document.activeElement.blur()} else if ($event.keyCode != 9 && $event.keyCode != 13) {$refs.searchInput.focus();}"
|
30
|
+
>
|
31
|
+
{% if not widget.required %}
|
32
|
+
<div class="hoverable" style="max-height: 200px; width: 100%; overflow-y: auto;">
|
33
|
+
<button type="button" class="dropdown-item px-1" value="" style="word-wrap: break-word; width: 100%; white-space: normal">---------</button>
|
34
|
+
</div>
|
35
|
+
{% endif %}
|
36
|
+
<div id="{{ widget.name }}_dropdown_items" class="hoverable" style="max-height: 200px; width: 100%; overflow-y: auto;">
|
37
|
+
{% include 'ui/widgets/model_search_select_options.html' %}
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</div>
|
@@ -0,0 +1,78 @@
|
|
1
|
+
{% load i18n %}
|
2
|
+
{% load ui %}
|
3
|
+
|
4
|
+
<div class="" x-data="{open: false, shouldClose(event) {return event.relatedTarget == $refs.dropdownContent || $refs.dropdownContent.contains(event.relatedTarget);} }"
|
5
|
+
@click.outside="open = false" style="width: 100%;" hx-disinherit="*"
|
6
|
+
>
|
7
|
+
<select multiple id="{{ widget.attrs.id }}" hidden="hidden" name="{{ widget.name }}" aria-label="{{ widget.name }}" x-ref="inputValue">
|
8
|
+
{% for obj in selected %}
|
9
|
+
<option selected value="{{ obj.pk }}"></option>
|
10
|
+
{% endfor %}
|
11
|
+
</select>
|
12
|
+
<div id="{{ widget.attrs.id }}_display" class="input select py-1 pl-1" tabindex="0" x-ref="inputDisplay" aria-label="inputDisplay"
|
13
|
+
style="padding-right: 30px; border-radius: 0; border-top: 0; border-right:0; border-left: 0; min-height: var(--bulma-control-height); height: fit-content; width: 100%"
|
14
|
+
x-on:focus="open = true;"
|
15
|
+
x-on:keydown="if ($event.keyCode == 27) {document.activeElement.blur()} else if ($event.keyCode != 9) {$refs.searchInput.focus();}"
|
16
|
+
x-on:focusout="open = shouldClose($event)"
|
17
|
+
>
|
18
|
+
<div class="tags" x-ref="inputDisplayTags"
|
19
|
+
@click="
|
20
|
+
if ($event.target.classList.contains('delete')) {
|
21
|
+
let val = $event.target.getAttribute('data-value');
|
22
|
+
$refs.inputValue.querySelector(`option[value=${CSS.escape(val)}]`).remove();
|
23
|
+
$event.target.parentElement.remove();
|
24
|
+
$dispatch('change');
|
25
|
+
}
|
26
|
+
"
|
27
|
+
>
|
28
|
+
{% for obj in selected %}
|
29
|
+
<span class="tag has-text-weight-normal is-medium">
|
30
|
+
{{ obj }}
|
31
|
+
<button class="delete" type="button" data-value="{{ obj.pk }}"></button>
|
32
|
+
</span>
|
33
|
+
{% endfor %}
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
<div style="position: relative; top: 10px; z-index: 9">
|
37
|
+
<div x-show="open" class="box pt-1 mb-2" x-ref="dropdownContent" x-cloak="" tabindex="-1" x-transition style="position: absolute; width: 100%;">
|
38
|
+
<input id="id_{{ widget.uuid }}_search" type="search" autocomplete="off" class="input" x-ref="searchInput" aria-label="search"
|
39
|
+
name="{{ widget.search_parameter }}"
|
40
|
+
placeholder="{% translate 'Type to search' %}"
|
41
|
+
x-on:keydown="if ($event.keyCode == 27) {document.activeElement.blur()}"
|
42
|
+
x-on:focusout="open = shouldClose($event)"
|
43
|
+
hx-get="{{ widget.search_url }}"
|
44
|
+
hx-trigger="input changed delay:300ms, search"
|
45
|
+
hx-target="#id_{{ widget.uuid }}_dropdown_items"
|
46
|
+
hx-swap="innerHTML"
|
47
|
+
@change.stop=""
|
48
|
+
>
|
49
|
+
<div id="id_{{ widget.uuid }}_dropdown_items" class="hoverable" style="max-height: 200px; width: 100%; overflow-y: auto;"
|
50
|
+
x-on:click="
|
51
|
+
if (! $refs.inputValue.querySelectorAll(`option[value=${CSS.escape($event.target.value)}]`).length) {
|
52
|
+
let option = document.createElement('option');
|
53
|
+
let tag = document.createElement('span');
|
54
|
+
let delButton = document.createElement('button');
|
55
|
+
option.setAttribute('selected', '');
|
56
|
+
option.value = $event.target.value;
|
57
|
+
tag.classList.add('tag', 'has-text-weight-normal', 'is-medium');
|
58
|
+
tag.innerText = $event.target.innerText;
|
59
|
+
delButton.classList.add('delete');
|
60
|
+
delButton.type = 'button';
|
61
|
+
delButton.setAttribute('data-value', $event.target.value);
|
62
|
+
tag.appendChild(delButton);
|
63
|
+
$refs.inputValue.appendChild(option);
|
64
|
+
$refs.inputDisplayTags.appendChild(tag);
|
65
|
+
$dispatch('change');
|
66
|
+
};
|
67
|
+
{% if widget.hx_trigger_on_change %}htmx.trigger('#{{ widget.attrs.id }}_value', 'change');{% endif %}
|
68
|
+
"
|
69
|
+
x-on:keydown="
|
70
|
+
if ($event.keyCode == 27) {document.activeElement.blur()}
|
71
|
+
else if ($event.keyCode != 9 && $event.keyCode != 13) {$refs.searchInput.focus();}
|
72
|
+
"
|
73
|
+
>
|
74
|
+
{% include 'ui/widgets/model_search_select_options.html' %}
|
75
|
+
</div>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
</div>
|
@@ -1,15 +1,14 @@
|
|
1
1
|
import logging
|
2
|
+
import re
|
2
3
|
from datetime import datetime, date, timedelta
|
4
|
+
|
3
5
|
from django.utils.translation import gettext_lazy as _
|
4
6
|
from django import template
|
5
|
-
from django.db.models import Manager
|
6
|
-
from django.forms import Form, ModelForm
|
7
|
+
from django.db.models import Manager, DecimalField, IntegerField, FloatField, Model
|
7
8
|
from django.conf import settings
|
8
9
|
from django.template.loader import render_to_string
|
9
10
|
from django.utils.safestring import mark_safe
|
10
11
|
|
11
|
-
from accrete.contrib.ui import TableField, TableFieldType
|
12
|
-
|
13
12
|
_logger = logging.getLogger(__name__)
|
14
13
|
register = template.Library()
|
15
14
|
|
@@ -27,34 +26,19 @@ def combine_templates(template_name, request=None):
|
|
27
26
|
return mark_safe(html)
|
28
27
|
|
29
28
|
|
30
|
-
@register.filter(name='
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
if attr is None:
|
37
|
-
return None
|
38
|
-
if isinstance(attr, str):
|
39
|
-
return attr
|
40
|
-
return attr
|
41
|
-
f_name = field.name
|
42
|
-
if field.field_type == TableFieldType.CHOICE_DISPLAY:
|
43
|
-
f_name = f'get_{f_name}_display'
|
44
|
-
|
45
|
-
try:
|
46
|
-
attribute = getattr(obj, f_name, False)
|
47
|
-
except (AttributeError,):
|
48
|
-
_logger.warning(f'Object {obj} has no attribute {f_name}')
|
49
|
-
attribute = None
|
50
|
-
return return_save(attribute)
|
29
|
+
@register.filter(name='related_obj_url')
|
30
|
+
def related_obj_url(param: object, value: str):
|
31
|
+
attr = getattr(param, value, False)
|
32
|
+
if attr and hasattr(attr, 'get_absolute_url'):
|
33
|
+
return attr.get_absolute_url()
|
34
|
+
return '#'
|
51
35
|
|
52
36
|
|
53
37
|
@register.filter(name='get_attr')
|
54
38
|
def get_attr_from_string(param: object, value: str):
|
55
39
|
|
56
40
|
def return_save(attr):
|
57
|
-
if isinstance(attr, Manager):
|
41
|
+
if isinstance(attr, Manager) and hasattr(attr, 'related_model'):
|
58
42
|
return attr.related_model
|
59
43
|
if attr is None:
|
60
44
|
return None
|
@@ -70,43 +54,67 @@ def get_attr_from_string(param: object, value: str):
|
|
70
54
|
return return_save(attribute)
|
71
55
|
|
72
56
|
|
73
|
-
@register.filter(name='
|
74
|
-
def
|
75
|
-
|
76
|
-
if attr and hasattr(attr, 'get_absolute_url'):
|
77
|
-
return attr.get_absolute_url()
|
78
|
-
return '#'
|
57
|
+
@register.filter(name='has_attr')
|
58
|
+
def has_attr(param: object, value:str):
|
59
|
+
return hasattr(param, value)
|
79
60
|
|
80
61
|
|
81
|
-
@register.filter(name='
|
82
|
-
def
|
83
|
-
|
84
|
-
return 'is-success'
|
85
|
-
if param.level == 30:
|
86
|
-
return 'is-warning'
|
87
|
-
if param.level == 40:
|
88
|
-
return 'is-danger'
|
62
|
+
@register.filter(name='get_item')
|
63
|
+
def get_item(param, value):
|
64
|
+
return param[value]
|
89
65
|
|
90
66
|
|
91
|
-
@register.filter(name='
|
92
|
-
def
|
93
|
-
|
94
|
-
return False
|
95
|
-
for error in form.non_field_errors().as_data():
|
96
|
-
if error.code and error.code == code:
|
97
|
-
return True
|
98
|
-
return False
|
67
|
+
@register.filter(name='get_model')
|
68
|
+
def get_model(instance):
|
69
|
+
return instance._meta.model
|
99
70
|
|
100
71
|
|
101
|
-
@register.filter(name='
|
102
|
-
def
|
103
|
-
if not
|
72
|
+
@register.filter(name='verbose_field_name')
|
73
|
+
def verbose_field_name(param: object, value: str):
|
74
|
+
if not param:
|
104
75
|
return ''
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
return
|
76
|
+
if isinstance(param, Model):
|
77
|
+
name = param._meta.get_field(value).verbose_name
|
78
|
+
else:
|
79
|
+
name = getattr(param.model, value).field.verbose_name
|
80
|
+
return name
|
81
|
+
|
82
|
+
|
83
|
+
@register.filter(name='table_display')
|
84
|
+
def table_display(param: object, value: str):
|
85
|
+
field = getattr(param, value)
|
86
|
+
if isinstance(field, Manager):
|
87
|
+
return mark_safe(f"""
|
88
|
+
<span>
|
89
|
+
<div class="tags">
|
90
|
+
{''.join(['<span class="tag">{}</span>'.format(o) for o in field.all()])}
|
91
|
+
</div>
|
92
|
+
</span>
|
93
|
+
""")
|
94
|
+
return get_attr_from_string(param, value)
|
95
|
+
|
96
|
+
|
97
|
+
@register.filter(name='table_alignment')
|
98
|
+
def table_alignment(param, value):
|
99
|
+
if isinstance(param, str):
|
100
|
+
return 'left'
|
101
|
+
if isinstance(param, Model):
|
102
|
+
field = param._meta.get_field(value)
|
103
|
+
else:
|
104
|
+
field = getattr(param.model, value).field
|
105
|
+
if isinstance(field, (DecimalField, IntegerField, FloatField)):
|
106
|
+
return 'right'
|
107
|
+
return 'left'
|
108
|
+
|
109
|
+
|
110
|
+
@register.filter(name='message_class')
|
111
|
+
def message_class(message):
|
112
|
+
if message.level == 25:
|
113
|
+
return 'is-success'
|
114
|
+
if message.level == 30:
|
115
|
+
return 'is-warning'
|
116
|
+
if message.level == 40:
|
117
|
+
return 'is-danger'
|
110
118
|
|
111
119
|
|
112
120
|
@register.filter(name='timedelta_cast')
|
@@ -120,7 +128,7 @@ def timedelta_cast(td: timedelta, code: str) -> str | None:
|
|
120
128
|
|
121
129
|
|
122
130
|
@register.filter(name='weekday')
|
123
|
-
def datetime_to_weekday(dt: datetime|date, default=None):
|
131
|
+
def datetime_to_weekday(dt: datetime|date, default=None) -> str:
|
124
132
|
if dt is None:
|
125
133
|
return default
|
126
134
|
mapping = {
|
@@ -133,3 +141,7 @@ def datetime_to_weekday(dt: datetime|date, default=None):
|
|
133
141
|
7: _('Sun')
|
134
142
|
}
|
135
143
|
return mapping[dt.isoweekday()]
|
144
|
+
|
145
|
+
@register.filter(name='xrefsave')
|
146
|
+
def x_ref_save(param: str):
|
147
|
+
return re.sub(r'[^A-Za-z]+','', param)
|
accrete/contrib/ui/urls.py
CHANGED
@@ -5,5 +5,7 @@ from . import views
|
|
5
5
|
app_name = 'ui'
|
6
6
|
|
7
7
|
urlpatterns = [
|
8
|
-
path('', views.
|
8
|
+
path('filter/<str:model>/params/', views.params, name='filter_params'),
|
9
|
+
path('filter/<str:model>/input/', views.set_filter_input, name='filter_input'),
|
10
|
+
path('filter/<str:model>/query/add/', views.filter_add_query, name='filter_add_query')
|
9
11
|
]
|
accrete/contrib/ui/views.py
CHANGED
@@ -1,5 +1,35 @@
|
|
1
|
-
|
1
|
+
import json
|
2
2
|
|
3
|
+
from django.contrib.auth.decorators import login_required
|
4
|
+
from django.apps import apps
|
5
|
+
from django.http import HttpResponse
|
6
|
+
from .filter import Filter
|
3
7
|
|
4
|
-
|
5
|
-
|
8
|
+
|
9
|
+
@login_required
|
10
|
+
def params(request, model: str):
|
11
|
+
app_label, model_name = model.split('.')
|
12
|
+
Model = apps.get_model(app_label, model_name)
|
13
|
+
return HttpResponse(Filter(Model, request.GET).query_params())
|
14
|
+
|
15
|
+
|
16
|
+
@login_required
|
17
|
+
def set_filter_input(request, model: str):
|
18
|
+
app_label, model_name = model.split('.')
|
19
|
+
Model = apps.get_model(app_label, model_name)
|
20
|
+
lookup = request.GET.get('lookup')
|
21
|
+
return HttpResponse(Filter(Model, request.GET).query_input(lookup))
|
22
|
+
|
23
|
+
|
24
|
+
@login_required
|
25
|
+
def filter_add_query(request, model: str):
|
26
|
+
app_label, model_name = model.split('.')
|
27
|
+
Model = apps.get_model(app_label, model_name)
|
28
|
+
query = json.loads(request.GET.get('q', '[]'))
|
29
|
+
lookup = request.GET.get('filter_lookup')
|
30
|
+
value = request.GET.get('filter_input')
|
31
|
+
query.append({lookup: value})
|
32
|
+
query = json.dumps(query)
|
33
|
+
query_dict = request.GET.copy()
|
34
|
+
query_dict.update(q=query)
|
35
|
+
return HttpResponse(Filter(Model, query_dict).query_tags())
|
@@ -0,0 +1 @@
|
|
1
|
+
from .search_select import ModelSearchSelect, ModelSearchSelectMulti
|
@@ -0,0 +1,106 @@
|
|
1
|
+
from uuid import uuid4
|
2
|
+
from django.forms import widgets
|
3
|
+
from django.shortcuts import resolve_url
|
4
|
+
|
5
|
+
|
6
|
+
class ModelSearchSelect(widgets.NumberInput):
|
7
|
+
template_name = 'ui/widgets/model_search_select.html'
|
8
|
+
option_template_name = 'ui/widgets/model_search_select_options.html'
|
9
|
+
input_type = 'number'
|
10
|
+
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
search_url: str,
|
14
|
+
search_parameter: str = 'search',
|
15
|
+
limit: int | None = 5,
|
16
|
+
hx_trigger_on_change: bool = False,
|
17
|
+
choices=()
|
18
|
+
):
|
19
|
+
super().__init__()
|
20
|
+
self.search_url = search_url
|
21
|
+
self.search_parameter = search_parameter
|
22
|
+
self.limit = limit
|
23
|
+
self.hx_trigger_on_change = hx_trigger_on_change
|
24
|
+
self.choices = choices
|
25
|
+
|
26
|
+
def get_context(self, name, value, attrs):
|
27
|
+
qs = self.choices.queryset
|
28
|
+
if self.limit:
|
29
|
+
qs = qs[:self.limit]
|
30
|
+
uuid = str(uuid4())
|
31
|
+
|
32
|
+
context = {
|
33
|
+
"widget": {
|
34
|
+
"name": name,
|
35
|
+
"is_hidden": self.is_hidden,
|
36
|
+
"required": self.is_required,
|
37
|
+
"value": self.format_value(value or ''),
|
38
|
+
'value_display': self.value_display(value),
|
39
|
+
"attrs": self.build_attrs(self.attrs, attrs),
|
40
|
+
"template_name": self.template_name,
|
41
|
+
'search_url': self.search_url,
|
42
|
+
'search_parameter': self.search_parameter,
|
43
|
+
'hx_trigger_on_change': self.hx_trigger_on_change,
|
44
|
+
'uuid': uuid
|
45
|
+
},
|
46
|
+
'options': qs
|
47
|
+
}
|
48
|
+
return context
|
49
|
+
|
50
|
+
def format_value(self, value):
|
51
|
+
res = super().format_value(value)
|
52
|
+
return res or ''
|
53
|
+
|
54
|
+
def value_display(self, value):
|
55
|
+
if value is None:
|
56
|
+
return ''
|
57
|
+
try:
|
58
|
+
value = int(value)
|
59
|
+
except ValueError:
|
60
|
+
return ''
|
61
|
+
return str(self.choices.queryset.get(pk=value))
|
62
|
+
|
63
|
+
|
64
|
+
class ModelSearchSelectMulti(widgets.SelectMultiple):
|
65
|
+
template_name = 'ui/widgets/model_search_select_multi.html'
|
66
|
+
option_template_name = 'ui/widgets/model_search_select_options.html'
|
67
|
+
input_type = 'number'
|
68
|
+
|
69
|
+
def __init__(
|
70
|
+
self,
|
71
|
+
search_url: str,
|
72
|
+
search_parameter: str = 'search',
|
73
|
+
limit: int | None = 5,
|
74
|
+
hx_trigger_on_change: bool = False,
|
75
|
+
choices=()
|
76
|
+
):
|
77
|
+
super().__init__()
|
78
|
+
self.search_url = search_url
|
79
|
+
self.search_parameter = search_parameter
|
80
|
+
self.limit = limit
|
81
|
+
self.hx_trigger_on_change = hx_trigger_on_change
|
82
|
+
self.choices = choices
|
83
|
+
|
84
|
+
def get_context(self, name, value, attrs):
|
85
|
+
qs = self.choices.queryset
|
86
|
+
if self.limit:
|
87
|
+
qs = qs[:self.limit]
|
88
|
+
uuid = uuid4()
|
89
|
+
|
90
|
+
context = {
|
91
|
+
"widget": {
|
92
|
+
"name": name,
|
93
|
+
"is_hidden": self.is_hidden,
|
94
|
+
"required": self.is_required,
|
95
|
+
"value": self.format_value(value),
|
96
|
+
"attrs": self.build_attrs(self.attrs, attrs),
|
97
|
+
"template_name": self.template_name,
|
98
|
+
'search_url': resolve_url(self.search_url),
|
99
|
+
'search_parameter': self.search_parameter,
|
100
|
+
'hx_trigger_on_change': self.hx_trigger_on_change,
|
101
|
+
'uuid': uuid
|
102
|
+
},
|
103
|
+
'options': qs,
|
104
|
+
'selected': self.choices.queryset.filter(pk__in=value) if value else qs.none()
|
105
|
+
}
|
106
|
+
return context
|