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.
- django_spire/consts.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/template/app/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +15 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template +18 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/bots.py.template +18 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/intel.py.template +7 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/prompts.py.template +32 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/migrations/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template +52 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template +20 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seed.py.template +6 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seeder.py.template +26 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/services/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/services/factory_service.py.template +12 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/services/intelligence_service.py.template +12 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/services/processor_service.py.template +12 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/services/service.py.template +22 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/services/transformation_service.py.template +12 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/test_bots.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_models.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_factory_service.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_intelligence_service.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_processor_service.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_service.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_transformation_service.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_form_urls.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_page_urls.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_form_views.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_page_views.py.template +8 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +9 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +15 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +11 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/views/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template +134 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template +44 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/{spirechildapp_detail_card.html → ${detail_card_template_name}.html.template} +7 -7
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/{spirechildapp_form_card.html → ${form_card_template_name}.html.template} +2 -2
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template +18 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/{spirechildapp_form.html → ${form_template_name}.html.template} +4 -4
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${item_template_name}.html.template +24 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_detail_page.html → ${detail_page_template_name}.html.template} +3 -3
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_form_page.html → ${form_page_template_name}.html.template} +1 -1
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_list_page.html → ${list_page_template_name}.html.template} +2 -2
- 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/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.13.dist-info → django_spire-0.17.1.dist-info}/METADATA +1 -1
- {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/RECORD +75 -23
- 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_list_card.html +0 -18
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
- {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/WHEEL +0 -0
- {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/top_level.txt +0 -0
django_spire/consts.py
CHANGED
|
@@ -1,80 +1,118 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from django.core.management.base import BaseCommand
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
from django_spire.core.management.commands.spire_startapp_pkg.builder import TemplateBuilder
|
|
8
|
+
from django_spire.core.management.commands.spire_startapp_pkg.config import (
|
|
9
|
+
AppConfigFactory,
|
|
10
|
+
PathConfig,
|
|
11
|
+
)
|
|
12
|
+
from django_spire.core.management.commands.spire_startapp_pkg.filesystem import FileSystem
|
|
13
|
+
from django_spire.core.management.commands.spire_startapp_pkg.generator import (
|
|
14
|
+
AppGenerator,
|
|
15
|
+
# TemplateGenerator,
|
|
13
16
|
)
|
|
14
17
|
from django_spire.core.management.commands.spire_startapp_pkg.processor import (
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
TemplateEngine,
|
|
19
|
+
TemplateProcessor,
|
|
17
20
|
)
|
|
21
|
+
from django_spire.core.management.commands.spire_startapp_pkg.registry import AppRegistry
|
|
18
22
|
from django_spire.core.management.commands.spire_startapp_pkg.reporter import Reporter
|
|
23
|
+
from django_spire.core.management.commands.spire_startapp_pkg.resolver import PathResolver
|
|
24
|
+
from django_spire.core.management.commands.spire_startapp_pkg.user_input import UserInputCollector
|
|
25
|
+
from django_spire.core.management.commands.spire_startapp_pkg.validator import AppValidator
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from typing import Any
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
class Command(BaseCommand):
|
|
32
|
+
"""
|
|
33
|
+
Django management command for creating custom Spire apps.
|
|
34
|
+
|
|
35
|
+
This command guides users through an interactive wizard to create a new Django app
|
|
36
|
+
with a pre-configured structure including models, views, forms, services, and templates.
|
|
37
|
+
It handles nested app structures (e.g., app.parent.child) and validates that all
|
|
38
|
+
parent apps are properly registered before creating child apps.
|
|
39
|
+
"""
|
|
40
|
+
|
|
22
41
|
help = 'Create a custom Spire app.'
|
|
23
42
|
|
|
24
|
-
def
|
|
25
|
-
|
|
43
|
+
def handle(self, *args: Any, **kwargs: Any) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Main entry point for the management command.
|
|
46
|
+
|
|
47
|
+
Orchestrates the entire app creation process by initializing helper classes,
|
|
48
|
+
collecting user input through an interactive wizard, validating the app structure,
|
|
49
|
+
generating app files and templates, and providing feedback to the user.
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
|
|
51
|
+
:param args: Positional arguments (not used).
|
|
52
|
+
:param kwargs: Keyword arguments (not used).
|
|
53
|
+
"""
|
|
29
54
|
|
|
30
|
-
|
|
31
|
-
|
|
55
|
+
filesystem = FileSystem()
|
|
56
|
+
path_config = PathConfig.default()
|
|
57
|
+
path_resolver = PathResolver()
|
|
58
|
+
registry = AppRegistry()
|
|
59
|
+
reporter = Reporter(self)
|
|
32
60
|
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
template_engine = TemplateEngine()
|
|
62
|
+
template_processor = TemplateProcessor(template_engine, filesystem)
|
|
35
63
|
|
|
36
|
-
|
|
37
|
-
self.html_processor = HTMLTemplateProcessor()
|
|
64
|
+
validator = AppValidator(reporter, registry, path_resolver, filesystem)
|
|
38
65
|
|
|
39
|
-
|
|
66
|
+
user_input_collector = UserInputCollector(reporter, validator)
|
|
40
67
|
|
|
41
|
-
|
|
42
|
-
from django.apps import apps
|
|
43
|
-
return [config.name for config in apps.get_app_configs()]
|
|
68
|
+
config_factory = AppConfigFactory(path_resolver)
|
|
44
69
|
|
|
45
|
-
|
|
46
|
-
app = input('Enter the path of the app (e.g., "app.maintenance.work_order"):')
|
|
70
|
+
template_builder = TemplateBuilder(reporter)
|
|
47
71
|
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
app_generator = AppGenerator(filesystem, template_processor, reporter, path_config)
|
|
73
|
+
# template_generator = TemplateGenerator(filesystem, template_processor, reporter, path_config)
|
|
50
74
|
|
|
51
|
-
|
|
75
|
+
user_inputs = user_input_collector.collect_all_inputs()
|
|
76
|
+
app_path = user_inputs['app_path']
|
|
52
77
|
|
|
53
|
-
|
|
54
|
-
self.app_manager.is_valid_root_apps(components)
|
|
78
|
+
validator.validate_app_format(app_path)
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
config = config_factory.create_config(app_path, user_inputs)
|
|
81
|
+
validator.validate_root_app(config.components)
|
|
57
82
|
|
|
58
|
-
|
|
59
|
-
f'
|
|
60
|
-
|
|
83
|
+
reporter.write(
|
|
84
|
+
f'\nChecking app components: {config.components}\n',
|
|
85
|
+
reporter.style_notice
|
|
61
86
|
)
|
|
62
87
|
|
|
63
|
-
missing =
|
|
88
|
+
missing = registry.get_missing_components(config.components)
|
|
64
89
|
|
|
65
90
|
if missing:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
reporter.report_missing_components(missing)
|
|
92
|
+
|
|
93
|
+
template_builder.build_app_tree_structure(
|
|
94
|
+
path_resolver.get_base_dir(),
|
|
95
|
+
config.components,
|
|
96
|
+
registry.get_installed_apps(),
|
|
97
|
+
path_config.app_template
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# template_builder.build_html_tree_structure(
|
|
101
|
+
# path_resolver.get_base_dir(),
|
|
102
|
+
# config.components,
|
|
103
|
+
# registry.get_installed_apps(),
|
|
104
|
+
# path_config.html_template
|
|
105
|
+
# )
|
|
106
|
+
|
|
107
|
+
if not reporter.prompt_confirmation('\nProceed with app creation? (y/n): '):
|
|
108
|
+
reporter.write('App creation aborted.', reporter.style_error)
|
|
72
109
|
return
|
|
73
110
|
|
|
74
111
|
for module in [missing[-1]]:
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
module_config = config_factory.create_config(module, user_inputs)
|
|
113
|
+
app_generator.generate(module_config)
|
|
114
|
+
# template_generator.generate(module_config)
|
|
77
115
|
|
|
78
|
-
|
|
116
|
+
reporter.report_installed_apps_suggestion(missing)
|
|
79
117
|
else:
|
|
80
|
-
|
|
118
|
+
reporter.write('All component(s) exist.', reporter.style_success)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.management.commands.spire_startapp_pkg.builder import TemplateBuilder
|
|
4
|
+
from django_spire.core.management.commands.spire_startapp_pkg.config import (
|
|
5
|
+
AppConfig,
|
|
6
|
+
AppConfigFactory,
|
|
7
|
+
PathConfig,
|
|
8
|
+
)
|
|
9
|
+
from django_spire.core.management.commands.spire_startapp_pkg.filesystem import (
|
|
10
|
+
FileSystem,
|
|
11
|
+
FileSystemInterface,
|
|
12
|
+
)
|
|
13
|
+
from django_spire.core.management.commands.spire_startapp_pkg.generator import (
|
|
14
|
+
AppGenerator,
|
|
15
|
+
TemplateGenerator,
|
|
16
|
+
)
|
|
17
|
+
from django_spire.core.management.commands.spire_startapp_pkg.maps import generate_replacement_map
|
|
18
|
+
from django_spire.core.management.commands.spire_startapp_pkg.permissions import PermissionInheritanceHandler
|
|
19
|
+
from django_spire.core.management.commands.spire_startapp_pkg.processor import (
|
|
20
|
+
TemplateEngine,
|
|
21
|
+
TemplateProcessor,
|
|
22
|
+
)
|
|
23
|
+
from django_spire.core.management.commands.spire_startapp_pkg.registry import (
|
|
24
|
+
AppRegistry,
|
|
25
|
+
AppRegistryInterface,
|
|
26
|
+
)
|
|
27
|
+
from django_spire.core.management.commands.spire_startapp_pkg.reporter import (
|
|
28
|
+
Reporter,
|
|
29
|
+
ReporterInterface,
|
|
30
|
+
)
|
|
31
|
+
from django_spire.core.management.commands.spire_startapp_pkg.resolver import (
|
|
32
|
+
PathResolver,
|
|
33
|
+
PathResolverInterface,
|
|
34
|
+
)
|
|
35
|
+
from django_spire.core.management.commands.spire_startapp_pkg.user_input import UserInputCollector
|
|
36
|
+
from django_spire.core.management.commands.spire_startapp_pkg.validator import AppValidator
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
'AppConfig',
|
|
41
|
+
'AppConfigFactory',
|
|
42
|
+
'AppGenerator',
|
|
43
|
+
'AppRegistry',
|
|
44
|
+
'AppRegistryInterface',
|
|
45
|
+
'AppValidator',
|
|
46
|
+
'FileSystem',
|
|
47
|
+
'FileSystemInterface',
|
|
48
|
+
'PathConfig',
|
|
49
|
+
'PathResolver',
|
|
50
|
+
'PathResolverInterface',
|
|
51
|
+
'PermissionInheritanceHandler',
|
|
52
|
+
'Reporter',
|
|
53
|
+
'ReporterInterface',
|
|
54
|
+
'TemplateBuilder',
|
|
55
|
+
'TemplateEngine',
|
|
56
|
+
'TemplateGenerator',
|
|
57
|
+
'TemplateProcessor',
|
|
58
|
+
'UserInputCollector',
|
|
59
|
+
'generate_replacement_map',
|
|
60
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from django_spire.core.management.commands.spire_startapp_pkg.maps import generate_replacement_map
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from django_spire.core.management.commands.spire_startapp_pkg.reporter import ReporterInterface
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TemplateBuilder:
|
|
14
|
+
"""
|
|
15
|
+
Builds and displays tree structures for app and template creation.
|
|
16
|
+
|
|
17
|
+
This class generates visual representations of the file structure
|
|
18
|
+
that will be created for new apps and their associated templates.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, reporter: ReporterInterface):
|
|
22
|
+
"""
|
|
23
|
+
Initializes the TemplateBuilder with a reporter for output.
|
|
24
|
+
|
|
25
|
+
:param reporter: Reporter instance for displaying output to the user.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
self._reporter = reporter
|
|
29
|
+
|
|
30
|
+
def build_app_tree_structure(
|
|
31
|
+
self,
|
|
32
|
+
base: Path,
|
|
33
|
+
components: list[str],
|
|
34
|
+
registry: list[str],
|
|
35
|
+
template: Path
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Displays a tree structure of the app files that will be created.
|
|
39
|
+
|
|
40
|
+
This method shows the user what Python files, directories, and modules
|
|
41
|
+
will be generated for the new Django app before creation.
|
|
42
|
+
|
|
43
|
+
:param base: Base directory where the app will be created.
|
|
44
|
+
:param components: List of app path components (e.g., ['app', 'human_resource', 'employee']).
|
|
45
|
+
:param registry: List of already registered apps in the Django project.
|
|
46
|
+
:param template: Path to the app template directory.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
self._reporter.report_tree_structure(
|
|
50
|
+
title='\nThe following app(s) will be created:\n\n',
|
|
51
|
+
base=base,
|
|
52
|
+
components=components,
|
|
53
|
+
registry=registry,
|
|
54
|
+
template=template,
|
|
55
|
+
formatter=self._reporter.format_app_item,
|
|
56
|
+
transformation=self._reporter.transform_app_component,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def build_html_tree_structure(
|
|
60
|
+
self,
|
|
61
|
+
base: Path,
|
|
62
|
+
components: list[str],
|
|
63
|
+
registry: list[str],
|
|
64
|
+
template: Path
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Displays a tree structure of the HTML template files that will be created.
|
|
68
|
+
|
|
69
|
+
This method shows the user what HTML templates, cards, forms, and pages
|
|
70
|
+
will be generated for the new Django app before creation.
|
|
71
|
+
|
|
72
|
+
:param base: Base directory where templates will be created.
|
|
73
|
+
:param components: List of app path components.
|
|
74
|
+
:param registry: List of already registered apps in the Django project.
|
|
75
|
+
:param template: Path to the HTML template directory.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
replacement = generate_replacement_map(components)
|
|
79
|
+
|
|
80
|
+
def html_formatter_with_replacement(item: Path) -> str:
|
|
81
|
+
return self._reporter.format_html_item(item, replacement)
|
|
82
|
+
|
|
83
|
+
self._reporter.report_tree_structure(
|
|
84
|
+
title='\nThe following template(s) will be created:\n\n',
|
|
85
|
+
base=base,
|
|
86
|
+
components=components,
|
|
87
|
+
registry=registry,
|
|
88
|
+
template=template,
|
|
89
|
+
formatter=html_formatter_with_replacement,
|
|
90
|
+
transformation=self._reporter.transform_html_component,
|
|
91
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import django_spire
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from django_spire.core.management.commands.spire_startapp_pkg.resolver import PathResolverInterface
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class PathConfig:
|
|
15
|
+
"""
|
|
16
|
+
Configuration for template directory paths.
|
|
17
|
+
|
|
18
|
+
This class holds the paths to the app and HTML template directories
|
|
19
|
+
used for generating new Django apps.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
app_template: Path
|
|
23
|
+
html_template: Path
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def default(cls) -> PathConfig:
|
|
27
|
+
"""
|
|
28
|
+
Creates a default PathConfig with standard template locations.
|
|
29
|
+
|
|
30
|
+
:return: PathConfig instance with paths to default app and HTML templates.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
base = Path(django_spire.__file__).parent
|
|
34
|
+
|
|
35
|
+
return cls(
|
|
36
|
+
app_template=base / 'core/management/commands/spire_startapp_pkg/template/app',
|
|
37
|
+
html_template=base / 'core/management/commands/spire_startapp_pkg/template/templates'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class AppConfig:
|
|
43
|
+
"""
|
|
44
|
+
Configuration for a new Django app being created.
|
|
45
|
+
|
|
46
|
+
This class contains all the information needed to generate a new app,
|
|
47
|
+
including its name, path, components, destinations, and user-provided inputs.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
app_name: str
|
|
51
|
+
app_path: str
|
|
52
|
+
components: list[str]
|
|
53
|
+
destination: Path
|
|
54
|
+
template_destination: Path
|
|
55
|
+
user_inputs: dict[str, str]
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def app_label(self) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Gets the Django app label.
|
|
61
|
+
|
|
62
|
+
:return: The app label, either from user input or derived from app name.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
return self.user_inputs.get('app_label', self.app_name.lower())
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def model_name(self) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Gets the model class name.
|
|
71
|
+
|
|
72
|
+
:return: The model name, either from user input or TitleCase version of app name.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
default = ''.join(word.title() for word in self.app_name.split('_'))
|
|
76
|
+
return self.user_inputs.get('model_name', default)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AppConfigFactory:
|
|
80
|
+
"""
|
|
81
|
+
Factory class for creating AppConfig instances.
|
|
82
|
+
|
|
83
|
+
This class handles the creation of AppConfig objects by resolving
|
|
84
|
+
paths and processing user inputs.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, path_resolver: PathResolverInterface):
|
|
88
|
+
"""
|
|
89
|
+
Initializes the factory with a path resolver.
|
|
90
|
+
|
|
91
|
+
:param path_resolver: Path resolver for determining file system locations.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
self._path_resolver = path_resolver
|
|
95
|
+
|
|
96
|
+
def create_config(self, app_path: str, user_inputs: dict[str, str]) -> AppConfig:
|
|
97
|
+
"""
|
|
98
|
+
Creates an AppConfig from an app path and user inputs.
|
|
99
|
+
|
|
100
|
+
:param app_path: Dot-separated app path (e.g., 'app.human_resource.employee').
|
|
101
|
+
:param user_inputs: Dictionary of user-provided configuration values.
|
|
102
|
+
:return: Configured AppConfig instance ready for app generation.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
components = app_path.split('.')
|
|
106
|
+
app_name = user_inputs.get('app_name', components[-1])
|
|
107
|
+
|
|
108
|
+
return AppConfig(
|
|
109
|
+
app_name=app_name,
|
|
110
|
+
app_path=app_path,
|
|
111
|
+
components=components,
|
|
112
|
+
destination=self._path_resolver.get_app_destination(components),
|
|
113
|
+
template_destination=self._path_resolver.get_template_destination(components),
|
|
114
|
+
user_inputs=user_inputs
|
|
115
|
+
)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Iterator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileSystemInterface(Protocol):
|
|
13
|
+
"""
|
|
14
|
+
Protocol defining the interface for file system operations.
|
|
15
|
+
|
|
16
|
+
This protocol specifies the methods required for interacting with
|
|
17
|
+
the file system during app creation.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def copy_tree(self, src: Path, dst: Path) -> None: ...
|
|
21
|
+
def create_directory(self, path: Path) -> None: ...
|
|
22
|
+
def exists(self, path: Path) -> bool: ...
|
|
23
|
+
def has_content(self, path: Path) -> bool: ...
|
|
24
|
+
def iterate_files(self, path: Path, pattern: str) -> Iterator[Path]: ...
|
|
25
|
+
def read_file(self, path: Path) -> str: ...
|
|
26
|
+
def rename(self, old: Path, new: Path) -> None: ...
|
|
27
|
+
def write_file(self, path: Path, content: str) -> None: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FileSystem:
|
|
31
|
+
"""
|
|
32
|
+
Implementation of file system operations for app generation.
|
|
33
|
+
|
|
34
|
+
This class provides concrete implementations of file system operations
|
|
35
|
+
needed to create directories, copy templates, and manage files.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def copy_tree(self, src: Path, dst: Path) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Copies an entire directory tree from source to destination.
|
|
41
|
+
|
|
42
|
+
Ignores Python cache directories and compiled files during the copy.
|
|
43
|
+
|
|
44
|
+
:param src: Source directory path to copy from.
|
|
45
|
+
:param dst: Destination directory path to copy to.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
shutil.copytree(
|
|
49
|
+
src,
|
|
50
|
+
dst,
|
|
51
|
+
dirs_exist_ok=True,
|
|
52
|
+
ignore=shutil.ignore_patterns('__pycache__', '*.pyc')
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def create_directory(self, path: Path) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Creates a directory and all necessary parent directories.
|
|
58
|
+
|
|
59
|
+
:param path: Directory path to create.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
def exists(self, path: Path) -> bool:
|
|
65
|
+
"""
|
|
66
|
+
Checks if a path exists on the file system.
|
|
67
|
+
|
|
68
|
+
:param path: Path to check for existence.
|
|
69
|
+
:return: True if the path exists, False otherwise.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
return path.exists()
|
|
73
|
+
|
|
74
|
+
def has_content(self, path: Path) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Checks if a directory exists and contains any files or subdirectories.
|
|
77
|
+
|
|
78
|
+
:param path: Directory path to check.
|
|
79
|
+
:return: True if the directory exists and has content, False otherwise.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
return self.exists(path) and any(path.iterdir())
|
|
83
|
+
|
|
84
|
+
def iterate_files(self, path: Path, pattern: str) -> Iterator[Path]:
|
|
85
|
+
"""
|
|
86
|
+
Recursively finds all files matching a pattern in a directory.
|
|
87
|
+
|
|
88
|
+
:param path: Directory path to search within.
|
|
89
|
+
:param pattern: Glob pattern to match files (e.g., '*.py', '*.template').
|
|
90
|
+
:return: Iterator of matching file paths.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
return path.rglob(pattern)
|
|
94
|
+
|
|
95
|
+
def read_file(self, path: Path) -> str:
|
|
96
|
+
"""
|
|
97
|
+
Reads the entire contents of a text file.
|
|
98
|
+
|
|
99
|
+
:param path: File path to read.
|
|
100
|
+
:return: File contents as a string.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
with open(path, 'r', encoding='utf-8') as handle:
|
|
104
|
+
return handle.read()
|
|
105
|
+
|
|
106
|
+
def rename(self, old: Path, new: Path) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Renames a file or directory.
|
|
109
|
+
|
|
110
|
+
:param old: Current path of the file or directory.
|
|
111
|
+
:param new: New path for the file or directory.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
old.rename(new)
|
|
115
|
+
|
|
116
|
+
def write_file(self, path: Path, content: str) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Writes content to a text file.
|
|
119
|
+
|
|
120
|
+
:param path: File path to write to.
|
|
121
|
+
:param content: String content to write to the file.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
with open(path, 'w', encoding='utf-8') as handle:
|
|
125
|
+
handle.write(content)
|