Skip to content

Commit

Permalink
feat: Search added to get tags view
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Sep 11, 2023
1 parent 702cc05 commit 3e5f659
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 40 deletions.
15 changes: 11 additions & 4 deletions openedx_tagging/core/tagging/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,29 @@ def get_tags(taxonomy: Taxonomy) -> list[Tag]:
return taxonomy.cast().get_tags()


def get_root_tags(taxonomy: Taxonomy) -> list[Tag]:
def get_root_tags(taxonomy: Taxonomy, search_term: str | None = None) -> 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 taxonomy.cast().get_tags(only_roots=True)
return list(taxonomy.cast().get_filtered_tags(search_term=search_term))


def get_children_tags(taxonomy: Taxonomy, parent_tag_id: int) -> list[Tag]:
def get_children_tags(
taxonomy: Taxonomy,
parent_tag_id: int,
search_term: str | None = None,
) -> list[Tag]:
"""
Returns a list of children tags for the given parent tag.
Note that if the taxonomy allows free-text tags, then the returned list will be empty.
"""
return taxonomy.cast().get_children_tags(parent_tag_id)
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 Down
38 changes: 24 additions & 14 deletions openedx_tagging/core/tagging/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,14 @@ def copy(self, taxonomy: Taxonomy) -> Taxonomy:

