django-spire 0.16.13__py3-none-any.whl → 0.17.1__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 (79) hide show
  1. django_spire/consts.py +1 -1
  2. django_spire/core/management/commands/spire_startapp.py +84 -46
  3. django_spire/core/management/commands/spire_startapp_pkg/__init__.py +60 -0
  4. django_spire/core/management/commands/spire_startapp_pkg/builder.py +91 -0
  5. django_spire/core/management/commands/spire_startapp_pkg/config.py +115 -0
  6. django_spire/core/management/commands/spire_startapp_pkg/filesystem.py +125 -0
  7. django_spire/core/management/commands/spire_startapp_pkg/generator.py +167 -0
  8. django_spire/core/management/commands/spire_startapp_pkg/maps.py +783 -25
  9. django_spire/core/management/commands/spire_startapp_pkg/permissions.py +147 -0
  10. django_spire/core/management/commands/spire_startapp_pkg/processor.py +144 -57
  11. django_spire/core/management/commands/spire_startapp_pkg/registry.py +89 -0
  12. django_spire/core/management/commands/spire_startapp_pkg/reporter.py +245 -108
  13. django_spire/core/management/commands/spire_startapp_pkg/resolver.py +86 -0
  14. django_spire/core/management/commands/spire_startapp_pkg/template/app/__init__.py.template +0 -0
  15. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +15 -0
  16. django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template +18 -0
  17. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/__init__.py.template +0 -0
  18. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/bots.py.template +18 -0
  19. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/intel.py.template +7 -0
  20. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/prompts.py.template +32 -0
  21. django_spire/core/management/commands/spire_startapp_pkg/template/app/migrations/__init__.py.template +0 -0
  22. django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template +52 -0
  23. django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template +20 -0
  24. django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/__init__.py.template +0 -0
  25. django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seed.py.template +6 -0
  26. django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seeder.py.template +26 -0
  27. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/__init__.py.template +0 -0
  28. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/factory_service.py.template +12 -0
  29. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/intelligence_service.py.template +12 -0
  30. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/processor_service.py.template +12 -0
  31. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/service.py.template +22 -0
  32. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/transformation_service.py.template +12 -0
  33. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/__init__.py.template +0 -0
  34. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template +0 -0
  35. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/test_bots.py.template +8 -0
  36. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_models.py.template +8 -0
  37. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/__init__.py.template +0 -0
  38. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_factory_service.py.template +8 -0
  39. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_intelligence_service.py.template +8 -0
  40. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_processor_service.py.template +8 -0
  41. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_service.py.template +8 -0
  42. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_transformation_service.py.template +8 -0
  43. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/__init__.py.template +0 -0
  44. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_form_urls.py.template +8 -0
  45. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_page_urls.py.template +8 -0
  46. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/__init__.py.template +0 -0
  47. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_form_views.py.template +8 -0
  48. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_page_views.py.template +8 -0
  49. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +9 -0
  50. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +15 -0
  51. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +11 -0
  52. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/__init__.py.template +0 -0
  53. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template +134 -0
  54. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template +44 -0
  55. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/{spirechildapp_detail_card.html → ${detail_card_template_name}.html.template} +7 -7
  56. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/{spirechildapp_form_card.html → ${form_card_template_name}.html.template} +2 -2
  57. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template +18 -0
  58. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/{spirechildapp_form.html → ${form_template_name}.html.template} +4 -4
  59. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${item_template_name}.html.template +24 -0
  60. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_detail_page.html → ${detail_page_template_name}.html.template} +3 -3
  61. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_form_page.html → ${form_page_template_name}.html.template} +1 -1
  62. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_list_page.html → ${list_page_template_name}.html.template} +2 -2
  63. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +252 -0
  64. django_spire/core/management/commands/spire_startapp_pkg/validator.py +96 -0
  65. django_spire/core/middleware/__init__.py +1 -2
  66. django_spire/profiling/__init__.py +13 -0
  67. django_spire/profiling/middleware/__init__.py +6 -0
  68. django_spire/{core → profiling}/middleware/profiling.py +63 -58
  69. django_spire/profiling/panel.py +345 -0
  70. django_spire/profiling/templates/panel.html +166 -0
  71. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/METADATA +1 -1
  72. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/RECORD +75 -23
  73. django_spire/core/management/commands/spire_startapp_pkg/constants.py +0 -4
  74. django_spire/core/management/commands/spire_startapp_pkg/manager.py +0 -176
  75. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html +0 -18
  76. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
  77. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/WHEEL +0 -0
  78. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/licenses/LICENSE.md +0 -0
  79. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,345 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import time
