django-spire 0.17.3__py3-none-any.whl → 0.17.5__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 CHANGED
@@ -1,4 +1,4 @@
1
- __VERSION__ = '0.17.3'
1
+ __VERSION__ = '0.17.5'
2
2
 
3
3
 
4
4
  AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME = 'AI_CHAT_WORKFLOW_NAME'
@@ -0,0 +1,10 @@
1
+ from django_spire.contrib.progress.mixins import ProgressTrackingMixin
2
+ from django_spire.contrib.progress.tracker import ProgressTracker
3
+ from django_spire.contrib.progress.views import sse_stream_view
4
+
5
+
6
+ __all__ = [
7
+ 'ProgressTracker',
8
+ 'ProgressTrackingMixin',
9
+ 'sse_stream_view',
10
+ ]
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django_spire.contrib.progress.tracker import ProgressTracker
6
+
7
+ if TYPE_CHECKING:
8
+ from typing import Any
9
+
10
+
11
+ class ProgressTrackingMixin:
12
+ _tracker: ProgressTracker | None = None
13
+
14
+ def get_tracker_key(self) -> str:
15
+ raise NotImplementedError
16
+
17
+ @property
18
+ def tracker(self) -> ProgressTracker:
19
+ if self._tracker is None:
20
+ key = self.get_tracker_key()
21
+ self._tracker = ProgressTracker(key)
22
+
23
+ return self._tracker
24
+
25
+ def update_progress(
26
+ self,
27
+ step: str,
28
+ message: str,
29
+ progress: int,
30
+ **kwargs: Any
31
+ ) -> None:
32
+ self.tracker.update(step, message, progress, **kwargs)
33
+
34
+ def progress_error(self, message: str) -> None:
35
+ self.tracker.error(message)
@@ -0,0 +1,62 @@
1
+ class ProgressStream {
2
+ constructor(url, config = {}) {
3
+ this.url = url;
4
+ this.event_source = null;
5
+ this.config = {
6
+ on_update: config.on_update || (() => {}),
7
+ on_complete: config.on_complete || (() => {}),
8
+ on_error: config.on_error || (() => {}),
9
+ redirect_on_complete: config.redirect_on_complete || null,
10
+ redirect_delay: config.redirect_delay || 1000
11
+ };
12
+ }
13
+
14
+ start() {
15
+ this.event_source = new EventSource(this.url);
16
+
17
+ this.event_source.onmessage = (event) => {
18
+ const data = JSON.parse(event.data);
19
+
20
+ this.config.on_update(data);
21
+
22
+ if (data.step === 'error') {
23
+ this.config.on_error(data);
24
+ this.stop();
25
+
26
+ return;
27
+ }
28
+
29
+ if (data.progress >= 100) {
30
+ this.config.on_complete(data);
31
+ this.stop();
32
+
33
+ if (this.config.redirect_on_complete) {
34
+ setTimeout(() => {
35
+ window.location.href = this.config.redirect_on_complete;
36
+ }, this.config.redirect_delay);
37
+ }
38
+ }
39
+ };
40
+
41
+ this.event_source.onerror = (error) => {
42
+ console.error('ProgressStream error:', error);
43
+
44
+ this.config.on_error({
45
+ step: 'error',
46
+ message: 'Connection error',
47
+ progress: 0
48
+ });
49
+
50
+ this.stop();
51
+ };
52
+ }
53
+
54
+ stop() {
55
+ if (this.event_source) {
56
+ this.event_source.close();
57
+ this.event_source = null;
58
+ }
59
+ }
60
+ }
61
+
62
+ window.ProgressStream = ProgressStream;
@@ -0,0 +1,81 @@
1
+ {% extends 'django_spire/card/title_card.html' %}
2
+
3
+ {% block card_title %}
4
+ <i class="bi bi-magic"></i>
5
+ {% block progress_card_title %}Processing{% endblock %}
6
+ {% endblock %}
7
+
8
+ {% block card_title_content %}
9
+ <div
10
+ data-stream-url="{% block progress_stream_url %}{% endblock %}"
11
+ data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
12
+ data-redirect-delay="{% block progress_redirect_delay %}1000{% endblock %}"
13
+ x-data="{
14
+ step: 'starting',
15
+ message: 'Initializing...',
16
+ progress: 0,
17
+ has_error: false,
18
+ stream: null,
19
+ init() {
20
+ const stream_url = this.$el.dataset.streamUrl;
21
+ const redirect_url = this.$el.dataset.redirectUrl;
22
+ const redirect_delay = parseInt(this.$el.dataset.redirectDelay);
23
+
24
+ this.stream = new ProgressStream(stream_url, {
25
+ on_update: (data) => {
26
+ this.step = data.step;
27
+ this.message = data.message;
28
+ this.progress = data.progress;
29
+ },
30
+ on_complete: (data) => {},
31
+ on_error: (data) => {
32
+ this.has_error = true;
33
+ this.message = data.message;
34
+ },
35
+ redirect_on_complete: redirect_url,
36
+ redirect_delay: redirect_delay
37
+ });
38
+ this.stream.start();
39
+ }
40
+ }"
41
+ >
42
+ <div class="row g-3">
43
+ <div class="col-12" x-show="!has_error">
44
+ <div class="d-flex justify-content-between align-items-center mb-2">
45
+ <span class="fs-6" x-text="message"></span>
46
+ {% include 'django_spire/badge/accent_badge.html' with x_badge_text="progress + '%'" %}
47
+ </div>
48
+ <div class="progress" style="height: 8px;">
49
+ <div
50
+ class="progress-bar bg-app-accent"
51
+ role="progressbar"
52
+ :style="'width: ' + progress + '%'"
53
+ :aria-valuenow="progress"
54
+ aria-valuemin="0"
55
+ aria-valuemax="100"
56
+ ></div>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="col-12" x-show="!has_error">
61
+ <p class="text-app-secondary mb-0 fs-7">
62
+ {% block progress_wait_message %}Processing your request. This may take a moment.{% endblock %}
63
+ </p>
64
+ </div>
65
+
66
+ <div class="col-12" x-show="has_error" x-cloak>
67
+ <div class="row g-2">
68
+ <div class="col-12">
69
+ {% include 'django_spire/element/attribute_element.html' with attribute_title='Error' %}
70
+ <span class="fs-6 text-app-danger" x-text="message"></span>
71
+ </div>
72
+ <div class="col-12">
73
+ {% block progress_error_button %}
74
+ {% include 'django_spire/button/primary_button.html' with button_text='Go Back' %}
75
+ {% endblock %}
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ {% endblock %}
@@ -0,0 +1,90 @@
1
+ {% extends 'django_spire/modal/content/modal_title_content.html' %}
2
+
3
+ {% block modal_content_title %}
4
+ <i class="bi bi-magic"></i>
5
+ {% block progress_modal_title %}Processing{% endblock %}
6
+ {% endblock %}
7
+
8
+ {% block modal_content_close_button %}
9
+ {% endblock %}
10
+
11
+ {% block modal_content_content %}
12
+ <div
13
+ data-stream-url="{% block progress_stream_url %}{% endblock %}"
14
+ data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
15
+ data-redirect-delay="{% block progress_redirect_delay %}1000{% endblock %}"
16
+ x-data="{
17
+ step: 'starting',
18
+ message: 'Initializing...',
19
+ progress: 0,
20
+ has_error: false,
21
+ stream: null,
22
+ init() {
23
+ const stream_url = this.$el.dataset.streamUrl;
24
+ const redirect_url = this.$el.dataset.redirectUrl;
25
+ const redirect_delay = parseInt(this.$el.dataset.redirectDelay);
26
+
27
+ this.stream = new ProgressStream(stream_url, {
28
+ on_update: (data) => {
29
+ this.step = data.step;
30
+ this.message = data.message;
31
+ this.progress = data.progress;
32
+ },
33
+ on_complete: (data) => {
34
+ setTimeout(() => {
35
+ close_modal();
36
+ }, 500);
37
+ },
38
+ on_error: (data) => {
39
+ this.has_error = true;
40
+ this.message = data.message;
41
+ },
42
+ redirect_on_complete: redirect_url,
43
+ redirect_delay: redirect_delay
44
+ });
45
+ this.stream.start();
46
+ }
47
+ }"
48
+ >
49
+ <div class="row g-3">
50
+ <div class="col-12" x-show="!has_error">
51
+ <div class="d-flex justify-content-between align-items-center mb-2">
52
+ <span class="fs-6" x-text="message"></span>
53
+ {% include 'django_spire/badge/accent_badge.html' with x_badge_text="progress + '%'" %}
54
+ </div>
55
+ <div class="progress" style="height: 8px;">
56
+ <div
57
+ class="progress-bar bg-app-accent"
58
+ role="progressbar"
59
+ :style="'width: ' + progress + '%'"
60
+ :aria-valuenow="progress"
61
+ aria-valuemin="0"
62
+ aria-valuemax="100"
63
+ ></div>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="col-12" x-show="!has_error">
68
+ <p class="text-app-secondary mb-0 fs-7">
69
+ {% block progress_wait_message %}We are processing your request. This may take a moment.{% endblock %}
70
+ </p>
71
+ </div>
72
+
73
+ <div class="col-12" x-show="has_error" x-cloak>
74
+ <div class="row g-2">
75
+ <div class="col-12">
76
+ {% include 'django_spire/element/attribute_element.html' with attribute_title='Error' %}
77
+ <span class="fs-6 text-app-danger" x-text="message"></span>
78
+ </div>
79
+ <div class="col-12">
80
+ <span @click="close_modal()">
81
+ {% block progress_error_button %}
82
+ {% include 'django_spire/button/primary_button.html' with button_text='Close' %}
83
+ {% endblock %}
84
+ </span>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ {% endblock %}
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django.core.cache import cache
6
+
7
+ if TYPE_CHECKING:
8
+ from typing import Any
9
+
10
+
11
+ class ProgressTracker:
12
+ def __init__(self, key: str, timeout: int = 300) -> None:
13
+ self.key = f'progress_tracker_{key}'
14
+ self.timeout = timeout
15
+
16
+ def update(
17
+ self,
18
+ step: str,
19
+ message: str,
20
+ progress: int,
21
+ **kwargs: Any
22
+ ) -> None:
23
+ data = {
24
+ 'step': step,
25
+ 'message': message,
26
+ 'progress': progress,
27
+ **kwargs
28
+ }
29
+
30
+ cache.set(self.key, data, timeout=self.timeout)
31
+
32
+ def get(self) -> dict[str, Any] | None:
33
+ return cache.get(self.key)
34
+
35
+ def clear(self) -> None:
36
+ cache.delete(self.key)
37
+
38
+ def error(self, message: str) -> None:
39
+ self.update('error', message, 0)
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ from django.http import StreamingHttpResponse
9
+
10
+ from django_spire.contrib.progress.tracker import ProgressTracker
11
+
12
+ if TYPE_CHECKING:
13
+ from typing import Callable
14
+
15
+
16
+ def sse_stream_view(
17
+ key: str,
18
+ interval: float = 0.5,
19
+ should_continue: Callable[[dict], bool] | None = None
20
+ ) -> StreamingHttpResponse:
21
+ def event_stream() -> None:
22
+ tracker = ProgressTracker(key)
23
+ previous_progress = -1
24
+
25
+ while True:
26
+ time.sleep(interval)
27
+ data = tracker.get()
28
+
29
+ if data:
30
+ current_progress = data.get('progress', 0)
31
+
32
+ if current_progress != previous_progress:
33
+ yield f'data: {json.dumps(data)}\n\n'
34
+ previous_progress = current_progress
35
+
36
+ if should_continue and not should_continue(data):
37
+ break
38
+
39
+ if current_progress >= 100 or data.get('step') == 'error':
40
+ break
41
+
42
+ response = StreamingHttpResponse(
43
+ event_stream(),
44
+ content_type='text/event-stream'
45
+ )
46
+
47
+ response['Cache-Control'] = 'no-cache'
48
+ response['X-Accel-Buffering'] = 'no'
49
+
50
+ return response
@@ -4,5 +4,11 @@
4
4
  {{ badge_class|default:'fs--1' }}
