diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index bbab0242..1276d025 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" diff --git a/openedx_tagging/core/tagging/api.py b/openedx_tagging/core/tagging/api.py index ab1fef91..0ccb4688 100644 --- a/openedx_tagging/core/tagging/api.py +++ b/openedx_tagging/core/tagging/api.py @@ -96,16 +96,14 @@ def resync_object_tags(object_tags: QuerySet = None) -> int: def get_object_tags( - object_id: str, taxonomy: Taxonomy = None, valid_only=True -) -> Iterator[ObjectTag]: + object_id: str, taxonomy_id: str = None +) -> QuerySet: """ - Generates a list of object tags for a given object. + Returns a Queryset of object tags for a given object. Pass taxonomy to limit the returned object_tags to a specific taxonomy. - - Pass valid_only=False when displaying tags to content authors, so they can see invalid tags too. - Invalid tags will (probably) be hidden from learners. """ + taxonomy = get_taxonomy(taxonomy_id) ObjectTagClass = taxonomy.object_tag_class if taxonomy else ObjectTag tags = ( ObjectTagClass.objects.filter( @@ -117,9 +115,7 @@ def get_object_tags( if taxonomy: tags = tags.filter(taxonomy=taxonomy) - for object_tag in tags: - if not valid_only or object_tag.is_valid(): - yield object_tag + return tags def delete_object_tags(object_id: str): diff --git a/openedx_tagging/core/tagging/rest_api/v1/permissions.py b/openedx_tagging/core/tagging/rest_api/v1/permissions.py index e8915a62..245fc3cb 100644 --- a/openedx_tagging/core/tagging/rest_api/v1/permissions.py +++ b/openedx_tagging/core/tagging/rest_api/v1/permissions.py @@ -1,5 +1,5 @@ """ -Taxonomy permissions +Tagging permissions """ from rest_framework.permissions import DjangoObjectPermissions @@ -15,3 +15,15 @@ class TaxonomyObjectPermissions(DjangoObjectPermissions): "PATCH": ["%(app_label)s.change_%(model_name)s"], "DELETE": ["%(app_label)s.delete_%(model_name)s"], } + + +class ObjectTagObjectPermissions(DjangoObjectPermissions): + perms_map = { + "GET": ["%(app_label)s.view_%(model_name)s"], + "OPTIONS": [], + "HEAD": ["%(app_label)s.view_%(model_name)s"], + "POST": ["%(app_label)s.add_%(model_name)s"], + "PUT": ["%(app_label)s.change_%(model_name)s"], + "PATCH": ["%(app_label)s.change_%(model_name)s"], + "DELETE": ["%(app_label)s.delete_%(model_name)s"], + } diff --git a/openedx_tagging/core/tagging/rest_api/v1/serializers.py b/openedx_tagging/core/tagging/rest_api/v1/serializers.py index 53f64742..593b7298 100644 --- a/openedx_tagging/core/tagging/rest_api/v1/serializers.py +++ b/openedx_tagging/core/tagging/rest_api/v1/serializers.py @@ -4,7 +4,7 @@ from rest_framework import serializers -from openedx_tagging.core.tagging.models import Taxonomy +from openedx_tagging.core.tagging.models import Taxonomy, ObjectTag class TaxonomyListQueryParamsSerializer(serializers.Serializer): @@ -29,3 +29,29 @@ class Meta: "system_defined", "visible_to_authors", ] + + +class ObjectTagListQueryParamsSerializer(serializers.Serializer): + """ + Serializer for the query params for the ObjectTag GET view + """ + + taxonomy = serializers.PrimaryKeyRelatedField( + queryset=Taxonomy.objects.all(), required=False + ) + + +class ObjectTagSerializer(serializers.ModelSerializer): + """ + Serializer for the ObjectTag model. + """ + + class Meta: + model = ObjectTag + fields = [ + "name", + "value", + "taxonomy_id", + "tag_ref", + "is_valid", + ] diff --git a/openedx_tagging/core/tagging/rest_api/v1/urls.py b/openedx_tagging/core/tagging/rest_api/v1/urls.py index 80500990..c449eb5c 100644 --- a/openedx_tagging/core/tagging/rest_api/v1/urls.py +++ b/openedx_tagging/core/tagging/rest_api/v1/urls.py @@ -10,5 +10,6 @@ router = DefaultRouter() router.register("taxonomies", views.TaxonomyView, basename="taxonomy") +router.register("object_tags", views.ObjectTagView, basename="object_tag") urlpatterns = [path("", include(router.urls))] diff --git a/openedx_tagging/core/tagging/rest_api/v1/views.py b/openedx_tagging/core/tagging/rest_api/v1/views.py index a86e3c27..13e00acd 100644 --- a/openedx_tagging/core/tagging/rest_api/v1/views.py +++ b/openedx_tagging/core/tagging/rest_api/v1/views.py @@ -2,15 +2,23 @@ Tagging API Views """ from django.http import Http404 -from rest_framework.viewsets import ModelViewSet +from rest_framework import status +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet +from rest_framework.response import Response from ...api import ( create_taxonomy, get_taxonomy, get_taxonomies, + get_object_tags, +) +from .permissions import TaxonomyObjectPermissions, ObjectTagObjectPermissions +from .serializers import ( + TaxonomyListQueryParamsSerializer, + TaxonomySerializer, + ObjectTagListQueryParamsSerializer, + ObjectTagSerializer, ) -from .permissions import TaxonomyObjectPermissions -from .serializers import TaxonomyListQueryParamsSerializer, TaxonomySerializer class TaxonomyView(ModelViewSet): @@ -145,3 +153,76 @@ def perform_create(self, serializer): Create a new taxonomy. """ serializer.instance = create_taxonomy(**serializer.validated_data) + + +class ObjectTagView(ReadOnlyModelViewSet): + """ + View to retrieve paginated ObjectTags for an Object, given its Object ID. + (What tags does this object have?) + + **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 + * 400 - Invalid query parameter + * 403 - Permission denied + + **Create Query Returns** + * 403 - Permission denied + * 405 - Method not allowed + + **Update Query Returns** + * 403 - Permission denied + * 405 - Method not allowed + + **Delete Query Returns** + * 403 - Permission denied + * 405 - Method not allowed + """ + + serializer_class = ObjectTagSerializer + permission_classes = [ObjectTagObjectPermissions] + lookup_field = "object_id" + + def get_queryset(self): + """ + Return a queryset of object tags for a given object. + + If a taxonomy is passed in, object tags are limited to that taxonomy. + """ + object_id = self.kwargs.get("object_id") + query_params = ObjectTagListQueryParamsSerializer( + data=self.request.query_params.dict() + ) + query_params.is_valid(raise_exception=True) + taxonomy_id = query_params.data.get("taxonomy", None) + return get_object_tags(object_id, taxonomy_id) + + def retrieve(self, request, object_id=None): + """ + Retrieve ObjectTags that belong to a given Object given its + object_id and return paginated results. + + 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 + it in as a query_param) to retrieve the related ObjectTags. + By default retrieve would expect an ObjectTag ID to be passed in the + path and returns a it as a single result however that is not + 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) diff --git a/openedx_tagging/core/tagging/rules.py b/openedx_tagging/core/tagging/rules.py index 364178c7..b79a5634 100644 --- a/openedx_tagging/core/tagging/rules.py +++ b/openedx_tagging/core/tagging/rules.py @@ -73,7 +73,7 @@ def can_change_object_tag(user: User, object_tag: ObjectTag = None) -> bool: rules.add_perm("oel_tagging.view_tag", rules.always_allow) # ObjectTag -rules.add_perm("oel_tagging.add_object_tag", can_change_object_tag) -rules.add_perm("oel_tagging.change_object_tag", can_change_object_tag) -rules.add_perm("oel_tagging.delete_object_tag", is_taxonomy_admin) -rules.add_perm("oel_tagging.view_object_tag", rules.always_allow) +rules.add_perm("oel_tagging.add_objecttag", can_change_object_tag) +rules.add_perm("oel_tagging.change_objecttag", can_change_object_tag) +rules.add_perm("oel_tagging.delete_objecttag", is_taxonomy_admin) +rules.add_perm("oel_tagging.view_objecttag", rules.always_allow) diff --git a/tests/openedx_tagging/core/tagging/test_api.py b/tests/openedx_tagging/core/tagging/test_api.py index 42639219..ae77d715 100644 --- a/tests/openedx_tagging/core/tagging/test_api.py +++ b/tests/openedx_tagging/core/tagging/test_api.py @@ -259,7 +259,7 @@ def test_tag_object(self): assert ( list( tagging_api.get_object_tags( - taxonomy=self.taxonomy, + taxonomy_id=self.taxonomy.pk, object_id="biology101", ) ) @@ -355,7 +355,7 @@ def test_tag_object_language_taxonomy(self): assert ( list( tagging_api.get_object_tags( - taxonomy=self.language_taxonomy, + taxonomy_id=self.language_taxonomy.pk, object_id="biology101", ) ) @@ -401,7 +401,7 @@ def test_tag_object_model_system_taxonomy(self): assert ( list( tagging_api.get_object_tags( - taxonomy=self.user_taxonomy, + taxonomy_id=self.user_taxonomy.pk, object_id="biology101", ) ) @@ -445,37 +445,17 @@ def test_get_object_tags(self): assert list( tagging_api.get_object_tags( object_id="abc", - valid_only=False, ) ) == [ alpha, beta, ] - # No valid tags for this object yet.. - assert not list( - tagging_api.get_object_tags( - object_id="abc", - valid_only=True, - ) - ) - beta.tag = self.mammalia - beta.save() - assert list( - tagging_api.get_object_tags( - object_id="abc", - valid_only=True, - ) - ) == [ - beta, - ] - # Fetch all the tags for a given object ID + taxonomy assert list( tagging_api.get_object_tags( object_id="abc", - taxonomy=self.taxonomy, - valid_only=False, + taxonomy_id=self.taxonomy.pk, ) ) == [ beta, diff --git a/tests/openedx_tagging/core/tagging/test_rules.py b/tests/openedx_tagging/core/tagging/test_rules.py index 415ae193..cc4f5040 100644 --- a/tests/openedx_tagging/core/tagging/test_rules.py +++ b/tests/openedx_tagging/core/tagging/test_rules.py @@ -161,8 +161,8 @@ def test_view_tag(self): # ObjectTag @ddt.data( - "oel_tagging.add_object_tag", - "oel_tagging.change_object_tag", + "oel_tagging.add_objecttag", + "oel_tagging.change_objecttag", ) def test_add_change_object_tag(self, perm): """Taxonomy administrators can create/edit an ObjectTag with an enabled Taxonomy""" @@ -174,8 +174,8 @@ def test_add_change_object_tag(self, perm): assert not self.learner.has_perm(perm, self.object_tag) @ddt.data( - "oel_tagging.add_object_tag", - "oel_tagging.change_object_tag", + "oel_tagging.add_objecttag", + "oel_tagging.change_objecttag", ) def test_object_tag_disabled_taxonomy(self, perm): """Taxonomy administrators cannot create/edit an ObjectTag with a disabled Taxonomy""" @@ -189,23 +189,23 @@ def test_object_tag_disabled_taxonomy(self, perm): True, False, ) - def test_delete_object_tag(self, enabled): + def test_delete_objecttag(self, enabled): """Taxonomy administrators can delete any ObjectTag, even those associated with a disabled Taxonomy.""" self.taxonomy.enabled = enabled self.taxonomy.save() - assert self.superuser.has_perm("oel_tagging.delete_object_tag") - assert self.superuser.has_perm("oel_tagging.delete_object_tag", self.object_tag) - assert self.staff.has_perm("oel_tagging.delete_object_tag") - assert self.staff.has_perm("oel_tagging.delete_object_tag", self.object_tag) - assert not self.learner.has_perm("oel_tagging.delete_object_tag") + assert self.superuser.has_perm("oel_tagging.delete_objecttag") + assert self.superuser.has_perm("oel_tagging.delete_objecttag", self.object_tag) + assert self.staff.has_perm("oel_tagging.delete_objecttag") + assert self.staff.has_perm("oel_tagging.delete_objecttag", self.object_tag) + assert not self.learner.has_perm("oel_tagging.delete_objecttag") assert not self.learner.has_perm( - "oel_tagging.delete_object_tag", self.object_tag + "oel_tagging.delete_objecttag", self.object_tag ) @ddt.data( - "oel_tagging.add_object_tag", - "oel_tagging.change_object_tag", - "oel_tagging.delete_object_tag", + "oel_tagging.add_objecttag", + "oel_tagging.change_objecttag", + "oel_tagging.delete_objecttag", ) def test_object_tag_no_taxonomy(self, perm): """Taxonomy administrators can modify an ObjectTag with no Taxonomy""" @@ -216,9 +216,9 @@ def test_object_tag_no_taxonomy(self, perm): def test_view_object_tag(self): """Anyone can view any ObjectTag""" - assert self.superuser.has_perm("oel_tagging.view_object_tag") - assert self.superuser.has_perm("oel_tagging.view_object_tag", self.object_tag) - assert self.staff.has_perm("oel_tagging.view_object_tag") - assert self.staff.has_perm("oel_tagging.view_object_tag", self.object_tag) - assert self.learner.has_perm("oel_tagging.view_object_tag") - assert self.learner.has_perm("oel_tagging.view_object_tag", self.object_tag) + assert self.superuser.has_perm("oel_tagging.view_objecttag") + assert self.superuser.has_perm("oel_tagging.view_objecttag", self.object_tag) + assert self.staff.has_perm("oel_tagging.view_objecttag") + assert self.staff.has_perm("oel_tagging.view_objecttag", self.object_tag) + assert self.learner.has_perm("oel_tagging.view_objecttag") + assert self.learner.has_perm("oel_tagging.view_objecttag", self.object_tag) diff --git a/tests/openedx_tagging/core/tagging/test_views.py b/tests/openedx_tagging/core/tagging/test_views.py index 69805e23..a0b946cb 100644 --- a/tests/openedx_tagging/core/tagging/test_views.py +++ b/tests/openedx_tagging/core/tagging/test_views.py @@ -8,7 +8,7 @@ from rest_framework.test import APITestCase from urllib.parse import urlparse, parse_qs -from openedx_tagging.core.tagging.models import Taxonomy +from openedx_tagging.core.tagging.models import Taxonomy, ObjectTag, Tag from openedx_tagging.core.tagging.models.system_defined import SystemDefinedTaxonomy User = get_user_model() @@ -17,6 +17,9 @@ TAXONOMY_DETAIL_URL = "/tagging/rest_api/v1/taxonomies/{pk}/" +OBJECT_TAGS_RETRIEVE_URL = '/tagging/rest_api/v1/object_tags/{object_id}/' + + def check_taxonomy( data, id, @@ -376,3 +379,271 @@ def test_delete_taxonomy_404(self): self.client.force_authenticate(user=self.staff) response = self.client.delete(url) assert response.status_code, status.HTTP_404_NOT_FOUND + + +@ddt.ddt +class TestObjectTagViewSet(APITestCase): + """ + Testing various cases for the ObjectTagView. + """ + + def setUp(self): + super().setUp() + + self.user = User.objects.create( + username="user", + email="user@example.com", + ) + + self.staff = User.objects.create( + username="staff", + email="staff@example.com", + is_staff=True, + ) + + # System-defined language taxonomy with valid ObjectTag + self.system_taxonomy = SystemDefinedTaxonomy.objects.create( + name="System Taxonomy" + ) + self.tag1 = Tag.objects.create( + taxonomy=self.system_taxonomy, value="Tag 1" + ) + ObjectTag.objects.create( + object_id="abc", + taxonomy=self.system_taxonomy, + tag=self.tag1 + ) + + # Closed Taxonomies created by taxonomy admins, each with 20 ObjectTags + self.enabled_taxonomy = Taxonomy.objects.create(name="Enabled Taxonomy") + for i in range(20): + # Valid ObjectTags + tag = Tag.objects.create( + taxonomy=self.enabled_taxonomy, value=f"Tag {i}" + ) + ObjectTag.objects.create( + object_id="abc", taxonomy=self.enabled_taxonomy, + tag=tag, _value=tag.value + ) + + # Taxonomy with invalid ObjectTag + self.taxonomy_with_invalid_object_tag = Taxonomy.objects.create() + self.to_be_deleted_tag = Tag.objects.create( + taxonomy=self.enabled_taxonomy, value="Deleted Tag" + ) + ObjectTag.objects.create( + object_id="abc", taxonomy=self.taxonomy_with_invalid_object_tag, + tag=self.to_be_deleted_tag, _value=self.to_be_deleted_tag.value + ) + self.to_be_deleted_tag.delete() # Delete tag so ObjectTag is invalid + + # Free-Text Taxonomies created by taxonomy admins, each linked + # to 200 ObjectTags + self.open_taxonomy_enabled = Taxonomy.objects.create( + name="Enabled Free-Text Taxonomy", allow_free_text=True + ) + self.open_taxonomy_disabled = Taxonomy.objects.create( + name="Disabled Free-Text Taxonomy", enabled=False, allow_free_text=True + ) + for i in range(200): + ObjectTag.objects.create( + object_id="abc", taxonomy=self.open_taxonomy_enabled, _value=f"Free Text {i}" + ) + ObjectTag.objects.create( + object_id="abc", taxonomy=self.open_taxonomy_disabled, _value=f"Free Text {i}" + ) + + @ddt.data( + (None, "abc", status.HTTP_403_FORBIDDEN, None, None), + ("user", "abc", status.HTTP_200_OK, 422, 10), + ("staff", "abc", status.HTTP_200_OK, 422, 10), + (None, "non-existing-id", status.HTTP_403_FORBIDDEN, None, None), + ("user", "non-existing-id", status.HTTP_200_OK, 0, 0), + ("staff", "non-existing-id", status.HTTP_200_OK, 0, 0), + ) + @ddt.unpack + def test_retrieve_object_tags( + self, user_attr, object_id, expected_status, expected_count, expected_results + + ): + """ + Test retrieving object tags + """ + url = OBJECT_TAGS_RETRIEVE_URL.format(object_id=object_id) + + if user_attr: + user = getattr(self, user_attr) + self.client.force_authenticate(user=user) + + response = self.client.get(url) + assert response.status_code == expected_status + + if status.is_success(expected_status): + assert response.data.get("count") == expected_count + assert response.data.get("results") is not None + assert len(response.data.get("results")) == expected_results + + @ddt.data( + (None, "abc", status.HTTP_403_FORBIDDEN, None, None, None, None), + ("user", "abc", status.HTTP_200_OK, 20, 10, 1, 1), + ("staff", "abc", status.HTTP_200_OK, 20, 10, 1, 1), + ) + @ddt.unpack + def test_retrieve_object_tags_taxonomy_queryparam( + self, user_attr, object_id, expected_status, + expected_count, expected_results, + expected_invalid_count, expected_invalid_results + ): + """ + Test retrieving object tags for specific taxonomies provided + """ + url = OBJECT_TAGS_RETRIEVE_URL.format(object_id=object_id) + + if user_attr: + user = getattr(self, user_attr) + self.client.force_authenticate(user=user) + + # Check valid object tags + response = self.client.get(url, {"taxonomy": self.enabled_taxonomy.pk}) + assert response.status_code == expected_status + if status.is_success(expected_status): + assert response.data.get("count") == expected_count + assert response.data.get("results") is not None + assert len(response.data.get("results")) == expected_results + object_tags = response.data.get("results") + for object_tag in object_tags: + assert object_tag.get("is_valid") is True + assert object_tag.get("taxonomy_id") == self.enabled_taxonomy.pk + + # Check invalid object tags + response = self.client.get( + url, {"taxonomy": self.taxonomy_with_invalid_object_tag.pk} + ) + assert response.status_code == expected_status + if status.is_success(expected_status): + assert response.data.get("count") == expected_invalid_count + assert response.data.get("results") is not None + assert len(response.data.get("results")) == expected_invalid_results + object_tags = response.data.get("results") + for object_tag in object_tags: + assert object_tag.get("is_valid") is False + assert object_tag.get("taxonomy_id") == \ + self.taxonomy_with_invalid_object_tag.pk + + @ddt.data( + (None, "abc", status.HTTP_403_FORBIDDEN), + ("user", "abc", status.HTTP_400_BAD_REQUEST), + ("staff", "abc", status.HTTP_400_BAD_REQUEST), + ) + @ddt.unpack + def test_retrieve_object_tags_invalid_taxonomy_queryparam( + self, user_attr, object_id, expected_status + ): + """ + Test retrieving object tags for invalid taxonomy + """ + url = OBJECT_TAGS_RETRIEVE_URL.format(object_id=object_id) + + if user_attr: + user = getattr(self, user_attr) + self.client.force_authenticate(user=user) + + # Invalid Taxonomy + response = self.client.get(url, {"taxonomy": 123123}) + assert response.status_code == expected_status + + @ddt.data( + # Page 1, default page size 10, total count 200, returns 10 results + (None, 1, None, status.HTTP_403_FORBIDDEN, None, None), + ("user", 1, None, status.HTTP_200_OK, 200, 10), + ("staff", 1, None, status.HTTP_200_OK, 200, 10), + # Page 2, default page size 10, total count 200, returns 10 results + (None, 2, None, status.HTTP_403_FORBIDDEN, None, None), + ("user", 2, None, status.HTTP_200_OK, 200, 10), + ("staff", 2, None, status.HTTP_200_OK, 200, 10), + # Page 21, default page size 10, total count 200, no more results + (None, 21, None, status.HTTP_403_FORBIDDEN, None, None), + ("user", 21, None, status.HTTP_404_NOT_FOUND, None, None), + ("staff", 21, None, status.HTTP_404_NOT_FOUND, None, None), + # Page 3, page size 2, total count 200, returns 2 results + (None, 3, 2, status.HTTP_403_FORBIDDEN, 200, 2), + ("user", 3, 2, status.HTTP_200_OK, 200, 2), + ("staff", 3, 2, status.HTTP_200_OK, 200, 2), + ) + @ddt.unpack + def test_retrieve_object_tags_pagination( + self, user_attr, page, page_size, expected_status, expected_count, expected_results + ): + """ + Test pagination for retrieve object tags + """ + url = OBJECT_TAGS_RETRIEVE_URL.format(object_id="abc") + + if user_attr: + user = getattr(self, user_attr) + self.client.force_authenticate(user=user) + + query_params = { + "taxonomy": self.open_taxonomy_enabled.pk, + "page": page + } + if page_size: + query_params["page_size"] = page_size + + response = self.client.get(url, query_params) + assert response.status_code == expected_status + if status.is_success(expected_status): + assert response.data.get("count") == expected_count + assert response.data.get("results") is not None + assert len(response.data.get("results")) == expected_results + object_tags = response.data.get("results") + for object_tag in object_tags: + assert object_tag.get("taxonomy_id") == self.open_taxonomy_enabled.pk + + @ddt.data( + (None, "POST", status.HTTP_403_FORBIDDEN), + (None, "PUT", status.HTTP_403_FORBIDDEN), + (None, "PATCH", status.HTTP_403_FORBIDDEN), + (None, "DELETE", status.HTTP_403_FORBIDDEN), + ("user", "POST", status.HTTP_403_FORBIDDEN), + ("user", "PUT", status.HTTP_403_FORBIDDEN), + ("user", "PATCH", status.HTTP_403_FORBIDDEN), + ("user", "DELETE", status.HTTP_403_FORBIDDEN), + ("staff", "POST", status.HTTP_405_METHOD_NOT_ALLOWED), + ("staff", "PUT", status.HTTP_405_METHOD_NOT_ALLOWED), + ("staff", "PATCH", status.HTTP_405_METHOD_NOT_ALLOWED), + ("staff", "DELETE", status.HTTP_405_METHOD_NOT_ALLOWED), + ) + @ddt.unpack + def test_object_tags_remaining_http_methods( + self, user_attr, http_method, expected_status, + + ): + """ + Test POST/PUT/PATCH/DELETE method for ObjectTagView + + Only staff users should have permissions to perform the actions, + however the methods are currently not allowed. + """ + url = OBJECT_TAGS_RETRIEVE_URL.format(object_id="abc") + + if user_attr: + user = getattr(self, user_attr) + self.client.force_authenticate(user=user) + + if http_method == "POST": + response = self.client.post( + url, {"test": "payload"}, format="json" + ) + elif http_method == "PUT": + response = self.client.put( + url, {"test": "payload"}, format="json" + ) + elif http_method == "PATCH": + response = self.client.patch( + url, {"test": "payload"}, format="json" + ) + elif http_method == "DELETE": + response = self.client.delete(url) + + assert response.status_code == expected_status