Skip to content

Commit

Permalink
Add REST API to list report and download pdf report (#3689)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Klopper <[email protected]>
Co-authored-by: stephanie0x00 <[email protected]>
  • Loading branch information
3 people authored Oct 28, 2024
1 parent 2442f45 commit a2f8f00
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 0 deletions.
64 changes: 64 additions & 0 deletions rocky/account/mixins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime, timezone
from functools import cached_property

import structlog.contextvars
from django.conf import settings
Expand All @@ -8,6 +9,8 @@
from django.http import Http404
from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.exceptions import ValidationError
from rest_framework.request import Request
from tools.models import Indemnification, Organization, OrganizationMember

from octopoes.connector.octopoes import OctopoesAPIConnector
Expand Down Expand Up @@ -203,3 +206,64 @@ class OrganizationPermissionRequiredMixin(PermissionRequiredMixin):
def has_permission(self) -> bool:
perms = self.get_permission_required()
return self.organization_member.has_perms(perms)


class OrganizationAPIMixin:
request: Request

def get_organization(self, field: str, value: str) -> Organization:
lookup_param = {field: value}
try:
organization = Organization.objects.get(**lookup_param)
except Organization.DoesNotExist as e:
raise Http404(f"Organization with {field} {value} does not exist") from e

if self.request.user.has_perm("tools.can_access_all_organizations"):
return organization

try:
organization_member = OrganizationMember.objects.get(user=self.request.user, organization=organization)
except OrganizationMember.DoesNotExist as e:
raise Http404(f"Organization with {field} {value} does not exist") from e

if organization_member.blocked:
raise PermissionDenied()

return organization

@cached_property
def organization(self) -> Organization:
try:
organization_id = self.request.query_params["organization_id"]
except KeyError:
pass
else:
return self.get_organization("id", organization_id)

try:
organization_code = self.request.query_params["organization_code"]
except KeyError as e:
raise ValidationError("Missing organization_id or organization_code query parameter") from e
else:
return self.get_organization("code", organization_code)

@cached_property
def octopoes_api_connector(self) -> OctopoesAPIConnector:
return OctopoesAPIConnector(settings.OCTOPOES_API, self.organization.code)

@cached_property
def valid_time(self) -> datetime:
try:
valid_time = self.request.query_params["valid_time"]
except KeyError:
return datetime.now(timezone.utc)
else:
try:
ret = datetime.fromisoformat(valid_time)
except ValueError:
raise ValidationError(f"Wrong format for valid_time: {valid_time}")

if not ret.tzinfo:
ret = ret.replace(tzinfo=timezone.utc)

return ret
19 changes: 19 additions & 0 deletions rocky/reports/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from rest_framework import serializers

from rocky.views.mixins import HydratedReport


class ReportSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
if isinstance(instance, HydratedReport):
report = instance.parent_report
else:
report = instance
return {
"id": report.report_id,
"valid_time": report.observed_at,
"name": report.name,
"report_type": report.report_type,
"generated_at": report.date_generated,
"intput_oois": report.input_oois,
}
43 changes: 43 additions & 0 deletions rocky/reports/viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from account.mixins import OrganizationAPIMixin
from django.http import HttpResponseRedirect
from django.urls import reverse
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from structlog import get_logger
from tools.view_helpers import url_with_querystring

from octopoes.models import Reference
from reports.serializers import ReportSerializer
from rocky.views.mixins import ReportList

logger = get_logger(__name__)


class ReportViewSet(OrganizationAPIMixin, viewsets.ModelViewSet):
# There are no extra permissions needed to view reports, so IsAuthenticated
# is enough for list/retrieve. OrganizationAPIMixin will check if the user
# is a member of the requested organization.
permission_classes = [IsAuthenticated]
serializer_class = ReportSerializer

def get_queryset(self):
return ReportList(self.octopoes_api_connector, self.valid_time)

def get_object(self):
pk = self.kwargs["pk"]

return self.octopoes_api_connector.get(Reference.from_str(f"Report|{pk}"), valid_time=self.valid_time)

@action(detail=True)
def pdf(self, request, pk):
report_ooi_id = f"Report|{pk}"

url = url_with_querystring(
reverse("view_report_pdf", kwargs={"organization_code": self.organization.code}),
True,
report_id=report_ooi_id,
observed_at=self.valid_time.isoformat(),
)

return HttpResponseRedirect(redirect_to=url)
2 changes: 2 additions & 0 deletions rocky/rocky/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ def immutable_file_test(path, url):
"DEFAULT_AUTHENTICATION_CLASSES": DEFAULT_AUTHENTICATION_CLASSES,
"DEFAULT_PERMISSION_CLASSES": ["rocky.permissions.KATModelPermissions"],
"DEFAULT_RENDERER_CLASSES": DEFAULT_RENDERER_CLASSES,
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 100,
"EXCEPTION_HANDLER": "drf_standardized_errors.handler.exception_handler",
}

Expand Down
2 changes: 2 additions & 0 deletions rocky/rocky/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.contrib import admin
from django.urls import include, path
from django.views.generic.base import TemplateView
from reports.viewsets import ReportViewSet
from rest_framework import routers
from tools.viewsets import OrganizationViewSet
from two_factor.urls import urlpatterns as tf_urls
Expand Down Expand Up @@ -54,6 +55,7 @@

router = routers.SimpleRouter()
router.register(r"organization", OrganizationViewSet)
router.register(r"report", ReportViewSet, basename="report")

urlpatterns = [
path("i18n/", include("django.conf.urls.i18n")),
Expand Down
5 changes: 5 additions & 0 deletions rocky/tools/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
class OrganizationViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all()
serializer_class = OrganizationSerializer
# When we created this viewset we didn't have pagination enabled in the
# django-rest-framework settings. Enabling it afterwards would cause the API
# to change in an incompatible way, we should enable this when we introduce
# a new API version.
pagination_class = None

# Unfortunately django-rest-framework doesn't have support for create only
# fields so we have to change the serializer class depending on the request
Expand Down

0 comments on commit a2f8f00

Please sign in to comment.