Skip to content
2 changes: 1 addition & 1 deletion openedx_learning/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""
Open edX Learning ("Learning Core").
"""
__version__ = "0.1.6"
__version__ = "0.1.7"
14 changes: 14 additions & 0 deletions openedx_tagging/core/tagging/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,25 @@ def _find_object_tag_index(tag_ref, object_tags) -> int:
-1,
)

def _check_new_tag_count(new_tag_count: int) -> None:
"""
Checks if the new count of tags for the object is equal or less than 100
"""
# Exclude self.id to avoid counting the tags that are going to be updated
current_count = ObjectTag.objects.filter(object_id=object_id).exclude(taxonomy_id=self.id).count()

if current_count + new_tag_count > 100:
raise ValueError(
_(f"Cannot add more than 100 tags to ({object_id}).")
)

if not isinstance(tags, list):
raise ValueError(_(f"Tags must be a list, not {type(tags).__name__}."))

tags = list(dict.fromkeys(tags)) # Remove duplicates preserving order

_check_new_tag_count(len(tags))

if not self.allow_multiple and len(tags) > 1:
raise ValueError(_(f"Taxonomy ({self.id}) only allows one tag per object."))

Expand Down
18 changes: 6 additions & 12 deletions openedx_tagging/core/tagging/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from rest_framework import mixins
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied, ValidationError
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ModelViewSet

from openedx_tagging.core.tagging.models.base import Tag
Expand Down Expand Up @@ -194,21 +195,17 @@ class ObjectTagView(
GenericViewSet,
):
"""
View to retrieve paginated ObjectTags for a provided Object ID (object_id).
View to retrieve ObjectTags for a provided Object ID (object_id).

**Retrieve Parameters**
* object_id (required): - The Object ID to retrieve ObjectTags for.

**Retrieve Query Parameters**
* taxonomy (optional) - PK of taxonomy to filter ObjectTags for.
* page (optional) - Page number of paginated results.
* page_size (optional) - Number of results included in each page.

**Retrieve Example Requests**
GET api/tagging/v1/object_tags/:object_id
GET api/tagging/v1/object_tags/:object_id?taxonomy=1
GET api/tagging/v1/object_tags/:object_id?taxonomy=1&page=2
GET api/tagging/v1/object_tags/:object_id?taxonomy=1&page=2&page_size=10

**Retrieve Query Returns**
* 200 - Success
Expand Down Expand Up @@ -255,8 +252,7 @@ def get_queryset(self) -> models.QuerySet:

def retrieve(self, request, object_id=None):
"""
Retrieve ObjectTags that belong to a given object_id and
return paginated results.
Retrieve ObjectTags that belong to a given object_id

Note: We override `retrieve` here instead of `list` because we are
passing in the Object ID (object_id) in the path (as opposed to passing
Expand All @@ -266,14 +262,12 @@ def retrieve(self, request, object_id=None):
behavior we want.
"""
object_tags = self.get_queryset()
paginated_object_tags = self.paginate_queryset(object_tags)
serializer = ObjectTagSerializer(paginated_object_tags, many=True)
return self.get_paginated_response(serializer.data)
serializer = ObjectTagSerializer(object_tags, many=True)
return Response(serializer.data)

def update(self, request, object_id, partial=False):
"""
Update ObjectTags that belong to a given object_id and
return the list of these ObjecTags paginated.
Update ObjectTags that belong to a given object_id

Pass a list of Tag ids or Tag values to be applied to an object id in the
body `tag` parameter. Passing an empty list will remove all tags from
Expand Down
47 changes: 44 additions & 3 deletions tests/openedx_tagging/core/tagging/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_bad_taxonomy_class(self) -> None:
def test_get_taxonomy(self) -> None:
tax1 = tagging_api.get_taxonomy(1)
assert tax1 == self.taxonomy
no_tax = tagging_api.get_taxonomy(10)
no_tax = tagging_api.get_taxonomy(200)
assert no_tax is None

def test_get_taxonomies(self) -> None:
Expand All @@ -78,7 +78,7 @@ def test_get_taxonomies(self) -> None:
self.taxonomy,
self.system_taxonomy,
self.user_taxonomy,
]
] + self.dummy_taxonomies
assert str(enabled[0]) == f"<Taxonomy> ({tax1.id}) Enabled"
assert str(enabled[1]) == "<Taxonomy> (5) Import Taxonomy Test"
assert str(enabled[2]) == "<Taxonomy> (-1) Languages"
Expand All @@ -100,7 +100,7 @@ def test_get_taxonomies(self) -> None:
self.taxonomy,
self.system_taxonomy,
self.user_taxonomy,
]
] + self.dummy_taxonomies

@override_settings(LANGUAGES=test_languages)
def test_get_tags(self) -> None:
Expand Down Expand Up @@ -587,6 +587,47 @@ def test_tag_object_model_system_taxonomy_invalid(self) -> None:
exc.exception
)

def test_tag_object_limit(self) -> None:
"""
Test that the tagging limit is enforced.
"""
# The user can add up to 100 tags to a object
for taxonomy in self.dummy_taxonomies:
tagging_api.tag_object(
taxonomy,
["Dummy Tag"],
"object_1",
)

# Adding a new tag should fail
with self.assertRaises(ValueError) as exc:
tagging_api.tag_object(
self.taxonomy,
["Eubacteria"],
"object_1",
)
assert exc.exception
assert "Cannot add more than 100 tags to" in str(exc.exception)

# Updating existing tags should work
for taxonomy in self.dummy_taxonomies:
tagging_api.tag_object(
taxonomy,
["New Dummy Tag"],
"object_1",
)

# Updating existing tags adding a new one should fail
for taxonomy in self.dummy_taxonomies:
with self.assertRaises(ValueError) as exc:
tagging_api.tag_object(
taxonomy,
["New Dummy Tag 1", "New Dummy Tag 2"],
"object_1",
)
assert exc.exception
assert "Cannot add more than 100 tags to" in str(exc.exception)

def test_get_object_tags(self) -> None:
# Alpha tag has no taxonomy
alpha = ObjectTag(object_id="abc")
Expand Down
15 changes: 15 additions & 0 deletions tests/openedx_tagging/core/tagging/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ def setUp(self):
get_tag("System Tag 4"),
]

self.dummy_taxonomies = []
for i in range(100):
taxonomy = Taxonomy.objects.create(
name=f"ZZ Dummy Taxonomy {i:03}",
allow_free_text=True,
allow_multiple=True
)
ObjectTag.objects.create(
object_id="limit_tag_count",
taxonomy=taxonomy,
_name=taxonomy.name,
_value="Dummy Tag",
)
self.dummy_taxonomies.append(taxonomy)

def setup_tag_depths(self):
"""
Annotate our tags with depth so we can compare them.
Expand Down
Loading