5
5
  "
6
6
  >
7
- {{ badge_text }}
7
+ {% if badge_text %}
8
+ {{ badge_text }}
9
+ {% endif %}
10
+
11
+ {% if x_badge_text %}
12
+ <span x-text="{{ x_badge_text }}"></span>
13
+ {% endif %}
8
14
  </span>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.17.3
3
+ Version: 0.17.5
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) 2024 Stratus Advanced Technologies and Contributors.
@@ -48,7 +48,7 @@ License-File: LICENSE.md
48
48
  Requires-Dist: boto3>=1.34.0
49
49
  Requires-Dist: botocore>=1.34.0
50
50
  Requires-Dist: crispy-bootstrap5==2024.10
51
- Requires-Dist: dandy==1.1.2
51
+ Requires-Dist: dandy==1.1.3
52
52
  Requires-Dist: django>=5.1.8
53
53
  Requires-Dist: django-crispy-forms==2.3
54
54
  Requires-Dist: django-glue>=0.8.1
@@ -1,6 +1,6 @@
1
1
  django_spire/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  django_spire/conf.py,sha256=EYC1hXqYqheYrb_b6Q93LrSgNl91JV633E4N5j-KGTo,1012
3
- django_spire/consts.py,sha256=km3fdksyNcELR-UlQrrS0G1_KVwTiVaiqauBjKMlpHg,390
3
+ django_spire/consts.py,sha256=1kja89XlrvvNvZp5jPCCpGIpORx633GOJvBWrbHutbo,390
4
4
  django_spire/exceptions.py,sha256=L5ndRO5ftMmh0pHkO2z_NG3LSGZviJ-dDHNT73SzTNw,48
