Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce limit on number of tags per object #81

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
Loading