django-spire 0.24.2__py3-none-any.whl → 0.25.0__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.
- django_spire/consts.py +1 -1
- django_spire/contrib/admin/__init__.py +0 -0
- django_spire/contrib/admin/admin.py +140 -0
- django_spire/contrib/choices/__init__.py +0 -0
- django_spire/contrib/choices/choices.py +9 -0
- django_spire/contrib/choices/tests/__init__.py +0 -0
- django_spire/contrib/choices/tests/test_choices.py +62 -0
- django_spire/core/static/django_spire/css/app-printing.css +25 -0
- django_spire/core/templates/django_spire/dropdown/ellipsis_modal_dropdown.html +15 -3
- django_spire/core/templates/django_spire/element/attribute_element.html +7 -0
- django_spire/core/templates/django_spire/element/copy_to_clipboard_element.html +31 -0
- django_spire/metric/report/__init__.py +7 -0
- django_spire/metric/report/helper.py +92 -0
- django_spire/metric/report/report.py +79 -13
- django_spire/metric/report/templates/django_spire/metric/report/form/report_form.html +32 -10
- django_spire/metric/report/templates/django_spire/metric/report/print/report_print.html +29 -16
- django_spire/metric/report/tools.py +2 -0
- django_spire/metric/report/views/page_views.py +27 -5
- django_spire/notification/app/templates/django_spire/notification/app/dropdown/notification_dropdown.html +10 -10
- django_spire/notification/app/templates/django_spire/notification/app/dropdown/notification_dropdown_content.html +1 -33
- django_spire/notification/app/templates/django_spire/notification/app/item/notification_item.html +24 -23
- django_spire/notification/app/templates/django_spire/notification/app/page/list_page.html +1 -1
- django_spire/notification/app/templates/django_spire/notification/app/scroll/container/dropdown_container.html +54 -0
- django_spire/notification/app/templates/django_spire/notification/app/scroll/item/items.html +5 -0
- django_spire/notification/app/urls/template_urls.py +9 -2
- django_spire/notification/app/views/page_views.py +2 -10
- django_spire/notification/app/views/template_views.py +22 -7
- {django_spire-0.24.2.dist-info → django_spire-0.25.0.dist-info}/METADATA +2 -2
- {django_spire-0.24.2.dist-info → django_spire-0.25.0.dist-info}/RECORD +32 -23
- django_spire/notification/app/templates/django_spire/notification/app/card/list_card.html +0 -11
- {django_spire-0.24.2.dist-info → django_spire-0.25.0.dist-info}/WHEEL +0 -0
- {django_spire-0.24.2.dist-info → django_spire-0.25.0.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.24.2.dist-info → django_spire-0.25.0.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="col-11 col-md-10 col-lg-8 col-xl-6 mx-auto my-3">
|
|
5
5
|
|
|
6
6
|
<div class="row">
|
|
7
|
-
<div class="col
|
|
7
|
+
<div class="col h5 text-center text-app-primary">
|
|
8
8
|
{{ report.title }}
|
|
9
9
|
</div>
|
|
10
10
|
</div>
|
|
@@ -23,12 +23,19 @@
|
|
|
23
23
|
|
|
24
24
|
{% for argument, params in report_run_arguments.items %}
|
|
25
25
|
<div class="row">
|
|
26
|
-
<div class="col
|
|
27
|
-
|
|
26
|
+
<div class="col">
|
|
28
27
|
<label class="form-label">
|
|
29
|
-
{{ argument|underscores_to_spaces|title }}
|
|
28
|
+
{{ argument|underscores_to_spaces|title }} <span class="text-danger">*</span>
|
|
30
29
|
</label>
|
|
31
|
-
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="row">
|
|
33
|
+
<div class="col mb-2 ps-4">
|
|
34
|
+
{% if params.annotation == 'date' %}
|
|
35
|
+
<input class="form-control" type="date" name="{{ argument }}" value="{{ params.default|date:'Y-m-d' }}">
|
|
36
|
+
{% elif params.annotation == 'datetime' %}
|
|
37
|
+
<input class="form-control" type="datetime-local" name="{{ argument }}" value="{{ params.default|date:'Y-m-d\\TH:i:s' }}">
|
|
38
|
+
{% elif params.annotation == 'str' %}
|
|
32
39
|
<input class="form-control" type="text" name="{{ argument }}" value="{{ params.default }}">
|
|
33
40
|
{% elif params.annotation == 'int' %}
|
|
34
41
|
<input class="form-control" type="number" name="{{ argument }}"
|
|
@@ -37,22 +44,37 @@
|
|
|
37
44
|
<input class="form-control" type="number" name="{{ argument }}"
|
|
38
45
|
value="{{ params.default }}">
|
|
39
46
|
{% elif params.annotation == 'bool' %}
|
|
40
|
-
<
|
|
41
|
-
|
|
47
|
+
<div class="form-check form-switch">
|
|
48
|
+
<input class="form-check-input" type="checkbox" role="switch" {% if params.default %}checked{% endif %} id="flexSwitchCheckDefault">
|
|
49
|
+
</div>
|
|
50
|
+
{% elif params.annotation == 'multi_select' %}
|
|
51
|
+
<select class="form-select" multiple size="8" name="{{ argument }}" aria-selected="{{ params.default }}">
|
|
52
|
+
{% for key, val in params.choices %}
|
|
53
|
+
<option value="{{ key }}">{{ val }}</option>
|
|
54
|
+
{% endfor %}
|
|
55
|
+
</select>
|
|
42
56
|
{% elif params.annotation == 'select' %}
|
|
43
57
|
<select class="form-select" name="{{ argument }}" aria-selected="{{ params.default }}">
|
|
44
58
|
{% for key, val in params.choices %}
|
|
45
59
|
<option value="{{ key }}">{{ val }}</option>
|
|
46
60
|
{% endfor %}
|
|
47
61
|
</select>
|
|
62
|
+
{% else %}
|
|
63
|
+
<br>
|
|
64
|
+
<span class="text-danger">Unsupported parameter type: {{ params.annotation }}</span>
|
|
48
65
|
{% endif %}
|
|
49
66
|
</div>
|
|
50
67
|
</div>
|
|
51
68
|
{% endfor %}
|
|
52
69
|
|
|
53
|
-
<div
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
<div x-data="{
|
|
71
|
+
loading: false
|
|
72
|
+
}" class="row">
|
|
73
|
+
<div class="col mt-3 text-center" x-show="!loading">
|
|
74
|
+
<input class="btn btn-app-primary" @click="loading = true" type="submit" value="Run Report">
|
|
75
|
+
</div>
|
|
76
|
+
<div class="col mt-3 text-center text-app-primary" x-show="loading">
|
|
77
|
+
<span class="spinner-border" role="status" aria-hidden="true"></span>
|
|
56
78
|
</div>
|
|
57
79
|
</div>
|
|
58
80
|
<div class="row">
|
|
@@ -2,41 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="row mb-1 font-monospace">
|
|
4
4
|
<div class="col">
|
|
5
|
-
<div class="fw-bold">
|
|
5
|
+
<div class="h5 fw-bold">
|
|
6
6
|
{{ report.title }}
|
|
7
7
|
</div>
|
|
8
8
|
</div>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
<div class="row mx-0 mb-3 text-muted fw-normal fs
|
|
11
|
+
<div class="row mx-0 mb-3 text-muted fw-normal fs-print-2 border font-monospace">
|
|
12
12
|
{% if report.description %}
|
|
13
13
|
<div class="col-auto ps-1 pe-3">
|
|
14
|
-
Description: {{ report.description }}
|
|
14
|
+
Description: <span class="fw-bold">{{ report.description }}</span>
|
|
15
15
|
</div>
|
|
16
16
|
{% endif %}
|
|
17
|
-
|
|
18
|
-
Printed On: {% now "D, M j, Y" %} at {% now "g:i A" %}
|
|
17
|
+
<div class="col-auto ps-1 pe-3">
|
|
18
|
+
Printed On: <span class="fw-bold">{% now "D, M j, Y" %} at {% now "g:i A" %}</span>
|
|
19
19
|
</div>
|
|
20
|
-
|
|
21
|
-
Financially Accurate: {% if report.is_financially_accurate %}True{% else %}False{% endif %}
|
|
20
|
+
<div class="col-auto ps-1 pe-3">
|
|
21
|
+
Financially Accurate: <span class="fw-bold">{% if report.is_financially_accurate %}True{% else %}False{% endif %}</span>
|
|
22
22
|
</div>
|
|
23
23
|
{% for argument, value in report_run_arguments_values.items %}
|
|
24
24
|
<div class="col-auto ps-1 pe-3">
|
|
25
|
-
{{ argument|underscores_to_spaces|title }}: {{ value }}
|
|
25
|
+
{{ argument|underscores_to_spaces|title }}: <span class="fw-bold">{{ value }}</span>
|
|
26
26
|
</div>
|
|
27
27
|
{% endfor %}
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
30
|
<div class="row">
|
|
31
|
-
<div class="col fs
|
|
31
|
+
<div class="col fs-print-2">
|
|
32
32
|
|
|
33
|
-
<table class="table table-sm table-hover table-print">
|
|
33
|
+
<table class="table table-sm table-hover table-print align-middle">
|
|
34
34
|
<thead>
|
|
35
35
|
{% for column in report.columns %}
|
|
36
|
-
<th class="align-text-top {{ column.css_class }}">
|
|
36
|
+
<th class="align-text-top {{ column.css_class }}" style="border-bottom-width: 4px;">
|
|
37
37
|
{{ column.title }}
|
|
38
38
|
{% if column.sub_title %}
|
|
39
|
-
<span class="text-muted fw-normal fs
|
|
39
|
+
<span class="text-muted fw-normal fs-print-3">
|
|
40
40
|
<br>{{ column.sub_title }}
|
|
41
41
|
</span>
|
|
42
42
|
{% endif %}
|
|
@@ -45,12 +45,24 @@
|
|
|
45
45
|
</thead>
|
|
46
46
|
<tbody>
|
|
47
47
|
{% for row in report.rows %}
|
|
48
|
+
{% if row.page_break %}
|
|
49
|
+
<tr style="page-break-before: always;"></tr>
|
|
50
|
+
{% endif %}
|
|
48
51
|
<tr class="{% if row.bold %}fw-bold{% endif %}"
|
|
49
|
-
{% if row.
|
|
52
|
+
style="{% if row.border_top %}border-top-width: 4px;{% endif %}{% if row.border_bottom %}border-bottom-width: 4px;{% endif %}">
|
|
50
53
|
{% if row.span_all_columns %}
|
|
51
54
|
<td colspan="{{ report.column_count }}">
|
|
52
55
|
{% for cell in row.cells %}
|
|
53
|
-
{
|
|
56
|
+
{% if cell.value %}
|
|
57
|
+
{{ cell.value }}
|
|
58
|
+
{% else %}
|
|
59
|
+
|
|
60
|
+
{% endif %}
|
|
61
|
+
{% if cell.sub_value %}
|
|
62
|
+
<span class="text-muted fw-normal fs-print-3">
|
|
63
|
+
<br>{{ cell.sub_value_verbose }}
|
|
64
|
+
</span>
|
|
65
|
+
{% endif %}
|
|
54
66
|
{% endfor %}
|
|
55
67
|
</td>
|
|
56
68
|
{% else %}
|
|
@@ -58,8 +70,9 @@
|
|
|
58
70
|
<td class="{{ cell.css_class }}">
|
|
59
71
|
{{ cell.value_verbose }}
|
|
60
72
|
{% if cell.sub_value %}
|
|
61
|
-
<
|
|
62
|
-
|
|
73
|
+
<span class="text-muted fw-normal fs-print-3">
|
|
74
|
+
<br>{{ cell.sub_value_verbose }}
|
|
75
|
+
</span>
|
|
63
76
|
{% endif %}
|
|
64
77
|
</td>
|
|
65
78
|
{% endfor %}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from datetime import datetime
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from django.conf import settings
|
|
@@ -37,9 +38,9 @@ def report_view(request: WSGIRequest) -> TemplateResponse:
|
|
|
37
38
|
report_registry_class()
|
|
38
39
|
)
|
|
39
40
|
|
|
40
|
-
context_data =
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
context_data = dict()
|
|
42
|
+
|
|
43
|
+
context_data['registry'] = page_report_registry
|
|
43
44
|
|
|
44
45
|
if request.GET:
|
|
45
46
|
report_key_stack = request.GET.get('report_key_stack', None)
|
|
@@ -57,9 +58,30 @@ def report_view(request: WSGIRequest) -> TemplateResponse:
|
|
|
57
58
|
|
|
58
59
|
context_data['report_run_arguments_values'] = {}
|
|
59
60
|
|
|
60
|
-
for argument in
|
|
61
|
+
for argument in context_data['report_run_arguments']:
|
|
61
62
|
if context_data['report_run_arguments'][argument]['annotation'] == 'bool':
|
|
62
63
|
get_request_value = request.GET.get(argument, False)
|
|
64
|
+
|
|
65
|
+
elif context_data['report_run_arguments'][argument]['annotation'] == 'date':
|
|
66
|
+
date_str = request.GET.get(argument, None)
|
|
67
|
+
|
|
68
|
+
if date_str:
|
|
69
|
+
get_request_value = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
70
|
+
else:
|
|
71
|
+
get_request_value = date_str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
elif context_data['report_run_arguments'][argument]['annotation'] == 'datetime':
|
|
75
|
+
datetime_str = request.GET.get(argument, None)
|
|
76
|
+
|
|
77
|
+
if datetime_str:
|
|
78
|
+
get_request_value = datetime.strptime(datetime_str, '%Y-%m-%dT%H:%M:%S')
|
|
79
|
+
else:
|
|
80
|
+
get_request_value = datetime_str
|
|
81
|
+
|
|
82
|
+
elif context_data['report_run_arguments'][argument]['annotation'] == 'multi_select':
|
|
83
|
+
get_request_value = request.GET.getlist(argument, [])
|
|
84
|
+
|
|
63
85
|
else:
|
|
64
86
|
get_request_value = request.GET.get(argument, None)
|
|
65
87
|
|
|
@@ -73,7 +95,7 @@ def report_view(request: WSGIRequest) -> TemplateResponse:
|
|
|
73
95
|
ReportRun.objects.create(
|
|
74
96
|
report_key_stack=report_key_stack,
|
|
75
97
|
)
|
|
76
|
-
report.run()
|
|
98
|
+
report.run(**context_data['report_run_arguments_values'])
|
|
77
99
|
|
|
78
100
|
context_data['report'] = report
|
|
79
101
|
context_data['report_run_count'] = ReportRun.objects.run_count(report_key_stack)
|
|
@@ -4,28 +4,28 @@
|
|
|
4
4
|
<div x-data="{
|
|
5
5
|
new_notification: false,
|
|
6
6
|
async init(){
|
|
7
|
-
await this.check_new_notifications()
|
|
7
|
+
await this.check_new_notifications();
|
|
8
8
|
setInterval(await this.check_new_notifications, 30000);
|
|
9
9
|
},
|
|
10
10
|
|
|
11
11
|
async check_new_notifications(){
|
|
12
|
-
let url = '{% url "django_spire:notification:app:json:check_new" %}'
|
|
13
|
-
let response = await django_glue_fetch(url)
|
|
14
|
-
this.new_notification = response.has_new_notifications
|
|
12
|
+
let url = '{% url "django_spire:notification:app:json:check_new" %}';
|
|
13
|
+
let response = await django_glue_fetch(url);
|
|
14
|
+
this.new_notification = response.has_new_notifications;
|
|
15
15
|
},
|
|
16
16
|
|
|
17
17
|
async render_dropdown(){
|
|
18
18
|
let dropdown_content = new ViewGlue(
|
|
19
19
|
url='{% url "django_spire:notification:app:template:notification_dropdown" %}',
|
|
20
20
|
shared_payload={app_notification_list_url: '{{ app_notification_list_url|default:"" }}'}
|
|
21
|
-
)
|
|
22
|
-
await dropdown_content.render_inner($refs.spire_notification_dropdown_content)
|
|
23
|
-
await this.mark_notifications_as_viewed()
|
|
21
|
+
);
|
|
22
|
+
await dropdown_content.render_inner($refs.spire_notification_dropdown_content);
|
|
23
|
+
await this.mark_notifications_as_viewed();
|
|
24
24
|
},
|
|
25
25
|
|
|
26
26
|
async mark_notifications_as_viewed(){
|
|
27
|
-
let response = await django_glue_fetch('{% url "django_spire:notification:app:json:set_viewed" %}')
|
|
28
|
-
this.new_notification = false
|
|
27
|
+
let response = await django_glue_fetch('{% url "django_spire:notification:app:json:set_viewed" %}');
|
|
28
|
+
this.new_notification = false;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
}"
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
{% block dropdown_position %}top-100 end-50{% endblock %}
|
|
45
45
|
|
|
46
46
|
{% block dropdown_content %}
|
|
47
|
-
<div x-ref="spire_notification_dropdown_content"></div>
|
|
47
|
+
<div :style="{ width: window.innerWidth < 768 ? (window.innerWidth / 2).toFixed(0) + 'px' : '350px' }" x-ref="spire_notification_dropdown_content"></div>
|
|
48
48
|
{% endblock %}
|
|
@@ -1,33 +1 @@
|
|
|
1
|
-
|
|
2
|
-
:style="window.innerWidth < 768
|
|
3
|
-
? 'width: 350px; position: fixed; top: 5%; left: 50%; transform: translateX(-50%);'
|
|
4
|
-
: 'width: 350px'"
|
|
5
|
-
class="container border rounded-2 bg-app-layer-two"
|
|
6
|
-
>
|
|
7
|
-
<div class="col-12 w-100">
|
|
8
|
-
<h6 class="mt-2 ms-2 text-center">Notifications</h6>
|
|
9
|
-
<hr class="m-0">
|
|
10
|
-
</div>
|
|
11
|
-
|
|
12
|
-
<div
|
|
13
|
-
style="max-height: 300px !important;"
|
|
14
|
-
class="text-start overflow-y-scroll overflow-x-hidden"
|
|
15
|
-
>
|
|
16
|
-
{% for app_notification in app_notification_list %}
|
|
17
|
-
<div class="col-12">
|
|
18
|
-
{% include app_notification.template %}
|
|
19
|
-
</div>
|
|
20
|
-
{% empty %}
|
|
21
|
-
No Notifications
|
|
22
|
-
{% endfor %}
|
|
23
|
-
</div>
|
|
24
|
-
<div class="col-12">
|
|
25
|
-
<hr class="m-0">
|
|
26
|
-
</div>
|
|
27
|
-
<div class="col-12 fs-7 text-center py-1">
|
|
28
|
-
{% if not app_notification_list_url %}
|
|
29
|
-
{% url "django_spire:notification:app:page:list" as app_notification_list_url %}
|
|
30
|
-
{% endif %}
|
|
31
|
-
<a href='{{ app_notification_list_url }}' class="text-decoration-none text-app-secondary my-2">View All</a>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
1
|
+
{% include 'django_spire/notification/app/scroll/container/dropdown_container.html' with endpoint=notification_endpoint container_height='300px' batch_size=10 %}
|
django_spire/notification/app/templates/django_spire/notification/app/item/notification_item.html
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
{% extends 'django_spire/item/infinite_scroll_item.html' %}
|
|
2
|
+
|
|
3
|
+
{% block item_content %}
|
|
4
|
+
{% if app_notification.notification.url %}
|
|
5
|
+
<a href="{{ app_notification.notification.url }}"
|
|
6
|
+
{% else %}
|
|
7
|
+
<div
|
|
8
|
+
{% endif %}
|
|
9
|
+
class="row bg-app-layer-two-hover rounded m-0"
|
|
10
|
+
>
|
|
11
|
+
<div class="col-12">
|
|
12
|
+
<span class="fs-7 fw-semibold">{{ app_notification.notification.title }}</span>
|
|
13
|
+
{% if not app_notification.viewed %}
|
|
14
|
+
{% include 'django_spire/badge/primary_badge.html' with badge_text='New' %}
|
|
15
|
+
{% endif %}
|
|
16
|
+
</div>
|
|
17
|
+
<div class="col-12">
|
|
18
|
+
<span class="fs--1">{{ app_notification.notification.body }}</span><br>
|
|
19
|
+
<span class="text-app-secondary text-muted fs--2">{{ app_notification.verbose_time_since_delivered }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
{% if app_notification.notification.url %}
|
|
22
|
+
</a>
|
|
23
|
+
{% else %}
|
|
21
24
|
</div>
|
|
22
|
-
{% if not forloop.last %}
|
|
23
|
-
<hr class="m-0 mt-1">
|
|
24
25
|
{% endif %}
|
|
25
|
-
|
|
26
|
+
{% endblock %}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{% extends 'django_spire/page/full_page.html' %}
|
|
2
2
|
|
|
3
3
|
{% block full_page_content %}
|
|
4
|
-
{% include "django_spire/
|
|
4
|
+
{% include "django_spire/card/infinite_scroll_card.html" with card_title='Notifications' endpoint=notification_endpoint scroll_height='55vh' %}
|
|
5
5
|
{% endblock %}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{% extends 'django_spire/infinite_scroll/base.html' %}
|
|
2
|
+
|
|
3
|
+
{% block scroll_wrapper_class %}bg-app-layer-two rounded-2{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block scroll_container %}
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col-12">
|
|
8
|
+
<div
|
|
9
|
+
:style="{
|
|
10
|
+
maxHeight: '{{ container_height|default:'300px' }}',
|
|
11
|
+
overflowX: 'hidden',
|
|
12
|
+
overflowY: 'auto',
|
|
13
|
+
overscrollBehavior: 'contain',
|
|
14
|
+
WebkitOverflowScrolling: 'touch',
|
|
15
|
+
maxWidth: window.innerWidth < 768 ? (window.innerWidth / 2).toFixed(0) + 'px' : '350px',
|
|
16
|
+
}"
|
|
17
|
+
x-ref="scroll_container"
|
|
18
|
+
:data-scroll-id="scroll_id"
|
|
19
|
+
>
|
|
20
|
+
<div x-ref="content_container">
|
|
21
|
+
{% block scroll_content %}{% endblock %}
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div style="height: 10px;" x-ref="infinite_scroll_trigger"></div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<template x-if="show_loading && !is_refreshing">
|
|
28
|
+
<div
|
|
29
|
+
class="position-absolute d-flex justify-content-center align-items-center"
|
|
30
|
+
style="top: 0; left: 0; right: 0; bottom: 0; background: color-mix(in srgb, var(--app-layer-one) 85%, transparent);"
|
|
31
|
+
>
|
|
32
|
+
<div class="spinner-border text-app-primary" role="status"></div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
{% endblock %}
|
|
38
|
+
|
|
39
|
+
{% block scroll_footer %}
|
|
40
|
+
<div class="row text-center border-top border-dark-subtle m-0">
|
|
41
|
+
<div class="col-12">
|
|
42
|
+
<span class="fs-7 text-app-secondary">
|
|
43
|
+
Showing <span x-text="loaded_count"></span> of <span x-text="total_count"></span> {{ footer_label|default:'items' }}
|
|
44
|
+
</span>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="col-12 fs-7 text-center py-1">
|
|
48
|
+
{% if not app_notification_list_url %}
|
|
49
|
+
{% url "django_spire:notification:app:page:list" as app_notification_list_url %}
|
|
50
|
+
{% endif %}
|
|
51
|
+
<a href='{{ app_notification_list_url }}' class="text-decoration-none text-app-secondary my-2">View All</a>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
{% endblock %}
|
|
@@ -8,7 +8,14 @@ from django_spire.notification.app.views import template_views
|
|
|
8
8
|
app_name = 'django_spire_notification'
|
|
9
9
|
|
|
10
10
|
urlpatterns = [
|
|
11
|
-
path(
|
|
11
|
+
path(
|
|
12
|
+
'notficiation/scroll/items',
|
|
13
|
+
template_views.notification_infinite_scroll_view,
|
|
14
|
+
name='scroll_items'
|
|
15
|
+
),
|
|
16
|
+
path(
|
|
17
|
+
'notficiation/dropdown/template/',
|
|
12
18
|
template_views.notification_dropdown_template_view,
|
|
13
|
-
name='notification_dropdown'
|
|
19
|
+
name='notification_dropdown'
|
|
20
|
+
)
|
|
14
21
|
]
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from django.contrib.auth.decorators import login_required
|
|
6
|
+
from django.urls import reverse
|
|
6
7
|
|
|
7
8
|
from django_spire.contrib.generic_views import portal_views
|
|
8
9
|
|
|
@@ -15,18 +16,9 @@ if TYPE_CHECKING:
|
|
|
15
16
|
|
|
16
17
|
@login_required()
|
|
17
18
|
def app_notification_list_view(request: WSGIRequest) -> TemplateResponse:
|
|
18
|
-
app_notification_list = (
|
|
19
|
-
AppNotification.objects.active()
|
|
20
|
-
.is_sent()
|
|
21
|
-
.annotate_is_viewed_by_user(request.user)
|
|
22
|
-
.select_related('notification')
|
|
23
|
-
.distinct()
|
|
24
|
-
.ordered_by_priority_and_sent_datetime()
|
|
25
|
-
)
|
|
26
|
-
|
|
27
19
|
return portal_views.list_view(
|
|
28
20
|
request,
|
|
29
|
-
context_data={'
|
|
21
|
+
context_data={'notification_endpoint': reverse('django_spire:notification:app:template:scroll_items')},
|
|
30
22
|
model=AppNotification,
|
|
31
23
|
template='django_spire/notification/app/page/list_page.html'
|
|
32
24
|
)
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
|
|
5
4
|
from typing import TYPE_CHECKING
|
|
6
5
|
|
|
7
6
|
from django.contrib.auth.models import AnonymousUser
|
|
8
|
-
from django.
|
|
7
|
+
from django.urls import reverse
|
|
9
8
|
|
|
9
|
+
from django_spire.contrib.generic_views.portal_views import infinite_scrolling_view
|
|
10
|
+
from django.template.response import TemplateResponse
|
|
10
11
|
from django_spire.notification.app.models import AppNotification
|
|
11
12
|
|
|
13
|
+
|
|
12
14
|
if TYPE_CHECKING:
|
|
13
15
|
from django.core.handlers.wsgi import WSGIRequest
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
def notification_dropdown_template_view(request: WSGIRequest) -> TemplateResponse:
|
|
17
|
+
def notification_infinite_scroll_view(request: WSGIRequest) -> TemplateResponse:
|
|
17
18
|
if isinstance(request.user, AnonymousUser):
|
|
18
|
-
|
|
19
|
+
notifications = []
|
|
19
20
|
|
|
20
21
|
else:
|
|
21
|
-
|
|
22
|
+
notifications = (
|
|
22
23
|
AppNotification.objects.active()
|
|
23
24
|
.is_sent()
|
|
24
25
|
.annotate_is_viewed_by_user(request.user)
|
|
@@ -29,11 +30,25 @@ def notification_dropdown_template_view(request: WSGIRequest) -> TemplateRespons
|
|
|
29
30
|
|
|
30
31
|
body_data = json.loads(request.body.decode('utf-8'))
|
|
31
32
|
|
|
33
|
+
return infinite_scrolling_view(
|
|
34
|
+
request,
|
|
35
|
+
queryset=notifications,
|
|
36
|
+
queryset_name='notifications',
|
|
37
|
+
context_data={
|
|
38
|
+
'app_notification_list_url': body_data.get('app_notification_list_url'),
|
|
39
|
+
},
|
|
40
|
+
template='django_spire/notification/app/scroll/item/items.html',
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def notification_dropdown_template_view(request: WSGIRequest) -> TemplateResponse:
|
|
45
|
+
body_data = json.loads(request.body.decode('utf-8'))
|
|
46
|
+
|
|
32
47
|
return TemplateResponse(
|
|
33
48
|
request,
|
|
34
49
|
context={
|
|
35
|
-
'app_notification_list': app_notification_list,
|
|
36
50
|
'app_notification_list_url': body_data.get('app_notification_list_url'),
|
|
51
|
+
'notification_endpoint': reverse('django_spire:notification:app:template:scroll_items')
|
|
37
52
|
},
|
|
38
53
|
template='django_spire/notification/app/dropdown/notification_dropdown_content.html'
|
|
39
54
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-spire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.0
|
|
4
4
|
Summary: A project for Django Spire
|
|
5
5
|
Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
|
|
6
6
|
License: Copyright (c) 2025 Stratus Advanced Technologies and Contributors.
|
|
@@ -52,7 +52,7 @@ Requires-Dist: crispy-bootstrap5==2024.10
|
|
|
52
52
|
Requires-Dist: dandy>=1.3.5
|
|
53
53
|
Requires-Dist: django>=5.1.8
|
|
54
54
|
Requires-Dist: django-crispy-forms==2.3
|
|
55
|
-
Requires-Dist: django-glue>=0.8.
|
|
55
|
+
Requires-Dist: django-glue>=0.8.12
|
|
56
56
|
Requires-Dist: django-sendgrid-v5
|
|
57
57
|
Requires-Dist: django-storages==1.14.5
|
|
58
58
|
Requires-Dist: docutils
|