5
5
  django_spire/settings.py,sha256=tGxgEri3TQRBaiwX7YRcWifMf_g1xv1RznplFR6zZnI,821
6
6
  django_spire/urls.py,sha256=mKeZszb5U4iIGqddMb5Tt5fRC72U2wABEOi6mvOfEBU,656
@@ -329,6 +329,14 @@ django_spire/contrib/pagination/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TI
329
329
  django_spire/contrib/pagination/templatetags/pagination_tags.py,sha256=xE3zTDJZDdDvWf6D7fMyg_Vi0PW37OA3aZxCjOHDp4I,1173
330
330
  django_spire/contrib/performance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
331
331
  django_spire/contrib/performance/decorators.py,sha256=xOjXX-3FVOEWqr2TTTqdh4lkIhmHTlROevOVgXDeyt8,507
332
+ django_spire/contrib/progress/__init__.py,sha256=zfB13CHEfaGLA8ILNTmePG2efwBygpitqxA9CDplc0U,292
333
+ django_spire/contrib/progress/mixins.py,sha256=970sYbaZq2WAey-kAVED5hskQFXNh4WeCBIgNJLfdFU,834
334
+ django_spire/contrib/progress/tracker.py,sha256=VtzcD0pxCsq736RHPXJfspfC7qNdjb74Cl6VKI7U3Iw,870
335
+ django_spire/contrib/progress/views.py,sha256=bBEbTktWMlizzrdchPn6G4EEnDqc64bNH7Vt005XF2E,1268
336
+ django_spire/contrib/progress/static/django_spire/css/contrib/progress/progress.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
337
+ django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js,sha256=2Zuwiu1nWJpghzU-6fmf6s4qKLLyiZUcAJwB9-v5xWI,1701
338
+ django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html,sha256=2Ajv0UBJEGwc4InVe69aLe2u4K56yh2JAuvO6uH24j8,3353
339
+ django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html,sha256=oZ7mOxzro5809wSHLe4A90PonAqcr_eVawR_xHKXIt8,3682
332
340
  django_spire/contrib/queryset/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