5
+
6
+ from dataclasses import asdict, dataclass
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing_extensions import TYPE_CHECKING
10
+
11
+ from debug_toolbar.panels import Panel
12
+ from django.conf import settings
13
+ from django.http import Http404, HttpRequest, HttpResponse, JsonResponse
14
+ from django.urls import re_path
15
+ from django.utils import timezone
16
+ from django.views.decorators.csrf import csrf_exempt
17
+
18
+ from django_spire.profiling import lock
19
+
20
+ if TYPE_CHECKING:
21
+ from typing_extensions import Any
22
+
23
+
24
+ @dataclass
25
+ class Profile:
26
+ filename: str
27
+ path: str
28
+ method: str
29
+ duration: str
30
+ profile_id: str
31
+ size: str
32
+ modified: str
33
+ timestamp: float
34
+
35
+
36
+ @dataclass
37
+ class ProfileStats:
38
+ profiles: list[Profile]
39
+ count: int
40
+ directory: str
41
+ total_size: str
42
+ enabled: bool
43
+
44
+
45
+ @dataclass
46
+ class ProfileList:
47
+ profiles: list[dict[str, Any]]
48
+ count: int
49
+ total_size: str
50
+
51
+
52
+ class ProfilingPanel(Panel):
53
+ nav_title = 'Profiles'
54
+ template = 'panel.html'
55
+ title = 'Profiling'
56
+
57
+ def __init__(self, *args, **kwargs):
58
+ super().__init__(*args, **kwargs)
59
+
60
+ self.directory = self._get_directory()
61
+
62
+ def _format_size(self, size: int) -> str:
63
+ for unit in ['B', 'KB', 'MB', 'GB']:
64
+ if size < 1024.0:
65
+ return f'{size:.1f} {unit}'
66
+
67
+ size /= 1024.0
68
+
69
+ return f'{size:.1f} TB'
70
+
71
+ def _get_directory(self) -> Path:
72
+ location = os.getenv('PROFILING_DIR', '.profile')
73
+
74
+ if isinstance(location, str):
75
+ if not Path(location).is_absolute():
76
+ current = Path.cwd()
77
+ base = getattr(settings, 'BASE_DIR', current)
78
+ location = Path(base) / location
79
+ else:
80
+ location = Path(location)
81
+
82
+ return Path(location)
83
+
84
+ def _get_files(self) -> list[Profile]:
85
+ profiles = []
86
+
87
+ if not self.directory.exists():
88
+ return profiles
89
+
90
+ with lock:
91
+ files = self._list_files()
92
+
93
+ for file in files:
94
+ if not file.exists():
95
+ continue
96
+
97
+ profile = self._parse_file(file)
98
+
99
+ if profile:
100
+ profiles.append(profile)
101
+
102
+ return profiles
103
+
104
+ def _get_size(self) -> int:
105
+ total = 0
106
+
107
+ if self.directory.exists():
108
+ with lock:
109
+ for file in self.directory.glob('*.html'):
110
+ if file.exists():
111
+ total = total + file.stat().st_size
112
+
113
+ return total
114
+
115
+ def _list_files(self) -> list[Path]:
116
+ files = [
117
+ file for file in self.directory.glob('*.html')
118
+ if file.exists()
119
+ ]
120
+
121
+ files.sort(
122
+ key=lambda p: p.stat().st_mtime if p.exists() else 0,
123
+ reverse=True
124
+ )
125
+
126
+ return files
127
+
128
+ def _parse_file(self, file: Path) -> Profile | None:
129
+ stat = file.stat()
130
+ parts = file.stem.split('_')
131
+
132
+ timestamp, method, path_part, duration, profile_id = self._parse_filename(
133
+ parts,
134
+ stat
135
+ )
136
+
137
+ display = self._parse_path(path_part)
138
+
139
+ mst = timezone.get_fixed_timezone(-360)
140
+ modified = datetime.fromtimestamp(timestamp, tz=mst)
141
+
142
+ return Profile(
143
+ filename=file.name,
144
+ path=display,
145
+ method=method,
146
+ duration=duration,
147
+ profile_id=profile_id,
148
+ size=self._format_size(stat.st_size),
149
+ modified=modified.strftime('%b %d, %Y %I:%M %p MST'),
150
+ timestamp=timestamp
151
+ )
152
+
153
+ def _parse_filename(self, parts: list[str], stat: Any) -> tuple[float, str, str, str, str]:
154
+ if len(parts) >= 4:
155
+ timestamp = int(parts[0]) / 1000
156
+ method = parts[1]
157
+ path_part = '_'.join(parts[2:-2])
158
+ duration = parts[-2] if len(parts) > 3 else 'N/A'
159
+ profile_id = parts[-1] if len(parts) > 4 else 'N/A'
160
+ else:
161
+ timestamp = stat.st_mtime
162
+ method = 'Unknown'
163
+ path_part = '_'.join(parts)
164
+ duration = 'N/A'
165
+ profile_id = 'N/A'
166
+
167
+ return timestamp, method, path_part, duration, profile_id
168
+
169
+ def _parse_path(self, path_part: str) -> str:
170
+ if path_part == 'root':
171
+ return '/'
172
+
173
+ display = path_part.replace('_', '/')
174
+
175
+ if not display.startswith('/'):
176
+ display = '/' + display
177
+
178
+ return display
179
+
180
+ def _resolve_location(self) -> Path:
181
+ location = os.getenv('PROFILING_DIR', '.profile')
182
+
183
+ if not Path(location).is_absolute():
184
+ base = getattr(settings, 'BASE_DIR', Path.cwd())
185
+ location = Path(base) / location
186
+ else:
187
+ location = Path(location)
188
+
189
+ return location
190
+
191
+ def _try_delete(self, filepath: Path) -> JsonResponse | None:
192
+ for i in range(3):
193
+ try:
194
+ filepath.unlink()
195
+ except FileNotFoundError:
196
+ return JsonResponse({'success': True, 'is_deleted': True})
197
+ except PermissionError:
198
+ if i < 2:
199
+ time.sleep(0.1)
200
+ continue
201
+
202
+ return JsonResponse(
203
+ {'error': 'File is in use, try again'},
204
+ status=409
205
+ )
206
+ except Exception as e:
207
+ return JsonResponse({'error': str(e)}, status=500)
208
+ else:
209
+ return JsonResponse({'success': True})
210
+
211
+ return None
212
+
213
+ def _validate_filename(self, filename: str) -> str | None:
214
+ if '..' in filename or '/' in filename or '\\' in filename:
215
+ return 'Invalid filename'
216
+
217
+ if not filename.endswith('.html'):
218
+ return 'Only HTML files are allowed'
219
+
220
+ return None
221
+
222
+ def _validate_filepath(self, filepath: Path, location: Path) -> str | None:
223
+ if not filepath.is_file():
224
+ return 'Not a file'
225
+
226
+ try:
227
+ filepath.resolve().relative_to(location.resolve())
228
+ except ValueError:
229
+ return 'Invalid file path'
230
+
231
+ return None
232
+
233
+ @classmethod
234
+ def delete_file(cls, request: HttpRequest, filename: str) -> JsonResponse:
235
+ panel = cls(None, None)
236
+ error = panel._validate_filename(filename)
237
+
238
+ if error:
239
+ return JsonResponse({'error': error}, status=400)
240
+
241
+ location = panel._resolve_location()
242
+ filepath = location / filename
243
+
244
+ with lock:
245
+ if not filepath.exists():
246
+ return JsonResponse({'success': True, 'is_deleted': True})
247
+
248
+ error = panel._validate_filepath(filepath, location)
249
+
250
+ if error:
251
+ return JsonResponse({'error': error}, status=400)
252
+
253
+ result = panel._try_delete(filepath)
254
+
255
+ if result:
256
+ return result
257
+
258
+ return JsonResponse({'error': 'Could not delete file'}, status=500)
259
+
260
+ def generate_stats(self, request: HttpRequest, response: HttpResponse) -> None:
261
+ profiles = self._get_files()
262
+ profiling = os.getenv('PROFILING_ENABLED', 'False') == 'True'
263
+
264
+ stats = ProfileStats(
265
+ profiles=[asdict(profile) for profile in profiles],
266
+ count=len(profiles),
267
+ directory=str(self.directory),
268
+ total_size=self._format_size(self._get_size()),
269
+ enabled=profiling
270
+ )
271
+
272
+ self.record_stats(asdict(stats))
273
+
274
+ @classmethod
275
+ def get_urls(cls) -> list:
276
+ return [
277
+ re_path(
278
+ r'^profiling/view/(?P<filename>[^/]+)/$',
279
+ cls.serve_file,
280
+ name='profiling_view'
281
+ ),
282
+ re_path(
283
+ r'^profiling/delete/(?P<filename>[^/]+)/$',
284
+ csrf_exempt(cls.delete_file),
285
+ name='profiling_delete'
286
+ ),
287
+ re_path(
288
+ r'^profiling/list/$',
289
+ cls.list_files,
290
+ name='profiling_list'
291
+ ),
292
+ ]
293
+
294
+ @classmethod
295
+ def list_files(cls, request: HttpRequest) -> JsonResponse:
296
+ panel = cls(None, None)
297
+ panel.directory = panel._resolve_location()
298
+ profiles = panel._get_files()
299
+
300
+ response = ProfileList(
301
+ profiles=[asdict(profile) for profile in profiles],
302
+ count=len(profiles),
303
+ total_size=panel._format_size(panel._get_size())
304
+ )
305
+
306
+ return JsonResponse(asdict(response))
307
+
308
+ @property
309
+ def nav_subtitle(self) -> str:
310
+ count = 0
311
+
312
+ if self.directory.exists():
313
+ with lock:
314
+ count = sum(
315
+ 1 for f in self.directory.glob('*.html')
316
+ if f.exists()
317
+ )
318
+
319
+ return f'{count} profiles'
320
+
321
+ @classmethod
322
+ def serve_file(cls, request: HttpRequest, filename: str) -> HttpResponse:
323
+ panel = cls(None, None)
324
+ error = panel._validate_filename(filename)
325
+
326
+ if error:
327
+ raise Http404(error)
328
+
329
+ location = panel._resolve_location()
330
+ filepath = location / filename
331
+
332
+ with lock:
333
+ if not filepath.exists():
334
+ message = 'Profile has been removed'
335
+ raise Http404(message)
336
+
337
+ error = panel._validate_filepath(filepath, location)
338
+
339
+ if error:
340
+ raise Http404(error)
341
+
342
+ with open(filepath, 'r', encoding='utf-8') as handle:
343
+ content = handle.read()
344
+
345
+ return HttpResponse(content, content_type='text/html')
@@ -0,0 +1,166 @@
1
+ {% load i18n %}
2
+
3
+ <style>
4
+ .djdt-profiling-panel {
5
+ padding: 10px;
6
+ }
7
+
8
+ .djdt-profiling-stats {
9
+ display: flex;
10
+ gap: 30px;
11
+ margin-bottom: 30px;
12
+ align-items: center;
13
+ padding-bottom: 20px;
14
+ border-bottom: 1px solid #e0e0e0;
15
+ }
16
+
17
+ .djdt-profiling-stat {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 10px;
21
+ }
22
+
23
+ .djdt-profiling-stat-label {
24
+ opacity: 0.7;
25
+ }
26
+
27
+ .djdt-profiling-stat-value {
28
+ font-weight: bold;
29
+ }
30
+
31
+ .djdt-profiling-table {
32
+ width: 100%;
33
+ border-collapse: collapse;
34
+ font-size: 0.9em;
35
+ }
36
+
37
+ .djdt-profiling-table th {
38
+ padding: 8px;
39
+ text-align: left;
40
+ font-weight: bold;
41
+ }
42
+
43
+ .djdt-profiling-table td {
44
+ padding: 8px;
45
+ }
46
+
47
+ .djdt-profile-link {
48
+ text-decoration: none;
49
+ font-weight: bold;
50
+ }
51
+
52
+ .djdt-profile-link:hover {
53
+ text-decoration: underline;
54
+ }
55
+
56
+ .djdt-profile-delete {
57
+ text-decoration: none;
58
+ font-weight: bold;
59
+ }
60
+
61
+ .djdt-profile-delete:hover {
62
+ text-decoration: underline;
63
+ }
64
+
65
+ .djdt-no-profiles {
66
+ padding: 20px;
67
+ text-align: center;
68
+ font-style: italic;
69
+ }
70
+
71
+ .djdt-circular-buffer-note {
72
+ padding: 8px;
73
+ border-radius: 3px;
74
+ margin-bottom: 10px;
75
+ font-size: 3.85em;
76
+ }
77
+ </style>
78
+
79
+ <div class="djdt-profiling-panel">
80
+ <div class="djdt-profiling-stats">
81
+ <div class="djdt-profiling-stat">
82
+ <div class="djdt-profiling-stat-label">Status:</div>
83
+ <div class="djdt-profiling-stat-value">
84
+ {% if enabled %}Enabled{% else %}Disabled{% endif %}
85
+ </div>
86
+ </div>
87
+
88
+ <div class="djdt-profiling-stat">
89
+ <div class="djdt-profiling-stat-label">Profiles:</div>
90
+ <div class="djdt-profiling-stat-value" id="profile-count">{{ count }}</div>
91
+ </div>
92
+
93
+ <div class="djdt-profiling-stat">
94
+ <div class="djdt-profiling-stat-label">Size:</div>
95
+ <div class="djdt-profiling-stat-value" id="total-size">{{ total_size }}</div>
96
+ </div>
97
+
98
+ <div class="djdt-profiling-stat">
99
+ <div class="djdt-profiling-stat-label">Directory:</div>
100
+ <div class="djdt-profiling-stat-value">{{ directory }}</div>
101
+ </div>
102
+
103
+ <div class="djdt-profiling-stat" style="margin-left: auto;">
104
+ <a href="#" onclick="if(confirm('Delete all profiles?')){let rows=document.querySelectorAll('#profiling-tbody tr');let promises=[];rows.forEach(row=>{let filename=row.dataset.filename;if(filename)promises.push(fetch('/__debug__/profiling/delete/'+filename+'/').then(r=>r.json()).catch(e=>({error:e.toString()})));});Promise.all(promises).then(results=>{let success_count=0;let is_deleted=0;let failures=0;results.forEach(r=>{if(r.success)success_count++;else if(r.is_deleted)is_deleted++;else failures++;});document.getElementById('profiling-tbody').innerHTML='';document.getElementById('profiling-table').outerHTML='<div class=djdt-no-profiles>No profiles found</div>';document.getElementById('profile-count').textContent='0';document.getElementById('total-size').textContent='0 B';let panels=document.querySelectorAll('.djDebugPanelButton');panels.forEach(p=>{if(p.textContent.includes('Profiles')){let small=p.querySelector('small');if(small)small.textContent='0 profiles';}});if(failures>0){alert('Deleted '+success_count+' profiles. '+failures+' failed (may have been removed by circular buffer).');}}).catch(e=>alert('Error during bulk delete: '+e));}return false;" style="color: #d32f2f; font-weight: bold; text-decoration: none;">
105
+ Delete All
106
+ </a>
107
+ </div>
108
+ </div>
109
+
110
+ <br>
111
+
112
+ <div class="djdt-circular-buffer-note">
113
+ <strong>Note:</strong> The ten most recent profiles are kept, and the remainder are removed.
114
+ </div>
115
+
116
+ <br>
117
+
118
+ {% if not enabled %}
119
+ <div style="padding: 10px; margin-bottom: 15px; font-size: 0.9em; opacity: 0.7;">
120
+ Set PROFILING_ENABLED=True to enable profiling
121
+ </div>
122
+ {% endif %}
123
+
124
+ {% if profiles %}
125
+ <table class="djdt-profiling-table" id="profiling-table">
126
+ <thead>
127
+ <tr>
128
+ <th>Method</th>
129
+ <th>Path</th>
130
+ <th>Duration</th>
131
+ <th>Time</th>
132
+ <th>Size</th>
133
+ <th>View</th>
134
+ <th>Delete</th>
135
+ </tr>
136
+ </thead>
137
+ <tbody id="profiling-tbody">
138
+ {% for profile in profiles %}
139
+ <tr data-filename="{{ profile.filename }}">
140
+ <td>
141
+ <span>{{ profile.method }}</span>
142
+ </td>
143
+ <td>{{ profile.path }}</td>
144
+ <td>{{ profile.duration }}</td>
145
+ <td>{{ profile.modified }}</td>
146
+ <td>{{ profile.size }}</td>
147
+ <td>
148
+ <a href="{% url 'djdt:profiling_view' profile.filename %}" target="_blank" class="djdt-profile-link">
149
+ View
150
+ </a>
151
+ </td>
152
+ <td>
153
+ <a href="#" onclick="if(confirm('Delete this profile?')){fetch('/__debug__/profiling/delete/{{ profile.filename }}/').then(r=>r.json()).then(d=>{if(d.success||d.is_deleted){let row=this.closest('tr');row.remove();let count=document.querySelectorAll('#profiling-tbody tr').length;document.getElementById('profile-count').textContent=count;fetch('/__debug__/profiling/list/').then(r=>r.json()).then(data=>{document.getElementById('total-size').textContent=data.total_size;});let panels=document.querySelectorAll('.djDebugPanelButton');panels.forEach(p=>{if(p.textContent.includes('Profiles')){let small=p.querySelector('small');if(small)small.textContent=count+' profiles';}});if(count===0){document.getElementById('profiling-table').outerHTML='<div class=djdt-no-profiles>No profiles found</div>';}if(d.is_deleted){alert('Profile was already removed (circular buffer)');}}else if(d.error){alert('Failed to delete: '+d.error);}}).catch(e=>{console.error('Delete error:',e);alert('Network error while deleting');});}return false;" class="djdt-profile-delete">
154
+ Delete
155
+ </a>
156
+ </td>
157
+ </tr>
158
+ {% endfor %}
159
+ </tbody>
160
+ </table>
161
+ {% else %}
162
+ <div class="djdt-no-profiles">
163
+ No profiles found in {{ directory }}
164
+ </div>
165
+ {% endif %}
166
+ </div>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.16.13
3
+ Version: 0.17.1
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.
@@ -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=J6U3ROjjdihsXICCCn68i2QMWT14HbmGOO8dZ9agt9U,391
3
+ django_spire/consts.py,sha256=OZWVVkyHmTiy_6lNgmeAJAedfpjT64zQzYfPdZwTjg8,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
@@ -408,25 +408,72 @@ django_spire/core/forms/widgets.py,sha256=_XgJZ9SoMJ4yz-NhLCAOppSvEWk_5JLUtn3I3_
408
408
  django_spire/core/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
