django-spire 0.25.1__py3-none-any.whl → 0.26.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 (124) hide show
  1. django_spire/ai/chat/router.py +10 -20
  2. django_spire/ai/chat/templates/django_spire/ai/chat/dropdown/ellipsis_dropdown.html +5 -3
  3. django_spire/ai/chat/templates/django_spire/ai/chat/message/default_message.html +6 -2
  4. django_spire/ai/chat/templatetags/spire_ai_chat_tags.py +19 -0
  5. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +20 -88
  6. django_spire/auth/templates/django_spire/auth/element/android_and_chrome_app_install_element.html +33 -0
  7. django_spire/auth/templates/django_spire/auth/element/ios_app_install_element.html +48 -0
  8. django_spire/auth/templates/django_spire/auth/page/auth_page.html +2 -1
  9. django_spire/auth/templates/django_spire/auth/page/login_page.html +2 -0
  10. django_spire/comment/mixins.py +3 -3
  11. django_spire/comment/templates/django_spire/comment/card/comment_list_card.html +8 -5
  12. django_spire/comment/templates/django_spire/comment/form/comment_form.html +3 -3
  13. django_spire/comment/templates/django_spire/comment/form/content/comment_form_content.html +1 -0
  14. django_spire/comment/templates/django_spire/comment/item/comment_item_ellipsis.html +12 -8
  15. django_spire/comment/views.py +8 -8
  16. django_spire/consts.py +1 -1
  17. django_spire/contrib/form/utils.py +3 -3
  18. django_spire/contrib/progress/session.py +1 -1
  19. django_spire/contrib/queryset/filter_tools.py +56 -14
  20. django_spire/contrib/queryset/mixins.py +24 -3
  21. django_spire/contrib/service/django_model_service.py +5 -6
  22. django_spire/core/management/commands/spire_startapp.py +42 -25
  23. django_spire/core/management/commands/spire_startapp_pkg/exceptions.py +5 -0
  24. django_spire/core/management/commands/spire_startapp_pkg/maps.py +64 -32
  25. django_spire/core/management/commands/spire_startapp_pkg/template/app/constants.py.template +1 -0
  26. django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template +4 -0
  27. django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template +2 -1
  28. django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template +15 -6
  29. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +1 -0
  30. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +6 -6
  31. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -2
  32. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/template_urls.py.template +12 -0
  33. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template +10 -11
  34. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template +17 -3
  35. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/template_views.py.template +40 -0
  36. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${form_card_template_name}.html.template +1 -1
  37. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_base_card_template_name}.html.template +16 -0
  38. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_items_card_template_name}.html.template +16 -0
  39. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_table_card_template_name}.html.template +16 -0
  40. django_spire/core/management/commands/spire_startapp_pkg/template/templates/container/${list_container_template_name}.html.template +1 -0
  41. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/${list_filter_form_template_name}.html.template +30 -0
  42. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${item_template_name}.html.template +32 -20
  43. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${list_items_template_name}.html.template +3 -0
  44. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${detail_page_template_name}.html.template +3 -3
  45. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${form_page_template_name}.html.template +2 -2
  46. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${list_page_template_name}.html.template +2 -2
  47. django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_row_template_name}.html.template +6 -0
  48. django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_rows_template_name}.html.template +3 -0
  49. django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_template_name}.html.template +6 -0
  50. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +82 -9
  51. django_spire/core/management/commands/spire_startapp_pkg/validator.py +19 -6
  52. django_spire/core/middleware.py +2 -3
  53. django_spire/core/querysets.py +19 -0
  54. django_spire/core/static/django_spire/js/theme.js +10 -7
  55. django_spire/core/templates/django_spire/badge/base_badge.html +2 -3
  56. django_spire/core/templates/django_spire/base/base.html +1 -0
  57. django_spire/core/templates/django_spire/button/base_button.html +2 -1
  58. django_spire/core/templates/django_spire/card/title_card.html +13 -10
  59. django_spire/core/templates/django_spire/container/container.html +1 -1
  60. django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html +1 -1
  61. django_spire/core/templates/django_spire/form/field/_base_file_field.html +216 -0
  62. django_spire/core/templates/django_spire/form/field/_multi_checkbox_field.html +52 -0
  63. django_spire/core/templates/django_spire/form/field/base_field.html +128 -0
  64. django_spire/core/templates/django_spire/form/field/char_field.html +1 -0
  65. django_spire/core/templates/django_spire/form/field/color_field.html +1 -0
  66. django_spire/core/templates/django_spire/form/field/date_field.html +1 -0
  67. django_spire/core/templates/django_spire/form/field/datetime_field.html +1 -0
  68. django_spire/core/templates/django_spire/form/field/decimal_field.html +1 -0
  69. django_spire/core/templates/django_spire/form/field/element/select_checkmark_element.html +4 -0
  70. django_spire/core/templates/django_spire/form/field/element/select_down_arrow_element.html +5 -0
  71. django_spire/core/templates/django_spire/form/field/email_field.html +1 -0
  72. django_spire/core/templates/django_spire/form/field/input_field.html +13 -0
  73. django_spire/core/templates/django_spire/form/field/item/select_choice_item.html +15 -0
  74. django_spire/core/templates/django_spire/form/field/item/selected_choice_item.html +5 -0
  75. django_spire/core/templates/django_spire/form/field/list_field.html +112 -0
  76. django_spire/core/templates/django_spire/form/field/multi_file_field.html +90 -0
  77. django_spire/core/templates/django_spire/form/field/multi_select_field.html +155 -0
  78. django_spire/core/templates/django_spire/form/field/number_field.html +11 -0
  79. django_spire/core/templates/django_spire/form/field/password_field.html +1 -0
  80. django_spire/core/templates/django_spire/form/field/radio_field.html +24 -0
  81. django_spire/core/templates/django_spire/form/field/range_field.html +1 -0
  82. django_spire/core/templates/django_spire/form/field/search_and_select_field.html +119 -0
  83. django_spire/core/templates/django_spire/form/field/search_field.html +5 -0
  84. django_spire/core/templates/django_spire/form/field/select_field.html +78 -0
  85. django_spire/core/templates/django_spire/form/field/single_checkbox_field.html +27 -0
  86. django_spire/core/templates/django_spire/form/field/single_file_field.html +90 -0
  87. django_spire/core/templates/django_spire/form/field/telephone_field.html +1 -0
  88. django_spire/core/templates/django_spire/form/field/text_field.html +11 -0
  89. django_spire/core/templates/django_spire/form/field/time_field.html +1 -0
  90. django_spire/core/templates/django_spire/infinite_scroll/base.html +2 -1
  91. django_spire/core/templatetags/model_tags.py +34 -0
  92. django_spire/knowledge/entry/forms.py +1 -1
  93. django_spire/knowledge/entry/models.py +18 -0
  94. django_spire/knowledge/entry/querysets.py +8 -6
  95. django_spire/knowledge/entry/services/processor_service.py +1 -0
  96. django_spire/knowledge/entry/services/search_index_service.py +61 -0
  97. django_spire/knowledge/entry/services/search_service.py +99 -0
  98. django_spire/knowledge/entry/services/service.py +6 -0
  99. django_spire/knowledge/entry/version/services/processor_service.py +2 -0
  100. django_spire/knowledge/entry/version/tests/factories.py +9 -4
  101. django_spire/knowledge/entry/version/tests/test_services.py +7 -16
  102. django_spire/knowledge/intelligence/bots/knowledge_answer_bot.py +40 -6
  103. django_spire/knowledge/intelligence/bots/knowledge_entries_bot.py +4 -2
  104. django_spire/knowledge/intelligence/bots/search_preprocessing_bot.py +32 -0
  105. django_spire/knowledge/intelligence/intel/entry_intel.py +12 -0
  106. django_spire/knowledge/intelligence/router.py +47 -4
  107. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +24 -42
  108. django_spire/knowledge/intelligence/workflows/search_preprocessing_workflow.py +78 -0
  109. django_spire/knowledge/management/__init__.py +0 -0
  110. django_spire/knowledge/management/commands/__init__.py +0 -0
  111. django_spire/knowledge/management/commands/rebuild_knowledge_search_index.py +16 -0
  112. django_spire/knowledge/migrations/0010_entry__search_text_entry__search_vector_and_more.py +40 -0
  113. django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +31 -23
  114. django_spire/metric/report/enums.py +11 -5
  115. django_spire/metric/report/report.py +24 -12
  116. django_spire/metric/report/tools.py +14 -4
  117. django_spire/testing/playwright/fixtures.py +4 -5
  118. {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/METADATA +1 -1
  119. {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/RECORD +123 -69
  120. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template +0 -18
  121. /django_spire/{core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template → ai/chat/templatetags/__init__.py} +0 -0
  122. {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/WHEEL +0 -0
  123. {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/licenses/LICENSE.md +0 -0
  124. {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,30 @@
1
+ {% extends 'django_spire/filtering/form/base_session_filter_form.html' %}
2
+
3
+ {% load session_tags %}
4
+
5
+ {% block session_filter_key %}{{ filter_session.session_key }}{% endblock %}
6
+
7
+ {% block filter_content %}
8
+ <div
9
+ class="row g-2 mb-2"
10
+ x-data="{
11
+ async init() {
12
+ this.search_field.set_attribute('placeholder', 'Search...')
13
+ },
14
+
15
+ session_controller: new SessionController('{% session_controller_to_json filter_session.session_key %}'),
16
+ search_field: new GlueCharField(
17
+ 'search',
18
+ {
19
+ value: '{{ filter_session.search|default:"" }}',
20
+ label: 'Search',
21
+ name: 'search',
22
+ }
23
+ ),
24
+ }"
25
+ >
26
+ <div class="col-3">
27
+ {% include 'django_glue/form/field/char_field.html' with glue_field='search_field' %}
28
+ </div>
29
+ </div>
30
+ {% endblock %}
@@ -1,24 +1,36 @@
1
- {% extends 'django_spire/item/item.html' %}
1
+ {% extends 'django_spire/item/infinite_scroll_item.html' %}
2
2
 
3
- {% block item_title %}
4
- {% url '${url_reverse_path}:page:detail' pk=${context_single_var}.pk as ${context_single_var}_view_url %}
5
- {% include 'django_spire/element/attribute_element.html' with attribute_title='${model_verbose_name}' attribute_value=${context_single_var}.name attribute_href=${context_single_var}_view_url %}
6
- {% endblock %}
3
+ {% block item_content %}
4
+ <div class="row justify-content-center align-items-center position-relative">
5
+ <div class="col">
6
+ <div class="row justify-content-between align-items-center {% block item_row_content_outer_class %}{% endblock %}">
7
+ <div class="col">
8
+ <div class="row align-items-center {% block item_content_gutter %}g-2{% endblock %}">
9
+ <div class="row mt-3">
10
+ <div class="col-6">
11
+ {% url '${url_reverse_path}:page:detail' pk=${context_single_var}.pk as ${context_single_var}_view_url %}
12
+ {% include 'django_spire/element/attribute_element.html' with attribute_title='${model_verbose_name}' attribute_value=${context_single_var}.name attribute_href=${context_single_var}_view_url %}
13
+ </div>
14
+ <div class="col-6">
15
+ {% include 'django_spire/element/attribute_element.html' with attribute_title='Description' attribute_value=${context_single_var}.description %}
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ <div class="col-auto top-0 end-0 position-absolute position-lg-static pt-2 pt-lg-0">
21
+ {% url '${url_reverse_path}:page:detail' pk=${context_single_var}.pk as ${context_single_var}_view_url %}
7
22
 
8
- {% block item_row_content %}
9
- {% include 'django_spire/element/attribute_element.html' with attribute_title='Description' attribute_value=${context_single_var}.description %}
10
- {% endblock %}
23
+ {% if perms.${permission_prefix}.change_${model_instance_name} %}
24
+ {% url '${url_reverse_path}:form:update' pk=${context_single_var}.pk as ${context_single_var}_edit_url %}
25
+ {% endif %}
11
26
 
12
- {% block item_button %}
13
- {% url '${url_reverse_path}:page:detail' pk=${context_single_var}.pk as ${context_single_var}_view_url %}
27
+ {% if perms.${permission_prefix}.delete_${model_instance_name} %}
28
+ {% url '${url_reverse_path}:form:delete' pk=${context_single_var}.pk as ${context_single_var}_delete_url %}
29
+ {% endif %}
14
30
 
15
- {% if perms.${permission_prefix}.change_${model_instance_name} %}
16
- {% url '${url_reverse_path}:form:update' pk=${context_single_var}.pk as ${context_single_var}_edit_url %}
17
- {% endif %}
18
-
19
- {% if perms.${permission_prefix}.delete_${model_instance_name} %}
20
- {% url '${url_reverse_path}:form:delete' pk=${context_single_var}.pk as ${context_single_var}_delete_url %}
21
- {% endif %}
22
-
23
- {% include 'django_spire/dropdown/ellipsis_dropdown.html' with view_url=${context_single_var}_view_url edit_url=${context_single_var}_edit_url delete_url=${context_single_var}_delete_url %}
24
- {% endblock %}
31
+ {% include 'django_spire/dropdown/ellipsis_dropdown.html' with view_url=${context_single_var}_view_url edit_url=${context_single_var}_edit_url delete_url=${context_single_var}_delete_url %}
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ {% endblock %}
@@ -0,0 +1,3 @@
1
+ {% for ${context_single_var} in ${context_plural_var} %}
2
+ {% include '${template_directory_path}/item/${item_template_name}.html' %}
3
+ {% endfor %}
@@ -1,13 +1,13 @@
1
1
  {% extends 'django_spire/page/full_page.html' %}
2
2
 
3
- {% block page_content %}
3
+ {% block full_page_content %}
4
4
  <div class="row g-3">
5
5
  <div class="col-12">
6
- {% include '${template_directory_path}/card/${detail_card_template_name}' %}
6
+ {% include '${template_directory_path}/card/${detail_card_template_name}.html' %}
7
7
  </div>
8
8
 
9
9
  <div class="col-4">
10
- {% include 'history/card/activity_list_card.html' with activity_log=${context_single_var}.activity_log.prefetch_user %}
10
+ {% include 'django_spire/activity/card/list_card.html' with activity_log=${context_single_var}.activity_log.prefetch_user %}
11
11
  </div>
12
12
  </div>
13
13
  {% endblock %}
@@ -1,12 +1,12 @@
1
1
  {% extends 'django_spire/page/form_full_page.html' %}
2
2
 
3
- {% block page_content %}
3
+ {% block full_page_content %}
4
4
  <form method="post">
5
5
  {% csrf_token %}
6
6
 
7
7
  <div class="row g-3">
8
8
  <div class="col-12">
9
- {% include '${template_directory_path}/card/${form_card_template_name}' %}
9
+ {% include '${template_directory_path}/card/${form_card_template_name}.html' %}
10
10
  </div>
11
11
  </div>
12
12
  </form>
@@ -1,9 +1,9 @@
1
1
  {% extends 'django_spire/page/full_page.html' %}
2
2
 
3
- {% block page_content %}
3
+ {% block full_page_content %}
4
4
  <div class="row">
5
5
  <div class="col-12">
6
- {% include '${template_directory_path}/card/${list_card_template_name}' %}
6
+ {% include '${template_directory_path}/card/${list_card_template_name}.html' %}
7
7
  </div>
8
8
  </div>
9
9
  {% endblock %}
@@ -0,0 +1,6 @@
1
+ {% extends 'django_spire/table/element/row.html' %}
2
+
3
+ {% block table_cell %}
4
+ {# Add a td element for each column header defined in your table.html, e.g. #}
5
+ {# <td>{{ ${context_single_var}.name }}</td> #}
6
+ {% endblock %}
@@ -0,0 +1,3 @@
1
+ {% for ${context_single_var} in ${context_plural_var} %}
2
+ {% include '${template_directory_path}/table/${table_row_template_name}.html' with row=${context_single_var} %}
3
+ {% endfor %}
@@ -0,0 +1,6 @@
1
+ {% extends 'django_spire/table/base.html' %}
2
+
3
+ {% block table_header %}
4
+ {# Add your table's column headers here #}
5
+ {# e.g. {% include 'django_spire/table/element/header.html' with sort_key='name' label='${model_verbose_name} Name' %}#}
6
+ {% endblock %}
@@ -2,10 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
 
5
- from typing import TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, Tuple
6
6
 
7
7
  from django.core.management.base import CommandError
8
8
 
9
+ from django_spire.core.management.commands.spire_startapp_pkg.exceptions import \
10
+ AppExistsError
11
+
9
12
  if TYPE_CHECKING:
10
13
  from django_spire.core.management.commands.spire_startapp_pkg.reporter import Reporter
11
14
  from django_spire.core.management.commands.spire_startapp_pkg.validator import AppValidator
@@ -30,6 +33,8 @@ class UserInputCollector:
30
33
  self.reporter = reporter
31
34
  self.validator = validator
32
35
 
36
+ self.skip_app_creation = False
37
+
33
38
  def collect_all_inputs(self) -> dict[str, str]:
34
39
  """
35
40
  Collects all required user inputs for app creation.
@@ -52,13 +57,19 @@ class UserInputCollector:
52
57
  db_table_name = self._collect_db_table_name(components, app_name) # Changed from app_label to components, app_name
53
58
  model_permission_path = self._collect_model_permission_path(app_path, model_name)
54
59
 
55
- permission_data = self._collect_permission_inheritance(components)
60
+ if not self.skip_app_creation:
61
+ permission_data = self._collect_permission_inheritance(components)
62
+ else:
63
+ permission_data = {}
64
+
65
+ template_options = self._collect_template_generation_options(components)
56
66
 
57
67
  verbose_name, verbose_name_plural = self._derive_verbose_names(model_name, model_name_plural)
58
68
 
59
69
  return {
60
70
  'app_path': app_path,
61
71
  'app_name': app_name,
72
+ 'skip_app_generation': self.skip_app_creation,
62
73
  'model_name': model_name,
63
74
  'model_name_plural': model_name_plural,
64
75
  'app_label': app_label,
@@ -67,6 +78,7 @@ class UserInputCollector:
67
78
  'verbose_name': verbose_name,
68
79
  'verbose_name_plural': verbose_name_plural,
69
80
  **permission_data,
81
+ **template_options
70
82
  }
71
83
 
72
84
  def _collect_app_label(self, components: list[str], app_name: str) -> str:
@@ -79,8 +91,12 @@ class UserInputCollector:
79
91
  """
80
92
 
81
93
  immediate_parent = components[-2] if len(components) > 2 else None
94
+
82
95
  default = immediate_parent.lower() + '_' + app_name.lower() if immediate_parent else app_name.lower()
83
- return self._collect_input('Enter the app label', default, '3/8')
96
+ if self.skip_app_creation:
97
+ return default
98
+ else:
99
+ return self._collect_input('Enter the app label', default, '3/8')
84
100
 
85
101
  def _collect_app_name(self, components: list[str]) -> str:
86
102
  """
@@ -91,7 +107,10 @@ class UserInputCollector:
91
107
  """
92
108
 
93
109
  default = components[-1]
94
- return self._collect_input('Enter the app name', default, '2/8')
110
+ if self.skip_app_creation:
111
+ return default
112
+ else:
113
+ return self._collect_input('Enter the app name', default, '2/8')
95
114
 
96
115
  def _collect_app_path(self) -> str:
97
116
  """
@@ -110,7 +129,12 @@ class UserInputCollector:
110
129
  raise CommandError(message)
111
130
 
112
131
  components = app_path.split('.')
113
- self.validator.validate_app_path(components)
132
+
133
+ try:
134
+ self.validator.validate_app_path(components)
135
+ except AppExistsError as exc:
136
+ self.reporter.write('\nSkipping app creation.', self.reporter.style_notice)
137
+ self.skip_app_creation = True
114
138
 
115
139
  return app_path
116
140
 
@@ -125,7 +149,10 @@ class UserInputCollector:
125
149
 
126
150
  parent_parts = components[1:-1] if len(components) > 1 else []
127
151
  default = '_'.join(parent_parts).lower() + '_' + app_name.lower() if parent_parts else app_name.lower()
128
- return self._collect_input('Enter the database table name', default, '6/8')
152
+ if self.skip_app_creation:
153
+ return default
154
+ else:
155
+ return self._collect_input('Enter the database table name', default, '6/8')
129
156
 
130
157
  def _collect_input(self, prompt: str, default: str, step_number: str) -> str:
131
158
  """
@@ -150,7 +177,10 @@ class UserInputCollector:
150
177
  """
151
178
 
152
179
  default = ''.join(word.title() for word in app_name.split('_'))
153
- return self._collect_input('Enter the model name', default, '4/8')
180
+ if self.skip_app_creation:
181
+ return default
182
+ else:
183
+ return self._collect_input('Enter the model name', default, '4/8')
154
184
 
155
185
  def _collect_model_name_plural(self, model_name: str) -> str:
156
186
  """
@@ -161,7 +191,10 @@ class UserInputCollector:
161
191
  """
162
192
 
163
193
  default = model_name + 's'
164
- return self._collect_input('Enter the model name plural', default, '5/8')
194
+ if self.skip_app_creation:
195
+ return default
196
+ else:
197
+ return self._collect_input('Enter the model name plural', default, '5/8')
165
198
 
166
199
  def _collect_model_permission_path(self, app_path: str, model_name: str) -> str:
167
200
  """
@@ -173,7 +206,11 @@ class UserInputCollector:
173
206
  """
174
207
 
175
208
  default = f'{app_path}.models.{model_name}'
176
- return self._collect_input('Enter the model permission path', default, '7/8')
209
+ if self.skip_app_creation:
210
+ return default
211
+ else:
212
+ return self._collect_input('Enter the model permission path', default,
213
+ '7/8')
177
214
 
178
215
  def _collect_permission_inheritance(self, components: list[str]) -> dict[str, str]:
179
216
  """
@@ -253,3 +290,39 @@ class UserInputCollector:
253
290
  self.reporter.write('\n[8/8]: Do you want this app to inherit permissions from its parent? (y/n)', self.reporter.style_notice)
254
291
  user_input = input('Default is "n": ').strip().lower()
255
292
  return user_input == 'y'
293
+
294
+ def _collect_template_generation_options(self, components):
295
+ user_input = {}
296
+
297
+ try:
298
+ self.validator.validate_template_path(components)
299
+ except AppExistsError:
300
+ user_input['skip_template_generation'] = True
301
+ return user_input
302
+
303
+ self.reporter.write('\n[Template Creation Wizard]\n\n',
304
+ self.reporter.style_success)
305
+
306
+ user_input[
307
+ 'skip_template_generation'] = self._collect_skip_template_generation()
308
+
309
+ if not user_input['skip_template_generation']:
310
+ user_input['list_display_type'] = self._collect_input(
311
+ 'How do you want to display items on the list page? (items/table)',
312
+ 'items',
313
+ '2/2')
314
+
315
+ return user_input
316
+
317
+ def _collect_skip_template_generation(self):
318
+ """
319
+ Prompts the user to generate templates.
320
+
321
+ :return: True if user wants to skip template generation, False otherwise.
322
+ """
323
+
324
+ user_input = self._collect_input(
325
+ 'Do you want to generate templates for this app? (y/n)', 'n',
326
+ '1/2')
327
+
328
+ return user_input == 'n'
@@ -4,6 +4,9 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  from django.core.management.base import CommandError
6
6
 
7
+ from django_spire.core.management.commands.spire_startapp_pkg.exceptions import \
8
+ AppExistsError
9
+
7
10
  if TYPE_CHECKING:
8
11
  from django_spire.core.management.commands.spire_startapp_pkg.filesystem import FileSystem
9
12
  from django_spire.core.management.commands.spire_startapp_pkg.registry import AppRegistry
@@ -65,14 +68,24 @@ class AppValidator:
65
68
  destination = self._path_resolver.get_app_destination(components)
66
69
 
67
70
  if self._filesystem.has_content(destination):
68
- self._reporter.write('\n', self._reporter.style_notice)
71
+ message = f'The app already exists at {destination}.'
72
+ self._reporter.write(f'\n{message}', self._reporter.style_notice)
73
+ raise AppExistsError(message)
69
74
 
70
- message = (
71
- f'The app already exists at {destination}. '
72
- 'Please remove the existing app or choose a different name.'
73
- )
75
+ def validate_template_path(self, components: list[str]) -> None:
76
+ """
77
+ Validates that an app's template path doesn't already exist.
74
78
 
75
- raise CommandError(message)
79
+ :param components: List of app path components.
80
+ :raises CommandError: If an app already exists at the destination path.
81
+ """
82
+
83
+ destination = self._path_resolver.get_template_destination(components)
84
+
85
+ if self._filesystem.has_content(destination):
86
+ message = f'App templates already exists at {destination}.'
87
+ self._reporter.write(f'\n{message}', self._reporter.style_notice)
88
+ raise AppExistsError(message)
76
89
 
77
90
  def validate_root_app(self, components: list[str]) -> None:
78
91
  """
@@ -7,9 +7,8 @@ from typing import TYPE_CHECKING, Callable
7
7
 
8
8
  # from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
9
9
 
10
- if TYPE_CHECKING:
11
- from django.core.handlers.wsgi import WSGIRequest
12
- from django.http import HttpResponse
10
+ from django.core.handlers.wsgi import WSGIRequest
11
+ from django.http import HttpResponse
13
12
 
14
13
 
15
14
  class MaintenanceMiddleware:
@@ -0,0 +1,19 @@
1
+ from django.db.models import QuerySet, Q
2
+
3
+
4
+ class SearchQuerySetMixin:
5
+ def search(self, search_value: str) -> QuerySet:
6
+ words = search_value.split(' ')
7
+
8
+ filtered_query = self
9
+
10
+ char_fields = [
11
+ field.name for field in self.model._meta.fields
12
+ if field.get_internal_type() == 'CharField'
13
+ ]
14
+
15
+ for word in words:
16
+ or_conditions = Q()
17
+ for field in char_fields:
18
+ or_conditions |= Q(**{f"{field}__icontains": word})
19
+ filtered_query = filtered_query.filter(or_conditions)
@@ -247,16 +247,19 @@ window.get_echarts_theme = function() {
247
247
  let is_dark = document.documentElement.getAttribute('data-theme') === 'dark';
248
248
 
249
249
  return {
250
- text: styles.getPropertyValue('--app-default-text-color').trim(),
251
- primary: styles.getPropertyValue('--app-primary').trim(),
252
- primary_dark: styles.getPropertyValue('--app-primary-dark').trim(),
253
- secondary: styles.getPropertyValue('--app-secondary').trim(),
254
- secondary_dark: styles.getPropertyValue('--app-secondary-dark').trim(),
255
- border: styles.getPropertyValue('--bs-border-color').trim(),
256
250
  bg: styles.getPropertyValue('--app-layer-one').trim(),
251
+ border: styles.getPropertyValue('--bs-border-color').trim(),
252
+ danger: styles.getPropertyValue('--app-danger').trim(),
253
+ is_dark: is_dark,
257
254
  layer_two: styles.getPropertyValue('--app-layer-two').trim(),
258
255
  layer_three: styles.getPropertyValue('--app-layer-three').trim(),
259
256
  layer_four: styles.getPropertyValue('--app-layer-four').trim(),
260
- is_dark: is_dark,
257
+ primary: styles.getPropertyValue('--app-primary').trim(),
258
+ primary_dark: styles.getPropertyValue('--app-primary-dark').trim(),
259
+ secondary: styles.getPropertyValue('--app-secondary').trim(),
260
+ secondary_dark: styles.getPropertyValue('--app-secondary-dark').trim(),
261
+ success: styles.getPropertyValue('--app-success').trim(),
262
+ text: styles.getPropertyValue('--app-default-text-color').trim(),
263
+ warning: styles.getPropertyValue('--app-warning').trim(),
261
264
  };
262
265
  };
@@ -4,9 +4,8 @@
4
4
  {{ badge_class|default:'fs--1' }}
5
5
  "
6
6
  >
7
- {% if badge_text %}
8
- {{ badge_text }}
9
- {% endif %}
7
+ {% block badge_text %}{% endblock %}
8
+ {{ badge_text }}
10
9
 
11
10
  {% if x_badge_text %}
12
11
  <span x-text="{{ x_badge_text }}"></span>
@@ -42,6 +42,7 @@
42
42
  path: '{{ DJANGO_SPIRE_THEME_PATH|escapejs }}'
43
43
  };
44
44
  </script>
45
+ <link rel="manifest" href="{% static 'django_spire/favicons/manifest.json' %}">
45
46
 
46
47
  <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
47
48
  <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.x/dist/echarts.min.js"></script>
@@ -1,8 +1,9 @@
1
1
  <a
2
2
  class="btn shadow-sm {% block button_class %}{% endblock %} {{ button_class|default:'btn-sm' }}"
3
3
  {% block button_attributes %}{% endblock %}
4
+ {{ button_attributes }}
4
5
  {% if button_href %}href="{{ button_href }}{{ button_url_params }}"{% endif %}
5
- {% if x_button_click %}@click="{{ x_button_click }}"{% endif %}
6
+ {% if x_button_click or button_modal_href %}@click="{% if button_modal_href %}dispatch_modal_view('{{ button_modal_href }}'){% else %}{{ x_button_click }}{% endif %}"{% endif %}
6
7
  >
7
8
  {% if button_icon %}
8
9
  <i class="{{ button_icon }}"></i>
@@ -7,7 +7,8 @@
7
7
  {% block card_body_style %}display: flex; flex-direction: column; min-height: 0;{% endblock %}
8
8
 
9
9
  {% block card_content %}
10
- <div
10
+ <{% block card_title_content_container_element_type %}div{% endblock %}
11
+ {% block card_title_content_container_attributes %}{% endblock %}
11
12
  class="d-flex flex-column flex-grow-1"
12
13
  style="min-height: 0;"
13
14
  x-data="{
@@ -17,17 +18,19 @@
17
18
  }
18
19
  }"
19
20
  >
20
- <div class="row justify-content-between align-items-center mb-2 pb-2 border-bottom flex-shrink-0" style="min-height: 38px;">
21
- <div class="col d-flex align-items-center">
22
- <div class="card-title text-uppercase mb-0">
23
- {% block card_title %}
24
- {% endblock %}
21
+ {% block card_title_header_content %}
22
+ <div class="row justify-content-between align-items-center mb-2 pb-2 border-bottom flex-shrink-0" style="min-height: 38px;">
23
+ <div class="col d-flex align-items-center">
24
+ <div class="card-title text-uppercase mb-0">
25
+ {% block card_title %}
26
+ {% endblock %}
27
+ </div>
28
+ </div>
29
+ <div class="{% block card_button_col_class %}col-auto{% endblock %} d-flex">
30
+ {% block card_button %}{% endblock %}
25
31
  </div>
26
32
  </div>
27
- <div class="col-auto d-flex">
28
- {% block card_button %}{% endblock %}
29
- </div>
30
- </div>
33
+ {% endblock %}
31
34
  <div x-show="card_title_dropdown" x-cloak x-collapse class="flex-shrink-0">
32
35
  {% block card_dropdown_content %}
33
36
  {% endblock %}
@@ -2,7 +2,7 @@
2
2
  <div class="col-12">
3
3
  <div class="row justify-content-between align-items-end px-md-3 py-2">
4
4
  <div class="col">
5
- <h1 class="fs-1 fw-semi-bold">
5
+ <h1 class="{% block container_title_class %}fs-1 fw-semi-bold{% endblock %}">
6
6
  {% block container_title %}
7
7
  {% endblock %}
8
8
  </h1>
@@ -1,7 +1,7 @@
1
1
  <form
2
2
  method="get"
3
3
  action="{% block session_filter_url %}{% endblock %}"
4
- class="row border-bottom"
4
+ class="{% block form_class %}row border-bottom{% endblock %}"
5
5
  >
6
6
  <div class="col-12">
7
7
  <input type="text" hidden value="{% block session_filter_key %}{% endblock %}" name="session_filter_key">