333
341
  django_spire/contrib/queryset/enums.py,sha256=FhrwaEZ8Ql3AnImDleJFonp11-ObCZT24bC5fCCVBxE,132
334
342
  django_spire/contrib/queryset/filter_tools.py,sha256=o7OBB3Jjtyoj7aaWspkFKS2bv0BeMIigXz7DVvt6Ueo,1087
@@ -549,7 +557,7 @@ django_spire/core/templates/django_spire/maintenance.html,sha256=T4dwFh8jyCxxVaz
549
557
  django_spire/core/templates/django_spire/accordion/accordion.html,sha256=12a9S-8Be2m4-SozdVQDm7XwlYeD75MD5VMyJkwYq-U,566
550
558
  django_spire/core/templates/django_spire/accordion/view_glue_accordion.html,sha256=QrLebgX726YVX2EBbM9wVhB0B-IsWcmA2EbSd3C2lUM,869
551
559
  django_spire/core/templates/django_spire/badge/accent_badge.html,sha256=1Ib4sZ8JUkVx8seLWyqHhzt2Fosc5U3rTZecv04h_jU,103
552
- django_spire/core/templates/django_spire/badge/base_badge.html,sha256=RrKqN4oaz3BZ2AjKG_NQyUdNzyPsrUFMQIhKgFJ5tNI,176
560
+ django_spire/core/templates/django_spire/badge/base_badge.html,sha256=t1oLpIIi0aLGMu5ySDZYy9fD-Z8W6mZjuNYhJh0q314,313
553
561
  django_spire/core/templates/django_spire/badge/danger_badge.html,sha256=CgPL64GZxPbkGW9tDh2WPsPkURDFDjTqRbSOG2Kc_6k,103
