django-spire 0.16.11__py3-none-any.whl → 0.16.13__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 +3 -1
- django_spire/ai/apps.py +2 -0
- django_spire/ai/chat/admin.py +15 -9
- django_spire/ai/chat/apps.py +4 -1
- django_spire/ai/chat/auth/controller.py +3 -1
- django_spire/ai/chat/choices.py +2 -0
- django_spire/ai/chat/intelligence/maps/intent_llm_map.py +8 -5
- django_spire/ai/chat/intelligence/prompts.py +4 -2
- django_spire/ai/chat/intelligence/workflows/chat_workflow.py +27 -28
- django_spire/ai/chat/message_intel.py +7 -4
- django_spire/ai/chat/models.py +8 -9
- django_spire/ai/chat/querysets.py +3 -1
- django_spire/ai/chat/responses.py +19 -10
- django_spire/ai/chat/tools.py +20 -15
- django_spire/ai/chat/urls/message_urls.py +2 -1
- django_spire/ai/chat/urls/page_urls.py +1 -0
- django_spire/ai/chat/views/message_request_views.py +2 -0
- django_spire/ai/chat/views/message_response_views.py +4 -4
- django_spire/ai/chat/views/message_views.py +2 -0
- django_spire/ai/chat/views/page_views.py +7 -2
- django_spire/ai/chat/views/template_views.py +2 -0
- django_spire/ai/decorators.py +13 -7
- django_spire/ai/mixins.py +4 -2
- django_spire/ai/models.py +7 -2
- django_spire/ai/prompt/bots.py +14 -32
- django_spire/ai/prompt/intel.py +1 -1
- django_spire/ai/prompt/prompts.py +7 -1
- django_spire/ai/prompt/system/bots.py +42 -75
- django_spire/ai/prompt/system/intel.py +5 -4
- django_spire/ai/prompt/system/prompts.py +5 -1
- django_spire/ai/prompt/system/system_prompt_cli.py +15 -9
- django_spire/ai/prompt/tests/test_bots.py +14 -11
- django_spire/ai/prompt/text_to_prompt_cli.py +5 -2
- django_spire/ai/prompt/tuning/bot_tuning_cli.py +14 -13
- django_spire/ai/prompt/tuning/bots.py +68 -116
- django_spire/ai/prompt/tuning/intel.py +1 -1
- django_spire/ai/prompt/tuning/mixins.py +2 -0
- django_spire/ai/prompt/tuning/prompt_tuning_cli.py +8 -8
- django_spire/ai/prompt/tuning/prompts.py +4 -2
- django_spire/ai/sms/admin.py +3 -1
- django_spire/ai/sms/apps.py +2 -0
- django_spire/ai/sms/decorators.py +2 -0
- django_spire/ai/sms/intel.py +4 -2
- django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +8 -8
- django_spire/ai/sms/models.py +16 -14
- django_spire/ai/sms/querysets.py +4 -1
- django_spire/ai/sms/tools.py +18 -16
- django_spire/ai/sms/urls.py +1 -1
- django_spire/ai/sms/views.py +2 -0
- django_spire/ai/tests/test_ai.py +3 -5
- django_spire/ai/urls.py +1 -0
- django_spire/consts.py +1 -1
- django_spire/contrib/seeding/field/django/seeder.py +6 -4
- django_spire/contrib/seeding/field/llm.py +1 -2
- django_spire/contrib/seeding/intelligence/bots/field_seeding_bots.py +7 -8
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +15 -16
- django_spire/contrib/seeding/intelligence/intel.py +1 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +5 -7
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +3 -5
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +1 -2
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +3 -5
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +1 -1
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +3 -3
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +1 -1
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +2 -4
- django_spire/contrib/seeding/management/commands/seeding.py +5 -2
- django_spire/contrib/seeding/model/base.py +12 -10
- django_spire/contrib/seeding/model/django/seeder.py +13 -10
- django_spire/contrib/seeding/tests/test_seeding.py +1 -1
- django_spire/file/apps.py +13 -0
- django_spire/file/interfaces.py +42 -9
- django_spire/knowledge/admin.py +2 -0
- django_spire/knowledge/apps.py +2 -0
- django_spire/knowledge/auth/controller.py +2 -0
- django_spire/knowledge/collection/admin.py +3 -0
- django_spire/knowledge/collection/forms.py +2 -0
- django_spire/knowledge/collection/models.py +4 -0
- django_spire/knowledge/collection/querysets.py +3 -3
- django_spire/knowledge/collection/seeding/seed.py +2 -0
- django_spire/knowledge/collection/tests/factories.py +2 -0
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +2 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +2 -0
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +2 -0
- django_spire/knowledge/collection/views/json_views.py +2 -0
- django_spire/knowledge/collection/views/page_views.py +2 -0
- django_spire/knowledge/context_processors.py +2 -0
- django_spire/knowledge/entry/services/automation_service.py +2 -4
- django_spire/knowledge/entry/services/factory_service.py +1 -2
- django_spire/knowledge/entry/services/tool_service.py +2 -2
- django_spire/knowledge/entry/services/transformation_services.py +0 -1
- django_spire/knowledge/entry/tests/factories.py +3 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +2 -0
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +2 -0
- django_spire/knowledge/entry/urls/form_urls.py +1 -0
- django_spire/knowledge/entry/urls/json_urls.py +1 -0
- django_spire/knowledge/entry/urls/page_urls.py +1 -0
- django_spire/knowledge/entry/urls/template_urls.py +1 -0
- django_spire/knowledge/entry/version/admin.py +2 -0
- django_spire/knowledge/entry/version/block/admin.py +2 -0
- django_spire/knowledge/entry/version/block/blocks/heading_block.py +2 -0
- django_spire/knowledge/entry/version/block/blocks/sub_heading_block.py +2 -0
- django_spire/knowledge/entry/version/block/blocks/text_block.py +2 -0
- django_spire/knowledge/entry/version/block/maps.py +2 -0
- django_spire/knowledge/entry/version/block/models.py +2 -0
- django_spire/knowledge/entry/version/block/tests/factories.py +2 -0
- django_spire/knowledge/entry/version/block/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/entry/version/block/views/json_views.py +2 -0
- django_spire/knowledge/entry/version/converters/docx_converter.py +1 -1
- django_spire/knowledge/entry/version/converters/markdown_converter.py +2 -1
- django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +12 -9
- django_spire/knowledge/entry/version/maps.py +2 -0
- django_spire/knowledge/entry/version/models.py +2 -0
- django_spire/knowledge/entry/version/querysets.py +2 -0
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -0
- django_spire/knowledge/entry/version/tests/factories.py +2 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +3 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_form_urls.py +2 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +2 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/form_urls.py +1 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +1 -0
- django_spire/knowledge/entry/version/urls/page_urls.py +1 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +1 -0
- django_spire/knowledge/entry/version/views/form_views.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +2 -0
- django_spire/knowledge/entry/version/views/page_views.py +2 -0
- django_spire/knowledge/entry/version/views/redirect_views.py +2 -0
- django_spire/knowledge/entry/views/form_views.py +4 -2
- django_spire/knowledge/exceptions.py +2 -0
- django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +5 -16
- django_spire/knowledge/intelligence/intel/collection_intel.py +3 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +3 -3
- django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
- django_spire/knowledge/intelligence/maps/collection_map.py +9 -10
- django_spire/knowledge/intelligence/maps/entry_map.py +8 -9
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +8 -6
- django_spire/knowledge/models.py +2 -0
- django_spire/knowledge/seeding/seed.py +2 -0
- django_spire/knowledge/templatetags/spire_knowledge_tags.py +3 -0
- django_spire/knowledge/urls/__init__.py +1 -0
- {django_spire-0.16.11.dist-info → django_spire-0.16.13.dist-info}/METADATA +7 -2
- {django_spire-0.16.11.dist-info → django_spire-0.16.13.dist-info}/RECORD +147 -148
- django_spire/knowledge/entry/tests/constants.py +0 -1
- {django_spire-0.16.11.dist-info → django_spire-0.16.13.dist-info}/WHEEL +0 -0
- {django_spire-0.16.11.dist-info → django_spire-0.16.13.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.16.11.dist-info → django_spire-0.16.13.dist-info}/top_level.txt +0 -0
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
from dandy
|
|
2
|
-
|
|
1
|
+
from dandy import Bot, LlmConfigOptions
|
|
3
2
|
|
|
4
3
|
from django_spire.contrib.seeding.intelligence.intel import SourceIntel
|
|
4
|
+
from django_spire.contrib.seeding.intelligence.prompts.generate_django_model_seeder_prompts import (
|
|
5
|
+
generate_django_model_seeder_user_prompt,
|
|
6
|
+
generate_django_model_seeder_system_prompt
|
|
7
|
+
)
|
|
5
8
|
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SeederGeneratorBot(BaseLlmBot):
|
|
10
|
+
class SeederGeneratorBot(Bot):
|
|
11
|
+
llm_config = 'PYTHON_MODULE'
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
config_options = LlmConfigOptions(
|
|
13
|
+
llm_config_options = LlmConfigOptions(
|
|
16
14
|
temperature=0.3,
|
|
17
15
|
randomize_seed=True
|
|
18
16
|
)
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
llm_role = 'You are an expert Python developer specializing in Django model seeders.'
|
|
19
|
+
|
|
21
20
|
def process(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
):
|
|
26
|
-
return
|
|
21
|
+
self,
|
|
22
|
+
model_import: str,
|
|
23
|
+
model_description: str
|
|
24
|
+
) -> SourceIntel:
|
|
25
|
+
return self.llm.prompt_to_intel(
|
|
27
26
|
prompt=generate_django_model_seeder_user_prompt(
|
|
28
27
|
model_import,
|
|
29
28
|
model_description,
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
from typing import Type
|
|
2
|
-
|
|
3
1
|
from django.db.models.base import Model
|
|
4
2
|
|
|
5
|
-
from dandy
|
|
3
|
+
from dandy import Prompt
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
class SeedingModelClassPromptFactory:
|
|
9
7
|
|
|
10
|
-
def __init__(self, model_class:
|
|
8
|
+
def __init__(self, model_class: type[Model]):
|
|
11
9
|
self.model_class = model_class
|
|
12
10
|
|
|
13
11
|
def objective_prompt(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
self,
|
|
13
|
+
model_description: str,
|
|
14
|
+
sector_description: str
|
|
17
15
|
) -> Prompt:
|
|
18
16
|
model_name = self.model_class._meta.verbose_name_plural.title()
|
|
19
17
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
from typing_extensions import Type
|
|
2
|
-
|
|
3
1
|
from django.db.models import Model
|
|
4
2
|
|
|
5
|
-
from dandy
|
|
3
|
+
from dandy import Prompt
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
def foreign_key_selection_prompt(
|
|
9
|
-
model_class:
|
|
10
|
-
related_model_class:
|
|
7
|
+
model_class: type[Model],
|
|
8
|
+
related_model_class: type[Model]
|
|
11
9
|
) -> Prompt:
|
|
12
10
|
model_name = model_class._meta.verbose_name.title()
|
|
13
11
|
related_model_name = related_model_class._meta.verbose_name.title()
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
from typing_extensions import List, Type
|
|
2
|
-
|
|
3
1
|
from django.contrib.contenttypes.models import ContentType
|
|
4
2
|
from django.db.models import Model
|
|
5
3
|
|
|
6
|
-
from dandy
|
|
4
|
+
from dandy import Prompt
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
def generic_relationship_selection_prompt(
|
|
10
|
-
model_class:
|
|
11
|
-
related_model_classes:
|
|
8
|
+
model_class: type[Model],
|
|
9
|
+
related_model_classes: list[type[Model]]
|
|
12
10
|
) -> Prompt:
|
|
13
11
|
related_model_classes_map = {
|
|
14
12
|
related_model_class._meta.verbose_name.title(): {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from dandy
|
|
1
|
+
from dandy import Prompt
|
|
2
2
|
from django.db.models.enums import TextChoices
|
|
3
3
|
from django.db.models.fields import Field
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def model_field_choices_prompt(
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
model_field: Field,
|
|
8
|
+
choices: TextChoices
|
|
9
9
|
) -> Prompt:
|
|
10
10
|
model_name = model_field.field.model.__name__
|
|
11
11
|
field_name = model_field.field.name
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
from django.db.models import Model
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
from dandy.llm import Prompt
|
|
3
|
+
from dandy import Prompt
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
def objective_prompt(
|
|
9
|
-
model_class:
|
|
7
|
+
model_class: type[Model],
|
|
10
8
|
model_description: str,
|
|
11
9
|
sector_description: str,
|
|
12
10
|
) -> Prompt:
|
|
@@ -31,12 +31,15 @@ class Command(BaseCommand):
|
|
|
31
31
|
# @recorder_to_html_file('seeding_generator')
|
|
32
32
|
def handle(self, *args, **kwargs):
|
|
33
33
|
if not kwargs['model_import'] or not kwargs['model_description']:
|
|
34
|
-
|
|
34
|
+
message = 'You must provide a model import path and a model description'
|
|
35
|
+
raise CommandError(message)
|
|
35
36
|
|
|
36
37
|
model_import = kwargs['model_import']
|
|
37
38
|
model_description = ' '.join(kwargs['model_description'])
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
# Instantiate the bot
|
|
41
|
+
bot = SeederGeneratorBot()
|
|
42
|
+
source_intel = bot.process(model_import, model_description)
|
|
40
43
|
|
|
41
44
|
Path(_SEEDING_OUTPUT_PATH).mkdir(parents=True, exist_ok=True)
|
|
42
45
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
3
|
from dandy.recorder import recorder_to_html_file
|
|
4
|
-
from dandy
|
|
5
|
-
from dandy.cache.utils import
|
|
4
|
+
from dandy import SqliteCache
|
|
5
|
+
from dandy.cache.utils import generate_cache_key
|
|
6
6
|
|
|
7
7
|
from django_spire.contrib.seeding.field.override import FieldOverride
|
|
8
8
|
from django_spire.contrib.seeding.model.config import FieldsConfig
|
|
@@ -59,10 +59,7 @@ class BaseModelSeeder(ABC):
|
|
|
59
59
|
|
|
60
60
|
@classmethod
|
|
61
61
|
def clear_cache(cls):
|
|
62
|
-
SqliteCache(
|
|
63
|
-
cache_name=cls.cache_name,
|
|
64
|
-
limit=cls.cache_limit
|
|
65
|
-
).clear(cache_name=cls.cache_name)
|
|
62
|
+
SqliteCache.clear(cache_name=cls.cache_name)
|
|
66
63
|
|
|
67
64
|
@classmethod
|
|
68
65
|
@abstractmethod
|
|
@@ -80,9 +77,14 @@ class BaseModelSeeder(ABC):
|
|
|
80
77
|
field_config = cls.get_field_config().override(fields) if fields else cls.get_field_config()
|
|
81
78
|
|
|
82
79
|
if cls.cache_seed:
|
|
83
|
-
|
|
80
|
+
cache_key = generate_cache_key(
|
|
81
|
+
cls.seed_data,
|
|
82
|
+
count=count,
|
|
83
|
+
fields=field_config.fields
|
|
84
|
+
)
|
|
85
|
+
|
|
84
86
|
cache = SqliteCache(cache_name=cls.cache_name, limit=cls.cache_limit)
|
|
85
|
-
formatted_seed_data = cache.get(
|
|
87
|
+
formatted_seed_data = cache.get(cache_key)
|
|
86
88
|
|
|
87
89
|
if formatted_seed_data:
|
|
88
90
|
return formatted_seed_data
|
|
@@ -95,13 +97,13 @@ class BaseModelSeeder(ABC):
|
|
|
95
97
|
if len(seeder.seeder_fields) > 0:
|
|
96
98
|
seed_data.append(seeder.seed(cls, count))
|
|
97
99
|
|
|
98
|
-
formatted_seed_data = [
|
|
100
|
+
formatted_seed_data = [{} for _ in range(max(len(sublist) for sublist in seed_data))]
|
|
99
101
|
for sublist in seed_data:
|
|
100
102
|
for i, d in enumerate(sublist):
|
|
101
103
|
formatted_seed_data[i].update(d)
|
|
102
104
|
|
|
103
105
|
if cls.cache_seed:
|
|
104
|
-
cache.set(
|
|
106
|
+
cache.set(cache_key, formatted_seed_data)
|
|
105
107
|
|
|
106
108
|
return formatted_seed_data
|
|
107
109
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import TypeVar, TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from dandy.llm import Prompt
|
|
6
5
|
from django.db.models import ForeignKey
|
|
7
|
-
|
|
8
6
|
from django.db.models.base import Model
|
|
9
7
|
|
|
10
8
|
from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
|
|
@@ -14,6 +12,9 @@ from django_spire.contrib.seeding.field.static import StaticFieldSeeder
|
|
|
14
12
|
from django_spire.contrib.seeding.model.base import BaseModelSeeder
|
|
15
13
|
from django_spire.contrib.seeding.model.django.config import DjangoModelFieldsConfig
|
|
16
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from dandy import Prompt
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
TypeModel = TypeVar('TypeModel', bound=Model)
|
|
19
20
|
|
|
@@ -21,7 +22,7 @@ TypeModel = TypeVar('TypeModel', bound=Model)
|
|
|
21
22
|
class DjangoModelSeeder(BaseModelSeeder):
|
|
22
23
|
field_config_class = DjangoModelFieldsConfig
|
|
23
24
|
|
|
24
|
-
model_class:
|
|
25
|
+
model_class: type[Model]
|
|
25
26
|
prompt: Prompt = None
|
|
26
27
|
_field_seeders = [
|
|
27
28
|
CustomFieldSeeder,
|
|
@@ -32,14 +33,16 @@ class DjangoModelSeeder(BaseModelSeeder):
|
|
|
32
33
|
]
|
|
33
34
|
|
|
34
35
|
@classmethod
|
|
35
|
-
def __init_subclass__(cls, **kwargs):
|
|
36
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
36
37
|
super().__init_subclass__(**kwargs)
|
|
37
38
|
|
|
38
39
|
if cls.model_class is None:
|
|
39
|
-
|
|
40
|
+
message = "Seeds must have a model class"
|
|
41
|
+
raise ValueError(message)
|
|
40
42
|
|
|
41
43
|
if cls.fields is None:
|
|
42
|
-
|
|
44
|
+
message = "Seeds must have fields"
|
|
45
|
+
raise ValueError(message)
|
|
43
46
|
|
|
44
47
|
@classmethod
|
|
45
48
|
def field_names(cls) -> list[str]:
|
|
@@ -51,9 +54,9 @@ class DjangoModelSeeder(BaseModelSeeder):
|
|
|
51
54
|
|
|
52
55
|
@classmethod
|
|
53
56
|
def seed_database(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
cls,
|
|
58
|
+
count=1,
|
|
59
|
+
fields: dict | None = None
|
|
57
60
|
) -> list[TypeModel]:
|
|
58
61
|
model_objects = cls.seed(count, fields)
|
|
59
62
|
return cls.model_class.objects.bulk_create(model_objects)
|
django_spire/file/apps.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from django.apps import AppConfig
|
|
2
|
+
from django.conf import settings
|
|
2
3
|
|
|
3
4
|
from django_spire.utils import check_required_apps
|
|
4
5
|
|
|
@@ -13,4 +14,16 @@ class FileConfig(AppConfig):
|
|
|
13
14
|
URLPATTERNS_NAMESPACE = 'file'
|
|
14
15
|
|
|
15
16
|
def ready(self) -> None:
|
|
17
|
+
if not hasattr(settings, 'BASE_FOLDER_NAME'):
|
|
18
|
+
raise ValueError(
|
|
19
|
+
f'"BASE_FOLDER_NAME" must be set in the django settings when '
|
|
20
|
+
f'using "{self.label}".'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
elif not isinstance(getattr(settings, 'BASE_FOLDER_NAME'), str):
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f'"BASE_FOLDER_NAME" must be a string in the django settings when '
|
|
26
|
+
f'using "{self.label}".'
|
|
27
|
+
)
|
|
28
|
+
|
|
16
29
|
check_required_apps(self.label)
|
django_spire/file/interfaces.py
CHANGED
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.core.files.base import ContentFile
|
|
5
8
|
from typing_extensions import TYPE_CHECKING
|
|
6
9
|
|
|
7
10
|
from django.contrib.contenttypes.models import ContentType
|
|
@@ -35,16 +38,27 @@ class FileFormatter:
|
|
|
35
38
|
|
|
36
39
|
@property
|
|
37
40
|
def location(self) -> str:
|
|
38
|
-
|
|
41
|
+
location = settings.BASE_FOLDER_NAME + '/'
|
|
42
|
+
location += str(self.app_name) + '/'
|
|
43
|
+
|
|
44
|
+
if self.related_field is not None:
|
|
45
|
+
location += str(self.related_field) + '/'
|
|
46
|
+
|
|
47
|
+
location += random_64_char_token() + '/'
|
|
48
|
+
location += self.name
|
|
49
|
+
|
|
50
|
+
return location
|
|
39
51
|
|
|
40
52
|
def null_file_obj(self) -> File:
|
|
41
|
-
|
|
42
|
-
file=self.file,
|
|
53
|
+
file_obj = File(
|
|
43
54
|
name=self.name,
|
|
44
55
|
size=self.size_verbose(),
|
|
45
56
|
type=self.type,
|
|
46
|
-
related_field=self.related_field
|
|
57
|
+
related_field=self.related_field,
|
|
47
58
|
)
|
|
59
|
+
self.save_file(file_obj)
|
|
60
|
+
|
|
61
|
+
return file_obj
|
|
48
62
|
|
|
49
63
|
def size_verbose(self) -> str:
|
|
50
64
|
if self.file.size < 512000:
|
|
@@ -62,6 +76,10 @@ class FileFormatter:
|
|
|
62
76
|
|
|
63
77
|
return str(value) + ext
|
|
64
78
|
|
|
79
|
+
def save_file(self, file_obj: File):
|
|
80
|
+
file_path = self.location + '.' + self.type
|
|
81
|
+
file_obj.file.save(file_path, ContentFile(self.file.read()), save=False)
|
|
82
|
+
|
|
65
83
|
|
|
66
84
|
@dataclass
|
|
67
85
|
class FileContentObjectFormatter(FileFormatter):
|
|
@@ -69,26 +87,41 @@ class FileContentObjectFormatter(FileFormatter):
|
|
|
69
87
|
|
|
70
88
|
@property
|
|
71
89
|
def location(self) -> str:
|
|
72
|
-
|
|
90
|
+
location = settings.BASE_FOLDER_NAME + '/'
|
|
91
|
+
location += str(self.content_object._meta.app_label) + '/'
|
|
92
|
+
|
|
93
|
+
if self.related_field is not None:
|
|
94
|
+
location += str(self.related_field) + '/'
|
|
95
|
+
|
|
96
|
+
location += random_64_char_token() + '/'
|
|
97
|
+
location += self.name
|
|
98
|
+
return location
|
|
73
99
|
|
|
74
100
|
def null_file_obj(self) -> File:
|
|
75
|
-
|
|
101
|
+
file_obj = File(
|
|
76
102
|
content_type=ContentType.objects.get_for_model(self.content_object),
|
|
77
103
|
object_id=self.content_object.id,
|
|
78
|
-
file=self.file,
|
|
79
104
|
name=self.name,
|
|
80
105
|
size=self.size_verbose(),
|
|
81
106
|
type=self.type,
|
|
82
|
-
related_field=self.related_field
|
|
107
|
+
related_field=self.related_field,
|
|
83
108
|
)
|
|
109
|
+
self.save_file(file_obj)
|
|
110
|
+
|
|
111
|
+
return file_obj
|
|
84
112
|
|
|
85
113
|
|
|
86
114
|
@dataclass
|
|
87
115
|
class FileUploader(ABC):
|
|
88
116
|
related_field: str | None
|
|
117
|
+
app_name: str = 'Uncategorized'
|
|
89
118
|
|
|
90
119
|
def null_file_obj(self, file):
|
|
91
|
-
formatted_file = FileFormatter(
|
|
120
|
+
formatted_file = FileFormatter(
|
|
121
|
+
file=file,
|
|
122
|
+
related_field=self.related_field,
|
|
123
|
+
app_name=self.app_name
|
|
124
|
+
)
|
|
92
125
|
return formatted_file.null_file_obj()
|
|
93
126
|
|
|
94
127
|
@abstractmethod
|
django_spire/knowledge/admin.py
CHANGED
django_spire/knowledge/apps.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django.db import models
|
|
2
4
|
|
|
3
5
|
from django_spire.auth.group.models import AuthGroup
|
|
@@ -44,10 +46,12 @@ class CollectionGroup(models.Model):
|
|
|
44
46
|
related_name='groups',
|
|
45
47
|
related_query_name='group',
|
|
46
48
|
)
|
|
49
|
+
|
|
47
50
|
auth_group = models.ForeignKey(
|
|
48
51
|
AuthGroup,
|
|
49
52
|
on_delete=models.CASCADE,
|
|
50
53
|
related_name='collection_groups',
|
|
51
54
|
related_query_name='collection_group',
|
|
52
55
|
)
|
|
56
|
+
|
|
53
57
|
services = CollectionGroupService()
|
|
@@ -2,15 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from django.
|
|
6
|
-
from django.db.models import Count, Q
|
|
5
|
+
from django.db.models import Count
|
|
7
6
|
|
|
8
7
|
from django_spire.auth.controller.controller import AppAuthController
|
|
9
8
|
from django_spire.contrib.ordering.querysets import OrderingQuerySetMixin
|
|
10
9
|
from django_spire.history.querysets import HistoryQuerySet
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
|
-
from
|
|
12
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
14
13
|
from django.db.models import QuerySet
|
|
15
14
|
from django_spire.knowledge.collection.models import Collection
|
|
16
15
|
|
|
@@ -57,6 +56,7 @@ class CollectionQuerySet(HistoryQuerySet, OrderingQuerySetMixin):
|
|
|
57
56
|
accessible_ids = set(direct_access.values_list('id', flat=True))
|
|
58
57
|
|
|
59
58
|
current_level_ids = accessible_ids.copy()
|
|
59
|
+
|
|
60
60
|
while current_level_ids:
|
|
61
61
|
next_level = self.filter(parent_id__in=current_level_ids)
|
|
62
62
|
new_ids = set(next_level.values_list('id', flat=True)) - accessible_ids
|
|
@@ -35,11 +35,9 @@ class EntryAutomationService(BaseDjangoModelService['Entry']):
|
|
|
35
35
|
)
|
|
36
36
|
except Exception as e:
|
|
37
37
|
errored.append({'file': file_object.name, 'error': str(e)})
|
|
38
|
-
file_object.
|
|
39
|
-
file_object.delete()
|
|
38
|
+
file_object.set_deleted()
|
|
40
39
|
else:
|
|
41
|
-
file_object.
|
|
42
|
-
file_object.delete()
|
|
40
|
+
file_object.set_deleted()
|
|
43
41
|
|
|
44
42
|
message = f'Files Converted: {len(file_objects) - len(errored)}'
|
|
45
43
|
if errored:
|
|
@@ -7,7 +7,6 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
7
7
|
from django_spire.auth.user.models import AuthUser
|
|
8
8
|
from django_spire.contrib.service import BaseDjangoModelService
|
|
9
9
|
from django_spire.file.models import File
|
|
10
|
-
from django_spire.knowledge.entry.tests.constants import ENTRY_IMPORT_RELATED_FIELD
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
12
|
from django_spire.knowledge.entry.models import Entry
|
|
@@ -34,7 +33,7 @@ class EntryFactoryService(BaseDjangoModelService['Entry']):
|
|
|
34
33
|
|
|
35
34
|
file.content_type = ContentType.objects.get_for_model(entry.__class__)
|
|
36
35
|
file.object_id = entry.id
|
|
37
|
-
file.related_field =
|
|
36
|
+
file.related_field = None
|
|
38
37
|
|
|
39
38
|
entry.ordering_services.processor.move_to_position(
|
|
40
39
|
destination_objects=collection.entries.active(),
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from django.contrib.contenttypes.models import ContentType
|
|
7
8
|
|
|
8
9
|
from django_spire.contrib.service import BaseDjangoModelService
|
|
9
10
|
from django_spire.file.models import File
|
|
10
|
-
from django_spire.knowledge.entry.tests.constants import ENTRY_IMPORT_RELATED_FIELD
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from django.db.models import QuerySet
|
|
@@ -19,7 +19,7 @@ class EntryToolService(BaseDjangoModelService['Entry']):
|
|
|
19
19
|
|
|
20
20
|
def get_files_to_convert(self) -> QuerySet[File]:
|
|
21
21
|
return (
|
|
22
|
-
File.objects
|
|
22
|
+
File.objects
|
|
23
23
|
.filter(content_type=ContentType.objects.get_for_model(self.obj_class))
|
|
24
24
|
.active()
|
|
25
25
|
.order_by('object_id')
|