django-spire 0.21.1__py3-none-any.whl → 0.22.1__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/consts.py +1 -1
- django_spire/contrib/generic_views/portal_views.py +43 -0
- django_spire/core/models.py +9 -0
- django_spire/core/static/django_spire/css/bootstrap-extension.css +41 -0
- django_spire/core/static/django_spire/css/bootstrap-override.css +28 -4
- django_spire/core/{tags → tag}/intelligence/tag_set_bot.py +5 -4
- django_spire/core/tag/mixins.py +23 -0
- django_spire/core/{tags → tag}/models.py +1 -2
- django_spire/core/tag/service/tag_service.py +72 -0
- django_spire/core/{tags → tag}/tests/test_intelligence.py +1 -1
- django_spire/core/tag/tests/test_tags.py +102 -0
- django_spire/core/tag/tools.py +66 -0
- django_spire/core/templates/django_spire/dropdown/ellipsis_table_dropdown.html +26 -0
- django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html +4 -4
- django_spire/core/templates/django_spire/modal/content/dispatch_modal_delete_confirmation_content.html +6 -4
- django_spire/core/templates/django_spire/table/base.html +409 -0
- django_spire/core/templates/django_spire/table/element/child_row.html +59 -0
- django_spire/core/templates/django_spire/table/element/expandable_row.html +45 -0
- django_spire/core/templates/django_spire/table/element/footer.html +17 -0
- django_spire/core/templates/django_spire/table/element/header.html +9 -0
- django_spire/core/templates/django_spire/table/element/loading_skeleton.html +13 -0
- django_spire/core/templates/django_spire/table/element/refreshing_skeleton.html +13 -0
- django_spire/core/templates/django_spire/table/element/row.html +94 -0
- django_spire/core/templates/django_spire/table/element/trigger.html +1 -0
- django_spire/core/templates/django_spire/table/item/no_data_item.html +11 -0
- django_spire/core/templates/django_spire/tag/element/tag.html +1 -0
- django_spire/core/templatetags/spire_core_tags.py +12 -0
- django_spire/knowledge/collection/models.py +2 -2
- django_spire/knowledge/collection/services/tag_service.py +16 -14
- django_spire/knowledge/entry/models.py +2 -2
- django_spire/knowledge/entry/querysets.py +5 -0
- django_spire/knowledge/entry/services/tag_service.py +5 -5
- django_spire/knowledge/intelligence/bots/entries_search_llm_bot.py +44 -0
- django_spire/knowledge/intelligence/intel/entry_intel.py +24 -4
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +46 -25
- django_spire/knowledge/models.py +12 -4
- django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html +6 -4
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +3 -2
- django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +14 -6
- django_spire/settings.py +1 -1
- {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/METADATA +2 -2
- {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/RECORD +50 -40
- django_spire/core/tags/mixins.py +0 -61
- django_spire/core/tags/tests/test_tags.py +0 -102
- django_spire/core/tags/tools.py +0 -20
- django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +0 -45
- django_spire/knowledge/intelligence/decoders/collection_decoder.py +0 -19
- django_spire/knowledge/intelligence/decoders/entry_decoder.py +0 -22
- django_spire/knowledge/intelligence/intel/collection_intel.py +0 -8
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/form_page.html +0 -26
- /django_spire/core/{tags → tag}/__init__.py +0 -0
- /django_spire/core/{tags → tag}/intelligence/__init__.py +0 -0
- /django_spire/core/{tags → tag}/querysets.py +0 -0
- /django_spire/core/{tags/tests → tag/service}/__init__.py +0 -0
- /django_spire/{knowledge/intelligence/decoders → core/tag/tests}/__init__.py +0 -0
- {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/WHEEL +0 -0
- {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/top_level.txt +0 -0
django_spire/consts.py
CHANGED
|
@@ -270,6 +270,49 @@ def model_form_view(
|
|
|
270
270
|
)
|
|
271
271
|
|
|
272
272
|
|
|
273
|
+
def table_view(
|
|
274
|
+
request: WSGIRequest,
|
|
275
|
+
*,
|
|
276
|
+
queryset: QuerySet,
|
|
277
|
+
queryset_name: str,
|
|
278
|
+
template: str,
|
|
279
|
+
context_data: dict[str, Any] | None = None
|
|
280
|
+
) -> TemplateResponse:
|
|
281
|
+
if context_data is None:
|
|
282
|
+
context_data = {}
|
|
283
|
+
|
|
284
|
+
default_batch_size = 25
|
|
285
|
+
|
|
286
|
+
page = int(request.GET.get('page', 1))
|
|
287
|
+
|
|
288
|
+
batch_size = (
|
|
289
|
+
context_data.get('batch_size')
|
|
290
|
+
if 'batch_size' in context_data
|
|
291
|
+
else request.GET.get('batch_size', default_batch_size)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
batch_size = int(batch_size)
|
|
295
|
+
offset = (page - 1) * batch_size
|
|
296
|
+
|
|
297
|
+
total_count = queryset.count()
|
|
298
|
+
object_list = queryset[offset:offset + batch_size]
|
|
299
|
+
has_next = offset + batch_size < total_count
|
|
300
|
+
|
|
301
|
+
base_context_data = {
|
|
302
|
+
queryset_name: object_list,
|
|
303
|
+
'has_next': has_next,
|
|
304
|
+
'total_count': total_count,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
context_data.update(base_context_data)
|
|
308
|
+
|
|
309
|
+
return TemplateResponse(
|
|
310
|
+
request,
|
|
311
|
+
context=context_data,
|
|
312
|
+
template=template
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
273
316
|
def template_view(
|
|
274
317
|
request: WSGIRequest,
|
|
275
318
|
page_title: str,
|
|
@@ -378,6 +378,47 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
378
378
|
transform: translateX(0);
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
.skeleton-box {
|
|
382
|
+
background: linear-gradient(
|
|
383
|
+
90deg,
|
|
384
|
+
rgba(var(--bs-secondary-rgb), 0.1) 0%,
|
|
385
|
+
rgba(var(--bs-secondary-rgb), 0.2) 50%,
|
|
386
|
+
rgba(var(--bs-secondary-rgb), 0.1) 100%
|
|
387
|
+
);
|
|
388
|
+
background-size: 200% 100%;
|
|
389
|
+
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
390
|
+
border-radius: 4px;
|
|
391
|
+
display: inline-block;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
@keyframes skeleton-loading {
|
|
395
|
+
0% {
|
|
396
|
+
background-position: 200% 0;
|
|
397
|
+
}
|
|
398
|
+
100% {
|
|
399
|
+
background-position: -200% 0;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.skeleton-row {
|
|
404
|
+
pointer-events: none;
|
|
405
|
+
user-select: none;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.table tbody {
|
|
409
|
+
transition: opacity 0.15s ease-in-out;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.table-fixed {
|
|
413
|
+
table-layout: fixed;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.table-fixed th,
|
|
417
|
+
.table-fixed td {
|
|
418
|
+
overflow: hidden;
|
|
419
|
+
text-overflow: ellipsis;
|
|
420
|
+
}
|
|
421
|
+
|
|
381
422
|
.text-200 {
|
|
382
423
|
font-weight: 200 !important;
|
|
383
424
|
}
|
|
@@ -135,11 +135,11 @@
|
|
|
135
135
|
--bs-popover-header-bg: var(--app-layer-two);
|
|
136
136
|
--bs-popover-header-color: var(--app-default-text-color);
|
|
137
137
|
|
|
138
|
-
--bs-table-bg:
|
|
139
|
-
--bs-table-border-color: var(--
|
|
138
|
+
--bs-table-bg: var(--app-table-row-color);
|
|
139
|
+
--bs-table-border-color: var(--bs-border-color);
|
|
140
140
|
--bs-table-color: var(--app-default-text-color);
|
|
141
|
-
--bs-table-hover-bg: var(--app-
|
|
142
|
-
--bs-table-striped-bg: var(--app-
|
|
141
|
+
--bs-table-hover-bg: var(--app-table-row-hover-color);
|
|
142
|
+
--bs-table-striped-bg: var(--app-table-row-alt-color);
|
|
143
143
|
|
|
144
144
|
--bs-tooltip-bg: var(--app-dark);
|
|
145
145
|
--bs-tooltip-color: var(--app-light);
|
|
@@ -263,6 +263,30 @@
|
|
|
263
263
|
background-color: var(--app-primary);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
.table > :not(caption) > * > * {
|
|
267
|
+
color: var(--app-default-text-color);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.table-striped tbody tr:nth-of-type(4n+1),
|
|
271
|
+
.table-striped tbody tr:nth-of-type(4n+2) {
|
|
272
|
+
--bs-table-bg-type: var(--app-table-row-alt-color);
|
|
273
|
+
background-color: var(--app-table-row-alt-color);
|
|
274
|
+
color: var(--app-default-text-color);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.table-striped tbody tr:nth-of-type(4n+3),
|
|
278
|
+
.table-striped tbody tr:nth-of-type(4n+4) {
|
|
279
|
+
--bs-table-bg-type: color-mix(in srgb, var(--app-secondary) 10%, transparent);
|
|
280
|
+
background-color: color-mix(in srgb, var(--app-secondary) 10%, transparent);
|
|
281
|
+
color: var(--app-default-text-color);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.table-hover > tbody > tr:hover > * {
|
|
285
|
+
--bs-table-bg-state: var(--app-table-row-hover-color);
|
|
286
|
+
background-color: var(--app-table-row-hover-color);
|
|
287
|
+
color: var(--app-default-text-color);
|
|
288
|
+
}
|
|
289
|
+
|
|
266
290
|
.ce-block__content [data-placeholder]:empty::before,
|
|
267
291
|
.ce-block__content [data-placeholder][data-empty="true"]::before {
|
|
268
292
|
color: var(--app-primary) !important;
|
|
@@ -13,10 +13,11 @@ class TagSetBot(Bot):
|
|
|
13
13
|
Prompt()
|
|
14
14
|
.list([
|
|
15
15
|
'Make sure to have enough tags to properly cover all the provided content.',
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
16
|
+
'Include tags that help searchability.',
|
|
17
|
+
'Focus on tagging the words in the content.',
|
|
18
|
+
'Only add additional words that are very relevant to the content.',
|
|
19
|
+
'Use spaces to separate words in tags.',
|
|
20
|
+
'Include common acronyms in addition to the tags.',
|
|
20
21
|
])
|
|
21
22
|
)
|
|
22
23
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tag.models import Tag
|
|
4
|
+
from django_spire.core.tag.tools import simplify_and_weight_tag_set_to_dict, simplify_tag_set
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TagModelMixin(models.Model):
|
|
8
|
+
tags = models.ManyToManyField(Tag, related_name='+', null=True, blank=True, editable=False)
|
|
9
|
+
|
|
10
|
+
class Meta:
|
|
11
|
+
abstract = True
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def tag_set(self) -> set[str]:
|
|
15
|
+
return set(self.tags.values_list('name', flat=True))
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def simplified_tag_set(self) -> set[str]:
|
|
19
|
+
return simplify_tag_set(self.tag_set)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def simplified_and_weighted_tag_dict(self) -> dict[str, int]:
|
|
23
|
+
return simplify_and_weight_tag_set_to_dict(self.tag_set)
|
|
@@ -3,7 +3,7 @@ from typing import Sequence, Self
|
|
|
3
3
|
from django.db import models
|
|
4
4
|
from django.utils.text import slugify
|
|
5
5
|
|
|
6
|
-
from django_spire.core.
|
|
6
|
+
from django_spire.core.tag.querysets import TagQuerySet
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Tag(models.Model):
|
|
@@ -35,4 +35,3 @@ class Tag(models.Model):
|
|
|
35
35
|
db_table = 'django_spire_core_tag'
|
|
36
36
|
verbose_name = 'Tag'
|
|
37
37
|
verbose_name_plural = 'Tags'
|
|
38
|
-
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from abc import abstractmethod, ABC
|
|
2
|
+
from typing import Generic
|
|
3
|
+
|
|
4
|
+
from django.db.models import QuerySet
|
|
5
|
+
|
|
6
|
+
from django_spire.contrib.constructor.django_model_constructor import TypeDjangoModel
|
|
7
|
+
from django_spire.contrib.service import BaseDjangoModelService
|
|
8
|
+
from django_spire.core.tag import tools
|
|
9
|
+
from django_spire.core.tag.models import Tag
|
|
10
|
+
from django_spire.core.tag.tools import get_score_percentage_from_tag_set_weighted
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseTagService(
|
|
14
|
+
BaseDjangoModelService[TypeDjangoModel],
|
|
15
|
+
ABC,
|
|
16
|
+
Generic[TypeDjangoModel]
|
|
17
|
+
):
|
|
18
|
+
obj: TypeDjangoModel
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def process_and_set_tags(self):
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
def add_tags_from_tag_set(self, tag_set: set[str]):
|
|
25
|
+
self._update_global_tags_from_set(tag_set)
|
|
26
|
+
|
|
27
|
+
self.obj.tags.add(*Tag.objects.in_tag_set(tag_set))
|
|
28
|
+
|
|
29
|
+
def clear_tags(self):
|
|
30
|
+
self.obj.tags.clear()
|
|
31
|
+
|
|
32
|
+
def get_matching_tags_from_tag_set(self, tag_set: set[str]) -> QuerySet:
|
|
33
|
+
return self.obj.tags.in_tag_set(tag_set)
|
|
34
|
+
|
|
35
|
+
def get_matching_percentage_of_tag_set(self, tag_set: set[str]) -> float:
|
|
36
|
+
return tools.get_matching_a_percentage_from_tag_sets(tag_set, self.obj.tag_set)
|
|
37
|
+
|
|
38
|
+
def get_matching_percentage_of_model_tags_from_tag_set(self, tag_set: set[str]) -> float:
|
|
39
|
+
return tools.get_matching_b_percentage_from_tag_sets(tag_set, self.obj.tag_set)
|
|
40
|
+
|
|
41
|
+
def get_score_percentage_from_tag_set_weighted(self, tag_set: set[str]) -> float:
|
|
42
|
+
return get_score_percentage_from_tag_set_weighted(
|
|
43
|
+
tag_set_actual=tag_set,
|
|
44
|
+
tag_set_reference=self.obj.tag_set
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def get_simplified_and_weighted_tag_dict_above_minimum(self, minimum_weight: int = 1) -> dict[str, int]:
|
|
48
|
+
above_minimum_tag_dict = self.obj.simplified_and_weighted_tag_dict
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
tag: weight
|
|
52
|
+
for tag, weight in above_minimum_tag_dict.items()
|
|
53
|
+
if weight >= minimum_weight
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def has_tags_in_tag_set(self, tag_set: set[str]) -> bool:
|
|
57
|
+
return self.obj.tag_set.issuperset(tag_set)
|
|
58
|
+
|
|
59
|
+
def remove_tags_by_tag_set(self, tag_set: set[str]):
|
|
60
|
+
tag_objects = Tag.objects.in_tag_set(tag_set)
|
|
61
|
+
|
|
62
|
+
self.obj.tags.remove(*tag_objects)
|
|
63
|
+
|
|
64
|
+
def set_tags_from_tag_set(self, tag_set: set[str]):
|
|
65
|
+
self._update_global_tags_from_set(tag_set)
|
|
66
|
+
|
|
67
|
+
tag_objects = Tag.objects.in_tag_set(tag_set)
|
|
68
|
+
self.obj.tags.set(tag_objects)
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _update_global_tags_from_set(tag_set: set[str]):
|
|
72
|
+
Tag.add_tags([Tag(name=tag) for tag in tag_set])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from unittest import TestCase
|
|
2
2
|
|
|
3
|
-
from django_spire.core.
|
|
3
|
+
from django_spire.core.tag.intelligence.tag_set_bot import TagSetBot
|
|
4
4
|
|
|
5
5
|
TEST_INPUT = """
|
|
6
6
|
I love reading books about science fiction, fantasy, and adventure, especially ones
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tag.models import Tag
|
|
4
|
+
from django_spire.knowledge.collection.models import Collection
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestTags(TestCase):
|
|
8
|
+
def setUp(self):
|
|
9
|
+
self.collection = Collection.objects.create(
|
|
10
|
+
name='Test Collection', description='Test Collection Description'
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
self.tag_set_a = {'apple', 'banana', 'orange'}
|
|
14
|
+
self.tag_set_b = {'grape', 'kiwi', 'pear'}
|
|
15
|
+
self.tag_set_c = {'lemon', 'lime', 'mango'}
|
|
16
|
+
|
|
17
|
+
self.mixed_tag_set = {'apple', 'grape', 'lemon'}
|
|
18
|
+
|
|
19
|
+
self.dirty_tag_set = {'HaT~', 'four* tacos', '123 hoop!!'}
|
|
20
|
+
|
|
21
|
+
self.collection.services.tag.add_tags_from_tag_set(self.tag_set_a)
|
|
22
|
+
self.collection.services.tag.add_tags_from_tag_set(self.tag_set_b)
|
|
23
|
+
self.collection.services.tag.add_tags_from_tag_set(self.tag_set_c)
|
|
24
|
+
|
|
25
|
+
self.collection.services.tag.add_tags_from_tag_set(self.mixed_tag_set)
|
|
26
|
+
|
|
27
|
+
self.collection.services.tag.add_tags_from_tag_set(self.dirty_tag_set)
|
|
28
|
+
|
|
29
|
+
def test_tag_removal(self):
|
|
30
|
+
self.assertIn('apple', self.collection.tag_set)
|
|
31
|
+
|
|
32
|
+
self.collection.services.tag.remove_tags_by_tag_set(self.mixed_tag_set)
|
|
33
|
+
|
|
34
|
+
self.assertNotIn('lemon', self.collection.tag_set)
|
|
35
|
+
|
|
36
|
+
self.assertIn('kiwi', self.collection.tag_set)
|
|
37
|
+
|
|
38
|
+
def test_tag_add_and_set(self):
|
|
39
|
+
self.collection.services.tag.set_tags_from_tag_set(self.mixed_tag_set)
|
|
40
|
+
|
|
41
|
+
self.assertIn('lemon', self.collection.tag_set)
|
|
42
|
+
|
|
43
|
+
self.assertNotIn('banana', self.collection.tag_set)
|
|
44
|
+
|
|
45
|
+
def test_tag_count(self):
|
|
46
|
+
self.assertEqual(Tag.objects.count(), 12)
|
|
47
|
+
self.assertEqual(self.collection.tags.count(), 12)
|
|
48
|
+
|
|
49
|
+
def test_tag_removal(self):
|
|
50
|
+
self.collection.services.tag.remove_tags_by_tag_set(self.tag_set_a)
|
|
51
|
+
|
|
52
|
+
self.assertEqual(self.collection.tags.count(), 9)
|
|
53
|
+
|
|
54
|
+
def test_tag_bool_methods(self):
|
|
55
|
+
self.collection.services.tag.remove_tags_by_tag_set(self.tag_set_b)
|
|
56
|
+
|
|
57
|
+
self.assertTrue(self.collection.services.tag.has_tags_in_tag_set(self.tag_set_c))
|
|
58
|
+
self.assertFalse(self.collection.services.tag.has_tags_in_tag_set(self.mixed_tag_set))
|
|
59
|
+
|
|
60
|
+
def test_tag_matching_methods(self):
|
|
61
|
+
self.assertEqual(len(self.collection.services.tag.get_matching_tags_from_tag_set(self.tag_set_a)), 3)
|
|
62
|
+
|
|
63
|
+
self.collection.services.tag.remove_tags_by_tag_set(self.tag_set_a)
|
|
64
|
+
|
|
65
|
+
self.assertEqual(len(self.collection.services.tag.get_matching_tags_from_tag_set(self.tag_set_a)), 0)
|
|
66
|
+
self.assertEqual(len(self.collection.services.tag.get_matching_tags_from_tag_set(self.tag_set_b)), 3)
|
|
67
|
+
self.assertEqual(len(self.collection.services.tag.get_matching_tags_from_tag_set(self.mixed_tag_set)), 2)
|
|
68
|
+
|
|
69
|
+
self.collection.services.tag.remove_tags_by_tag_set(self.tag_set_b)
|
|
70
|
+
|
|
71
|
+
self.assertEqual(len(self.collection.services.tag.get_matching_tags_from_tag_set(self.tag_set_b)), 0)
|
|
72
|
+
|
|
73
|
+
self.assertEqual(len(self.collection.services.tag.get_matching_tags_from_tag_set(self.mixed_tag_set)), 1)
|
|
74
|
+
|
|
75
|
+
def test_tag_metric_methods(self):
|
|
76
|
+
self.assertAlmostEqual(
|
|
77
|
+
self.collection.services.tag.get_matching_percentage_of_tag_set(self.tag_set_a),
|
|
78
|
+
1.00,
|
|
79
|
+
2
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.assertAlmostEqual(
|
|
83
|
+
self.collection.services.tag.get_matching_percentage_of_model_tags_from_tag_set(self.tag_set_b),
|
|
84
|
+
0.25,
|
|
85
|
+
2
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.collection.services.tag.remove_tags_by_tag_set(self.mixed_tag_set)
|
|
89
|
+
|
|
90
|
+
self.assertAlmostEqual(
|
|
91
|
+
self.collection.services.tag.get_matching_percentage_of_tag_set(self.tag_set_a),
|
|
92
|
+
0.666,
|
|
93
|
+
2
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
self.assertAlmostEqual(
|
|
98
|
+
self.collection.services.tag.get_matching_percentage_of_model_tags_from_tag_set(self.tag_set_b),
|
|
99
|
+
0.222,
|
|
100
|
+
2
|
|
101
|
+
)
|
|
102
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
def get_matching_tag_set_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> set[str]:
|
|
2
|
+
return tag_set_a.intersection(tag_set_b)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_matching_count_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> int:
|
|
6
|
+
return len(get_matching_tag_set_from_tag_sets(tag_set_a, tag_set_b))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_matching_a_percentage_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> float:
|
|
10
|
+
try:
|
|
11
|
+
return get_matching_count_from_tag_sets(tag_set_a, tag_set_b) / len(tag_set_a)
|
|
12
|
+
except ZeroDivisionError:
|
|
13
|
+
return 0.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_matching_b_percentage_from_tag_sets(tag_set_a: set[str], tag_set_b: set[str]) -> float:
|
|
17
|
+
try:
|
|
18
|
+
return get_matching_count_from_tag_sets(tag_set_a, tag_set_b) / len(tag_set_b)
|
|
19
|
+
except ZeroDivisionError:
|
|
20
|
+
return 0.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_score_percentage_from_tag_set_weighted(tag_set_actual: set[str], tag_set_reference: set[str]) -> float:
|
|
24
|
+
total_points = len(simplify_tag_set(tag_set_reference))
|
|
25
|
+
|
|
26
|
+
multiplier = get_matching_count_from_tag_sets(tag_set_actual, tag_set_reference)
|
|
27
|
+
|
|
28
|
+
weighted_points = sum(
|
|
29
|
+
weight
|
|
30
|
+
for tag, weight in simplify_and_weight_tag_set_to_dict(tag_set_reference).items()
|
|
31
|
+
if tag in tag_set_actual
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if weighted_points == 0 or total_points == 0:
|
|
35
|
+
return 0.0
|
|
36
|
+
|
|
37
|
+
return (weighted_points / total_points) * multiplier
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def simplify_tag_set(tag_set: set[str]) -> set[str]:
|
|
41
|
+
return set(simplify_tag_set_to_list(tag_set))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def simplify_tag_set_to_list(tag_set: set[str]) -> list[str]:
|
|
45
|
+
simplified_tag_words = []
|
|
46
|
+
|
|
47
|
+
for tag in tag_set:
|
|
48
|
+
tag_words = tag.split('-')
|
|
49
|
+
simplified_tag_words.extend(tag_words)
|
|
50
|
+
|
|
51
|
+
return simplified_tag_words
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def simplify_and_weight_tag_set_to_dict(tag_set: set[str]) -> dict[str, int]:
|
|
55
|
+
simplified_and_weighted_tag_words = {}
|
|
56
|
+
|
|
57
|
+
for tag_word in simplify_tag_set_to_list(tag_set):
|
|
58
|
+
simplified_and_weighted_tag_words[tag_word] = simplified_and_weighted_tag_words.get(tag_word, 0) + 1
|
|
59
|
+
|
|
60
|
+
return dict(
|
|
61
|
+
sorted(
|
|
62
|
+
simplified_and_weighted_tag_words.items(),
|
|
63
|
+
key=lambda item: item[1],
|
|
64
|
+
reverse=True
|
|
65
|
+
)
|
|
66
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{% extends 'django_spire/dropdown/dropdown.html' %}
|
|
2
|
+
|
|
3
|
+
{% block dropdown_position %}start-0 top-100{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block dropdown_trigger %}
|
|
6
|
+
<span
|
|
7
|
+
class="bi bi-three-dots-vertical px-1 {% block ellipsis_class %}{% endblock %}"
|
|
8
|
+
style="overflow: visible;"
|
|
9
|
+
x-bind="trigger"
|
|
10
|
+
>
|
|
11
|
+
</span>
|
|
12
|
+
{% endblock %}
|
|
13
|
+
|
|
14
|
+
{% block dropdown_content %}
|
|
15
|
+
{% if view_url %}
|
|
16
|
+
{% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with view_url=view_url link_text='View' %}
|
|
17
|
+
{% endif %}
|
|
18
|
+
|
|
19
|
+
{% if edit_url %}
|
|
20
|
+
{% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with view_url=edit_url link_text='Edit' %}
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
23
|
+
{% if delete_url %}
|
|
24
|
+
{% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with view_url=delete_url link_text='Delete' link_css='text-app-danger' %}
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% endblock %}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<form
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
method="get"
|
|
3
|
+
action="{% block session_filter_url %}{% endblock %}"
|
|
4
|
+
class="row border-bottom"
|
|
5
5
|
>
|
|
6
6
|
<div class="col-12">
|
|
7
7
|
<input type="text" hidden value="{% block session_filter_key %}{% endblock %}" name="session_filter_key">
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{% block filter_content %}{% endblock %}
|
|
10
10
|
|
|
11
11
|
{% block filter_buttons %}
|
|
12
|
-
<div class="
|
|
12
|
+
<div class="pt-2 pb-5">
|
|
13
13
|
<input
|
|
14
14
|
type="submit"
|
|
15
15
|
name="action"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{% extends 'django_spire/modal/content/modal_title_content.html' %}
|
|
2
2
|
|
|
3
3
|
{% block modal_content_title %}
|
|
4
|
-
{{ form_title }}
|
|
4
|
+
{{ form_title|truncatechars:75 }}
|
|
5
5
|
{% endblock %}
|
|
6
6
|
|
|
7
7
|
{% block modal_content_content %}
|
|
@@ -16,14 +16,16 @@
|
|
|
16
16
|
}"
|
|
17
17
|
>
|
|
18
18
|
{% csrf_token %}
|
|
19
|
+
|
|
19
20
|
{{ form_description }}
|
|
20
21
|
|
|
21
22
|
{% include 'django_glue/form/field/input_field.html' with glue_field='should_delete' %}
|
|
22
23
|
|
|
23
24
|
<div class="d-flex mt-3">
|
|
24
|
-
{% include 'django_spire/contrib/form/button/form_submit_button.html' with button_text='Delete' button_class='btn-app-danger
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
{% include 'django_spire/contrib/form/button/form_submit_button.html' with button_text='Delete' button_class='btn-app-danger shadow-sm' %}
|
|
26
|
+
|
|
27
|
+
<span class="ms-2" @click="close_modal()">
|
|
28
|
+
{% include 'django_spire/button/secondary_button.html' with button_text='Cancel' button_class='btn' %}
|
|
27
29
|
</span>
|
|
28
30
|
</div>
|
|
29
31
|
</form>
|