diff options
| author | 2018-05-18 23:05:40 +0100 | |
|---|---|---|
| committer | 2018-05-18 23:05:40 +0100 | |
| commit | fd147b934ef03a6609bee4efff24967f854f5495 (patch) | |
| tree | 74f213bb258f662045fe921645d5f25825c2fe76 | |
| parent | Added SVG file and edited navbar accordingly (#72) (diff) | |
[Jams] A bunch more work. That's all for tonight, though.
Diffstat (limited to '')
| -rw-r--r-- | pysite/constants.py | 10 | ||||
| -rw-r--r-- | pysite/database.py | 2 | ||||
| -rw-r--r-- | pysite/tables.py | 6 | ||||
| -rw-r--r-- | pysite/views/staff/jams/actions.py | 138 | ||||
| -rw-r--r-- | static/js/jams.js | 81 | ||||
| -rw-r--r-- | templates/staff/jams/index.html | 8 | 
6 files changed, 234 insertions, 11 deletions
| diff --git a/pysite/constants.py b/pysite/constants.py index c93a09d1..e30ed10b 100644 --- a/pysite/constants.py +++ b/pysite/constants.py @@ -75,6 +75,16 @@ JAM_STATES = [      "finished"  ] +JAM_QUESTION_TYPES = [ +    "email", +    "number", +    "radio", +    "range", +    "text", +    "textarea", +    "slider" +] +  # PaperTrail logging  PAPERTRAIL_ADDRESS = environ.get("PAPERTRAIL_ADDRESS") or None  PAPERTRAIL_PORT = int(environ.get("PAPERTRAIL_PORT") or 0) diff --git a/pysite/database.py b/pysite/database.py index 2fb10598..a8c6c559 100644 --- a/pysite/database.py +++ b/pysite/database.py @@ -364,7 +364,7 @@ class RethinkDB:              coerce=list          ) -    def get(self, table_name: str, key: str) -> Optional[Dict[str, Any]]: +    def get(self, table_name: str, key: Any) -> Optional[Dict[str, Any]]:          """          Get a single document from a table by primary key diff --git a/pysite/tables.py b/pysite/tables.py index 8ec550fb..499172d9 100644 --- a/pysite/tables.py +++ b/pysite/tables.py @@ -65,8 +65,11 @@ TABLES = {      "code_jam_questions": Table(  # Application form questions          primary_key="id",          keys=sorted([ +            "data",  # dict              "id",  # uuid -            ""  # TODO +            "optional",  # bool +            "title",  # str +            "type",  # str          ])      ), @@ -76,6 +79,7 @@ TABLES = {              "id",  # uuid              "jam",  # int              "answers",  # dict {question, answer, metadata} +            "approved"  # bool          ])      ), diff --git a/pysite/views/staff/jams/actions.py b/pysite/views/staff/jams/actions.py index 93a7f2c7..9aa7e79f 100644 --- a/pysite/views/staff/jams/actions.py +++ b/pysite/views/staff/jams/actions.py @@ -5,23 +5,93 @@ from pysite.constants import ALL_STAFF_ROLES, ErrorCodes  from pysite.decorators import csrf, require_roles  from pysite.mixins import DBMixin -ACTIONS = ["state"] +GET_ACTIONS = ["questions"] +POST_ACTIONS = ["associate_question", "disassociate_question", "questions", "state"]  KEYS = ["action"] +QUESTION_KEYS = ["optional", "title", "type"] +  class ActionView(APIView, DBMixin): -    path = "/jams/action/" +    path = "/jams/action"      name = "jams.action" +      table_name = "code_jams" +    forms_table = "code_jam_forms" +    questions_table = "code_jam_questions" + +    @csrf +    @require_roles(*ALL_STAFF_ROLES) +    def get(self): +        action = request.args.get("action") + +        if action not in GET_ACTIONS: +            return self.error(ErrorCodes.incorrect_parameters) + +        if action == "questions": +            questions = self.db.get_all(self.questions_table) + +            print(questions) +            return jsonify({"questions": questions})      @csrf      @require_roles(*ALL_STAFF_ROLES)      def post(self):          action = request.args.get("action") -        if action not in ACTIONS: +        if action not in POST_ACTIONS:              return self.error(ErrorCodes.incorrect_parameters) +        if action == "associate_question": +            form = int(request.args.get("form")) +            question = request.args.get("question") + +            form_obj = self.db.get(self.forms_table, form) + +            if not form_obj: +                return self.error(ErrorCodes.incorrect_parameters, f"Unknown form: {form}") + +            question_obj = self.db.get(self.questions_table, question) + +            if not question_obj: +                return self.error(ErrorCodes.incorrect_parameters, f"Unknown question: {question}") + +            if question_obj["id"] not in form_obj["questions"]: +                form_obj["questions"].append(question_obj["id"]) +                self.db.insert(self.forms_table, form_obj, conflict="replace") + +                return jsonify({"question": question_obj}) +            else: +                return self.error( +                    ErrorCodes.incorrect_parameters, +                    f"Question {question} already associated with form {form}" +                ) + +        if action == "disassociate_question": +            form = int(request.args.get("form")) +            question = request.args.get("question") + +            form_obj = self.db.get(self.forms_table, form) + +            if not form_obj: +                return self.error(ErrorCodes.incorrect_parameters, f"Unknown form: {form}") + +            question_obj = self.db.get(self.questions_table, question) + +            if not question_obj: +                return self.error(ErrorCodes.incorrect_parameters, f"Unknown question: {question}") + +            if question_obj["id"] in form_obj["questions"]: +                form_obj["questions"].remove(question_obj["id"]) +                self.db.insert(self.forms_table, form_obj, conflict="replace") + +                return jsonify({"question": question_obj}) +            else: +                return self.error( +                    ErrorCodes.incorrect_parameters, +                    f"Question {question} not already associated with form {form}" +                ) +          if action == "state":              jam = int(request.args.get("jam"))              state = request.args.get("state") @@ -34,3 +104,65 @@ class ActionView(APIView, DBMixin):              self.db.insert(self.table_name, jam_obj, conflict="update")              return jsonify({}) + +        if action == "questions": +            data = request.get_json(force=True) + +            for key in QUESTION_KEYS: +                if key not in data: +                    return self.error(ErrorCodes.incorrect_parameters, f"Missing key: {key}") + +            title = data["title"] +            optional = data["optional"] +            question_type = data["type"] +            question_data = data.get("data", {}) + +            if question_type in ["number", "range", "slider"]: +                if "max" not in question_data or "min" not in question_data: +                    return self.error( +                        ErrorCodes.incorrect_parameters, f"{question_type} questions must have both max and min values" +                    ) + +                result = self.db.insert( +                    self.questions_table, +                    { +                        "title": title, +                        "optional": optional, +                        "type": question_type, +                        "data": { +                            "max": question_data["max"], +                            "min": question_data["min"] +                        } +                    }, +                    conflict="error" +                ) +            elif question_type == "radio": +                if "options" not in question_data: +                    return self.error( +                        ErrorCodes.incorrect_parameters, f"{question_type} questions must have both options" +                    ) + +                result = self.db.insert( +                    self.questions_table, +                    { +                        "title": title, +                        "optional": optional, +                        "type": question_type, +                        "data": { +                            "options": question_data["options"] +                        } +                    }, +                    conflict="error" +                ) +            else: +                result = self.db.insert( +                    self.questions_table, +                    {  # No extra data for other types of question +                        "title": title, +                        "optional": optional, +                        "type": question_type +                    }, +                    conflict="error" +                ) + +            return jsonify({"id": result["generated_keys"][0]}) diff --git a/static/js/jams.js b/static/js/jams.js index bdce1060..b2d5b1bd 100644 --- a/static/js/jams.js +++ b/static/js/jams.js @@ -49,17 +49,19 @@ class Actions {          let oReq = new XMLHttpRequest();          oReq.addEventListener("load", function() { +            let result; +              try { -                data = JSON.parse(this.responseText); +                result = JSON.parse(this.responseText);              } catch (e) {                  return callback(false);              } -            if ("error_code" in data) { -                return callback(false, data); +            if ("error_code" in result) { +                return callback(false, result);              } -            return callback(true, data); +            return callback(true, result);          });          data["action"] = action; @@ -72,6 +74,35 @@ class Actions {          oReq.send();      } +    send_json(action, method, data, callback) { +        let oReq = new XMLHttpRequest(); + +        oReq.addEventListener("load", function() { +            let result; + +            try { +                result = JSON.parse(this.responseText); +            } catch (e) { +                return callback(false); +            } + +            if ("error_code" in result) { +                return callback(false, result); +            } + +            return callback(true, result); +        }); + +        data = JSON.stringify(data); + +        let params = this.get_params({"action": action}); +        let url = this.url + "?" + params; + +        oReq.open(method, url); +        oReq.setRequestHeader("X-CSRFToken", this.csrf_token); +        oReq.send(data); +    } +      get_params(data) {  // https://stackoverflow.com/a/12040639          return Object.keys(data).map(function(key) {              return [key, data[key]].map(encodeURIComponent).join("="); @@ -89,4 +120,46 @@ class Actions {              callback          );      } + +    get_questions(callback) { +        this.send( +            "questions", +            "GET", +            {}, +            callback +        ); +    } + +    create_question(data, callback) { +        this.send_json( +            "questions", +            "POST", +            data, +            callback +        ) +    } + +    associate_question(form, question, callback) { +        this.send( +            "associate_question", +            "POST", +            { +                "form": form, +                "question": question, +            }, +            callback +        ) +    } + +    disassociate_question(form, question, callback) { +        this.send( +            "disassociate_question", +            "POST", +            { +                "form": form, +                "question": question, +            }, +            callback +        ) +    }  }
\ No newline at end of file diff --git a/templates/staff/jams/index.html b/templates/staff/jams/index.html index 0f0ce022..336addee 100644 --- a/templates/staff/jams/index.html +++ b/templates/staff/jams/index.html @@ -9,9 +9,9 @@      <div class="uk-container uk-container-small uk-section">          <h1>Code Jams</h1> -        <a class="uk-button uk-button-default" href="{{ url_for("staff.index") }}"><i class="uk-icon fa-fw fas fa-arrow-left"></i>  Back</a> +        <a class="uk-button uk-button-default" href="{{ url_for("staff.index") }}"><i class="uk-icon fa-fw far fa-arrow-left"></i>  Back</a> +        <a class="uk-button uk-button-primary" href="{{ url_for("staff.jams.create") }}"><i class="uk-icon fa-fw far fa-plus"></i>  Create</a> -        <a class="uk-button uk-button-primary" href="{{ url_for("staff.jams.create") }}">Create</a>          {% if not jams %}              <p>                  No code jams found. Create one above! @@ -102,6 +102,9 @@                      </div>                      <br/>                      <div class="uk-button-group uk-width-1-1"> +                        <a class="uk-button uk-button-danger uk-width-expand" href="{{ url_for("staff.jams.forms.view", jam=jam.number) }}"> +                            <i class="uk-icon fa-fw far fa-list"></i>  Form +                        </a>                          <a class="uk-button uk-button-secondary uk-width-expand" href="# TODO">                              <i class="uk-icon fa-fw far fa-user"></i>  Participants                          </a> @@ -154,6 +157,7 @@      </div>      <script type="application/javascript"> +        "use strict";          const actions = new Actions("{{ url_for("staff.jams.action") }}", "{{ csrf_token() }}");          // State modal objects | 
