Commit 05b99447 authored by Joseph Weston's avatar Joseph Weston

Merge commit 'a2fa8' into dualstack

parents a6bd2b76 a2fa8377
This diff is collapsed.
import React from 'react';
import * as api from '../../api';
import IDBlock from './IDBlock.jsx';
const BackButton = (props) => (
<button className="button is-light is-fullwidth" onClick={props.onClick}>
<span className="icon is-small">
<i className="fa fa-chevron-left"></i>
</span>
<span>search</span>
</button>
)
const SaveButton = (props) => (
<button className="button is-primary is-fullwidth" disabled={props.disabled} onClick={props.onClick}>
<span className="icon is-small">
<i className="fa fa-floppy-o"></i>
</span>
<span>save</span>
</button>
)
const UploadButton = (props) => (
<button className="button is-link is-fullwidth" onClick={props.onClick}>
<span className="icon is-small">
<i className="fa fa-upload"></i>
</span>
<span>upload</span>
</button>
)
class EditPanel extends React.Component {
state = {
id: "",
firstName: "",
lastName: "",
email: ""
}
componentWillMount = () => {
if (this.props.editStud){
const stud = this.props.editStud;
this.setState({
id: stud.id,
firstName: stud.firstName,
lastName: stud.lastName,
email: stud.email
})
}
}
changeFirstName = (event) => {
this.setState({
firstName: event.target.value
})
}
changeLastName = (event) => {
this.setState({
lastName: event.target.value
})
}
changeMail = (event) => {
this.setState({
email: event.target.value
})
}
setID = (id, student) => {
this.setState({
id: id
})
if (student) {
this.setState({
firstName: student.firstName,
lastName: student.lastName,
email: student.email
})
}
}
saveStudent = () => {
api.put('students', {
studentID: this.state.id,
firstName: this.state.firstName,
lastName: this.state.lastName,
email: this.state.email
})
.then((stud) => {
this.setState({
id: "",
firstName: "",
lastName: "",
email: ""
})
this.idblock.clear();
})
}
render() {
const empty = !(this.state.id + this.state.firstName + this.state.lastName + this.state.email);
const full = this.state.id && this.state.firstName && this.state.lastName;
return (
<nav className="panel">
<p className="panel-heading">
Manage students
</p>
<IDBlock setID={this.setID} editStud={this.state.id} ref={(id) => { this.idblock = id; }}/>
<div className="panel-block">
<div className="field">
<label className="label">Name</label>
<div className="control has-icons-left">
<input className="input" type="email" placeholder="First name"
value={this.state.firstName} onChange={this.changeFirstName} />
<span className="icon is-small is-left">
<i className="fa fa-quote-left"></i>
</span>
</div>
</div>
</div>
<div className="panel-block">
<div className="field">
<div className="control has-icons-left">
<input className="input" type="email" placeholder="Second name"
value={this.state.lastName} onChange={this.changeLastName} />
<span className="icon is-small is-left">
<i className="fa fa-quote-right"></i>
</span>
</div>
</div>
</div>
<div className="panel-block">
<div className="field">
<label className="label">Email</label>
<div className="control has-icons-left has-icons-right">
<input className="input" type="email" placeholder="Email input"
value={this.state.email} onChange={this.changeMail} />
<span className="icon is-small is-left">
<i className="fa fa-envelope"></i>
</span>
</div>
</div>
</div>
<div className="panel-block">
<BackButton onClick={this.props.toggleEdit} />
{empty ?
<UploadButton />
:
<SaveButton disabled={!full} onClick={this.saveStudent} />
}
</div>
</nav>
)
}
}
export default EditPanel;
import React from 'react';
import * as api from '../../api';
class IDBlock extends React.Component {
state = {
input: "",
editing: true,
short: true,
new: true,
}
componentDidMount = () => {
if (this.props.editStud) {
this.setState({
input: this.props.editStud,
editing: false,
short: false,
new: false
})
}
}
clear = () => {
this.setState({
input: "",
editing: true,
short: false,
new: false
})
}
statusIcon = () => {
if (this.state.editing) {
return null
} else {
const icon = this.state.short ? "exclamation-triangle" : "check";
return (
<span className="icon is-small is-right">
<i className={"fa fa-" + icon}></i>
</span>
)
}
}
helpText = () => {
if (this.state.editing) {
return null
}
let text;
let boldText = null;
if (this.state.short) {
text = "This entered number is ";
boldText = "too short"
} else {
text = "This student will be "
boldText = this.state.new ? "added" : "updated"
}
return (
<p className={"help" + this.color()}>{text}<b>{boldText}</b></p>
)
}
color = () => {
if (this.state.editing) {
return ""
} else {
return this.state.short ? " is-danger" : " is-success"
}
}
changeID = (event) => {
const patt = new RegExp(/^[1-9]\d*$|^()$/);
if (patt.test(event.target.value)) {
this.setState({
input: event.target.value
})
}
}
blur = (event) => {
const id = parseInt(event.target.value);
if (id >= 1000000) {
api.get('students/' + id)
.then(stud => {
this.setState({
editing: false,
short: false,
new: false
})
this.props.setID(id, stud);
})
.catch(res => {
console.log('If your browser just gave a 404 error, that is normal - do not worry!');
this.setState({
editing: false,
short: false,
new: true
})
this.props.setID(id);
})
} else {
this.setState({
editing: false,
short: true,
})
}
}
focus = () => {
this.setState({
editing: true
})
}
render() {
return (
<div className="panel-block">
<div className="field">
<label className="label">Student number</label>
<div className="control has-icons-left has-icons-right">
<input className={"input" + this.color()} type="text" maxLength="7" placeholder="Student number"
value={this.state.input} onChange={this.changeID}
onBlur={this.blur} onFocus={this.focus} />
<span className="icon is-small is-left">
<i className="fa fa-user"></i>
</span>
{this.statusIcon()}
</div>
{this.helpText()}
</div>
</div>
)
}
}
export default IDBlock;
\ No newline at end of file
import React from 'react';
import Fuse from 'fuse.js';
import * as api from '../../api';
import StudentPanelBlock from './StudentPanelBlock.jsx';
class SearchPanel extends React.Component {
students = [
{
id: 0,
firstName: "",
lastName: "",
email: ""
}
];
state= {
input: '',
selected: 0,
result: []
}
componentDidMount = () => {
api.get('students')
.then(students => {
this.students = students;
this.listMatchedStudent();
})
.catch(err => {
alert('failed to get students (see javascript console for details)')
console.error('failed to get students:', err)
throw err
})
}
search = (event) => {
const options = {
shouldSort: true,
threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
"id",
"firstName",
"lastName"
]
};
const fuse = new Fuse(this.students, options);
const result = fuse.search(event.target.value).slice(0, 10);
this.setState({
input: event.target.value,
selected: 0,
result: result
})
}
specialKey = (event) => {
if (event.keyCode === 38 || event.keyCode === 40) {
event.preventDefault();
let sel = this.state.selected;
if (event.keyCode == 38 && sel > 0) sel--;
if (event.keyCode == 40 && sel < this.state.result.length - 1) sel++;
this.setState({
...this.state,
selected: sel
})
}
if (event.keyCode === 27) this.searchInput.blur();
if (event.keyCode === 13) {
const stud = this.state.result[this.state.selected];
if (!stud) return;
this.props.matchStudent(stud.id);
}
}
selectStudent = (event) => {
if (event.target.selected) {
this.props.matchStudent(this.state.result[this.state.selected].id);
} else {
const index = this.state.result.findIndex(result => result.id == event.target.id);
this.setState({
...this.state,
selected: index
})
}
}
listMatchedStudent = () => {
const studIndex = this.students.findIndex(stud =>
stud.id === this.props.studentID);
const stud = studIndex > -1 ? [this.students[studIndex]] : [];
this.setState({
input: '',
selected: 0,
result: stud
})
if (!this.props.validated) {
this.searchInput.focus();
}
}
render() {
return (
<nav className="panel">
<p className="panel-heading">
Search students
</p>
<div className="panel-block">
<p className="control has-icons-left">
<input className="input" type="text"
ref={(input) => { this.searchInput = input; }}
value={this.state.input} onChange={this.search} onKeyDown={this.specialKey} />
<span className="icon is-left">
<i className="fa fa-search"></i>
</span>
</p>
</div>
{this.state.result.map((student, index) =>
<StudentPanelBlock key={student.id} student={student}
selected={index === this.state.selected}
matched={student.id === this.props.studentID && this.props.validated}
selectStudent={this.selectStudent} editStudent={this.props.toggleEdit}/>
)}
<div className="panel-block is-hidden-mobile">
<button className="button is-link is-outlined is-fullwidth" onClick={this.props.toggleEdit}>
<span className="icon is-small">
<i className="fa fa-user-plus"></i>
</span>
<span>add students</span>
</button>
</div>
</nav>
)
}
}
export default SearchPanel;
\ No newline at end of file
......@@ -23,9 +23,9 @@ const StudentPanelBlock = (props) => {
<div className={"panel-block" + (props.selected ? " is-info" : " is-hidden")}
key="info" style={{ backgroundColor: '#dbdbdb' }}>
<span className="panel-icon">
<a className="panel-icon" onClick={() => props.editStudent(props.student)}>
<i className="fa fa-database"></i>
</span>
</a>
{props.student.id}&emsp;
<span className="panel-icon">
<i className="fa fa-check"></i>
......
......@@ -23,7 +23,7 @@ api.add_resource(Graders, '/graders')
api.add_resource(Exams, '/exams')
api.add_resource(ExamConfig, '/exams/<int:exam_id>')
api.add_resource(Pdfs, '/pdfs/<int:exam_id>')
api.add_resource(Students, '/students')
api.add_resource(Students, '/students', '/students/<int:student_id>')
api.add_resource(Submissions,
'/submissions/<int:exam_id>',
'/submissions/<int:exam_id>/<int:submission_id>')
......
This diff is collapsed.
from flask_restful import Resource
from flask_restful import Resource, reqparse
from pony import orm
from ..models import Student
......@@ -7,17 +7,37 @@ class Students(Resource):
"""Getting a list of students."""
@orm.db_session
def get(self):
def get(self, student_id=None):
"""get all students for the course.
Parameters
----------
student_id : int, optional
The ID of the student, often the studentnumber but mainly used as unique identifier
Returns
-------
list of:
If a valid 'student_id' is provided the single instance of the student will be returned:
id: int
first_name: str
last_name: str
email: str
email: str
If no student_id is provided the entire list of students will be returned.
"""
if student_id is not None:
s = Student.get(id=student_id)
if not s:
raise orm.core.ObjectNotFound(Student)
return {
'id': s.id,
'firstName': s.first_name,
'lastName': s.last_name,
'email': s.email,
}
return [
{
'id': s.id,
......@@ -27,3 +47,58 @@ class Students(Resource):
}
for s in Student.select()
]
put_parser = reqparse.RequestParser()
put_parser.add_argument('studentID', type=int, required=True)
put_parser.add_argument('firstName', type=str, required=True)
put_parser.add_argument('lastName', type=str, required=True)
put_parser.add_argument('email', type=str, required=False)
@orm.db_session
def put(self, student_id=None):
"""Insert or update an existing student
Expects a json payload in the format::
{
"studentID": int,
"firstName": str,
"lastName": str,
"email": str OR null - this value is optional and may be empty, but must be unique
}
Parameters
----------
None. 'student_id' will be ignored.
Returns
-------
instance of student in JSON format:
studentID: int
firstName: str
lastName: str
email: str OR null
"""
args = self.put_parser.parse_args()
student = Student.get(id=args.studentID)
if not student:
student = Student(id = args.studentID,
first_name = args.firstName,
last_name = args.lastName,
email = args.email or None)
else:
student.set(id = args.studentID,
first_name = args.firstName,
last_name = args.lastName,
email = args.email or None)
orm.commit()
return {
'studentID': student.id,
"firstName": student.first_name,
"lastName": student.last_name,
"email": student.email
}
\ No newline at end of file
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