django-spire 0.16.12__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.
- django_spire/ai/admin.py +3 -1
- django_spire/ai/apps.py +2 -0
- django_spire/ai/chat/admin.py +15 -9
- django_spire/ai/chat/apps.py +4 -1
- django_spire/ai/chat/auth/controller.py +3 -1
- django_spire/ai/chat/choices.py +2 -0
- django_spire/ai/chat/intelligence/maps/intent_llm_map.py +8 -5
- django_spire/ai/chat/intelligence/prompts.py +4 -2
- django_spire/ai/chat/intelligence/workflows/chat_workflow.py +27 -28
- django_spire/ai/chat/message_intel.py +7 -4
- django_spire/ai/chat/models.py +8 -9
- django_spire/ai/chat/querysets.py +3 -1
- django_spire/ai/chat/responses.py +19 -10
- django_spire/ai/chat/tools.py +20 -15
- django_spire/ai/chat/urls/message_urls.py +2 -1
- django_spire/ai/chat/urls/page_urls.py +1 -0
- django_spire/ai/chat/views/message_request_views.py +2 -0
- django_spire/ai/chat/views/message_response_views.py +4 -4
- django_spire/ai/chat/views/message_views.py +2 -0
- django_spire/ai/chat/views/page_views.py +7 -2
- django_spire/ai/chat/views/template_views.py +2 -0
- django_spire/ai/decorators.py +13 -7
- django_spire/ai/mixins.py +4 -2
- django_spire/ai/models.py +7 -2
- django_spire/ai/prompt/bots.py +14 -32
- django_spire/ai/prompt/intel.py +1 -1
- django_spire/ai/prompt/prompts.py +7 -1
- django_spire/ai/prompt/system/bots.py +42 -75
- django_spire/ai/prompt/system/intel.py +5 -4
- django_spire/ai/prompt/system/prompts.py +5 -1
- django_spire/ai/prompt/system/system_prompt_cli.py +15 -9
- django_spire/ai/prompt/tests/test_bots.py +14 -11
- django_spire/ai/prompt/text_to_prompt_cli.py +5 -2
- django_spire/ai/prompt/tuning/bot_tuning_cli.py +14 -13
- django_spire/ai/prompt/tuning/bots.py +68 -116
- django_spire/ai/prompt/tuning/intel.py +1 -1
- django_spire/ai/prompt/tuning/mixins.py +2 -0
- django_spire/ai/prompt/tuning/prompt_tuning_cli.py +8 -8
- django_spire/ai/prompt/tuning/prompts.py +4 -2
- django_spire/ai/sms/admin.py +3 -1
- django_spire/ai/sms/apps.py +2 -0
- django_spire/ai/sms/decorators.py +2 -0
- django_spire/ai/sms/intel.py +4 -2
- django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +8 -8
- django_spire/ai/sms/models.py +16 -14
- django_spire/ai/sms/querysets.py +4 -1
- django_spire/ai/sms/tools.py +18 -16
- django_spire/ai/sms/urls.py +1 -1
- django_spire/ai/sms/views.py +2 -0
- django_spire/ai/tests/test_ai.py +3 -5
- django_spire/ai/urls.py +1 -0
- django_spire/consts.py +1 -1
- django_spire/contrib/seeding/field/django/seeder.py +6 -4
- django_spire/contrib/seeding/field/llm.py +1 -2
- django_spire/contrib/seeding/intelligence/bots/field_seeding_bots.py +7 -8
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +15 -16
- django_spire/contrib/seeding/intelligence/intel.py +1 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +5 -7
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +3 -5
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +1 -2
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +3 -5
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +1 -1
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +3 -3
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +1 -1
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +2 -4
- django_spire/contrib/seeding/management/commands/seeding.py +5 -2
- django_spire/contrib/seeding/model/base.py +12 -10
- django_spire/contrib/seeding/model/django/seeder.py +13 -10
- django_spire/contrib/seeding/tests/test_seeding.py +1 -1
- django_spire/core/management/commands/spire_startapp.py +84 -46
- django_spire/core/management/commands/spire_startapp_pkg/__init__.py +60 -0
- django_spire/core/management/commands/spire_startapp_pkg/builder.py +91 -0
- django_spire/core/management/commands/spire_startapp_pkg/config.py +115 -0
- django_spire/core/management/commands/spire_startapp_pkg/filesystem.py +125 -0
- django_spire/core/management/commands/spire_startapp_pkg/generator.py +167 -0
- django_spire/core/management/commands/spire_startapp_pkg/maps.py +783 -25
- django_spire/core/management/commands/spire_startapp_pkg/permissions.py +147 -0
- django_spire/core/management/commands/spire_startapp_pkg/processor.py +144 -57
- django_spire/core/management/commands/spire_startapp_pkg/registry.py +89 -0
- django_spire/core/management/commands/spire_startapp_pkg/reporter.py +245 -108
- django_spire/core/management/commands/spire_startapp_pkg/resolver.py +86 -0
- django_spire/core/management/commands/spire_startapp_pkg/user_input.py +252 -0
- django_spire/core/management/commands/spire_startapp_pkg/validator.py +96 -0
- django_spire/core/middleware/__init__.py +1 -2
- django_spire/knowledge/admin.py +2 -0
- django_spire/knowledge/apps.py +2 -0
- django_spire/knowledge/auth/controller.py +2 -0
- django_spire/knowledge/collection/admin.py +3 -0
- django_spire/knowledge/collection/forms.py +2 -0
- django_spire/knowledge/collection/models.py +4 -0
- django_spire/knowledge/collection/querysets.py +3 -3
- django_spire/knowledge/collection/seeding/seed.py +2 -0
- django_spire/knowledge/collection/tests/factories.py +2 -0
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +2 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +2 -0
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +2 -0
- django_spire/knowledge/collection/views/json_views.py +2 -0
- django_spire/knowledge/collection/views/page_views.py +2 -0
- django_spire/knowledge/context_processors.py +2 -0
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +0 -1
- django_spire/knowledge/entry/tests/factories.py +3 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +2 -0
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +2 -0
- django_spire/knowledge/entry/urls/form_urls.py +1 -0
- django_spire/knowledge/entry/urls/json_urls.py +1 -0
- django_spire/knowledge/entry/urls/page_urls.py +1 -0
- django_spire/knowledge/entry/urls/template_urls.py +1 -0
- django_spire/knowledge/entry/version/admin.py +2 -0
- django_spire/knowledge/entry/version/block/admin.py +2 -0
- django_spire/knowledge/entry/version/block/blocks/heading_block.py +2 -0
- django_spire/knowledge/entry/version/block/blocks/sub_heading_block.py +2 -0
- django_spire/knowledge/entry/version/block/blocks/text_block.py +2 -0
- django_spire/knowledge/entry/version/block/maps.py +2 -0
- django_spire/knowledge/entry/version/block/models.py +2 -0
- django_spire/knowledge/entry/version/block/tests/factories.py +2 -0
- django_spire/knowledge/entry/version/block/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/entry/version/block/views/json_views.py +2 -0
- django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +12 -9
- django_spire/knowledge/entry/version/maps.py +2 -0
- django_spire/knowledge/entry/version/models.py +2 -0
- django_spire/knowledge/entry/version/querysets.py +2 -0
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -0
- django_spire/knowledge/entry/version/tests/factories.py +2 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +3 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_form_urls.py +2 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/form_urls.py +1 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +1 -0
- django_spire/knowledge/entry/version/urls/page_urls.py +1 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +1 -0
- django_spire/knowledge/entry/version/views/form_views.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +2 -0
- django_spire/knowledge/entry/version/views/page_views.py +2 -0
- django_spire/knowledge/entry/version/views/redirect_views.py +2 -0
- django_spire/knowledge/exceptions.py +2 -0
- django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +5 -16
- django_spire/knowledge/intelligence/intel/collection_intel.py +3 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +3 -3
- django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
- django_spire/knowledge/intelligence/maps/collection_map.py +9 -10
- django_spire/knowledge/intelligence/maps/entry_map.py +8 -9
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +8 -6
- django_spire/knowledge/models.py +2 -0
- django_spire/knowledge/seeding/seed.py +2 -0
- django_spire/knowledge/templatetags/spire_knowledge_tags.py +3 -0
- django_spire/knowledge/urls/__init__.py +1 -0
- django_spire/profiling/__init__.py +13 -0
- django_spire/profiling/middleware/__init__.py +6 -0
- django_spire/{core → profiling}/middleware/profiling.py +63 -58
- django_spire/profiling/panel.py +345 -0
- django_spire/profiling/templates/panel.html +166 -0
- {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/METADATA +5 -4
- {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/RECORD +160 -157
- django_spire/core/management/commands/spire_startapp_pkg/constants.py +0 -4
- django_spire/core/management/commands/spire_startapp_pkg/manager.py +0 -176
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_detail_card.html +0 -24
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_form_card.html +0 -9
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html +0 -18
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/spirechildapp_form.html +0 -22
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_detail_page.html +0 -13
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_form_page.html +0 -13
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_list_page.html +0 -9
- {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/WHEEL +0 -0
- {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.16.12.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'
|
|
6
|
+
__all__ = ['MaintenanceMiddleware']
|
django_spire/knowledge/admin.py
CHANGED
django_spire/knowledge/apps.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django.db import models
|
|
2
4
|
|
|
3
5
|
from django_spire.auth.group.models import AuthGroup
|
|
@@ -44,10 +46,12 @@ class CollectionGroup(models.Model):
|
|
|
44
46
|
related_name='groups',
|
|
45
47
|
related_query_name='group',
|
|
46
48
|
)
|
|
49
|
+
|
|
47
50
|
auth_group = models.ForeignKey(
|
|
48
51
|
AuthGroup,
|
|
49
52
|
on_delete=models.CASCADE,
|
|
50
53
|
related_name='collection_groups',
|
|
51
54
|
related_query_name='collection_group',
|
|
52
55
|
)
|
|
56
|
+
|
|
53
57
|
services = CollectionGroupService()
|
|
@@ -2,15 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from django.
|
|
6
|
-
from django.db.models import Count, Q
|
|
5
|
+
from django.db.models import Count
|
|
7
6
|
|
|
8
7
|
from django_spire.auth.controller.controller import AppAuthController
|
|
9
8
|
from django_spire.contrib.ordering.querysets import OrderingQuerySetMixin
|
|
10
9
|
from django_spire.history.querysets import HistoryQuerySet
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
|
-
from
|
|
12
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
14
13
|
from django.db.models import QuerySet
|
|
15
14
|
from django_spire.knowledge.collection.models import Collection
|
|
16
15
|
|
|
@@ -57,6 +56,7 @@ class CollectionQuerySet(HistoryQuerySet, OrderingQuerySetMixin):
|
|
|
57
56
|
accessible_ids = set(direct_access.values_list('id', flat=True))
|
|
58
57
|
|
|
59
58
|
current_level_ids = accessible_ids.copy()
|
|
59
|
+
|
|
60
60
|
while current_level_ids:
|
|
61
61
|
next_level = self.filter(parent_id__in=current_level_ids)
|
|
62
62
|
new_ids = set(next_level.values_list('id', flat=True)) - accessible_ids
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django_spire.knowledge.collection.tests.factories import create_test_collection
|
|
2
4
|
from django_spire.knowledge.entry.models import Entry
|
|
3
5
|
|
|
@@ -9,5 +11,6 @@ def create_test_entry(**kwargs) -> Entry:
|
|
|
9
11
|
'is_deleted': False,
|
|
10
12
|
'is_active': True
|
|
11
13
|
}
|
|
14
|
+
|
|
12
15
|
data.update(kwargs)
|
|
13
16
|
return Entry.objects.create(**data)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django_spire.knowledge.entry.version.block.blocks.list_block import ListItemBlock
|
|
2
4
|
from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
|
|
3
5
|
from django_spire.knowledge.entry.version.block.blocks.heading_block import HeadingBlock
|