diff options
author | 2018-05-19 13:53:53 +0100 | |
---|---|---|
committer | 2018-05-19 13:53:53 +0100 | |
commit | 4a66fe0427aaacac3cbfdf6b2ea47bb3e056e88b (patch) | |
tree | 581cef536c213ed00cc6ca48998f67b0df7e0b5b | |
parent | Fix some small question-editing bugs (diff) |
[Jams] Linting and infractions
-rw-r--r-- | pysite/tables.py | 4 | ||||
-rw-r--r-- | pysite/views/staff/jams/actions.py | 39 | ||||
-rw-r--r-- | pysite/views/staff/jams/forms/questions_edit.py | 6 | ||||
-rw-r--r-- | pysite/views/staff/jams/forms/view.py | 2 | ||||
-rw-r--r-- | pysite/views/staff/jams/infractions/__init__.py | 0 | ||||
-rw-r--r-- | pysite/views/staff/jams/infractions/view.py | 29 | ||||
-rw-r--r-- | static/js/jams.js | 24 | ||||
-rw-r--r-- | templates/staff/jams/index.html | 3 | ||||
-rw-r--r-- | templates/staff/jams/infractions/view.html | 329 |
9 files changed, 426 insertions, 10 deletions
diff --git a/pysite/tables.py b/pysite/tables.py index 67503cdb..c180e161 100644 --- a/pysite/tables.py +++ b/pysite/tables.py @@ -97,8 +97,8 @@ TABLES = { "code_jam_infractions": Table( # Individual infractions for each user primary_key="id", keys=sorted([ - "snowflake", # int - "participant", # int + "id", # uuid + "participant", # str "reason", # str "number" # int (optionally -1 for permanent) ]) diff --git a/pysite/views/staff/jams/actions.py b/pysite/views/staff/jams/actions.py index 3683db39..f08b3635 100644 --- a/pysite/views/staff/jams/actions.py +++ b/pysite/views/staff/jams/actions.py @@ -6,8 +6,8 @@ from pysite.decorators import csrf, require_roles from pysite.mixins import DBMixin GET_ACTIONS = ["questions"] -POST_ACTIONS = ["associate_question", "disassociate_question", "questions", "state"] -DELETE_ACTIONS = ["question"] +POST_ACTIONS = ["associate_question", "disassociate_question", "infraction", "questions", "state"] +DELETE_ACTIONS = ["infraction", "question"] KEYS = ["action"] QUESTION_KEYS = ["optional", "title", "type"] @@ -19,6 +19,7 @@ class ActionView(APIView, DBMixin): table_name = "code_jams" forms_table = "code_jam_forms" + infractions_table = "code_jam_infractions" questions_table = "code_jam_questions" @csrf @@ -168,6 +169,25 @@ class ActionView(APIView, DBMixin): return jsonify({"id": result["generated_keys"][0]}) + if action == "infraction": + participant = request.args.get("participant") + reason = request.args.get("reason") + + if not participant or not reason or "number" not in request.args: + return self.error( + ErrorCodes.incorrect_parameters, "Infractions must have a participant, reason and number" + ) + + number = int(request.args.get("number")) + + result = self.db.insert(self.infractions_table, { + "participant": participant, + "reason": reason, + "number": number + }) + + return jsonify({"id": result["generated_keys"][0]}) + @csrf @require_roles(*ALL_STAFF_ROLES) def delete(self): @@ -195,3 +215,18 @@ class ActionView(APIView, DBMixin): self.db.insert(self.forms_table, form_obj, conflict="replace") return jsonify({"id": question}) + + if action == "infraction": + infraction = request.args.get("id") + + if not infraction: + return self.error(ErrorCodes.incorrect_parameters, "Missing key id") + + infraction_obj = self.db.get(self.infractions_table, infraction) + + if not infraction_obj: + return self.error(ErrorCodes.incorrect_parameters, f"Unknown infraction: {infraction}") + + self.db.delete(self.infractions_table, infraction) + + return jsonify({"id": infraction_obj["id"]}) diff --git a/pysite/views/staff/jams/forms/questions_edit.py b/pysite/views/staff/jams/forms/questions_edit.py index cbc5158e..4de06793 100644 --- a/pysite/views/staff/jams/forms/questions_edit.py +++ b/pysite/views/staff/jams/forms/questions_edit.py @@ -1,11 +1,11 @@ import json -from flask import request, redirect, url_for -from werkzeug.exceptions import NotFound, BadRequest +from flask import redirect, request, url_for +from werkzeug.exceptions import BadRequest, NotFound from pysite.base_route import RouteView from pysite.constants import ALL_STAFF_ROLES -from pysite.decorators import require_roles, csrf +from pysite.decorators import csrf, require_roles from pysite.mixins import DBMixin REQUIRED_KEYS = ["title", "date_start", "date_end"] diff --git a/pysite/views/staff/jams/forms/view.py b/pysite/views/staff/jams/forms/view.py index eaa910f2..0c73bc58 100644 --- a/pysite/views/staff/jams/forms/view.py +++ b/pysite/views/staff/jams/forms/view.py @@ -1,5 +1,3 @@ -from werkzeug.exceptions import NotFound - from pysite.base_route import RouteView from pysite.constants import ALL_STAFF_ROLES from pysite.decorators import require_roles diff --git a/pysite/views/staff/jams/infractions/__init__.py b/pysite/views/staff/jams/infractions/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pysite/views/staff/jams/infractions/__init__.py diff --git a/pysite/views/staff/jams/infractions/view.py b/pysite/views/staff/jams/infractions/view.py new file mode 100644 index 00000000..235f99ac --- /dev/null +++ b/pysite/views/staff/jams/infractions/view.py @@ -0,0 +1,29 @@ +from pysite.base_route import RouteView +from pysite.constants import ALL_STAFF_ROLES +from pysite.decorators import require_roles +from pysite.mixins import DBMixin + +REQUIRED_KEYS = ["title", "date_start", "date_end"] + + +class StaffView(RouteView, DBMixin): + path = "/jams/infractions" + name = "jams.infractions" + + table_name = "code_jam_infractions" + users_table = "users" + + @require_roles(*ALL_STAFF_ROLES) + def get(self): + infractions = self.db.get_all(self.table_name) + + for document in infractions: + user_obj = self.db.get(self.users_table, document["participant"]) + + if user_obj: + document["participant"] = user_obj + + return self.render( + "staff/jams/infractions/view.html", infractions=infractions, + infraction_ids=[i["id"] for i in infractions] + ) diff --git a/static/js/jams.js b/static/js/jams.js index 37d8a764..2ee547fa 100644 --- a/static/js/jams.js +++ b/static/js/jams.js @@ -173,4 +173,28 @@ class Actions { callback ) } + + create_infraction(id, reason, number, callback) { + this.send( + "infraction", + "POST", + { + "participant": id, + "reason": reason, + "number": number + }, + callback + ) + } + + delete_infraction(id, callback) { + this.send( + "infraction", + "DELETE", + { + "id": id, + }, + callback + ) + } }
\ No newline at end of file diff --git a/templates/staff/jams/index.html b/templates/staff/jams/index.html index b249ec3f..39300064 100644 --- a/templates/staff/jams/index.html +++ b/templates/staff/jams/index.html @@ -10,8 +10,9 @@ <h1>Code Jams</h1> <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-secondary" href="{{ url_for("staff.jams.forms.questions") }}"><i class="uk-icon fa-fw far fa-list"></i> Questions</a> + <a class="uk-button uk-button-secondary" href="{{ url_for("staff.jams.infractions") }}"><i class="uk-icon fa-fw far fa-exclamation-triangle"></i> Infractions</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.forms.questions") }}"><i class="uk-icon fa-fw far fa-list"></i> Questions</a> {% if not jams %} <p> diff --git a/templates/staff/jams/infractions/view.html b/templates/staff/jams/infractions/view.html new file mode 100644 index 00000000..0b515cdb --- /dev/null +++ b/templates/staff/jams/infractions/view.html @@ -0,0 +1,329 @@ +{% extends "main/base.html" %} +{% block title %}Staff | Jams | Infractions{% endblock %} +{% block og_title %}Staff | Jams | Infractions{% endblock %} +{% block og_description %}Manage infractions{% endblock %} +{% block extra_head %} + <script src="{{ static_file('js/jams.js') }}"></script> +{% endblock %} +{% block content %} + <div class="uk-container uk-container-small uk-section"> + <h1>Infractions</h1> + + <a class="uk-button uk-button-default" href="{{ url_for("staff.jams.index") }}"><i class="uk-icon fa-fw far fa-arrow-left"></i> Back</a> + <button class="uk-button uk-button-primary" id="add-button"><i class="uk-icon fa-fw far fa-plus"></i> Add Infraction</button> + + {% if not infractions %} + <p id="no-infractions-paragraph">No infractions found.</p> + <table class="uk-table uk-table-divider uk-table-striped uk-border" id="table" hidden="hidden"> + <thead> + <tr> + <th class="uk-table-shrink"> </th> + <th class="uk-table-shrink"><strong>ID</strong></th> + <th class="uk-table-shrink">Participant</th> + <th>Reason</th> + <th class="uk-table-shrink">Number</th> + </tr> + </thead> + <tbody id="table-body"> + </tbody> + </table> + {% else %} + <p id="no-infractions-paragraph" hidden="hidden">No questions found.</p> + + <div class="uk-overflow-auto"> + <br /> + <table class="uk-table uk-table-divider uk-table-striped uk-border" id="table"> + <thead> + <tr> + <th class="uk-table-shrink"> </th> + <th class="uk-table-shrink"><strong>ID</strong></th> + <th class="uk-table-shrink">Participant</th> + <th>Reason</th> + <th class="uk-table-shrink">Number</th> + </tr> + </thead> + <tbody id="table-body"> + {% for infraction in infractions %} + <tr id="row-{{ infraction.id }}"> + <td class="uk-table-shrink"> + <button class="uk-button-small uk-button uk-button-danger delete-infraction-button" style="padding-left: 5px; padding-right: 5px;" data-infraction-id="{{ infraction.id }}"><i class="uk-icon fa-fw far fa-trash"></i></button> + </td> + <td class="uk-text-truncate" title="{{ infraction.id }}">{{ infraction.id }}</td> + <td class="uk-table-shrink"> + {% if infraction.participant is not string %} + {{ infraction.participant.username }}#{{ infraction.participant.discriminator }} ({{ infraction.participant.id }}) + {% else %} + {{ infraction.participant }} + {% endif %} + </td> + <td title="{{ infraction.reason }}">{{ infraction.reason }}</td> + <td class="uk-table-shrink">{{ infraction.number }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} + </div> + + <div id="add-modal" class="uk-flex-top" uk-modal> + <div class="uk-modal-dialog"> + <button class="uk-modal-close-default" type="button" uk-close></button> + + <div class="uk-modal-header"> + <h2 class="uk-modal-title">Add Infraction</h2> + </div> + + <div class="uk-modal-body"> + <form class="uk-form-horizontal"> + <div> + <div class="uk-form-label"> + <label class="uk-form-label" for="user-id">User ID</label> + </div> + <div class="uk-form-controls-text uk-form-controls"> + <input class="uk-input" id="user-id" name="user-id"> + </div> + </div> + <div> + <div class="uk-form-label"> + <label class="uk-form-label" for="reason">Reason</label> + </div> + <div class="uk-form-controls-text uk-form-controls"> + <input class="uk-input" id="reason" name="reason"> + </div> + </div> + <div> + <div class="uk-form-label"> + <label class="uk-form-label" for="number"> + Number of jams + </label> + </div> + <div class="uk-form-controls-text uk-form-controls"> + <input class="uk-input" id="number" name="number" placeholder="Jams to ban for / -1 for infinite"> + </div> + </div> + <div id="loading-spinner" class="uk-text-center uk-margin-small-top" hidden="hidden"> + <div uk-spinner></div> + </div> + </form> + </div> + + <div class="uk-modal-footer"> + <div class="uk-text-center"> + <button class="uk-button uk-button-danger uk-modal-close" type="button" id="state-cancel"> + <i class="uk-icon fa-fw far fa-times"></i> Cancel + </button> + <button class="uk-button uk-button-primary" type="button" id="state-submit" disabled> + <i class="uk-icon fa-fw far fa-check"></i> Save + </button> + </div> + </div> + </div> + </div> + + <script type="application/javascript"> + "use strict"; + const actions = new Actions("{{ url_for("staff.jams.action") }}", "{{ csrf_token() }}"); + + const table_body = document.getElementById("table-body"); + const table = document.getElementById("table"); + const no_infractions_paragraph = document.getElementById("no-infractions-paragraph"); + + let all_infractions = {{ infraction_ids | safe }}; + + const add_button = document.getElementById("add-button"); + const modal = UIkit.modal(document.getElementById("add-modal")); + const loading_spinner = document.getElementById("loading-spinner"); + + const input_user_id = document.getElementById("user-id"); + const input_reason = document.getElementById("reason"); + const input_number = document.getElementById("number"); + + const submit_button = document.getElementById("state-submit"); + + add_button.onclick = function() { + clearModal(); + checkModal(); + modal.show(); + }; + + input_user_id.oninput = function() { + if (isNaN(parseInt(this.value))) { + this.classList.add("uk-form-danger"); + } else { + this.classList.remove("uk-form-danger"); + } + + checkModal(); + }; + + input_reason.oninput = function() { + checkModal(); + }; + + input_number.oninput = function() { + if (isNaN(parseInt(this.value))) { + this.classList.add("uk-form-danger"); + } else { + this.classList.remove("uk-form-danger"); + } + + checkModal(); + }; + + submit_button.onclick = function () { + loading_spinner.removeAttribute("hidden"); + + let user_id = input_user_id.value; + let reason = input_reason.value; + let number = input_number.value; + + actions.create_infraction(user_id, reason, number, function(result, data) { + if (result) { + let infraction = { + "id": data.id, + "participant": user_id, + "reason": reason, + "number": number + }; + + addToTable(infraction); + modal.hide(); + clearModal(); + + UIkit.notification({ + "message": "Infraction added", + "status": "success", + "pos": "bottom-center", + "timeout": 5000, + }); + } else { + console.log(data); + UIkit.notification({ + "message": "Failed to add infraction", + "status": "danger", + "pos": "bottom-center", + "timeout": 5000, + }); + } + }); + }; + + function hookUpDeleteButtons() { + for (let element of document.getElementsByClassName("delete-infraction-button")) { + element.onclick = function() { + let infraction_id = this.getAttribute("data-infraction-id"); + let row = document.getElementById("row-" + infraction_id); + + actions.delete_infraction(infraction_id, function(result, data) { + if (result) { + table_body.removeChild(row); + + let index = all_infractions.indexOf(infraction_id); + + if (index < 0) { // We have a problem! + console.log("Unable to remove infraction from memory because it doesn't exist: " + infraction_id) + } else { + all_infractions.splice(index, 1); + } + + if (all_infractions.length < 1) { + table.setAttribute("hidden", "hidden"); + no_infractions_paragraph.removeAttribute("hidden"); + } + + UIkit.notification({ + "message": "Infraction removed", + "status": "success", + "pos": "bottom-center", + "timeout": 5000, + }); + } else { + console.log(data); + UIkit.notification({ + "message": "Failed to remove infraction", + "status": "danger", + "pos": "bottom-center", + "timeout": 5000, + }); + } + }) + } + } + } + + function clearModal() { + + // Existing question section + loading_spinner.setAttribute("hidden", "hidden"); + + input_number.value = ""; + input_reason.value = ""; + input_user_id.value = ""; + } + + function checkModal() { + if (input_reason.value.length < 1 + || input_number.value.length < 1 + || input_user_id.value.length < 1) { + return setButtonEnabled(false); + } + + if (isNaN(parseInt(input_number.value))) { + return setButtonEnabled(false); + } + + if (isNaN(parseInt(input_user_id.value))) { + return setButtonEnabled(false); + } + + return setButtonEnabled(true); + } + + function setButtonEnabled(enabled) { + submit_button.disabled = !enabled; + } + + function addToTable(infraction) { + console.log(infraction); + if (all_infractions.indexOf(infraction.id) === -1) { + all_infractions.push(infraction.id); + + let element = document.createElement("tr"); + element.id = "row-" + infraction.id; + element.innerHTML = getRowHTML(infraction); + + table_body.appendChild(element); + } + + if (all_infractions.length > 0) { + table.removeAttribute("hidden"); + no_infractions_paragraph.setAttribute("hidden", "hidden"); + } + hookUpDeleteButtons(); + } + + function getRowHTML(infraction) { + let participant; + + if (typeof infraction.participant === 'string' || infraction.participant instanceof String) { + participant = infraction.participant; + } else { + participant = infraction.participant; + participant = `${participant.username}#${participant.discrminiator} (${participant.id})` + } + const row = ` + <td class="uk-table-shrink"> + <button class="uk-button-small uk-button uk-button-danger delete-infraction-button" style="padding-left: 5px; padding-right: 5px;" data-question-id="${infraction.id}"><i class="uk-icon fa-fw far fa-times"></i></button> + </td> + <td class="uk-text-truncate" title="${infraction.id}">${infraction.id}</td> + <td class="uk-table-shrink">${participant}</td> + <td title="${infraction.reason}">${infraction.reason}</td> + <td class="uk-table-shrink">${infraction.number}</td> + `; + return row + } + + hookUpDeleteButtons(); + </script> +{% endblock %} |