From b508995ead1d3654d5d557c34bffbc3e33456add Mon Sep 17 00:00:00 2001 From: monsieurswag Date: Fri, 7 Jun 2024 22:05:14 +0200 Subject: [PATCH] User can add evidences from applied controls Formatter Formatter --- backend/core/views.py | 43 ++++++- .../ModelTable/LibraryActions.svelte | 1 + .../components/ModelTable/ModelTable.svelte | 12 +- .../TableRowActions/TableRowActions.svelte | 107 +++++++++++++++++- frontend/src/lib/utils/crud.ts | 28 ++++- .../[id=uuid]/+layout.server.ts | 7 +- .../[model=urlmodel]/[id=uuid]/+page.svelte | 19 +++- .../[id=uuid]/+page.server.ts | 51 +++++++++ 8 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 frontend/src/routes/(app)/applied_controls/[id=uuid]/+page.server.ts diff --git a/backend/core/views.py b/backend/core/views.py index dd6691b41..79f278608 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -34,7 +34,12 @@ from rest_framework.parsers import FileUploadParser from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN +from rest_framework.status import ( + HTTP_204_NO_CONTENT, + HTTP_400_BAD_REQUEST, + HTTP_403_FORBIDDEN, + HTTP_404_NOT_FOUND, +) from rest_framework.utils.serializer_helpers import ReturnDict from rest_framework.views import APIView from weasyprint import HTML @@ -555,6 +560,42 @@ class AppliedControlViewSet(BaseModelViewSet): ] search_fields = ["name", "description", "risk_scenarios", "requirement_assessments"] + @action(detail=True, methods=["post"], name="Add an evidence to an applied control") + def add_evidence(self, request, pk): + evidence = request.data.get("evidence") + if evidence is None: + return Response(status=HTTP_400_BAD_REQUEST) + try: + applied_control = AppliedControl.objects.get(id=pk) + except: + return Response("Applied control not found", status=HTTP_404_NOT_FOUND) + try: + evidence_to_add = Evidence.objects.get(id=evidence) + except: + return Response("Evidence not found", status=HTTP_404_NOT_FOUND) + # It would be faster to directly put an UUID in .add to avoid fetching the evidence from the database for nothing + applied_control.evidences.add(evidence_to_add) + return Response(status=HTTP_204_NO_CONTENT) + + @action( + detail=True, methods=["post"], name="Remove an evidence to an applied control" + ) + def remove_evidence(self, request, pk): + evidence = request.data.get("evidence") + if evidence is None: + return Response(status=HTTP_400_BAD_REQUEST) + try: + applied_control = AppliedControl.objects.get(id=pk) + except: + return Response("Applied control not found", status=HTTP_404_NOT_FOUND) + try: + evidence_to_remove = Evidence.objects.get(id=evidence) + except: + return Response("Evidence not found", status=HTTP_404_NOT_FOUND) + # It would be faster to directly put an UUID in .remove to avoid fetching the evidence from the database for nothing + applied_control.evidences.remove(evidence_to_remove) + return Response(status=HTTP_204_NO_CONTENT) + @action(detail=False, name="Get status choices") def status(self, request): return Response(dict(AppliedControl.Status.choices)) diff --git a/frontend/src/lib/components/ModelTable/LibraryActions.svelte b/frontend/src/lib/components/ModelTable/LibraryActions.svelte index 3b08e7c3f..f5893f2dd 100644 --- a/frontend/src/lib/components/ModelTable/LibraryActions.svelte +++ b/frontend/src/lib/components/ModelTable/LibraryActions.svelte @@ -28,6 +28,7 @@ {#if actionsURLModel === 'stored-libraries' && Object.hasOwn(library, 'is_loaded') && !library.is_loaded} {#if loading.form && loading.library === library.urn} +
{#if !hasBody} + {#if displayAdd && containerObject} + {#if isInsideObject} + + {:else} + + {/if} + {/if} {#if displayDetail} { type RelatedModel = { urlModel: urlModel; + addOption: boolean; info: ModelMapEntry; table: TableSource; deleteForm: SuperValidated; @@ -43,7 +44,9 @@ export const load: LayoutServerLoad = async ({ fetch, params }) => { const initialData = {}; await Promise.all( model.reverseForeignKeyFields.map(async (e) => { - const relEndpoint = `${BASE_API_URL}/${e.urlModel}/?${e.field}=${params.id}`; + const relEndpoint = e.displayAll + ? `${BASE_API_URL}/${e.urlModel}/` + : `${BASE_API_URL}/${e.urlModel}/?${e.field}=${params.id}`; const res = await fetch(relEndpoint); const revData = await res.json().then((res) => res.results); @@ -62,6 +65,7 @@ export const load: LayoutServerLoad = async ({ fetch, params }) => { }; const info = getModelInfo(e.urlModel); + const addOption = Boolean(e.addOption); const urlModel = e.urlModel; const deleteForm = await superValidate(zod(z.object({ id: z.string().uuid() }))); @@ -116,6 +120,7 @@ export const load: LayoutServerLoad = async ({ fetch, params }) => { } relatedModels[e.urlModel] = { urlModel, + addOption, info, table, deleteForm, diff --git a/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte index 7da65737a..bfcc33226 100644 --- a/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte @@ -25,6 +25,7 @@ const toastStore: ToastStore = getToastStore(); export let data; + $: data.relatedModels = Object.fromEntries(Object.entries(data.relatedModels).sort()); if (data.model.detailViewFields) { @@ -276,7 +277,23 @@
{#if model.table} - + + {@const containerReferences = model.addOption + ? new Set( + model.foreignKeys[data.urlModel.replace('-', '_')] + .filter((obj) => obj.id === data.data.id)[0] + [urlmodel].map((obj) => obj.id) + ) + : undefined} +