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.
Files changed (170) hide show
  1. django_spire/ai/admin.py +3 -1
  2. django_spire/ai/apps.py +2 -0
  3. django_spire/ai/chat/admin.py +15 -9
  4. django_spire/ai/chat/apps.py +4 -1
  5. django_spire/ai/chat/auth/controller.py +3 -1
  6. django_spire/ai/chat/choices.py +2 -0
  7. django_spire/ai/chat/intelligence/maps/intent_llm_map.py +8 -5
  8. django_spire/ai/chat/intelligence/prompts.py +4 -2
  9. django_spire/ai/chat/intelligence/workflows/chat_workflow.py +27 -28
  10. django_spire/ai/chat/message_intel.py +7 -4
  11. django_spire/ai/chat/models.py +8 -9
  12. django_spire/ai/chat/querysets.py +3 -1
  13. django_spire/ai/chat/responses.py +19 -10
  14. django_spire/ai/chat/tools.py +20 -15
  15. django_spire/ai/chat/urls/message_urls.py +2 -1
  16. django_spire/ai/chat/urls/page_urls.py +1 -0
  17. django_spire/ai/chat/views/message_request_views.py +2 -0
  18. django_spire/ai/chat/views/message_response_views.py +4 -4
  19. django_spire/ai/chat/views/message_views.py +2 -0
  20. django_spire/ai/chat/views/page_views.py +7 -2
  21. django_spire/ai/chat/views/template_views.py +2 -0
  22. django_spire/ai/decorators.py +13 -7
  23. django_spire/ai/mixins.py +4 -2
  24. django_spire/ai/models.py +7 -2
  25. django_spire/ai/prompt/bots.py +14 -32
  26. django_spire/ai/prompt/intel.py +1 -1
  27. django_spire/ai/prompt/prompts.py +7 -1
  28. django_spire/ai/prompt/system/bots.py +42 -75
  29. django_spire/ai/prompt/system/intel.py +5 -4
  30. django_spire/ai/prompt/system/prompts.py +5 -1
  31. django_spire/ai/prompt/system/system_prompt_cli.py +15 -9
  32. django_spire/ai/prompt/tests/test_bots.py +14 -11
  33. django_spire/ai/prompt/text_to_prompt_cli.py +5 -2
  34. django_spire/ai/prompt/tuning/bot_tuning_cli.py +14 -13
  35. django_spire/ai/prompt/tuning/bots.py +68 -116
  36. django_spire/ai/prompt/tuning/intel.py +1 -1
  37. django_spire/ai/prompt/tuning/mixins.py +2 -0
  38. django_spire/ai/prompt/tuning/prompt_tuning_cli.py +8 -8
  39. django_spire/ai/prompt/tuning/prompts.py +4 -2
  40. django_spire/ai/sms/admin.py +3 -1
  41. django_spire/ai/sms/apps.py +2 -0
  42. django_spire/ai/sms/decorators.py +2 -0
  43. django_spire/ai/sms/intel.py +4 -2
  44. django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +8 -8
  45. django_spire/ai/sms/models.py +16 -14
  46. django_spire/ai/sms/querysets.py +4 -1
  47. django_spire/ai/sms/tools.py +18 -16
  48. django_spire/ai/sms/urls.py +1 -1
  49. django_spire/ai/sms/views.py +2 -0
  50. django_spire/ai/tests/test_ai.py +3 -5
  51. django_spire/ai/urls.py +1 -0
  52. django_spire/consts.py +1 -1
  53. django_spire/contrib/seeding/field/django/seeder.py +6 -4
  54. django_spire/contrib/seeding/field/llm.py +1 -2
  55. django_spire/contrib/seeding/intelligence/bots/field_seeding_bots.py +7 -8
  56. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +15 -16
  57. django_spire/contrib/seeding/intelligence/intel.py +1 -1
  58. django_spire/contrib/seeding/intelligence/prompts/factory.py +5 -7
  59. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +3 -5
  60. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +1 -2
  61. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +3 -5
  62. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +1 -1
  63. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +3 -3
  64. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +1 -1
  65. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +2 -4
  66. django_spire/contrib/seeding/management/commands/seeding.py +5 -2
  67. django_spire/contrib/seeding/model/base.py +12 -10
  68. django_spire/contrib/seeding/model/django/seeder.py +13 -10
  69. django_spire/contrib/seeding/tests/test_seeding.py +1 -1
  70. django_spire/core/management/commands/spire_startapp.py +84 -46
  71. django_spire/core/management/commands/spire_startapp_pkg/__init__.py +60 -0
  72. django_spire/core/management/commands/spire_startapp_pkg/builder.py +91 -0
  73. django_spire/core/management/commands/spire_startapp_pkg/config.py +115 -0
  74. django_spire/core/management/commands/spire_startapp_pkg/filesystem.py +125 -0
  75. django_spire/core/management/commands/spire_startapp_pkg/generator.py +167 -0
  76. django_spire/core/management/commands/spire_startapp_pkg/maps.py +783 -25
  77. django_spire/core/management/commands/spire_startapp_pkg/permissions.py +147 -0
  78. django_spire/core/management/commands/spire_startapp_pkg/processor.py +144 -57
  79. django_spire/core/management/commands/spire_startapp_pkg/registry.py +89 -0
  80. django_spire/core/management/commands/spire_startapp_pkg/reporter.py +245 -108
  81. django_spire/core/management/commands/spire_startapp_pkg/resolver.py +86 -0
  82. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +252 -0
  83. django_spire/core/management/commands/spire_startapp_pkg/validator.py +96 -0
  84. django_spire/core/middleware/__init__.py +1 -2
  85. django_spire/knowledge/admin.py +2 -0
  86. django_spire/knowledge/apps.py +2 -0
  87. django_spire/knowledge/auth/controller.py +2 -0
  88. django_spire/knowledge/collection/admin.py +3 -0
  89. django_spire/knowledge/collection/forms.py +2 -0
  90. django_spire/knowledge/collection/models.py +4 -0
  91. django_spire/knowledge/collection/querysets.py +3 -3
  92. django_spire/knowledge/collection/seeding/seed.py +2 -0
  93. django_spire/knowledge/collection/tests/factories.py +2 -0
  94. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +2 -0
  95. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +2 -0
  96. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +2 -0
  97. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +2 -0
  98. django_spire/knowledge/collection/views/json_views.py +2 -0
  99. django_spire/knowledge/collection/views/page_views.py +2 -0
  100. django_spire/knowledge/context_processors.py +2 -0
  101. django_spire/knowledge/entry/services/tool_service.py +1 -0
  102. django_spire/knowledge/entry/services/transformation_services.py +0 -1
  103. django_spire/knowledge/entry/tests/factories.py +3 -0
  104. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +2 -0
  105. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +2 -0
  106. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +2 -0
  107. django_spire/knowledge/entry/urls/form_urls.py +1 -0
  108. django_spire/knowledge/entry/urls/json_urls.py +1 -0
  109. django_spire/knowledge/entry/urls/page_urls.py +1 -0
  110. django_spire/knowledge/entry/urls/template_urls.py +1 -0
  111. django_spire/knowledge/entry/version/admin.py +2 -0
  112. django_spire/knowledge/entry/version/block/admin.py +2 -0
  113. django_spire/knowledge/entry/version/block/blocks/heading_block.py +2 -0
  114. django_spire/knowledge/entry/version/block/blocks/sub_heading_block.py +2 -0
  115. django_spire/knowledge/entry/version/block/blocks/text_block.py +2 -0
  116. django_spire/knowledge/entry/version/block/maps.py +2 -0
  117. django_spire/knowledge/entry/version/block/models.py +2 -0
  118. django_spire/knowledge/entry/version/block/tests/factories.py +2 -0
  119. django_spire/knowledge/entry/version/block/tests/test_urls/test_json_urls.py +2 -0
  120. django_spire/knowledge/entry/version/block/views/json_views.py +2 -0
  121. django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +12 -9
  122. django_spire/knowledge/entry/version/maps.py +2 -0
  123. django_spire/knowledge/entry/version/models.py +2 -0
  124. django_spire/knowledge/entry/version/querysets.py +2 -0
  125. django_spire/knowledge/entry/version/seeding/seeder.py +1 -0
  126. django_spire/knowledge/entry/version/tests/factories.py +2 -0
  127. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +3 -0
  128. django_spire/knowledge/entry/version/tests/test_urls/test_form_urls.py +2 -0
  129. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +2 -0
  130. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +2 -0
  131. django_spire/knowledge/entry/version/urls/form_urls.py +1 -0
  132. django_spire/knowledge/entry/version/urls/json_urls.py +1 -0
  133. django_spire/knowledge/entry/version/urls/page_urls.py +1 -0
  134. django_spire/knowledge/entry/version/urls/redirect_urls.py +1 -0
  135. django_spire/knowledge/entry/version/views/form_views.py +2 -0
  136. django_spire/knowledge/entry/version/views/json_views.py +2 -0
  137. django_spire/knowledge/entry/version/views/page_views.py +2 -0
  138. django_spire/knowledge/entry/version/views/redirect_views.py +2 -0
  139. django_spire/knowledge/exceptions.py +2 -0
  140. django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +5 -16
  141. django_spire/knowledge/intelligence/intel/collection_intel.py +3 -1
  142. django_spire/knowledge/intelligence/intel/entry_intel.py +3 -3
  143. django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
  144. django_spire/knowledge/intelligence/maps/collection_map.py +9 -10
  145. django_spire/knowledge/intelligence/maps/entry_map.py +8 -9
  146. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +8 -6
  147. django_spire/knowledge/models.py +2 -0
  148. django_spire/knowledge/seeding/seed.py +2 -0
  149. django_spire/knowledge/templatetags/spire_knowledge_tags.py +3 -0
  150. django_spire/knowledge/urls/__init__.py +1 -0
  151. django_spire/profiling/__init__.py +13 -0
  152. django_spire/profiling/middleware/__init__.py +6 -0
  153. django_spire/{core → profiling}/middleware/profiling.py +63 -58
  154. django_spire/profiling/panel.py +345 -0
  155. django_spire/profiling/templates/panel.html +166 -0
  156. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/METADATA +5 -4
  157. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/RECORD +160 -157
  158. django_spire/core/management/commands/spire_startapp_pkg/constants.py +0 -4
  159. django_spire/core/management/commands/spire_startapp_pkg/manager.py +0 -176
  160. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_detail_card.html +0 -24
  161. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_form_card.html +0 -9
  162. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html +0 -18
  163. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/spirechildapp_form.html +0 -22
  164. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
  165. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_detail_page.html +0 -13
  166. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_form_page.html +0 -13
  167. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_list_page.html +0 -9
  168. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/WHEEL +0 -0
  169. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/licenses/LICENSE.md +0 -0
  170. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -0,0 +1,167 @@
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 pathlib import Path
9
+
10
+ from django_spire.core.management.commands.spire_startapp_pkg.config import AppConfig, PathConfig
11
+ from django_spire.core.management.commands.spire_startapp_pkg.filesystem import FileSystemInterface
12
+ from django_spire.core.management.commands.spire_startapp_pkg.processor import TemplateProcessor
13
+ from django_spire.core.management.commands.spire_startapp_pkg.reporter import ReporterInterface
14
+
15
+
16
+ class AppGenerator:
17
+ """
18
+ Generates Django app structures from templates.
19
+
20
+ This class handles the creation of new Django apps by copying template
21
+ files and processing them with user-provided configuration.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ filesystem: FileSystemInterface,
27
+ processor: TemplateProcessor,
28
+ reporter: ReporterInterface,
29
+ path_config: PathConfig
30
+ ):
31
+ """
32
+ Initializes the AppGenerator with required dependencies.
33
+
34
+ :param filesystem: File system interface for file operations.
35
+ :param processor: Template processor for replacing placeholders.
36
+ :param reporter: Reporter for user feedback and output.
37
+ :param path_config: Configuration containing template paths.
38
+ """
39
+
40
+ self._filesystem = filesystem
41
+ self._path_config = path_config
42
+ self._processor = processor
43
+ self._reporter = reporter
44
+
45
+ def generate(self, config: AppConfig) -> None:
46
+ """
47
+ Generates a new Django app from templates.
48
+
49
+ Creates the app directory, copies template files, and processes
50
+ them with user configuration. Skips generation if the app already exists.
51
+
52
+ :param config: Configuration for the app to generate.
53
+ """
54
+
55
+ if self._filesystem.has_content(config.destination):
56
+ self._reporter.report_app_exists(config.app_path, config.destination)
57
+ return
58
+
59
+ self._validate_template_exists(self._path_config.app_template, 'app template')
60
+ self._reporter.report_creating_app(config.app_path, config.destination)
61
+
62
+ self._filesystem.create_directory(config.destination)
63
+ self._filesystem.copy_tree(self._path_config.app_template, config.destination)
64
+
65
+ self._processor.process_app_templates(
66
+ config.destination,
67
+ config.components,
68
+ config.user_inputs
69
+ )
70
+
71
+ self._reporter.report_app_creation_success(config.app_path)
72
+
73
+ def _validate_template_exists(self, path: Path, template_type: str) -> None:
74
+ """
75
+ Validates that a template directory exists.
76
+
77
+ :param path: Path to the template directory.
78
+ :param template_type: Description of the template type for error messages.
79
+ :raises CommandError: If the template directory does not exist.
80
+ """
81
+
82
+ if not self._filesystem.exists(path):
83
+ self._reporter.write('\n', self._reporter.style_notice)
84
+
85
+ message = (
86
+ f'Template directory "{path}" is missing. '
87
+ f'Ensure you have a valid {template_type}.'
88
+ )
89
+
90
+ raise CommandError(message)
91
+
92
+
93
+ class TemplateGenerator:
94
+ """
95
+ Generates HTML templates for Django apps.
96
+
97
+ This class handles the creation of HTML template files including
98
+ forms, cards, pages, and items for new Django apps.
99
+ """
100
+
101
+ def __init__(
102
+ self,
103
+ filesystem: FileSystemInterface,
104
+ processor: TemplateProcessor,
105
+ reporter: ReporterInterface,
106
+ path_config: PathConfig
107
+ ):
108
+ """
109
+ Initializes the TemplateGenerator with required dependencies.
110
+
111
+ :param filesystem: File system interface for file operations.
112
+ :param processor: Template processor for replacing placeholders.
113
+ :param reporter: Reporter for user feedback and output.
114
+ :param path_config: Configuration containing template paths.
115
+ """
116
+
117
+ self._filesystem = filesystem
118
+ self._path_config = path_config
119
+ self._processor = processor
120
+ self._reporter = reporter
121
+
122
+ def generate(self, config: AppConfig) -> None:
123
+ """
124
+ Generates HTML templates for a new Django app.
125
+
126
+ Creates the template directory, copies template files, and processes
127
+ them with user configuration. Skips generation if templates already exist.
128
+
129
+ :param config: Configuration for the templates to generate.
130
+ """
131
+
132
+ if self._filesystem.has_content(config.template_destination):
133
+ self._reporter.report_templates_exist(config.app_path, config.template_destination)
134
+ return
135
+
136
+ self._validate_template_exists(self._path_config.html_template, 'templates template')
137
+ self._reporter.report_creating_templates(config.app_path, config.template_destination)
138
+
139
+ self._filesystem.create_directory(config.template_destination)
140
+ self._filesystem.copy_tree(self._path_config.html_template, config.template_destination)
141
+
142
+ self._processor.process_html_templates(
143
+ config.template_destination,
144
+ config.components,
145
+ config.user_inputs
146
+ )
147
+
148
+ self._reporter.report_templates_creation_success(config.app_path)
149
+
150
+ def _validate_template_exists(self, path: Path, template_type: str) -> None:
151
+ """
152
+ Validates that a template directory exists.
153
+
154
+ :param path: Path to the template directory.
155
+ :param template_type: Description of the template type for error messages.
156
+ :raises CommandError: If the template directory does not exist.
157
+ """
158
+
159
+ if not self._filesystem.exists(path):
160
+ self._reporter.write('\n', self._reporter.style_notice)
161
+
162
+ message = (
163
+ f'Template directory "{path}" is missing. '
164
+ f'Ensure you have a valid {template_type}.'
165
+ )
166
+
167
+ raise CommandError(message)