409
409
  django_spire/core/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
410
410
  django_spire/core/management/commands/spire_remove_migration.py,sha256=G3dibVdzvEm9ZMfE_czOk6SyPDn5IEpJ3m1nw-rxuYs,1755
411
- django_spire/core/management/commands/spire_startapp.py,sha256=TWSN7c8-dgn0BBHn6pOq0oMBTxZ7keKBs3Yzu7WKfRo,3001
412
- django_spire/core/management/commands/spire_startapp_pkg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
413
- django_spire/core/management/commands/spire_startapp_pkg/constants.py,sha256=PPlf_6xV2nQucGvGuqEHk64ZN8HEJiyot1Yst8jSY2A,94
414
- django_spire/core/management/commands/spire_startapp_pkg/manager.py,sha256=HWgPHtApsDDsw4o1NhboSdw40kr2Yc7WP5Grh9ippkQ,5251
415
- django_spire/core/management/commands/spire_startapp_pkg/maps.py,sha256=F2oYHMYII2x1sKMRHEc3hF8Rd3ftjIiIxYnYUALs49A,1140
416
- django_spire/core/management/commands/spire_startapp_pkg/processor.py,sha256=yEiGQMrgTOxvd-PAvmgFQjFIaAE-NBu80LD2CR5abfY,2623
417
- django_spire/core/management/commands/spire_startapp_pkg/reporter.py,sha256=alxyyH7zX7f0cFO-rKJq0pP9N7UfZIUGVx2XN9yh7bs,6489
411
+ django_spire/core/management/commands/spire_startapp.py,sha256=XAA3m7a-dKL4ZYO_WDzKW8ZDgB3__j2RAyiz6qllYjk,4572
412
+ django_spire/core/management/commands/spire_startapp_pkg/__init__.py,sha256=DN9Zllp-XszJawudgbcMKfhhTIPnjWzeTGUSRlwEaG4,1894
413
+ django_spire/core/management/commands/spire_startapp_pkg/builder.py,sha256=1rGZlHdnTM9QdTFFjDf87xCSCA2QIUd8ndtVvf-PbGE,3170
414
+ django_spire/core/management/commands/spire_startapp_pkg/config.py,sha256=qoIGKUYJcRAnYW1WbdpSxKerWckeJGxoDVCZ9yWxrgY,3390
415
+ django_spire/core/management/commands/spire_startapp_pkg/filesystem.py,sha256=shYly0AElxhUsb1WOsK8FdcB3qcsMmghZeEP1IByHq8,3719
416
+ django_spire/core/management/commands/spire_startapp_pkg/generator.py,sha256=MleLiKyFnILhCiQy_BcMRVCdw-1JOhB-7EZBMFdukKc,5989
417
+ django_spire/core/management/commands/spire_startapp_pkg/maps.py,sha256=m0SKcy_TZ4O-mjI74qkxyhVjoDN4QvMmQxgvbbn6mAE,22977
418
+ django_spire/core/management/commands/spire_startapp_pkg/permissions.py,sha256=czSE-ZNBw-3LsGrtkaD7CBFnitYX18KQjgJ_jjbap7k,5260
419
+ django_spire/core/management/commands/spire_startapp_pkg/processor.py,sha256=fqnbgaw8AsNHQBZnbDLmU4WPueFx5bQzA9Cwi_lD_KQ,5928
420
+ django_spire/core/management/commands/spire_startapp_pkg/registry.py,sha256=izf5SHkek2nIEf0UkODXL4uY1SkWXmoL3_s6pihF2QE,2689
421
+ django_spire/core/management/commands/spire_startapp_pkg/reporter.py,sha256=C48v1dLgnzmGijE9m_G_k5dMuOmNq3Am1zETjiEzEuQ,11085
422
+ django_spire/core/management/commands/spire_startapp_pkg/resolver.py,sha256=-y86EMmWU65bMzGeS5mI_O0iVu-xfVrdVKXtrOgbwUg,2835
423
+ django_spire/core/management/commands/spire_startapp_pkg/user_input.py,sha256=VUyY9PrqM07vRvgbVJhkC4lgNolnBik3K8S_8U9s_zM,9518
424
+ django_spire/core/management/commands/spire_startapp_pkg/validator.py,sha256=cVzQqaShGZ0ae-M69tHFDgXlYBmtR2Zg0zblIj55-Nc,3404
418
425
  django_spire/core/management/commands/spire_startapp_pkg/template/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
