From 1e93e263aca5feca76bfae142fa4431ea70e5ba9 Mon Sep 17 00:00:00 2001
From: Roosted7 <thomasroos@live.nl>
Date: Fri, 26 Jan 2018 17:22:32 +0100
Subject: [PATCH] Use Flask_restful for better API handeling

---
 requirements.txt           |   1 +
 zesje/__init__.py          |  13 ++--
 zesje/api.py               | 136 ++-----------------------------------
 zesje/resources/graders.py |  59 ++++++++++++++++
 4 files changed, 73 insertions(+), 136 deletions(-)
 create mode 100644 zesje/resources/graders.py

diff --git a/requirements.txt b/requirements.txt
index a38ef9d3c..02d97ad3c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
 flask
 Flask-BasicAuth
+flask_restful
 pony
 
 opencv-python
diff --git a/zesje/__init__.py b/zesje/__init__.py
index 4b4ef9eb1..767bca696 100644
--- a/zesje/__init__.py
+++ b/zesje/__init__.py
@@ -1,16 +1,16 @@
 from os import path
-from os.path import abspath, dirname, isfile
+from os.path import abspath, dirname
 
-from werkzeug.exceptions import NotFound
 from flask import Flask
 from flask_basicauth import BasicAuth
+from werkzeug.exceptions import NotFound
 
 from . import db, api
 
-static_folder_path = path.join(abspath(dirname(__file__)), 'static')
+STATIC_FOLDER_PATH = path.join(abspath(dirname(__file__)), 'static')
 
-app = Flask(__name__, static_folder=static_folder_path)
-db.use_db()
+app = Flask(__name__, static_folder=STATIC_FOLDER_PATH)
+app.register_blueprint(api.api_bp, url_prefix='/api')
 
 
 ####### Very Very VERY import to use this outsite dev/sandbox environment
@@ -20,11 +20,12 @@ app.config['BASIC_AUTH_FORCE'] = False # Don't forget to set this to 'True'
 basic_auth = BasicAuth(app)
 
 
-app.register_blueprint(api.app, url_prefix='/api')
+db.use_db()
 
 @app.route('/')
 @app.route('/<file>')
 def index(file='index.html'):
+    """Serve the static react content, otherwise fallback to the index.html"""
     try:
         return app.send_static_file(file)
     except NotFound:
diff --git a/zesje/api.py b/zesje/api.py
index 9097a00c0..0b39b7c19 100644
--- a/zesje/api.py
+++ b/zesje/api.py
@@ -1,133 +1,9 @@
-import json
-import yaml
+from flask import Blueprint
+from flask_restful import Api
 
-from flask import Blueprint, Response, jsonify, request, abort
+from .resources.graders import Graders
 
-from . import db
+api_bp = Blueprint(__name__, __name__)
+api = Api(api_bp)
 
