django-spire 0.18.3__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +1 -0
- django_spire/ai/chat/apps.py +1 -8
- django_spire/ai/chat/intelligence/decoders/tools.py +29 -0
- django_spire/ai/chat/intelligence/prompts.py +13 -7
- django_spire/ai/chat/intelligence/workflows/chat_workflow.py +40 -52
- django_spire/ai/chat/models.py +2 -1
- django_spire/ai/chat/querysets.py +1 -3
- django_spire/ai/chat/responses.py +5 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/card/chat_card.html +2 -2
- django_spire/ai/chat/templates/django_spire/ai/chat/dropdown/ellipsis_dropdown.html +1 -1
- django_spire/ai/chat/templates/django_spire/ai/chat/element/recent_chat_select_element.html +82 -44
- django_spire/ai/chat/templates/django_spire/ai/chat/message/loading_response_message.html +8 -3
- django_spire/ai/chat/templates/django_spire/ai/chat/message/message.html +16 -7
- django_spire/ai/chat/templates/django_spire/ai/chat/message/request_message.html +5 -8
- django_spire/ai/chat/templates/django_spire/ai/chat/message/response_message.html +14 -10
- django_spire/ai/chat/templates/django_spire/ai/chat/page/chat_page.html +35 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +72 -23
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/selection_widget.html +42 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +4 -2
- django_spire/ai/chat/urls/__init__.py +1 -1
- django_spire/ai/chat/urls/json_urls.py +1 -1
- django_spire/ai/chat/urls/page_urls.py +1 -1
- django_spire/ai/chat/urls/{template_urls.py → template/template_urls.py} +1 -1
- django_spire/ai/chat/views/json_views.py +4 -3
- django_spire/ai/chat/views/message_request_views.py +32 -14
- django_spire/ai/chat/views/message_response_views.py +14 -11
- django_spire/ai/chat/views/message_views.py +7 -1
- django_spire/ai/chat/views/page_views.py +2 -2
- django_spire/ai/chat/views/{template_views.py → template/template_views.py} +1 -7
- django_spire/ai/context/__init__.py +0 -0
- django_spire/ai/context/admin.py +15 -0
- django_spire/ai/context/apps.py +16 -0
- django_spire/ai/context/choices.py +11 -0
- django_spire/ai/context/intelligence/__init__.py +0 -0
- django_spire/ai/context/intelligence/prompts/__init__.py +0 -0
- django_spire/ai/context/intelligence/prompts/organization_prompts.py +19 -0
- django_spire/ai/context/migrations/0001_initial.py +67 -0
- django_spire/ai/context/migrations/__init__.py +0 -0
- django_spire/ai/context/models.py +67 -0
- django_spire/ai/context/querysets.py +15 -0
- django_spire/ai/context/seeding/__init__.py +0 -0
- django_spire/ai/context/seeding/seed.py +24 -0
- django_spire/ai/prompt/system/bots.py +3 -5
- django_spire/ai/prompt/tuning/bots.py +5 -10
- django_spire/ai/sms/admin.py +2 -0
- django_spire/ai/sms/apps.py +2 -7
- django_spire/ai/sms/decorators.py +2 -2
- django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +18 -18
- django_spire/ai/sms/tests/test_webhook.py +5 -4
- django_spire/ai/sms/urls.py +3 -0
- django_spire/ai/sms/views.py +3 -5
- django_spire/ai/urls.py +2 -0
- django_spire/auth/templates/django_spire/auth/form/login_form.html +25 -0
- django_spire/auth/templates/django_spire/auth/page/auth_page.html +2 -3
- django_spire/auth/templates/django_spire/auth/page/login_page.html +1 -25
- django_spire/conf.py +1 -3
- django_spire/consts.py +1 -7
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +3 -2
- django_spire/contrib/utils.py +2 -0
- django_spire/core/static/django_spire/css/app-button.css +11 -1
- django_spire/core/static/django_spire/css/app-import.css +2 -0
- django_spire/core/static/django_spire/css/app-layout.css +15 -0
- django_spire/core/static/django_spire/css/app-navigation.css +24 -2
- django_spire/core/static/django_spire/css/app-page.css +8 -0
- django_spire/core/static/django_spire/css/app-side-panel.css +102 -0
- django_spire/core/static/django_spire/css/app-text.css +19 -1
- django_spire/core/static/django_spire/css/bootstrap-extension.css +128 -7
- django_spire/core/static/django_spire/css/bootstrap-override.css +161 -111
- django_spire/core/static/django_spire/css/themes/ayu/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/ayu/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/catppuccin/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/catppuccin/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/default/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/default/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/dracula/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/dracula/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/gruvbox/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/gruvbox/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/material/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/material/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/nord/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/nord/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/oceanic-next/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/oceanic-next/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/one-dark/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/one-dark/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/palenight/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/palenight/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/rose-pine/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/rose-pine/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/synthwave/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/synthwave/app-light.css +0 -4
- django_spire/core/static/django_spire/css/themes/tokyo-night/app-dark.css +0 -4
- django_spire/core/static/django_spire/css/themes/tokyo-night/app-light.css +0 -4
- django_spire/core/static/django_spire/font/Poppins-Black.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-BlackItalic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-Bold.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-BoldItalic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-ExtraBold.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-ExtraBoldItalic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-ExtraLight.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-ExtraLightItalic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-Italic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-Light.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-LightItalic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-Medium.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-MediumItalic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-SemiBold.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-SemiBoldItalic.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-Thin.ttf +0 -0
- django_spire/core/static/django_spire/font/Poppins-ThinItalic.ttf +0 -0
- django_spire/core/static/django_spire/js/ui.js +12 -0
- django_spire/core/templates/django_spire/button/base_button.html +1 -1
- django_spire/core/templates/django_spire/button/primary_dark_outlined_button.html +3 -0
- django_spire/core/templates/django_spire/dropdown/element/dropdown_link_element.html +12 -8
- django_spire/core/templates/django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html +2 -1
- django_spire/core/templates/django_spire/dropdown/ellipsis_dropdown.html +2 -1
- django_spire/core/templates/django_spire/navigation/accordion/nav_accordion.html +1 -1
- django_spire/core/templates/django_spire/navigation/elements/nav_link.html +1 -1
- django_spire/core/templates/django_spire/navigation/elements/nav_title_divider.html +1 -1
- django_spire/core/templates/django_spire/navigation/side_navigation.html +52 -8
- django_spire/core/templates/django_spire/navigation/top_navigation.html +8 -2
- django_spire/core/templates/django_spire/page/full_page.html +218 -11
- django_spire/core/utils.py +26 -2
- django_spire/knowledge/collection/models.py +24 -0
- django_spire/knowledge/collection/services/transformation_service.py +2 -1
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +44 -42
- django_spire/knowledge/collection/urls/form_urls.py +1 -0
- django_spire/knowledge/collection/urls/page_urls.py +1 -0
- django_spire/knowledge/collection/views/form_views.py +13 -2
- django_spire/knowledge/collection/views/page_views.py +36 -4
- django_spire/knowledge/entry/models.py +32 -0
- django_spire/knowledge/entry/services/transformation_services.py +10 -10
- django_spire/knowledge/entry/version/block/data/data.py +2 -1
- django_spire/knowledge/entry/version/block/data/heading_data.py +2 -2
- django_spire/knowledge/entry/version/block/data/list/data.py +3 -4
- django_spire/knowledge/entry/version/block/data/maps.py +3 -5
- django_spire/knowledge/entry/version/block/data/text_data.py +2 -2
- django_spire/knowledge/entry/version/block/models.py +10 -7
- django_spire/knowledge/entry/version/block/services/factory_service.py +11 -10
- django_spire/knowledge/entry/version/block/tests/factories.py +6 -5
- django_spire/knowledge/entry/version/converters/docx_converter.py +1 -1
- django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +14 -12
- django_spire/knowledge/entry/version/seeding/seeder.py +3 -3
- django_spire/knowledge/entry/version/services/processor_service.py +36 -10
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +1 -1
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +1 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +1 -1
- django_spire/knowledge/entry/version/views/json_views.py +5 -3
- django_spire/knowledge/entry/version/views/page_views.py +11 -12
- django_spire/knowledge/entry/version/views/redirect_views.py +1 -1
- django_spire/knowledge/entry/views/form_views.py +1 -1
- django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +33 -11
- django_spire/knowledge/intelligence/decoders/__init__.py +0 -0
- django_spire/knowledge/intelligence/{maps/collection_map.py → decoders/collection_decoder.py} +5 -5
- django_spire/knowledge/intelligence/{maps/entry_map.py → decoders/entry_decoder.py} +3 -3
- django_spire/knowledge/intelligence/intel/collection_intel.py +1 -0
- django_spire/knowledge/intelligence/intel/entry_intel.py +5 -2
- django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +43 -51
- django_spire/knowledge/migrations/0007_alter_collection_options.py +17 -0
- django_spire/knowledge/static/django_spire/knowledge/entry/version/js/editor.js +24 -5
- django_spire/knowledge/templates/django_spire/knowledge/collection/card/top_level_list_card.html +21 -0
- django_spire/knowledge/templates/django_spire/knowledge/collection/component/x_collection_navigation.html +15 -6
- django_spire/knowledge/templates/django_spire/knowledge/collection/element/ellipsis_dropdown.html +22 -0
- django_spire/knowledge/templates/django_spire/knowledge/collection/form/form.html +21 -9
- django_spire/knowledge/templates/django_spire/knowledge/collection/item/collection_item.html +19 -0
- django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html +49 -0
- django_spire/knowledge/templates/django_spire/knowledge/collection/page/form_page.html +1 -1
- django_spire/knowledge/templates/django_spire/knowledge/container/container.html +33 -0
- django_spire/knowledge/templates/django_spire/knowledge/entry/file/page/list_page.html +1 -1
- django_spire/knowledge/templates/django_spire/knowledge/entry/item/list_item.html +2 -2
- django_spire/knowledge/templates/django_spire/knowledge/entry/page/form_page.html +1 -1
- django_spire/knowledge/templates/django_spire/knowledge/entry/page/import_form_page.html +1 -1
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/container/detail_container.html +51 -45
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +54 -0
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/form_page.html +1 -1
- django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +18 -1
- django_spire/knowledge/templates/django_spire/knowledge/page/full_page.html +37 -0
- django_spire/knowledge/templates/django_spire/knowledge/page/home_page.html +2 -2
- django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/collection_sub_navigation_item.html +29 -0
- django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/item/entry_sub_navigation_item.html +20 -0
- django_spire/knowledge/templates/django_spire/knowledge/{navigation/content/navigation_content.html → sub_navigation/widget/collection_entry_sub_navigation_widget.html} +42 -19
- django_spire/knowledge/views/page_views.py +4 -4
- django_spire/settings.py +3 -3
- django_spire/theme/templates/django_spire/theme/card/badges_preview_card.html +0 -1
- django_spire/theme/templates/django_spire/theme/card/base_preview_card.html +1 -0
- django_spire/theme/templates/django_spire/theme/card/typography_preview_card.html +1 -1
- django_spire/theme/templates/django_spire/theme/example/form/example_form.html +2 -0
- django_spire/theme/templates/django_spire/theme/example/form/example_form_card.html +5 -0
- django_spire/theme/templates/django_spire/theme/page/badges_page.html +7 -9
- django_spire/theme/templates/django_spire/theme/page/borders_page.html +6 -9
- django_spire/theme/templates/django_spire/theme/page/buttons_page.html +6 -9
- django_spire/theme/templates/django_spire/theme/page/colors_page.html +6 -6
- django_spire/theme/templates/django_spire/theme/page/dashboard_page.html +57 -0
- django_spire/theme/templates/django_spire/theme/page/django_glue_page.html +7 -9
- django_spire/theme/templates/django_spire/theme/page/theme_page.html +24 -0
- django_spire/theme/templates/django_spire/theme/page/typography_page.html +6 -9
- django_spire/theme/templates/django_spire/theme/section/badge_section_card.html +5 -0
- django_spire/theme/templates/django_spire/theme/section/border_section_card.html +5 -0
- django_spire/theme/templates/django_spire/theme/section/button_section.html +1 -0
- django_spire/theme/templates/django_spire/theme/section/button_section_card.html +5 -0
- django_spire/theme/templates/django_spire/theme/section/color_section.html +10 -10
- django_spire/theme/templates/django_spire/theme/section/color_section_card.html +5 -0
- django_spire/theme/templates/django_spire/theme/section/typography_section.html +41 -1
- django_spire/theme/templates/django_spire/theme/section/typography_section_card.html +5 -0
- {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/METADATA +3 -3
- {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/RECORD +217 -178
- django_spire/ai/chat/intelligence/bots/chat_bot.py +0 -8
- django_spire/ai/chat/intelligence/maps/intent_llm_map.py +0 -10
- django_spire/ai/chat/templates/django_spire/ai/chat/page/home_page.html +0 -9
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/chat_widget.html +0 -64
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/search_chat_results_widget.html +0 -25
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/search_chat_widget.html +0 -39
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/select_chat_widget.html +0 -24
- django_spire/ai/chat/tools.py +0 -57
- django_spire/ai/sms/tools.py +0 -57
- django_spire/core/static/django_spire/font/Karla-Bold.ttf +0 -0
- django_spire/core/static/django_spire/font/Merriweather.ttf +0 -0
- django_spire/knowledge/context_processors.py +0 -18
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/detail_page.html +0 -25
- django_spire/knowledge/templates/django_spire/knowledge/navigation/card/navigation_card.html +0 -9
- django_spire/knowledge/templates/django_spire/knowledge/navigation/item/collection/collection_item.html +0 -29
- django_spire/knowledge/templates/django_spire/knowledge/navigation/item/entry/entry_item.html +0 -18
- django_spire/knowledge/templates/django_spire/knowledge/navigation/page/full_page.html +0 -90
- /django_spire/ai/chat/intelligence/{bots → decoders}/__init__.py +0 -0
- /django_spire/ai/chat/{intelligence/maps → urls/template}/__init__.py +0 -0
- /django_spire/{knowledge/intelligence/maps → ai/chat/views/template}/__init__.py +0 -0
- /django_spire/knowledge/entry/version/{constants.py → consts.py} +0 -0
- /django_spire/knowledge/templates/django_spire/knowledge/{navigation/item/collection/dropdown/navigation_ellipsis_dropdown.html → sub_navigation/element/collection_sub_navigation_ellipsis_dropdown.html} +0 -0
- /django_spire/knowledge/templates/django_spire/knowledge/{navigation/item/entry/dropdown/navigation_ellipsis_dropdown.html → sub_navigation/element/entry_sub_navigation_ellipsis_dropdown.html} +0 -0
- {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/WHEEL +0 -0
- {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.18.3.dist-info → django_spire-0.19.0.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{% load static %}
|
|
2
2
|
|
|
3
|
-
<div class="top-navigation row justify-content-between align-items-center p-1 p-md-2 mb-
|
|
3
|
+
<div class="top-navigation row justify-content-between align-items-center p-1 p-md-2 mb-2 border-bottom shadow-sm">
|
|
4
4
|
{% block top_navigation_content %}
|
|
5
5
|
<div class="col">
|
|
6
6
|
<div class="row">
|
|
7
7
|
<div class="col">
|
|
8
8
|
<a href="/" class="d-block mb-3">
|
|
9
|
-
<span class="fs-5 fs-md-1 fw-bold d-
|
|
9
|
+
<span class="fs-5 fs-md-1 fw-bold d-lg-block float-start">
|
|
10
10
|
{% block top_navigation_title %}
|
|
11
11
|
Django Spire
|
|
12
12
|
{% endblock %}
|
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
Admin Panel
|
|
60
60
|
</a>
|
|
61
61
|
</li>
|
|
62
|
+
<li class="py-0 ">
|
|
63
|
+
<a class="dropdown-item fs--1 bg-app-layer-three-hover"
|
|
64
|
+
href="{% url 'django_spire:theme:page:dashboard' %}" target="_blank">
|
|
65
|
+
Theme Dashboard
|
|
66
|
+
</a>
|
|
67
|
+
</li>
|
|
62
68
|
{% endif %}
|
|
63
69
|
<li class="py-0">
|
|
64
70
|
<a href="{% url 'django_spire:auth:admin:password_change' %}"
|
|
@@ -1,35 +1,242 @@
|
|
|
1
1
|
{% extends 'django_spire/page/page.html' %}
|
|
2
2
|
|
|
3
3
|
{% block page_content %}
|
|
4
|
-
<div
|
|
5
|
-
|
|
4
|
+
<div
|
|
5
|
+
@mousemove.window="
|
|
6
|
+
if (is_resizing_sub && !is_mobile) {
|
|
7
|
+
let delta = $event.clientX - resize_start_x;
|
|
8
|
+
sub_nav_width = resize_start_width + delta;
|
|
9
|
+
|
|
10
|
+
if (sub_nav_width < sub_nav_min_width) sub_nav_width = sub_nav_min_width;
|
|
11
|
+
if (sub_nav_width > sub_nav_max_width) sub_nav_width = sub_nav_max_width;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (is_resizing_info && !is_mobile) {
|
|
15
|
+
let delta = resize_start_x - $event.clientX;
|
|
16
|
+
info_nav_width = resize_start_width + delta;
|
|
17
|
+
|
|
18
|
+
if (info_nav_width < info_nav_min_width) info_nav_width = info_nav_min_width;
|
|
19
|
+
if (info_nav_width > info_nav_max_width) info_nav_width = info_nav_max_width;
|
|
20
|
+
}
|
|
21
|
+
"
|
|
22
|
+
@mouseup.window="is_resizing_sub = false; is_resizing_info = false"
|
|
23
|
+
class="d-flex min-vh-100 w-100"
|
|
24
|
+
x-data="{
|
|
25
|
+
info_nav_max_width: 600,
|
|
26
|
+
info_nav_min_width: 200,
|
|
27
|
+
info_nav_width: 400,
|
|
28
|
+
is_mobile: window.innerWidth < 992,
|
|
29
|
+
is_resizing_info: false,
|
|
30
|
+
is_resizing_sub: false,
|
|
31
|
+
resize_start_width: 0,
|
|
32
|
+
resize_start_x: 0,
|
|
33
|
+
show_info_nav: false,
|
|
34
|
+
show_sub_nav: true,
|
|
35
|
+
sub_nav_has_been_resized: false,
|
|
36
|
+
sub_nav_max_width: 600,
|
|
37
|
+
sub_nav_min_width: 300,
|
|
38
|
+
sub_nav_width: 300,
|
|
39
|
+
|
|
40
|
+
init() {
|
|
41
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
42
|
+
|
|
43
|
+
if (urlParams.has('show_sub_nav')) {
|
|
44
|
+
this.show_sub_nav = urlParams.get('show_sub_nav') === 'true'
|
|
45
|
+
} else {
|
|
46
|
+
this.show_sub_nav = window.innerWidth >= 992
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
window.addEventListener('resize', () => {
|
|
50
|
+
this.is_mobile = window.innerWidth < 992
|
|
51
|
+
if (this.is_mobile) {
|
|
52
|
+
this.show_sub_nav = false
|
|
53
|
+
this.show_info_nav = false
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
start_resize_info(event) {
|
|
59
|
+
if (!this.is_mobile) {
|
|
60
|
+
this.is_resizing_info = true
|
|
61
|
+
this.resize_start_x = event.clientX
|
|
62
|
+
this.resize_start_width = this.info_nav_width
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
start_resize_sub(event) {
|
|
67
|
+
if (!this.is_mobile) {
|
|
68
|
+
let current_width = $refs.sub_nav_container.offsetWidth
|
|
69
|
+
this.sub_nav_width = current_width
|
|
70
|
+
this.sub_nav_has_been_resized = true
|
|
71
|
+
this.is_resizing_sub = true
|
|
72
|
+
this.resize_start_x = event.clientX
|
|
73
|
+
this.resize_start_width = current_width
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
toggle_info() {
|
|
78
|
+
this.show_info_nav = !this.show_info_nav
|
|
79
|
+
|
|
80
|
+
if (this.is_mobile) {
|
|
81
|
+
this.show_sub_nav = false
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
toggle_sub() {
|
|
86
|
+
this.show_sub_nav = !this.show_sub_nav
|
|
87
|
+
|
|
88
|
+
if (this.is_mobile) {
|
|
89
|
+
this.show_info_nav = false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}"
|
|
93
|
+
>
|
|
94
|
+
<div class="d-none d-lg-block pe-1 side-navigation">
|
|
6
95
|
{% block full_page_side_navigation %}
|
|
7
96
|
{% include 'django_spire/navigation/side_navigation.html' %}
|
|
8
97
|
{% endblock %}
|
|
9
98
|
</div>
|
|
10
99
|
|
|
11
|
-
<div class="d-flex flex-column
|
|
100
|
+
<div class="container-fluid d-flex flex-column">
|
|
12
101
|
<div class="row sticky-top">
|
|
13
|
-
<div class="col-12
|
|
102
|
+
<div class="col-12">
|
|
14
103
|
{% block full_page_top_navigation %}
|
|
15
104
|
{% include 'django_spire/navigation/top_navigation.html' %}
|
|
16
105
|
{% endblock %}
|
|
17
106
|
</div>
|
|
18
107
|
</div>
|
|
19
108
|
|
|
20
|
-
<div class="row
|
|
21
|
-
<div
|
|
109
|
+
<div class="row">
|
|
110
|
+
<div
|
|
111
|
+
:class="is_mobile ? '' : 'position-relative col-auto'"
|
|
112
|
+
:style="is_mobile ? 'position: fixed; width: 100vw; height: 100vh; left: 0; top: 0; z-index: 1040;' : (sub_nav_has_been_resized ? `width: ${sub_nav_width}px; min-width: ${sub_nav_width}px; max-width: ${sub_nav_width}px;` : `min-width: ${sub_nav_min_width}px;`)"
|
|
113
|
+
x-ref="sub_nav_container"
|
|
114
|
+
x-show="has_content($refs.full_page_sub_navigation) && show_sub_nav"
|
|
115
|
+
x-transition:enter="side-panel-transition-enter"
|
|
116
|
+
x-transition:enter-end="side-panel-transition-enter-end"
|
|
117
|
+
x-transition:enter-start="side-panel-transition-enter-start-left"
|
|
118
|
+
x-transition:leave="side-panel-transition-leave"
|
|
119
|
+
x-transition:leave-end="side-panel-transition-leave-end-left"
|
|
120
|
+
x-transition:leave-start="side-panel-transition-leave-start"
|
|
121
|
+
>
|
|
122
|
+
<div
|
|
123
|
+
:style="is_mobile ? 'height: 100vh;' : ''"
|
|
124
|
+
class="row sticky-top border-end border-app-primary side-panel overflow-y-auto"
|
|
125
|
+
>
|
|
126
|
+
<div class="col">
|
|
127
|
+
<div class="row pb-2 align-items-center">
|
|
128
|
+
<div class="col h5 text-app-primary text-center text-nowrap overflow-hidden text-truncate mb-0" style="min-width: 0;">
|
|
129
|
+
{% block full_page_sub_navigation_title %}
|
|
130
|
+
{% endblock %}
|
|
131
|
+
</div>
|
|
132
|
+
<div class="col-auto px-1">
|
|
133
|
+
{% block full_page_sub_navigation_buttons %}
|
|
134
|
+
{% endblock %}
|
|
135
|
+
</div>
|
|
136
|
+
<div class="col-auto px-1">
|
|
137
|
+
<button
|
|
138
|
+
@click="show_sub_nav = false"
|
|
139
|
+
class="btn-close-panel"
|
|
140
|
+
type="button"
|
|
141
|
+
>
|
|
142
|
+
<i class="bi bi-x-lg f5"></i>
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div class="row">
|
|
148
|
+
<div class="col" x-ref="full_page_sub_navigation">
|
|
149
|
+
{% block full_page_sub_navigation %}
|
|
150
|
+
{% endblock %}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
<div
|
|
156
|
+
@mousedown="start_resize_sub($event)"
|
|
157
|
+
class="position-absolute top-0 d-flex align-items-center justify-content-center"
|
|
158
|
+
style="cursor: ew-resize; height: 100%; right: -8px; user-select: none; width: 16px; z-index: 1000;"
|
|
159
|
+
x-show="!is_mobile"
|
|
160
|
+
>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<div
|
|
165
|
+
class="panel-toggle-container panel-toggle-container-left"
|
|
166
|
+
x-show="has_content($refs.full_page_sub_navigation) && !show_sub_nav && !(is_mobile && show_info_nav)"
|
|
167
|
+
>
|
|
168
|
+
{% include 'django_spire/button/primary_button.html' with button_icon='bi bi-chevron-right' x_button_click='toggle_sub()' %}
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div class="col">
|
|
22
172
|
{% block full_page_content %}
|
|
23
173
|
{% endblock %}
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
174
|
|
|
27
|
-
<div class="row">
|
|
28
|
-
<div class="col-12 px-0 mt-2">
|
|
29
175
|
{% block full_page_bottom_content %}
|
|
30
|
-
{% include 'django_spire/footer/footer.html' %}
|
|
31
176
|
{% endblock %}
|
|
32
177
|
</div>
|
|
178
|
+
|
|
179
|
+
<div
|
|
180
|
+
class="col-auto panel-toggle-container panel-toggle-container-right"
|
|
181
|
+
x-show="has_content($refs.full_page_info_navigation) && !show_info_nav && !(is_mobile && show_sub_nav)"
|
|
182
|
+
>
|
|
183
|
+
{% include 'django_spire/button/primary_button.html' with button_icon='bi bi-chevron-left' x_button_click='toggle_info()' %}
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div
|
|
187
|
+
:class="is_mobile ? '' : 'position-relative col-auto'"
|
|
188
|
+
:style="is_mobile ? 'position: fixed; width: 100vw; height: 100vh; right: 0; top: 0; z-index: 1040;' : `width: ${info_nav_width}px; min-width: ${info_nav_width}px; max-width: ${info_nav_width}px; margin-left: -${info_nav_width}px; z-index: 1040;`"
|
|
189
|
+
x-show="has_content($refs.full_page_info_navigation) && show_info_nav"
|
|
190
|
+
x-transition:enter="side-panel-transition-enter"
|
|
191
|
+
x-transition:enter-end="side-panel-transition-enter-end"
|
|
192
|
+
x-transition:enter-start="side-panel-transition-enter-start-right"
|
|
193
|
+
x-transition:leave="side-panel-transition-leave"
|
|
194
|
+
x-transition:leave-end="side-panel-transition-leave-end-right"
|
|
195
|
+
x-transition:leave-start="side-panel-transition-leave-start"
|
|
196
|
+
>
|
|
197
|
+
<div
|
|
198
|
+
:style="is_mobile ? 'height: 100vh;' : ''"
|
|
199
|
+
class="row sticky-top border-start border-app-primary side-panel overflow-y-auto bg-app-layer-one"
|
|
200
|
+
>
|
|
201
|
+
<div class="col">
|
|
202
|
+
<div class="row pb-2">
|
|
203
|
+
<div class="col text-center pt-1 h5 text-app-primary">
|
|
204
|
+
{% block full_page_info_navigation_title %}
|
|
205
|
+
{% endblock %}
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div class="col-auto px-1">
|
|
209
|
+
{% block full_page_info_navigation_buttons %}
|
|
210
|
+
{% endblock %}
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div class="col-auto px-1">
|
|
214
|
+
<button
|
|
215
|
+
@click="show_info_nav = false"
|
|
216
|
+
class="btn-close-panel"
|
|
217
|
+
type="button"
|
|
218
|
+
>
|
|
219
|
+
<i class="bi bi-x-lg"></i>
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="row">
|
|
225
|
+
<div class="col" x-ref="full_page_info_navigation">
|
|
226
|
+
{% block full_page_info_navigation %}
|
|
227
|
+
{% endblock %}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div
|
|
234
|
+
@mousedown="start_resize_info($event)"
|
|
235
|
+
class="position-absolute top-0"
|
|
236
|
+
style="cursor: ew-resize; height: 100%; left: -8px; user-select: none; width: 16px; z-index: 1000;"
|
|
237
|
+
x-show="!is_mobile"
|
|
238
|
+
></div>
|
|
239
|
+
</div>
|
|
33
240
|
</div>
|
|
34
241
|
</div>
|
|
35
242
|
</div>
|
django_spire/core/utils.py
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
import inspect
|
|
2
|
+
from inspect import Signature
|
|
3
|
+
from typing import Callable, Any, Sequence
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_object_from_module_string(module_string: str) -> Any:
|
|
2
7
|
try:
|
|
3
8
|
module_string, object_name = module_string.rsplit('.', 1)
|
|
4
9
|
module = __import__(module_string, fromlist=[object_name])
|
|
5
10
|
except ImportError:
|
|
6
11
|
raise ImportError(f'Could not import module: {module_string}')
|
|
7
12
|
|
|
8
|
-
return getattr(module, object_name)
|
|
13
|
+
return getattr(module, object_name)
|
|
14
|
+
|
|
15
|
+
def get_callable_from_module_string_and_validate_arguments(
|
|
16
|
+
module_string: str,
|
|
17
|
+
valid_args: Sequence[str],
|
|
18
|
+
) -> Callable:
|
|
19
|
+
callable_ = get_object_from_module_string(module_string)
|
|
20
|
+
|
|
21
|
+
if not callable(callable_):
|
|
22
|
+
raise TypeError(f'Object {module_string} is not callable')
|
|
23
|
+
|
|
24
|
+
sig = inspect.signature(callable_)
|
|
25
|
+
params = sig.parameters
|
|
26
|
+
|
|
27
|
+
for name in valid_args:
|
|
28
|
+
if name not in params:
|
|
29
|
+
message = f'{callable_.__qualname__} is missing required argument: {name}'
|
|
30
|
+
raise TypeError(message)
|
|
31
|
+
|
|
32
|
+
return callable_
|
|
@@ -3,7 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from django.db import models
|
|
4
4
|
|
|
5
5
|
from django_spire.auth.group.models import AuthGroup
|
|
6
|
+
from django_spire.contrib import Breadcrumbs
|
|
6
7
|
from django_spire.contrib.ordering.mixins import OrderingModelMixin
|
|
8
|
+
from django_spire.contrib.utils import truncate_string
|
|
7
9
|
from django_spire.history.mixins import HistoryModelMixin
|
|
8
10
|
from django_spire.knowledge.collection.querysets import CollectionQuerySet
|
|
9
11
|
from django_spire.knowledge.collection.services.service import CollectionGroupService, \
|
|
@@ -29,6 +31,24 @@ class Collection(HistoryModelMixin, OrderingModelMixin):
|
|
|
29
31
|
def __str__(self):
|
|
30
32
|
return self.name
|
|
31
33
|
|
|
34
|
+
@property
|
|
35
|
+
def name_short(self) -> str:
|
|
36
|
+
return truncate_string(self.name, 32)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def top_level_parent(self) -> Collection:
|
|
40
|
+
if self.parent is None:
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
return self.parent.top_level_parent
|
|
44
|
+
|
|
45
|
+
def base_breadcrumb(self) -> Breadcrumbs:
|
|
46
|
+
breadcrumbs = Breadcrumbs()
|
|
47
|
+
|
|
48
|
+
breadcrumbs.add_breadcrumb(self.name_short)
|
|
49
|
+
|
|
50
|
+
return breadcrumbs
|
|
51
|
+
|
|
32
52
|
class Meta:
|
|
33
53
|
verbose_name = 'Collection'
|
|
34
54
|
verbose_name_plural = 'Collections'
|
|
@@ -37,6 +57,7 @@ class Collection(HistoryModelMixin, OrderingModelMixin):
|
|
|
37
57
|
('can_access_all_collections', 'Can Access All Collections'),
|
|
38
58
|
('can_change_collection_groups', 'Can Change Collection Groups'),
|
|
39
59
|
]
|
|
60
|
+
ordering = ['name']
|
|
40
61
|
|
|
41
62
|
|
|
42
63
|
class CollectionGroup(models.Model):
|
|
@@ -55,3 +76,6 @@ class CollectionGroup(models.Model):
|
|
|
55
76
|
)
|
|
56
77
|
|
|
57
78
|
services = CollectionGroupService()
|
|
79
|
+
|
|
80
|
+
def __str__(self):
|
|
81
|
+
return f'{self.collection.name} - {self.auth_group.name}'
|
|
@@ -19,10 +19,11 @@ if TYPE_CHECKING:
|
|
|
19
19
|
class CollectionTransformationService(BaseDjangoModelService['Collection']):
|
|
20
20
|
obj: Collection
|
|
21
21
|
|
|
22
|
-
def to_hierarchy_json(self, request: WSGIRequest) -> str:
|
|
22
|
+
def to_hierarchy_json(self, request: WSGIRequest, parent_id: int | None = None) -> str:
|
|
23
23
|
collections = (
|
|
24
24
|
self.obj_class.objects
|
|
25
25
|
.active()
|
|
26
|
+
.by_parent_id(parent_id=parent_id)
|
|
26
27
|
.request_user_has_access(request)
|
|
27
28
|
.select_related('parent')
|
|
28
29
|
)
|
|
@@ -25,45 +25,47 @@ class TestCollectionTransformationService(BaseTestCase):
|
|
|
25
25
|
response = self.client.get(collection_json['import_entry_url'].replace(' ', ''))
|
|
26
26
|
self.assertEqual(response.status_code, 200)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
28
|
+
# Todo: Talk to Chase about this
|
|
29
|
+
# def test_to_hierarchy_json(self):
|
|
30
|
+
# settings.SITE_ID = self.site.id
|
|
31
|
+
#
|
|
32
|
+
# self.test_collection_1 = Collection.objects.create(name='Grand Parent A', id=1, parent_id=None)
|
|
33
|
+
# self.test_collection_2 = Collection.objects.create(name='Parent A1', id=2, parent_id=1)
|
|
34
|
+
# self.test_collection_3 = Collection.objects.create(name='Child A1a', id=3, parent_id=2)
|
|
35
|
+
#
|
|
36
|
+
# request = RequestFactory().get('/')
|
|
37
|
+
# request.user = self.super_user
|
|
38
|
+
#
|
|
39
|
+
# family_tree = Collection.services.transformation.to_hierarchy_json(
|
|
40
|
+
# request=request
|
|
41
|
+
# )
|
|
42
|
+
#
|
|
43
|
+
# for collection_json in json.loads(family_tree):
|
|
44
|
+
# self.assertIsNotNone(collection_json['id'])
|
|
45
|
+
# self.assertEqual(collection_json['name'], 'Grand Parent A')
|
|
46
|
+
#
|
|
47
|
+
# self.assertEqual(len(collection_json['entries']), 0)
|
|
48
|
+
#
|
|
49
|
+
# self._test_collection_urls_json(collection_json=collection_json)
|
|
50
|
+
#
|
|
51
|
+
# self.assertEqual(len(collection_json['children']), 1)
|
|
52
|
+
#
|
|
53
|
+
# for child_json in collection_json['children']:
|
|
54
|
+
# self.assertIsNotNone(child_json['id'])
|
|
55
|
+
# self.assertEqual(child_json['name'], 'Parent A1')
|
|
56
|
+
#
|
|
57
|
+
# self.assertEqual(len(child_json['entries']), 0)
|
|
58
|
+
#
|
|
59
|
+
# self._test_collection_urls_json(collection_json=child_json)
|
|
60
|
+
#
|
|
61
|
+
# self.assertEqual(len(child_json['children']), 1)
|
|
62
|
+
#
|
|
63
|
+
# for grand_child_json in child_json['children']:
|
|
64
|
+
# self.assertIsNotNone(grand_child_json['id'])
|
|
65
|
+
# self.assertEqual(grand_child_json['name'], 'Child A1a')
|
|
66
|
+
#
|
|
67
|
+
# self.assertEqual(len(grand_child_json['entries']), 0)
|
|
68
|
+
#
|
|
69
|
+
# self._test_collection_urls_json(collection_json=grand_child_json)
|
|
70
|
+
#
|
|
71
|
+
# self.assertEqual(len(grand_child_json['children']), 0)
|
|
@@ -23,11 +23,13 @@ if TYPE_CHECKING:
|
|
|
23
23
|
@AppAuthController('knowledge').permission_required('can_add')
|
|
24
24
|
def form_view(
|
|
25
25
|
request: WSGIRequest,
|
|
26
|
-
pk: int = 0
|
|
26
|
+
pk: int = 0,
|
|
27
|
+
parent_pk: int = None,
|
|
27
28
|
) -> TemplateResponse | HttpResponseRedirect:
|
|
28
29
|
collection = get_object_or_null_obj(Collection, pk=pk)
|
|
29
30
|
|
|
30
31
|
dg.glue_model_object(request, unique_name='collection', model_object=collection)
|
|
32
|
+
|
|
31
33
|
dg.glue_query_set(
|
|
32
34
|
request,
|
|
33
35
|
unique_name='collections',
|
|
@@ -59,8 +61,16 @@ def form_view(
|
|
|
59
61
|
collection=collection,
|
|
60
62
|
)
|
|
61
63
|
|
|
64
|
+
if collection.parent_id:
|
|
65
|
+
return_url = reverse(
|
|
66
|
+
'django_spire:knowledge:collection:page:top_level',
|
|
67
|
+
kwargs={'pk': collection.parent_id}
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
return_url = reverse('django_spire:knowledge:page:home')
|
|
71
|
+
|
|
62
72
|
return HttpResponseRedirect(
|
|
63
|
-
|
|
73
|
+
return_url
|
|
64
74
|
)
|
|
65
75
|
|
|
66
76
|
show_form_errors(request, form)
|
|
@@ -73,6 +83,7 @@ def form_view(
|
|
|
73
83
|
obj=collection,
|
|
74
84
|
context_data={
|
|
75
85
|
'collection': collection,
|
|
86
|
+
'collection_parent_pk': parent_pk,
|
|
76
87
|
'group_ids': list(
|
|
77
88
|
collection.groups.all().values_list('auth_group_id', flat=True)
|
|
78
89
|
) if collection.id else [],
|
|
@@ -6,20 +6,52 @@ from django.template.response import TemplateResponse
|
|
|
6
6
|
from django.urls import reverse
|
|
7
7
|
|
|
8
8
|
from django_spire.auth.controller.controller import AppAuthController
|
|
9
|
+
from django_spire.contrib import Breadcrumbs
|
|
9
10
|
from django_spire.contrib.generic_views import portal_views
|
|
10
11
|
from django_spire.knowledge.collection.models import Collection
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
@AppAuthController('knowledge').permission_required('can_view')
|
|
15
|
+
def top_level_collection_view(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
16
|
+
collection = get_object_or_404(Collection, pk=pk)
|
|
17
|
+
|
|
18
|
+
breadcrumbs = Breadcrumbs()
|
|
19
|
+
breadcrumbs.add_breadcrumb('Knowledge', reverse('django_spire:knowledge:page:home'))
|
|
20
|
+
breadcrumbs.add_base_breadcrumb(collection)
|
|
21
|
+
|
|
22
|
+
return portal_views.template_view(
|
|
23
|
+
request,
|
|
24
|
+
page_title='Knowledge Collection',
|
|
25
|
+
page_description='',
|
|
26
|
+
breadcrumbs=breadcrumbs,
|
|
27
|
+
context_data={
|
|
28
|
+
'collection': collection,
|
|
29
|
+
'collection_tree_json': Collection.services.transformation.to_hierarchy_json(
|
|
30
|
+
request=request, parent_id=collection.id
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
template='django_spire/knowledge/collection/page/display_page.html',
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
13
37
|
@AppAuthController('knowledge').permission_required('can_delete')
|
|
14
38
|
def delete_view(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
15
39
|
collection = get_object_or_404(Collection, pk=pk)
|
|
16
40
|
|
|
41
|
+
if collection.parent:
|
|
42
|
+
return_url = request.GET.get(
|
|
43
|
+
'return_url',
|
|
44
|
+
reverse(
|
|
45
|
+
'django_spire:knowledge:collection:page:top_level',
|
|
46
|
+
kwargs={'pk': collection.parent_id}
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
return_url = request.GET.get('return_url', reverse('django_spire:knowledge:page:home'))
|
|
51
|
+
|
|
17
52
|
return portal_views.delete_form_view(
|
|
18
53
|
request,
|
|
19
54
|
obj=collection,
|
|
20
55
|
delete_func=collection.services.processor.set_deleted,
|
|
21
|
-
return_url=
|
|
22
|
-
'return_url',
|
|
23
|
-
reverse('django_spire:knowledge:page:home')
|
|
24
|
-
)
|
|
56
|
+
return_url=return_url
|
|
25
57
|
)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from django.db import models
|
|
2
|
+
from django.urls import reverse
|
|
2
3
|
|
|
4
|
+
from django_spire.contrib import Breadcrumbs
|
|
3
5
|
from django_spire.contrib.ordering.mixins import OrderingModelMixin
|
|
6
|
+
from django_spire.contrib.utils import truncate_string
|
|
4
7
|
from django_spire.history.mixins import HistoryModelMixin
|
|
5
8
|
from django_spire.knowledge.collection.models import Collection
|
|
6
9
|
from django_spire.knowledge.entry.querysets import EntryQuerySet
|
|
@@ -32,6 +35,35 @@ class Entry(HistoryModelMixin, OrderingModelMixin):
|
|
|
32
35
|
def __str__(self):
|
|
33
36
|
return self.name
|
|
34
37
|
|
|
38
|
+
@property
|
|
39
|
+
def name_short(self) -> str:
|
|
40
|
+
return truncate_string(self.name, 32)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def top_level_collection(self) -> Collection:
|
|
44
|
+
return self.collection.top_level_parent
|
|
45
|
+
|
|
46
|
+
def base_breadcrumb(self) -> Breadcrumbs:
|
|
47
|
+
bread_crumbs = Breadcrumbs()
|
|
48
|
+
|
|
49
|
+
bread_crumbs.add_breadcrumb(
|
|
50
|
+
self.top_level_collection.name_short,
|
|
51
|
+
reverse(
|
|
52
|
+
'django_spire:knowledge:collection:page:top_level',
|
|
53
|
+
kwargs={'pk': self.top_level_collection.pk}
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
bread_crumbs.add_breadcrumb(
|
|
58
|
+
self.collection.name_short
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
bread_crumbs.add_breadcrumb(
|
|
62
|
+
self.name_short,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return bread_crumbs
|
|
66
|
+
|
|
35
67
|
class Meta:
|
|
36
68
|
verbose_name = 'Entry'
|
|
37
69
|
verbose_name_plural = 'Entries'
|