419
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_detail_card.html,sha256=mLq1OENpsrJQ7FiEAwowwE4ZlhcFZuwhfZPvYN3mZs8,953
420
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_form_card.html,sha256=m-z23qNNn5R4ctuAypXoQgcti2mz-l57uvBmptBZG-E,233
421
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html,sha256=tJIZLL89SUV8As6RBRiYwIDVhEjgUu8173YRCDaRZHo,659
422
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/spirechildapp_form.html,sha256=fQ-kULFIGACH7YVWK2S4O3hEp1DwjumqxOgymp15e7E,686
423
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html,sha256=m_R9HlPcH89eSNcKkNwD8DXnuH7oBU3pw7YrENWgrKI,1244
424
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_detail_page.html,sha256=dOxIi0ISZwYpyPTrH1IuAJjWR4jWzgtpzaWpWYxSA3I,434
425
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_form_page.html,sha256=9eSbAsMK8rjzSYmrq_NZOB6F1EphMIO4KIoDlv6YOrw,331
426
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_list_page.html,sha256=SwTuxzNzTW6_nlZvrglSG2amithT0ZCjnNnrAd9aaA0,236
427
- django_spire/core/middleware/__init__.py,sha256=peocpyT_vXsjK3MXzpWIbsNVnHBbuyybH90R9V-lN1c,243
426
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
427
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template,sha256=hry02l60eqkUVUraogSZ9XRoZAFqrUkJw51U0KOcBuc,393
428
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template,sha256=zdHsuTjk_fM2s7o0_JgcHioIZKxBcmo4vziHXYEYHmA,361
429
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template,sha256=U5-5feDcgy63uPiuxm5mwrtsoC61ExAIyhK1c-pK3iY,1406
430
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template,sha256=my06IPSuiyKw1Goj7-cAMWRIRWDSY0RFpkrmFYLwuiE,539
431
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
432
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/bots.py.template,sha256=jd6AhAhEU_e0QmIX7uMUDnvjSRJ6TU9kV8-rv2NXJ5k,472
433
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/intel.py.template,sha256=byeizQFWtrhcnEOaSIpRfm12vo1ZyhoMcH6EjxBptWU,119
434
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/prompts.py.template,sha256=1FxXSCC5V2fQexyHWfoOsepNngxe49_tv7DEykA2xPQ,826
435
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/migrations/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
436
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
437
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seed.py.template,sha256=M7EqfGNfjkgM6Fq4CeWhSDaD6cvbRLY_iX0pjuH57rU,146
438
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seeder.py.template,sha256=LSSdIsbPpsXcLrRi0sSzz1NjxM-6nEtG7EGdawlLhiY,633
439
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/services/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
440
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/services/factory_service.py.template,sha256=yWvLFT6G0hLUOYafp9UwLmmHY0j1OYHgHfqpCZ0glso,326
441
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/services/intelligence_service.py.template,sha256=FHjE-Gm4bJ6V7ksb7HObo-gvB8tuceAxct7kvYM57mg,331
442
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/services/processor_service.py.template,sha256=IGs5kH4_tnKJLe6u30KVs-Ky1zWbUHM3EXQbAiSWR5U,328
443
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/services/service.py.template,sha256=aakNxhOJC4_uzz_7DSgzIMy-kKXau6lZnUrKLQqT4bg,888
444
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/services/transformation_service.py.template,sha256=KziILbCSErJLR5yet_uQPpZdjqNn_jdz61wzGkzsYLY,333
445
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
446
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_models.py.template,sha256=SI9aMtCJinF0dV_oEqhMe6T-cS_yxXpe0EJbZKZCZxY,197
447
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
448
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/test_bots.py.template,sha256=CRb3mM0PwsYhzBbjZKTtqnX6NuVWEy4GHS_VLjZ4ftg,195
449
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
450
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_factory_service.py.template,sha256=oE5XfkPYG14ol3yBrXlGgIsZX-OwCxnJfG8f2RVIDFg,207
451
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_intelligence_service.py.template,sha256=tHECog2BMvHPBvreCIS1SaLI6w9eTTZAUcP91M89878,212
452
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_processor_service.py.template,sha256=QkDbgr8_JQv71FZa_supA08KjAqP8KeWxdew8IWWJz4,209
453
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_service.py.template,sha256=_Wu60zUWEigP4laPamAv0EpzJKTNV3LTq8jIz8QpGmQ,199
454
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_transformation_service.py.template,sha256=XdQYL1abaeKHC3kCRIQOmmZ9OUW0TKr255WcRwBT6AU,214
455
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
456
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_form_urls.py.template,sha256=JluI4IZYqetiqOTYDi9ohSK6w8Yj8D36A9DA7zLSURo,195
457
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_page_urls.py.template,sha256=JluI4IZYqetiqOTYDi9ohSK6w8Yj8D36A9DA7zLSURo,195
458
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
459
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_form_views.py.template,sha256=ZXbAwKt9jl9Wv_ho-DoBQGdhRpPXld7PFbIFWfPLCMo,196
460
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_page_views.py.template,sha256=ZXbAwKt9jl9Wv_ho-DoBQGdhRpPXld7PFbIFWfPLCMo,196
461
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template,sha256=oLUVg3_pnCtG-wRGA38vmLU7J__SIw1xUtrhi0EvlQI,252
462
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template,sha256=D2ReVjflX_ZIVU3xGJzokHWnWdwpLEfW92gAMQxo-Pw,641
463
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template,sha256=f3y2mJCP92HrLMw37T7xSUc1E6Ky8ZnqiFIzJYnM3jc,262
464
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/views/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
465
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template,sha256=KWvIdPrhfPdpvuJMHYvoUQXi1_ngUeSx28OX691JO10,4538
466
+ django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template,sha256=h1MBeRmY7c3bioVp46zHwTINtHYJ5dmCf_kmMRDUHC8,1399
467
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${detail_card_template_name}.html.template,sha256=-TtrmvKgiB5fMmVtP6ZZdg3tkkKvKdG0Z6DNtdzL1Lw,1004
468
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${form_card_template_name}.html.template,sha256=Tybi6-14YHAoXeqPFDE4ZIesCaEggPBlAP6wHJ7uzPw,236
469
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template,sha256=s8w4sibmXKMmAa3OWM43OrjKUplbWAQMRxtjDgKgXk8,677
470
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/${form_template_name}.html.template,sha256=JDoLlUlu1TAlYV0Sqk3XqjpUcnmbt9utp94asoLH5sk,716
471
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${item_template_name}.html.template,sha256=reHnBcH87yOJvCqEduAeiQ1eafqj687tBtaXO4ZJMYw,1334
472
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${detail_page_template_name}.html.template,sha256=ffmg2eNi3AIkS1hAxMgWP51qAzZ8HUuyloNEP3SXXw0,436
473
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${form_page_template_name}.html.template,sha256=JdUCGpWgCvwg328dgAkCA9b7MGTyQG2EZs_Dol8c-pM,346
474
+ django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${list_page_template_name}.html.template,sha256=Ve0RVvWjMU6m33wXdDv10MkDoEFKWULuu-1_BK-O4aI,254
475
+ django_spire/core/middleware/__init__.py,sha256=BvBwVHLVebknJkObtKk_s5OqaIJaAmnavTvnnv4TBO8,149
428
476
  django_spire/core/middleware/maintenance.py,sha256=jJtmz69UXJBBgPkq5SNspWuER1bGfAjSnsVipYS4TF0,921
