accrete 0.0.143__py3-none-any.whl → 0.0.144__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 (39) hide show
  1. accrete/contrib/log/admin.py +1 -1
  2. accrete/contrib/system_mail/tasks.py +4 -5
  3. accrete/contrib/ui/__init__.py +15 -14
  4. accrete/contrib/ui/filter.py +22 -13
  5. accrete/contrib/ui/response.py +540 -0
  6. accrete/contrib/ui/static/css/accrete.css +69 -12
  7. accrete/contrib/ui/static/css/accrete.css.map +1 -1
  8. accrete/contrib/ui/static/css/accrete.scss +370 -300
  9. accrete/contrib/ui/templates/ui/content.html +0 -0
  10. accrete/contrib/ui/templates/ui/content_right.html +2 -2
  11. accrete/contrib/ui/templates/ui/detail.html +11 -0
  12. accrete/contrib/ui/templates/ui/filter/filter.html +5 -5
  13. accrete/contrib/ui/templates/ui/filter/query_tags.html +1 -1
  14. accrete/contrib/ui/templates/ui/layout.html +69 -64
  15. accrete/contrib/ui/templates/ui/list.html +21 -19
  16. accrete/contrib/ui/templates/ui/list_update.html +9 -3
  17. accrete/contrib/ui/templates/ui/message.html +1 -1
  18. accrete/contrib/ui/templates/ui/modal.html +41 -32
  19. accrete/contrib/ui/templates/ui/table_row_update.html +2 -2
  20. accrete/contrib/ui/templates/ui/templatetags/field.html +19 -6
  21. accrete/contrib/ui/templates/ui/widgets/model_search_select.html +7 -7
  22. accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html +3 -3
  23. accrete/contrib/ui/templatetags/ui.py +24 -2
  24. accrete/contrib/user/templates/user/password_forgotten.html +1 -0
  25. accrete/contrib/user/templates/user/user_preferences.html +43 -0
  26. accrete/contrib/user/views.py +36 -37
  27. accrete/migrations/0008_alter_member_access_groups_and_more.py +23 -0
  28. accrete/utils/__init__.py +1 -0
  29. accrete/utils/forms.py +11 -3
  30. accrete/utils/views.py +1 -2
  31. accrete/views.py +2 -3
  32. {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/METADATA +2 -2
  33. {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/RECORD +35 -33
  34. accrete/contrib/ui/context.py +0 -123
  35. accrete/contrib/ui/static/css/.sass-cache/15adf1eed05371361b08787c918a7f18fc15be79/accrete.scssc +0 -0
  36. accrete/contrib/ui/utils.py +0 -88
  37. accrete/contrib/user/templates/user/user_form.html +0 -58
  38. {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/WHEEL +0 -0
  39. {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,13 @@
1
1
  {% load partials %}
2
2
 
3
3
  {% partialdef header inline=True %}
4
- <div id="content-right-header" class="is-flex is-flex-wrap-wrap">
4
+ <div id="detail-header" class="is-flex is-flex-wrap-wrap is-flex-grow-1">
5
5
  {% block header %}{% endblock %}
6
6
  </div>
7
7
  {% endpartialdef %}
8
8
 
9
9
  {% partialdef content inline=True %}
10
- <div id="content-right" style="overflow-y: auto; height: 100%;">
10
+ <div id="detail" style="overflow-y: auto; height: 100%;">
11
11
  {% block content %}{% endblock %}
12
12
  </div>
13
13
  {% endpartialdef %}
@@ -0,0 +1,11 @@
1
+ {% if detail_header_template %}
2
+ <div hx-select-oob="innerHTML:#detail-header">
3
+ {% include detail_header_template %}
4
+ </div>
5
+ {% endif %}
6
+
7
+ {% if detail_data_template %}
8
+ <div hx-swap-oob="innerHTML:#detail-data">
9
+ {% include detail_data_template %}
10
+ </div>
11
+ {% endif %}
@@ -7,21 +7,21 @@
7
7
  <div id="filter" x-data="filter">
8
8
 
9
9
  <div id="query-tags" x-ref="queryTags" class="mb-1">
10
- {{ filter.query_tags }}
10
+ {{ ui_filter.query_tags }}
11
11
  </div>
12
12
 
13
13
  <div id="query-input" class="">
14
- {{ filter.query_input }}
14
+ {{ ui_filter.query_input }}
15
15
  </div>
16
16
 
17
17
  <div class="mb-6" style="width: 100%" x-show="showParams" x-cloak="" x-on:click.outside="showParams = false;" x-transition.duration.150ms>
18
- {{ filter.query_params }}
18
+ {{ ui_filter.query_params }}
19
19
  </div>
20
20
 
21
21
  <div id="query-apply" x-ref="queryApply"
22
22
  hx-trigger="click" hx-replace-url="true"
23
- hx-target="#content-left-container"
24
- hx-select="#content-left-container"
23
+ hx-target="#overview-container"
24
+ hx-select="#overview-container"
25
25
  hx-select-oob="#query-tags,#query-apply">
26
26
  </div>
27
27
  </div>
@@ -20,7 +20,7 @@
20
20
  {% for item in tag.lhs %}{{ item }}{% if forloop.last %}<span class="mr-1">:</span>{% else %} > {% endif %}{% endfor %}
21
21
  <span class="has-text-weight-bold">{{ tag.rhs }}</span></span>
22
22
  </div>
23
- <button class="delete" x-on:click="removeTag();"></button>
23
+ <button class="delete my-2" x-on:click="removeTag();"></button>
24
24
  </div>
25
25
  </div>
26
26
  {% endfor %}
@@ -11,7 +11,7 @@
11
11
  <meta charset="utf-8">
12
12
  <meta name="viewport" content="width=device-width, initial-scale=1">
13
13
  {% include 'ui/favicon.html' %}
14
- <link rel="stylesheet" type="text/css" href="{% static "css/accrete.css" %}?v=4">
14
+ <link rel="stylesheet" type="text/css" href="{% static "css/accrete.css" %}?v=0.0.144">
15
15
  <link rel="stylesheet" type="text/css" href="{% static "css/icons.css" %}">
16
16
  <link rel="stylesheet" type="text/css" href="{% static "css/fa.css" %}">
17
17
 
@@ -52,12 +52,14 @@
52
52
  >
53
53
  <nav id="navbar" class="navbar is-success is-fixed-top" role="navigation" aria-label="main navigation">
54
54
  <div class="navbar-brand">
55
- <div class="navbar-item is-hidden-widescreen pr-2" x-on:click.stop="$dispatch('toggle-panel')">
56
- <button>
57
- <i class="fa fa-indent" x-show="!showPanel"></i>
58
- <i class="fa fa-outdent" x-show="showPanel"></i>
59
- </button>
60
- </div>
55
+ {% if has_panel %}
56
+ <div class="navbar-item is-hidden-widescreen pr-2" x-on:click.stop="$dispatch('toggle-panel')">
57
+ <button>
58
+ <i class="fa fa-indent" x-show="!showPanel"></i>
59
+ <i class="fa fa-outdent" x-show="showPanel"></i>
60
+ </button>
61
+ </div>
62
+ {% endif %}
61
63
  <div class="navbar-item is-unselectable has-text-weight-bold"
62
64
  onclick="document.getElementById('tenant-quick-switch').classList.toggle('is-invisible')"
63
65
  >
@@ -83,76 +85,74 @@
83
85
  </div>
84
86
 
85
87
  <div class="navbar-end">
86
- {% block navbar_end %}
87
- <div class="navbar-item has-dropdown" x-data="{navbarEndDropdown: false}"
88
- x-bind:class="navbarEndDropdown ? 'is-active' : ''"
89
- x-on:click="navbarEndDropdown = !navbarEndDropdown"
90
- x-on:click.outside="navbarEndDropdown = false"
91
- >
92
- <a id="navbar-user-name" class="navbar-link is-arrowless">{{ user }}</a>
93
- <div id="navbar-end-dropdown" class="navbar-dropdown is-right">
94
- {% combine_templates 'accrete_navbar_end_dropdown.html' request %}
95
- </div>
88
+
89
+ <div class="navbar-item has-dropdown" x-data="{navbarEndDropdown: false}"
90
+ x-bind:class="navbarEndDropdown ? 'is-active' : ''"
91
+ x-on:click="navbarEndDropdown = !navbarEndDropdown"
92
+ x-on:click.outside="navbarEndDropdown = false"
93
+ >
94
+ <a id="navbar-user-name" class="navbar-link is-arrowless">{{ user }}</a>
95
+ <div id="navbar-end-dropdown" class="navbar-dropdown is-right">
96
+ {% combine_templates 'accrete_navbar_end_dropdown.html' request %}
96
97
  </div>
97
- {% endblock %}
98
+ </div>
98
99
  </div>
99
100
  </div>
100
101
  </nav>
101
102
 
102
103
  <div class="is-flex" style="padding-top: var(--bulma-navbar-height); height: 100svh;" x-init="showPanel = window.innerWidth >= 1216;">
103
- <div x-show="showPanel" >
104
- <div id="action-panel" x-show="showPanel"
105
- @resize.debounce.100.window="openPanel()"
106
- @toggle-panel.window="showPanel = !showPanel"
107
- x-transition.duration.200ms
108
- >
109
- <nav class="px-1" style="height: 100%; overflow-y: auto; position: sticky; top: 0">
110
- {% block panel %}{% endblock %}
111
- {% if filter %}
112
- <div class="mt-4">
113
- <div class="mx-3 mb-1" style="border-bottom: 1px var(--bulma-grey) solid">
114
- <span>{% translate 'Filter' %}</span>
115
- </div>
116
- <div class="p-3">
117
- {% include 'ui/filter/filter.html' %}
104
+ {% if has_panel %}
105
+ <div x-show="showPanel">
106
+ <div id="action-panel" x-show="showPanel"
107
+ @resize.debounce.100.window="openPanel()"
108
+ @toggle-panel.window="showPanel = !showPanel"
109
+ x-transition.duration.200ms
110
+ >
111
+ <nav class="px-1" style="height: 100%; overflow-y: auto; position: sticky; top: 0">
112
+ {% if panel_template %}{% include panel_template %}{% endif %}
113
+ {% if ui_filter %}
114
+ <div class="mt-4">
115
+ <div class="mx-3 mb-1" style="border-bottom: 1px var(--bulma-grey) solid">
116
+ <span>{% translate 'Filter' %}</span>
117
+ </div>
118
+ <div class="p-3">
119
+ {% include 'ui/filter/filter.html' %}
120
+ </div>
118
121
  </div>
119
-
120
- </div>
121
- {% endif %}
122
- </nav>
122
+ {% endif %}
123
+ </nav>
124
+ </div>
123
125
  </div>
124
- </div>
126
+ {% endif %}
125
127
 
126
128
  <div class="is-flex-grow-1" style="height: 100%; overflow-x: auto;">
127
129
  <div id="content" x-ref="content" style="height: calc(100svh - var(--bulma-navbar-height))">
128
130
  <div id="message" class="" style="position: fixed; top: calc(var(--bulma-navbar-height) + 5px); left: 50%; transform: translateX(-50%); z-index: 999">
129
131
  {% for message in messages %}
130
132
  <div class="mb-2" style="min-width: 330px; max-width: 330px" x-data="{ show: false }" x-show="show" x-cloak="" x-init="show = true; setTimeout(() => show = false, 4000)" x-transition.duration.200ms>
131
- <div class="notification is-light {{ message|message_class }}">
132
- <p>{{ message }}</p>
133
+ <div class="notification is-light has-text-centered {{ message|message_class }}" style="box-shadow: 2px 2px 5px">
134
+ {{ message }}
133
135
  </div>
134
136
  </div>
135
137
  {% endfor %}
136
138
  </div>
137
139
 
138
140
  <div x-data="{showContentRight: false}"
139
- @activate-content-right.window="showContentRight = true;"
140
- @deactivate-content-right.window="showContentRight = false;"
141
+ @activate-detail.window="showContentRight = true;"
142
+ @deactivate-detail.window="showContentRight = false;"
141
143
  class="is-flex is-flex-direction-row"
142
144
  style="overflow: hidden; height: calc(100svh - var(--bulma-navbar-height))"
143
145
  >
144
146
 
145
- <div id="content-left-container" class="table-container is-flex is-flex-grow-1 is-flex-direction-column mb-0" x-bind:class="showContentRight ? 'is-hidden-mobile' : ''">
146
- <div id="content-left-header">
147
- <div class="is-flex is-justify-content-space-between is-flex-wrap-wrap">
148
- <div class="is-flex is-flex-wrap-wrap is-flex-grow-5 is-align-self-center py-2">
149
- {% block header_left %}
150
- <div class="mr-1">
151
- <p class="title is-5 px-2 py-1 my-1">{{ title }}</p>
152
- </div>
153
- <div id="content-left-buttons" class="buttons mx-2">
154
- {% block header_left_buttons %}{% endblock %}
147
+ <div id="overview-container" class="table-container is-flex is-flex-grow-1 is-flex-direction-column mb-0" x-bind:class="showContentRight ? 'is-hidden-mobile' : ''">
148
+ <div id="overview-header">
149
+ <div class="is-flex is-justify-content-space-between is-flex-wrap-wrap {% if is_centered %}container{% endif %}">
150
+ <div class="is-flex is-flex-wrap-wrap is-flex-grow-5 is-align-self-center py-2 header-items">
151
+ {% block overview_header %}
152
+ <div class="ml-0" style="align-self: center">
153
+ <p class="title is-5">{{ title }}</p>
155
154
  </div>
155
+ {% if header_template %}{% include header_template %}{% endif %}
156
156
  {% endblock %}
157
157
  </div>
158
158
  {% if page %}
@@ -162,7 +162,7 @@
162
162
  <button id="pagination-prev-button" class="button"
163
163
  hx-get="{% if page.has_previous %}{% querystring page=page.previous_page_number %}{% else %}{% querystring page=page.paginator.num_pages %}{% endif %}"
164
164
  hx-replace-url="true"
165
- hx-select-oob="#content-left,#pagination,#content-left-buttons"
165
+ hx-select-oob="#overview,#pagination,#overview-buttons"
166
166
  >
167
167
  &lt;
168
168
  </button>
@@ -180,7 +180,7 @@
180
180
  <button id="pagination-next-button" class="button"
181
181
  hx-get="{% if page.has_next %}{% querystring page=page.next_page_number %}{% else %}{% querystring page=1 %}{% endif %}"
182
182
  hx-replace-url="true"
183
- hx-select-oob="#content-left,#pagination,#content-left-buttons"
183
+ hx-select-oob="#overview,#pagination,#overview-buttons"
184
184
  >
185
185
  &gt;
186
186
  </button>
@@ -190,25 +190,30 @@
190
190
  {% endif %}
191
191
  </div>
192
192
  </div>
193
- <div id="content-left" class="is-flex-grow-1" style="overflow-y: auto;">
194
- {% block content_left %}{% endblock %}
193
+ <div id="overview" class="is-flex-grow-1" style="overflow-y: auto;">
194
+ <section class="{% if is_centered %}container{% endif %}">
195
+ {% if overview_template %}{% include overview_template %}{% endif %}
196
+ </section>
195
197
  </div>
196
198
  </div>
197
199
 
198
- <div id="content-right-container" x-show="showContentRight" style="display: none;"
199
- x-transition:enter.duration.500ms x-transition:leave.duration.150ms
200
+ <div id="detail-container" x-show="showContentRight" style="display: none;"
201
+ x-transition:enter.duration.200ms x-transition:leave.duration.50ms
200
202
  >
203
+ <div id="detail-indicator" class="htmx-indicator">
204
+ <progress class="progress is-success" max="100"></progress>
205
+ </div>
201
206
  <div class="is-flex is-flex-direction-column" style="height: 100%">
202
- <div class="is-flex is-flex-wrap-nowrap is-justify-content-space-between m-1">
203
- <div id="content-right-header" class="is-flex is-flex-wrap-wrap">
204
- {% block header_right %}{% endblock %}
207
+ <div class="is-flex is-flex-wrap-nowrap is-justify-content-space-between m-2">
208
+ <div id="detail-header" class="is-flex is-flex-wrap-wrap is-flex-grow-1">
209
+ {% if detail_header_template %}{% include detail_header_template %}{% endif %}
205
210
  </div>
206
- <button class="button is-light is-align-self-baseline m-1" style="border-radius: var(--bulma-radius-medium)" x-on:click="showContentRight = false;">
211
+ <button class="button is-light is-align-self-baseline" style="border-radius: var(--bulma-radius-medium)" x-on:click="showContentRight = false;">
207
212
  <span class="icon"><i class="fa fa-xmark"></i></span>
208
213
  </button>
209
214
  </div>
210
- <div id="content-right" style="overflow-y: auto; flex-grow: 1">
211
- {% block content_right %}{% endblock %}
215
+ <div id="detail-data" style="overflow-y: auto; flex-grow: 1">
216
+ {% if detail_data_template %}{% include detail_data_template %}{% endif %}
212
217
  </div>
213
218
  </div>
214
219
  </div>
@@ -1,29 +1,32 @@
1
- {% extends 'ui/layout.html' %}
1
+ {#{% extends 'ui/layout.html' %}#}
2
2
  {% load i18n %}
3
3
  {% load ui %}
4
4
  {% load partials %}
5
5
 
6
- {% block content_left %}
7
- {% partialdef content inline=True %}
6
+ {% block overview %}
8
7
  <div class="fixed-grid has-{{ column_count }}-cols has-1-cols-mobile m-2">
9
8
  <div id="list-grid" class="grid m-0">
10
- {% for object in page.object_list %}
11
- <div id="list-entry-{{ object.pk }}" class="list-entry cell pb-0" style="height: {{ column_height }}{{ column_height_unit }}">
12
- <div class="box p-3"
13
- style="word-break: break-word; height: 100%; overflow-y: auto; {% if object.get_absolute_url %}cursor:pointer;{% endif %}"
14
- {% if object.get_absolute_url %}
15
- hx-get="{{ object.get_absolute_url }}{% querystring %}" hx-swap="none"
16
- x-on:click="$dispatch('unselect-list-entry'); selected = true; $nextTick(() => { $el.scrollIntoView( {block: 'nearest'} ) });" x-bind:class="selected ? 'box selected' : ''"
17
- {% endif %}
18
- x-data="{selected: false}" @unselect-list-entry.window="selected = false"
19
- >
20
- <div id="list-data-{{ object.pk }}">
21
- {% block data %}
22
- {{ object }}
23
- {% endblock %}
9
+ {% for instance in page.object_list %}
10
+ {% partialdef entry inline=True %}
11
+ <div id="list-entry-{{ instance.pk }}" class="list-entry cell pb-0" style="height: {{ column_height }}">
12
+ <div class="box p-3"
13
+ style="word-break: break-word; height: 100%; overflow-y: auto; {% if instance.get_absolute_url %}cursor:pointer;{% endif %}"
14
+ {% if instance.get_absolute_url %}
15
+ hx-get="{{ instance.get_absolute_url }}{% querystring %}" hx-swap="none" hx-indicator="#detail-indicator"
16
+ x-on:click="$dispatch('unselect-list-entry'); selected = true; $nextTick(() => { $el.scrollIntoView({block: 'nearest'}) });" x-bind:class="selected ? 'box selected' : ''"
17
+ {% endif %}
18
+ x-data="{selected: false}" @unselect-list-entry.window="selected = false"
19
+ >
20
+ <div id="list-data-{{ instance.pk }}">
21
+ {% if list_entry_template %}
22
+ {% include list_entry_template %}
23
+ {% else %}
24
+ {{ instance }}
25
+ {% endif %}
26
+ </div>
24
27
  </div>
25
28
  </div>
26
- </div>
29
+ {% endpartialdef %}
27
30
  {% endfor %}
28
31
  </div>
29
32
  {% if endless_scroll and page.has_next %}
@@ -39,5 +42,4 @@
39
42
  <progress id="endless-scroll-indicator" class="htmx-indicator progress is-small is-success" max="100">15%</progress>
40
43
  {% endif %}
41
44
  </div>
42
- {% endpartialdef %}
43
45
  {% endblock %}
@@ -1,3 +1,9 @@
1
- <div hx-swap-oob="innerHTML:#list-data-{{ object.pk }}">
2
- {% include template with object=object %}
3
- </div>
1
+ {% if is_new %}
2
+ <div hx-swap-oob="beforeend:#list-grid">
3
+ {% include 'ui/list.html#entry' %}
4
+ </div>
5
+ {% else %}
6
+ <div hx-swap-oob="innerHTML:#list-data-{{ object.pk }}">
7
+ {% include list_entry_template with instance=instance %}
8
+ </div>
9
+ {% endif %}
@@ -4,7 +4,7 @@
4
4
  {% for message in messages %}
5
5
  <div hx-swap-oob="{% if append %}beforeend:#message{% else %}innerHTML:#message{% endif %}">
6
6
  <div class="mb-2" style="min-width: 340px; max-width: 340px" x-data="{ show: false }" x-show="show" x-cloak="" x-init="show = true; {% if not persistent %}setTimeout(() => show = false, 4000){% endif %}" x-transition.duration.200ms>
7
- <div class="notification has-text-centered {{ message|message_class }}" style="box-shadow: 2px 2px 5px">
7
+ <div class="notification is-light has-text-centered {{ message|message_class }}" style="box-shadow: 2px 2px 5px">
8
8
  {% if persistent %}<button class="delete" x-on:click="show = false;"></button>{% endif %}
9
9
  {{ message }}
10
10
  </div>
@@ -1,39 +1,48 @@
1
1
  {% load i18n %}
2
2
  {% load ui %}
3
3
 
4
- <div id="{{ modal_id }}" x-data="{showModal: false, close() {$dispatch('close-modal-{{ modal_id }}')} }" x-ref="modal" class="modal is-active"
5
- @close-modal-{{ modal_id }}.window="showModal = false; setTimeout(() => $refs.modal.remove(), 200);"
6
- @close-modals.window="showModal = false; setTimeout(() => $refs.modal.remove(), 200);"
7
- @keyup.escape.window="if (! $refs.{{ modal_id|xrefsave }}Indicator.classList.contains('htmx-request')) {$dispatch('close-modal-{{ modal_id }}')}"
8
- {% if blocking %}
9
- hx-indicator="#{{ modal_id }}-indicator"
10
- hx-disabled-elt="#{{ modal_id }}-background"
11
- {% endif %}
12
- >
13
- <button id="{{ modal_id }}-background" class="modal-background" @click="$dispatch('close-modal-{{ modal_id }}')" style="cursor: default"></button>
14
- <div class="modal-card" x-show="showModal" x-transition.duration.200ms x-init="$nextTick(() => {showModal = true;})" >
15
- <header class="modal-card-head">
16
- <p class="modal-card-title is-flex-shrink-1">
17
- {{ title }}
18
- </p>
19
- <button class="delete is-align-self-baseline" aria-label="close" @click="$dispatch('close-modal-{{ modal_id }}')"></button>
20
- </header>
21
- <section class="modal-card-body">
22
- {% block modal_content %}{% endblock %}
23
- </section>
24
- <footer class="modal-card-foot">
25
- {% block modal_footer %}
26
- <div class="buttons" style="width: 100%">
27
- {% block modal_buttons %}
28
- <button type="button" class="button is-light" @click="$dispatch('close-modal-{{ modal_id }}')">
29
- {% translate 'Close' %}
30
- </button>
31
- {% endblock %}
4
+ <div hx-swap-oob="{% if is_update %}outerHTML:#{{ modal_id }}{% else %}beforeend:body{% endif %}">
5
+ <div id="{{ modal_id }}" x-data="{showModal: false, close() {$dispatch('close-modal-{{ modal_id }}')} }" x-ref="modal" class="modal is-active is-justify-content-flex-start"
6
+ style="padding-top: calc(var(--bulma-navbar-height) + 20px); padding-bottom: 40px;"
7
+ @close-modal-{{ modal_id }}.window="showModal = false; setTimeout(() => $refs.modal.remove(), 200);"
8
+ @close-modals.window="showModal = false; setTimeout(() => $refs.modal.remove(), 200);"
9
+ {% if not is_blocking %}@keyup.escape.window="if (! $refs.{{ modal_id|xrefsave }}Indicator.classList.contains('htmx-request')) {$dispatch('close-modal-{{ modal_id }}')}"{% endif %}
10
+ {% if is_blocking %}{% endif %}hx-indicator="#{{ modal_id }}-indicator" hx-disabled-elt="#{{ modal_id }}-background"
11
+ >
12
+ <button id="{{ modal_id }}-background" class="modal-background" {% if not is_blocking %}@click="$dispatch('close-modal-{{ modal_id }}')"{% endif %} style="cursor: default"></button>
13
+
14
+ <div class="modal-card" style="width: {{ modal_width|default_if_none:'var(--bulma-modal-content-width)' }}" x-show="showModal" x-transition.duration.200ms x-init="$nextTick(() => {showModal = true;})">
15
+
16
+ <header class="modal-card-head pt-0 pr-0 pb-2 pl-2" style="box-shadow: none; background-color: var(--bulma-scheme-main)">
17
+ <div class="is-flex is-justify-content-space-between is-flex-wrap-nowrap" style="width: 100%">
18
+ <p class="is-flex-grow-1 is-align-self-center has-text-weight-bold pt-1" style="text-align: center">{{ title|default_if_none:'' }}</p>
19
+ {% if not is_blocking %}
20
+ <button class="button is-subtle is-align-self-baseline"
21
+ style="border-top-right-radius: var(--bulma-modal-card-head-radius); border-bottom-left-radius: var(--bulma-modal-card-head-radius); border-top-left-radius: 0; border-bottom-right-radius: 0"
22
+ @click="$dispatch('close-modal-{{ modal_id }}')"
23
+ ><span class="icon"><i class="fa fa-xmark"></i></span></button>
24
+ {% endif %}
32
25
  </div>
33
- {% endblock %}
34
- </footer>
35
- <div id="{{ modal_id }}-indicator" x-ref="{{ modal_id|xrefsave }}Indicator" class="htmx-indicator modal-request-overlay" style="position: absolute; inset: 0; background: rgba(47, 47, 62, 0.2)">
36
- <progress class="progress is-success" max="100" style="position: fixed; inset: 0; min-width: 300px; max-width: 20vw; margin: auto;"></progress>
26
+ </header>
27
+
28
+ <section class="modal-card-body">
29
+ {% block modal_content %}{% endblock %}
30
+ </section>
31
+
32
+ <footer class="modal-card-foot pt-2 pb-3 px-3" style="background-color: var(--bulma-scheme-main)">
33
+ {% block modal_footer %}
34
+ <div class="buttons is-right" style="width: 100%">
35
+ {% block modal_buttons %}
36
+ <button type="button" class="button is-light" @click="$dispatch('close-modal-{{ modal_id }}')">
37
+ {% translate 'Close' %}
38
+ </button>
39
+ {% endblock %}
40
+ </div>
41
+ {% endblock %}
42
+ </footer>
43
+ <div id="{{ modal_id }}-indicator" x-ref="{{ modal_id|xrefsave }}Indicator" class="htmx-indicator modal-request-overlay" style="position: absolute; inset: 0; background: rgba(47, 47, 62, 0.2)">
44
+ <progress class="progress is-success" max="100" style="position: absolute; inset: 0; min-width: 300px; max-width: 20vw; margin: auto;"></progress>
45
+ </div>
37
46
  </div>
38
47
  </div>
39
48
  </div>
@@ -1,13 +1,13 @@
1
1
  <table>
2
2
  <tbody>
3
3
  <tr hx-swap-oob="innerHTML:#tr-{{ object.pk }}">
4
- {% include 'ui/table.html#td' with object=object fields=fields queryset=queryset %}
4
+ {% include 'ui/table.html#td' with object=object fields=fields %}
5
5
  </tr>
6
6
  </tbody>
7
7
  {% if footer %}
8
8
  <tfoot>
9
9
  <tr hx-swap-oob="innerHTML:#tf-row">
10
- {% include 'ui/table.html#tf' with object=object fields=fields queryset=queryset %}
10
+ {% include 'ui/table.html#tf' with object=object fields=fields %}
11
11
  </tr>
12
12
  </tfoot>
13
13
  {% endif %}
@@ -1,6 +1,19 @@
1
- <label class="label mt-2 mb-5">
2
- {{ field.label }}
3
- {{ field }}
4
- <span class="has-text-danger is-size-7">{{ field.errors }}</span>
5
- <p class="helptext">{{ field.help_text }}</p>
6
- </label>
1
+ {% load ui %}
2
+ {% load partials %}
3
+
4
+ {% partialdef form_field %}
5
+ <label class="label mt-2 mb-5">
6
+ {{ field }}
7
+ <span class="has-text-danger is-size-6">{{ field.errors }}</span>
8
+ <p class="helptext {% if field.field.required %}has-text-weight-bold{% endif %}">{% if label %}{{ label }}{% else %}{{ field.help_text|default_if_falsy:field.label }}{% endif %}</p>
9
+ </label>
10
+ {% endpartialdef %}
11
+
12
+ {% partialdef model_field %}
13
+ <label class="label mt-2 mb-5">
14
+ <span class="field has-addons mb-0">
15
+ <span class="control input has-text-weight-normal">{{ value|default_if_none:'' }}</span>
16
+ </span>
17
+ <span class="helptext {% if required %}has-text-weight-bold{% endif %}">{{ label }}</span>
18
+ </label>
19
+ {% endpartialdef %}
@@ -4,18 +4,18 @@
4
4
 
5
5
  <div class="select is-fullwidth" x-data="{open: false}"
6
6
  @click.outside="open = false" hx-disinherit="*" @keyup.escape="if (open) {$event.stopPropagation();} open = false;"
7
+ x-on:keydown="if (!open && [38, 40].includes($event.keyCode)) {open = true};"
7
8
  >
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 }}"
9
+ <input id="id_{{ widget.name }}_value" type="{{ widget.type }}" name="{{ widget.name }}" x-ref="inputValue" aria-label="inputValue"
10
+ value="{{ widget.value }}" style="display: none"
10
11
  >
11
12
  <input id="id_{{ widget.name }}_display" class="input" type="text" readonly x-ref="inputDisplay" value="{{ widget.value_display }}" aria-label="inputDisplay"
12
13
  style="padding-right: 30px"
13
- x-on:focus="open = true;"
14
- x-on:keydown="if (![9, 27, 38, 40].includes($event.keyCode)) {$refs.searchInput.focus();}"
15
- x-on:mousedown.stop="if (open) {open = false; } else {open = true; $refs.searchInput.focus()}"
14
+ x-on:keydown="if (!open && [38, 40].includes($event.keyCode)) {}; if (![9, 27, 38, 40].includes($event.keyCode)) {$refs.searchInput.focus();}"
15
+ x-on:click.stop="if (open) {open = false; } else {open = true; $refs.searchInput.focus()}"
16
16
  >
17
- <div style="position: relative; top: 10px; z-index: 90;">
18
- <div x-show="open" x-cloak="" class="box pt-1 px-3 mb-2" x-ref="dropdownContent" tabindex="-1" x-transition style="position: absolute; width: 100%">
17
+ <div style="position: relative; top: 1.5rem; z-index: 90;">
18
+ <div x-show="open" x-effect="if (open) {$nextTick(() => { $refs.dropdownContent.scrollIntoView(true) });}" x-cloak="" class="box pt-1 px-3 mb-2" x-ref="dropdownContent" tabindex="-1" x-transition style="position: absolute; width: 100%">
19
19
  <input id="id_{{ widget.name }}_search" type="search" autocomplete="off" class="input mb-2" x-ref="searchInput" aria-label="search"
20
20
  name="{{ widget.search_parameter }}"
21
21
  placeholder="{% translate 'Type to search' %}"
@@ -3,6 +3,7 @@
3
3
 
4
4
  <div class="is-fullwidth" x-data="{open: false}"
5
5
  @click.outside="open = false" hx-disinherit="*" @keyup.escape="if (open) {$event.stopPropagation();} open = false;"
6
+ x-on:keydown="if (!open && [38, 40].includes($event.keyCode)) {open = true};"
6
7
  >
7
8
  <select multiple id="{{ widget.attrs.id }}" hidden="hidden" name="{{ widget.name }}" aria-label="{{ widget.name }}" x-ref="inputValue">
8
9
  {% for obj in selected %}
@@ -11,7 +12,6 @@
11
12
  </select>
12
13
  <div id="{{ widget.attrs.id }}_display" class="input select py-1 pl-1" tabindex="0" x-ref="inputDisplay" aria-label="inputDisplay"
13
14
  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="if (!$event.target.classList.contains('delete')) {open = true;}"
15
15
  x-on:keydown="if (![9, 27, 38, 40].includes($event.keyCode)) {$refs.searchInput.focus();}"
16
16
  x-on:mousedown.stop="if (!$event.target.classList.contains('delete')) {if (open) {open = false; } else {open = true; $refs.searchInput.focus();}}"
17
17
  >
@@ -33,8 +33,8 @@
33
33
  {% endfor %}
34
34
  </div>
35
35
  </div>
36
- <div style="position: relative; top: 10px; z-index: 9">
37
- <div x-show="open" class="box pt-1 px-3 mb-2" x-ref="dropdownContent" x-cloak="" tabindex="-1" x-transition style="position: absolute; width: 100%">
36
+ <div style="position: relative; top: 1.5rem; z-index: 9">
37
+ <div x-show="open" x-effect="if (open) {$nextTick(() => { $refs.dropdownContent.scrollIntoView(true) });}" class="box pt-1 px-3 mb-2" x-ref="dropdownContent" x-cloak="" tabindex="-1" x-transition style="position: absolute; width: 100%">
38
38
  <input id="id_{{ widget.uuid }}_search" type="search" autocomplete="off" class="input mb-2" x-ref="searchInput" aria-label="search"
39
39
  name="{{ widget.search_parameter }}"
40
40
  placeholder="{% translate 'Type to search' %}"
@@ -150,6 +150,28 @@ def x_ref_save(param: str):
150
150
 
151
151
 
152
152
  @register.filter(name='wrap_label')
153
- def wrap_label(field):
154
- html = render_to_string('ui/templatetags/field.html', {'field': field})
153
+ def wrap_label(field, label=None):
154
+ html = render_to_string('ui/templatetags/field.html#form_field', {'field': field, 'label': label})
155
155
  return mark_safe(html)
156
+
157
+
158
+ @register.filter(name='wrap_model_field')
159
+ def wrap_model_field(instance, field_name):
160
+ field = instance._meta.get_field(field_name)
161
+ if field_name == 'invoicing_period' and field.choices:
162
+ value = getattr(instance, f'get_{field_name}_display')()
163
+ else:
164
+ value = getattr(instance, field_name)
165
+ html = render_to_string(
166
+ 'ui/templatetags/field.html#model_field', {
167
+ 'label': field.help_text or field.verbose_name,
168
+ 'value': value
169
+ })
170
+ return mark_safe(html)
171
+
172
+
173
+ @register.filter(name='default_if_falsy')
174
+ def default_if_falsy(value, default):
175
+ if bool(value):
176
+ return value
177
+ return default
@@ -0,0 +1 @@
1
+ {% extends 'ui/layout.html' %}