Skip to content

Commit

Permalink
Add endpoint to stream appeal documents
Browse files Browse the repository at this point in the history
  • Loading branch information
kevincarrogan committed Feb 7, 2024
1 parent 7063a06 commit 46cf759
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 18 deletions.
6 changes: 6 additions & 0 deletions api/appeals/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from rest_framework import filters


class AppealFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(appeal_id=view.kwargs["pk"])
89 changes: 89 additions & 0 deletions api/appeals/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from unittest import mock

from moto import mock_aws

from django.http import FileResponse
from django.urls import reverse
from django.utils.timezone import now

Expand Down Expand Up @@ -180,3 +183,89 @@ def test_get_document_different_organisation(self):
response.status_code,
status.HTTP_403_FORBIDDEN,
)


@mock_aws
class TestAppealDocumentStream(DataTestClient):
def setUp(self):
super().setUp()
self.appeal = AppealFactory()
application = self.create_standard_application_case(
organisation=self.exporter_user.organisation,
)
application.appeal = self.appeal
application.save()

self.create_default_bucket()
self.put_object_in_default_bucket("thisisakey", b"test")

def test_get_document_stream(self):
appeal_document = AppealDocumentFactory(
appeal=self.appeal,
s3_key="thisisakey",
safe=True,
)

url = reverse(
"appeals:document_stream",
kwargs={
"pk": str(self.appeal.pk),
"document_pk": str(appeal_document.pk),
},
)
response = self.client.get(url, **self.exporter_headers)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIsInstance(response, FileResponse)
self.assertEqual(b"".join(response.streaming_content), b"test")

def test_get_document_stream_invalid_appeal_pk(self):
appeal_document = AppealDocumentFactory(appeal=self.appeal)

url = reverse(
"appeals:document_stream",
kwargs={
"pk": "0f415f8a-e3e8-4c49-b053-ef03b1c477d5",
"document_pk": str(appeal_document.pk),
},
)
response = self.client.get(url, **self.exporter_headers)

self.assertEqual(
response.status_code,
status.HTTP_404_NOT_FOUND,
)

def test_get_document_stream_invalid_document_pk(self):
url = reverse(
"appeals:document_stream",
kwargs={
"pk": str(self.appeal.pk),
"document_pk": "0b551122-1ac2-4ea2-82b3-f1aaf0bf4923",
},
)
response = self.client.get(url, **self.exporter_headers)

self.assertEqual(
response.status_code,
status.HTTP_404_NOT_FOUND,
)

def test_get_document_stream_different_organisation(self):
self.appeal.baseapplication.organisation = self.create_organisation_with_exporter_user()[0]
self.appeal.baseapplication.save()
appeal_document = AppealDocumentFactory(appeal=self.appeal)

url = reverse(
"appeals:document_stream",
kwargs={
"pk": str(self.appeal.pk),
"document_pk": str(appeal_document.pk),
},
)
response = self.client.get(url, **self.exporter_headers)

self.assertEqual(
response.status_code,
status.HTTP_403_FORBIDDEN,
)
5 changes: 5 additions & 0 deletions api/appeals/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@
views.AppealDocumentAPIView.as_view(),
name="document",
),
path(
"<uuid:pk>/documents/<uuid:document_pk>/stream/",
views.AppealDocumentStreamAPIView.as_view(),
name="document_stream",
),
]
16 changes: 12 additions & 4 deletions api/appeals/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

from api.core.authentication import ExporterAuthentication
from api.core.permissions import IsExporterInOrganisation
from api.core.views import DocumentStreamAPIView

from .filters import AppealFilter
from .models import (
Appeal,
AppealDocument,
Expand Down Expand Up @@ -36,9 +38,15 @@ def get_serializer_context(self):


class AppealDocumentAPIView(BaseAppealDocumentAPIView, RetrieveAPIView):
filter_backends = (AppealFilter,)
lookup_url_kwarg = "document_pk"
queryset = AppealDocument.objects.all()

def get_queryset(self):
return AppealDocument.objects.filter(
appeal_id=self.kwargs["pk"],
)

class AppealDocumentStreamAPIView(BaseAppealDocumentAPIView, DocumentStreamAPIView):
filter_backends = (AppealFilter,)
lookup_url_kwarg = "document_pk"
queryset = AppealDocument.objects.all()

def get_document(self, instance):
return instance
17 changes: 17 additions & 0 deletions api/core/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from django.contrib import admin
from django.http import Http404
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic import View

from rest_framework.generics import RetrieveAPIView

from api.documents.libraries.s3_operations import document_download_stream


class LoginProviderView(View):
"""If user if not logged in then send them to staff sso, otherwise show them vanilla django admin login page"""
Expand All @@ -12,3 +17,15 @@ def dispatch(self, request):
return redirect(reverse("authbroker_client:login"))
# to show the "you're not an admin" message.
return admin.site.login(request)


class DocumentStreamAPIView(RetrieveAPIView):
def get_document(self, instance):
raise NotImplementedError()

def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
document = self.get_document(instance)
if not document.safe:
raise Http404()
return document_download_stream(document)
11 changes: 5 additions & 6 deletions api/documents/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from api.cases.generated_documents.signing import get_certificate_data
from api.core.authentication import SharedAuthentication
from api.core.exceptions import NotFoundError
from api.documents.libraries.s3_operations import document_download_stream
from api.core.views import DocumentStreamAPIView
from api.documents.models import Document
from api.documents.serializers import DocumentViewSerializer
from api.documents import permissions
Expand Down Expand Up @@ -47,15 +47,14 @@ def get(self, request):
return response


class DocumentStream(RetrieveAPIView):
class DocumentStream(DocumentStreamAPIView):
"""
Get streamed content of a document.
"""

authentication_classes = (SharedAuthentication,)
queryset = Document.objects.filter(safe=True)
queryset = Document.objects.all()
permission_classes = (permissions.IsCaseworkerOrInDocumentOrganisation,)

def retrieve(self, request, *args, **kwargs):
document = self.get_object()
return document_download_stream(document)
def get_document(self, instance):
return instance
13 changes: 5 additions & 8 deletions api/organisations/views/documents.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from rest_framework import viewsets
from rest_framework.generics import RetrieveAPIView

from django.http import JsonResponse

from api.audit_trail import service as audit_trail_service
from api.audit_trail.enums import AuditType
from api.core.authentication import SharedAuthentication
from api.documents.libraries.s3_operations import document_download_stream
from api.core.views import DocumentStreamAPIView
from api.organisations import (
filters,
models,
Expand Down Expand Up @@ -85,14 +84,12 @@ def update(self, request, pk, document_on_application_pk):
return JsonResponse({"document": serializer.data}, status=200)


class DocumentOnOrganisationStreamView(RetrieveAPIView):
class DocumentOnOrganisationStreamView(DocumentStreamAPIView):
authentication_classes = (SharedAuthentication,)
filter_backends = (filters.OrganisationFilter,)
lookup_url_kwarg = "document_on_application_pk"
permission_classes = (permissions.IsCaseworkerOrInDocumentOrganisation,)
queryset = models.DocumentOnOrganisation.objects.filter(document__safe=True)
queryset = models.DocumentOnOrganisation.objects.all()

def retrieve(self, request, *args, **kwargs):
document = self.get_object()
document = document.document
return document_download_stream(document)
def get_document(self, instance):
return instance.document

0 comments on commit 46cf759

Please sign in to comment.