diff --git a/backend/sophon/projects/urls.py b/backend/sophon/projects/urls.py index b276966..265b62b 100644 --- a/backend/sophon/projects/urls.py +++ b/backend/sophon/projects/urls.py @@ -4,10 +4,14 @@ import rest_framework.routers from . import views -router = rest_framework.routers.DefaultRouter() -router.register("", views.ResearchProjectViewSet, basename="research-project") +group_router = rest_framework.routers.DefaultRouter() +group_router.register("", views.ResearchProjectsByGroupViewSet, basename="research-project-by-group") + +slug_router = rest_framework.routers.DefaultRouter() +slug_router.register("", views.ResearchProjectsBySlugViewSet, basename="research-project-by-slug") urlpatterns = [ - path("", include(router.urls)), + path("by-group//", include(group_router.urls)), + path("by-slug/", include(slug_router.urls)), ] diff --git a/backend/sophon/projects/views.py b/backend/sophon/projects/views.py index ba8b5d3..fc3a9df 100644 --- a/backend/sophon/projects/views.py +++ b/backend/sophon/projects/views.py @@ -1,3 +1,5 @@ +import abc + from django.db.models import Q from sophon.core.models import ResearchGroup @@ -6,7 +8,29 @@ from sophon.core.views import SophonGroupViewSet from . import models -class ResearchProjectViewSet(SophonGroupViewSet): +class ResearchProjectViewSet(SophonGroupViewSet, metaclass=abc.ABCMeta): + def get_group_from_serializer(self, serializer) -> ResearchGroup: + return serializer.validated_data["group"] + + +class ResearchProjectsByGroupViewSet(ResearchProjectViewSet): + def get_queryset(self): + if self.request.user.is_anonymous: + return models.ResearchProject.objects.filter( + Q(group__slug=self.kwargs["group_slug"]) & + Q(visibility="PUBLIC") + ) + else: + return models.ResearchProject.objects.filter( + Q(group__slug=self.kwargs["group_slug"]) & ( + Q(visibility="PUBLIC") | + Q(visibility="INTERNAL") | + Q(visibility="PRIVATE", group__members__in=[self.request.user]) + ) + ) + + +class ResearchProjectsBySlugViewSet(ResearchProjectViewSet): def get_queryset(self): if self.request.user.is_anonymous: return models.ResearchProject.objects.filter( @@ -18,6 +42,3 @@ class ResearchProjectViewSet(SophonGroupViewSet): Q(visibility="INTERNAL") | Q(visibility="PRIVATE", group__members__in=[self.request.user]) ) - - def get_group_from_serializer(self, serializer) -> ResearchGroup: - return serializer.validated_data["group"] diff --git a/frontend/src/components/ResearchGroupDescriptionBox.tsx b/frontend/src/components/ResearchGroupDescriptionBox.tsx new file mode 100644 index 0000000..26e5625 --- /dev/null +++ b/frontend/src/components/ResearchGroupDescriptionBox.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {useDRFManagedDetail} from "../hooks/useDRF"; +import {Box, Heading} from "@steffo/bluelib-react"; +import {ResearchProject} from "../types"; +import {Loading} from "./Loading"; + + +interface ResearchGroupDescriptionBoxProps { + pk: string, +} + + +export function ResearchGroupDescriptionBox({pk}: ResearchGroupDescriptionBoxProps): JSX.Element { + const group = useDRFManagedDetail("/api/core/groups/", pk) + + if(group.resource) { + return ( + + + {group.resource.name} + +

+ {group.resource.description} +

+
+ ) + } + else { + return ( + + + + ) + } +} diff --git a/frontend/src/components/ResearchProjectPanel.tsx b/frontend/src/components/ResearchProjectPanel.tsx new file mode 100644 index 0000000..759e888 --- /dev/null +++ b/frontend/src/components/ResearchProjectPanel.tsx @@ -0,0 +1,43 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {ObjectPanel} from "./ObjectPanel"; +import {ResearchProject} from "../types"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faEnvelope, faGlobe, faLock, faQuestion, faUniversity} from "@fortawesome/free-solid-svg-icons"; +import {Link} from "./Link"; + + +export function ResearchProjectPanel({visibility, slug, name, description, group}: ResearchProject): JSX.Element { + let accessIcon: JSX.Element + if(visibility === "PUBLIC") { + accessIcon = + } + else if(visibility === "INTERNAL") { + accessIcon = + } + else if(visibility === "PRIVATE") { + accessIcon = + } + else { + accessIcon = + } + + return ( + + + {accessIcon} + + + + {name} + + + + + + + + + + ) +} diff --git a/frontend/src/components/ResearchProjectsByGroupListBox.tsx b/frontend/src/components/ResearchProjectsByGroupListBox.tsx new file mode 100644 index 0000000..1ebe9c0 --- /dev/null +++ b/frontend/src/components/ResearchProjectsByGroupListBox.tsx @@ -0,0 +1,41 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {useDRFManagedList} from "../hooks/useDRF"; +import {ResearchGroup, ResearchProject} from "../types"; +import {Loading} from "./Loading"; +import {ResearchGroupPanel} from "./ResearchGroupPanel"; +import {Box, Heading} from "@steffo/bluelib-react"; +import {ResearchProjectPanel} from "./ResearchProjectPanel"; + + +interface ProjectsListBoxProps { + group_pk: string +} + + +export function ResearchProjectsByGroupListBox({group_pk}: ProjectsListBoxProps): JSX.Element { + const {resources} = useDRFManagedList(`/api/projects/by-group/${group_pk}/`, "slug") + + const groups = React.useMemo( + () => { + if(!resources) { + return + } + return resources.map( + (res, key) => + ) + }, + [resources] + ) + + return ( + + + Research projects + +
+ {groups} +
+
+ ) +} diff --git a/frontend/src/components/ResearchProjectsListBox.tsx b/frontend/src/components/ResearchProjectsListBox.tsx new file mode 100644 index 0000000..a452e93 --- /dev/null +++ b/frontend/src/components/ResearchProjectsListBox.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {useDRFManagedList} from "../hooks/useDRF"; +import {ResearchGroup, ResearchProject} from "../types"; +import {Loading} from "./Loading"; +import {ResearchGroupPanel} from "./ResearchGroupPanel"; +import {Box, Heading} from "@steffo/bluelib-react"; +import {ResearchProjectPanel} from "./ResearchProjectPanel"; + + +export function ResearchProjectsListBox(): JSX.Element { + const {resources} = useDRFManagedList(`/api/projects/by-slug/`, "slug") + + const groups = React.useMemo( + () => { + if(!resources) { + return + } + return resources.map( + (res, key) => + ) + }, + [resources] + ) + + return ( + + + Research projects + +
+ {groups} +
+
+ ) +} diff --git a/frontend/src/routes/InstancePage.tsx b/frontend/src/routes/InstancePage.tsx index 3eb993c..8320d44 100644 --- a/frontend/src/routes/InstancePage.tsx +++ b/frontend/src/routes/InstancePage.tsx @@ -1,6 +1,7 @@ import * as React from "react" import {ResearchGroupListBox} from "../components/ResearchGroupListBox"; import {InstanceDescriptionBox} from "../components/InstanceDescriptionBox"; +import {ResearchProjectsListBox} from "../components/ResearchProjectsListBox"; export function InstancePage(): JSX.Element { @@ -8,6 +9,7 @@ export function InstancePage(): JSX.Element {
+
) } diff --git a/frontend/src/routes/ResearchGroupPage.tsx b/frontend/src/routes/ResearchGroupPage.tsx new file mode 100644 index 0000000..8a1b6e5 --- /dev/null +++ b/frontend/src/routes/ResearchGroupPage.tsx @@ -0,0 +1,19 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {ResearchGroupDescriptionBox} from "../components/ResearchGroupDescriptionBox"; +import {ResearchProjectsByGroupListBox} from "../components/ResearchProjectsByGroupListBox"; + + +interface ResearchGroupPageProps { + pk: string, +} + + +export function ResearchGroupPage({pk}: ResearchGroupPageProps): JSX.Element { + return ( +
+ + +
+ ) +} diff --git a/frontend/src/routes/Router.jsx b/frontend/src/routes/Router.jsx index e5244ab..90fb4a6 100644 --- a/frontend/src/routes/Router.jsx +++ b/frontend/src/routes/Router.jsx @@ -5,9 +5,11 @@ import { InstancePage } from "./InstancePage" import { ErrorCatcherBox, NotFoundBox } from "../components/ErrorBox" import { InstanceTitle } from "../components/InstanceTitle" import { UserPage } from "./UserPage" +import { ResearchGroupPage } from "./ResearchGroupPage" export function Router() { + // noinspection RequiredAttributes return <> @@ -16,6 +18,7 @@ export function Router() { +