554
562
  django_spire/core/templates/django_spire/badge/primary_badge.html,sha256=AsZZiVRWlHzgLDcocf2tWr7Q0Z3oADj2-jilQpnZ_Sc,104
555
563
  django_spire/core/templates/django_spire/badge/primary_outlined_badge.html,sha256=WA1EX5-ovSm7Hs6h7sVpMdNf9gF5db_pW2TFni5D_p4,132
@@ -1097,8 +1105,8 @@ django_spire/theme/urls/page_urls.py,sha256=S8nkKkgbhG3XHI3uMUL-piOjXIrRkuY2UlM_
1097
1105
  django_spire/theme/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1098
1106
  django_spire/theme/views/json_views.py,sha256=W1khC2K_EMbEzAFmMxC_P76_MFnkRH4-eVdodrRfAhw,1904
1099
1107
  django_spire/theme/views/page_views.py,sha256=pHr8iekjtR99xs7w1taj35HEo133Svq1dvDD0y0VL1c,3933
1100
- django_spire-0.17.3.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1101
- django_spire-0.17.3.dist-info/METADATA,sha256=tIwxrqNEAbyJ9jbgwEB6gOHWJB4NuGF2G0IwLi3GqoQ,4936
1102
- django_spire-0.17.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1103
- django_spire-0.17.3.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1104
- django_spire-0.17.3.dist-info/RECORD,,
1108
+ django_spire-0.17.5.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1109
+ django_spire-0.17.5.dist-info/METADATA,sha256=2i3NkfAg8_fKKsLY33UwdVxFfGavbwYkcRLvJbN8xAk,4936
1110
+ django_spire-0.17.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1111
+ django_spire-0.17.5.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1112
+ django_spire-0.17.5.dist-info/RECORD,,