django-spire 0.16.13__py3-none-any.whl → 0.17.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.
Files changed (36) 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/user_input.py +252 -0
  15. django_spire/core/management/commands/spire_startapp_pkg/validator.py +96 -0
  16. django_spire/core/middleware/__init__.py +1 -2
  17. django_spire/profiling/__init__.py +13 -0
  18. django_spire/profiling/middleware/__init__.py +6 -0
  19. django_spire/{core → profiling}/middleware/profiling.py +63 -58
  20. django_spire/profiling/panel.py +345 -0
  21. django_spire/profiling/templates/panel.html +166 -0
  22. {django_spire-0.16.13.dist-info → django_spire-0.17.0.dist-info}/METADATA +1 -1
  23. {django_spire-0.16.13.dist-info → django_spire-0.17.0.dist-info}/RECORD +26 -23
  24. django_spire/core/management/commands/spire_startapp_pkg/constants.py +0 -4
  25. django_spire/core/management/commands/spire_startapp_pkg/manager.py +0 -176
  26. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_detail_card.html +0 -24
  27. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_form_card.html +0 -9
  28. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html +0 -18
  29. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/spirechildapp_form.html +0 -22
  30. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
  31. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_detail_page.html +0 -13
  32. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_form_page.html +0 -13
  33. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_list_page.html +0 -9
  34. {django_spire-0.16.13.dist-info → django_spire-0.17.0.dist-info}/WHEEL +0 -0
  35. {django_spire-0.16.13.dist-info → django_spire-0.17.0.dist-info}/licenses/LICENSE.md +0 -0
  36. {django_spire-0.16.13.dist-info → django_spire-0.17.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,252 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ from typing_extensions import TYPE_CHECKING
6
+
7
+ from django.core.management.base import CommandError
8
+
9
+ if TYPE_CHECKING:
10
+ from django_spire.core.management.commands.spire_startapp_pkg.reporter import Reporter
11
+ from django_spire.core.management.commands.spire_startapp_pkg.validator import AppValidator
12
+
13
+
14
+ class UserInputCollector:
15
+ """
16
+ Collects user input for Django app creation through an interactive wizard.
17
+
18
+ This class guides users through a step-by-step process to gather all
19
+ necessary configuration for creating a new Django app.
20
+ """
21
+
22
+ def __init__(self, reporter: Reporter, validator: AppValidator):
23
+ """
24
+ Initializes the collector with a reporter and validator.
25
+
26
+ :param reporter: Reporter instance for displaying prompts and messages.
27
+ :param validator: Validator for checking user input validity.
28
+ """
29
+
30
+ self.reporter = reporter
31
+ self.validator = validator
32
+
33
+ def collect_all_inputs(self) -> dict[str, str]:
34
+ """
35
+ Collects all required user inputs for app creation.
36
+
37
+ Guides the user through an 8-step wizard to gather app path, names,
38
+ labels, and configuration options.
39
+
40
+ :return: Dictionary containing all collected user inputs.
41
+ """
42
+
43
+ self.reporter.write('\n[App Creation Wizard]\n\n', self.reporter.style_success)
44
+
45
+ app_path = self._collect_app_path()
46
+ components = app_path.split('.')
47
+
48
+ app_name = self._collect_app_name(components)
49
+ app_label = self._collect_app_label(components, app_name)
50
+ model_name = self._collect_model_name(app_name)
51
+ model_name_plural = self._collect_model_name_plural(model_name)
52
+ db_table_name = self._collect_db_table_name(app_label)
53
+ model_permission_path = self._collect_model_permission_path(app_path, model_name)
54
+
55
+ permission_data = self._collect_permission_inheritance(components)
56
+
57
+ verbose_name, verbose_name_plural = self._derive_verbose_names(model_name, model_name_plural)
58
+
59
+ return {
60
+ 'app_path': app_path,
61
+ 'app_name': app_name,
62
+ 'model_name': model_name,
63
+ 'model_name_plural': model_name_plural,
64
+ 'app_label': app_label,
65
+ 'db_table_name': db_table_name,
66
+ 'model_permission_path': model_permission_path,
67
+ 'verbose_name': verbose_name,
68
+ 'verbose_name_plural': verbose_name_plural,
69
+ **permission_data,
70
+ }
71
+
72
+ def _collect_app_label(self, components: list[str], app_name: str) -> str:
73
+ """
74
+ Prompts the user for the Django app label.
75
+
76
+ :param components: List of app path components.
77
+ :param app_name: Name of the app.
78
+ :return: User-provided or default app label.
79
+ """
80
+
81
+ parent_parts = components[1:-1] if len(components) > 1 else []
82
+ default = '_'.join(parent_parts).lower() + '_' + app_name.lower() if parent_parts else app_name.lower()
83
+ return self._collect_input('Enter the app label', default, '3/8')
84
+
85
+ def _collect_app_name(self, components: list[str]) -> str:
86
+ """
87
+ Prompts the user for the app name.
88
+
89
+ :param components: List of app path components.
90
+ :return: User-provided or default app name.
91
+ """
92
+
93
+ default = components[-1]
94
+ return self._collect_input('Enter the app name', default, '2/8')
95
+
96
+ def _collect_app_path(self) -> str:
97
+ """
98
+ Prompts the user for the app path and validates it.
99
+
100
+ :return: Validated app path in dot notation.
101
+ :raises CommandError: If the app path is empty or invalid.
102
+ """
103
+
104
+ app_path = self._collect_simple_input('Enter the app path (e.g., "app.human_resource.employee.skill")', '1/8')
105
+
106
+ if not app_path:
107
+ self.reporter.write('\n', self.reporter.style_notice)
108
+
109
+ message = 'The app path is required'
110
+ raise CommandError(message)
111
+
112
+ components = app_path.split('.')
113
+ self.validator.validate_app_path(components)
114
+
115
+ return app_path
116
+
117
+ def _collect_db_table_name(self, app_label: str) -> str:
118
+ """
119
+ Prompts the user for the database table name.
120
+
121
+ :param app_label: App label to use as default.
122
+ :return: User-provided or default database table name.
123
+ """
124
+
125
+ return self._collect_input('Enter the database table name', app_label, '6/8')
126
+
127
+ def _collect_input(self, prompt: str, default: str, step_number: str) -> str:
128
+ """
129
+ Prompts the user for input with a default value.
130
+
131
+ :param prompt: Prompt message to display.
132
+ :param default: Default value if user presses Enter.
133
+ :param step_number: Step number in the wizard (e.g., '1/8').
134
+ :return: User-provided input or default value.
135
+ """
136
+
137
+ self.reporter.write(f'\n[{step_number}]: {prompt} (default: "{default}")', self.reporter.style_notice)
138
+ user_input = input('Press Enter to use default or type a custom value: ').strip()
139
+ return user_input if user_input else default
140
+
141
+ def _collect_model_name(self, app_name: str) -> str:
142
+ """
143
+ Prompts the user for the model class name.
144
+
145
+ :param app_name: App name to derive default from.
146
+ :return: User-provided or default model name in TitleCase.
147
+ """
148
+
149
+ default = ''.join(word.title() for word in app_name.split('_'))
150
+ return self._collect_input('Enter the model name', default, '4/8')
151
+
152
+ def _collect_model_name_plural(self, model_name: str) -> str:
153
+ """
154
+ Prompts the user for the plural form of the model name.
155
+
156
+ :param model_name: Singular model name.
157
+ :return: User-provided or default plural model name.
158
+ """
159
+
160
+ default = model_name + 's'
161
+ return self._collect_input('Enter the model name plural', default, '5/8')
162
+
163
+ def _collect_model_permission_path(self, app_path: str, model_name: str) -> str:
164
+ """
165
+ Prompts the user for the model permission path.
166
+
167
+ :param app_path: Dot-separated app path.
168
+ :param model_name: Model class name.
169
+ :return: User-provided or default model permission path.
170
+ """
171
+
172
+ default = f'{app_path}.models.{model_name}'
173
+ return self._collect_input('Enter the model permission path', default, '7/8')
174
+
175
+ def _collect_permission_inheritance(self, components: list[str]) -> dict[str, str]:
176
+ """
177
+ Collects permission inheritance configuration if applicable.
178
+
179
+ Prompts the user about inheriting permissions from parent apps
180
+ and collects necessary parent model information.
181
+
182
+ :param components: List of app path components.
183
+ :return: Dictionary containing permission inheritance settings.
184
+ """
185
+
186
+ if len(components) <= 2:
187
+ return {'inherit_permissions': False, 'parent_permission_prefix': '', 'parent_model_instance_name': ''}
188
+
189
+ if not self._should_inherit_permissions():
190
+ return {'inherit_permissions': False, 'parent_permission_prefix': '', 'parent_model_instance_name': ''}
191
+
192
+ parent_parts = components[1:-1]
193
+ parent_name = components[-2]
194
+ parent_model_class = ''.join(word.title() for word in parent_name.split('_'))
195
+
196
+ self.reporter.write('\n[Permission Inheritance Configuration]\n', self.reporter.style_notice)
197
+
198
+ return {
199
+ 'inherit_permissions': True,
200
+ 'parent_permission_prefix': self._collect_input(
201
+ 'Enter the parent permission prefix',
202
+ '_'.join(parent_parts).lower(),
203
+ '1/3'
204
+ ),
205
+ 'parent_model_instance_name': self._collect_input(
206
+ 'Enter the parent model instance name',
207
+ parent_name.lower(),
208
+ '2/3'
209
+ ),
210
+ 'parent_model_path': self._collect_input(
211
+ 'Enter the parent model path',
212
+ '.'.join(components[:-1]) + f'.models.{parent_model_class}',
213
+ '3/3'
214
+ ),
215
+ }
216
+
217
+ def _collect_simple_input(self, prompt: str, step_number: str) -> str:
218
+ """
219
+ Prompts the user for simple input without a default value.
220
+
221
+ :param prompt: Prompt message to display.
222
+ :param step_number: Step number in the wizard.
223
+ :return: User-provided input.
224
+ """
225
+
226
+ return input(f'[{step_number}]: {prompt}: ').strip()
227
+
228
+ def _derive_verbose_names(self, model_name: str, model_name_plural: str) -> tuple[str, str]:
229
+ """
230
+ Derives human-readable verbose names from model names.
231
+
232
+ Converts CamelCase model names to space-separated words.
233
+
234
+ :param model_name: Singular model name in CamelCase.
235
+ :param model_name_plural: Plural model name in CamelCase.
236
+ :return: Tuple of (verbose_name, verbose_name_plural).
237
+ """
238
+
239
+ verbose_name = re.sub(r'(?<!^)(?=[A-Z])', ' ', model_name)
240
+ verbose_name_plural = re.sub(r'(?<!^)(?=[A-Z])', ' ', model_name_plural)
241
+ return verbose_name, verbose_name_plural
242
+
243
+ def _should_inherit_permissions(self) -> bool:
244
+ """
245
+ Prompts the user to confirm permission inheritance.
246
+
247
+ :return: True if user wants to inherit permissions, False otherwise.
248
+ """
249
+
250
+ self.reporter.write('\n[8/8]: Do you want this app to inherit permissions from its parent? (y/n)', self.reporter.style_notice)
251
+ user_input = input('Default is "n": ').strip().lower()
252
+ return user_input == 'y'
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django.core.management.base import CommandError
6
+
7
+ if TYPE_CHECKING:
8
+ from django_spire.core.management.commands.spire_startapp_pkg.filesystem import FileSystemInterface
9
+ from django_spire.core.management.commands.spire_startapp_pkg.registry import AppRegistryInterface
10
+ from django_spire.core.management.commands.spire_startapp_pkg.reporter import ReporterInterface
11
+ from django_spire.core.management.commands.spire_startapp_pkg.resolver import PathResolverInterface
12
+
13
+
14
+ class AppValidator:
15
+ """
16
+ Validates Django app paths and configurations.
17
+
18
+ This class performs validation checks to ensure app paths are properly
19
+ formatted, don't conflict with existing apps, and use valid root apps.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ reporter: ReporterInterface,
25
+ registry: AppRegistryInterface,
26
+ path_resolver: PathResolverInterface,
27
+ filesystem: FileSystemInterface
28
+ ):
29
+ """
30
+ Initializes the validator with required dependencies.
31
+
32
+ :param reporter: Reporter for displaying error messages.
33
+ :param registry: Registry for checking installed apps.
34
+ :param path_resolver: Path resolver for determining file locations.
35
+ :param filesystem: File system interface for checking file existence.
36
+ """
37
+
38
+ self._filesystem = filesystem
39
+ self._path_resolver = path_resolver
40
+ self._registry = registry
41
+ self._reporter = reporter
42
+
43
+ def validate_app_format(self, app_path: str) -> None:
44
+ """
45
+ Validates that an app path uses dot notation.
46
+
47
+ :param app_path: App path to validate.
48
+ :raises CommandError: If the app path doesn't contain dots.
49
+ """
50
+
51
+ if '.' not in app_path:
52
+ self._reporter.write('\n', self._reporter.style_notice)
53
+
54
+ message = 'Invalid app name format. The app path must use dot notation (e.g., "parent.child").'
55
+ raise CommandError(message)
56
+
57
+ def validate_app_path(self, components: list[str]) -> None:
58
+ """
59
+ Validates that an app path doesn't already exist.
60
+
61
+ :param components: List of app path components.
62
+ :raises CommandError: If an app already exists at the destination path.
63
+ """
64
+
65
+ destination = self._path_resolver.get_app_destination(components)
66
+
67
+ if self._filesystem.has_content(destination):
68
+ self._reporter.write('\n', self._reporter.style_notice)
69
+
70
+ message = (
71
+ f'The app already exists at {destination}. '
72
+ 'Please remove the existing app or choose a different name.'
73
+ )
74
+
75
+ raise CommandError(message)
76
+
77
+ def validate_root_app(self, components: list[str]) -> None:
78
+ """
79
+ Validates that the root app component is registered in Django.
80
+
81
+ :param components: List of app path components.
82
+ :raises CommandError: If the root app is not a valid registered app.
83
+ """
84
+
85
+ valid_roots = self._registry.get_valid_root_apps()
86
+ root = components[0]
87
+
88
+ if root not in valid_roots:
89
+ self._reporter.write('\n', self._reporter.style_notice)
90
+
91
+ message = (
92
+ f'Invalid root app "{root}". '
93
+ f'Valid root apps: {", ".join(sorted(valid_roots))}.'
94
+ )
95
+
96
+ raise CommandError(message)
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django_spire.core.middleware.maintenance import MaintenanceMiddleware
4
- from django_spire.core.middleware.profiling import ProfilingMiddleware
5
4
 
6
5
 
7
- __all__ = ['MaintenanceMiddleware', 'ProfilingMiddleware']
6
+ __all__ = ['MaintenanceMiddleware']
@@ -0,0 +1,13 @@
1
+ import threading
2
+
3
+ lock = threading.Lock()
4
+
5
+ from django_spire.profiling.middleware.profiling import ProfilingMiddleware
6
+ from django_spire.profiling.panel import ProfilingPanel
7
+
8
+
9
+ __all__ = [
10
+ 'ProfilingMiddleware',
11
+ 'ProfilingPanel',
12
+ 'lock'
13
+ ]
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.profiling.middleware.profiling import ProfilingMiddleware
4
+
5
+
6
+ __all__ = ['ProfilingMiddleware']
@@ -5,20 +5,42 @@ import threading
5
5
  import time
6
6
 
7
7
  from pathlib import Path
8
- from typing_extensions import Any, Callable, TYPE_CHECKING
8
+ from typing_extensions import TYPE_CHECKING
9
9
 
10
10
  from django.conf import settings
11
11
  from django.utils.deprecation import MiddlewareMixin
12
12
 
13
+ from django_spire.profiling import lock
14
+
13
15
  try:
14
16
  from pyinstrument import Profiler
15
17
  except ImportError:
16
18
  Profiler = None
17
19
 
18
20
  if TYPE_CHECKING:
21
+ from typing_extensions import Any, Callable
22
+
19
23
  from django.http import HttpRequest, HttpResponse
20
24
 
21
25
 
26
+ IGNORE_EXTENSION = [
27
+ '.eot',
28
+ '.gif',
29
+ '.ico',
30
+ '.jpeg',
31
+ '.jpg',
32
+ '.js',
33
+ '.map',
34
+ '.pdf',
35
+ '.png',
36
+ '.svg',
37
+ '.ttf',
38
+ '.txt',
39
+ '.woff',
40
+ '.woff2',
41
+ '.zip',
42
+ ]
43
+
22
44
  IGNORE_PATH = [
23
45
  '/__',
24
46
  '/__debug__/',
@@ -36,12 +58,13 @@ IGNORE_PATH = [
36
58
  '/debug/',
37
59
  '/debug-toolbar/',
38
60
  '/django_glue/',
61
+ '/django_spire/theme/json/get_config/',
39
62
  '/docs/',
40
63
  '/favicon.ico',
41
64
  '/media/',
42
65
  '/openapi/',
43
66
  '/redoc/',
44
- '/robots.txt',
67
+ '/robots.txt/',
45
68
  '/schema/',
46
69
  '/sitemap.xml',
47
70
  '/static/',
@@ -50,37 +73,15 @@ IGNORE_PATH = [
50
73
  ]
51
74
 
52
75
 
53
- IGNORE_EXTENSION = [
54
- '.css',
55
- '.eot',
56
- '.gif',
57
- '.ico',
58
- '.jpeg',
59
- '.jpg',
60
- '.js',
61
- '.map',
62
- '.pdf',
63
- '.png',
64
- '.svg',
65
- '.ttf',
66
- '.txt',
67
- '.woff',
68
- '.woff2',
69
- '.zip',
70
- ]
71
-
72
-
73
76
  class ProfilingMiddleware(MiddlewareMixin):
74
77
  def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None:
75
78
  super().__init__(get_response)
76
79
 
77
- if Profiler is None:
78
- message = 'pyinstrument is required for profiling.'
79
- raise ImportError(message)
80
-
81
80
  configuration = {
82
81
  'PROFILING_DIR': os.getenv('PROFILING_DIR', '.profile'),
83
82
  'PROFILING_ENABLED': os.getenv('PROFILING_ENABLED', 'False') == 'True',
83
+ 'PROFILING_MAX_FILES': int(os.getenv('PROFILING_MAX_FILES', '10')),
84
+ 'PROFILE_THRESHOLD': float(os.getenv('PROFILE_THRESHOLD', '0')),
84
85
  }
85
86
 
86
87
  directory = configuration.get('PROFILING_DIR', '.profile')
@@ -97,32 +98,28 @@ class ProfilingMiddleware(MiddlewareMixin):
97
98
  self.directory.mkdir(exist_ok=True)
98
99
 
99
100
  self.enabled = configuration.get('PROFILING_ENABLED', False)
100
- self.profile_threshold = configuration.get('PROFILE_THRESHOLD', 0)
101
+ self.threshold = configuration.get('PROFILE_THRESHOLD', 0)
102
+ self.maximum = configuration.get('PROFILING_MAX_FILES', 10)
101
103
 
102
104
  self.count = 0
103
105
  self.lock = threading.Lock()
104
106
 
105
- def _remove_profile(self) -> None:
106
- files = self.directory.glob('*.html')
107
- profiles = list(files)
107
+ def _remove_profiles(self) -> None:
108
+ files = list(self.directory.glob('*.html'))
109
+
110
+ if len(files) <= self.maximum:
111
+ return
108
112
 
109
- profiles.sort(
110
- key=lambda p: p.stat().st_mtime,
113
+ files.sort(
114
+ key=lambda p: p.stat().st_mtime if p.exists() else 0,
111
115
  reverse=True
112
116
  )
113
117
 
114
- maximum = 10
115
-
116
- if len(profiles) > maximum:
117
- for profile in profiles[maximum:]:
118
+ for profile in files[self.maximum:]:
119
+ if profile.exists():
118
120
  profile.unlink()
119
121
 
120
- def _save_profile(
121
- self,
122
- profiler: Profiler,
123
- request: HttpRequest,
124
- duration_ms: float
125
- ) -> None:
122
+ def _save_profile(self, profiler: Profiler, request: HttpRequest, duration: float) -> None:
126
123
  with self.lock:
127
124
  timestamp = int(time.time() * 1000)
128
125
  method = request.method
@@ -131,21 +128,29 @@ class ProfilingMiddleware(MiddlewareMixin):
131
128
  if not path or path == '_':
132
129
  path = 'root'
133
130
 
134
- filename = f'{timestamp}_{method}_{path}_{duration_ms:.1f}ms_{request._profiling_id}.html'
135
-
136
- path = str(self.directory / filename)
137
- profiler.write_html(path)
131
+ profileid = request._profiling_id
132
+ filename = f'{timestamp}_{method}_{path}_{duration:.1f}ms_{profileid}.html'
133
+ filepath = self.directory / filename
138
134
 
139
- self._remove_profile()
135
+ with lock:
136
+ profiler.write_html(str(filepath))
137
+ self._remove_profiles()
140
138
 
141
- def _should_skip_profiling(self, request: HttpRequest) -> bool:
139
+ def _should_skip(self, request: HttpRequest) -> bool:
142
140
  path = request.path
143
141
 
144
- return (
145
- any(path.startswith(pattern) or pattern in path for pattern in IGNORE_PATH) or
146
- any(path.endswith(extension) or extension in path for extension in IGNORE_EXTENSION)
142
+ path_match = any(
143
+ path.startswith(pattern) or pattern in path
144
+ for pattern in IGNORE_PATH
147
145
  )
148
146
 
147
+ extension_match = any(
148
+ path.endswith(extension) or extension in path
149
+ for extension in IGNORE_EXTENSION
150
+ )
151
+
152
+ return path_match or extension_match
153
+
149
154
  def process_view(
150
155
  self,
151
156
  request: HttpRequest,
@@ -159,7 +164,7 @@ class ProfilingMiddleware(MiddlewareMixin):
159
164
  if not self.enabled:
160
165
  return None
161
166
 
162
- if self._should_skip_profiling(request):
167
+ if self._should_skip(request):
163
168
  return None
164
169
 
165
170
  with self.lock:
@@ -167,7 +172,7 @@ class ProfilingMiddleware(MiddlewareMixin):
167
172
  request._profiling_id = self.count
168
173
 
169
174
  profiler = Profiler(interval=0.001)
170
- start_time = time.time()
175
+ start = time.time()
171
176
  profiler.start()
172
177
 
173
178
  try:
@@ -177,15 +182,15 @@ class ProfilingMiddleware(MiddlewareMixin):
177
182
  response.render()
178
183
  except Exception:
179
184
  profiler.stop()
180
- duration_ms = (time.time() - start_time) * 1000
181
- self._save_profile(profiler, request, duration_ms)
185
+ duration = (time.time() - start) * 1000
186
+ self._save_profile(profiler, request, duration)
182
187
 
183
188
  raise
184
189
  else:
185
190
  profiler.stop()
186
- duration_ms = (time.time() - start_time) * 1000
191
+ duration = (time.time() - start) * 1000
187
192
 
188
- if duration_ms >= self.profile_threshold:
189
- self._save_profile(profiler, request, duration_ms)
193
+ if duration >= self.threshold:
194
+ self._save_profile(profiler, request, duration)
190
195
 
191
196
  return response