зеркало из https://github.com/mozilla/mentoring.git
Support adding/updating enrollment
This completes the enrollment flow.
This commit is contained in:
Родитель
e3a68dbe75
Коммит
9f11ae3ebb
|
@ -44,8 +44,8 @@ export default function AvailabilitySelector({ timeAvailability, onChange }) {
|
|||
|
||||
// if null or invalid, treat as 9-5 local time
|
||||
if (timeAvailability === null || timeAvailability.length !== 24) {
|
||||
timeAvailability = localToUtc('NNNNNNNNNYYYYYYYYNNNNNNN');
|
||||
onChange(timeAvailability);
|
||||
console.error(`Invalid timeAvailability value ${timeAvailability}; substituting default`);
|
||||
timeAvailability = AvailabilitySelector.default();
|
||||
}
|
||||
|
||||
// translate the availability to local time by rotating the string
|
||||
|
@ -118,3 +118,7 @@ AvailabilitySelector.propTypes = {
|
|||
onChange: propTypes.func.isRequired,
|
||||
};
|
||||
|
||||
// Get a default value for this component (which depends on the timezon)
|
||||
AvailabilitySelector.default = () => {
|
||||
return localToUtc('NNNNNNNNNYYYYYYYYNNNNNNN');
|
||||
}
|
||||
|
|
|
@ -18,9 +18,29 @@ export function useParticipantByEmail(email) {
|
|||
return [{ loading, error, participant }];
|
||||
}
|
||||
|
||||
// Return [{loading, error}, postParticipant] where postParticipant() will
|
||||
// asynchronously post that partcipant to the backend. If `participant.id` is
|
||||
// set, then the participant will be updated; otherwise, it will be created.
|
||||
export function usePostParticipant(participant) {
|
||||
const {id, ...data} = participant;
|
||||
return useAxios({
|
||||
...(id === undefined ? {
|
||||
// create a participant
|
||||
url: '/api/participants',
|
||||
method: 'POST',
|
||||
} : {
|
||||
// update an existing participant
|
||||
url: `/api/participants/${id}`,
|
||||
method: 'PUT',
|
||||
}),
|
||||
data,
|
||||
headers: { 'X-CSRFToken': MENTORING_SETTINGS.csrftoken },
|
||||
}, { manual: true });
|
||||
}
|
||||
|
||||
// a propTypes shape describing the data expected from the API
|
||||
export const participantType = propTypes.shape({
|
||||
id: propTypes.number.isRequired,
|
||||
id: propTypes.number,
|
||||
full_name: propTypes.string.isRequired,
|
||||
email: propTypes.string.isRequired,
|
||||
is_mentor: propTypes.bool.isRequired,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { useParticipants, useParticipantByEmail } from './participants';
|
||||
import { renderHook, act } from '@testing-library/react-hooks'
|
||||
import { useParticipants, useParticipantByEmail, usePostParticipant } from './participants';
|
||||
import { api } from '../../test/helper';
|
||||
|
||||
jest.mock('axios-hooks');
|
||||
|
@ -46,3 +46,37 @@ describe('useParticipantByEmail', () => {
|
|||
expect(result.current[0].participant).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('usePostParticipant', () => {
|
||||
test('create', async () => {
|
||||
let posted;
|
||||
api.mock(api.onCreateParticipant(arg => { posted = arg; }));
|
||||
const { result } = renderHook(() => usePostParticipant({
|
||||
full_name: 'Averill',
|
||||
}));
|
||||
const [participant, postParticipant] = result.current;
|
||||
|
||||
// nothing has happened yet
|
||||
expect(participant.loading).toBeFalsy();
|
||||
expect(posted).toBeFalsy();
|
||||
|
||||
act(() => postParticipant());
|
||||
});
|
||||
|
||||
test('update', async () => {
|
||||
let posted;
|
||||
api.mock(api.onUpdateParticipant(13, arg => { posted = arg; }));
|
||||
const { result } = renderHook(() => usePostParticipant({
|
||||
id: 13,
|
||||
full_name: 'Averill',
|
||||
}));
|
||||
const [participant, postParticipant] = result.current;
|
||||
|
||||
// nothing has happened yet
|
||||
expect(participant.loading).toBeFalsy();
|
||||
expect(posted).toBeFalsy();
|
||||
|
||||
act(() => postParticipant());
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel';
|
|||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import InterestsControl from '../../components/InterestsControl';
|
||||
import AvailabilitySelector from '../../components/AvailabilitySelector';
|
||||
import { participantType } from '../../data/participants';
|
||||
|
@ -24,6 +25,16 @@ const useStyles = makeStyles(theme => ({
|
|||
maxWidth: "80em",
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
buttonWrapper: {
|
||||
position: 'relative',
|
||||
},
|
||||
buttonProgress: {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: -12,
|
||||
marginLeft: -12,
|
||||
},
|
||||
}));
|
||||
|
||||
const MENTORING_MANA_PAGE = "https://mana.mozilla.org/wiki/pages/viewpage.action?spaceKey=PR&title=Mozilla+Mentoring+Program";
|
||||
|
@ -66,7 +77,7 @@ InfoLink.propTypes = {
|
|||
children: propTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default function EnrollmentForm({ participant, update, onParticipantChange, onSubmit }) {
|
||||
export default function EnrollmentForm({ participant, update, onParticipantChange, onSubmit, submitLoading }) {
|
||||
const classes = useStyles();
|
||||
|
||||
const mentor = participant.is_mentor;
|
||||
|
@ -241,6 +252,7 @@ export default function EnrollmentForm({ participant, update, onParticipantChang
|
|||
{...textFieldProps('track_change')}
|
||||
label="Track Change"
|
||||
disabled={!learner}
|
||||
required={learner}
|
||||
select
|
||||
helperText="Are you considering change track (such as between IC and management)?" >
|
||||
{menuItems(TRACK_CHANGE_INTEREST)}
|
||||
|
@ -276,7 +288,12 @@ export default function EnrollmentForm({ participant, update, onParticipantChang
|
|||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button type="submit" color="primary">{either ? (update ? "Update" : "Enroll") : "Leave"}</Button>
|
||||
<div className={classes.buttonWrapper}>
|
||||
<Button disabled={submitLoading} type="submit" color="primary">
|
||||
{either ? (update ? "Update" : "Enroll") : "Leave"}
|
||||
</Button>
|
||||
{submitLoading && <CircularProgress size={24} className={classes.buttonProgress} />}
|
||||
</div>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</form>
|
||||
|
@ -289,4 +306,5 @@ EnrollmentForm.propTypes = {
|
|||
update: propTypes.bool,
|
||||
onParticipantChange: propTypes.func.isRequired,
|
||||
onSubmit: propTypes.func.isRequired,
|
||||
submitLoading: propTypes.bool.isRequired,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
|
||||
export default function PostedDialog() {
|
||||
const history = useHistory();
|
||||
const goHome = () => history.push("/");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog
|
||||
open
|
||||
onClose={goHome}
|
||||
aria-labelledby="posted-title"
|
||||
aria-describedby="posted-desc">
|
||||
<>
|
||||
<DialogTitle id="posted-title">Responses Submitted</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="posted-desc">
|
||||
Thank you!
|
||||
Your responses have been submitted.
|
||||
You can expect to hear from the mentoring committee soon.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={goHome} color="primary">OK</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -3,9 +3,11 @@ import propTypes from 'prop-types';
|
|||
import Helmet from 'react-helmet';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { useParticipantByEmail } from '../../data/participants';
|
||||
import AvailabilitySelector from '../../components/AvailabilitySelector';
|
||||
import Loading from '../../components/Loading';
|
||||
import EnrollmentForm from './EnrollmentForm';
|
||||
import { participantType } from '../../data/participants';
|
||||
import PostedDialog from './PostedDialog';
|
||||
import { participantType, usePostParticipant } from '../../data/participants';
|
||||
|
||||
export default function Enrollment({ role, update }) {
|
||||
const { user } = MENTORING_SETTINGS;
|
||||
|
@ -13,20 +15,20 @@ export default function Enrollment({ role, update }) {
|
|||
// load the participant's enrollment from the API, if present
|
||||
const [existing] = useParticipantByEmail(user.email);
|
||||
const initialParticipant = existing.participant ? existing.participant : {
|
||||
id: 0,
|
||||
// no `id` property, meaning a new participant
|
||||
full_name: `${user.first_name} ${user.last_name}`.trim(),
|
||||
email: user.email,
|
||||
manager: '',
|
||||
manager_email: '',
|
||||
is_mentor: role === 'mentor',
|
||||
is_learner: role === 'learner' || update, // default this to true for updates, just in case
|
||||
time_availability: 'NNNNNNNNNNNNNNNNNNNNNNNN',
|
||||
is_learner: role === 'learner' || Boolean(update),
|
||||
time_availability: AvailabilitySelector.default(),
|
||||
org: '',
|
||||
org_level: '',
|
||||
time_at_org_level: '',
|
||||
learner_interests: [],
|
||||
mentor_interests: [],
|
||||
track_change: '',
|
||||
track_change: 'maybe', // default for mentors, since the choice is disabled
|
||||
org_chart_distance: '',
|
||||
comments: '',
|
||||
};
|
||||
|
@ -47,26 +49,44 @@ Enrollment.propTypes = {
|
|||
// participant is loaded.
|
||||
function WithLoadedParticipant({ update, initialParticipant }) {
|
||||
const [participant, setParticipant] = useState(initialParticipant);
|
||||
const [{loading: postLoading, error: postError}, postParticipant] = usePostParticipant(participant);
|
||||
const [posted, setPosted] = useState(false);
|
||||
|
||||
const handleSubmit = event => {
|
||||
console.log(participant);
|
||||
postParticipant().then(
|
||||
() => setPosted(true),
|
||||
// errors are reported via postError, so ignore them here
|
||||
() => {});
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
if (postError) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Error</h2>
|
||||
<pre>{postError.toString()}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>Mozilla Mentorship Program - Enrollment</title>
|
||||
</Helmet>
|
||||
<Grid container justify="center">
|
||||
<Grid item>
|
||||
<EnrollmentForm
|
||||
update={update}
|
||||
participant={participant}
|
||||
onParticipantChange={setParticipant}
|
||||
onSubmit={handleSubmit} />
|
||||
{posted ? <PostedDialog /> : (
|
||||
<Grid container justify="center">
|
||||
<Grid item>
|
||||
<EnrollmentForm
|
||||
update={update}
|
||||
participant={participant}
|
||||
onParticipantChange={setParticipant}
|
||||
onSubmit={handleSubmit}
|
||||
submitLoading={postLoading}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -66,4 +66,24 @@ export const api = {
|
|||
implementation: [ { loading: false }, cb ],
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Call the given callback when a participant is posted (created)
|
||||
*/
|
||||
onCreateParticipant(cb) {
|
||||
return {
|
||||
config: { method: 'POST', url: '/api/participants' },
|
||||
implementation: [ { loading: false }, cb ],
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Call the given callback when a participant is PUT (updated).
|
||||
*/
|
||||
onUpdateParticipant(id, cb) {
|
||||
return {
|
||||
config: { method: 'PUT', url: `/api/participants/${id}` },
|
||||
implementation: [ { loading: false }, cb ],
|
||||
};
|
||||
},
|
||||
}
|
||||
|
|
|
@ -22,3 +22,11 @@ class PairSerializer(serializers.HyperlinkedModelSerializer):
|
|||
class PairViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Pair.objects.all()
|
||||
serializer_class = PairSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save()
|
||||
|
||||
# bump expiration for the participants involved
|
||||
for participant in (serializer.validated_data[k] for k in ['learner', 'mentor']):
|
||||
participant.bump_expiration()
|
||||
participant.save()
|
||||
|
|
|
@ -72,6 +72,10 @@ class PairTest(TestCase):
|
|||
self.assertEqual(pair.mentor.id, mentor.id)
|
||||
self.assertEqual(pair.learner.id, learner.id)
|
||||
|
||||
# check that expirations have been bumped
|
||||
self.assertGreater(pair.mentor.expires, datetime.datetime.now(pytz.UTC))
|
||||
self.assertGreater(pair.learner.expires, datetime.datetime.now(pytz.UTC))
|
||||
|
||||
def test_make_pair_rest_mentor_as_learner(self):
|
||||
learner, mentor = self.make_particips()
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.1.2 on 2021-02-08 20:37
|
||||
|
||||
from django.db import migrations, models
|
||||
from textwrap import dedent
|
||||
|
||||
from mentoring.participants.models import current_expiration
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participants', '0003_auto_20210125_1622'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='participant',
|
||||
name='expires',
|
||||
field=models.DateTimeField(
|
||||
default=current_expiration, help_text=dedent('''\
|
||||
The date that this information expires. This can be extended (such as when
|
||||
a pairing is made), and expiration is contingent on not being in a current
|
||||
pair. This field accomplishes the "lean data" practice of not keeping
|
||||
user information forever. ''')),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,8 @@
|
|||
import pytz
|
||||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from textwrap import dedent
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -18,6 +22,11 @@ def validate_interests(interests):
|
|||
raise ValidationError('interests must contain strings')
|
||||
|
||||
|
||||
def current_expiration():
|
||||
"""Return an appropriate expiration date for a participant updated today."""
|
||||
return datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=settings.DATA_RETENTION_DAYS)
|
||||
|
||||
|
||||
class Participant(models.Model):
|
||||
"""
|
||||
A Participant in the program.
|
||||
|
@ -26,11 +35,14 @@ class Participant(models.Model):
|
|||
def __str__(self):
|
||||
return f'{self.full_name}'
|
||||
|
||||
expires = models.DateTimeField(null=False, help_text=dedent('''\
|
||||
The date that this information expires. This can be extended (such as when
|
||||
a pairing is made), and expiration is contingent on not being in a current
|
||||
pair. This field accomplishes the "lean data" practice of not keeping
|
||||
user information forever. '''))
|
||||
expires = models.DateTimeField(
|
||||
null=False,
|
||||
default=current_expiration,
|
||||
help_text=dedent('''\
|
||||
The date that this information expires. This can be extended (such as when
|
||||
a pairing is made), and expiration is contingent on not being in a current
|
||||
pair. This field accomplishes the "lean data" practice of not keeping
|
||||
user information forever. '''))
|
||||
|
||||
email = models.EmailField(null=False, unique=True, help_text=dedent('''\
|
||||
The participant's work email address. This is used as a key. '''))
|
||||
|
@ -104,5 +116,10 @@ class Participant(models.Model):
|
|||
help_text=dedent('''Open comments from the participant's enrollment'''),
|
||||
)
|
||||
|
||||
def bump_expiration(self):
|
||||
"""Bump the expiration time for this participant, such as when making a substantive
|
||||
change to the participant's record."""
|
||||
self.expires = current_expiration()
|
||||
|
||||
class Meta:
|
||||
db_table = "participants"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from rest_framework import serializers, viewsets, permissions, decorators, status
|
||||
from rest_framework import serializers, viewsets, permissions, decorators, status, mixins, exceptions
|
||||
from rest_framework.response import Response
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from .models import Participant
|
||||
from .models import Participant, current_expiration
|
||||
|
||||
|
||||
class ParticipantSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
@ -28,12 +28,50 @@ class ParticipantSerializer(serializers.HyperlinkedModelSerializer):
|
|||
'comments',
|
||||
]
|
||||
|
||||
def validate(self, data):
|
||||
"""Require that the `email` property, if given, matches the user during deserialization"""
|
||||
if not self.context or "request" not in self.context:
|
||||
raise serializers.ValidationError("validation requires request context")
|
||||
request = self.context['request']
|
||||
|
||||
# ViewSets define the view behavior.
|
||||
class ParticipantViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
if 'email' in data and data['email'] != request.user.email:
|
||||
raise serializers.ValidationError({"email": "email field must match your own email"})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class IsParticipantOrAdminUser(permissions.BasePermission):
|
||||
"""
|
||||
Object-level permission to allow users to access their own participant record.
|
||||
"""
|
||||
|
||||
# note that this cannot be represented as IsParticipant | IsAdminUser, as the
|
||||
# IsAdminUser class is a view-level permission
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.user.is_staff:
|
||||
return True
|
||||
if request.user.is_anonymous:
|
||||
return False
|
||||
return obj.email == request.user.email
|
||||
|
||||
|
||||
class ParticipantViewSet(
|
||||
mixins.CreateModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Participant.objects.all()
|
||||
serializer_class = ParticipantSerializer
|
||||
|
||||
def get_permissions(self):
|
||||
# non-admin users cannot list anything..
|
||||
if self.action == 'list':
|
||||
permission_classes = [permissions.IsAdminUser]
|
||||
else:
|
||||
# ..but can read, create, and update (and by_email) their own user
|
||||
permission_classes = [IsParticipantOrAdminUser]
|
||||
return [permission() for permission in permission_classes]
|
||||
|
||||
@decorators.action(detail=False, url_path='by_email')
|
||||
def by_email(self, request, pk=None):
|
||||
"""Get a participant by their email address, using `?email=..`"""
|
||||
|
@ -41,5 +79,10 @@ class ParticipantViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
if email is None:
|
||||
return Response("Missing `email` query parameter", status=status.HTTP_400_BAD_REQUEST)
|
||||
particip = get_object_or_404(Participant, email=email)
|
||||
self.check_object_permissions(self.request, particip)
|
||||
data = self.serializer_class(particip).data
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
# on every update, bump the expiration time
|
||||
serializer.save(expires=current_expiration())
|
||||
|
|
|
@ -1,3 +1,204 @@
|
|||
from django.test import TestCase
|
||||
import pytz
|
||||
import datetime
|
||||
|
||||
# Create your tests here.
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from .models import Participant
|
||||
from .rest import ParticipantSerializer
|
||||
|
||||
|
||||
def make_particip(no_save=False):
|
||||
particip = Participant(
|
||||
expires=datetime.datetime.now(pytz.UTC),
|
||||
email='llearner@mozilla.com',
|
||||
is_learner=True,
|
||||
is_mentor=False,
|
||||
full_name='Logan Learner',
|
||||
manager='Mani Shur',
|
||||
manager_email='mshur@mozilla.com',
|
||||
time_availability='N' * 24,
|
||||
)
|
||||
if not no_save:
|
||||
particip.save()
|
||||
return particip
|
||||
|
||||
|
||||
def login(client, email, admin=False):
|
||||
if admin:
|
||||
user = User.objects.create_superuser('particip', email=email)
|
||||
else:
|
||||
user = User.objects.create_user('particip', email=email)
|
||||
user.save()
|
||||
client.force_authenticate(user=user)
|
||||
|
||||
|
||||
class ParticipantTestAnonumous(TestCase):
|
||||
|
||||
def test_api_list_anonymous(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
res = client.get('/api/participants')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
def test_api_get_anonymous(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
res = client.get(f'/api/participants/{particip.id}')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
def test_api_get_by_email_anonymous(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
res = client.get(f'/api/participants/by_email?email={particip.email}')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
|
||||
class ParticipantTestRegularUser(TestCase):
|
||||
|
||||
def test_api_list_user(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.get('/api/participants')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
def test_api_get_user_not_self(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, 'someone-else@mozilla.com')
|
||||
res = client.get(f'/api/participants/{particip.id}')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
def test_api_get_user_self(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.get(f'/api/participants/{particip.id}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.json()['email'], particip.email)
|
||||
|
||||
def test_api_get_user_by_email_not_self(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, 'someone-else@mozilla.com')
|
||||
res = client.get(f'/api/participants/by_email?email={particip.email}')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
def test_api_get_user_by_email_self(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.get(f'/api/participants/by_email?email={particip.email}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.json()['email'], particip.email)
|
||||
|
||||
def test_api_update_user_not_self(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, 'someone-else@mozilla.com')
|
||||
res = client.put(
|
||||
f'/api/participants/{particip.id}',
|
||||
{})
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
def test_api_update_user_self(self):
|
||||
particip = make_particip()
|
||||
particip_json = ParticipantSerializer(particip).data
|
||||
particip_json['full_name'] = 'UPDATED'
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.put(
|
||||
f'/api/participants/{particip.id}',
|
||||
particip_json,
|
||||
format='json')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
res = client.get(f'/api/participants/{particip.id}')
|
||||
self.assertEqual(res.json()['full_name'], 'UPDATED')
|
||||
|
||||
def test_api_partial_update_user_self(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.patch(
|
||||
f'/api/participants/{particip.id}',
|
||||
{'full_name': 'UPDATED'},
|
||||
format='json')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.json()["full_name"], "UPDATED")
|
||||
|
||||
def test_api_update_user_self_change_email(self):
|
||||
particip = make_particip()
|
||||
particip_json = ParticipantSerializer(particip).data
|
||||
particip_json['email'] = 'UPDATED@mozilla.com'
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.put(
|
||||
f'/api/participants/{particip.id}',
|
||||
particip_json,
|
||||
format='json')
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertEqual(res.json(), {"email": ["email field must match your own email"]})
|
||||
|
||||
def test_api_partial_update_user_self_change_email(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.patch(
|
||||
f'/api/participants/{particip.id}',
|
||||
{'email': 'UPDATED@mozilla.com'},
|
||||
format='json')
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertEqual(res.json(), {"email": ["email field must match your own email"]})
|
||||
|
||||
def test_api_create_user_self(self):
|
||||
particip = make_particip(no_save=True)
|
||||
particip_json = ParticipantSerializer(particip).data
|
||||
client = APIClient()
|
||||
login(client, particip.email)
|
||||
res = client.post(
|
||||
'/api/participants',
|
||||
particip_json,
|
||||
format='json')
|
||||
self.assertEqual(res.status_code, 201)
|
||||
self.assertEqual(res.json()['email'], particip.email)
|
||||
|
||||
def test_api_create_user_not_self(self):
|
||||
particip = make_particip(no_save=True)
|
||||
particip_json = ParticipantSerializer(particip).data
|
||||
client = APIClient()
|
||||
login(client, 'someone-else@mozilla.com')
|
||||
res = client.post(
|
||||
'/api/participants',
|
||||
particip_json,
|
||||
format='json')
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertEqual(res.json(), {"email": ["email field must match your own email"]})
|
||||
|
||||
|
||||
class ParticipantTestAdminUser(TestCase):
|
||||
|
||||
def test_api_list_admin(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email, admin=True)
|
||||
res = client.get('/api/participants')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual([r['email'] for r in res.json()], [particip.email])
|
||||
|
||||
def test_api_get_admin(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email, admin=True)
|
||||
res = client.get(f'/api/participants/{particip.id}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.json()['email'], particip.email)
|
||||
|
||||
def test_api_get_by_email_admin(self):
|
||||
particip = make_particip()
|
||||
client = APIClient()
|
||||
login(client, particip.email, admin=True)
|
||||
res = client.get(f'/api/participants/by_email?email={particip.email}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.json()['email'], particip.email)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
asgiref==3.2.10
|
||||
Django==3.1.2
|
||||
djangorestframework==3.12.1
|
||||
djangorestframework==3.12.2
|
||||
django-csp==3.7
|
||||
pytz==2020.1
|
||||
sqlparse==0.4.1
|
||||
|
|
Загрузка…
Ссылка в новой задаче