django-spire 0.23.8__py3-none-any.whl → 0.23.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. django_spire/consts.py +1 -1
  2. django_spire/contrib/progress/__init__.py +7 -13
  3. django_spire/contrib/progress/enums.py +2 -1
  4. django_spire/contrib/progress/session.py +283 -0
  5. django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +35 -57
  6. django_spire/contrib/progress/tasks.py +67 -0
  7. django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html +25 -24
  8. django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html +58 -67
  9. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
  10. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
  11. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
  12. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
  13. django_spire/core/templates/django_spire/card/card.html +1 -1
  14. django_spire/core/templates/django_spire/card/title_card.html +7 -4
  15. django_spire/core/templates/django_spire/table/base.html +4 -4
  16. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/METADATA +2 -2
  17. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/RECORD +20 -23
  18. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/licenses/LICENSE.md +1 -1
  19. django_spire/contrib/progress/mixins.py +0 -36
  20. django_spire/contrib/progress/runner.py +0 -140
  21. django_spire/contrib/progress/states.py +0 -64
  22. django_spire/contrib/progress/task.py +0 -40
  23. django_spire/contrib/progress/tracker.py +0 -245
  24. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/WHEEL +0 -0
  25. {django_spire-0.23.8.dist-info → django_spire-0.23.10.dist-info}/top_level.txt +0 -0
@@ -10,115 +10,106 @@
10
10
 
11
11
  {% block modal_content_content %}
12
12
  <div
13
- data-stream-url="{% block progress_stream_url %}{% endblock %}"
14
- data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
15
13
  data-redirect-delay="{% block progress_redirect_delay %}1000{% endblock %}"
14
+ data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
15
+ data-stream-url="{% block progress_stream_url %}{% endblock %}"
16
16
  x-data="{
17
- tasks: {},
18
- task_order: [],
19
- overall_progress: 0,
20
- message: 'Initializing...',
21
17
  has_error: false,
22
- stream: null,
23
-
24
- init() {
25
- let stream_url = this.$el.dataset.streamUrl;
26
- let redirect_url = this.$el.dataset.redirectUrl;
27
- let redirect_delay = parseInt(this.$el.dataset.redirectDelay);
18
+ message: 'Initializing...',
19
+ overall_percent: 0,
20
+ tasks: {},
28
21
 
29
- this.stream = new ProgressStream(stream_url, {
30
- on_update: (data) => {
31
- this.message = data.message;
32
- this.overall_progress = data.progress;
33
- if (data.task_order) {
34
- this.task_order = data.task_order;
35
- }
36
- if (data.tasks) {
37
- this.tasks = data.tasks;
38
- }
39
- },
40
- on_complete: (data) => {
41
- setTimeout(() => {
42
- close_modal();
43
- }, 500);
22
+ async init() {
23
+ let stream = new ProgressStream(this.$el.dataset.streamUrl, {
24
+ on_complete: () => {
25
+ setTimeout(() => close_modal(), 500);
44
26
  },
45
27
  on_error: (data) => {
46
28
  this.has_error = true;
47
- this.message = data.message;
29
+ this.message = data.message || 'An error occurred';
30
+ },
31
+ on_update: (data) => {
32
+ this.overall_percent = data.overall_percent;
33
+ this.tasks = data.tasks;
48
34
  },
49
- redirect_on_complete: redirect_url,
50
- redirect_delay: redirect_delay
35
+ redirect_delay: parseInt(this.$el.dataset.redirectDelay),
36
+ redirect_url: this.$el.dataset.redirectUrl,
51
37
  });
52
- this.stream.start();
53
- },
54
38
 
55
- format_task_name(name) {
56
- return name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
39
+ await stream.start();
57
40
  },
58
41
 
59
- get_progress_class(step) {
60
- if (step === 'complete') return 'bg-success';
61
- if (step === 'error') return 'bg-danger';
62
- if (step === 'processing') return 'bg-app-accent';
63
- return 'bg-secondary';
42
+ get_status_icon(status) {
43
+ if (status === 'complete') return 'bi-check-circle-fill text-success';
44
+ if (status === 'error') return 'bi-x-circle-fill text-danger';
45
+ if (status === 'running') return 'bi-arrow-repeat text-primary spin';
46
+ return 'bi-circle text-secondary';
64
47
  }
65
48
  }"
66
49
  >
67
50
  <div class="row g-3">
68
51
  <div class="col-12" x-show="!has_error">
69
- <template x-for="name in task_order" :key="name">
70
- <div class="mb-3" x-show="tasks[name]">
71
- <div class="d-flex justify-content-between align-items-center mb-1">
72
- <span class="fs-7" x-text="format_task_name(name)"></span>
73
- <span class="fs-7" x-text="tasks[name]?.progress + '%'"></span>
52
+ <template x-for="(task, task_id) in tasks" :key="task_id">
53
+ <div class="mb-3">
54
+ <div class="d-flex align-items-center mb-1">
55
+ <i class="bi me-2" :class="get_status_icon(task.status)"></i>
56
+ <span class="fw-medium" x-text="task.name"></span>
57
+ <span class="ms-auto fs-7" x-text="task.percent + '%'"></span>
74
58
  </div>
75
- <div class="progress" style="height: 6px;">
59
+ <div class="progress" style="height: 4px;">
76
60
  <div
77
61
  class="progress-bar"
78
- :class="get_progress_class(tasks[name]?.step)"
62
+ :class="task.status === 'error' ? 'bg-danger' : 'bg-success'"
79
63
  role="progressbar"
80
- :style="'width: ' + (tasks[name]?.progress || 0) + '%'"
64
+ :style="'width: ' + task.percent + '%'"
81
65
  ></div>
82
66
  </div>
67
+ <small class="text-muted" x-text="task.message"></small>
83
68
  </div>
84
69
  </template>
85
70
 
86
71
  <div class="mt-4 pt-3 border-top">
87
72
  <div class="d-flex justify-content-between align-items-center mb-2">
88
- <span class="fs-6 fw-medium">Overall Progress</span>
89
- <span class="fs-6" x-text="overall_progress + '%'"></span>
73
+ <span class="fw-semibold">Overall Progress</span>
74
+ <span x-text="overall_percent + '%'"></span>
90
75
  </div>
91
76
  <div class="progress" style="height: 8px;">
92
77
  <div
93
- class="progress-bar bg-app-accent"
78
+ class="progress-bar bg-primary"
94
79
  role="progressbar"
95
- :style="'width: ' + overall_progress + '%'"
80
+ :style="'width: ' + overall_percent + '%'"
96
81
  ></div>
97
82
  </div>
98
83
  </div>
99
- </div>
100
84
 
101
- <div class="col-12" x-show="!has_error">
102
- <p class="text-app-secondary mb-0 fs-7">
103
- {% block progress_wait_message %}Processing your request. This may take a moment.{% endblock %}
85
+ <p class="text-app-secondary mb-0 fs-7 text-center mt-3">
86
+ {% block progress_wait_message %}This may take a moment. Please do not close this window.{% endblock %}
104
87
  </p>
105
88
  </div>
106
89
 
107
90
  <div class="col-12" x-show="has_error" x-cloak>
108
- <div class="row g-2">
109
- <div class="col-12">
110
- {% include 'django_spire/element/attribute_element.html' with attribute_title='Error' %}
111
- <span class="fs-6 text-app-danger" x-text="message"></span>
112
- </div>
113
- <div class="col-12">
114
- <span @click="close_modal()">
115
- {% block progress_error_button %}
116
- {% include 'django_spire/button/primary_button.html' with button_text='Close' %}
117
- {% endblock %}
118
- </span>
119
- </div>
91
+ <div class="d-flex align-items-center justify-content-center py-4">
92
+ <i class="bi bi-x-circle-fill text-danger me-3 fs-4"></i>
93
+ <span class="fs-5" x-text="message"></span>
94
+ </div>
95
+ <div class="text-center">
96
+ <span @click="close_modal()">
97
+ {% block progress_error_button %}
98
+ {% include 'django_spire/button/primary_button.html' with button_text='Close' %}
99
+ {% endblock %}
100
+ </span>
120
101
  </div>
121
102
  </div>
122
103
  </div>
123
104
  </div>
105
+
106
+ <style>
107
+ .spin {
108
+ animation: spin 1s linear infinite;
109
+ }
110
+ @keyframes spin {
111
+ from { transform: rotate(0deg); }
112
+ to { transform: rotate(360deg); }
113
+ }
114
+ </style>
124
115
  {% endblock %}
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import AppConfig
2
4
 
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls.conf import include, path
2
4
 
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from ${module_path}.views import form_views
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from ${module_path}.views import page_views
@@ -2,7 +2,7 @@
2
2
  class="card ms-0 shadow-sm rounded-3 bg-app-layer-two {% block card_class %}{% endblock %}"
3
3
  style="{% block card_style %}{% endblock %}"
4
4
  >
5
- <div class="{% block card_padding %}p-3{% endblock %}" style="{% block card_body_style %}{% endblock %}">
5
+ <div class="h-100 {% block card_padding %}p-3{% endblock %}" style="min-height: 0; {% block card_body_style %}{% endblock %}">
6
6
  {% block card_content %}{% endblock %}
7
7
  </div>
8
8
 
@@ -1,12 +1,15 @@
1
1
  {% extends 'django_spire/card/card.html' %}
2
2
 
3
- {% block card_padding %}p-3 h-100{% endblock %}
3
+ {% block card_class %}d-flex flex-column{% endblock %}
4
4
 
5
- {% block card_body_style %}display: flex; flex-direction: column; overflow: hidden;{% endblock %}
5
+ {% block card_padding %}p-3 flex-grow-1{% endblock %}
6
+
7
+ {% block card_body_style %}display: flex; flex-direction: column; min-height: 0;{% endblock %}
6
8
 
7
9
  {% block card_content %}
8
10
  <div
9
- class="d-flex flex-column flex-grow-1 overflow-hidden"
11
+ class="d-flex flex-column flex-grow-1"
12
+ style="min-height: 0;"
10
13
  x-data="{
11
14
  card_title_dropdown: false,
12
15
  toggle_card_title_dropdown() {
@@ -29,7 +32,7 @@
29
32
  {% block card_dropdown_content %}
30
33
  {% endblock %}
31
34
  </div>
32
- <div class="flex-grow-1 overflow-hidden" style="{% block card_title_content_style %}{% endblock %}">
35
+ <div class="flex-grow-1 d-flex flex-column" style="min-height: 0;">
33
36
  {% block card_title_content %}
34
37
  {% endblock %}
35
38
  </div>
@@ -1,6 +1,6 @@
1
1
  {% extends 'django_spire/infinite_scroll/base.html' %}
2
2
 
3
- {% block scroll_wrapper_class %}d-flex flex-column{% if not table_height %} h-100{% endif %}{% endblock %}
3
+ {% block scroll_wrapper_class %}d-flex flex-column h-100{% endblock %}
4
4
 
5
5
  {% block scroll_xdata %}
6
6
  responsive_mode: '{{ responsive_mode|default:"collapse" }}',
@@ -140,8 +140,8 @@
140
140
 
141
141
  {% block scroll_container %}
142
142
  <div
143
- class="position-relative table-container {% if not table_height %}flex-grow-1 min-height-0{% endif %}"
144
- style="height: {{ table_height|default:'600px' }}; max-height: 100%; overflow-x: auto; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;"
143
+ class="position-relative table-container flex-grow-1"
144
+ style="min-height: 200px; overflow-x: auto; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;"
145
145
  x-ref="scroll_container"
146
146
  :data-table-id="table_id"
147
147
  >
@@ -205,7 +205,7 @@
205
205
  {% endblock %}
206
206
 
207
207
  {% block scroll_footer %}
208
- <div class="row mt-3">
208
+ <div class="row mt-3 flex-shrink-0">
209
209
  <div class="col text-start">
210
210
  <span class="fs-7 text-app-secondary">
211
211
  Showing <span x-text="loaded_count"></span> of <span x-text="total_count"></span> rows
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.23.8
3
+ Version: 0.23.10
4
4
  Summary: A project for Django Spire
5
5
  Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
6
- License: Copyright (c) 2024 Stratus Advanced Technologies and Contributors.
6
+ License: Copyright (c) 2025 Stratus Advanced Technologies and Contributors.
7
7
 
8
8
  Permission is hereby granted, free of charge, to any person
9
9
  obtaining a copy of this software and associated documentation
@@ -1,6 +1,6 @@
1
1
  django_spire/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  django_spire/conf.py,sha256=3oUB1mtgHRjvbJsxfQWG5uL1KUP9uGig3zdP2dZphe8,942
3
- django_spire/consts.py,sha256=N6aGfBOBcchAtS2D8tFechvBOB-PrwSl3xfSSxo9Ppo,171
3
+ django_spire/consts.py,sha256=d5_qKitqKzGvVZucpkte-GZyZj9s2j8C0KuzkalsyVQ,172
4
4
  django_spire/exceptions.py,sha256=M7buFvm-K4lK09pH5fVcZ-MxsDIzdpEJBF33Xss5bSw,289
5
5
  django_spire/settings.py,sha256=0xImrKpF7VW4wc9jLGO07e2ev6D4-i_TREgpTepLp3I,1042
6
6
  django_spire/urls.py,sha256=wQx6R-nXx69MeOF-WmDxcEUM5WmUHGplbY5uZ_HnDp8,703
@@ -397,17 +397,14 @@ django_spire/contrib/performance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
397
397
  django_spire/contrib/performance/decorators.py,sha256=vCENOqcmXoEvi1Y3Uw55J21zB-ZvpWk7rRo2jqyg8pc,731
398
398
  django_spire/contrib/performance/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
399
399
  django_spire/contrib/performance/tests/test_performance.py,sha256=wVNzrUEG0bTMcvJrqkSWkAE27LSyJMxrRZRd04ZKDQM,3029
400
- django_spire/contrib/progress/__init__.py,sha256=pvd6_7puMDWStjZqcwsWEulpeGupQPv2fladV1IiaXg,631
401
- django_spire/contrib/progress/enums.py,sha256=nwyfKxQno_ghGGTbpK8lRO1cY7dCfbxnnsQ4cSbkXBw,194
402
- django_spire/contrib/progress/mixins.py,sha256=V84LRtniFDLK2E5dKcSZNh-a1zTf9Ee7QXg1rDfoFHo,895
403
- django_spire/contrib/progress/runner.py,sha256=3jSqpdk2MJ7HPNutwF43Hs81O0NlzX4E9gnVaenhXUs,4445
404
- django_spire/contrib/progress/states.py,sha256=V7JU_nzpZN77TQFW0S51Gjil8xn8CKLKkAWDOpqzA8k,1908
405
- django_spire/contrib/progress/task.py,sha256=4pMbzOsjRRNqPKNohh08D39DRFpC3QomTCH5IdqQS0A,780
406
- django_spire/contrib/progress/tracker.py,sha256=xjGlEyKe5aiKW_KC8NKZ8F0BjQdFpLUqHz5Gr_C7jws,6490
400
+ django_spire/contrib/progress/__init__.py,sha256=5a7LTmUyw_cfO-3_QuN5M6zAhrCNs7MjEypL13qhoQA,345
401
+ django_spire/contrib/progress/enums.py,sha256=Rexk804FhmVQou7UOKSOCnjaZrSDZdniV_MWLh-kGxI,218
402
+ django_spire/contrib/progress/session.py,sha256=Oo1QS8f7aTN7K_Y9bwXZHo6otZQkRSp6V_6c0stvYcI,8825
403
+ django_spire/contrib/progress/tasks.py,sha256=afC4vhWzvuRq6-P80LdB-BmDGD5kADtgYGM2SaKz8Ao,1625
407
404
  django_spire/contrib/progress/static/django_spire/css/contrib/progress/progress.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
408
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js,sha256=BeT52b8rHoBzk3tMpXX3bO3R-bDYTjyrEHjjc1AS2LQ,2228
409
- django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html,sha256=2Ajv0UBJEGwc4InVe69aLe2u4K56yh2JAuvO6uH24j8,3353
410
- django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html,sha256=Qxer-q4GBC5h8onzno2NUlPrXluw_tO1kWlGComQ98E,5232
405
+ django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js,sha256=vhjwLWTqkjcuK7j_z9ndCGdWfBFmRAM4MvF8zpEKJL0,1730
406
+ django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html,sha256=UB5PpaZzaSihiE6BnThYrM21qVrf1iT1fF-TbtkoZrc,3389
407
+ django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html,sha256=613hvePQFurk4Q0Id8NuQz9Umbh35eJSOR-IQipo_o4,4857
411
408
  django_spire/contrib/queryset/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
412
409
  django_spire/contrib/queryset/enums.py,sha256=dkzI464Q53huqtNGfnM-biuKpHUQU78WwuWvt3Pm8mI,145
413
410
  django_spire/contrib/queryset/filter_tools.py,sha256=V_s6pG2uyqvoTNtBXXXNKXE69ipwexxWAR-X30H1-XI,1129
@@ -517,7 +514,7 @@ django_spire/core/management/commands/spire_startapp_pkg/user_input.py,sha256=kj
517
514
  django_spire/core/management/commands/spire_startapp_pkg/validator.py,sha256=q6fsyRmR1kcN2eAFu-5pDaadJ05FDog7N23eR215Yx0,3322
518
515
  django_spire/core/management/commands/spire_startapp_pkg/template/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
519
516
  django_spire/core/management/commands/spire_startapp_pkg/template/app/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
520
- django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template,sha256=hry02l60eqkUVUraogSZ9XRoZAFqrUkJw51U0KOcBuc,393
517
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template,sha256=KG--onS7UQG7a1KUe142OzejZb3sBGOXSedSY-lU9Yw,429
521
518
  django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template,sha256=zdHsuTjk_fM2s7o0_JgcHioIZKxBcmo4vziHXYEYHmA,361
522
519
  django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template,sha256=U5-5feDcgy63uPiuxm5mwrtsoC61ExAIyhK1c-pK3iY,1406
523
520
  django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template,sha256=my06IPSuiyKw1Goj7-cAMWRIRWDSY0RFpkrmFYLwuiE,539
@@ -551,9 +548,9 @@ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test
551
548
  django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
552
549
  django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_form_views.py.template,sha256=ZXbAwKt9jl9Wv_ho-DoBQGdhRpPXld7PFbIFWfPLCMo,196
553
550
  django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_page_views.py.template,sha256=ZXbAwKt9jl9Wv_ho-DoBQGdhRpPXld7PFbIFWfPLCMo,196
554
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template,sha256=oLUVg3_pnCtG-wRGA38vmLU7J__SIw1xUtrhi0EvlQI,252
555
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template,sha256=D2ReVjflX_ZIVU3xGJzokHWnWdwpLEfW92gAMQxo-Pw,641
556
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template,sha256=f3y2mJCP92HrLMw37T7xSUc1E6Ky8ZnqiFIzJYnM3jc,262
551
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template,sha256=fPEmfEX9UIIFXQ5Xh570s8Np3NGJgJSUtVBT6OHQndA,288
552
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template,sha256=a0v-kIjlbcpMKeSQmfRUs00DeYAAyCfmVLDSZyJMmfs,677
553
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template,sha256=PR3pHMcuHyxVd6fd_q5TQIs1n9lowhfRSJIBie_9yr8,298
557
554
  django_spire/core/management/commands/spire_startapp_pkg/template/app/views/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
558
555
  django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template,sha256=np31b1k9htY_alOGc9VqDasomtiQRUfNtRKVObAP3H4,4548
559
556
  django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template,sha256=QMozesEIYhDMhsvVJH_MxgobE-SVnczP5dRmTdxyMFM,1409
@@ -702,12 +699,12 @@ django_spire/core/templates/django_spire/button/success_outlined_button.html,sha
702
699
  django_spire/core/templates/django_spire/button/warning_button.html,sha256=KUlpESYRvZ4GklLhn5YBKnvjDJOaTARrLxv8Fpd-hNU,108
703
700
  django_spire/core/templates/django_spire/button/warning_dark_button.html,sha256=5bqj3HO1Qwye0qgRsQ4jpPje-w62JQz3yyAAUthUGzc,113
704
701
  django_spire/core/templates/django_spire/button/warning_outlined_button.html,sha256=_PLvsB6wCRoDcAien0f57urCSY_FcCkL8zWDDsNUbE0,117
705
- django_spire/core/templates/django_spire/card/card.html,sha256=7HlP7wnE-p22Vqei7S-0TjC8W9xwiy7WoILGI9dzNjY,370
702
+ django_spire/core/templates/django_spire/card/card.html,sha256=Nryuc1qgmSncY8t4ZZ2Bofs2Kr9D1VX1L5H6O-lhqSw,391
706
703
  django_spire/core/templates/django_spire/card/delete_confirmation_form_card.html,sha256=A-IrDQHKgQ1iveGJJ9EKsvs-TUW0eVY8uTcWM4dmQ_4,1097
707
704
  django_spire/core/templates/django_spire/card/delete_form_card.html,sha256=7nAZ0LuIq9l2BwNiBny_C__Q9EUXYQ_CM1aFXrafd6c,212
708
705
  django_spire/core/templates/django_spire/card/form_card.html,sha256=bctVy9CeHuf05xsuE-zEsQVOaq24Igjq6xTKgv2AO4Y,391
709
706
  django_spire/core/templates/django_spire/card/infinite_scroll_card.html,sha256=EM57LOCjuKqJZfsTwNmzRB3I8lzvRLcXdmpq8h7c-q0,261
710
- django_spire/core/templates/django_spire/card/title_card.html,sha256=oCrVFpF_XH7vbMD_RljLcn5AxD3cKnvYLcJxVp4gu24,1383
707
+ django_spire/core/templates/django_spire/card/title_card.html,sha256=5ahO5PoB3ZPiluEZI7FqwB8E9t-BqVmj-uLSVh2RYPQ,1424
711
708
  django_spire/core/templates/django_spire/container/container.html,sha256=Dfr7K8xgEbg3CZYxMleLjjiQGyRS-GNJdPySA4OYY8U,867
712
709
  django_spire/core/templates/django_spire/container/form_container.html,sha256=FZwZs4gnIYkurV5u8v6fj2rbrm4WDX16jyUSTMniEtw,313
713
710
  django_spire/core/templates/django_spire/container/infinite_scroll_container.html,sha256=L3sPkIQG99SFJpkuYOg_RnlYFFobxE0B-weuDqUwiv0,2494
@@ -776,7 +773,7 @@ django_spire/core/templates/django_spire/speech/speech_synthesis.html,sha256=25H
776
773
  django_spire/core/templates/django_spire/tab/tab.html,sha256=1zSIX0Xk337VV9Qa4tvoSJlhoT2LDTfEfCwJvESNG-Q,1964
777
774
  django_spire/core/templates/django_spire/tab/element/tab_section_element.html,sha256=tEo0ENKhss74ll8HcFE9_kml35vusAfiMQHpudh3aEc,181
778
775
  django_spire/core/templates/django_spire/tab/element/tab_trigger_element.html,sha256=9VmMhfEGa3ARHerwyXojtQWyIkUj9sOPGhckcQR3h6k,442
779
- django_spire/core/templates/django_spire/table/base.html,sha256=PxwX-RcOtivN8e2ir-dGuX-3GZ13bv4fPSKvUZrCOSQ,7384
776
+ django_spire/core/templates/django_spire/table/base.html,sha256=Bu0ijKNIaPJ8cTgp3HW1xwAX_uUwXayORmjTwKEVWYg,7270
780
777
  django_spire/core/templates/django_spire/table/element/child_row.html,sha256=B0fIXw3-p7YREdNtpIBwijCom6RS64Lrzj-teRuvvKI,1916
781
778
  django_spire/core/templates/django_spire/table/element/expandable_row.html,sha256=sAzzUeGishdYRUqs9t4lduxuxZaOzUYNhMfMytRLq90,1516
782
779
  django_spire/core/templates/django_spire/table/element/footer.html,sha256=fVxbDrVj4C9jIswkri7B2Dr4nEr2Lf_EPPULMU3mRs0,312
@@ -1372,8 +1369,8 @@ django_spire/theme/urls/page_urls.py,sha256=Oak3x_xwQEb01NKdrsB1nk6yPaOEnheuSG1m
1372
1369
  django_spire/theme/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1373
1370
  django_spire/theme/views/json_views.py,sha256=PWwVTaty0BVGbj65L5cxex6JNhc-xVAI_rEYjbJWqEM,1893
1374
1371
  django_spire/theme/views/page_views.py,sha256=WenjOa6Welpu3IMolY56ZwBjy4aK9hpbiMNuygjAl1A,3922
1375
- django_spire-0.23.8.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1376
- django_spire-0.23.8.dist-info/METADATA,sha256=who5EpafuzWVzaRU9CL5vai5ujBowv5mYsTkzDT-fQs,5127
1377
- django_spire-0.23.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1378
- django_spire-0.23.8.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1379
- django_spire-0.23.8.dist-info/RECORD,,
1372
+ django_spire-0.23.10.dist-info/licenses/LICENSE.md,sha256=ZAeCT76WvaoEZE9xPhihyWjTwH0wQZXQmyRsnV2VPFs,1091
1373
+ django_spire-0.23.10.dist-info/METADATA,sha256=tdZ2P4q_PlnHfvRd4XibSjAzFHyxjzKG9PDX3EjAVU4,5128
1374
+ django_spire-0.23.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1375
+ django_spire-0.23.10.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1376
+ django_spire-0.23.10.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
- Copyright (c) 2024 Stratus Advanced Technologies and Contributors.
1
+ Copyright (c) 2025 Stratus Advanced Technologies and Contributors.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
@@ -1,36 +0,0 @@
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
- from django_spire.contrib.progress.enums import ProgressStatus
11
-
12
-
13
- class ProgressTrackingMixin:
14
- _tracker: ProgressTracker | None = None
15
-
16
- def get_tracker_key(self) -> str:
17
- raise NotImplementedError
18
-
19
- @property
20
- def tracker(self) -> ProgressTracker:
21
- if self._tracker is None:
22
- self._tracker = ProgressTracker(self.get_tracker_key())
23
-
24
- return self._tracker
25
-
26
- def progress_error(self, message: str) -> None:
27
- self.tracker.error(message)
28
-
29
- def update_progress(
30
- self,
31
- status: ProgressStatus,
32
- message: str,
33
- progress: int,
34
- **kwargs: Any
35
- ) -> None:
36
- self.tracker.update(status, message, progress, **kwargs)
@@ -1,140 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import math
4
- import random
5
- import threading
6
- import time
7
-
8
- from typing import TYPE_CHECKING
9
-
10
- from django_spire.contrib.progress.enums import ProgressStatus
11
- from django_spire.contrib.progress.task import ProgressMessages, Task, TaskResult
12
-
13
- if TYPE_CHECKING:
14
- from typing import Any, Callable
15
-
16
- from django_spire.contrib.progress.tracker import ProgressTracker
17
-
18
-
19
- class TaskProgressUpdater:
20
- def __init__(self, tracker: ProgressTracker, task: Task) -> None:
21
- self._messages = ProgressMessages()
22
- self._task = task
23
- self._tracker = tracker
24
-
25
- def complete(self, message: str | None = None) -> None:
26
- self._tracker._update_task(
27
- self._task.name,
28
- ProgressStatus.COMPLETE,
29
- self._format(message or self._messages.complete),
30
- 100
31
- )
32
-
33
- def error(self, message: str) -> None:
34
- self._tracker._update_task(
35
- self._task.name,
36
- ProgressStatus.ERROR,
37
- self._format(message),
38
- 0
39
- )
40
-
41
- def start(self) -> None:
42
- self._tracker._update_task(
43
- self._task.name,
44
- ProgressStatus.PROCESSING,
45
- self._format(self._messages.starting),
46
- 2
47
- )
48
-
49
- def update(self, message: str, progress: int) -> None:
50
- self._tracker._update_task(
51
- self._task.name,
52
- ProgressStatus.PROCESSING,
53
- self._format(message),
54
- progress
55
- )
56
-
57
- def _format(self, message: str) -> str:
58
- return f'{self._task.label}: {message}'
59
-
60
-
61
- class ProgressSimulator:
62
- def __init__(
63
- self,
64
- updater: TaskProgressUpdater,
65
- max_progress: int = 90,
66
- update_interval: float = 0.15
67
- ) -> None:
68
- self._max_progress = max_progress
69
- self._messages = ProgressMessages()
70
- self._update_interval = update_interval
71
- self._updater = updater
72
-
73
- def run(self, stop_event: threading.Event) -> None:
74
- start_time = time.time()
75
- duration = random.uniform(8.0, 15.0)
76
-
77
- while not stop_event.is_set():
78
- elapsed = time.time() - start_time
79
- t = min(elapsed / duration, 1.0)
80
-
81
- progress = self._ease_out_expo(t) * self._max_progress
82
- progress = min(int(progress), self._max_progress)
83
-
84
- jitter = random.uniform(-1.5, 1.5) if progress < self._max_progress - 5 else 0
85
- progress = max(2, min(int(progress + jitter), self._max_progress))
86
-
87
- message = self._get_message_for_progress(progress)
88
- self._updater.update(message, progress)
89
-
90
- if progress >= self._max_progress:
91
- break
92
-
93
- time.sleep(self._update_interval + random.uniform(0, 0.1))
94
-
95
- def _ease_out_expo(self, t: float) -> float:
96
- if t >= 1.0:
97
- return 1.0
98
-
99
- return 1.0 - math.pow(2, -10 * t)
100
-
101
- def _get_message_for_progress(self, progress: int) -> str:
102
- steps = self._messages.steps
103
- index = min(int(progress / (self._max_progress / len(steps))), len(steps) - 1)
104
-
105
- return steps[index]
106
-
107
-
108
- class TaskRunner:
109
- def __init__(self, tracker: ProgressTracker, task: Task) -> None:
110
- self._stop_event = threading.Event()
111
- self._task = task
112
- self._tracker = tracker
113
- self._updater = TaskProgressUpdater(tracker, task)
114
-
115
- def run_parallel(self, results: dict[str, TaskResult]) -> None:
116
- self._execute(results, lambda: self._task.func())
117
-
118
- def run_sequential(self, results: dict[str, TaskResult]) -> None:
119
- unwrapped = {name: result.value for name, result in results.items()}
120
- self._execute(results, lambda: self._task.func(unwrapped))
121
-
122
- def stop(self) -> None:
123
- self._stop_event.set()
124
-
125
- def _execute(self, results: dict[str, TaskResult], executor: Callable[[], Any]) -> None:
126
- simulator = ProgressSimulator(self._updater)
127
- progress_thread = threading.Thread(target=simulator.run, args=(self._stop_event,))
128
- progress_thread.start()
129
-
130
- try:
131
- self._updater.start()
132
- value = executor()
133
- results[self._task.name] = TaskResult(value=value)
134
- self._updater.complete()
135
- except BaseException as e:
136
- results[self._task.name] = TaskResult(error=e)
137
- self._updater.error(str(e))
138
- finally:
139
- self._stop_event.set()
140
- progress_thread.join(timeout=1)
@@ -1,64 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
5
-
6
- from django_spire.contrib.progress.enums import ProgressStatus
7
-
8
- if TYPE_CHECKING:
9
- from typing import Any
10
-
11
-
12
- @dataclass
13
- class TaskState:
14
- message: str = 'Waiting...'
15
- progress: int = 0
16
- status: ProgressStatus = ProgressStatus.PENDING
17
-
18
- @classmethod
19
- def from_dict(cls, data: dict[str, Any]) -> TaskState:
20
- return cls(
21
- message=data.get('message', 'Waiting...'),
22
- progress=data.get('progress', 0),
23
- status=ProgressStatus(data.get('step', ProgressStatus.PENDING)),
24
- )
25
-
26
- def to_dict(self) -> dict[str, Any]:
27
- return {
28
- 'message': self.message,
29
- 'progress': self.progress,
30
- 'step': self.status.value,
31
- }
32
-
33
-
34
- @dataclass
35
- class TrackerState:
36
- message: str = 'Initializing...'
37
- progress: int = 0
38
- status: ProgressStatus = ProgressStatus.PENDING
39
- task_order: list[str] = field(default_factory=list)
40
- tasks: dict[str, TaskState] = field(default_factory=dict)
41
-
42
- @classmethod
43
- def from_dict(cls, data: dict[str, Any]) -> TrackerState:
44
- tasks = {
45
- name: TaskState.from_dict(task_data)
46
- for name, task_data in data.get('tasks', {}).items()
47
- }
48
-
49
- return cls(
50
- message=data.get('message', 'Initializing...'),
51
- progress=data.get('progress', 0),
52
- status=ProgressStatus(data.get('step', ProgressStatus.PENDING)),
53
- task_order=data.get('task_order', []),
54
- tasks=tasks,
55
- )
56
-
57
- def to_dict(self) -> dict[str, Any]:
58
- return {
59
- 'message': self.message,
60
- 'progress': self.progress,
61
- 'step': self.status.value,
62
- 'task_order': self.task_order,
63
- 'tasks': {name: task.to_dict() for name, task in self.tasks.items()},
64
- }