mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-22 23:04: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
|
||||
|
||||
|
||||
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/<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 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"]
|
||||
|
|
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 {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 {
|
|||
<div>
|
||||
<InstanceDescriptionBox/>
|
||||
<ResearchGroupListBox/>
|
||||
<ResearchProjectsListBox/>
|
||||
</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 { InstanceTitle } from "../components/InstanceTitle"
|
||||
import { UserPage } from "./UserPage"
|
||||
import { ResearchGroupPage } from "./ResearchGroupPage"
|
||||
|
||||
|
||||
export function Router() {
|
||||
// noinspection RequiredAttributes
|
||||
return <>
|
||||
<Reach.Router primary={false}>
|
||||
<InstanceTitle default/>
|
||||
|
@ -16,6 +18,7 @@ export function Router() {
|
|||
<Reach.Router primary={true}>
|
||||
<LoginPage path={"/"}/>
|
||||
<InstancePage path={"/g/"}/>
|
||||
<ResearchGroupPage path={"/g/:pk"}/>
|
||||
<UserPage path={"/u/:pk"}/>
|
||||
<NotFoundBox default/>
|
||||
</Reach.Router>
|
||||
|
|
Loading…
Reference in a new issue