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 %} | 
