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
@@ -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
- pass
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.contenttypes.fields import GenericForeignKey
8
+ from django.contrib.auth.models import User
10
9
  from django.db import transaction
11
- from django.db.models import Model, Field, AutoField, ForeignObjectRel, FileField
10
+ from django.db.models import Model, AutoField, FileField
12
11
 
13
- from django_spire.contrib.constructor.django_model_constructor import BaseDjangoModelConstructor
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
- # template_generator = TemplateGenerator(filesystem, template_processor, reporter, path_config)
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
- missing = registry.get_missing_components(config.components)
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
- if missing:
91
- reporter.report_missing_components(missing)
113
+ app_generator.generate(module_config)
92
114
 
93
- template_builder.build_app_tree_structure(
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.app_template
127
+ path_config.html_template
98
128
  )
99
129
 
100
- # template_builder.build_html_tree_structure(
101
- # path_resolver.get_base_dir(),
102
- # config.components,
103
- # registry.get_installed_apps(),
104
- # path_config.html_template
105
- # )
106
-
107
- if not reporter.prompt_confirmation('\nProceed with app creation? (y/n): '):
108
- reporter.write('App creation aborted.', reporter.style_error)
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
- for module in [missing[-1]]:
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)
@@ -0,0 +1,5 @@
1
+ from django.core.management import CommandError
2
+
3
+
4
+ class AppExistsError(CommandError):
5
+ pass
@@ -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=app_name.lower() + '_detail_card',
625
- detail_page_template_name=app_name.lower() + '_detail_page',
626
- form_card_template_name=app_name.lower() + '_form_card',
627
- form_page_template_name=app_name.lower() + '_form_page',
628
- form_template_name=app_name.lower() + '_form',
629
- item_template_name=app_name.lower() + '_item',
630
- list_card_template_name=app_name.lower() + '_list_card',
631
- list_page_template_name=app_name.lower() + '_list_page',
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
- list_page_view_name: 'list_page_view'
751
- detail_page_view_name: 'detail_page_view'
752
- create_form_view_name: 'create_form_view'
753
- update_form_view_name: 'update_form_view'
754
- delete_form_view_name: 'delete_form_view'
755
- create_modal_form_view_name: 'create_modal_form_view'
756
- update_modal_form_view_name: 'update_modal_form_view'
757
- delete_modal_form_view_name: 'delete_modal_form_view'
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
- create_form_view_name: str
762
- create_modal_form_view_name: str
763
- delete_form_view_name: str
764
- delete_modal_form_view_name: str
765
- detail_page_view_name: str
766
- list_page_view_name: str
767
- update_form_view_name: str
768
- update_modal_form_view_name: str
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
- create_form_view_name='create_form_view',
774
- create_modal_form_view_name='create_modal_form_view',
775
- delete_form_view_name='delete_form_view',
776
- delete_modal_form_view_name='delete_modal_form_view',
777
- detail_page_view_name='detail_page_view',
778
- list_page_view_name='list_page_view',
779
- update_form_view_name='update_form_view',
780
- update_modal_form_view_name='update_modal_form_view',
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'
@@ -16,3 +16,7 @@ class ${form_class_name}(forms.ModelForm):
16
16
  class Meta:
17
17
  model = models.${model_class_name}
18
18
  exclude: ClassVar = []
19
+
20
+
21
+ class ${model_class_name}ListFilterForm(forms.Form):
22
+ search = forms.CharField(required=False)
@@ -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}(HistoryQuerySet):
16
- def search(self, search_value: str) -> QuerySet[${model_class_name}]:
17
- return self.filter(
18
- Q(name__icontains=search_value) |
19
- Q(description__icontains=search_value)
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
@@ -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
  ]
@@ -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.${create_form_view_name}, name='create'),
12
- path('<int:pk>/update/', form_views.${update_form_view_name}, name='update'),
13
- path('<int:pk>/delete/', form_views.${delete_form_view_name}, name='delete'),
14
- path('create/modal/', form_views.${create_modal_form_view_name}, name='create_modal'),
15
- path('<int:pk>/update/modal/', form_views.${update_modal_form_view_name}, name='update_modal'),
16
- path('<int:pk>/delete/modal/', form_views.${delete_modal_form_view_name}, name='delete_modal'),
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
  ]
@@ -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.${list_page_view_name}, name='list'),
12
- path('<int:pk>/detail/', page_views.${detail_page_view_name}, name='detail'),
11
+ path('list/', page_views.${list_view_name}, name='list'),
12
+ path('<int:pk>/detail/', page_views.${detail_view_name}, name='detail'),
13
13
  ]
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from django.urls import path
4
+
5
+ from ${module_path}.views import template_views
6
+
7
+
8
+ app_name = 'template'
9
+
10
+ urlpatterns = [
11
+ path('items/', template_views.${list_items_view_name}, name='items'),
12
+ ]
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
23
23
 
24
24
 
25
25
  @permission_required('${permission_prefix}.delete_${model_instance_name}')
26
- def ${delete_modal_form_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
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 ${delete_form_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
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 ${create_modal_form_view_name}(request: WSGIRequest) -> TemplateResponse:
72
- return _modal_form_view(request)
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 ${update_modal_form_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
77
- return _modal_form_view(request, pk)
75
+ def ${update_modal_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
76
+ return _modal_view(request, pk)
78
77
 
79
78
 
80
- def _modal_form_view(request: WSGIRequest, pk: int = 0) -> TemplateResponse:
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 ${create_form_view_name}(request: WSGIRequest) -> TemplateResponse:
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 ${update_form_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
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.save()
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(
@@ -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 ${detail_page_view_name}(request: WSGIRequest, pk: int) -> TemplateResponse:
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 ${list_page_view_name}(request: WSGIRequest) -> TemplateResponse:
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
- '${context_plural_var}': models.${model_class_name}.objects.all()
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
+ )
@@ -5,5 +5,5 @@
5
5
  {% endblock %}
6
6
 
7
7
  {% block card_title_content %}
8
- {% include '${template_directory_path}/form/${form_template_name}' %}
8
+ {% include '${template_directory_path}/form/${form_template_name}.html' %}
9
9
  {% 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/${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' %}