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.
- django_spire/ai/chat/router.py +10 -20
- django_spire/ai/chat/templates/django_spire/ai/chat/dropdown/ellipsis_dropdown.html +5 -3
- django_spire/ai/chat/templates/django_spire/ai/chat/message/default_message.html +6 -2
- django_spire/ai/chat/templatetags/spire_ai_chat_tags.py +19 -0
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +20 -88
- django_spire/auth/templates/django_spire/auth/element/android_and_chrome_app_install_element.html +33 -0
- django_spire/auth/templates/django_spire/auth/element/ios_app_install_element.html +48 -0
- django_spire/auth/templates/django_spire/auth/page/auth_page.html +2 -1
- django_spire/auth/templates/django_spire/auth/page/login_page.html +2 -0
- django_spire/comment/mixins.py +3 -3
- django_spire/comment/templates/django_spire/comment/card/comment_list_card.html +8 -5
- django_spire/comment/templates/django_spire/comment/form/comment_form.html +3 -3
- django_spire/comment/templates/django_spire/comment/form/content/comment_form_content.html +1 -0
- django_spire/comment/templates/django_spire/comment/item/comment_item_ellipsis.html +12 -8
- django_spire/comment/views.py +8 -8
- django_spire/consts.py +1 -1
- django_spire/contrib/form/utils.py +3 -3
- django_spire/contrib/progress/session.py +1 -1
- django_spire/contrib/queryset/filter_tools.py +56 -14
- django_spire/contrib/queryset/mixins.py +24 -3
- django_spire/contrib/service/django_model_service.py +5 -6
- django_spire/core/management/commands/spire_startapp.py +42 -25
- django_spire/core/management/commands/spire_startapp_pkg/exceptions.py +5 -0
- django_spire/core/management/commands/spire_startapp_pkg/maps.py +64 -32
- django_spire/core/management/commands/spire_startapp_pkg/template/app/constants.py.template +1 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template +4 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template +2 -1
- django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template +15 -6
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +1 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +6 -6
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -2
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/template_urls.py.template +12 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template +10 -11
- django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template +17 -3
- django_spire/core/management/commands/spire_startapp_pkg/template/app/views/template_views.py.template +40 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${form_card_template_name}.html.template +1 -1
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_base_card_template_name}.html.template +16 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_items_card_template_name}.html.template +16 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_table_card_template_name}.html.template +16 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/container/${list_container_template_name}.html.template +1 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/${list_filter_form_template_name}.html.template +30 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${item_template_name}.html.template +32 -20
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${list_items_template_name}.html.template +3 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${detail_page_template_name}.html.template +3 -3
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${form_page_template_name}.html.template +2 -2
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${list_page_template_name}.html.template +2 -2
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_row_template_name}.html.template +6 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_rows_template_name}.html.template +3 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_template_name}.html.template +6 -0
- django_spire/core/management/commands/spire_startapp_pkg/user_input.py +82 -9
- django_spire/core/management/commands/spire_startapp_pkg/validator.py +19 -6
- django_spire/core/middleware.py +2 -3
- django_spire/core/querysets.py +19 -0
- django_spire/core/static/django_spire/js/theme.js +10 -7
- django_spire/core/templates/django_spire/badge/base_badge.html +2 -3
- django_spire/core/templates/django_spire/base/base.html +1 -0
- django_spire/core/templates/django_spire/button/base_button.html +2 -1
- django_spire/core/templates/django_spire/card/title_card.html +13 -10
- django_spire/core/templates/django_spire/container/container.html +1 -1
- django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html +1 -1
- django_spire/core/templates/django_spire/form/field/_base_file_field.html +216 -0
- django_spire/core/templates/django_spire/form/field/_multi_checkbox_field.html +52 -0
- django_spire/core/templates/django_spire/form/field/base_field.html +128 -0
- django_spire/core/templates/django_spire/form/field/char_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/color_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/date_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/datetime_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/decimal_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/element/select_checkmark_element.html +4 -0
- django_spire/core/templates/django_spire/form/field/element/select_down_arrow_element.html +5 -0
- django_spire/core/templates/django_spire/form/field/email_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/input_field.html +13 -0
- django_spire/core/templates/django_spire/form/field/item/select_choice_item.html +15 -0
- django_spire/core/templates/django_spire/form/field/item/selected_choice_item.html +5 -0
- django_spire/core/templates/django_spire/form/field/list_field.html +112 -0
- django_spire/core/templates/django_spire/form/field/multi_file_field.html +90 -0
- django_spire/core/templates/django_spire/form/field/multi_select_field.html +155 -0
- django_spire/core/templates/django_spire/form/field/number_field.html +11 -0
- django_spire/core/templates/django_spire/form/field/password_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/radio_field.html +24 -0
- django_spire/core/templates/django_spire/form/field/range_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/search_and_select_field.html +119 -0
- django_spire/core/templates/django_spire/form/field/search_field.html +5 -0
- django_spire/core/templates/django_spire/form/field/select_field.html +78 -0
- django_spire/core/templates/django_spire/form/field/single_checkbox_field.html +27 -0
- django_spire/core/templates/django_spire/form/field/single_file_field.html +90 -0
- django_spire/core/templates/django_spire/form/field/telephone_field.html +1 -0
- django_spire/core/templates/django_spire/form/field/text_field.html +11 -0
- django_spire/core/templates/django_spire/form/field/time_field.html +1 -0
- django_spire/core/templates/django_spire/infinite_scroll/base.html +2 -1
- django_spire/core/templatetags/model_tags.py +34 -0
- django_spire/knowledge/entry/forms.py +1 -1
- django_spire/knowledge/entry/models.py +18 -0
- django_spire/knowledge/entry/querysets.py +8 -6
- django_spire/knowledge/entry/services/processor_service.py +1 -0
- django_spire/knowledge/entry/services/search_index_service.py +61 -0
- django_spire/knowledge/entry/services/search_service.py +99 -0
- django_spire/knowledge/entry/services/service.py +6 -0
- django_spire/knowledge/entry/version/services/processor_service.py +2 -0
- django_spire/knowledge/entry/version/tests/factories.py +9 -4
- django_spire/knowledge/entry/version/tests/test_services.py +7 -16
- django_spire/knowledge/intelligence/bots/knowledge_answer_bot.py +40 -6
- django_spire/knowledge/intelligence/bots/knowledge_entries_bot.py +4 -2
- django_spire/knowledge/intelligence/bots/search_preprocessing_bot.py +32 -0
- django_spire/knowledge/intelligence/intel/entry_intel.py +12 -0
- django_spire/knowledge/intelligence/router.py +47 -4
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +24 -42
- django_spire/knowledge/intelligence/workflows/search_preprocessing_workflow.py +78 -0
- django_spire/knowledge/management/__init__.py +0 -0
- django_spire/knowledge/management/commands/__init__.py +0 -0
- django_spire/knowledge/management/commands/rebuild_knowledge_search_index.py +16 -0
- django_spire/knowledge/migrations/0010_entry__search_text_entry__search_vector_and_more.py +40 -0
- django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +31 -23
- django_spire/metric/report/enums.py +11 -5
- django_spire/metric/report/report.py +24 -12
- django_spire/metric/report/tools.py +14 -4
- django_spire/testing/playwright/fixtures.py +4 -5
- {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/METADATA +1 -1
- {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/RECORD +123 -69
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template +0 -18
- /django_spire/{core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template → ai/chat/templatetags/__init__.py} +0 -0
- {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/WHEEL +0 -0
- {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.25.1.dist-info → django_spire-0.26.0.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
from abc import abstractmethod
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from django.db.models import QuerySet
|
|
8
|
+
from django.db.models import QuerySet, Q
|
|
9
9
|
from django_spire.contrib.form.utils import show_form_errors
|
|
10
10
|
from django_spire.contrib.queryset.enums import SessionFilterActionEnum
|
|
11
11
|
from django_spire.contrib.session.controller import SessionController
|
|
@@ -67,6 +67,27 @@ class SessionFilterQuerySetMixin(QuerySet):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class SearchQuerySetMixin(QuerySet):
|
|
70
|
-
@abstractmethod
|
|
71
70
|
def search(self, value: str | None) -> QuerySet:
|
|
72
|
-
|
|
71
|
+
words = value.split(' ')
|
|
72
|
+
|
|
73
|
+
filtered_query = self
|
|
74
|
+
|
|
75
|
+
char_fields = [
|
|
76
|
+
field.name for field in self.model._meta.fields
|
|
77
|
+
if field.get_internal_type() == 'CharField'
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for word in words:
|
|
81
|
+
or_conditions = Q()
|
|
82
|
+
for field in char_fields:
|
|
83
|
+
or_conditions |= Q(**{f"{field}__icontains": word})
|
|
84
|
+
filtered_query = filtered_query.filter(or_conditions)
|
|
85
|
+
|
|
86
|
+
return filtered_query
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ChoicesQueryMixin(QuerySet):
|
|
90
|
+
def to_choices(self):
|
|
91
|
+
return [
|
|
92
|
+
(obj.pk, str(obj)) for obj in self
|
|
93
|
+
]
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
|
|
5
4
|
from abc import ABC
|
|
6
5
|
from itertools import chain
|
|
7
6
|
from typing import Generic, TypeVar
|
|
8
7
|
|
|
9
|
-
from django.contrib.
|
|
8
|
+
from django.contrib.auth.models import User
|
|
10
9
|
from django.db import transaction
|
|
11
|
-
from django.db.models import Model,
|
|
10
|
+
from django.db.models import Model, AutoField, FileField
|
|
12
11
|
|
|
13
|
-
from django_spire.contrib.constructor.django_model_constructor import
|
|
12
|
+
from django_spire.contrib.constructor.django_model_constructor import \
|
|
13
|
+
BaseDjangoModelConstructor
|
|
14
14
|
from django_spire.contrib.service.exceptions import ServiceError
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
log = logging.getLogger(__name__)
|
|
19
18
|
|
|
20
19
|
TypeDjangoModel_co = TypeVar('TypeDjangoModel_co', bound=Model, covariant=True)
|
|
@@ -82,7 +81,7 @@ class BaseDjangoModelService(
|
|
|
82
81
|
then it calls self.obj.save(), then updates the m2m fields on the object instance
|
|
83
82
|
using logic similar to django's `BaseModelForm._save_m2m()` method. In all cases,
|
|
84
83
|
it treats the incoming `field_data` exactly the same as `cleaned_data` is treated
|
|
85
|
-
in the django code that it is emulating, and therefore does not perform any validation
|
|
84
|
+
in the django code that it is emulating, and therefore does not perform any validation
|
|
86
85
|
on the data or the model instance as it is assumed that field_data has already been validated upstream.
|
|
87
86
|
|
|
88
87
|
Args:
|
|
@@ -11,8 +11,7 @@ from django_spire.core.management.commands.spire_startapp_pkg.config import (
|
|
|
11
11
|
)
|
|
12
12
|
from django_spire.core.management.commands.spire_startapp_pkg.filesystem import FileSystem
|
|
13
13
|
from django_spire.core.management.commands.spire_startapp_pkg.generator import (
|
|
14
|
-
AppGenerator,
|
|
15
|
-
# TemplateGenerator,
|
|
14
|
+
AppGenerator, TemplateGenerator,
|
|
16
15
|
)
|
|
17
16
|
from django_spire.core.management.commands.spire_startapp_pkg.processor import (
|
|
18
17
|
TemplateEngine,
|
|
@@ -70,11 +69,19 @@ class Command(BaseCommand):
|
|
|
70
69
|
template_builder = TemplateBuilder(reporter)
|
|
71
70
|
|
|
72
71
|
app_generator = AppGenerator(filesystem, template_processor, reporter, path_config)
|
|
73
|
-
|
|
72
|
+
template_generator = TemplateGenerator(filesystem, template_processor, reporter,
|
|
73
|
+
path_config)
|
|
74
74
|
|
|
75
75
|
user_inputs = user_input_collector.collect_all_inputs()
|
|
76
76
|
app_path = user_inputs['app_path']
|
|
77
77
|
|
|
78
|
+
skip_app_generation = user_inputs['skip_app_generation']
|
|
79
|
+
skip_template_generation = user_inputs['skip_template_generation']
|
|
80
|
+
|
|
81
|
+
if skip_app_generation and skip_template_generation:
|
|
82
|
+
reporter.write('Skipped file generation.', reporter.style_notice)
|
|
83
|
+
return
|
|
84
|
+
|
|
78
85
|
validator.validate_app_format(app_path)
|
|
79
86
|
|
|
80
87
|
config = config_factory.create_config(app_path, user_inputs)
|
|
@@ -85,34 +92,44 @@ class Command(BaseCommand):
|
|
|
85
92
|
reporter.style_notice
|
|
86
93
|
)
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
if not skip_app_generation:
|
|
96
|
+
missing = registry.get_missing_components(config.components)
|
|
97
|
+
|
|
98
|
+
if missing:
|
|
99
|
+
reporter.report_missing_components(missing)
|
|
100
|
+
|
|
101
|
+
template_builder.build_app_tree_structure(
|
|
102
|
+
path_resolver.get_base_dir(),
|
|
103
|
+
config.components,
|
|
104
|
+
registry.get_installed_apps(),
|
|
105
|
+
path_config.app_template
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if reporter.prompt_confirmation('\nProceed with app creation? (y/n): '):
|
|
109
|
+
for module in [missing[-1]]:
|
|
110
|
+
module_config = config_factory.create_config(module,
|
|
111
|
+
user_inputs)
|
|
89
112
|
|
|
90
|
-
|
|
91
|
-
reporter.report_missing_components(missing)
|
|
113
|
+
app_generator.generate(module_config)
|
|
92
114
|
|
|
93
|
-
|
|
115
|
+
reporter.report_installed_apps_suggestion(missing)
|
|
116
|
+
else:
|
|
117
|
+
reporter.write('Skipping app creation.', reporter.style_notice)
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
reporter.write('All component(s) exist.', reporter.style_success)
|
|
121
|
+
|
|
122
|
+
if not skip_template_generation:
|
|
123
|
+
template_builder.build_html_tree_structure(
|
|
94
124
|
path_resolver.get_base_dir(),
|
|
95
125
|
config.components,
|
|
96
126
|
registry.get_installed_apps(),
|
|
97
|
-
path_config.
|
|
127
|
+
path_config.html_template
|
|
98
128
|
)
|
|
99
129
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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)
|
|
130
|
+
if not reporter.prompt_confirmation(
|
|
131
|
+
'\nProceed with template creation? (y/n): '):
|
|
132
|
+
reporter.write('Skipping template creation.', reporter.style_notice)
|
|
109
133
|
return
|
|
110
134
|
|
|
111
|
-
|
|
112
|
-
module_config = config_factory.create_config(module, user_inputs)
|
|
113
|
-
app_generator.generate(module_config)
|
|
114
|
-
# template_generator.generate(module_config)
|
|
115
|
-
|
|
116
|
-
reporter.report_installed_apps_suggestion(missing)
|
|
117
|
-
else:
|
|
118
|
-
reporter.write('All component(s) exist.', reporter.style_success)
|
|
135
|
+
template_generator.generate(config)
|
|
@@ -591,14 +591,24 @@ class TemplatePaths:
|
|
|
591
591
|
"""
|
|
592
592
|
|
|
593
593
|
detail_card_template_name: str
|
|
594
|
+
detail_container_template_name: str
|
|
594
595
|
detail_page_template_name: str
|
|
595
596
|
form_card_template_name: str
|
|
597
|
+
form_container_template_name: str
|
|
596
598
|
form_page_template_name: str
|
|
597
599
|
form_template_name: str
|
|
598
600
|
item_template_name: str
|
|
601
|
+
list_items_card_template_name: str
|
|
602
|
+
list_table_card_template_name: str
|
|
599
603
|
list_card_template_name: str
|
|
604
|
+
list_container_template_name: str
|
|
605
|
+
list_items_template_name: str
|
|
606
|
+
list_filter_form_template_name: str
|
|
600
607
|
list_page_template_name: str
|
|
601
608
|
template_directory_path: str
|
|
609
|
+
table_row_template_name: str
|
|
610
|
+
table_rows_template_name: str
|
|
611
|
+
table_template_name: str
|
|
602
612
|
|
|
603
613
|
@classmethod
|
|
604
614
|
def build(
|
|
@@ -620,15 +630,31 @@ class TemplatePaths:
|
|
|
620
630
|
else app_name.lower()
|
|
621
631
|
)
|
|
622
632
|
|
|
633
|
+
list_display_type = (
|
|
634
|
+
user_inputs.get('list_display_type', 'items')
|
|
635
|
+
if user_inputs
|
|
636
|
+
else 'items'
|
|
637
|
+
)
|
|
638
|
+
|
|
623
639
|
return cls(
|
|
624
|
-
detail_card_template_name=
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
640
|
+
detail_card_template_name='detail_card',
|
|
641
|
+
detail_container_template_name='detail_container',
|
|
642
|
+
detail_page_template_name='detail_page',
|
|
643
|
+
form_card_template_name='form_card',
|
|
644
|
+
form_container_template_name='form_container',
|
|
645
|
+
form_page_template_name='form_page',
|
|
646
|
+
form_template_name='form',
|
|
647
|
+
item_template_name='item',
|
|
648
|
+
list_items_card_template_name=f'list_items_card',
|
|
649
|
+
list_table_card_template_name=f'list_table_card',
|
|
650
|
+
list_card_template_name=f'list_{list_display_type}_card',
|
|
651
|
+
list_container_template_name='list_container',
|
|
652
|
+
list_filter_form_template_name='list_filter_form',
|
|
653
|
+
list_items_template_name='list_items',
|
|
654
|
+
table_row_template_name='row',
|
|
655
|
+
table_rows_template_name='rows',
|
|
656
|
+
table_template_name='table',
|
|
657
|
+
list_page_template_name='list_page',
|
|
632
658
|
template_directory_path=template_path,
|
|
633
659
|
)
|
|
634
660
|
|
|
@@ -747,37 +773,43 @@ class ViewFunctions:
|
|
|
747
773
|
View function names.
|
|
748
774
|
|
|
749
775
|
Example:
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
776
|
+
list_view_name: 'list_page_view'
|
|
777
|
+
detail_view_name: 'detail_page_view'
|
|
778
|
+
create_view_name: 'create_form_view'
|
|
779
|
+
update_view_name: 'update_form_view'
|
|
780
|
+
delete_view_name: 'delete_form_view'
|
|
781
|
+
create_modal_view_name: 'create_modal_form_view'
|
|
782
|
+
update_modal_view_name: 'update_modal_form_view'
|
|
783
|
+
delete_modal_view_name: 'delete_modal_form_view'
|
|
784
|
+
list_items_view_name: 'items_view'
|
|
785
|
+
rows_view_name: 'rows_view'
|
|
758
786
|
|
|
759
787
|
"""
|
|
760
788
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
789
|
+
create_view_name: str
|
|
790
|
+
create_modal_view_name: str
|
|
791
|
+
delete_view_name: str
|
|
792
|
+
delete_modal_view_name: str
|
|
793
|
+
detail_view_name: str
|
|
794
|
+
list_view_name: str
|
|
795
|
+
update_view_name: str
|
|
796
|
+
update_modal_view_name: str
|
|
797
|
+
list_items_view_name: str
|
|
798
|
+
rows_view_name: str
|
|
769
799
|
|
|
770
800
|
@classmethod
|
|
771
801
|
def build(cls, components: list[str], **_kwargs) -> ViewFunctions:
|
|
772
802
|
return cls(
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
803
|
+
create_view_name='create_view',
|
|
804
|
+
create_modal_view_name='create_modal_view',
|
|
805
|
+
delete_view_name='delete_form_view',
|
|
806
|
+
delete_modal_view_name='delete_modal_view',
|
|
807
|
+
detail_view_name='detail_view',
|
|
808
|
+
list_view_name='list_view',
|
|
809
|
+
update_view_name='update_view',
|
|
810
|
+
update_modal_view_name='update_modal_view',
|
|
811
|
+
rows_view_name='rows_view',
|
|
812
|
+
list_items_view_name='items_view',
|
|
781
813
|
)
|
|
782
814
|
|
|
783
815
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
LIST_FILTERING_SESSION_KEY = '${context_single_var}_list_filter'
|
|
@@ -5,12 +5,13 @@ from django.urls import reverse
|
|
|
5
5
|
|
|
6
6
|
from django_spire.contrib.breadcrumb import Breadcrumbs
|
|
7
7
|
from django_spire.history.mixins import HistoryModelMixin
|
|
8
|
+
from django_spire.history.activity.mixins import ActivityMixin
|
|
8
9
|
|
|
9
10
|
from ${module_path} import querysets
|
|
10
11
|
from ${module_path}.services.service import ${service_class_name}
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class ${model_class_name}(HistoryModelMixin):
|
|
14
|
+
class ${model_class_name}(HistoryModelMixin, ActivityMixin):
|
|
14
15
|
name = models.CharField(max_length=255)
|
|
15
16
|
description = models.TextField(default='')
|
|
16
17
|
|
|
@@ -5,6 +5,8 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
|
|
7
7
|
from django_spire.history.querysets import HistoryQuerySet
|
|
8
|
+
from django_spire.contrib.queryset.mixins import SearchQuerySetMixin, \
|
|
9
|
+
SessionFilterQuerySetMixin
|
|
8
10
|
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from django.db.models import QuerySet
|
|
@@ -12,9 +14,16 @@ if TYPE_CHECKING:
|
|
|
12
14
|
from ${module_path}.models import ${model_class_name}
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class ${queryset_class_name}(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
class ${queryset_class_name}(
|
|
18
|
+
HistoryQuerySet,
|
|
19
|
+
SearchQuerySetMixin,
|
|
20
|
+
SessionFilterQuerySetMixin
|
|
21
|
+
):
|
|
22
|
+
def bulk_filter(self, filter_data: dict) -> QuerySet[${model_class_name}]:
|
|
23
|
+
queryset = self
|
|
24
|
+
|
|
25
|
+
search = filter_data.get('search', '')
|
|
26
|
+
if search:
|
|
27
|
+
queryset = queryset.search(search)
|
|
28
|
+
|
|
29
|
+
return queryset
|
django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template
CHANGED
|
@@ -8,4 +8,5 @@ app_name = '${url_namespace}'
|
|
|
8
8
|
urlpatterns = [
|
|
9
9
|
path('page/', include('${module_path}.urls.page_urls', namespace='page')),
|
|
10
10
|
path('form/', include('${module_path}.urls.form_urls', namespace='form')),
|
|
11
|
+
path('template/', include('${module_path}.urls.template_urls', namespace='template')),
|
|
11
12
|
]
|
django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template
CHANGED
|
@@ -8,10 +8,10 @@ from ${module_path}.views import form_views
|
|
|
8
8
|
app_name = 'form'
|
|
9
9
|
|
|
10
10
|
urlpatterns = [
|
|
11
|
-
path('create/', form_views.${
|
|
12
|
-
path('<int:pk>/update/', form_views.${
|
|
13
|
-
path('<int:pk>/delete/', form_views.${
|
|
14
|
-
path('create/modal/', form_views.${
|
|
15
|
-
path('<int:pk>/update/modal/', form_views.${
|
|
16
|
-
path('<int:pk>/delete/modal/', form_views.${
|
|
11
|
+
path('create/', form_views.${create_view_name}, name='create'),
|
|
12
|
+
path('<int:pk>/update/', form_views.${update_view_name}, name='update'),
|
|
13
|
+
path('<int:pk>/delete/', form_views.${delete_view_name}, name='delete'),
|
|
14
|
+
path('create/modal/', form_views.${create_modal_view_name}, name='create_modal'),
|
|
15
|
+
path('<int:pk>/update/modal/', form_views.${update_modal_view_name}, name='update_modal'),
|
|
16
|
+
path('<int:pk>/delete/modal/', form_views.${delete_modal_view_name}, name='delete_modal'),
|
|
17
17
|
]
|
django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template
CHANGED
|
@@ -8,6 +8,6 @@ from ${module_path}.views import page_views
|
|
|
8
8
|
app_name = 'page'
|
|
9
9
|
|
|
10
10
|
urlpatterns = [
|
|
11
|
-
path('list/', page_views.${
|
|
12
|
-
path('<int:pk>/detail/', page_views.${
|
|
11
|
+
path('list/', page_views.${list_view_name}, name='list'),
|
|
12
|
+
path('<int:pk>/detail/', page_views.${detail_view_name}, name='detail'),
|
|
13
13
|
]
|
django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template
CHANGED
|
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@permission_required('${permission_prefix}.delete_${model_instance_name}')
|
|
26
|
-
def ${
|
|
26
|
+
def ${delete_modal_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
27
27
|
${context_single_var} = get_object_or_404(models.${model_class_name}, pk=pk)
|
|
28
28
|
|
|
29
29
|
form_action = reverse(
|
|
@@ -35,7 +35,6 @@ def ${delete_modal_form_view_name}(request: WSGIRequest, pk: int) -> TemplateRes
|
|
|
35
35
|
${context_single_var}.add_activity(
|
|
36
36
|
user=request.user,
|
|
37
37
|
verb='deleted',
|
|
38
|
-
device=request.device,
|
|
39
38
|
information=f'{request.user.get_full_name()} deleted a ${model_instance_name}.'
|
|
40
39
|
)
|
|
41
40
|
|
|
@@ -52,7 +51,7 @@ def ${delete_modal_form_view_name}(request: WSGIRequest, pk: int) -> TemplateRes
|
|
|
52
51
|
|
|
53
52
|
|
|
54
53
|
@permission_required('${permission_prefix}.delete_${model_instance_name}')
|
|
55
|
-
def ${
|
|
54
|
+
def ${delete_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
56
55
|
${context_single_var} = get_object_or_404(models.${model_class_name}, pk=pk)
|
|
57
56
|
|
|
58
57
|
return_url = request.GET.get(
|
|
@@ -68,16 +67,16 @@ def ${delete_form_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
@permission_required('${permission_prefix}.add_${model_instance_name}')
|
|
71
|
-
def ${
|
|
72
|
-
return
|
|
70
|
+
def ${create_modal_view_name}(request: WSGIRequest) -> TemplateResponse:
|
|
71
|
+
return _modal_view(request)
|
|
73
72
|
|
|
74
73
|
|
|
75
74
|
@permission_required('${permission_prefix}.change_${model_instance_name}')
|
|
76
|
-
def ${
|
|
77
|
-
return
|
|
75
|
+
def ${update_modal_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
76
|
+
return _modal_view(request, pk)
|
|
78
77
|
|
|
79
78
|
|
|
80
|
-
def
|
|
79
|
+
def _modal_view(request: WSGIRequest, pk: int = 0) -> TemplateResponse:
|
|
81
80
|
${context_single_var} = get_object_or_404(models.${model_class_name}, pk=pk)
|
|
82
81
|
|
|
83
82
|
dg.glue_model_object(request, '${glue_model_key}', ${context_single_var})
|
|
@@ -94,12 +93,12 @@ def _modal_form_view(request: WSGIRequest, pk: int = 0) -> TemplateResponse:
|
|
|
94
93
|
|
|
95
94
|
|
|
96
95
|
@permission_required('${permission_prefix}.add_${model_instance_name}')
|
|
97
|
-
def ${
|
|
96
|
+
def ${create_view_name}(request: WSGIRequest) -> TemplateResponse:
|
|
98
97
|
return _form_view(request)
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
@permission_required('${permission_prefix}.change_${model_instance_name}')
|
|
102
|
-
def ${
|
|
101
|
+
def ${update_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
103
102
|
return _form_view(request, pk)
|
|
104
103
|
|
|
105
104
|
|
|
@@ -112,7 +111,7 @@ def _form_view(request: WSGIRequest, pk: int = 0) -> TemplateResponse|HttpRespon
|
|
|
112
111
|
form = forms.${form_class_name}(request.POST, instance=${context_single_var})
|
|
113
112
|
|
|
114
113
|
if form.is_valid():
|
|
115
|
-
${context_single_var} = form.
|
|
114
|
+
${context_single_var}, _ = ${context_single_var}.services.save_model_obj(**form.cleaned_data)
|
|
116
115
|
add_form_activity(${context_single_var}, pk, request.user)
|
|
117
116
|
|
|
118
117
|
return redirect(
|
django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template
CHANGED
|
@@ -4,10 +4,16 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from django.contrib.auth.decorators import permission_required
|
|
6
6
|
from django.shortcuts import get_object_or_404
|
|
7
|
+
from django.urls import reverse
|
|
7
8
|
|
|
8
9
|
from django_spire.contrib.generic_views import portal_views
|
|
10
|
+
from django_spire.core.table.enums import ResponsiveMode
|
|
11
|
+
from django_spire.contrib.session.controller import SessionController
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
from ${module_path} import models
|
|
15
|
+
from ${module_path}.forms import ${model_class_name}ListFilterForm
|
|
16
|
+
from ${module_path}.constants import LIST_FILTERING_SESSION_KEY
|
|
11
17
|
|
|
12
18
|
if TYPE_CHECKING:
|
|
13
19
|
from django.core.handlers.wsgi import WSGIRequest
|
|
@@ -15,7 +21,7 @@ if TYPE_CHECKING:
|
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
@permission_required('${permission_prefix}.view_${model_instance_name}')
|
|
18
|
-
def ${
|
|
24
|
+
def ${detail_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
19
25
|
${context_single_var} = get_object_or_404(models.${model_class_name}, pk=pk)
|
|
20
26
|
|
|
21
27
|
context_data = {
|
|
@@ -31,9 +37,17 @@ def ${detail_page_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
@permission_required('${permission_prefix}.view_${model_instance_name}')
|
|
34
|
-
def ${
|
|
40
|
+
def ${list_view_name}(request: WSGIRequest) -> TemplateResponse:
|
|
41
|
+
models.${model_class_name}.objects.process_session_filter(
|
|
42
|
+
request=request,
|
|
43
|
+
session_key=LIST_FILTERING_SESSION_KEY,
|
|
44
|
+
form_class=${model_class_name}ListFilterForm,
|
|
45
|
+
)
|
|
46
|
+
|
|
35
47
|
context_data = {
|
|
36
|
-
'
|
|
48
|
+
'responsive_mode': ResponsiveMode.SCROLL,
|
|
49
|
+
'${context_single_var}_items_endpoint': reverse('${url_reverse_path}:template:items'),
|
|
50
|
+
'filter_session': SessionController(request, LIST_FILTERING_SESSION_KEY),
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
return portal_views.list_view(
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from django.contrib.auth.decorators import permission_required
|
|
6
|
+
|
|
7
|
+
from django_spire.contrib.generic_views import portal_views
|
|
8
|
+
|
|
9
|
+
from ${module_path} import models
|
|
10
|
+
from ${module_path}.constants import LIST_FILTERING_SESSION_KEY
|
|
11
|
+
from ${module_path}.forms import ${model_class_name}ListFilterForm
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
15
|
+
from django.template.response import TemplateResponse
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@permission_required('${permission_prefix}.view_${model_instance_name}')
|
|
19
|
+
def ${list_items_view_name}(request: WSGIRequest) -> TemplateResponse:
|
|
20
|
+
sort_field = request.GET.get('sort', 'name')
|
|
21
|
+
sort_direction = request.GET.get('direction', 'asc')
|
|
22
|
+
|
|
23
|
+
${context_plural_var} = (
|
|
24
|
+
models.${model_class_name}.objects
|
|
25
|
+
.process_session_filter(
|
|
26
|
+
request=request,
|
|
27
|
+
session_key=LIST_FILTERING_SESSION_KEY,
|
|
28
|
+
form_class=${model_class_name}ListFilterForm,
|
|
29
|
+
).order_by(
|
|
30
|
+
f"{'-' if sort_direction == 'desc' else ''}{sort_field}"
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return portal_views.infinite_scrolling_view(
|
|
35
|
+
request,
|
|
36
|
+
context_data={'batch_size': 10,},
|
|
37
|
+
queryset=${context_plural_var},
|
|
38
|
+
queryset_name='${context_plural_var}',
|
|
39
|
+
template='${template_directory_path}/table/${table_rows_template_name}.html'
|
|
40
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% extends 'django_spire/card/title_card.html' %}
|
|
2
|
+
|
|
3
|
+
{% block card_title %}
|
|
4
|
+
${model_verbose_name_plural}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
|
|
7
|
+
{% block card_button %}
|
|
8
|
+
{% if perms.${permission_prefix}.add_${model_instance_name} %}
|
|
9
|
+
{% url '${url_reverse_path}:form:create' as new_${context_single_var}_url %}
|
|
10
|
+
{% include 'django_spire/button/primary_dark_button.html' with button_text='Add' button_icon='bi bi-plus' button_href=new_${context_single_var}_url %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% endblock %}
|
|
13
|
+
|
|
14
|
+
{% block card_title_content %}
|
|
15
|
+
{% include '${template_directory_path}/table/${list_table_card_template_name}.html' with endpoint=${context_single_var}_rows_endpoint %}
|
|
16
|
+
{% endblock %}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% extends 'django_spire/card/title_card.html' %}
|
|
2
|
+
|
|
3
|
+
{% block card_title %}
|
|
4
|
+
${model_verbose_name_plural}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
|
|
7
|
+
{% block card_button %}
|
|
8
|
+
{% if perms.${permission_prefix}.add_${model_instance_name} %}
|
|
9
|
+
{% url '${url_reverse_path}:form:create' as new_${context_single_var}_url %}
|
|
10
|
+
{% include 'django_spire/button/primary_dark_button.html' with button_text='Add' button_icon='bi bi-plus' button_href=new_${context_single_var}_url %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% endblock %}
|
|
13
|
+
|
|
14
|
+
{% block card_title_content %}
|
|
15
|
+
{% include '${template_directory_path}/container/${list_container_template_name}.html' with endpoint=${context_single_var}_items_endpoint %}
|
|
16
|
+
{% endblock %}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% extends 'django_spire/card/title_card.html' %}
|
|
2
|
+
|
|
3
|
+
{% block card_title %}
|
|
4
|
+
${model_verbose_name_plural}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
|
|
7
|
+
{% block card_button %}
|
|
8
|
+
{% if perms.${permission_prefix}.add_${model_instance_name} %}
|
|
9
|
+
{% url '${url_reverse_path}:form:create' as new_${context_single_var}_url %}
|
|
10
|
+
{% include 'django_spire/button/primary_dark_button.html' with button_text='Add' button_icon='bi bi-plus' button_href=new_${context_single_var}_url %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% endblock %}
|
|
13
|
+
|
|
14
|
+
{% block card_title_content %}
|
|
15
|
+
{% include '${template_directory_path}/table/${table_template_name}.html' with endpoint=${context_single_var}_items_endpoint %}
|
|
16
|
+
{% endblock %}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% extends 'django_spire/container/infinite_scroll_container.html' %}
|