From 2c891af807fd46b72a539ee99040dee18b3cd6e3 Mon Sep 17 00:00:00 2001
From: jwansek <eddie.atten.ea29@gmail.com>
Date: Wed, 27 Apr 2022 18:31:52 +0100
Subject: Added some sql stuff, fixed bugs with pdf rendering

---
 Smarker/assessments.py   | 96 ++++++++++++++++++++++++++++++++++++++++++++++++
 Smarker/database.py      | 66 +++++++++++++++++++++++++++++++--
 Smarker/misc_classes.py  |  8 ++--
 Smarker/requirements.txt |  1 +
 Smarker/smarker.py       | 39 +++++++++++++-------
 5 files changed, 189 insertions(+), 21 deletions(-)
 create mode 100644 Smarker/assessments.py

(limited to 'Smarker')

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():
-- 
cgit v1.2.3