Skip to content

Commit

Permalink
feat: searching tags
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Sep 12, 2023
1 parent 3e5f659 commit 58255f1
Show file tree
Hide file tree
Showing 14 changed files with 281 additions and 87 deletions.
32 changes: 20 additions & 12 deletions openedx_tagging/core/tagging/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,22 @@ def get_tags(taxonomy: Taxonomy) -> list[Tag]:
return taxonomy.cast().get_tags()


def get_root_tags(taxonomy: Taxonomy, search_term: str | None = None) -> list[Tag]:
def get_root_tags(taxonomy: Taxonomy) -> list[Tag]:
"""
Returns a list of the root tags for the given taxonomy.
Note that if the taxonomy allows free-text tags, then the returned list will be empty.
"""
return list(taxonomy.cast().get_filtered_tags(search_term=search_term))
return list(taxonomy.cast().get_filtered_tags())


def search_tags(taxonomy: Taxonomy, search_term: str) -> list[Tag]:
return list(
taxonomy.cast().get_filtered_tags(
search_term=search_term,
search_in_all=True,
)
)


def get_children_tags(
Expand All @@ -98,10 +107,12 @@ def get_children_tags(
Note that if the taxonomy allows free-text tags, then the returned list will be empty.
"""
return list(taxonomy.cast().get_filtered_tags(
parent_tag_id=parent_tag_id,
search_term=search_term,
))
return list(
taxonomy.cast().get_filtered_tags(
parent_tag_id=parent_tag_id,
search_term=search_term,
)
)


def resync_object_tags(object_tags: QuerySet | None = None) -> int:
Expand All @@ -123,8 +134,7 @@ def resync_object_tags(object_tags: QuerySet | None = None) -> int:


def get_object_tags(
object_id: str,
taxonomy_id: str | None = None
object_id: str, taxonomy_id: str | None = None
) -> QuerySet[ObjectTag]:
"""
Returns a Queryset of object tags for a given object.
Expand All @@ -149,10 +159,8 @@ def delete_object_tags(object_id: str):
"""
Delete all ObjectTag entries for a given object.
"""
tags = (
ObjectTag.objects.filter(
object_id=object_id,
)
tags = ObjectTag.objects.filter(
object_id=object_id,
)

tags.delete()
Expand Down
1 change: 1 addition & 0 deletions openedx_tagging/core/tagging/import_export/import_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class TagItem:
"""
Tag representation on the tag import plan
"""

id: str
value: str
index: int | None = 0
Expand Down
4 changes: 3 additions & 1 deletion openedx_tagging/core/tagging/import_export/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ def _export_data(cls, tags: list[dict], taxonomy: Taxonomy) -> str:
raise NotImplementedError

@classmethod
def _parse_tags(cls, tags_data: list[dict]) -> tuple[list[TagItem], list[TagParserError]]:
def _parse_tags(
cls, tags_data: list[dict]
) -> tuple[list[TagItem], list[TagParserError]]:
"""
Validate the required fields of each tag.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@


class Migration(migrations.Migration):

dependencies = [
('oel_tagging', '0006_auto_20230802_1631'),
("oel_tagging", "0006_auto_20230802_1631"),
]

operations = [
migrations.AlterField(
model_name='tagimporttask',
name='log',
field=models.TextField(blank=True, default=None, help_text='Action execution logs'),
model_name="tagimporttask",
name="log",
field=models.TextField(
blank=True, default=None, help_text="Action execution logs"
),
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@


class Migration(migrations.Migration):

dependencies = [
('oel_tagging', '0007_tag_import_task_log_null_fix'),
("oel_tagging", "0007_tag_import_task_log_null_fix"),
]

operations = [
migrations.AlterField(
model_name='taxonomy',
name='description',
field=openedx_learning.lib.fields.MultiCollationTextField(blank=True, default='', help_text='Provides extra information for the user when applying tags from this taxonomy to an object.'),
model_name="taxonomy",
name="description",
field=openedx_learning.lib.fields.MultiCollationTextField(
blank=True,
default="",
help_text="Provides extra information for the user when applying tags from this taxonomy to an object.",
),
preserve_default=False,
),
]
26 changes: 17 additions & 9 deletions openedx_tagging/core/tagging/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,25 +316,29 @@ def get_tags(

def get_filtered_tags(
self,
tag_set: models.QuerySet | None = None,
tag_set: models.QuerySet[Tag] | None = None,
parent_tag_id: int | None = None,
search_term: str | None = None,
search_term: str | None = None,
search_in_all: bool = False,
) -> models.QuerySet[Tag]:
"""
Returns a filtered QuerySet of tags.
By default returns the root tags of the given taxonomy
Use `parent_tag_id` to retunr the children of a tag.
Use `parent_tag_id` to return the children of a tag.
Use `search_term` to filter the results by values that contains `search_term`.
Set `search_in_all` to True to make the search in all tags on the given taxonomy.
"""
if tag_set is None:
tag_set = self.tag_set
tag_set = self.tag_set.all()

if self.allow_free_text:
return tag_set.none()

tag_set = tag_set.filter(parent=parent_tag_id)
if not search_in_all:
tag_set = tag_set.filter(parent=parent_tag_id)

if search_term:
tag_set = tag_set.filter(value__icontains=search_term)
Expand Down Expand Up @@ -379,7 +383,9 @@ def _check_taxonomy(
Subclasses can override this method to perform their own taxonomy validation checks.
"""
# Must be linked to this taxonomy
return (object_tag.taxonomy_id is not None) and object_tag.taxonomy_id == self.id
return (
object_tag.taxonomy_id is not None
) and object_tag.taxonomy_id == self.id

def _check_tag(
self,
Expand Down Expand Up @@ -512,9 +518,11 @@ def autocomplete_tags(
# Fetch tags that the object already has to exclude them from the result
excluded_tags: list[str] = []
if object_id:
excluded_tags = list(self.objecttag_set.filter(object_id=object_id).values_list(
"_value", flat=True
))
excluded_tags = list(
self.objecttag_set.filter(object_id=object_id).values_list(
"_value", flat=True
)
)
return (
# Fetch object tags from this taxonomy whose value contains the search
self.objecttag_set.filter(_value__icontains=search)
Expand Down
11 changes: 8 additions & 3 deletions openedx_tagging/core/tagging/models/system_defined.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,13 @@ def get_tags(
available_langs = self._get_available_languages()
tag_set = self.tag_set.filter(external_id__in=available_langs)
return super().get_tags(tag_set=tag_set)

def get_filtered_tags(
self,
tag_set: models.QuerySet[Tag] | None = None,
parent_tag_id: int | None = None,
search_term: str | None = None
search_term: str | None = None,
search_in_all: bool = False,
) -> models.QuerySet[Tag]:
"""
Returns a filtered QuerySet of available Language Tags.
Expand All @@ -271,7 +272,11 @@ def get_filtered_tags(

available_langs = self._get_available_languages()
tag_set = self.tag_set.filter(external_id__in=available_langs)
return super().get_filtered_tags(tag_set=tag_set, search_term=search_term)
return super().get_filtered_tags(
tag_set=tag_set,
search_term=search_term,
search_in_all=search_in_all,
)

def _get_available_languages(cls) -> set[str]:
"""
Expand Down
2 changes: 1 addition & 1 deletion openedx_tagging/core/tagging/rest_api/paginators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# From this point, the tags begin to be paginated
TAGS_THRESHOLD = 1000

# From this point, search tags begin to be paginated
# From this point, search tags begin to be paginated
SEARCH_TAGS_THRESHOLD = 200


Expand Down
35 changes: 33 additions & 2 deletions openedx_tagging/core/tagging/rest_api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,18 @@ class ObjectTagUpdateQueryParamsSerializer(serializers.Serializer):
Serializer of the query params for the ObjectTag UPDATE view
"""

taxonomy = serializers.PrimaryKeyRelatedField(queryset=Taxonomy.objects.all(), required=True)
taxonomy = serializers.PrimaryKeyRelatedField(
queryset=Taxonomy.objects.all(), required=True
)


class TagsSerializer(serializers.ModelSerializer):
"""
Serializer for Tags
Adds a link to get the sub tags
"""

sub_tags_link = serializers.SerializerMethodField()
children_count = serializers.SerializerMethodField()

Expand Down Expand Up @@ -104,6 +112,12 @@ def get_children_count(self, obj):


class TagsWithSubTagsSerializer(serializers.ModelSerializer):
"""
Serializer for Tags.
Represents a tree with a list of sub tags
"""

sub_tags = serializers.SerializerMethodField()
children_count = serializers.SerializerMethodField()

Expand All @@ -119,9 +133,26 @@ class Meta:

def get_sub_tags(self, obj):
serializer = TagsWithSubTagsSerializer(
obj.children.all(), many=True, read_only=True
obj.children.all().order_by("value", "id"),
many=True,
read_only=True,
)
return serializer.data

def get_children_count(self, obj):
return obj.children.count()


class TagsForSearchSerializer(TagsWithSubTagsSerializer):
"""
Serializer for Tags
Used to filter sub tags of a given tag
"""

def get_sub_tags(self, obj):
serializer = TagsWithSubTagsSerializer(obj.sub_tags, many=True, read_only=True)
return serializer.data

def get_children_count(self, obj):
return len(obj.sub_tags)
Loading

0 comments on commit 58255f1

Please sign in to comment.