Skip to content

Commit

Permalink
Merge branch 'main' into rpenido/fal-3518-permissions-for-taxonomies
Browse files Browse the repository at this point in the history
  • Loading branch information
rpenido authored Oct 13, 2023
2 parents 677260d + ca6bc85 commit 4f1cef5
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 36 deletions.
18 changes: 18 additions & 0 deletions openedx_tagging/core/tagging/rest_api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@ class TaxonomyListQueryParamsSerializer(serializers.Serializer): # pylint: disa
enabled = serializers.BooleanField(required=False)


class TaxonomyExportQueryParamsSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Serializer for the query params for the GET view
"""
download = serializers.BooleanField(required=False, default=False)
output_format = serializers.RegexField(r"(?i)^(json|csv)$", allow_blank=False)


class TaxonomySerializer(serializers.ModelSerializer):
"""
Serializer for the Taxonomy model.
"""
class Meta:
model = Taxonomy
fields = [
Expand All @@ -30,6 +41,13 @@ class Meta:
"visible_to_authors",
]

def to_representation(self, instance):
"""
Cast the taxonomy before serialize
"""
instance = instance.cast()
return super().to_representation(instance)


class ObjectTagListQueryParamsSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Expand Down
24 changes: 24 additions & 0 deletions openedx_tagging/core/tagging/rest_api/v1/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
Utilities for the API
"""
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication # type: ignore[import]
from edx_rest_framework_extensions.auth.session.authentication import ( # type: ignore[import]
SessionAuthenticationAllowInactiveUser,
)


def view_auth_classes(func_or_class):
"""
Function and class decorator that abstracts the authentication classes for api views.
"""
def _decorator(func_or_class):
"""
Requires either OAuth2 or Session-based authentication;
are the same authentication classes used on edx-platform
"""
func_or_class.authentication_classes = (
JwtAuthentication,
SessionAuthenticationAllowInactiveUser,
)
return func_or_class
return _decorator(func_or_class)
62 changes: 60 additions & 2 deletions openedx_tagging/core/tagging/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from __future__ import annotations

from django.db import models
from django.http import Http404
from django.http import Http404, HttpResponse
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied, ValidationError
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
Expand All @@ -23,6 +24,8 @@
search_tags,
tag_object,
)
from ...import_export.api import export_tags
from ...import_export.parsers import ParserFormat
from ...models import Taxonomy
from ...rules import ObjectTagPermissionItem
from ..paginators import SEARCH_TAGS_THRESHOLD, TAGS_THRESHOLD, DisabledTagsPagination, TagsPagination
Expand All @@ -35,14 +38,17 @@
TagsForSearchSerializer,
TagsSerializer,
TagsWithSubTagsSerializer,
TaxonomyExportQueryParamsSerializer,
TaxonomyListQueryParamsSerializer,
TaxonomySerializer,
)
from .utils import view_auth_classes


@view_auth_classes
class TaxonomyView(ModelViewSet):
"""
View to list, create, retrieve, update, or delete Taxonomies.
View to list, create, retrieve, update, delete or export Taxonomies.
**List Query Parameters**
* enabled (optional) - Filter by enabled status. Valid values: true,
Expand Down Expand Up @@ -141,6 +147,23 @@ class TaxonomyView(ModelViewSet):
* 404 - Taxonomy not found
* 403 - Permission denied
**Export Query Parameters**
* output_format - Define the output format. Valid values: json, csv
* download (optional) - Add headers on the response to let the browser
automatically download the file.
**Export Example Requests**
GET api/tagging/v1/taxonomy/:pk/export?output_format=csv - Export taxonomy as CSV
GET api/tagging/v1/taxonomy/:pk/export?output_format=json - Export taxonomy as JSON
GET api/tagging/v1/taxonomy/:pk/export?output_format=csv&download=1 - Export and downloads taxonomy as CSV
GET api/tagging/v1/taxonomy/:pk/export?output_format=json&download=1 - Export and downloads taxonomy as JSON
**Export Query Returns**
* 200 - Success
* 400 - Invalid query parameter
* 403 - Permission denied
"""

serializer_class = TaxonomySerializer
Expand Down Expand Up @@ -181,7 +204,41 @@ def perform_create(self, serializer) -> None:
"""
serializer.instance = create_taxonomy(**serializer.validated_data)

@action(detail=True, methods=["get"])
def export(self, request, **_kwargs) -> HttpResponse:
"""
Export a taxonomy.
"""
taxonomy = self.get_object()
perm = "oel_tagging.export_taxonomy"
if not request.user.has_perm(perm, taxonomy):
raise PermissionDenied("You do not have permission to export this taxonomy.")
query_params = TaxonomyExportQueryParamsSerializer(
data=request.query_params.dict()
)
query_params.is_valid(raise_exception=True)
output_format = query_params.data.get("output_format")
assert output_format is not None
if output_format.lower() == "json":
parser_format = ParserFormat.JSON
content_type = "application/json"
else:
parser_format = ParserFormat.CSV
if query_params.data.get("download"):
content_type = "text/csv"
else:
content_type = "text"

tags = export_tags(taxonomy, parser_format)
if query_params.data.get("download"):
response = HttpResponse(tags.encode('utf-8'), content_type=content_type)
response["Content-Disposition"] = f'attachment; filename="{taxonomy.name}{parser_format.value}"'
return response

return HttpResponse(tags, content_type=content_type)


@view_auth_classes
class ObjectTagView(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
Expand Down Expand Up @@ -346,6 +403,7 @@ def update(self, request, *args, **kwargs) -> Response:
return self.retrieve(request, object_id)


@view_auth_classes
class TaxonomyTagsView(ListAPIView):
"""
View to list tags of a taxonomy.
Expand Down
1 change: 1 addition & 0 deletions openedx_tagging/core/tagging/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def can_change_object_tag(
rules.add_perm("oel_tagging.change_taxonomy", can_change_taxonomy)
rules.add_perm("oel_tagging.delete_taxonomy", can_change_taxonomy)
rules.add_perm("oel_tagging.view_taxonomy", can_view_taxonomy)
rules.add_perm("oel_tagging.export_taxonomy", can_view_taxonomy)

# Tag
rules.add_perm("oel_tagging.add_tag", can_change_tag)
Expand Down
Loading

0 comments on commit 4f1cef5

Please sign in to comment.