Skip to content

Commit

Permalink
feat: adds user_permissions to the tagging REST API results
Browse files Browse the repository at this point in the history
These permissions reflect the current request.user's allowed actions
on the instances in the results list.
  • Loading branch information
pomegranited committed Jan 7, 2024
1 parent 05ea800 commit dd0d36a
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 21 deletions.
88 changes: 86 additions & 2 deletions openedx_tagging/core/tagging/rest_api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,71 @@ class TaxonomyExportQueryParamsSerializer(serializers.Serializer): # pylint: di
output_format = serializers.RegexField(r"(?i)^(json|csv)$", allow_blank=False)


class UserPermissionsSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Serializer for the permissions on tagging instances granted to the current user.
Requires the current request to be passed into the serializer context, or all permissions will return False.
"""
can_add = serializers.SerializerMethodField()
can_view = serializers.SerializerMethodField()
can_change = serializers.SerializerMethodField()
can_delete = serializers.SerializerMethodField()

def _check_permission(self, action, instance) -> bool:
"""
Returns True if the current `request.user` may perform the given `action` on the `instance` object.
Uses the current request as passed into the serializer context.
"""
assert action in ("add", "change", "view", "delete")
request = self.context.get('request')
if not (request and request.user and instance):
return False

if isinstance(instance, Taxonomy):
model = 'taxonomy'
elif isinstance(instance, Tag):
model = 'tag'
elif isinstance(instance, ObjectTag):
model = 'objecttag'
else:
return False

permission = f"oel_tagging.{action}_{model}"
return request.user.has_perm(permission, instance)

def get_can_add(self, instance) -> bool:
"""
Returns True if the current user is allowed to create new instances.
"""
return self._check_permission('add', instance)

def get_can_change(self, instance) -> bool:
"""
Returns True if the current user is allowed to change this instance.
"""
return self._check_permission('change', instance)

def get_can_view(self, instance) -> bool:
"""
Returns True if the current user is allowed to view this instance.
"""
return self._check_permission('view', instance)

def get_can_delete(self, instance) -> bool:
"""
Returns True if the current user is allowed to delete this instance.
"""
return self._check_permission('delete', instance)


class TaxonomySerializer(serializers.ModelSerializer):
"""
Serializer for the Taxonomy model.
"""
tags_count = serializers.SerializerMethodField()
user_permissions = serializers.SerializerMethodField()

class Meta:
model = Taxonomy
Expand All @@ -48,6 +108,7 @@ class Meta:
"system_defined",
"visible_to_authors",
"tags_count",
"user_permissions",
]

def to_representation(self, instance):
Expand All @@ -60,6 +121,13 @@ def to_representation(self, instance):
def get_tags_count(self, instance):
return instance.tag_set.count()

def get_user_permissions(self, instance):
"""
Returns the serialized user permissions on the given instance.
"""
permissions = UserPermissionsSerializer(instance, context=self.context)
return permissions.to_representation(instance)


class ObjectTagListQueryParamsSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Expand All @@ -78,9 +146,17 @@ class ObjectTagMinimalSerializer(serializers.ModelSerializer):

class Meta:
model = ObjectTag
fields = ["value", "lineage"]
fields = ["value", "lineage", "user_permissions"]

lineage = serializers.ListField(child=serializers.CharField(), source="get_lineage", read_only=True)
user_permissions = serializers.SerializerMethodField()

def get_user_permissions(self, instance):
"""
Returns the serialized user permissions on the given instance.
"""
permissions = UserPermissionsSerializer(instance, context=self.context)
return permissions.to_representation(instance)


class ObjectTagSerializer(ObjectTagMinimalSerializer):
Expand Down Expand Up @@ -122,7 +198,7 @@ def to_representation(self, instance: list[ObjectTag]) -> dict:
"tags": []
}
taxonomies.append(tax_entry)
tax_entry["tags"].append(ObjectTagMinimalSerializer(obj_tag).data)
tax_entry["tags"].append(ObjectTagMinimalSerializer(obj_tag, context=self.context).data)
return by_object


Expand Down Expand Up @@ -161,6 +237,7 @@ class TagDataSerializer(serializers.Serializer): # pylint: disable=abstract-met
_id = serializers.IntegerField(allow_null=True)

sub_tags_url = serializers.SerializerMethodField()
user_permissions = serializers.SerializerMethodField()

def get_sub_tags_url(self, obj: TagData | Tag):
"""
Expand All @@ -184,6 +261,13 @@ def get_sub_tags_url(self, obj: TagData | Tag):
return request.build_absolute_uri(url)
return None

def get_user_permissions(self, instance):
"""
Returns the serialized user permissions on the given instance.
"""
permissions = UserPermissionsSerializer(instance, context=self.context)
return permissions.to_representation(instance)

def to_representation(self, instance: TagData | Tag) -> dict:
"""
Convert this TagData (or Tag model instance) to the serialized dictionary
Expand Down
2 changes: 1 addition & 1 deletion openedx_tagging/core/tagging/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ def retrieve(self, request, *args, **kwargs) -> Response:
behavior we want.
"""
object_tags = self.filter_queryset(self.get_queryset())
serializer = ObjectTagsByTaxonomySerializer(list(object_tags))
serializer = ObjectTagsByTaxonomySerializer(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
Loading

0 comments on commit dd0d36a

Please sign in to comment.