Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#890] Downloadable zaakinformatieobject documenten #327

Merged
merged 10 commits into from
Nov 17, 2022
14 changes: 10 additions & 4 deletions src/open_inwoner/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
ActionListView,
ActionPrivateMediaView,
ActionUpdateView,
CasesListView,
CasesStatusView,
CaseDetailView,
CaseDocumentDownloadView,
CaseListView,
ContactCreateView,
ContactDeleteView,
ContactListView,
Expand Down Expand Up @@ -84,10 +85,15 @@
),
path("contacts/", ContactListView.as_view(), name="contact_list"),
path("themes/", MyCategoriesView.as_view(), name="my_themes"),
path("cases/", CasesListView.as_view(), name="my_cases"),
path("cases/", CaseListView.as_view(), name="my_cases"),
path(
"cases/document/<str:object_id>/",
CaseDocumentDownloadView.as_view(),
name="case_document_download",
),
path(
"cases/<str:object_id>/status/",
CasesStatusView.as_view(),
CaseDetailView.as_view(),
name="case_status",
),
path("edit/", EditProfileView.as_view(), name="edit_profile"),
Expand Down
2 changes: 1 addition & 1 deletion src/open_inwoner/accounts/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
LogPasswordResetConfirmView,
LogPasswordResetView,
)
from .cases import CasesListView, CasesStatusView
from .cases import CaseDetailView, CaseDocumentDownloadView, CaseListView
from .contacts import (
ContactCreateView,
ContactDeleteView,
Expand Down
138 changes: 118 additions & 20 deletions src/open_inwoner/accounts/views/cases.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
import dataclasses
from typing import List

from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.http import Http404, StreamingHttpResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import TemplateView

from view_breadcrumbs import BaseBreadcrumbMixin

from open_inwoner.openzaak.cases import (
fetch_case_types,
fetch_case_information_objects,
fetch_cases,
fetch_single_case,
fetch_single_case_type,
)
from open_inwoner.openzaak.statuses import (
fetch_case_information_objects,
fetch_single_status_type,
fetch_specific_statuses,
fetch_status_history,
)
from open_inwoner.openzaak.catalog import (
fetch_case_types,
fetch_single_case_type,
fetch_single_status_type,
fetch_status_types,
)
from open_inwoner.openzaak.documents import (
download_document,
fetch_single_information_object,
)
from open_inwoner.openzaak.models import OpenZaakConfig
from open_inwoner.openzaak.utils import filter_info_object_visibility


class CasesListView(
class CaseListView(
BaseBreadcrumbMixin, LoginRequiredMixin, UserPassesTestMixin, TemplateView
):
template_name = "pages/cases/list.html"
Expand Down Expand Up @@ -103,7 +115,14 @@ def get_context_data(self, **kwargs):
return context


class CasesStatusView(
@dataclasses.dataclass
class SimpleFile:
name: str
size: int
url: str


class CaseDetailView(
BaseBreadcrumbMixin, LoginRequiredMixin, UserPassesTestMixin, TemplateView
):
template_name = "pages/cases/status.html"
Expand Down Expand Up @@ -134,7 +153,8 @@ def get_context_data(self, **kwargs):
case = fetch_single_case(case_uuid)

if case:
case_info_objects = fetch_case_information_objects(case.url)
documents = self.get_case_document_files(case)

statuses = fetch_status_history(case.url)
statuses.sort(key=lambda status: status.datum_status_gezet)

Expand All @@ -149,24 +169,66 @@ def get_context_data(self, **kwargs):
context["case"] = {
"identification": case.identificatie,
"start_date": case.startdatum,
"end_date": case.einddatum if hasattr(case, "einddatum") else None,
"end_date": (case.einddatum if hasattr(case, "einddatum") else None),
"description": case.omschrijving,
"type_description": case_type.omschrijving
if case_type
else _("No data available"),
"current_status": statuses[-1].statustype.omschrijving
if statuses
and statuses[-1].statustype.omschrijving
in [st.omschrijving for st in status_types]
else _("No data available"),
"type_description": (
case_type.omschrijving if case_type else _("No data available")
),
"current_status": (
statuses[-1].statustype.omschrijving
if statuses
and statuses[-1].statustype.omschrijving
in [st.omschrijving for st in status_types]
else _("No data available")
),
Bartvaderkin marked this conversation as resolved.
Show resolved Hide resolved
"statuses": statuses,
"documents": case_info_objects,
"documents": documents,
}
context["anchors"] = self.get_anchors(statuses, case_info_objects)
context["anchors"] = self.get_anchors(statuses, documents)
else:
context["case"] = None
return context

def get_case_document_files(self, case) -> List[SimpleFile]:
case_info_objects = fetch_case_information_objects(case.url)

# get the information objects for the case objects

# TODO we'd like to use parallel() but it is borked in tests
Bartvaderkin marked this conversation as resolved.
Show resolved Hide resolved
# with parallel() as executor:
# info_objects = executor.map(
# fetch_single_information_object,
# [case_info.informatieobject for case_info in case_info_objects],
# )
info_objects = [
fetch_single_information_object(url=case_info.informatieobject)
for case_info in case_info_objects
]

config = OpenZaakConfig.get_solo()
documents = []
for case_info_obj, info_obj in zip(case_info_objects, info_objects):
if not info_obj:
continue
if not filter_info_object_visibility(
info_obj, config.document_max_confidentiality
):
continue
# restructure into something understood by the FileList template tag
documents.append(
SimpleFile(
name=info_obj.bestandsnaam,
size=info_obj.bestandsomvang,
url=reverse(
"accounts:case_document_download",
kwargs={
"object_id": info_obj.uuid,
},
),
)
)
return documents

def get_anchors(self, statuses, documents):
anchors = [["#title", _("Gegevens")]]

Expand All @@ -177,3 +239,39 @@ def get_anchors(self, statuses, documents):
anchors.append(["#documents", _("Documenten")])

return anchors


class CaseDocumentDownloadView(LoginRequiredMixin, UserPassesTestMixin, View):
def test_func(self):
return self.request.user.bsn is not None

def handle_no_permission(self):
if self.request.user.is_authenticated:
return redirect(reverse("root"))

return super().handle_no_permission()

def get(self, *args, **kwargs):
info_object_uuid = kwargs["object_id"]

info_object = fetch_single_information_object(uuid=info_object_uuid)
if not info_object:
raise Http404

config = OpenZaakConfig.get_solo()
if not filter_info_object_visibility(
info_object, config.document_max_confidentiality
):
raise PermissionDenied()

content_stream = download_document(info_object.inhoud)
if not content_stream:
raise Http404

headers = {
"Content-Disposition": f'attachment; filename="{info_object.bestandsnaam}"',
"Content-Type": info_object.formaat,
"Content-Length": info_object.bestandsomvang,
}
response = StreamingHttpResponse(content_stream, headers=headers)
return response
7 changes: 3 additions & 4 deletions src/open_inwoner/components/templatetags/file_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,19 @@ def case_document_list(documents: list[ZaakInformatieObject], **kwargs) -> dict:
{% case_document_list documents %}

Variables:
+ documents: ZaakInformatieObject[] | List ZaakInformatieObject objects.
+ documents: SimpleFile[]

Extra context:
+ files: list[dict] | A list of objects that are needed to render a file
+ show_download: bool | We disable the download button for the files.
"""

files = [
{
"file": document.titel or _("Geen titel"),
"file": document,
}
for document in documents
]
return {**kwargs, "documents": documents, "files": files, "show_download": False}
return {**kwargs, "documents": documents, "files": files, "show_download": True}


@register.inclusion_tag("components/File/FileTable.html")
Expand Down
101 changes: 101 additions & 0 deletions src/open_inwoner/openzaak/api_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from dataclasses import dataclass
from datetime import date, datetime
from typing import Optional

from zgw_consumers.api_models.base import ZGWModel

"""
Modified ZGWModel's to work with both OpenZaak and e-Suite implementations,
because there is an issue where e-Suite doesn't return all JSON fields the official API and dataclasses expect
"""


@dataclass
class Zaak(ZGWModel):
url: str
identificatie: str
bronorganisatie: str
omschrijving: str
# toelichting: str
zaaktype: str
registratiedatum: date
startdatum: date
einddatum_gepland: Optional[date]
uiterlijke_einddatum_afdoening: Optional[date]
# publicatiedatum: Optional[date]
vertrouwelijkheidaanduiding: str
status: str
einddatum: Optional[date] = None
# resultaat: str
# relevante_andere_zaken: list
# zaakgeometrie: dict


@dataclass
class ZaakType(ZGWModel):
url: str
# catalogus: str
identificatie: str
omschrijving: str
vertrouwelijkheidaanduiding: str
doel: str
aanleiding: str
indicatie_intern_of_extern: str
handeling_initiator: str
onderwerp: str
handeling_behandelaar: str
# doorlooptijd: relativedelta
# servicenorm: Optional[relativedelta]
# opschorting_en_aanhouding_mogelijk: bool
# verlenging_mogelijk: bool
# verlengingstermijn: Optional[relativedelta]
# publicatie_indicatie: bool
# producten_of_diensten: list
statustypen: list
# resultaattypen: list
# informatieobjecttypen: list
# roltypen: list
# besluittypen: list

# begin_geldigheid: date
# versiedatum: date


@dataclass
class ZaakInformatieObject(ZGWModel):
url: str
informatieobject: str
zaak: str
# aard_relatie_weergave: str
titel: str
# beschrijving: str
registratiedatum: datetime


@dataclass
class InformatieObject(ZGWModel):
url: str
identificatie: str
bronorganisatie: str
creatiedatum: date
titel: str
vertrouwelijkheidaanduiding: str
auteur: str
status: str
formaat: str
taal: str
versie: int
# beginRegistratie: datetime
bestandsnaam: str
inhoud: str
bestandsomvang: int
link: str
beschrijving: str
ontvangstdatum: str
verzenddatum: str
# indicatieGebruiksrecht: str
ondertekening: dict # {'soort': '', 'datum': None}
integriteit: dict # {'algoritme': '', 'waarde': '', 'datum': None}
informatieobjecttype: str
locked: bool
# bestandsdelen: List[str]
Loading