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.
- 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/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/querysets.py +19 -0
- 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/metric/report/tools.py +0 -2
- {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/METADATA +1 -1
- {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/RECORD +89 -45
- django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template +0 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template +0 -18
- {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/WHEEL +0 -0
- {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.25.2.dist-info → django_spire-0.26.0.dist-info}/top_level.txt +0 -0
django_spire/auth/templates/django_spire/auth/element/android_and_chrome_app_install_element.html
ADDED
|
@@ -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.
|
|
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>
|
django_spire/comment/mixins.py
CHANGED
|
@@ -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(
|
|
14
|
+
class CommentModelMixin(ActivityMixin):
|
|
15
15
|
comments = GenericRelation(
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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 %}
|
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
{%
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{%
|
|
17
|
-
{%
|
|
18
|
-
|
|
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 %}#}
|
django_spire/comment/views.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
@@ -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
|
|
9
|
+
from django.forms import BaseForm
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def form_errors_as_list(form:
|
|
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:
|
|
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
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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)
|