Commit 248562f2 authored by Adrià Labay's avatar Adrià Labay
Browse files

move exam layout description to the frontend

parent 239c2631
......@@ -7,33 +7,47 @@ import * as api from '../api.jsx'
import Hero from '../components/Hero.jsx'
import DropzoneContent from '../components/DropzoneContent.jsx'
const LAYOUTS = [
{
name: 'Templated',
value: 'templated',
acceptsPDF: true,
description: 'This is the default type, specially made for presencial exams. ' +
'In this mode, the pdf you upload is used as a template to create unique copies ' +
'where students can solve the exam. You can create open answer and multiple choice ' +
'problems, Zesje will take care of cropping the images from scaned PDFs with the solutions ' +
'as well as detecting blank answers and grading multiple choice questions automatically.'
},
{
name: 'Unstructured',
value: 'unstructured',
acceptsPDF: false,
description: 'Image based exam, this is specially made for take-home or virtual exam. ' +
'It is not based in any PDF, the scans can be images, pdfs or zipfiles made by students. ' +
'This flexibily comes at a cost, in this mode the creation of multiple choice questions ' +
'and autograding is not available.'
}
]
class Exams extends React.Component {
state = {
pdf: null,
previewPageCount: 0,
examName: '',
layouts: null,
selectedLayout: null
selectedLayout: LAYOUTS[0]
};
componentDidMount = () => {
api.get('/exams/layouts')
.then(layouts => {
this.setState({ layouts: layouts, selectedLayout: layouts[0] })
})
}
onChangeLayout = (event) => {
const newtype = this.state.layouts[event.target.value]
if (!newtype.acceptsPDF) {
const newLayout = LAYOUTS[event.target.value]
if (!newLayout.acceptsPDF) {
this.setState({
selectedLayout: newtype,
selectedLayout: newLayout,
pdf: null,
previewPageCount: 0
})
} else {
this.setState({
selectedLayout: newtype
selectedLayout: newLayout
})
}
}
......@@ -121,9 +135,9 @@ class Exams extends React.Component {
<div className='control'>
<div className='select'>
<select onChange={this.onChangeLayout}>
{this.state.layouts !== null ? this.state.layouts.map((layout, index) => {
{LAYOUTS.map((layout, index) => {
return <option key={`key_${index}`} value={index}>{layout.name}</option>
}) : null}
})}
</select>
</div>
</div>
......
......@@ -41,7 +41,7 @@ class Exams extends React.Component {
}
renderExamContent = () => {
const layout = this.state.exam.layout.value
const layout = this.state.exam.layout
const commonProps = {
examID: this.state.exam.id,
examName: this.state.exam.name,
......
......@@ -29,7 +29,7 @@ class Grade extends React.Component {
const partialState = {
submissions: metadata.submissions,
problems: metadata.problems,
isUnstructured: metadata.layout.value === 'unstructured',
isUnstructured: metadata.layout === 'unstructured',
examID: this.props.examID,
gradeAnonymous: metadata.gradeAnonymous
}
......
......@@ -35,7 +35,7 @@ def test_add_templated_exam(datadir, test_client):
assert len(data) == 1
assert data[0]['layout']['value'] == ExamLayout.templated.name
assert data[0]['layout'] == ExamLayout.templated.name
def test_add_templated_exam_without_pdf(datadir, test_client):
......@@ -55,7 +55,7 @@ def test_add_unstructured_exam(test_client):
assert len(data) == 1
assert data[0]['layout']['value'] == ExamLayout.unstructured.name
assert data[0]['layout'] == ExamLayout.unstructured.name
assert data[0]['finalized']
......@@ -157,16 +157,6 @@ def test_get_exams(test_client, no_with_subs, no_without_subs):
assert exams[exam_name] == i
def test_exam_types(test_client):
response = test_client.get('/api/exams/layouts')
assert response.status_code == 200
data = json.loads(response.data)
assert len(list(ExamLayout)) == len(data)
@pytest.mark.parametrize(
'finalized, status_code',
[(False, 200), (True, 409)],
......
......@@ -2,7 +2,7 @@ from flask import Blueprint
from flask_restful import Api
from .graders import Graders
from .exams import Exams, ExamSource, ExamGeneratedPdfs, ExamPreview, ExamLayouts
from .exams import Exams, ExamSource, ExamGeneratedPdfs, ExamPreview
from .scans import Scans
from .students import Students
from .copies import Copies, MissingPages
......@@ -28,7 +28,6 @@ api.add_resource(Exams, '/exams', '/exams/<int:exam_id>', '/exams/<int:exam_id>/
api.add_resource(ExamSource, '/exams/<int:exam_id>/source_pdf')
api.add_resource(ExamGeneratedPdfs, '/exams/<int:exam_id>/generated_pdfs')
api.add_resource(ExamPreview, '/exams/<int:exam_id>/preview')
api.add_resource(ExamLayouts, '/exams/layouts')
api.add_resource(Scans, '/scans/<int:exam_id>')
api.add_resource(Students, '/students', '/students/<int:student_id>')
api.add_resource(Copies,
......
......@@ -89,7 +89,7 @@ class Exams(Resource):
{
'id': ex.id,
'name': ex.name,
'layout': layout_to_data(ex.layout),
'layout': ex.layout.name,
'submissions': sub_count[ex.id] if ex.id in sub_count else 0,
'finalized': ex.finalized
}
......@@ -150,7 +150,7 @@ class Exams(Resource):
],
'finalized': exam.finalized,
'gradeAnonymous': exam.grade_anonymous,
'layout': layout_to_data(exam.layout)
'layout': exam.layout.name
}
def _get_single_metadata(self, exam_id, shuffle_seed):
......@@ -175,7 +175,7 @@ class Exams(Resource):
return {
'exam_id': exam.id,
'layout': layout_to_data(exam.layout),
'layout': exam.layout.name,
'submissions': [
{
'id': sub.id,
......@@ -478,59 +478,3 @@ class ExamPreview(Resource):
output_file,
cache_timeout=0,
mimetype='application/pdf')
def layout_to_data(layout):
"""Returns a description for the given exam type.
Parameters
----------
layout : int | ExamLayout
the type to explain
Returns
-------
dict : the description
`name` : str
human readable name that clearly distinguishes all types,
`value` : int
internal value to be passed to the requests (also unique),
`acceptsPDF` : bool
whether this layout is based on a PDF from that must be passed from the user,
`description` : str
text with a brief explanation of the layout.
"""
if layout == ExamLayout.templated:
return {
'name': 'Templated',
'value': ExamLayout.templated.name,
'acceptsPDF': True,
'description': 'This is the default type, specially made for presencial exams. '
'In this mode, the pdf you upload is used as a template to create unique copies '
'where students can solve the exam. You can create open answer and multiple choice '
'problems, Zesje will take care of cropping the images from scaned PDFs with the solutions '
'as well as detecting blank answers and grading multiple choice questions automatically.'
}
elif layout == ExamLayout.unstructured:
return {
'name': 'Unstructured',
'value': ExamLayout.unstructured.name,
'acceptsPDF': False,
'description': 'Image based exam, this is specially made for take-home or virtual exam. '
'It is not based in any PDF, the scans can be images, pdfs or zipfiles made by students. '
'This flexibily comes at a cost, in this mode the creation of multiple choice questions '
'and autograding is not available.'
}
class ExamLayouts(Resource):
"""Resource to request all the possible layouts to create an exam."""
def get(self):
"""Returns a list of all possible types ordered by `ExamLayout.value`
Returns
-------
list of dict : see `layout_to_data`.
"""
return [layout_to_data(layout) for layout in list(ExamLayout)]
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