429
- django_spire/core/middleware/profiling.py,sha256=oW8S5oZQYq96a28cmVPLwyhz3xBAZHkTK7NMxLUDXCw,4807
430
477
  django_spire/core/redirect/__init__.py,sha256=ubAsz_h3ufwgNNkdtqiJQDgXc6DodE2LTUyBwi9dgkE,71
431
478
  django_spire/core/redirect/generic_redirect.py,sha256=bfpA2GSRbkf5Y_zqTiTGzakQauLumm3JbaYMzmsDhjA,1245
432
479
  django_spire/core/redirect/safe_redirect.py,sha256=deGLqiR1wWwqlJ8BYp76qDUDHnfRrxL-1Vns3nozSG0,2901
@@ -994,6 +1041,11 @@ django_spire/notification/sms/urls/__init__.py,sha256=9R6ee7PseGpAiiraEU0m4eeF8G
994
1041
  django_spire/notification/sms/urls/media_urls.py,sha256=fYAW8LWMwUe5nP2QuWWdnhuSyv-0PX4nIw0vjbabENQ,271
995
1042
  django_spire/notification/sms/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
996
1043
  django_spire/notification/sms/views/media_views.py,sha256=STrTC1irBSHEFQ0SZfUhpBI9Xmc6luWDf7Lembfd5Ic,1080
1044
+ django_spire/profiling/__init__.py,sha256=V5zHoTaqGCOU-gfwkGQI01XcXeRYoAjO103yDkk1tIQ,251
1045
+ django_spire/profiling/panel.py,sha256=BBISa2T35_QV2ODtx0DsL2ypK_BbETFSdr9iu_wIgCk,9570
1046
+ django_spire/profiling/middleware/__init__.py,sha256=K6wjo7xUhOAJaB5Vbqa5JRf3EQM78NwToz_VtW3zz78,148
1047
+ django_spire/profiling/middleware/profiling.py,sha256=Vl8WHCEYoUl1P50uFog2Gp6hK2INSRZC2t4LuY46DOA,5103
1048
+ django_spire/profiling/templates/panel.html,sha256=YNK5ypTpUezWCtT0CE-awIpKPo4pKOPo9wbD4fkzgwc,6684
997
1049
  django_spire/theme/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
998
1050
  django_spire/theme/apps.py,sha256=yol2S7ooTuplc8JKETEg6oAu9hpeHUpJ6wt1Bo3dQ3E,479
999
1051
  django_spire/theme/enums.py,sha256=I0Sa5GzAntAPL8d7BJykhzuftA7ZweqZZtFIcH_g6kA,495
@@ -1043,8 +1095,8 @@ django_spire/theme/urls/page_urls.py,sha256=S8nkKkgbhG3XHI3uMUL-piOjXIrRkuY2UlM_
1043
1095
  django_spire/theme/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1044
1096
  django_spire/theme/views/json_views.py,sha256=W1khC2K_EMbEzAFmMxC_P76_MFnkRH4-eVdodrRfAhw,1904
1045
1097
  django_spire/theme/views/page_views.py,sha256=pHr8iekjtR99xs7w1taj35HEo133Svq1dvDD0y0VL1c,3933
1046
- django_spire-0.16.13.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1047
- django_spire-0.16.13.dist-info/METADATA,sha256=qkok6POcbtu3Fobg9hSCBCEceQU3mQaRWU3YM5moeNk,4937
1048
- django_spire-0.16.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1049
- django_spire-0.16.13.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1050
- django_spire-0.16.13.dist-info/RECORD,,
1098
+ django_spire-0.17.1.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1099
+ django_spire-0.17.1.dist-info/METADATA,sha256=TRZJFfU9itNrxHhw4T0ODV43rqFJa1g7dtsk4ZZX3KQ,4936
1100
+ django_spire-0.17.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1101
+ django_spire-0.17.1.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1102
+ django_spire-0.17.1.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- INDENTATION = ' '
2
- ICON_FOLDER_OPEN = '📂'
3
- ICON_FOLDER_CLOSED = '📁'
4
- ICON_FILE = '📄'