Allow actually posting pairings

This commit is contained in:
Dustin J. Mitchell 2020-10-16 20:09:08 +00:00
Родитель 873819c468
Коммит 9adbcc5b67
7 изменённых файлов: 80 добавлений и 21 удалений

Просмотреть файл

@ -9,7 +9,7 @@ Stuff to do!
* pairing
* option to show existing pairings with "unpair" button
* API method to create a pair
* check in model that pair.mentor.role == MENTOR etc.
* check in model that pair.mentor.role == MENTOR etc. and not already paired
* hardening
* protect API with is_staff or something

Просмотреть файл

@ -6,6 +6,7 @@ import Toolbar from '@material-ui/core/Toolbar';
import Tooltip from '@material-ui/core/Tooltip';
import Typography from '@material-ui/core/Typography';
import CogOutlineIcon from 'mdi-react/CogOutlineIcon';
import CodeJsonIcon from 'mdi-react/CodeJsonIcon';
import Home from './views/Home';
import Pairing from './views/Pairing';
import { ThemeProvider, createMuiTheme, makeStyles } from '@material-ui/core/styles';
@ -78,6 +79,13 @@ export default function App(props) {
</IconButton>
</Tooltip>
</a>
<a href="/api/">
<Tooltip placement="bottom" title="REST API">
<IconButton>
<CodeJsonIcon className={classes.appIcon} />
</IconButton>
</Tooltip>
</a>
</Toolbar>
</AppBar>
<Switch>

Просмотреть файл

@ -2,18 +2,19 @@ import React, { Fragment } from "react";
import LinearProgress from '@material-ui/core/LinearProgress';
// Show a spinner if any of the array of loads are still loading, or an error
// if the load fails; otherwise show children.
export default function Loading({ children, loads }) {
// if the load fails; otherwise show children. errorOnly is similar, but ignoring
// the loading state (for something that does not prevent rendering)
export default function Loading({ children, loads, errorOnly }) {
if (!loads.every(({ loading }) => !loading)) {
return <LinearProgress />
}
for (let {error} of loads) {
for (let {error} of loads.concat(errorOnly)) {
if (error) {
return (
<div>
<h2>Error</h2>
{error}
<pre>{error.toString()}</pre>
</div>
);
}

Просмотреть файл

@ -1,4 +1,5 @@
import React, { Fragment, useState } from "react";
import useAxios from 'axios-hooks'
import Helmet from 'react-helmet';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
@ -17,6 +18,7 @@ import CloseIcon from 'mdi-react/CloseIcon';
import ChevronDownIcon from 'mdi-react/ChevronDownIcon';
import Loading from '../components/Loading';
import Participant from '../components/Participant';
import LinearProgress from '@material-ui/core/LinearProgress';
import { useParticipants } from '../data/participants';
const useStyles = makeStyles(theme => ({
@ -58,7 +60,7 @@ function ParticipantColumn({ participants, title, filter, onSelect, selected })
);
}
function PairDrawer({ mentor, learner, open, onClose, onPair }) {
function PairDrawer({ mentor, learner, open, pairing, onClose, onPair }) {
const classes = useStyles();
// TODO: show time compatibility, shared interests, etc.
@ -73,6 +75,7 @@ function PairDrawer({ mentor, learner, open, onClose, onPair }) {
PaperProps={{ elevation: 4 }}>
{open && (
<Fragment>
{pairing && <LinearProgress />}
<DialogTitle>Proposed Pair: {mentor.full_name} / {learner.full_name}</DialogTitle>
<DialogContent>
<DialogContentText>
@ -94,24 +97,32 @@ function PairDrawer({ mentor, learner, open, onClose, onPair }) {
}
export default function Home(props) {
const [participants, refectParticipants] = useParticipants();
const [participants, refecthParticipants] = useParticipants();
const [mentor, setMentor] = useState(null);
const [learner, setLearner] = useState(null);
const [drawerOpen, setDrawerOpen] = useState(false);
const [pair, postPairing] = useAxios({
url: '/api/pairs',
method: 'POST',
headers: { 'X-CSRFToken': csrftoken },
}, { manual: true });
return (
<Fragment>
<Helmet>
<title>Mozilla Mentorship Program - Pairing</title>
</Helmet>
<Loading loads={ [ participants ] }>
<Loading loads={ [ participants ] } errorOnly={ [ pair ] }>
<ParticipantColumn
title="Mentors"
participants={participants}
filter={p => p.role == 'M'}
onSelect={p => {
setMentor(p);
learner && setDrawerOpen(true);
if (!pair.loading) {
setMentor(p);
learner && setDrawerOpen(true);
}
}}
selected={mentor} />
<ParticipantColumn
@ -119,26 +130,35 @@ export default function Home(props) {
participants={participants}
filter={p => p.role == 'L'}
onSelect={p => {
setLearner(p);
mentor && setDrawerOpen(true);
if (!pair.loading) {
setLearner(p);
mentor && setDrawerOpen(true);
}
}}
selected={learner} />
<div style={{ clear: 'all' }} />
<PairDrawer
mentor={mentor}
learner={learner}
open={drawerOpen}
open={mentor && learner && drawerOpen}
pairing={pair.loading}
onClose={() => {
setMentor(null);
setLearner(null);
setDrawerOpen(false);
if (!pair.loading) {
setDrawerOpen(false);
}
}}
onPair={(mentor, learner) => {
console.log('pair', mentor.full_name, learner.full_name);
setMentor(null);
setLearner(null);
setDrawerOpen(false);
refetchParticipants();
if (!pair.loading) {
postPairing({
data: { mentor: mentor.id, learner: learner.id },
}).then(() => {
// TODO: snackbar?
setDrawerOpen(false);
setMentor(null);
setLearner(null);
refetchParticipants();
});
}
}} />
</Loading>
</Fragment>

Просмотреть файл

@ -10,6 +10,10 @@
<!-- React will load here -->
</div>
</body>
{% csrf_token %}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>
{% load static %}
<script src="{% static "frontend/main.js" %}"></script>
</html>

24
mentoring/pairing/rest.py Normal file
Просмотреть файл

@ -0,0 +1,24 @@
from rest_framework import serializers, viewsets, permissions, mixins
from .models import Pair
from ..participants.models import Participant
class PairSerializer(serializers.HyperlinkedModelSerializer):
mentor = serializers.PrimaryKeyRelatedField(
queryset=Participant.objects.all().filter(role=Participant.MENTOR))
learner = serializers.PrimaryKeyRelatedField(
queryset=Participant.objects.all().filter(role=Participant.LEARNER))
class Meta:
model = Pair
fields = [
'mentor',
'learner',
]
# ViewSets define the view behavior.
class PairViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet):
permission_classes = [permissions.IsAuthenticated]
queryset = Pair.objects.all()
serializer_class = PairSerializer

Просмотреть файл

@ -1,6 +1,8 @@
from rest_framework import serializers, viewsets, routers
from .participants.rest import ParticipantViewSet
from .pairing.rest import PairViewSet
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'participants', ParticipantViewSet)
router.register(r'pairs', PairViewSet)