diff --git a/Pipfile b/Pipfile index 04e23572..31379b86 100644 --- a/Pipfile +++ b/Pipfile @@ -17,18 +17,17 @@ flask = "*" flask-sqlalchemy = "*" flask-assets = "*" flask-session = "*" -flask-wtf = "*" [dev-packages] bandit = "*" pytest = "*" -pytest-tornado = "*" ipython = "*" ipdb = "*" pylint = "*" black = "*" pytest-watch = "*" factory-boy = "*" +pytest-flask = "*" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 5dcf8f6b..ead0c686 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e04e11d9bd5c1dcc725de48b20902f5c416417e73774e557e45af7bd0c147ff5" + "sha256": "9f17530cb96833c424369b9cac305cb43a817cdf19605aaedeb2d98566302857" }, "pipfile-spec": 6, "requires": { @@ -62,14 +62,6 @@ "index": "pypi", "version": "==2.3.2" }, - "flask-wtf": { - "hashes": [ - "sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36", - "sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac" - ], - "index": "pypi", - "version": "==0.14.2" - }, "itsdangerous": { "hashes": [ "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" @@ -168,7 +160,6 @@ "sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622", "sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5" ], - "markers": "python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*'", "version": "==2018.5" }, "redis": { @@ -350,9 +341,16 @@ "sha256:0e9a1227a3a0f3297a485715e72ee6eb77081b17b629367042b586e38c03c867", "sha256:b4840807a94a3bad0217d6ed3f9b65a1cc6e1db1c99e1184673056ae2c0a4c4d" ], - "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", "version": "==0.8.17" }, + "flask": { + "hashes": [ + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" + ], + "index": "pypi", + "version": "==1.0.2" + }, "gitdb2": { "hashes": [ "sha256:87783b7f4a8f6b71c7fe81d32179b3c8781c1a7d6fa0c69bff2f315b00aff4f8", @@ -395,9 +393,14 @@ "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" ], - "markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", "version": "==4.3.4" }, + "itsdangerous": { + "hashes": [ + "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" + ], + "version": "==0.24" + }, "jedi": { "hashes": [ "sha256:b409ed0f6913a701ed474a614a3bb46e6953639033e31f769ca7581da5bd1ec1", @@ -405,6 +408,13 @@ ], "version": "==0.12.1" }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, "lazy-object-proxy": { "hashes": [ "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", @@ -439,6 +449,12 @@ ], "version": "==1.3.1" }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "version": "==1.0" + }, "mccabe": { "hashes": [ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", @@ -494,7 +510,6 @@ "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" ], - "markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", "version": "==0.7.1" }, "prompt-toolkit": { @@ -517,7 +532,6 @@ "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" ], - "markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", "version": "==1.5.4" }, "pygments": { @@ -543,13 +557,13 @@ "index": "pypi", "version": "==3.7.0" }, - "pytest-tornado": { + "pytest-flask": { "hashes": [ - "sha256:214fc59d06fb81696fce3028b56dff522168ac1cfc784cfc0077b7b1e425b4cd", - "sha256:687c1f9c0f5bda7808c1e53c14bbebfe4fb9452e34cc95b440e598d4724265e0" + "sha256:2c5a36f9033ef8b6f85ddbefaebdd4f89197fc283f94b20dfe1a1beba4b77f03", + "sha256:657c7de386215ab0230bee4d76ace0339ae82fcbb34e134e17a29f65032eef03" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.10.0" }, "pytest-watch": { "hashes": [ @@ -567,11 +581,15 @@ }, "pyyaml": { "hashes": [ + "sha256:1cbc199009e78f92d9edf554be4fe40fb7b0bef71ba688602a00e97a51909110", "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb", "sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2", "sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76", + "sha256:6f89b5c95e93945b597776163403d47af72d243f366bf4622ff08bdfd1c950b7", "sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b", - "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b" + "sha256:be622cc81696e24d0836ba71f6272a2b5767669b0d79fdcf0295d51ac2e156c8", + "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b", + "sha256:f39411e380e2182ad33be039e8ee5770a5d9efe01a2bfb7ae58d9ba31c4a2a9d" ], "version": "==4.2b4" }, @@ -615,19 +633,6 @@ ], "version": "==0.9.4" }, - "tornado": { - "hashes": [ - "sha256:1c0816fc32b7d31b98781bd8ebc7a9726d7dce67407dc353a2e66e697e138448", - "sha256:4f66a2172cb947387193ca4c2c3e19131f1c70fa8be470ddbbd9317fd0801582", - "sha256:5327ba1a6c694e0149e7d9126426b3704b1d9d520852a3e4aa9fc8fe989e4046", - "sha256:6a7e8657618268bb007646b9eae7661d0b57f13efc94faa33cd2588eae5912c9", - "sha256:a9b14804783a1d77c0bd6c66f7a9b1196cbddfbdf8bceb64683c5ae60bd1ec6f", - "sha256:c58757e37c4a3172949c99099d4d5106e4d7b63aa0617f9bb24bfbff712c7866", - "sha256:d8984742ce86c0855cccecd5c6f54a9f7532c983947cff06f3a0e2115b47f85c" - ], - "index": "pypi", - "version": "==5.1" - }, "traitlets": { "hashes": [ "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", @@ -684,6 +689,13 @@ ], "version": "==0.1.7" }, + "werkzeug": { + "hashes": [ + "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", + "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + ], + "version": "==0.14.1" + }, "wrapt": { "hashes": [ "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" diff --git a/README.md b/README.md index 455d5baa..5bfee424 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,12 @@ To log in as one of them, navigate to `/login-dev?username=`. Fo ## Testing +Tests require a test database: + +``` +createdb atat_test +``` + To run lint, static analysis, and unit tests: script/test diff --git a/atst/app.py b/atst/app.py index 1553679f..17063840 100644 --- a/atst/app.py +++ b/atst/app.py @@ -12,7 +12,7 @@ from atst.routes.workspaces import bp as workspace_routes from atst.routes.requests import requests_bp -ENV = os.getenv("TORNADO_ENV", "dev") +ENV = os.getenv("FLASK_ENV", "dev") def make_app(config): @@ -40,7 +40,7 @@ def make_app(config): def make_flask_callbacks(app): @app.before_request - def set_globals(): + def _set_globals(): g.navigationContext = ( "workspace" if re.match("\/workspaces\/[A-Za-z0-9]*", request.url) @@ -84,6 +84,10 @@ def make_config(): config = ConfigParser() config.optionxform = str + config_files = [BASE_CONFIG_FILENAME, ENV_CONFIG_FILENAME] + if OVERRIDE_CONFIG_FILENAME: + config_files.append(OVERRIDE_CONFIG_FILENAME) + config_files = [BASE_CONFIG_FILENAME, ENV_CONFIG_FILENAME] if OVERRIDE_CONFIG_FILENAME: config_files.append(OVERRIDE_CONFIG_FILENAME) diff --git a/atst/assets.py b/atst/assets.py index 6c8fd7e8..6d8351a6 100644 --- a/atst/assets.py +++ b/atst/assets.py @@ -1,5 +1,4 @@ from flask_assets import Environment, Bundle -from atst.home import home environment = Environment() diff --git a/atst/domain/pe_numbers.py b/atst/domain/pe_numbers.py index de362d20..62f8326a 100644 --- a/atst/domain/pe_numbers.py +++ b/atst/domain/pe_numbers.py @@ -1,24 +1,25 @@ from sqlalchemy.dialects.postgresql import insert +from atst.database import db from atst.models.pe_number import PENumber from .exceptions import NotFoundError class PENumbers(object): - def __init__(self, db_session): - self.db_session = db_session - def get(self, number): - pe_number = self.db_session.query(PENumber).get(number) + @classmethod + def get(cls, number): + pe_number = db.session.query(PENumber).get(number) if not pe_number: raise NotFoundError("pe_number") return pe_number - def create_many(self, list_of_pe_numbers): + @classmethod + def create_many(cls, list_of_pe_numbers): stmt = insert(PENumber).values(list_of_pe_numbers) do_update = stmt.on_conflict_do_update( index_elements=["number"], set_=dict(description=stmt.excluded.description) ) - self.db_session.execute(do_update) - self.db_session.commit() + db.session.execute(do_update) + db.session.commit() diff --git a/atst/domain/requests.py b/atst/domain/requests.py index cdb7f5c0..5c04d3c7 100644 --- a/atst/domain/requests.py +++ b/atst/domain/requests.py @@ -1,4 +1,3 @@ -import tornado.gen from sqlalchemy import exists, and_ from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.attributes import flag_modified diff --git a/atst/forms/financial.py b/atst/forms/financial.py index 240a8274..68196837 100644 --- a/atst/forms/financial.py +++ b/atst/forms/financial.py @@ -1,12 +1,11 @@ import re -import tornado -from tornado.gen import Return from wtforms.fields.html5 import EmailField from wtforms.fields import StringField, SelectField from wtforms.form import Form from wtforms.validators import Required, Email from atst.domain.exceptions import NotFoundError +from atst.domain.pe_numbers import PENumbers from .fields import NewlineListField from .forms import ValidatedForm @@ -40,9 +39,9 @@ def suggest_pe_id(pe_id): return None -def validate_pe_id(field, existing_request, pe_numbers_repo): +def validate_pe_id(field, existing_request): try: - pe_number = pe_numbers_repo.get(field.data) + pe_number = PENumbers.get(field.data) except NotFoundError: suggestion = suggest_pe_id(field.data) error_str = ( @@ -50,17 +49,17 @@ def validate_pe_id(field, existing_request, pe_numbers_repo): "If you have double checked it you can submit anyway. " "Your request will need to go through a manual review." ).format('Did you mean "{}"? '.format(suggestion) if suggestion else "") - field.errors.append(error_str) + field.errors += (error_str,) return False return True class FinancialForm(ValidatedForm): - def perform_extra_validation(self, existing_request, pe_numbers_repo): + def perform_extra_validation(self, existing_request): valid = True if not existing_request or existing_request.get("pe_id") != self.pe_id.data: - valid = yield validate_pe_id(self.pe_id, existing_request, pe_numbers_repo) + valid = validate_pe_id(self.pe_id, existing_request) return valid task_order_id = StringField( diff --git a/atst/handlers/request_financial_verification.py b/atst/handlers/request_financial_verification.py index ea4dcb35..e6061c39 100644 --- a/atst/handlers/request_financial_verification.py +++ b/atst/handlers/request_financial_verification.py @@ -47,6 +47,7 @@ class RequestFinancialVerification(BaseHandler): if form.validate(): yield self.update_request(request_id, form.data) + # pylint: disable=E1121 valid = yield form.perform_extra_validation( existing_request.body.get("financial_verification"), self.pe_numbers_repo, diff --git a/atst/routes/__init__.py b/atst/routes/__init__.py index ab7ec866..3c1f99ac 100644 --- a/atst/routes/__init__.py +++ b/atst/routes/__init__.py @@ -7,6 +7,11 @@ bp = Blueprint("atst", __name__) @bp.route("/") +def root(): + return render_template("root.html") + + +@bp.route("/home") def home(): return render_template("home.html") @@ -14,3 +19,8 @@ def home(): @bp.route("/styleguide") def styleguide(): return render_template("styleguide.html") + + +@bp.route('/') +def catch_all(path): + return render_template("{}.html".format(path)) diff --git a/atst/routes/requests/financial_verification.py b/atst/routes/requests/financial_verification.py index 4471b524..38420287 100644 --- a/atst/routes/requests/financial_verification.py +++ b/atst/routes/requests/financial_verification.py @@ -1,4 +1,5 @@ from flask import render_template, redirect, url_for +from flask import request as http_request from . import requests_bp from atst.domain.requests import Requests @@ -29,7 +30,7 @@ def update_financial_verification(request_id): existing_request.body.get("financial_verification") ) if valid: - redirect(url_for("requests.financial_verification_submitted")) + return redirect(url_for("requests.financial_verification_submitted")) else: return render_template( "requests/financial_verification.html", **rerender_args diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py index de60fb29..e1c646e6 100644 --- a/atst/routes/requests/jedi_request_flow.py +++ b/atst/routes/requests/jedi_request_flow.py @@ -1,7 +1,6 @@ from collections import defaultdict from atst.domain.requests import Requests -from atst.forms.financial import FinancialForm from atst.forms.request import RequestForm from atst.forms.org import OrgForm from atst.forms.poc import POCForm diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 43b3b050..e614c08c 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -1,7 +1,7 @@ from flask import Blueprint, render_template from atst.domain.workspaces import Projects, Members -from atst.database import db + bp = Blueprint("workspaces", __name__) diff --git a/atst/ui_methods.py b/atst/ui_methods.py index c863ee55..a3ccbbba 100644 --- a/atst/ui_methods.py +++ b/atst/ui_methods.py @@ -11,7 +11,7 @@ def navigationContext(self): def dev(self): - return os.getenv("TORNADO_ENV", "dev") == "dev" + return os.getenv("FLASK_ENV", "dev") == "dev" def matchesPath(self, href): diff --git a/config/test.ini b/config/test.ini new file mode 100644 index 00000000..0074f60b --- /dev/null +++ b/config/test.ini @@ -0,0 +1,2 @@ +[default] +PGDATABASE = atat_test diff --git a/script/test b/script/test index 1f6c3380..ceb1c5c2 100755 --- a/script/test +++ b/script/test @@ -4,6 +4,8 @@ source "$(dirname "${0}")"/../script/include/global_header.inc.sh +export FLASK_ENV=test + # Define all relevant python files and directories for this app PYTHON_FILES="./app.py ./atst ./config" diff --git a/templates/calculator.html.to b/templates/calculator.html similarity index 69% rename from templates/calculator.html.to rename to templates/calculator.html index bf19f0a5..4f1c9a3d 100644 --- a/templates/calculator.html.to +++ b/templates/calculator.html @@ -1,4 +1,4 @@ -{% extends "base.html.to" %} +{% extends "base.html" %} {% block content %} @@ -8,5 +8,5 @@ -{% end %} +{% endblock %} diff --git a/templates/member_edit.html.to b/templates/member_edit.html.to index 24893bf2..d454152c 100644 --- a/templates/member_edit.html.to +++ b/templates/member_edit.html.to @@ -16,40 +16,39 @@ level="info" ) %} -
-
-

{{ member_name }}

-
Workspace Role
{{member_workspace_role}}
-
-
-
-
-
DOD ID:
-
{{ member_id }}
-
-
-
Email:
-
{{ member_email }}
-
-
- edit account details + +
+
+

+ {% if is_new_member %} + Add new member + {% else %} + {{ member_name }} + {% end %} +

+
Workspace Role {{member_workspace_role}}
-