Skip to content
Snippets Groups Projects
Commit 88ef2d7a authored by Ruben Young On's avatar Ruben Young On
Browse files

Merge branch 'master' of https://gitlab.kwant-project.org/zesje/zesje into box-loc-db

parents af6792e4 e6415da2
No related branches found
No related tags found
1 merge request!5Add multiple choice checkbox location to database
Pipeline #17397 passed
......@@ -71,6 +71,18 @@ try the instructions listed
[here](https://alexvanderbist.com/posts/2018/fixing-imagick-error-unauthorized) as
a first resort.
### Database modifications
Zesje uses Flask-Migrate and Alembic for database versioning and migration. Flask-Migrate is an extension that handles SQLAlchemy database migrations for Flask applications using Alembic.
To change something in the database schema, simply add this change to `zesje/database.py`. After that run the following command to prepare a new migration:
yarn prepare-migration
This uses Flask-Migrate to make a new migration script in `migrations/versions` which needs to be reviewed and edited. Please suffix the name of this file with something distinctive and add a short description at the top of the file. To apply the database migration run:
yarn migrate:dev # (for the development database)
yarn migrate # (for the production database)
### Building and running the production version
......
......@@ -60,7 +60,7 @@ def upgrade():
op.create_table(
'exam',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('name', sa.Text(), nullable=False),
sa.Column('token', sa.String(length=12), nullable=True),
sa.Column('finalized', sa.Boolean(), server_default='f', nullable=True),
sa.PrimaryKeyConstraint('id'),
......@@ -69,22 +69,22 @@ def upgrade():
op.create_table(
'grader',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('name', sa.Text(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'student',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('first_name', sa.String(length=120), nullable=False),
sa.Column('last_name', sa.String(length=120), nullable=False),
sa.Column('email', sa.String(length=120), nullable=True),
sa.Column('first_name', sa.Text(), nullable=False),
sa.Column('last_name', sa.Text(), nullable=False),
sa.Column('email', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
op.create_table(
'widget',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=120), nullable=True),
sa.Column('name', sa.Text(), nullable=True),
sa.Column('x', sa.Integer(), nullable=False),
sa.Column('y', sa.Integer(), nullable=False),
sa.Column('type', sa.String(length=20), nullable=True),
......@@ -101,7 +101,7 @@ def upgrade():
op.create_table(
'problem',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('name', sa.Text(), nullable=False),
sa.Column('exam_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['exam_id'], ['exam.id'], ),
sa.PrimaryKeyConstraint('id')
......@@ -109,17 +109,17 @@ def upgrade():
op.create_table(
'scan',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('exam_id', sa.String(), nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('status', sa.String(length=120), nullable=False),
sa.Column('message', sa.String(length=120), nullable=True),
sa.Column('exam_id', sa.Integer(), nullable=False),
sa.Column('name', sa.Text(), nullable=False),
sa.Column('status', sa.Text(), nullable=False),
sa.Column('message', sa.Text(), nullable=True),
sa.ForeignKeyConstraint(['exam_id'], ['exam.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'submission',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('copy_number', sa.Integer(), nullable=True),
sa.Column('copy_number', sa.Integer(), nullable=False),
sa.Column('exam_id', sa.Integer(), nullable=False),
sa.Column('student_id', sa.Integer(), nullable=True),
sa.Column('signature_validated', sa.Boolean(), server_default='f', nullable=False),
......@@ -131,7 +131,7 @@ def upgrade():
'feedback_option',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('problem_id', sa.Integer(), nullable=True),
sa.Column('text', sa.String(length=120), nullable=False),
sa.Column('text', sa.Text(), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('score', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['problem_id'], ['problem.id'], ),
......@@ -140,7 +140,7 @@ def upgrade():
op.create_table(
'page',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('path', sa.String(length=120), nullable=False),
sa.Column('path', sa.Text(), nullable=False),
sa.Column('submission_id', sa.Integer(), nullable=True),
sa.Column('number', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['submission_id'], ['submission.id'], ),
......@@ -149,7 +149,7 @@ def upgrade():
op.create_table(
'problem_widget',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('problem_id', sa.Integer(), nullable=True),
sa.Column('problem_id', sa.Integer(), nullable=False),
sa.Column('page', sa.Integer(), nullable=True),
sa.Column('width', sa.Integer(), nullable=True),
sa.Column('height', sa.Integer(), nullable=True),
......
......@@ -15,7 +15,8 @@
"start": "ZESJE_SETTINGS=$(pwd)/zesje.dev.cfg python3 zesje",
"analyze": "webpack --config webpack.prod.js --profile --json > stats.json; webpack-bundle-analyzer stats.json zesje/static",
"migrate:dev": "ZESJE_SETTINGS=$(pwd)/zesje.dev.cfg FLASK_APP=zesje/__init__.py flask db upgrade",
"migrate": "FLASK_APP=zesje/__init__.py flask db upgrade"
"migrate": "FLASK_APP=zesje/__init__.py flask db upgrade",
"prepare-migration": "ZESJE_SETTINGS=$(pwd)/zesje.dev.cfg FLASK_APP=zesje/__init__.py flask db migrate"
},
"standard": {
"parser": "babel-eslint",
......
......@@ -19,14 +19,7 @@ from . import export
api_bp = Blueprint(__name__, __name__)
errors = {
'ObjectNotFound': {
'status': 404,
'message': 'Resource with that ID does not exist',
},
}
api = Api(api_bp, errors=errors)
api = Api(api_bp)
api.add_resource(Graders, '/graders')
api.add_resource(Exams, '/exams', '/exams/<int:exam_id>', '/exams/<int:exam_id>/<string:attr>')
......
......@@ -76,9 +76,10 @@ class Submissions(Resource):
.distinct(Page.number).all())
if submission_id is not None:
# Raises exception if zero or more than one found
sub = Submission.query.filter(Submission.exam_id == exam.id,
Submission.copy_number == submission_id).one()
Submission.copy_number == submission_id).one_or_none()
if sub is None:
return dict(status=404, message='Submission does not exist.'), 404
return sub_to_data(sub, all_pages)
......@@ -120,9 +121,10 @@ class Submissions(Resource):
if exam is None:
return dict(status=404, message='Exam does not exist.'), 404
# Raises exception if zero or more than one found
sub = Submission.query.filter(Submission.exam_id == exam.id,
Submission.copy_number == submission_id).one()
Submission.copy_number == submission_id).one_or_none()
if sub is None:
return dict(status=404, message='Submission does not exist.'), 404
student = Student.query.get(args.studentID)
if student is None:
......
......@@ -42,9 +42,9 @@ class Student(db.Model):
"""New students may be added throughout the course."""
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
first_name = Column(String, nullable=False)
last_name = Column(String, nullable=False)
email = Column(String, unique=True)
first_name = Column(Text, nullable=False)
last_name = Column(Text, nullable=False)
email = Column(Text, unique=True)
submissions = db.relationship('Submission', backref='student', lazy=True)
......@@ -52,7 +52,7 @@ class Grader(db.Model):
"""Graders can be created by any user at any time, but are immutable once they are created"""
__tablename__ = 'grader'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
name = Column(Text, nullable=False)
graded_solutions = db.relationship('Solution', backref='graded_by', lazy=True)
......@@ -60,7 +60,7 @@ class Exam(db.Model):
""" New instances are created when providing a new exam. """
__tablename__ = 'exam'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
name = Column(Text, nullable=False)
token = Column(String(token_length), unique=True, default=_generate_exam_token)
submissions = db.relationship('Submission', backref='exam', lazy=True)
problems = db.relationship('Problem', backref='exam', order_by='Problem.id', lazy=True)
......@@ -73,7 +73,7 @@ class Submission(db.Model):
"""Typically created when adding a new exam."""
__tablename__ = 'submission'
id = Column(Integer, primary_key=True, autoincrement=True)
copy_number = Column(Integer)
copy_number = Column(Integer, nullable=False)
exam_id = Column(Integer, ForeignKey('exam.id'), nullable=False)
solutions = db.relationship('Solution', backref='submission', order_by='Solution.problem_id', lazy=True)
pages = db.relationship('Page', backref='submission', lazy=True)
......@@ -85,7 +85,7 @@ class Page(db.Model):
"""Page of an exam"""
__tablename__ = 'page'
id = Column(Integer, primary_key=True, autoincrement=True)
path = Column(String, nullable=False)
path = Column(Text, nullable=False)
submission_id = Column(Integer, ForeignKey('submission.id'), nullable=True)
number = Column(Integer, nullable=False)
......@@ -94,7 +94,7 @@ class Problem(db.Model):
"""this will be initialized @ app initialization and immutable from then on."""
__tablename__ = 'problem'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
name = Column(Text, nullable=False)
exam_id = Column(Integer, ForeignKey('exam.id'), nullable=False)
feedback_options = db.relationship('FeedbackOption', backref='problem', order_by='FeedbackOption.id', lazy=True)
solutions = db.relationship('Solution', backref='problem', lazy=True)
......@@ -106,7 +106,7 @@ class FeedbackOption(db.Model):
__tablename__ = 'feedback_option'
id = Column(Integer, primary_key=True, autoincrement=True)
problem_id = Column(Integer, ForeignKey('problem.id'))
text = Column(String, nullable=False)
text = Column(Text, nullable=False)
description = Column(Text, nullable=True)
score = Column(Integer, nullable=True)
......@@ -139,17 +139,17 @@ class Scan(db.Model):
"""Metadata on uploaded PDFs"""
__tablename__ = 'scan'
id = Column(Integer, primary_key=True, autoincrement=True)
exam_id = Column(String, ForeignKey('exam.id'), nullable=False)
name = Column(String, nullable=False)
status = Column(String, nullable=False)
message = Column(String)
exam_id = Column(Integer, ForeignKey('exam.id'), nullable=False)
name = Column(Text, nullable=False)
status = Column(Text, nullable=False)
message = Column(Text)
class Widget(db.Model):
__tablename__ = 'widget'
id = Column(Integer, primary_key=True, autoincrement=True)
# Can be used to distinguish widgets for barcodes, student_id and problems
name = Column(String)
name = Column(Text)
x = Column(Integer, nullable=False)
y = Column(Integer, nullable=False)
type = Column(String(20))
......@@ -187,7 +187,7 @@ class ExamWidget(Widget):
class ProblemWidget(Widget):
__tablename__ = 'problem_widget'
id = Column(Integer, ForeignKey('widget.id'), primary_key=True, nullable=False)
problem_id = Column(Integer, ForeignKey('problem.id'), nullable=True)
problem_id = Column(Integer, ForeignKey('problem.id'), nullable=False)
page = Column(Integer)
width = Column(Integer)
height = Column(Integer)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment