mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-23 07:14:21 +00:00
✨ Add research projects listing
This commit is contained in:
parent
d197ffa5f2
commit
bbca2b6deb
9 changed files with 212 additions and 7 deletions
|
@ -4,10 +4,14 @@ import rest_framework.routers
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
router = rest_framework.routers.DefaultRouter()
|
group_router = rest_framework.routers.DefaultRouter()
|
||||||
router.register("", views.ResearchProjectViewSet, basename="research-project")
|
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 = [
|
urlpatterns = [
|
||||||
path("", include(router.urls)),
|
path("by-group/<slug:group_slug>/", include(group_router.urls)),
|
||||||
|
path("by-slug/", include(slug_router.urls)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import abc
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from sophon.core.models import ResearchGroup
|
from sophon.core.models import ResearchGroup
|
||||||
|
@ -6,7 +8,29 @@ from sophon.core.views import SophonGroupViewSet
|
||||||
from . import models
|
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):
|
def get_queryset(self):
|
||||||
if self.request.user.is_anonymous:
|
if self.request.user.is_anonymous:
|
||||||
return models.ResearchProject.objects.filter(
|
return models.ResearchProject.objects.filter(
|
||||||
|
@ -18,6 +42,3 @@ class ResearchProjectViewSet(SophonGroupViewSet):
|
||||||
Q(visibility="INTERNAL") |
|
Q(visibility="INTERNAL") |
|
||||||
Q(visibility="PRIVATE", group__members__in=[self.request.user])
|
Q(visibility="PRIVATE", group__members__in=[self.request.user])
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_group_from_serializer(self, serializer) -> ResearchGroup:
|
|
||||||
return serializer.validated_data["group"]
|
|
||||||
|
|
36
frontend/src/components/ResearchGroupDescriptionBox.tsx
Normal file
36
frontend/src/components/ResearchGroupDescriptionBox.tsx
Normal file
|
@ -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<ResearchProject>("/api/core/groups/", pk)
|
||||||
|
|
||||||
|
if(group.resource) {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Heading level={3}>
|
||||||
|
{group.resource.name}
|
||||||
|
</Heading>
|
||||||
|
<p>
|
||||||
|
{group.resource.description}
|
||||||
|
</p>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Loading/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
43
frontend/src/components/ResearchProjectPanel.tsx
Normal file
43
frontend/src/components/ResearchProjectPanel.tsx
Normal file
|
@ -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 = <FontAwesomeIcon icon={faGlobe} title={"Public"}/>
|
||||||
|
}
|
||||||
|
else if(visibility === "INTERNAL") {
|
||||||
|
accessIcon = <FontAwesomeIcon icon={faUniversity} title={"Internal"}/>
|
||||||
|
}
|
||||||
|
else if(visibility === "PRIVATE") {
|
||||||
|
accessIcon = <FontAwesomeIcon icon={faLock} title={"Private"}/>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
accessIcon = <FontAwesomeIcon icon={faQuestion} title={"Unknown"}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ObjectPanel>
|
||||||
|
<ObjectPanel.Icon>
|
||||||
|
{accessIcon}
|
||||||
|
</ObjectPanel.Icon>
|
||||||
|
<ObjectPanel.Name>
|
||||||
|
<Link href={`/g/${group}/p/${slug}`}>
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
|
</ObjectPanel.Name>
|
||||||
|
<ObjectPanel.Text>
|
||||||
|
|
||||||
|
</ObjectPanel.Text>
|
||||||
|
<ObjectPanel.Buttons>
|
||||||
|
|
||||||
|
</ObjectPanel.Buttons>
|
||||||
|
</ObjectPanel>
|
||||||
|
)
|
||||||
|
}
|
41
frontend/src/components/ResearchProjectsByGroupListBox.tsx
Normal file
41
frontend/src/components/ResearchProjectsByGroupListBox.tsx
Normal file
|
@ -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<ResearchProject>(`/api/projects/by-group/${group_pk}/`, "slug")
|
||||||
|
|
||||||
|
const groups = React.useMemo(
|
||||||
|
() => {
|
||||||
|
if(!resources) {
|
||||||
|
return <Loading/>
|
||||||
|
}
|
||||||
|
return resources.map(
|
||||||
|
(res, key) => <ResearchProjectPanel {...res} key={key}/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[resources]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Heading level={3}>
|
||||||
|
Research projects
|
||||||
|
</Heading>
|
||||||
|
<div>
|
||||||
|
{groups}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
36
frontend/src/components/ResearchProjectsListBox.tsx
Normal file
36
frontend/src/components/ResearchProjectsListBox.tsx
Normal file
|
@ -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<ResearchProject>(`/api/projects/by-slug/`, "slug")
|
||||||
|
|
||||||
|
const groups = React.useMemo(
|
||||||
|
() => {
|
||||||
|
if(!resources) {
|
||||||
|
return <Loading/>
|
||||||
|
}
|
||||||
|
return resources.map(
|
||||||
|
(res, key) => <ResearchProjectPanel {...res} key={key}/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[resources]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Heading level={3}>
|
||||||
|
Research projects
|
||||||
|
</Heading>
|
||||||
|
<div>
|
||||||
|
{groups}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {ResearchGroupListBox} from "../components/ResearchGroupListBox";
|
import {ResearchGroupListBox} from "../components/ResearchGroupListBox";
|
||||||
import {InstanceDescriptionBox} from "../components/InstanceDescriptionBox";
|
import {InstanceDescriptionBox} from "../components/InstanceDescriptionBox";
|
||||||
|
import {ResearchProjectsListBox} from "../components/ResearchProjectsListBox";
|
||||||
|
|
||||||
|
|
||||||
export function InstancePage(): JSX.Element {
|
export function InstancePage(): JSX.Element {
|
||||||
|
@ -8,6 +9,7 @@ export function InstancePage(): JSX.Element {
|
||||||
<div>
|
<div>
|
||||||
<InstanceDescriptionBox/>
|
<InstanceDescriptionBox/>
|
||||||
<ResearchGroupListBox/>
|
<ResearchGroupListBox/>
|
||||||
|
<ResearchProjectsListBox/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
19
frontend/src/routes/ResearchGroupPage.tsx
Normal file
19
frontend/src/routes/ResearchGroupPage.tsx
Normal file
|
@ -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 (
|
||||||
|
<div>
|
||||||
|
<ResearchGroupDescriptionBox pk={pk}/>
|
||||||
|
<ResearchProjectsByGroupListBox group_pk={pk}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,9 +5,11 @@ import { InstancePage } from "./InstancePage"
|
||||||
import { ErrorCatcherBox, NotFoundBox } from "../components/ErrorBox"
|
import { ErrorCatcherBox, NotFoundBox } from "../components/ErrorBox"
|
||||||
import { InstanceTitle } from "../components/InstanceTitle"
|
import { InstanceTitle } from "../components/InstanceTitle"
|
||||||
import { UserPage } from "./UserPage"
|
import { UserPage } from "./UserPage"
|
||||||
|
import { ResearchGroupPage } from "./ResearchGroupPage"
|
||||||
|
|
||||||
|
|
||||||
export function Router() {
|
export function Router() {
|
||||||
|
// noinspection RequiredAttributes
|
||||||
return <>
|
return <>
|
||||||
<Reach.Router primary={false}>
|
<Reach.Router primary={false}>
|
||||||
<InstanceTitle default/>
|
<InstanceTitle default/>
|
||||||
|
@ -16,6 +18,7 @@ export function Router() {
|
||||||
<Reach.Router primary={true}>
|
<Reach.Router primary={true}>
|
||||||
<LoginPage path={"/"}/>
|
<LoginPage path={"/"}/>
|
||||||
<InstancePage path={"/g/"}/>
|
<InstancePage path={"/g/"}/>
|
||||||
|
<ResearchGroupPage path={"/g/:pk"}/>
|
||||||
<UserPage path={"/u/:pk"}/>
|
<UserPage path={"/u/:pk"}/>
|
||||||
<NotFoundBox default/>
|
<NotFoundBox default/>
|
||||||
</Reach.Router>
|
</Reach.Router>
|
||||||
|
|
Loading…
Reference in a new issue