def get_tags(
self,
tag_set: models.QuerySet | None = None,
only_roots: bool = False,
tag_set: models.QuerySet[Tag] | None = None,
) -> list[Tag]:
"""
Returns a list of all Tags in the current taxonomy, from the root(s)
down to TAXONOMY_MAX_DEPTH tags, in tree order.
Use `tag_set` to do an initial filtering of the tags.
Use `only_roots` to get only the root tags (depth=0).
Annotates each returned Tag with its ``depth`` in the tree (starting at
0).
Expand All @@ -295,11 +292,8 @@ def get_tags(
tag_set = self.tag_set.all()

parents = None
max_depth = TAXONOMY_MAX_DEPTH
if only_roots:
max_depth = 1

for depth in range(max_depth):
for depth in range(TAXONOMY_MAX_DEPTH):
filtered_tags = tag_set.prefetch_related("parent")
if parents is None:
filtered_tags = filtered_tags.filter(parent=None)
Expand All @@ -320,16 +314,32 @@ def get_tags(
break
return tags

def get_children_tags(self, parent_tag_id: int) -> list[Tag]:
def get_filtered_tags(
self,
tag_set: models.QuerySet | None = None,
parent_tag_id: int | None = None,
search_term: str | None = None,
) -> models.QuerySet[Tag]:
"""
Returns a list of children tags of `parent_tag_id` in the current taxonomy.
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 `search_term` to filter the results by values that contains `search_term`.
"""
if tag_set is None:
tag_set = self.tag_set

if self.allow_free_text:
return []
return tag_set.none()

tag_set = tag_set.filter(parent=parent_tag_id)

if search_term:
tag_set = tag_set.filter(value__icontains=search_term)

return list(self.tag_set.filter(parent=parent_tag_id).order_by(
"parent__value", "value", "id"
))
return tag_set.order_by("value", "id")

def validate_object_tag(
self,
Expand Down
28 changes: 24 additions & 4 deletions openedx_tagging/core/tagging/models/system_defined.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from django.contrib.auth import get_user_model
from django.db import models

from openedx_tagging.core.tagging.models.base import ObjectTag
from openedx_tagging.core.tagging.models.base import ObjectTag, Tag

from .base import ObjectTag, Tag, Taxonomy

Expand Down Expand Up @@ -243,15 +243,35 @@ class Meta:

def get_tags(
self,
tag_set: models.QuerySet | None = None,
only_roots: bool = False,
tag_set: models.QuerySet[Tag] | None = None,
) -> list[Tag]:
"""
Returns a list of all the available Language Tags, annotated with ``depth`` = 0.
"""
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, only_roots=only_roots)
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
) -> models.QuerySet[Tag]:
"""
Returns a filtered QuerySet of available Language Tags.
By default returns all the available Language Tags.
`parent_tag_id` returns an empty result because all Language tags are root tags.
Use `search_term` to filter the results by values that contains `search_term`.
"""
if parent_tag_id:
return self.tag_set.none()

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)

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

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


class TagsPagination(DefaultPagination):
"""
Expand Down
38 changes: 31 additions & 7 deletions openedx_tagging/core/tagging/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@
)
from ...models import Taxonomy
from ...rules import ChangeObjectTagPermissionItem
from ..paginators import TAGS_THRESHOLD, DisabledTagsPagination, TagsPagination
from .permissions import ObjectTagObjectPermissions, TagListPermissions, TaxonomyObjectPermissions
from ..paginators import (
TAGS_THRESHOLD,
SEARCH_TAGS_THRESHOLD,
DisabledTagsPagination,
TagsPagination,
)
from .permissions import (
ObjectTagObjectPermissions,
TagListPermissions,
TaxonomyObjectPermissions,
)
from .serializers import (
ObjectTagListQueryParamsSerializer,
ObjectTagSerializer,
Expand Down Expand Up @@ -375,7 +384,8 @@ def get_taxonomy(self, pk: int) -> Taxonomy:
def get_matching_tags(
self,
taxonomy_id: int,
parent_tag_id: str | None = None
parent_tag_id: str | None = None,
search_term: str | None = None,
) -> tuple[list[Tag], bool]:
"""
Returns a list of tags for the given taxonomy. Also returns a boolean
Expand All @@ -386,14 +396,28 @@ def get_matching_tags(
Use `parent_tag_id` to get the children of the given tag.
TODO: Search tags
Use `search_term` to filter tags values that contains the given term.
TODO: Missing tests for search
"""
taxonomy = self.get_taxonomy(taxonomy_id)
if parent_tag_id:
return get_children_tags(taxonomy, int(parent_tag_id)), True
return get_children_tags(
taxonomy,
int(parent_tag_id),
search_term=search_term,
), True
else:
pagination_enabled = taxonomy.tag_set.count() > TAGS_THRESHOLD
return get_root_tags(taxonomy), pagination_enabled
result = get_root_tags(
taxonomy,
search_term=search_term
)
if search_term:
pagination_enabled = len(result) > SEARCH_TAGS_THRESHOLD
else:
pagination_enabled = taxonomy.tag_set.count() > TAGS_THRESHOLD

return result, pagination_enabled

def get_queryset(self) -> list[Tag]: # type: ignore
"""
Expand Down
36 changes: 32 additions & 4 deletions tests/openedx_tagging/core/tagging/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@
("az", "Azerbaijani"),
("en", "English"),
("id", "Indonesian"),
("ga", "Irish"),
("pl", "Polish"),
("qu", "Quechua"),
("zu", "Zulu"),
]
# Languages that contains 'ish'
filtered_test_languages = [
("en", "English"),
("ga", "Irish"),
("pl", "Polish"),
]


@ddt.ddt
Expand Down Expand Up @@ -112,18 +120,38 @@ def test_get_tags(self) -> None:
def test_get_root_tags(self):
self.setup_tag_depths()
assert tagging_api.get_root_tags(self.taxonomy) == self.domain_tags
assert (
tagging_api.get_root_tags(self.taxonomy, search_term='aR')
== self.filtered_domain_tags
)
assert tagging_api.get_root_tags(self.system_taxonomy) == self.system_tags
tags = tagging_api.get_root_tags(self.language_taxonomy)
langs = [tag.external_id for tag in tags]
expected_langs = [lang[0] for lang in test_languages]
assert langs == expected_langs

tags = tagging_api.get_root_tags(self.language_taxonomy, search_term='IsH')
langs = [tag.external_id for tag in tags]
expected_langs = [lang[0] for lang in filtered_test_languages]
assert langs == expected_langs

def test_get_children_tags(self):
assert (tagging_api.get_children_tags(self.taxonomy, self.animalia.id) ==
self.phylum_tags)
assert tagging_api.get_children_tags(
self.taxonomy,
self.animalia.id,
) == self.phylum_tags
assert tagging_api.get_children_tags(
self.taxonomy,
self.animalia.id,
search_term='dA',
) == self.filtered_phylum_tags
assert not tagging_api.get_children_tags(
self.system_taxonomy,
self.system_taxonomy_tag.id,
)
assert not tagging_api.get_children_tags(
self.system_taxonomy,
self.system_taxonomy_tag.id
self.language_taxonomy,
self.english_tag,
)

def check_object_tag(
Expand Down
52 changes: 45 additions & 7 deletions tests/openedx_tagging/core/tagging/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def setUp(self):
self.mammalia = get_tag("Mammalia")
self.animalia = get_tag("Animalia")
self.system_taxonomy_tag = get_tag("System Tag 1")
self.english_tag = get_tag("English")
self.user_1 = get_user_model()(
id=1,
username="test_user_1",
Expand All @@ -59,6 +60,12 @@ def setUp(self):
get_tag("Bacteria"),
get_tag("Eukaryota"),
]
# Domain tags that contains 'ar'
self.filtered_domain_tags = [
get_tag("Archaea"),
get_tag("Eukaryota"),
]

# Kingdom tags (depth=1)
self.kingdom_tags = [
# Kingdoms of https://en.wikipedia.org/wiki/Archaea
Expand All @@ -75,6 +82,7 @@ def setUp(self):
get_tag("Plantae"),
get_tag("Protista"),
]

# Phylum tags (depth=2)
self.phylum_tags = [
# Some phyla of https://en.wikipedia.org/wiki/Animalia
Expand All @@ -86,6 +94,12 @@ def setUp(self):
get_tag("Placozoa"),
get_tag("Porifera"),
]
# Phylum tags that contains 'da'
self.filtered_phylum_tags = [
get_tag("Arthropoda"),
get_tag("Chordata"),
get_tag("Cnidaria"),
]

self.system_tags = [
get_tag("System Tag 1"),
Expand Down Expand Up @@ -221,23 +235,47 @@ def test_get_tags(self):
*self.phylum_tags,
]

def test_get_only_roots(self):
self.setup_tag_depths()
assert self.taxonomy.get_tags(only_roots=True) == self.domain_tags
def test_get_root_tags(self):
assert list(self.taxonomy.get_filtered_tags()) == self.domain_tags
assert list(
self.taxonomy.get_filtered_tags(search_term='aR')
) == self.filtered_domain_tags

def test_get_tags_free_text(self):
self.taxonomy.allow_free_text = True
with self.assertNumQueries(0):
assert self.taxonomy.get_tags() == []

def test_get_children_tags(self):
assert (list(self.taxonomy.get_children_tags(self.animalia.id)) ==
self.phylum_tags)
assert not list(self.system_taxonomy.get_children_tags(self.system_taxonomy_tag.id))
assert list(
self.taxonomy.get_filtered_tags(parent_tag_id=self.animalia.id)
) == self.phylum_tags
print(self.taxonomy.get_filtered_tags(
parent_tag_id=self.animalia.id,
search_term='dA',
))
print(self.filtered_phylum_tags)
assert list(
self.taxonomy.get_filtered_tags(
parent_tag_id=self.animalia.id,
search_term='dA',
)
) == self.filtered_phylum_tags
assert not list(
self.system_taxonomy.get_filtered_tags(
parent_tag_id=self.system_taxonomy_tag.id
)
)

def test_get_children_tags_free_text(self):
self.taxonomy.allow_free_text = True
assert not list(self.taxonomy.get_children_tags(self.animalia.id))
assert not list(self.taxonomy.get_filtered_tags(
parent_tag_id=self.animalia.id
))
assert not list(self.taxonomy.get_filtered_tags(
parent_tag_id=self.animalia.id,
search_term='dA',
))

def test_get_tags_shallow_taxonomy(self):
taxonomy = Taxonomy.objects.create(name="Difficulty")
Expand Down
Loading

0 comments on commit 3e5f659

Please sign in to comment.