summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjwansek <eddie.atten.ea29@gmail.com>2022-04-27 18:31:52 +0100
committerjwansek <eddie.atten.ea29@gmail.com>2022-04-27 18:31:52 +0100
commit2c891af807fd46b72a539ee99040dee18b3cd6e3 (patch)
treeb10c83596e8d157d8e7cbdfd57797d2b4bd15505
parent24dbd9f360be1a533bb33e2506a88fd9af85eac2 (diff)
downloadSmarker-2c891af807fd46b72a539ee99040dee18b3cd6e3.tar.gz
Smarker-2c891af807fd46b72a539ee99040dee18b3cd6e3.zip
Added some sql stuff, fixed bugs with pdf rendering
-rw-r--r--.gitignore2
-rw-r--r--Dockerfile3
-rw-r--r--ExampleAssessments/example.yml5
-rw-r--r--ExampleSubmission/example.py4
-rw-r--r--README.md4
-rw-r--r--Smarker/assessments.py96
-rw-r--r--Smarker/database.py66
-rw-r--r--Smarker/misc_classes.py8
-rw-r--r--Smarker/requirements.txt1
-rw-r--r--Smarker/smarker.py39
10 files changed, 200 insertions, 28 deletions
diff --git a/.gitignore b/.gitignore
index d7b0b3f..78a3197 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
+out/
*_report.*
*.zip
smarker.conf
+*.aux
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/Dockerfile b/Dockerfile
index d6188ee..8f69849 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,8 @@ MAINTAINER Eden Attenborough "gae19jtu@uea.ac.uk"
ENV TZ=Europe/London
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update -y
-RUN apt-get install -y python3-pip python3-dev build-essential texlive-base wkhtmltopdf
+RUN apt-get install -y python3-pip python3-dev build-essential texlive-latex-base texlive-pictures texlive-science texlive-latex-extra wkhtmltopdf
+RUN mkdir /out
COPY ./Smarker /Smarker
WORKDIR /Smarker
RUN pip3 install -r requirements.txt
diff --git a/ExampleAssessments/example.yml b/ExampleAssessments/example.yml
index ec09561..c7695d4 100644
--- a/ExampleAssessments/example.yml
+++ b/ExampleAssessments/example.yml
@@ -1,4 +1,4 @@
-name: CMP-5021B-19-20
+name: example
files:
- example.py:
classes:
@@ -55,5 +55,4 @@ produced_files:
- animals.txt
dependencies:
files:
- - ../wsData.txt
- - ../IncludeDirectory \ No newline at end of file
+ - ../wsData.txt \ No newline at end of file
diff --git a/ExampleSubmission/example.py b/ExampleSubmission/example.py
index 07f21d4..d6f7015 100644
--- a/ExampleSubmission/example.py
+++ b/ExampleSubmission/example.py
@@ -1,11 +1,11 @@
# Eden Attenborough
# 12-01-21
-import tkinter as tk
+#import tkinter as tk
from dataclasses import dataclass
import sys
-class Application(tk.Tk):
+class Application:
"""An example class, which implements a GUI by inheriting from tkinter.Tk
"""
def __init__(self, *args, **kwargs):
diff --git a/README.md b/README.md
index 391e73b..0253245 100644
--- a/README.md
+++ b/README.md
@@ -3,4 +3,6 @@ An automated marking system for UEA programming assessments
## Running in docker
-`sudo docker run -v "$(pwd)/../wsData.txt":/wsData.txt -v "$(pwd)/ExampleAssessments/CMP-4009B.yml":/tmp/CMP-4009.yml -v "$(pwd)/../ExampleSubmissions/123456789.zip":/tmp/123456789.zip -e submission=/tmp/123456789.zip -e assessment=/tmp/CMP-4009.yml -e SMARKERDEPS=matplotlib smarker`
+`sudo docker run -v "$(pwd)/../wsData.txt":/wsData.txt -v "$(pwd)/100301654.zip":/tmp/100301654.zip -v "$(pwd)/out/":/out/ -e submission=/tmp/100301654.zip -e assessment=example -e SMARKERDEPS=matplotlib -e format=pdf -e output=/out/100301654.pdf --rm smarker`
+
+`sudo docker run -it --entrypoint python --rm smarker assessments.py --list yes` \ No newline at end of file
diff --git a/Smarker/assessments.py b/Smarker/assessments.py
new file mode 100644
index 0000000..d5c8daf
--- /dev/null
+++ b/Smarker/assessments.py
@@ -0,0 +1,96 @@
+import misc_classes
+import configparser
+import jinja_helpers
+import database
+import argparse
+import yaml
+import os
+
+if __name__ == "__main__":
+ config = configparser.ConfigParser()
+ config.read(os.path.join(os.path.split(__file__)[0], "smarker.conf"))
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-l", "--list",
+ action = misc_classes.EnvDefault,
+ envvar = "list",
+ help = "List assessment names, number enrolled, and number of files",
+ required = False
+ )
+ parser.add_argument(
+ "-c", "--create_assessment",
+ action = misc_classes.EnvDefault,
+ envvar = "create_assessment",
+ help = "Path to an assessment .yml file",
+ required = False
+ )
+ parser.add_argument(
+ "-rm", "--remove_assessment",
+ action = misc_classes.EnvDefault,
+ envvar = "remove_assessment",
+ help = "Name of an assessment to remove",
+ required = False
+ )
+ parser.add_argument(
+ "-e", "--number_enrolled",
+ action = misc_classes.EnvDefault,
+ envvar = "number_enrolled",
+ help = "Number of students enrolled onto an assessment. Required argument to create.",
+ required = False,
+ type = int
+ )
+ parser.add_argument(
+ "-s", "--create_student",
+ action = misc_classes.EnvDefault,
+ envvar = "create_student",
+ help = "Add a student in the form e.g. 123456789,Eden,Attenborough,E.Attenborough@uea.ac.uk",
+ required = False
+ )
+
+ for option in config.options("mysql"):
+ parser.add_argument(
+ "--%s_%s" % ("mysql", option),
+ action = misc_classes.EnvDefault,
+ envvar = "--%s_%s" % ("mysql", option),
+ default = config.get("mysql", option),
+ help = "Optional argument inherited from config file. Read smarker.conf for details."
+ )
+
+ args = vars(parser.parse_args())
+
+ with database.SmarkerDatabase(
+ args["mysql_host"], args["mysql_user"], args["mysql_passwd"],
+ "Smarker", int(args["mysql_port"])) as db:
+
+ if args["create_assessment"] is not None:
+ with open(args["create_assessment"], "r") as f:
+ assessment = yaml.safe_load(f)
+
+ db.create_assessment(
+ assessment["name"],
+ yaml.dump(assessment),
+ args["number_enrolled"],
+ jinja_helpers.flatten_struct(assessment["files"]).keys()
+ )
+
+ print("Added assessment %s..." % assessment["name"])
+
+ if args["remove_assessment"] is not None:
+ db.remove_assessment(args["remove_assessment"])
+
+ print("Removed %s..." % args["remove_assessment"])
+
+ if args["list"] is not None:
+ if args["list"] in ["True", "yes"]:
+ print(yaml.dump(db.get_assessments(), indent = 4))
+
+ if args["create_student"] is not None:
+ sid, name, email = args["create_student"].split(",")
+ db.add_student(int(sid), name, email)
+
+ print("Added student %s" % name)
+
+
+ # print(db.get_assessment_yaml("CMP-4009B-2020-A2"))
+
diff --git a/Smarker/database.py b/Smarker/database.py
index a0e3640..f17c285 100644
--- a/Smarker/database.py
+++ b/Smarker/database.py
@@ -1,5 +1,6 @@
from dataclasses import dataclass
import pymysql
+import yaml
@dataclass
class SmarkerDatabase:
@@ -12,7 +13,8 @@ class SmarkerDatabase:
def __enter__(self):
try:
self.__connection = self.__get_connection()
- except pymysql.err.OperationalError as e:
+ except Exception as e:
+ print(e.args[1])
if e.args[0] == 1049:
self.__build_db()
return self
@@ -62,7 +64,7 @@ class SmarkerDatabase:
student_no VARCHAR(10) NOT NULL,
assessment_name VARCHAR(30) NOT NULL,
submission_dt DATETIME NOT NULL default CURRENT_TIMESTAMP,
- submission_zip_path TEXT NOT NULL,
+ report_yaml TEXT NOT NULL,
FOREIGN KEY (student_no) REFERENCES students(student_no),
FOREIGN KEY (assessment_name) REFERENCES assessment(assessment_name)
);
@@ -79,14 +81,13 @@ class SmarkerDatabase:
CREATE TABLE submitted_files(
submission_id INT UNSIGNED NOT NULL,
file_id INT UNSIGNED NOT NULL,
- file_text TEXT NOT NULL,
+ file_text TEXT NULL,
FOREIGN KEY (submission_id) REFERENCES submissions(submission_id),
FOREIGN KEY (file_id) REFERENCES assessment_file(file_id),
PRIMARY KEY(submission_id, file_id)
);
""")
-
self.__connection.commit()
return self.__connection
@@ -95,3 +96,60 @@ class SmarkerDatabase:
with self.__connection.cursor() as cursor:
cursor.execute("SHOW TABLES;")
return cursor.fetchall()
+
+ def create_assessment(self, name, yaml_f, num_enrolled, files):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("INSERT INTO assessment VALUES (%s, %s, %s);", (
+ name, yaml_f, num_enrolled
+ ))
+
+ for file_ in files:
+ cursor.execute("INSERT INTO assessment_file (assessment_name, file_name) VALUES (%s, %s);", (
+ name, file_
+ ))
+ self.__connection.commit()
+
+ def remove_assessment(self, name):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("DELETE FROM assessment_file WHERE assessment_name = %s;", (name, ))
+ cursor.execute("DELETE FROM assessment WHERE assessment_name = %s;", (name, ))
+ self.__connection.commit()
+
+ def get_assessments(self):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("""
+ SELECT assessment.assessment_name, num_enrolled, COUNT(assessment.assessment_name)
+ FROM assessment
+ INNER JOIN assessment_file
+ ON assessment.assessment_name = assessment_file.assessment_name
+ GROUP BY assessment.assessment_name;
+ """)
+ return cursor.fetchall()
+
+ def get_assessment_yaml(self, name):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("SELECT yaml_path FROM assessment WHERE assessment_name = %s;", (name, ))
+ return yaml.safe_load(cursor.fetchone()[0])
+
+ def add_student(self, student_id, name, email):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("INSERT INTO students VALUES (%s, %s, %s);",
+ (student_id, name, email))
+ self.__connection.commit()
+
+ def add_submission(self, student_id, assessment_name, report_yaml, files):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("INSERT INTO submissions (student_no, assessment_name, report_yaml) VALUES (%s, %s, %s);", (
+ student_id, assessment_name, report_yaml
+ ))
+ submission_id = cursor.lastrowid
+
+ for file_name, file_contents in files:
+ cursor.execute("""
+ INSERT INTO submitted_files
+ (submission_id, file_id, file_text)
+ VALUES (%s, (SELECT file_id FROM assessment_file WHERE file_name = %s), %s);
+ """, (
+ submission_id, file_name, file_contents
+ ))
+
diff --git a/Smarker/misc_classes.py b/Smarker/misc_classes.py
index 7ab37de..d5dd7e1 100644
--- a/Smarker/misc_classes.py
+++ b/Smarker/misc_classes.py
@@ -23,9 +23,11 @@ latex_jinja_env = jinja2.Environment(
# https://stackoverflow.com/questions/10551117/setting-options-from-environment-variables-when-using-argparse
class EnvDefault(argparse.Action):
def __init__(self, envvar, required=True, default=None, **kwargs):
- if not default and envvar:
- if envvar in os.environ:
- default = os.environ[envvar]
+ # if not default and envvar:
+ # if envvar in os.environ:
+ # default = os.environ[envvar]
+ if envvar in os.environ:
+ default = os.environ[envvar]
if required and default:
required = False
super(EnvDefault, self).__init__(default=default, required=required,
diff --git a/Smarker/requirements.txt b/Smarker/requirements.txt
index 3831b5e..944b77a 100644
--- a/Smarker/requirements.txt
+++ b/Smarker/requirements.txt
@@ -8,3 +8,4 @@ pytest
junit2html
pdfkit
lxml
+pymysql
diff --git a/Smarker/smarker.py b/Smarker/smarker.py
index 46feb5f..8b8527e 100644
--- a/Smarker/smarker.py
+++ b/Smarker/smarker.py
@@ -3,6 +3,7 @@ import jinja_helpers
import configparser
import misc_classes
import subprocess
+import database
import argparse
import tempfile
import zipfile
@@ -17,8 +18,11 @@ def main(**kwargs):
student_no = os.path.splitext(os.path.split(args["submission"])[-1])[0]
with misc_classes.ExtractZipToTempDir(args["submission"]) as submission_files:
- with open(kwargs["assessment"], "r") as f:
- assessment_struct = yaml.safe_load(f)
+ with database.SmarkerDatabase(
+ kwargs["mysql_host"], kwargs["mysql_user"], kwargs["mysql_passwd"],
+ "Smarker", int(kwargs["mysql_port"])) as db:
+
+ assessment_struct = db.get_assessment_yaml(kwargs["assessment"])
with misc_classes.FileDependencies(assessment_struct):
output = reflect.gen_reflection_report(submission_files, assessment_struct, student_no, kwargs, args["submission"])
@@ -46,19 +50,26 @@ def main(**kwargs):
if output_file == "auto":
output_file = "%s_report.%s" % (student_no, kwargs["format"])
- with open(output_file, "w") as f:
- f.write(strout)
-
if kwargs["format"] == "pdf":
os.environ["TEXINPUTS"] = os.path.join(os.path.split(__file__)[0], "python-latex-highlighting") + ":"
- os.rename(output_file, os.path.splitext(output_file)[0] + ".tex")
+
output_file = os.path.splitext(output_file)[0] + ".tex"
+ with open(output_file, "w") as f:
+ f.write(strout)
subprocess.run(["pdflatex", output_file])
-
- os.remove(os.path.splitext(output_file)[0] + ".tex")
- os.remove(os.path.splitext(output_file)[0] + ".log")
- os.remove(os.path.splitext(output_file)[0] + ".aux")
+ subprocess.run(["mv", os.path.splitext(os.path.split(output_file)[-1])[0] + ".pdf", os.path.split(output_file)[0]])
+
+ if os.path.exists(os.path.splitext(output_file)[0] + ".tex"):
+ os.remove(os.path.splitext(output_file)[0] + ".tex")
+ if os.path.exists(os.path.splitext(output_file)[0] + ".log"):
+ os.remove(os.path.splitext(output_file)[0] + ".log")
+ if os.path.exists(os.path.splitext(output_file)[0] + ".aux"):
+ os.remove(os.path.splitext(output_file)[0] + ".aux")
+
+ else:
+ with open(output_file, "w") as f:
+ f.write(strout)
# input("\n\n[tempdir: %s] Press any key to close..." % tempdir)
@@ -71,7 +82,7 @@ if __name__ == "__main__":
"-a", "--assessment",
action = misc_classes.EnvDefault,
envvar = "assessment",
- help = "Path to an assessment .yml file",
+ help = "Name of the assessment",
# type = os.path.abspath,
required = True
)
@@ -86,18 +97,18 @@ if __name__ == "__main__":
parser.add_argument(
"-f", "--format",
action = misc_classes.EnvDefault,
+ default = "txt",
envvar = "format",
help = "Output format type",
type = str,
choices = ["yaml", "json", "pdf"] + [os.path.splitext(f)[0] for f in os.listdir(os.path.join(os.path.split(__file__)[0], "templates"))],
- default = "txt"
)
parser.add_argument(
"-o", "--out",
action = misc_classes.EnvDefault,
- envvar = "out",
- help = "Path to write the output to, or, by default write to stdout. 'auto' automatically generates a file name.",
default = "stdout",
+ envvar = "output",
+ help = "Path to write the output to, or, by default write to stdout. 'auto' automatically generates a file name.",
)
for section in config.sections():