diff --git a/backend/library/views.py b/backend/library/views.py index 54aab536c..a7c1391b2 100644 --- a/backend/library/views.py +++ b/backend/library/views.py @@ -3,7 +3,9 @@ from rest_framework import viewsets, status from rest_framework.status import ( HTTP_200_OK, + HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, + HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY, ) @@ -48,9 +50,31 @@ def get_serializer_class(self): return StoredLibrarySerializer return StoredLibraryDetailedSerializer + def retrieve(self, request, *args, pk, **kwargs): + if "view_storedlibrary" not in request.user.permissions: + return Response(status=HTTP_403_FORBIDDEN) + try: + lib = StoredLibrary.objects.get( + urn=pk + ) # There is no "locale" value involved in the fetch + we have to handle the exception if the pk urn doesn't exist + except: + return Response(data="Library not found.", status=HTTP_404_NOT_FOUND) + data = StoredLibrarySerializer(lib).data + return Response(data) + + def content(self, request, pk): + try : + lib = StoredLibrary.objects.get(urn=pk) + except : + return Response("Library not found.", status=HTTP_404_NOT_FOUND) + return Response(lib.content) + @action(detail=True, methods=["get"]) def content(self, request, pk): - lib = StoredLibrary.objects.get(id=pk) + try : + lib = StoredLibrary.objects.get(urn=pk) + except : + return Response("Library not found.", status=HTTP_404_NOT_FOUND) return Response(lib.content) def destroy(self, request, *args, pk, **kwargs): @@ -59,15 +83,15 @@ def destroy(self, request, *args, pk, **kwargs): perm=Permission.objects.get(codename="delete_storedlibrary"), folder=Folder.get_root_folder(), ): - return Response(status=status.HTTP_403_FORBIDDEN) + return Response(status=HTTP_403_FORBIDDEN) try: lib = StoredLibrary.objects.get(urn=pk) except: - return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) + return Response(data="Library not found.", status=HTTP_404_NOT_FOUND) lib.delete() - return Response(status=status.HTTP_204_NO_CONTENT) + return Response(status=HTTP_204_NO_CONTENT) @action(detail=True, methods=["get"], url_path="import") def import_library(self, request, pk): @@ -76,20 +100,20 @@ def import_library(self, request, pk): perm=Permission.objects.get(codename="add_loadedlibrary"), folder=Folder.get_root_folder(), ): - return Response(status=status.HTTP_403_FORBIDDEN) + return Response(status=HTTP_403_FORBIDDEN) try: library = StoredLibrary.objects.get( urn=pk ) # This is only fetching the lib by URN without caring about the locale or the version, this must change in the future. except: - return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) + return Response(data="Library not found.", status=HTTP_404_NOT_FOUND) try: error_msg = library.load() if error_msg is not None: return Response( {"status": "error", "error": error_msg}, - status=status.HTTP_400_BAD_REQUEST, + status=HTTP_400_BAD_REQUEST, ) # This can cause translation issues return Response({"status": "success"}) except Exception: @@ -103,9 +127,9 @@ def import_library(self, request, pk): @action(detail=True, methods=["get"]) def tree(self, request, pk): try: - lib = StoredLibrary.objects.get(id=pk) + lib = StoredLibrary.objects.get(urn=pk) except: - return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) + return Response(data="Library not found.", status=HTTP_404_NOT_FOUND) library_objects = json.loads(lib.content) # We may need caching for this if not (framework := library_objects.get("framework")): @@ -113,6 +137,7 @@ def tree(self, request, pk): data="This library does not include a framework.", status=HTTP_400_BAD_REQUEST, ) + preview = preview_library(framework) return Response( get_sorted_requirement_nodes(preview.get("requirement_nodes"), None) @@ -165,7 +190,7 @@ class LoadedLibraryViewSet(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): if "view_storedlibrary" not in request.user.permissions: - return Response(status=status.HTTP_403_FORBIDDEN) + return Response(status=HTTP_403_FORBIDDEN) loaded_libraries = [ { @@ -191,13 +216,13 @@ def list(self, request, *args, **kwargs): def retrieve(self, request, *args, pk, **kwargs): if "view_loadedlibrary" not in request.user.permissions: - return Response(status=status.HTTP_403_FORBIDDEN) + return Response(status=HTTP_403_FORBIDDEN) try: lib = LoadedLibrary.objects.get( urn=pk ) # There is no "locale" value involved in the fetch + we have to handle the exception if the pk urn doesn't exist except: - return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) + return Response(data="Library not found.", status=HTTP_404_NOT_FOUND) data = LoadedLibraryDetailedSerializer(lib).data data["objects"] = lib._objects return Response(data) @@ -208,30 +233,39 @@ def destroy(self, request, *args, pk, **kwargs): perm=Permission.objects.get(codename="delete_loadedlibrary"), folder=Folder.get_root_folder(), ): - return Response(status=status.HTTP_403_FORBIDDEN) + return Response(status=HTTP_403_FORBIDDEN) try: lib = LoadedLibrary.objects.get(urn=pk) except: - return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) + return Response(data="Library not found.", status=HTTP_404_NOT_FOUND) if lib.reference_count != 0: return Response( data="Library cannot be deleted because it has references.", - status=status.HTTP_400_BAD_REQUEST, + status=HTTP_400_BAD_REQUEST, ) lib.delete() - return Response(status=status.HTTP_204_NO_CONTENT) + return Response(status=HTTP_204_NO_CONTENT) + + @action(detail=True, methods=["get"]) + def content(self, request, pk): + try : + lib = LoadedLibrary.objects.get(urn=pk) + except : + return Response("Library not found.", status=HTTP_404_NOT_FOUND) + return Response(lib._objects) @action(detail=True, methods=["get"]) def tree( self, request, pk ): # We must ensure that users that are not allowed to read the content of libraries can't have any access to them either from the /api/{URLModel/{library_urn}/tree view or the /api/{URLModel}/{library_urn} view. + try: lib = LoadedLibrary.objects.get(urn=pk) except: - return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) + return Response(data="Library not found.", status=HTTP_404_NOT_FOUND) if lib.frameworks.count() == 0: return Response( diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index f92d8caaf..d038bb30c 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -72,7 +72,7 @@ const rowMetaData = $rows[rowIndex].meta; /** @event {rowMetaData} selected - Fires when a table row is clicked. */ if (!rowMetaData[identifierField] || !URLModel) return; - goto(`/${URLModel}/${rowMetaData[identifierField]}`); + goto(`/${URLModel}/${rowMetaData[identifierField]}${detailQueryParameter}`); } // Row Keydown Handler @@ -84,10 +84,10 @@ } export let identifierField = 'id'; - export let deleteForm: SuperValidated | undefined = undefined; - export let URLModel: urlModel | undefined = undefined; + export let detailQueryParameter: string | undefined ; + detailQueryParameter = detailQueryParameter ? `?${detailQueryParameter}` : ""; const user = $page.data.user; @@ -133,7 +133,7 @@ $: model = source.meta?.urlmodel ? URL_MODEL_MAP[source.meta.urlmodel] : URL_MODEL_MAP[URLModel]; $: source, handler.setRows(data); - const actionsURLModel = source.meta.urlmodel ?? URLModel; + const actionsURLModel = source.meta?.urlmodel ?? URLModel; const preventDelete = (row: TableSource) => (row.meta.builtin && actionsURLModel !== 'loaded-libraries') || (Object.hasOwn(row.meta, 'reference_count') && row.meta.reference_count > 0); @@ -272,7 +272,7 @@ deleteForm={deleteForm} {model} URLModel={actionsURLModel} - detailURL={`/${actionsURLModel}/${row.meta[identifierField]}`} + detailURL={`/${actionsURLModel}/${row.meta[identifierField]}${detailQueryParameter}`} editURL={!(row.meta.builtin || row.meta.urn) ? `/${actionsURLModel}/${row.meta[identifierField]}/edit?next=${$page.url.pathname}` : undefined} {row} hasBody={$$slots.actionsBody} diff --git a/frontend/src/routes/(app)/libraries/+page.svelte b/frontend/src/routes/(app)/libraries/+page.svelte index aba78fa54..6549adcab 100644 --- a/frontend/src/routes/(app)/libraries/+page.svelte +++ b/frontend/src/routes/(app)/libraries/+page.svelte @@ -46,6 +46,7 @@ identifierField="urn" pagination={false} deleteForm={data.deleteForm} + detailQueryParameter="loaded" /> {/if} diff --git a/frontend/src/routes/(app)/libraries/[id=urn]/+server.ts b/frontend/src/routes/(app)/libraries/[id=urn]/+server.ts index 28c9594bf..1879fe21c 100644 --- a/frontend/src/routes/(app)/libraries/[id=urn]/+server.ts +++ b/frontend/src/routes/(app)/libraries/[id=urn]/+server.ts @@ -6,23 +6,20 @@ import type { RequestHandler } from './$types'; export const GET: RequestHandler = async ({ fetch, url, params }) => { const isLoaded = url.searchParams.has('loaded'); const URLModel = isLoaded ? 'loaded-libraries' : 'stored-libraries'; - const endpoint = `${BASE_API_URL}/${URLModel}/?urn=${params.id}`; + const endpoint = `${BASE_API_URL}/${URLModel}/${params.id}/`; + const contentEndpoint = `${BASE_API_URL}/${URLModel}/${params.id}/content`; - const res = await fetch(endpoint); - if (!res.ok) { - error(res.status as NumericRange<400, 599>, await res.json()); - } - const data = await res - .json() - .then((res) => res.results) - .then((res) => res.reduce((acc, curr) => (acc.version > curr.version ? acc : curr))); // Get the latest version of the library + const [res,contentRes] = await Promise.all([ + fetch(endpoint), + fetch(contentEndpoint) + ]); - const uuid = data.id; - const contentEndpoint = `${BASE_API_URL}/${URLModel}/${uuid}/content`; - const contentRes = await fetch(contentEndpoint); - if (!contentRes.ok) { + if (!res.ok) + error(res.status as NumericRange<400, 599>, await res.json()); + if (!contentRes.ok) error(contentRes.status as NumericRange<400, 599>, await contentRes.json()); - } + + const data = await res.json() const content = await contentRes.json(); data.objects = content; if (!isLoaded) { diff --git a/frontend/src/routes/(app)/libraries/[id=urn]/tree/+server.ts b/frontend/src/routes/(app)/libraries/[id=urn]/tree/+server.ts index 5d2119ae1..d9c142e66 100644 --- a/frontend/src/routes/(app)/libraries/[id=urn]/tree/+server.ts +++ b/frontend/src/routes/(app)/libraries/[id=urn]/tree/+server.ts @@ -5,26 +5,13 @@ import type { RequestHandler } from './$types'; export const GET: RequestHandler = async ({ fetch, params, url }) => { const URLModel = url.searchParams.has('loaded') ? 'loaded-libraries' : 'stored-libraries'; - const endpoint = `${BASE_API_URL}/${URLModel}/?urn=${params.id}`; - + const endpoint = `${BASE_API_URL}/${URLModel}/${params.id}/tree`; const res = await fetch(endpoint); - if (!res.ok) { - error(res.status as NumericRange<400, 599>, await res.json()); - } - const data = await res - .json() - .then((res) => res.results) - .then((res) => res.reduce((acc, curr) => (acc.version > curr.version ? acc : curr))); // Get the latest version of the library - const uuid = data.id; - const treeEndpoint = `${BASE_API_URL}/${URLModel}/${uuid}/tree`; - const treeRes = await fetch(treeEndpoint); - if (!treeRes.ok) { - error(treeRes.status as NumericRange<400, 599>, await treeRes.json()); - } - - const tree = await treeRes.json(); + if (!res.ok) + error(res.status as NumericRange<400, 599>, await res.json()); + const tree = await res.json(); return new Response(JSON.stringify(tree), { headers: { 'Content-Type': 'application/json'