diff --git a/rsconcept/backend/apps/rsform/tests/s_views/EndpointTester.py b/rsconcept/backend/apps/rsform/tests/s_views/EndpointTester.py index bee52aa6..2608b2af 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/EndpointTester.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/EndpointTester.py @@ -29,7 +29,7 @@ class EndpointTester(APITestCase): self.client = APIClient() self.client.force_authenticate(user=self.user) - def toggle_staff(self, value: bool = True): + def toggle_admin(self, value: bool = True): self.user.is_staff = value self.user.save() diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_library.py b/rsconcept/backend/apps/rsform/tests/s_views/t_library.py index d2c4f6e2..794b8688 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_library.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_library.py @@ -66,7 +66,7 @@ class TestLibraryViewset(EndpointTester): self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT]) self.assertForbidden(item=self.unowned.id) - self.toggle_staff(True) + self.toggle_admin(True) response = self.execute(item=self.unowned.id) self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT]) @@ -111,6 +111,21 @@ class TestLibraryViewset(EndpointTester): self.assertFalse(response_contains(response, self.owned)) + @decl_endpoint('/api/library/all', method='get') + def test_retrieve_all(self): + self.toggle_admin(False) + self.assertForbidden() + self.toggle_admin(True) + response = self.execute() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue(response_contains(response, self.common)) + self.assertTrue(response_contains(response, self.unowned)) + self.assertTrue(response_contains(response, self.owned)) + + self.logout() + self.assertForbidden() + + @decl_endpoint('/api/library/active', method='get') def test_retrieve_subscribed(self): response = self.execute() diff --git a/rsconcept/backend/apps/rsform/urls.py b/rsconcept/backend/apps/rsform/urls.py index c674e2e2..9441dde3 100644 --- a/rsconcept/backend/apps/rsform/urls.py +++ b/rsconcept/backend/apps/rsform/urls.py @@ -8,7 +8,8 @@ library_router.register('library', views.LibraryViewSet, 'Library') library_router.register('rsforms', views.RSFormViewSet, 'RSForm') urlpatterns = [ - path('library/active', views.LibraryActiveView.as_view(), name='library'), + path('library/active', views.LibraryActiveView.as_view()), + path('library/all', views.LibraryAdminView.as_view()), path('library/templates', views.LibraryTemplatesView.as_view(), name='templates'), path('constituents/', views.ConstituentAPIView.as_view(), name='constituenta-detail'), path('rsforms/import-trs', views.TrsImportView.as_view()), diff --git a/rsconcept/backend/apps/rsform/views/__init__.py b/rsconcept/backend/apps/rsform/views/__init__.py index d58a8f06..93d6b28d 100644 --- a/rsconcept/backend/apps/rsform/views/__init__.py +++ b/rsconcept/backend/apps/rsform/views/__init__.py @@ -1,6 +1,7 @@ ''' REST API: Endpoint processors. ''' from .library import ( LibraryActiveView, + LibraryAdminView, LibraryTemplatesView, LibraryViewSet ) diff --git a/rsconcept/backend/apps/rsform/views/library.py b/rsconcept/backend/apps/rsform/views/library.py index 7c6e2f9b..f53e324e 100644 --- a/rsconcept/backend/apps/rsform/views/library.py +++ b/rsconcept/backend/apps/rsform/views/library.py @@ -34,6 +34,17 @@ class LibraryActiveView(generics.ListAPIView): ).distinct().order_by('-time_update') +@extend_schema(tags=['Library']) +@extend_schema_view() +class LibraryAdminView(generics.ListAPIView): + ''' Endpoint: Get list of all library items. Admin only ''' + permission_classes = (permissions.IsAdminUser,) + serializer_class = s.LibraryItemSerializer + + def get_queryset(self): + return m.LibraryItem.objects.all().order_by('-time_update') + + @extend_schema(tags=['Library']) @extend_schema_view() class LibraryTemplatesView(generics.ListAPIView): diff --git a/rsconcept/frontend/src/app/backendAPI.ts b/rsconcept/frontend/src/app/backendAPI.ts index 747f6e7a..9f4ae83b 100644 --- a/rsconcept/frontend/src/app/backendAPI.ts +++ b/rsconcept/frontend/src/app/backendAPI.ts @@ -192,6 +192,14 @@ export function getLibrary(request: FrontPull) { }); } +export function getAdminLibrary(request: FrontPull) { + AxiosGet({ + title: 'All LibraryItems list', + endpoint: '/api/library/all', + request: request + }); +} + export function getTemplates(request: FrontPull) { AxiosGet({ title: 'Available LibraryItems list', diff --git a/rsconcept/frontend/src/context/LibraryContext.tsx b/rsconcept/frontend/src/context/LibraryContext.tsx index dca3e4bb..625d6231 100644 --- a/rsconcept/frontend/src/context/LibraryContext.tsx +++ b/rsconcept/frontend/src/context/LibraryContext.tsx @@ -2,23 +2,25 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'react'; -import { ErrorData } from '@/components/info/InfoError'; -import { ILibraryItem } from '@/models/library'; -import { matchLibraryItem } from '@/models/libraryAPI'; -import { ILibraryFilter } from '@/models/miscellaneous'; -import { IRSForm, IRSFormCloneData, IRSFormCreateData, IRSFormData } from '@/models/rsform'; -import { RSFormLoader } from '@/models/RSFormLoader'; import { DataCallback, deleteLibraryItem, + getAdminLibrary, getLibrary, getRSFormDetails, getTemplates, postCloneLibraryItem, postNewRSForm } from '@/app/backendAPI'; +import { ErrorData } from '@/components/info/InfoError'; +import { ILibraryItem } from '@/models/library'; +import { matchLibraryItem } from '@/models/libraryAPI'; +import { ILibraryFilter } from '@/models/miscellaneous'; +import { IRSForm, IRSFormCloneData, IRSFormCreateData, IRSFormData } from '@/models/rsform'; +import { RSFormLoader } from '@/models/RSFormLoader'; import { useAuth } from './AuthContext'; +import { useConceptOptions } from './OptionsContext'; interface ILibraryContext { items: ILibraryItem[]; @@ -53,6 +55,7 @@ interface LibraryStateProps { export const LibraryState = ({ children }: LibraryStateProps) => { const { user } = useAuth(); + const { adminMode } = useConceptOptions(); const [items, setItems] = useState([]); const [templates, setTemplates] = useState([]); @@ -109,19 +112,36 @@ export const LibraryState = ({ children }: LibraryStateProps) => { [cachedTemplates] ); - const reload = useCallback((callback?: () => void) => { - setItems([]); - setError(undefined); - getLibrary({ - setLoading: setLoading, - showError: true, - onError: setError, - onSuccess: newData => { - setItems(newData); - if (callback) callback(); + const reloadItems = useCallback( + (callback?: () => void) => { + setItems([]); + setError(undefined); + if (user?.is_staff && adminMode) { + getAdminLibrary({ + setLoading: setLoading, + showError: true, + onError: setError, + onSuccess: newData => { + setItems(newData); + if (callback) callback(); + } + }); + } else { + getLibrary({ + setLoading: setLoading, + showError: true, + onError: setError, + onSuccess: newData => { + setItems(newData); + if (callback) callback(); + } + }); } - }); + }, + [user, adminMode] + ); + const reloadTemplates = useCallback(() => { setTemplates([]); getTemplates({ showError: true, @@ -130,8 +150,12 @@ export const LibraryState = ({ children }: LibraryStateProps) => { }, []); useEffect(() => { - reload(); - }, [reload, user]); + reloadItems(); + }, [reloadItems]); + + useEffect(() => { + reloadTemplates(); + }, [reloadTemplates]); const localUpdateItem = useCallback( (data: ILibraryItem) => { @@ -160,7 +184,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setLoading: setProcessing, onError: setError, onSuccess: newSchema => - reload(() => { + reloadItems(() => { if (user && !user.subscriptions.includes(newSchema.id)) { user.subscriptions.push(newSchema.id); } @@ -168,7 +192,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { }) }); }, - [reload, user] + [reloadItems, user] ); const destroyItem = useCallback( @@ -179,7 +203,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setLoading: setProcessing, onError: setError, onSuccess: () => - reload(() => { + reloadItems(() => { if (user && user.subscriptions.includes(target)) { user.subscriptions.splice( user.subscriptions.findIndex(item => item === target), @@ -190,7 +214,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { }) }); }, - [setError, reload, user] + [setError, reloadItems, user] ); const cloneItem = useCallback( @@ -205,7 +229,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { setLoading: setProcessing, onError: setError, onSuccess: newSchema => - reload(() => { + reloadItems(() => { if (user && !user.subscriptions.includes(newSchema.id)) { user.subscriptions.push(newSchema.id); } @@ -213,7 +237,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => { }) }); }, - [reload, setError, user] + [reloadItems, setError, user] ); return (