django-spire 0.25.2__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 (91) hide show
  1. django_spire/auth/templates/django_spire/auth/element/android_and_chrome_app_install_element.html +33 -0
  2. django_spire/auth/templates/django_spire/auth/element/ios_app_install_element.html +48 -0
  3. django_spire/auth/templates/django_spire/auth/page/auth_page.html +2 -1
  4. django_spire/auth/templates/django_spire/auth/page/login_page.html +2 -0
  5. django_spire/comment/mixins.py +3 -3
  6. django_spire/comment/templates/django_spire/comment/card/comment_list_card.html +8 -5
  7. django_spire/comment/templates/django_spire/comment/form/comment_form.html +3 -3
  8. django_spire/comment/templates/django_spire/comment/form/content/comment_form_content.html +1 -0
  9. django_spire/comment/templates/django_spire/comment/item/comment_item_ellipsis.html +12 -8
  10. django_spire/comment/views.py +8 -8
  11. django_spire/consts.py +1 -1
  12. django_spire/contrib/form/utils.py +3 -3
  13. django_spire/contrib/queryset/filter_tools.py +56 -14
  14. django_spire/contrib/queryset/mixins.py +24 -3
  15. django_spire/contrib/service/django_model_service.py +5 -6
  16. django_spire/core/management/commands/spire_startapp.py +42 -25
  17. django_spire/core/management/commands/spire_startapp_pkg/exceptions.py +5 -0
  18. django_spire/core/management/commands/spire_startapp_pkg/maps.py +64 -32
  19. django_spire/core/management/commands/spire_startapp_pkg/template/app/constants.py.template +1 -0
  20. django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template +4 -0
  21. django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template +2 -1
  22. django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template +15 -6
  23. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +1 -0
  24. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +6 -6
  25. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -2
  26. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/template_urls.py.template +12 -0
  27. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template +10 -11
  28. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template +17 -3
  29. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/template_views.py.template +40 -0
  30. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${form_card_template_name}.html.template +1 -1
  31. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_base_card_template_name}.html.template +16 -0
  32. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_items_card_template_name}.html.template +16 -0
  33. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_table_card_template_name}.html.template +16 -0
  34. django_spire/core/management/commands/spire_startapp_pkg/template/templates/container/${list_container_template_name}.html.template +1 -0
  35. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/${list_filter_form_template_name}.html.template +30 -0
  36. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${item_template_name}.html.template +32 -20
  37. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${list_items_template_name}.html.template +3 -0
  38. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${detail_page_template_name}.html.template +3 -3
  39. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${form_page_template_name}.html.template +2 -2
  40. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/${list_page_template_name}.html.template +2 -2
  41. django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_row_template_name}.html.template +6 -0
  42. django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_rows_template_name}.html.template +3 -0
  43. django_spire/core/management/commands/spire_startapp_pkg/template/templates/table/${table_template_name}.html.template +6 -0
  44. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +82 -9
  45. django_spire/core/management/commands/spire_startapp_pkg/validator.py +19 -6
  46. django_spire/core/querysets.py +19 -0
  47. django_spire/core/templates/django_spire/badge/base_badge.html +2 -3
  48. django_spire/core/templates/django_spire/base/base.html +1 -0
  49. django_spire/core/templates/django_spire/button/base_button.html +2 -1
  50. django_spire/core/templates/django_spire/card/title_card.html +13 -10
  51. django_spire/core/templates/django_spire/container/container.html +1 -1
  52. django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html +1 -1
  53. django_spire/core/templates/django_spire/form/field/_base_file_field.html +216 -0
  54. django_spire/core/templates/django_spire/form/field/_multi_checkbox_field.html +52 -0
  55. django_spire/core/templates/django_spire/form/field/base_field.html +128 -0
  56. django_spire/core/templates/django_spire/form/field/char_field.html +1 -0
  57. django_spire/core/templates/django_spire/form/field/color_field.html +1 -0
  58. django_spire/core/templates/django_spire/form/field/date_field.html +1 -0
  59. django_spire/core/templates/django_spire/form/field/datetime_field.html +1 -0
  60. django_spire/core/templates/django_spire/form/field/decimal_field.html +1 -0
  61. django_spire/core/templates/django_spire/form/field/element/select_checkmark_element.html +4 -0
  62. django_spire/core/templates/django_spire/form/field/element/select_down_arrow_element.html +5 -0
  63. django_spire/core/templates/django_spire/form/field/email_field.html +1 -0
  64. django_spire/core/templates/django_spire/form/field/input_field.html +13 -0
  65. django_spire/core/templates/django_spire/form/field/item/select_choice_item.html +15 -0
  66. django_spire/core/templates/django_spire/form/field/item/selected_choice_item.html +5 -0
  67. django_spire/core/templates/django_spire/form/field/list_field.html +112 -0
  68. django_spire/core/templates/django_spire/form/field/multi_file_field.html +90 -0
  69. django_spire/core/templates/django_spire/form/field/multi_select_field.html +155 -0
  70. django_spire/core/templates/django_spire/form/field/number_field.html +11 -0
  71. django_spire/core/templates/django_spire/form/field/password_field.html +1 -0
  72. django_spire/core/templates/django_spire/form/field/radio_field.html +24 -0
  73. django_spire/core/templates/django_spire/form/field/range_field.html +1 -0
  74. django_spire/core/templates/django_spire/form/field/search_and_select_field.html +119 -0
  75. django_spire/core/templates/django_spire/form/field/search_field.html +5 -0
  76. django_spire/core/templates/django_spire/form/field/select_field.html +78 -0
  77. django_spire/core/templates/django_spire/form/field/single_checkbox_field.html +27 -0
  78. django_spire/core/templates/django_spire/form/field/single_file_field.html +90 -0
  79. django_spire/core/templates/django_spire/form/field/telephone_field.html +1 -0
  80. django_spire/core/templates/django_spire/form/field/text_field.html +11 -0
  81. django_spire/core/templates/django_spire/form/field/time_field.html +1 -0
  82. django_spire/core/templates/django_spire/infinite_scroll/base.html +2 -1
  83. django_spire/core/templatetags/model_tags.py +34 -0
  84. django_spire/metric/report/tools.py +0 -2
  85. {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/METADATA +1 -1
  86. {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/RECORD +89 -45
  87. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template +0 -0
  88. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template +0 -18
  89. {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/WHEEL +0 -0
  90. {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/licenses/LICENSE.md +0 -0
  91. {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,33 @@
1
+ <div class="text-center">
2
+ <button id="android-install-button" class="btn btn-app-primary fs-4 mt-3" hidden>
3
+ Install App <i class="bi bi-file-arrow-down"></i>
4
+ </button>
5
+ </div>
6
+
7
+ <script type="application/javascript">
8
+ let android_install_prompt = null;
9
+ let android_install_button = document.querySelector("#android-install-button");
10
+
11
+ window.addEventListener("beforeinstallprompt", (event) => {
12
+ event.preventDefault();
13
+ android_install_prompt = event;
14
+ android_install_button.removeAttribute("hidden");
15
+ });
16
+
17
+ android_install_button.addEventListener("click", async () => {
18
+ if (!android_install_prompt) {
19
+ return;
20
+ }
21
+ const result = await android_install_prompt.prompt();
22
+ disableInAppInstallPrompt();
23
+ });
24
+
25
+ window.addEventListener("appinstalled", () => {
26
+ disableInAppInstallPrompt();
27
+ });
28
+
29
+ function disableInAppInstallPrompt() {
30
+ android_install_prompt = null;
31
+ android_install_button.setAttribute("hidden", "");
32
+ }
33
+ </script>
@@ -0,0 +1,48 @@
1
+ {% load static %}
2
+
3
+ <div class="text-center">
4
+ <button id="ios-install-button" class="btn btn-app-primary fs-4 mt-3" data-bs-toggle="modal" data-bs-target="#ios-install-app-modal" hidden>
5
+ Install App <i class="bi bi-file-arrow-down"></i>
6
+ </button>
7
+ </div>
8
+
9
+ <div class="modal fade" id="ios-install-app-modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true" data-bs-theme="{{ theme.mode }}">
10
+ <div class="modal-dialog">
11
+ <div class="modal-content bg-app-layer-one">
12
+ <div class="modal-header">
13
+ <img
14
+ src="{% block app_icon %}{% endblock %}"
15
+ alt="No Image"
16
+ class="border rounded-3 me-3 shadow"
17
+ style=" height: 3.0rem; "
18
+ >
19
+ <span class="fs-5 fw-semibold">{% block app_title %}{% endblock %}</span>
20
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
21
+ </div>
22
+ <div class="modal-body">
23
+ <p>Install the app on your Apple device to easily access it anytime by following the instructions below in the Safari Web Browser.</p>
24
+ <ol>
25
+ <li class="p-2">Tap on the <span class="border border-2 p-1 rounded-3"><i class="bi bi-box-arrow-up"></i></span> in your browser bar</li>
26
+ <li class="p-2">Select <span class="border border-2 p-1 rounded-3">Add to Home Screen</span></li>
27
+ <li class="p-2">Click <span class="border border-2 p-1 rounded-3">Add</span> When Prompted</li>
28
+ </ol>
29
+ <p class="m-0">You will need to close this popup to complete the installation process</p>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <script type="application/javascript">
36
+ let ios_install_button = document.querySelector("#ios-install-button");
37
+
38
+ const isIos = () => {
39
+ const userAgent = window.navigator.userAgent.toLowerCase();
40
+ return /iphone|ipad|ipod/.test(userAgent);
41
+ }
42
+
43
+ const isInStandaloneMode = () => ('standalone' in window.navigator) && (window.navigator.standalone);
44
+
45
+ if (isIos() && !isInStandaloneMode()) {
46
+ ios_install_button.removeAttribute("hidden");
47
+ }
48
+ </script>
@@ -10,7 +10,8 @@
10
10
  <div class="text-center pb-5">
11
11
  <a href="/">
12
12
  {% block authentication_page_img %}
13
- <img height="120px" class="ms-1" src="{% static 'img/django_spire.png' %}" alt="">
13
+ <img height="120px" class="ms-1" src="{% static 'django_spire/img/django_spire.svg' %}"
14
+ alt="">
14
15
  {% endblock %}
15
16
  </a>
16
17
  </div>
@@ -9,4 +9,6 @@
9
9
  Forgot my password
10
10
  </a>
11
11
  </div>
12
+ {% include 'django_spire/auth/element/android_and_chrome_app_install_element.html' %}
13
+ {% include 'django_spire/auth/element/ios_app_install_element.html' %}
12
14
  {% endblock %}
@@ -3,17 +3,17 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from django.contrib.contenttypes.fields import GenericRelation
6
- from django.db import models
7
6
 
8
7
  from django_spire.comment.models import Comment
8
+ from django_spire.history.activity.mixins import ActivityMixin
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from django.contrib.auth.models import User
12
12
 
13
13
 
14
- class CommentModelMixin(models.Model):
14
+ class CommentModelMixin(ActivityMixin):
15
15
  comments = GenericRelation(
16
- 'comment.Comment',
16
+ Comment,
17
17
  related_query_name='comment',
18
18
  editable=False
19
19
  )
@@ -1,30 +1,33 @@
1
1
  {% extends 'django_spire/card/title_card.html' %}
2
2
 
3
3
  {% load permission_tags %}
4
+ {% load model_tags %}
4
5
 
5
6
  {% block card_title %}
6
7
  Comments
7
8
  {% endblock %}
8
9
 
9
10
  {% block card_button %}
11
+ {% with app_label=obj|model_app_label model_name=obj|model_name %}
10
12
  {% check_permission user app_label model_name 'change' as has_access %}
11
13
  {% if has_access %}
12
14
  <span
13
- @click="dispatch_modal_view('{% url "comment:form_content" obj_pk=obj.pk comment_pk=0 app_label=app_label model_name=model_name %}')"
15
+ @click="dispatch_modal_view('{% url "django_spire:comment:form_content" obj_pk=obj.pk comment_pk=0 app_label=app_label model_name=model_name %}')"
14
16
  >
15
- {% include 'button/primary_dark_button.html' with button_logo_only='bi bi-plus' %}
16
- </span>
17
+ {% include 'django_spire/button/primary_button.html' with button_text='Add' button_icon='bi bi-plus' %}
18
+ </span>
17
19
  {% endif %}
20
+ {% endwith %}
18
21
  {% endblock %}
19
22
 
20
23
  {% block card_title_content %}
21
24
  {% with obj.comments.active.prefetch_user as comments %}
22
25
  {% if comments %}
23
26
  {% for comment in comments %}
24
- {% include 'comment/item/comment_item.html' %}
27
+ {% include 'django_spire/comment/item/comment_item.html' %}
25
28
  {% endfor %}
26
29
  {% else %}
27
- {% include 'item/no_data_item.html' %}
30
+ {% include 'django_spire/item/no_data_item.html' %}
28
31
  {% endif %}
29
32
  {% endwith %}
30
33
  {% endblock %}
@@ -1,18 +1,18 @@
1
1
  {% extends 'django_spire/modal/content/modal_title_content.html' %}
2
2
 
3
3
  {% block modal_content_title %}
4
- Comment
4
+ {{ model_name }} Comment
5
5
  {% endblock %}
6
6
 
7
7
  {% block modal_content_content %}
8
8
  <form
9
- action="{% url 'comment:form' comment_pk=comment.pk|default:0 obj_pk=obj_pk app_label=app_label model_name=model_name %}"
9
+ action="{% url 'django_spire:comment:form' comment_pk=comment.pk|default:0 obj_pk=obj_pk app_label=app_label model_name=model_name %}"
10
10
  method="post"
11
11
  >
12
12
  {% csrf_token %}
13
13
  {% include 'django_spire/comment/form/content/comment_form_content.html' %}
14
14
  <div class="pt-3">
15
- {% include 'django_spire/contrib/form/form_submit_button.html' %}
15
+ {% include 'django_spire/contrib/form/button/form_submit_button.html' %}
16
16
  </div>
17
17
  </form>
18
18
  {% endblock %}
@@ -4,6 +4,7 @@
4
4
  comment: new ModelObjectGlue('comment'),
5
5
  async init() {
6
6
  await this.comment.get()
7
+ this.comment.glue_fields.information.label = 'Message'
7
8
  }
8
9
  }"
9
10
  >
@@ -1,5 +1,6 @@
1
1
  {% extends 'django_spire/dropdown/ellipsis_dropdown.html' %}
2
2
  {% load spire_core_tags %}
3
+ {% load model_tags %}
3
4
 
4
5
  {% block dropdown_content %}
5
6
  {# {% include 'django_spire/comment/element/comment_edit_link.html' %}#}
@@ -8,14 +9,17 @@
8
9
  {# {% endif %}#}
9
10
 
10
11
  {# {% url 'comment:delete_form' pk=comment.pk as delete_url %}#}
11
- {% if comment.user_id == user.id %}
12
- {% url 'comment:form_content' comment_pk=comment.pk obj_pk=obj.pk app_label=app_label model_name=model_name as edit_url %}
13
- {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with link_text='Edit' view_url=edit_url %}
14
- {% endif %}
15
- {% if comment.user_id == user.id %}
16
- {% url 'comment:delete_form' comment_pk=comment.pk obj_pk=obj.pk app_label=app_label model_name=model_name as delete_url %}
17
- {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with link_text='Delete' link_css='text-danger' view_url=delete_url %}
18
- {% endif %}
12
+
13
+ {% with app_label=obj|model_app_label model_name=obj|model_name %}
14
+ {% if comment.user_id == user.id %}
15
+ {% url 'django_spire:comment:form_content' comment_pk=comment.pk obj_pk=obj.pk app_label=app_label model_name=model_name as edit_url %}
16
+ {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with link_text='Edit' view_url=edit_url %}
17
+ {% endif %}
18
+ {% if comment.user_id == user.id %}
19
+ {% url 'django_spire:comment:delete_form' comment_pk=comment.pk obj_pk=obj.pk app_label=app_label model_name=model_name as delete_url %}
20
+ {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with link_text='Delete' link_css='text-danger' view_url=delete_url %}
21
+ {% endif %}
22
+ {% endwith %}
19
23
  {# {% if comment.user_id == user.id %}#}
20
24
  {# {% include 'django_spire/dropdown/element/dropdown_link_element.html' with link_text='Delete' link_css='text-danger' link_href=delete_url %}#}
21
25
  {# {% endif %}#}
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ import django_glue as dg
5
6
  from django.contrib import messages
6
7
  from django.contrib.auth.decorators import login_required
7
8
  from django.http import HttpResponseRedirect
@@ -9,15 +10,14 @@ from django.shortcuts import get_object_or_404
9
10
  from django.template.response import TemplateResponse
10
11
  from django.urls import reverse
11
12
 
13
+ from django_spire.auth.group.utils import has_app_permission_or_404
12
14
  from django_spire.comment import models
13
15
  from django_spire.comment.forms import CommentForm
14
- from django_spire.core.redirect import safe_redirect_url
15
- from django_spire.core.shortcuts import get_object_or_null_obj, model_object_from_app_label
16
16
  from django_spire.contrib.form.utils import show_form_errors
17
- from django_spire.auth.group.utils import has_app_permission_or_404
18
17
  from django_spire.contrib.generic_views import dispatch_modal_delete_form_content
19
-
20
- import django_glue as dg
18
+ from django_spire.core.redirect import safe_redirect_url
19
+ from django_spire.core.shortcuts import get_object_or_null_obj, \
20
+ model_object_from_app_label
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from django.core.handlers.wsgi import WSGIRequest
@@ -112,7 +112,7 @@ def comment_modal_delete_form_view(
112
112
  messages.warning(request, 'You can only delete your comments.')
113
113
  return HttpResponseRedirect(return_url)
114
114
 
115
- form_action = reverse('comment:delete_form', kwargs={
115
+ form_action = reverse('django_spire:comment:delete_form', kwargs={
116
116
  'comment_pk': comment_pk,
117
117
  'obj_pk': obj_pk,
118
118
  'app_label': app_label,
@@ -123,8 +123,7 @@ def comment_modal_delete_form_view(
123
123
  obj.add_activity(
124
124
  user=request.user,
125
125
  verb='deleted',
126
- device=request.device,
127
- information=f'{request.user.get_full_name()} deleted a comment on "{obj}".'
126
+ information=f'{request.user.get_full_name()} deleted a comment on "{obj}".',
128
127
  )
129
128
 
130
129
  return dispatch_modal_delete_form_content(
@@ -133,4 +132,5 @@ def comment_modal_delete_form_view(
133
132
  form_action=form_action,
134
133
  activity_func=add_activity,
135
134
  return_url=return_url,
135
+ show_success_message=True
136
136
  )
django_spire/consts.py CHANGED
@@ -1,4 +1,4 @@
1
- __VERSION__ = '0.25.2'
1
+ __VERSION__ = '0.26.0'
2
2
 
3
3
  MAINTENANCE_MODE_SETTINGS_NAME = 'MAINTENANCE_MODE'
4
4
 
@@ -6,10 +6,10 @@ from django.contrib import messages
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from django.core.handlers.wsgi import WSGIRequest
9
- from django.forms import Form
9
+ from django.forms import BaseForm
10
10
 
11
11
 
12
- def form_errors_as_list(form: Form) -> list[str]:
12
+ def form_errors_as_list(form: BaseForm) -> list[str]:
13
13
  form_errors = []
14
14
 
15
15
  for field_name, error_list in form.errors.items():
@@ -34,7 +34,7 @@ def form_errors_as_list(form: Form) -> list[str]:
34
34
  return form_errors
35
35
 
36
36
 
37
- def show_form_errors(request: WSGIRequest, *forms: Form) -> None:
37
+ def show_form_errors(request: WSGIRequest, *forms: BaseForm) -> None:
38
38
  for form in forms:
39
39
  for error in form_errors_as_list(form):
40
40
  messages.error(request=request, message=error)
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from collections.abc import Sequence
4
+ from typing import Any
4
5
 
5
- if TYPE_CHECKING:
6
- from django.db.models import QuerySet
6
+ from django.contrib.contenttypes.fields import GenericForeignKey
7
+ from django.db.models import QuerySet, Field, ForeignObjectRel, ForeignKey
7
8
 
8
9
 
9
10
  def filter_by_lookup_map(
@@ -16,19 +17,15 @@ def filter_by_lookup_map(
16
17
  Filters a given queryset based on a lookup map and provided data. Additional filters
17
18
  can also be applied if provided.
18
19
 
19
- Parameters:
20
- queryset (QuerySet): The queryset to be filtered.
21
- lookup_map (dict): A dictionary mapping input data keys to filtering fields in
22
- the queryset.
23
- data (dict): A dictionary containing key-value pairs to filter the queryset.
24
- extra_filters (list | None): Optional. A list of extra positional filter
25
- arguments to be applied. Defaults to None.
26
-
27
- Returns:
28
- QuerySet: The filtered queryset.
20
+ :param queryset (QuerySet): The queryset to be filtered.
21
+ :param lookup_map (dict): A dictionary mapping input data keys to filtering fields in
22
+ the queryset.
23
+ :param data (dict): A dictionary containing key-value pairs to filter the queryset.
24
+ :param extra_filters (list | None): Optional. A list of extra positional filter
25
+ arguments to be applied. Defaults to None.
29
26
 
27
+ :returns: QuerySet: The filtered queryset.
30
28
  """
31
-
32
29
  if extra_filters is None:
33
30
  extra_filters = {}
34
31
 
@@ -39,3 +36,48 @@ def filter_by_lookup_map(
39
36
  } | extra_filters
40
37
 
41
38
  return queryset.filter(**lookup_kwargs)
39
+
40
+
41
+ def _get_kwarg_name_for_filter_field(
42
+ field: Field[Any, Any] | ForeignObjectRel | GenericForeignKey,
43
+ val: Any
44
+ ) -> str:
45
+ """
46
+ Determines the appropriate keyword argument name for filtering based on the field type
47
+ and value type.
48
+
49
+ :param field (Field | ForeignObjectRel | GenericForeignKey): The model field to generate
50
+ the filter keyword argument for.
51
+ :param val (Any): The value to be used for filtering.
52
+
53
+ :returns: str: The keyword argument string suitable for filtering.
54
+ """
55
+ if isinstance(val, Sequence):
56
+ if isinstance(field, ForeignKey):
57
+ return f'{field.name}_id__in'
58
+ return f'{field.name}__in'
59
+ else:
60
+ return field.name
61
+
62
+
63
+ def filter_by_model_fields(queryset: QuerySet, data: dict) -> QuerySet:
64
+ """
65
+ Filters a given queryset based on the queryset's model fields and provided data.
66
+
67
+ :param queryset (QuerySet): The queryset to be filtered.
68
+ :param data (dict): A dictionary containing key-value pairs to filter the queryset.
69
+
70
+ :returns: QuerySet: The filtered queryset.
71
+ """
72
+ model_fields = [f for f in queryset.model._meta.get_fields()]
73
+
74
+ lookup_kwargs = {
75
+ _get_kwarg_name_for_filter_field(
76
+ field=model_field,
77
+ val=data[model_field.name]
78
+ ): data[model_field.name]
79
+ for model_field in model_fields
80
+ if model_field.name in data and data[model_field.name] not in (None, "", [])
81
+ }
82
+
83
+ return queryset.filter(**lookup_kwargs)
@@ -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