From 2fbca637886c9c36cfaab163f8d5d2e880d96bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Sat, 19 Nov 2022 12:01:12 +0100 Subject: [PATCH 01/12] Minor style fix --- form_manager/config.py | 2 +- form_manager/user.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/form_manager/config.py b/form_manager/config.py index 8c8558f..abbe43e 100644 --- a/form_manager/config.py +++ b/form_manager/config.py @@ -30,6 +30,6 @@ class Config(object): MAIL_USE_SSL = False MAIL_DEFAULT_SENDER = ("Form Manager", "forms@example.com") - USER_FILTER = {} # Limits to users that may use the system, e.g. {"email": ["scilifelab.se"]} + USER_FILTER = {} # Limits to users that may use the system, e.g. {"email": ["scilifelab.se"]} REVERSE_PROXY = False # Behind a reverse proxy, use X_Forwarded-For to get the ip diff --git a/form_manager/user.py b/form_manager/user.py index 479340f..2c817b1 100644 --- a/form_manager/user.py +++ b/form_manager/user.py @@ -17,7 +17,7 @@ def user_info(): @blueprint.route("/login") def oidc_login(): """Perform a login using OpenID Connect.""" - redirect_uri = flask.url_for("user.oidc_authorize", _external=True) + redirect_uri = flask.url_for("user.oidc_authorize", _external=True) return oauth.oidc_entry.authorize_redirect(redirect_uri) @@ -31,7 +31,7 @@ def oidc_authorize(): if domain_limit: user_email = token.get("userinfo", {}).get("email") try: - domain = user_email[user_email.index('@')+1:] + domain = user_email[user_email.index("@") + 1 :] except ValueError: flask.abort(400) if domain not in domain_limit: From c07806ebac1a3f52833f0529436e4e97f7e44caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Tue, 22 Nov 2022 13:35:05 +0100 Subject: [PATCH 02/12] Add function for adding variables to template --- form_manager/utils.py | 23 +++++++++++++++++++++++ test/__init__.py | 0 test/test_utils.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 test/__init__.py create mode 100644 test/test_utils.py diff --git a/form_manager/utils.py b/form_manager/utils.py index 42ab68c..31f99c4 100644 --- a/form_manager/utils.py +++ b/form_manager/utils.py @@ -2,6 +2,7 @@ from datetime import datetime import functools import os +import re import secrets import flask @@ -111,3 +112,25 @@ def has_form_access(username, entry): bool: Whether the user has access. """ return username in entry["owners"] + + +def apply_template(template: str, data: dict) -> str: + """ + Fill a template with the values of the defined variables. + + Variables are entered as ``{{ variable }}``. + Currently using simple text replacement, but may use Jinja in the future. + + Args: + template (str): The template. + data (dict): The variables to use. + + Returns: + str: The resulting text. + """ + possible_inserts = re.findall(r"{{ (.+?) }}", template) + for ins in possible_inserts: + if data.get(ins): + template = template.replace(f"{{ {ins} }}", data[ins]) + + return template diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_utils.py b/test/test_utils.py new file mode 100644 index 0000000..c1ebc08 --- /dev/null +++ b/test/test_utils.py @@ -0,0 +1,30 @@ +"""Test functions from utils.py""" + +from form_manager import utils + +BASE_TEMPLATE = """{{ val }} random text here 12345 {{ }}. +more text here {{ asd }} {{ val }} +{{ + bad_variable +}} + +Text here {{ {{ spec_val }} }} + +{{ variable }}""" + + +def test_apply_template(): + """Test apply_template().""" + data = { + "val": "hit_1", + "asd": "hit_2", + "variable": "hit_3", + "spec_val": "unique", + "bad_variable": "BAD", + } + + res = utils.apply_template(BASE_TEMPLATE, data) + assert res.count("hit_1") == 2 + assert res.count("hit_2") == 1 + assert "BAD" not in data + assert "unique }}" not in data From 9167880044e574c90a2566879fef557a58304ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Tue, 22 Nov 2022 13:36:11 +0100 Subject: [PATCH 03/12] Add black check --- .github/workflows/python-black.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/python-black.yml diff --git a/.github/workflows/python-black.yml b/.github/workflows/python-black.yml new file mode 100644 index 0000000..1085bbe --- /dev/null +++ b/.github/workflows/python-black.yml @@ -0,0 +1,23 @@ +--- +# Perform Python code linting using black +# Remember to change "./code-folder" to the folder with the code +name: Black formatting + +on: [push, pull_request] + +jobs: + BlackFormatting: + concurrency: + group: ${{ github.ref }}-black + cancel-in-progress: true + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v3 + + - name: Check code formatting with Black + uses: psf/black@stable + with: + options: "-l 100 --check" + src: "./form_manager" From 466e423dc4de83ce3eb0bffb887ba1109937fd0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Tue, 22 Nov 2022 14:00:16 +0100 Subject: [PATCH 04/12] Move property back into tag --- frontend/src/components/StringListEditor.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/StringListEditor.vue b/frontend/src/components/StringListEditor.vue index b2a7649..3af12c9 100644 --- a/frontend/src/components/StringListEditor.vue +++ b/frontend/src/components/StringListEditor.vue @@ -8,8 +8,9 @@ hide-bottom-space outlined :label="fieldTitle" - :rules="[ function (val) { return (evaluateValue(val) || val.length === 0) || 'No whitespace at beginning nor end and must not already exist.' }]"> + :rules="[ function (val) { return (evaluateValue(val) || val.length === 0) || 'No whitespace at beginning nor end and must not already exist.' }]" @keyup.enter="addValue" + > + + + +
Edit Email Template
+ +
+ + + + + +
+
+ + + + + Are you sure you want to delete this form? + + + + + + + + + @@ -197,10 +280,15 @@ export default defineComponent({ return { entries: [], editData: {}, + isDeleting: false, filter: '', loading: false, loadError: false, - saveError: false, + showEditTemplateDialog: false, + showDeleteWarning: false, + currrentEditTemplate: {}, + currrentEditTemplateText: "", + currrentEditTemplateType: "", pagination: { rowsPerPage: 20 }, @@ -266,7 +354,7 @@ export default defineComponent({ this.$router.push({name: 'FormResponses', params: {identifier: identifier}}); }, deleteForm(entry) { - console.log(this.$q.cookies) + this.isDeleting = true; this.$axios .delete('/api/v1/form/' + entry.row.identifier, {headers: {'X-CSRFToken': this.$q.cookies.get('_csrf_token')}}) @@ -275,6 +363,7 @@ export default defineComponent({ delete this.editData[entry.row.identifier]; this.getEntries(); }) + .finally(() => this.isDeleting = false); }, addForm() { this.$axios @@ -282,22 +371,23 @@ export default defineComponent({ .then(() => this.getEntries()) }, expandItem(entry) { - console.log(entry) entry.expand = !entry.expand; if (!(entry.key in this.editData)) { this.editData[entry.key] = { title: entry.row.title, recaptcha_secret: entry.row.recaptcha_secret, email_recipients: JSON.parse(JSON.stringify(entry.row.email_recipients)), + email_custom: entry.row.email_custom, + email_text_template: entry.row.email_text_template, + email_html_template: entry.row.email_html_template, + email_title: entry.row.email_title, owners: JSON.parse(JSON.stringify(entry.row.owners)), redirect: entry.row.redirect, saving: false, - saveError: false, } } }, saveEdit(entry) { - this.saveError = false; this.editData[entry.key].saving = true; this.editData[entry.key].saveError = false; let outgoing = JSON.parse(JSON.stringify(this.editData[entry.key])); @@ -310,13 +400,27 @@ export default defineComponent({ delete this.editData[entry.key]; this.getEntries(); }) - .catch((err) => this.editData[entry.key].saveError = true) - .finally(() => this.editData[entry.key].saving = false); + .catch((err) => { + this.editData[entry.key].saveError = true + this.editData[entry.key].saving = false + }) }, cancelEdit(entry) { entry.expand = false; delete this.editData[entry.key]; }, + openTemplateDialog(entry, type) { + this.showEditTemplateDialog = true; + let prop = "email_" + type + "_template"; + this.currentEditTemplate = entry; + console.log(this.currentEditTemplate) + this.currentEditTemplateType = prop; + this.currentEditTemplateText = entry[prop]; + }, + saveTemplate() { + this.currentEditTemplate[this.currentEditTemplateType] = this.currentEditTemplateText; + this.showEditTemplateDialog = false; + }, }, }) From a4af3bf76f10b6daa5b3e010d728694a0632de84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 24 Nov 2022 15:31:22 +0100 Subject: [PATCH 11/12] Add delete confirmation for forms --- frontend/src/pages/FormBrowser.vue | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/FormBrowser.vue b/frontend/src/pages/FormBrowser.vue index 5249085..5422c54 100644 --- a/frontend/src/pages/FormBrowser.vue +++ b/frontend/src/pages/FormBrowser.vue @@ -202,7 +202,7 @@ size="md" icon="delete" color="negative" - @click="deleteForm(props)" /> + @click="confirmDelete(props)" /> Save failed @@ -252,7 +252,7 @@ label="Delete" color="negative" class="user-edit-confirm-delete" - @click="deleteEntry" /> + @click="deleteForm" /> { - entry.expand = false; - delete this.editData[entry.row.identifier]; + this.toDelete.expand = false; + this.showDeleteWarning = false; + delete this.editData[this.toDelete.row.identifier]; this.getEntries(); }) .finally(() => this.isDeleting = false); From 2e528f5f257613e12aec92b9475f6354a468af5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 24 Nov 2022 15:33:18 +0100 Subject: [PATCH 12/12] Fix response for delete requests --- form_manager/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/form_manager/forms.py b/form_manager/forms.py index 4402d4a..092436f 100644 --- a/form_manager/forms.py +++ b/form_manager/forms.py @@ -157,7 +157,7 @@ def delete_form(identifier: str): flask.abort(code=403) flask.g.db["forms"].delete_one(entry) flask.g.db["submissions"].delete_many({"identifier": entry["identifier"]}) - return flask.Submission(code=200) + return "" @csrf.exempt