Commit 47963c6a authored by Hugo Kerstens's avatar Hugo Kerstens

Remove pystrich from conda environment

parents d93b45ea 9423d064
# This base image can be found in 'Dockerfile'
image: zesje/base
image: gitlab.kwant-project.org:5005/zesje/zesje/test:latest
stages:
- build
......@@ -13,11 +13,13 @@ stages:
paths:
- .yarn-cache
before_script:
- source activate zesje-dev
- yarn install --cache-folder .yarn-cache
.python_packages: &python_packages
before_script:
- pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt
- source activate zesje-dev
- conda env update
build:
<<: *node_modules
......
FROM archlinux/base
## Install packages and clear the cache after installation. Yarn is fixed at 1.6.0 untill 1.8.0 is released due to a critical bug.
RUN pacman -Sy --noconfirm nodejs python-pip git libdmtx libsm libxrender libxext gcc libmagick6 imagemagick ghostscript; \
pacman -U --noconfirm https://archive.archlinux.org/packages/y/yarn/yarn-1.6.0-1-any.pkg.tar.xz
WORKDIR ~
ADD requirements*.txt ./
#ADD package.json .
RUN pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt;
#RUN yarn install; \
# yarn cache clean; \
# rm package.json
FROM continuumio/miniconda3
RUN apt-get update -y && apt-get install -y libdmtx0a libmagickwand-dev
WORKDIR /app
ADD environment.yml /app/environment.yml
RUN conda env create
# From https://medium.com/@chadlagore/conda-environments-with-docker-82cdc9d25754
RUN echo "source activate $(head -1 /app/environment.yml | cut -d' ' -f2)" > ~/.bashrc
ENV PATH /opt/conda/envs/$(head -1 /app/environment.yml | cut -d' ' -f2)/bin:$PATH
RUN rm /app/environment.yml
CMD bash
\ No newline at end of file
......@@ -15,10 +15,11 @@ Install Miniconda by following the instructions on this page:
https://conda.io/miniconda.html
Create a Conda environment that you will use for installing all
of zesje's dependencies:
Make sure you cloned this repository and enter its directory. Then
create a Conda environment that will automatically install all
of zesje's Python dependencies:
conda create -c conda-forge -n zesje-dev python=3.6 yarn
conda env create # Creates an environment from environment.yml
Then, *activate* the conda environment:
......@@ -31,10 +32,6 @@ Install all of the Javascript dependencies:
yarn install
Install all of the Python dependencies:
pip install -r requirements.txt -r requirements-dev.txt
Unfortunately there is also another dependency that must be installed
manually for now (we are working to bring this dependency into the
Conda ecosystem). You can install this dependency in the following way
......@@ -145,10 +142,10 @@ If you use Atom, install the [linter-js-standard-engine](https://atom.io/package
### Adding dependencies
#### Server-side
If you start using a new Python library, be sure to add it to `requirements.txt`. Python libraries for the testing are in `requirements-dev.txt`.
The packages can be installed and updated in your environment by `pip` using
If you start using a new Python library, be sure to add it to `environment.yml`.
The packages can be installed and updated in your environment by `conda` using
pip install -r requirements.txt -r requirements-dev.txt
conda env update
#### Client-side
......
......@@ -261,9 +261,13 @@ class Grade extends React.Component {
renderSuggestion={(submission) => {
const stud = submission.student
return (
<div>
<b>{`${stud.firstName} ${stud.lastName}`}</b>
<i style={{float: 'right'}}>({stud.id})</i>
<div className='flex-parent'>
<b className='flex-child truncated'>
{`${stud.firstName} ${stud.lastName}`}
</b>
<i className='flex-child fixed'>
({stud.id})
</i>
</div>
)
}}
......
.box.is-graded {
box-shadow: 0px 0px 6px #23d160, 0 0 0 1px rgba(10, 10, 10, 0.1);
}
.flex-parent {
display: flex;
align-items: center;
}
.flex-child.truncated {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.flex-child.fixed {
white-space: nowrap;
}
name: zesje-dev
channels:
- conda-forge
- anaconda
dependencies:
- python=3.6
- yarn
- redis
- pip
- pip:
# Core components
- flask
- flask_restful
- flask_sqlalchemy
- sqlalchemy
- Flask-Migrate
- alembic
- pyyaml
- celery
- redis
# General utilities
- numpy
- scipy
# summary plot generation
- matplotlib
- seaborn
# PDF generation
- pdfrw
- reportlab
- Wand
- Pillow # also scan processing
# Scan processing
- opencv-python
- git+https://github.com/mstamy2/PyPDF2
- pylibdmtx
# Exporting
- pandas
- openpyxl # required for writing dataframes as Excel spreadsheets
#
# Development dependencies
#
# Tests
- pytest
- pyssim
- pytest-cov
# Linting
- flake8
......@@ -3,7 +3,7 @@
"main": "index.js",
"license": "AGPL-3.0",
"scripts": {
"dev": "concurrently --kill-others --names \"WEBPACK,PYTHON,CELERY\" --prefix-colors \"bgBlue.bold,bgGreen.bold,bgRed.bold\" \"webpack-dev-server --hot --inline --progress --config webpack.dev.js\" \"ZESJE_SETTINGS=$(pwd)/zesje.dev.cfg python3 zesje\" \"ZESJE_SETTINGS=$(pwd)/zesje.dev.cfg celery -A zesje.celery worker -l info --autoscale=4,1 --max-tasks-per-child=16\"",
"dev": "concurrently --kill-others --names \"WEBPACK,PYTHON,CELERY,REDIS\" --prefix-colors \"bgBlue.bold,bgGreen.bold,bgRed.bold,bgYellow.bold\" \"webpack-dev-server --hot --inline --progress --config webpack.dev.js\" \"ZESJE_SETTINGS=$(pwd)/zesje.dev.cfg python3 zesje\" \"ZESJE_SETTINGS=$(pwd)/zesje.dev.cfg celery -A zesje.celery worker -l info --autoscale=4,1 --max-tasks-per-child=16\" \"redis-server redis.conf\"",
"build": "webpack --config webpack.prod.js",
"ci": "yarn lint; yarn test",
"lint": "yarn lint:js; yarn lint:py",
......
port 6479
loglevel notice
\ No newline at end of file
# Tests
pytest
pyssim
pytest-cov
# Linting
flake8
# Core components
flask
flask_restful
flask_sqlalchemy
sqlalchemy
Flask-Migrate
alembic
pyyaml
celery
redis
# General utilities
numpy
scipy
# summary plot generation
matplotlib
seaborn
# PDF generation
pdfrw
reportlab
Wand
Pillow # also scan processing
# Scan processing
opencv-python
git+https://github.com/mstamy2/PyPDF2
pylibdmtx
# Exporting
pandas
openpyxl # required for writing dataframes as Excel spreadsheets
import os
import pytest
from flask import Flask
from zesje.api import api_bp
from zesje.database import db
# Adapted from https://stackoverflow.com/a/46062148/1062698
@pytest.fixture
def datadir():
return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
@pytest.fixture(scope="module")
def app():
app = Flask(__name__, static_folder=None)
app.config.update(
SQLALCHEMY_DATABASE_URI='sqlite:///:memory:',
SQLALCHEMY_TRACK_MODIFICATIONS=False # Suppress future deprecation warning
)
db.init_app(app)
with app.app_context():
db.create_all()
app.register_blueprint(api_bp, url_prefix='/api')
return app
@pytest.fixture
def empty_app(app):
with app.app_context():
db.drop_all()
db.create_all()
return app
import pytest
from flask import Flask
from zesje.database import db, Exam, _generate_exam_token
from zesje.database import db, _generate_exam_token, Exam, Problem, ProblemWidget, Solution
from zesje.database import Submission, Scan, Page, ExamWidget, FeedbackOption
@pytest.mark.parametrize('duplicate_count', [
......@@ -32,3 +33,141 @@ def test_exam_generate_token_length_uppercase(duplicate_count, monkeypatch):
id = _generate_exam_token()
assert len(id) == 12
assert id.isupper()
def test_cascades_exam(empty_app, exam, problem, submission, scan, exam_widget):
"""Tests the cascades defined for an exam
Tests the cascades for the following relations:
- Exam -> Submission
- Exam -> Problem
- Exam -> Scan
- Exam -> ExamWidget
"""
empty_app.app_context().push()
exam.problems = [problem]
exam.scans = [scan]
exam.submissions = [submission]
exam.widgets = [exam_widget]
db.session.add(exam)
db.session.commit()
assert problem in db.session
assert submission in db.session
assert scan in db.session
assert exam_widget in db.session
db.session.delete(exam)
db.session.commit()
assert problem not in db.session
assert submission not in db.session
assert scan not in db.session
assert exam_widget not in db.session
def test_cascades_problem(empty_app, exam, problem, submission, solution, problem_widget, feedback_option):
"""Tests the cascades defined for a problem
Tests the cascades for the following relations:
- Problem -> Solution
- Problem -> ProblemWidget
- Problem -> FeedbackOption
"""
empty_app.app_context().push()
exam.problems = [problem]
exam.submissions = [submission]
solution.submission = submission
problem.widget = problem_widget
problem.solutions = [solution]
problem.feedback_options = [feedback_option]
db.session.add_all([exam, problem, submission])
db.session.commit()
assert solution in db.session
assert problem_widget in db.session
assert feedback_option in db.session
db.session.delete(problem)
db.session.commit()
assert solution not in db.session
assert problem_widget not in db.session
assert feedback_option not in db.session
def test_cascades_submission(empty_app, exam, problem, submission, solution, page):
"""Tests the cascades defined for a submission
Tests the cascades for the following relations:
- Submission -> Solution
- Submission -> Page
"""
empty_app.app_context().push()
exam.problems = [problem]
exam.submissions = [submission]
solution.problem = problem
solution.submission = submission
page.submission = submission
db.session.add_all([exam, problem, submission])
db.session.commit()
assert solution in db.session
assert page in db.session
db.session.delete(submission)
db.session.commit()
assert solution not in db.session
assert page not in db.session
@pytest.fixture
def exam():
return Exam(name='')
@pytest.fixture
def problem():
return Problem(name='')
@pytest.fixture
def problem_widget():
return ProblemWidget(name='', page=0, x=0, y=0, width=0, height=0)
@pytest.fixture
def exam_widget():
return ExamWidget(name='', x=0, y=0)
@pytest.fixture
def submission():
return Submission(copy_number=0)
@pytest.fixture
def solution():
return Solution()
@pytest.fixture
def scan():
return Scan(name='', status='')
@pytest.fixture
def page():
return Page(path='', number=0)
@pytest.fixture
def feedback_option():
return FeedbackOption(text='')
......@@ -42,19 +42,7 @@ class Exams(Resource):
elif Submission.query.filter(Submission.exam_id == exam.id).count():
return dict(status=500, message='Exam is not finalized but already has submissions.'), 500
else:
# Delete any scans that were wrongly uploaded to this exam
for scan in exam.scans:
db.session.delete(scan)
for widget in exam.widgets:
db.session.delete(widget)
for problem in exam.problems:
for fb_option in problem.feedback_options:
db.session.delete(fb_option)
db.session.delete(problem.widget)
db.session.delete(problem)
# All corresponding solutions, scans and problems are automatically deleted
db.session.delete(exam)
db.session.commit()
......
......@@ -105,10 +105,7 @@ class Problems(Resource):
if any([sol.graded_by is not None for sol in problem.solutions]):
return dict(status=403, message=f'Problem has already been graded'), 403
else:
# Delete all solutions associated with this problem
for sol in problem.solutions:
db.session.delete(sol)
db.session.delete(problem.widget)
# The widget and all associated solutions are automatically deleted
db.session.delete(problem)
db.session.commit()
return dict(status=200, message="ok"), 200
......@@ -62,10 +62,11 @@ class Exam(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True)
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)
scans = db.relationship('Scan', backref='exam', lazy=True)
widgets = db.relationship('ExamWidget', backref='exam', order_by='ExamWidget.id', lazy=True)
submissions = db.relationship('Submission', backref='exam', cascade='all', lazy=True)
problems = db.relationship('Problem', backref='exam', cascade='all', order_by='Problem.id', lazy=True)
scans = db.relationship('Scan', backref='exam', cascade='all', lazy=True)
widgets = db.relationship('ExamWidget', backref='exam', cascade='all',
order_by='ExamWidget.id', lazy=True)
finalized = Column(Boolean, default=False, server_default='f')
......@@ -75,8 +76,9 @@ class Submission(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True)
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)
solutions = db.relationship('Solution', backref='submission', cascade='all',
order_by='Solution.problem_id', lazy=True)
pages = db.relationship('Page', backref='submission', cascade='all', lazy=True)
student_id = Column(Integer, ForeignKey('student.id'), nullable=True)
signature_validated = Column(Boolean, default=False, server_default='f', nullable=False)
......@@ -96,9 +98,10 @@ class Problem(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True)
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)
widget = db.relationship('ProblemWidget', backref='problem', uselist=False, lazy=True)
feedback_options = db.relationship('FeedbackOption', backref='problem', cascade='all',
order_by='FeedbackOption.id', lazy=True)
solutions = db.relationship('Solution', backref='problem', cascade='all', lazy=True)
widget = db.relationship('ProblemWidget', backref='problem', cascade='all', uselist=False, lazy=True)
class FeedbackOption(db.Model):
......
......@@ -33,8 +33,8 @@ def create_app():
)
app.config.update(
CELERY_BROKER_URL='redis://localhost:6379',
CELERY_RESULT_BACKEND='redis://localhost:6379'
CELERY_BROKER_URL='redis://localhost:6479',
CELERY_RESULT_BACKEND='redis://localhost:6479'
)
db.init_app(app)
......
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