-app = Blueprint(__name__, __name__)
-
-# TODO: when making new database structure, have only a single
-#       'name' field: it is just an identifier
-
-@app.route('/graders', methods=['GET'])
-@db.session
-def get_graders():
-    """get all graders.
-
-
-    Returns
-    -------
-    list of:
-        id: int
-        first_name: str
-        last_name: str
-    """
-
-    return jsonify([
-        dict(id=g.id, first_name=g.first_name, last_name=g.last_name)
-        for g in db.Grader.select()
-    ])
-
-
-@app.route('/graders', methods=['POST'])
-@db.session
-def post_graders():
-    """add a grader.
-
-    Parameters
-    ----------
-    first_name: str
-    last_name: str
-
-    Returns
-    -------
-    id: int
-    first_name: str
-    last_name: str
-    """
-    grader_spec = request.get_json(silent=False, force=True)
-    try:
-        new_grader = db.Grader(first_name=grader_spec['first_name'],
-                               last_name=grader_spec['last_name'])
-        db.orm.commit()
-    except KeyError:
-        abort(400)
-
-    return jsonify(
-        id=new_grader.id,
-        first_name=new_grader.first_name,
-        last_name=new_grader.last_name,
-    )
-
-@app.route('/exams', methods=['POST'])
-def post_new_yaml():
-    if request.method == 'POST':
-        # check if the post request has the file part
-        if 'file' not in request.files:
-            print('No file part')
-            abort(400)
-        file = request.files['file']
-        # if user does not select file, browser also
-        # submit a empty part without filename
-        if file.filename == '':
-            print('No selected file')
-            abort(400)
-        if file:
-            try:
-
-                yml = yaml.safe_load(file.read())
-
-                try:
-                    yml = db.clean_yaml(yml)  # attempt to clean
-                except Exception:
-                    pass  # if the YAML is not valid parsing will raise anyway
-                version, exam_name, qr, widgets = db.parse_yaml(yml)
-                
-                # If there is already yaml for this exam, load it now so we can
-                # compute a diff later
-                existing_exam_data = get_exam_data(exam_name)
-                if existing_exam_data:
-                    existing_yml_version, *_, existing_widgets = existing_exam_data
-                    if not all(v == 1 for v in (version, existing_yml_version)):
-                        raise ValueError('Exam data for {} already exists, and updating it requires both the old '
-                                        'and new YAML to be version 1'.format(exam_name))
-                    if not existing_widgets.shape == widgets.shape:
-                        raise ValueError('Exam data for {} already exists, and contains a different number of '
-                                        'exam problems than {}'.format(exam_name, yaml_upload.filename))
-            except ValueError as exc:
-                print(str(exc))
-                abort(400)
-            except Exception as exc:
-                print("Invalid metadata")
-                print(exc)
-                abort(400)
-
-            # save yaml
-            yaml_filename = exam_name + '.yml'
-            print("Saving " + yaml_filename)
-            try:
-                with open(yaml_filename, 'w') as f:
-                    f.write(yaml.safe_dump(yml, default_flow_style=False))
-            except Exception:
-                print("Failed to save " + yaml_filename)
-                abort(400)
-            
-            print("Adding exam to database")
-            
-            try:
-                if existing_exam_data:
-                    db.update_exam(exam_name, yaml_filename)
-                    print("Updated problem names for {}".format(exam_name))
-                else:
-                    db.add_exam(yaml_filename)
-                    print("Added exam {} to database".format(exam_name))
-                os.makedirs(exam_name + '_data', exist_ok=True)
-            except Exception as exc:
-                print("Failed to add exam to database")
-                return
-            finally:
-                # XXX: use of global variables
-                # update list of exams
-                print("Metadata imported successfully")
-            
\ No newline at end of file
+api.add_resource(Graders, '/graders')
diff --git a/zesje/resources/graders.py b/zesje/resources/graders.py
new file mode 100644
index 000000000..3c520bf6b
--- /dev/null
+++ b/zesje/resources/graders.py
@@ -0,0 +1,59 @@
+from flask_restful import Resource, reqparse
+
+from .. import db
+
+parser = reqparse.RequestParser()
+parser.add_argument('first_name', type=str, required=True)
+parser.add_argument('last_name',  type=str, required=True)
+# TODO: when making new database structure, have only a single
+#       'name' field: it is just an identifier
+
+
+class Graders(Resource):
+    @db.session
+    def get(self):
+        """get all graders.
+
+
+        Returns
+        -------
+        list of:
+            id: int
+            first_name: str
+            last_name: str
+        """
+
+        return [
+                {
+                    'id' : g.id,
+                    'first_name' : g.first_name,
+                    'last_name' : g.last_name
+                }
+                for g in db.Grader.select()
+        ]
+
+    @db.session
+    def post(self):
+
+        args = parser.parse_args()
+
+        """add a grader.
+
+        Parameters
+        ----------
+        first_name: str
+        last_name: str
+
+        Returns
+        -------
+        id: int
+        first_name: str
+        last_name: str
+        """
+
+        db.Grader(first_name= args['first_name'],
+                    last_name=args['last_name'])
+        db.orm.commit()
+
+        return {
+        }
\ No newline at end of file
-- 
GitLab