Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/openedx_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
"""

# The version for the entire repository
__version__ = "0.39.2"
__version__ = "0.40.0"
11 changes: 9 additions & 2 deletions src/openedx_tagging/rest_api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,21 @@ class ObjectTagsByTaxonomySerializer(UserPermissionsSerializerMixin, serializers
class Meta:
model = ObjectTag

def get_can_tag_object(self, obj_tag) -> bool | None:
"""
Returns True if the current request user may tag objects with this taxonomy.
Override to customize permission logic.
"""
perm_name = f"{self.app_label}.can_tag_object"
return self._can(perm_name, obj_tag)

def to_representation(self, instance: list[ObjectTag]) -> dict:
"""
Convert this list of ObjectTags to the serialized dictionary, grouped by Taxonomy
"""
# Allows consumers like edx-platform to override this
ObjectTagViewMinimalSerializer = self.context["view"].minimal_serializer_class

can_tag_object_perm = f"{self.app_label}.can_tag_object"
by_object: dict[str, dict[str, Any]] = {}
for obj_tag in instance:
if obj_tag.object_id not in by_object:
Expand All @@ -192,7 +199,7 @@ def to_representation(self, instance: list[ObjectTag]) -> dict:
tax_entry = {
"name": obj_tag.taxonomy.name if obj_tag.taxonomy else None,
"taxonomy_id": obj_tag.taxonomy_id,
"can_tag_object": self._can(can_tag_object_perm, obj_tag),
"can_tag_object": self.get_can_tag_object(obj_tag),
"tags": [],
"export_id": obj_tag.export_id,
}
Expand Down
60 changes: 36 additions & 24 deletions src/openedx_tagging/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,43 @@ class ObjectTagView(
serializer_class = ObjectTagSerializer
# Serializer used in the result in `to_representation` in `ObjectTagsByTaxonomySerializer`
minimal_serializer_class = ObjectTagMinimalSerializer
# Serializer used in `retrieve` to group object tags by taxonomy
taxonomy_serializer_class = ObjectTagsByTaxonomySerializer
permission_classes = [ObjectTagObjectPermissions]
lookup_field = "object_id"
lookup_value_regex = r'[\w\.\+\-@:]+'

def check_view_object_tags_permission(self, object_id: str, taxonomy=None) -> None:
"""
Check if the current user can view object tags for the given object.
Raises PermissionDenied if not. Override to customize permission logic.
"""
perm_obj = ObjectTagPermissionItem(taxonomy=taxonomy, object_id=object_id)
if not self.request.user.has_perm(
"oel_tagging.view_objecttag",
# The obj arg expects a model, but we are passing an object
perm_obj, # type: ignore[arg-type]
Comment thread
wgu-taylor-payne marked this conversation as resolved.
):
raise PermissionDenied(
"You do not have permission to view object tags for this taxonomy or object_id."
)

def check_can_tag_object_permission(self, object_id: str, taxonomy) -> None:
"""
Check if the current user can tag the given object with the given taxonomy.
Raises PermissionDenied if not. Override to customize permission logic.
"""
perm_obj = ObjectTagPermissionItem(taxonomy=taxonomy, object_id=object_id)
if not self.request.user.has_perm(
"oel_tagging.can_tag_object",
# The obj arg expects a model, but we are passing an object
perm_obj, # type: ignore[arg-type]
Comment thread
wgu-taylor-payne marked this conversation as resolved.
):
raise PermissionDenied(f"""
You do not have permission to change object tags
for Taxonomy: {str(taxonomy)} or Object: {object_id}.
""")

def get_queryset(self) -> models.QuerySet:
"""
Return a queryset of object tags for a given object.
Expand All @@ -477,14 +510,7 @@ def get_queryset(self) -> models.QuerySet:
# objects, e.g. if object_id.endswith("*") then it results in a object_id__startswith query. However, for
# now we have no use case for that so we retrieve tags for one object at a time.
else:
if not self.request.user.has_perm(
"oel_tagging.view_objecttag",
# The obj arg expects a model, but we are passing an object
ObjectTagPermissionItem(taxonomy=taxonomy, object_id=object_id), # type: ignore[arg-type]
):
raise PermissionDenied(
"You do not have permission to view object tags for this taxonomy or object_id."
)
self.check_view_object_tags_permission(object_id, taxonomy)

return get_object_tags(object_id, taxonomy_id)

Expand All @@ -500,7 +526,7 @@ def retrieve(self, request, *args, **kwargs) -> Response:
behavior we want.
"""
object_tags = self.filter_queryset(self.get_queryset())
serializer = ObjectTagsByTaxonomySerializer(list(object_tags), context=self.get_serializer_context())
serializer = self.taxonomy_serializer_class(list(object_tags), context=self.get_serializer_context())
response_data = serializer.data
if self.kwargs["object_id"] not in response_data:
# For consistency, the key with the object_id should always be present in the response, even if there
Expand Down Expand Up @@ -556,7 +582,6 @@ def update(self, request, *args, **kwargs) -> Response:
raise MethodNotAllowed("PATCH", detail="PATCH not allowed")

object_id = kwargs.pop('object_id')
perm = "oel_tagging.can_tag_object"
body = ObjectTagUpdateBodySerializer(data=request.data)
body.is_valid(raise_exception=True)

Expand All @@ -568,20 +593,7 @@ def update(self, request, *args, **kwargs) -> Response:
# Check permissions
for tagsData in data:
taxonomy = tagsData.get("taxonomy")

perm_obj = ObjectTagPermissionItem(
taxonomy=taxonomy,
object_id=object_id,
)
if not request.user.has_perm(
perm,
# The obj arg expects a model, but we are passing an object
perm_obj, # type: ignore[arg-type]
):
raise PermissionDenied(f"""
You do not have permission to change object tags
for Taxonomy: {str(taxonomy)} or Object: {object_id}.
""")
self.check_can_tag_object_permission(object_id, taxonomy)

# Tag object_id per taxonomy
for tagsData in data:
Expand Down