Commit ad4f525b authored by Anton Akhmerov's avatar Anton Akhmerov
Browse files

Merge branch 'localise-email-view' into 'master'

Localise data in email view

See merge request zesje/zesje!303
parents a464b065 f46777e7
......@@ -154,8 +154,8 @@ class App extends React.Component {
? <Overview examID={match.params.examID} />
: <Fail message='No exams uploaded. Please do not bookmark URLs' />
)} />
<Route path='/email' render={() => (
exam.submissions.length ? <Email exam={exam} /> : <Fail message='No exams uploaded. Please do not bookmark URLs' />
<Route path='/email/:examID' render={({ match }) => (
exam.submissions.length ? <Email examID={match.params.examID} /> : <Fail message='No exams uploaded. Please do not bookmark URLs' />
)} />
<Route path='/graders' render={() =>
<Graders updateGraderList={this.menu.current ? this.menu.current.updateGraderList : null} />} />
......
......@@ -211,7 +211,7 @@ class NavBar extends React.Component {
text='Overview'
predicate={[predicateExamNotFinalized, predicateSubmissionsEmpty]} />
<TooltipLink
to='/email'
to={'/email/' + this.props.exam.id}
text='Email'
predicate={[predicateExamNotFinalized, predicateSubmissionsEmpty]} />
<ExportDropdown className='navbar-item' disabled={predicateSubmissionsEmpty[0]} exam={this.props.exam} />
......
......@@ -11,16 +11,40 @@ import TemplateEditor from './email/TemplateEditor.jsx'
class Email extends React.Component {
state = {
template: null,
selectedStudent: null
selectedStudent: null,
error: null
}
componentWillMount () {
this.loadTemplate()
}
componentDidUpdate = (prevProps, prevState) => {
if (this.props.examID !== prevProps.examID) {
this.loadTemplate()
}
}
loadTemplate = () => {
api
.get(`templates/${this.props.exam.id}`)
.get(`templates/${this.props.examID}`)
.then(template => this.setState({ template }))
.catch(err => {
console.log(err)
err.json().then(e => {
if (e.status === 404) {
this.setState({error: e.message})
}
})
})
}
render () {
// This should happen when the exam does not exist.
if (this.state.error) {
return <Hero title='Oops!' subtitle={this.state.error} />
}
return (
<React.Fragment>
<Hero title='Email' subtitle='So the students get their feedback' />
......@@ -29,11 +53,11 @@ class Email extends React.Component {
<div className='columns is-tablet'>
<div className='column is-3-tablet'>
<TemplateControls
exam={this.props.exam}
examID={this.props.examID}
template={this.state.template}
/>
<StudentControls
exam={this.props.exam}
examID={this.props.examID}
selectedStudent={this.state.selectedStudent}
setStudent={student => {
this.setState({
......@@ -43,13 +67,13 @@ class Email extends React.Component {
/>
<EmailControls
template={this.state.template}
exam={this.props.exam}
examID={this.props.examID}
student={this.state.selectedStudent}
/>
</div>
<TemplateEditor
exam={this.props.exam}
examID={this.props.examID}
student={this.state.selectedStudent}
template={this.state.template}
onTemplateChange={template => {
......
......@@ -97,7 +97,7 @@ class EmailIndividualControls extends React.Component {
this.setState({ sending: true })
try {
await api.post(
`email/${this.props.exam.id}/${this.props.student.id}`,
`email/${this.props.examID}/${this.props.student.id}`,
{
template: this.props.template,
attach: this.state.attachPDF,
......@@ -159,29 +159,29 @@ class EmailEveryoneControls extends React.Component {
attachPDF: true,
sending: false
}
disableAnonymousMode = () => {
console.log(this.props.exam)
if (this.props.exam.gradeAnonymous) {
api.put(`exams/${this.props.exam.id}`, {grade_anonymous: false}).then(
api.put(`exams/${this.props.examID}`, {grade_anonymous: false}).then(resp => {
if (resp.changed) {
Notification.info(
<div>
<p>
'Turned off anonymous grading for this exam'
</p>
<a onClick={() => api.put(`exams/${this.props.exam.id}`, {grade_anonymous: true})}>
<a onClick={() => api.put(`exams/${this.props.examID}`, {grade_anonymous: true})}>
(undo)
</a>
</div>
)
)
}
}
})
}
sendEmail = async () => {
this.setState({ sending: true })
try {
const response = await api.post(
`email/${this.props.exam.id}`,
`email/${this.props.examID}`,
{
template: this.props.template,
attach: this.state.attachPDF
......@@ -263,7 +263,7 @@ class EmailControls extends React.Component {
name: 'Individual',
panel: (
<EmailIndividualControls
exam={this.props.exam}
examID={this.props.examID}
student={this.props.student}
template={this.props.template}
/>
......@@ -273,7 +273,7 @@ class EmailControls extends React.Component {
name: 'Everyone',
panel: (
<EmailEveryoneControls
exam={this.props.exam}
examID={this.props.examID}
template={this.props.template}
/>
)
......
import React from 'react'
import Notification from 'react-bulma-notification'
import SearchBox from '../../components/SearchBox.jsx'
import * as api from '../../api.jsx'
function withoutDuplicates (items, keyFn = (x => x)) {
let seenKeys = new Set()
......@@ -20,13 +22,32 @@ class StudentControls extends React.Component {
}
componentWillMount () {
// Need to de-duplicate, as some students
const students = withoutDuplicates(
this.props.exam.submissions.map(s => s.student).filter(s => s !== null),
student => student.id
)
this.setState({ students })
this.props.setStudent(students[0] || null) // in case 'students' is empty
this.updateStudents()
}
componentDidUpdate = (prevProps, prevState) => {
if (this.props.examID !== prevProps.examID) {
this.updateStudents()
}
}
updateStudents = () => {
api.get('submissions/' + this.props.examID)
.then(submissions => {
// Need to de-duplicate, as some students
const students = withoutDuplicates(
submissions.map(s => s.student).filter(s => s !== null),
student => student.id
)
this.setState({ students })
this.props.setStudent(students[0] || null) // in case 'students' is empty
})
.catch(err => {
console.log(err)
this.setState({students: []})
this.props.setStudent(null)
Notification.error('Failed to load students.')
})
}
render () {
......
......@@ -30,7 +30,7 @@ class TemplateControls extends React.Component {
saveTemplate = async () => {
try {
await api.put(
`templates/${this.props.exam.id}`,
`templates/${this.props.examID}`,
{ template: this.props.template }
)
Notification.success('Template saved')
......
......@@ -10,7 +10,7 @@ const renderTemplate = async (props) => {
}
return api
.post(
`templates/rendered/${props.exam.id}/${props.student.id}`,
`templates/rendered/${props.examID}/${props.student.id}`,
{ template: props.template }
)
}
......
......@@ -88,6 +88,9 @@ class EmailTemplate(Resource):
def get(self, exam_id):
"""Get an email template for a given exam."""
if not Exam.query.get(exam_id):
return dict(status=404, message='Exam does not exist.'), 404
try:
with open(template_path(exam_id)) as f:
return f.read()
......@@ -101,6 +104,9 @@ class EmailTemplate(Resource):
def put(self, exam_id):
"""Update an email template."""
if not Exam.query.get(exam_id):
return dict(status=404, message='Exam does not exist.'), 404
email_template = self.put_parser.parse_args().template
try:
Template(email_template)
......
......@@ -280,9 +280,10 @@ class Exams(Resource):
return dict(status=403, message='Exam can not be unfinalized'), 403
if args['grade_anonymous'] is not None:
changed = exam.grade_anonymous != args['grade_anonymous']
exam.grade_anonymous = args['grade_anonymous']
db.session.commit()
return dict(status=200, message="ok"), 200
return dict(status=200, message="ok", changed=changed), 200
return dict(status=400, message='One of finalized or anonymous must be present'), 400
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment