From 6ccc522259ba857f9f290217f58824bd808370a6 Mon Sep 17 00:00:00 2001 From: Tim Baxter <32077682+tbaxter-18f@users.noreply.github.com> Date: Thu, 25 Oct 2018 16:16:59 -0500 Subject: [PATCH] 836 Django 2.0 Upgrade (#840) * Dependency updates * Updated pipfile * Flake8 cleanup --- Pipfile | 9 +- Pipfile.lock | 26 +- docs/local-development.md | 11 + package-lock.json | 450 ++++++++++++++++++--------- package.json | 8 +- requirements.txt | 63 ++-- tock/api/tests.py | 76 +++-- tock/api/urls.py | 30 +- tock/api/views.py | 35 ++- tock/employees/urls.py | 13 +- tock/employees/views.py | 1 - tock/hours/tests/test_views.py | 29 +- tock/hours/urls/reports.py | 52 ++-- tock/hours/urls/timesheets.py | 21 +- tock/hours/views.py | 26 +- tock/projects/models.py | 3 +- tock/projects/urls.py | 16 +- tock/tock/middleware.py | 2 +- tock/tock/settings/base.py | 1 - tock/tock/tests/test_middleware.py | 1 + tock/tock/tests/test_url_auth.py | 49 +-- tock/tock/tests/test_views.py | 10 +- tock/tock/urls.py | 44 +-- tock/tock/views.py | 48 +-- tock/utilization/tests/test_views.py | 8 +- tock/utilization/urls.py | 12 +- 26 files changed, 582 insertions(+), 462 deletions(-) diff --git a/Pipfile b/Pipfile index 5f504754c..b721e84e9 100644 --- a/Pipfile +++ b/Pipfile @@ -4,12 +4,12 @@ verify_ssl = true name = "pypi" [packages] -django = "==1.11.*" +django = "~=2.0" dj-database-url = "*" gevent = "*" gunicorn = "*" whitenoise = "*" -djangorestframework = "==3.6.*" +djangorestframework = "~=3.8" djangorestframework-csv = "*" bleach = "*" newrelic = "*" @@ -17,12 +17,11 @@ cfenv = "*" cg-django-uaa = "*" "psycopg2-binary" = "*" markdown = "*" -django-webtest = "==1.9.2" +django-webtest = "~=1.9" [dev-packages] bandit = "*" coverage = "*" -django = "==1.11.*" django-debug-toolbar = "*" django-webtest = "*" factory-boy = "*" @@ -31,4 +30,4 @@ nplusone = "*" codecov = "*" [requires] -python_version = "3.6" +python_version = "3.6" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index 83176cb68..a2690f203 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ecbd987d074c8e705485a2c0a71d67c8f414e0db74a5572a495003357e92c3b9" + "sha256": "565fdc5d1f74b8ad4e2435c6f0cd041df300651cf6e1c16f4b669718163e5d05" }, "host-environment-markers": { "implementation_name": "cpython", @@ -80,24 +80,24 @@ }, "django": { "hashes": [ - "sha256:37f5876c1fbfd66085001f4c06fa0bf96ef05442c53daf8d4294b6f29e7fa6b8", - "sha256:29268cc47816a44f27308e60f71da635f549c47d8a1d003b28de55141df75791" + "sha256:25df265e1fdb74f7e7305a1de620a84681bcc9c05e84a3ed97e4a1a63024f18d", + "sha256:d6d94554abc82ca37e447c3d28958f5ac39bd7d4adaa285543ae97fb1129fd69" ], - "version": "==1.11.16" + "version": "==2.0.9" }, "django-webtest": { "hashes": [ - "sha256:28f41acbd1b9eef4cfc2d312b12f6fe93c3ffce6de925c378abae7778e0b3eff", - "sha256:11c32547966917281fb487203a85b28c313670c1874a3d4d99a3632a123a33c4" + "sha256:f6a3eb8525c0b8de60484f4d41f3d7a704017b9e010639d206f42a7349cb6904", + "sha256:63492bbcabb961311876da9a49cfc282e1668d8d1cf63a5700fc65ca2d1f7c8a" ], - "version": "==1.9.2" + "version": "==1.9.3" }, "djangorestframework": { "hashes": [ - "sha256:0f9bfbac702f3376dfb2db4971ad8af4e066bfa35393b1b85e085f7e8b91189a", - "sha256:de8ac68b3cf6dd41b98e01dcc92dc0022a5958f096eafc181a17fa975d18ca42" + "sha256:63f76cbe1e7d12b94c357d7e54401103b2e52aef0f7c1650d6c820ad708776e5", + "sha256:607865b0bb1598b153793892101d881466bd5a991de12bd6229abb18b1c86136" ], - "version": "==3.6.4" + "version": "==3.9.0" }, "djangorestframework-csv": { "hashes": [ @@ -402,10 +402,10 @@ }, "django": { "hashes": [ - "sha256:37f5876c1fbfd66085001f4c06fa0bf96ef05442c53daf8d4294b6f29e7fa6b8", - "sha256:29268cc47816a44f27308e60f71da635f549c47d8a1d003b28de55141df75791" + "sha256:acdcc1f61fdb0a0c82a1d3bf1879a414e7732ea894a7632af7f6d66ec7ab5bb3", + "sha256:efbcad7ebb47daafbcead109b38a5bd519a3c3cd92c6ed0f691ff97fcdd16b45" ], - "version": "==1.11.16" + "version": "==2.1.2" }, "django-debug-toolbar": { "hashes": [ diff --git a/docs/local-development.md b/docs/local-development.md index c6286d2e7..eb6ee0517 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -85,6 +85,17 @@ container: `Control + p, Control + q`, one after another. If you mistakenly hit `Control + c`, you will kill the `tock_app` container! In that case, restart the `tock_app` container with a `docker-compose up` command. + +### Testing locally + +The easiest, most reliable way to test locally is from within the docker container, +which lets you access `manage.py`: + +``` +docker-compose run app bash +python manage.py test +``` + ### Making CSS changes `docker-compose up` will also launch a [Node] machine that compiles the [Sass] diff --git a/package-lock.json b/package-lock.json index 8478158b3..4fa55c0af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,15 +7,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.0.tgz", "integrity": "sha512-7IGHZQfRfa0bCd7zUBVUGFKFn31SpaLDFfNoCAqkTGQO5JlHC9BwQA/CG9KZlABFxIUtXznyFgechjPQEGrUTg==" }, - "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -42,6 +33,17 @@ } } }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -211,9 +213,9 @@ "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.4.tgz", "integrity": "sha512-Q4Rvn7P6ObyWfc4stqLWHtG1MJ8vVtjgT24Zbu+8UTzxYuZouqZsmNRRTFVMY/Ux0eIKv1d+JWzsInTX+fdHPQ==", "requires": { - "JSONStream": "1.3.2", "combine-source-map": "0.8.0", "defined": "1.0.0", + "JSONStream": "1.3.2", "safe-buffer": "5.1.1", "through2": "2.0.3", "umd": "3.0.3" @@ -239,7 +241,6 @@ "resolved": "https://registry.npmjs.org/browserify/-/browserify-13.3.0.tgz", "integrity": "sha1-tanJAgJD8McORnW+yCI7xifkFc4=", "requires": { - "JSONStream": "1.3.2", "assert": "1.4.1", "browser-pack": "6.0.4", "browser-resolve": "1.11.2", @@ -261,6 +262,7 @@ "https-browserify": "0.0.1", "inherits": "2.0.3", "insert-module-globals": "7.0.4", + "JSONStream": "1.3.2", "labeled-stream-splicer": "2.0.1", "module-deps": "4.1.1", "os-browserify": "0.1.2", @@ -426,9 +428,9 @@ } }, "chosen-js": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/chosen-js/-/chosen-js-1.8.3.tgz", - "integrity": "sha512-B1ruoi+9Y51W9lcw5BXBOWdWGrEGyk7sAPlBYn3tmmUKM/JelfFCrhR70tSAZ4YZlfGBdrbXTCHWxpHS/GwGCg==" + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/chosen-js/-/chosen-js-1.8.7.tgz", + "integrity": "sha512-eVdrZJ2U5ISdObkgsi0od5vIJdLwq1P1Xa/Vj/mgxkMZf14DlgobfB6nrlFi3kW4kkvKLsKk4NDqZj1MU1DCpw==" }, "cipher-base": { "version": "1.0.4", @@ -454,6 +456,11 @@ "wrap-ansi": "2.1.0" } }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -793,32 +800,6 @@ "safe-buffer": "5.1.1" } }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - } - } - }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -829,6 +810,16 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -968,6 +959,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, "har-validator": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", @@ -1125,10 +1121,10 @@ "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.4.tgz", "integrity": "sha512-Z/sfx2KOKyHQ3U4X3fnXn4Ms1Opa9pGvEfm8j6xKHE6oVqc1dMwVgBVxmj3yIrMtWTl8HYoy12rkhrR8MYym6A==", "requires": { - "JSONStream": "1.3.2", "combine-source-map": "0.7.2", "concat-stream": "1.6.2", "is-buffer": "1.1.6", + "JSONStream": "1.3.2", "lexical-scope": "1.2.0", "process": "0.11.10", "through2": "2.0.3", @@ -1276,6 +1272,11 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, "json-stable-stringify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", @@ -1304,6 +1305,15 @@ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" }, + "JSONStream": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1372,22 +1382,6 @@ "strip-bom": "2.0.0" } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - } - } - }, "lodash": { "version": "4.17.5", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", @@ -1436,6 +1430,14 @@ "yallist": "2.1.2" } }, + "map-age-cleaner": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", + "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "requires": { + "p-defer": "1.0.0" + } + }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", @@ -1466,14 +1468,6 @@ } } }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "requires": { - "mimic-fn": "1.2.0" - } - }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -1561,7 +1555,6 @@ "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", "requires": { - "JSONStream": "1.3.2", "browser-resolve": "1.11.2", "cached-path-relative": "1.0.1", "concat-stream": "1.5.2", @@ -1569,6 +1562,7 @@ "detective": "4.7.1", "duplexer2": "0.1.4", "inherits": "2.0.3", + "JSONStream": "1.3.2", "parents": "1.0.1", "readable-stream": "2.3.3", "resolve": "1.6.0", @@ -1583,6 +1577,11 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "node-gyp": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", @@ -1611,9 +1610,9 @@ } }, "node-sass": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.8.3.tgz", - "integrity": "sha512-tfFWhUsCk/Y19zarDcPo5xpj+IW3qCfOjVdHtYeG6S1CKbQOh1zqylnQK6cV3z9k80yxAnFX9Y+a9+XysDhhfg==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.1.tgz", + "integrity": "sha512-m6H1I6cHXsHsJ7BIWdnJsz9S9gVMyh+/H2cOTXgl2/2WqyyWlBcl4PHJcqrXo5RZVCfCUFqOtjPN0+0XbVHR5Q==", "requires": { "async-foreach": "0.1.3", "chalk": "1.1.3", @@ -1630,10 +1629,96 @@ "nan": "2.10.0", "node-gyp": "3.6.2", "npmlog": "4.1.2", - "request": "2.79.0", + "request": "2.87.0", "sass-graph": "2.2.4", "stdout-stream": "1.4.0", "true-case-path": "1.0.2" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.0.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.1", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + } } }, "nopt": { @@ -1729,31 +1814,20 @@ "os-tmpdir": "1.0.2" } }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, - "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "1.2.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "p-is-promise": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" }, "pako": { "version": "0.2.9", @@ -1843,6 +1917,11 @@ "sha.js": "2.4.11" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -2253,6 +2332,14 @@ "readable-stream": "2.3.3" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -2263,14 +2350,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -2446,9 +2525,9 @@ } }, "uswds": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/uswds/-/uswds-1.6.1.tgz", - "integrity": "sha512-h7DvkJKOwlCWTT106seB+tJxGNejZ+i3tn/OBs8o3an3lQs0q1oJKu5IgR3t0wsEXZt3zghHvVhmU7vbQjvOQQ==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/uswds/-/uswds-1.6.9.tgz", + "integrity": "sha512-k4Sai/5X95Mg3VxLJffg9fTynUEhqiC15Rr+PlwZd4MbR8vnq+eBECNi27Iky7WncwMXg/29ubXAg9eli2jUXQ==", "requires": { "@types/node": "8.10.0", "array-filter": "1.0.0", @@ -2462,7 +2541,7 @@ "receptor": "1.0.0", "resolve-id-refs": "0.1.0", "typescript": "2.8.1", - "yargs": "8.0.2" + "yargs": "12.0.2" }, "dependencies": { "ansi-regex": { @@ -2475,67 +2554,131 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.0", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "requires": { + "xregexp": "4.0.0" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "requires": { + "cross-spawn": "6.0.5", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { - "locate-path": "2.0.0" + "locate-path": "3.0.0" } }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, - "load-json-file": { + "lcid": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" + "invert-kv": "2.0.0" } }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "requires": { - "pify": "2.3.0" + "map-age-cleaner": "0.1.2", + "mimic-fn": "1.2.0", + "p-is-promise": "1.1.0" } }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "os-locale": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" + "execa": "0.10.0", + "lcid": "2.0.0", + "mem": "4.0.0" } }, - "read-pkg-up": { + "p-limit": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "p-try": "2.0.0" } }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -2553,40 +2696,34 @@ "ansi-regex": "3.0.0" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", + "cliui": "4.1.0", + "decamelize": "2.0.0", + "find-up": "3.0.0", "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", + "os-locale": "3.0.1", "require-directory": "2.1.1", "require-main-filename": "1.0.1", "set-blocking": "2.0.0", "string-width": "2.1.1", "which-module": "2.0.0", "y18n": "3.2.1", - "yargs-parser": "7.0.0" + "yargs-parser": "10.1.0" } }, "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "requires": { "camelcase": "4.1.0" } @@ -2687,6 +2824,11 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index c641d9929..7afaac535 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "node": ">=8.10.0" }, "dependencies": { - "chosen-js": "^1.8.3", - "jquery": "3.3.x", - "node-sass": "4.9.1", - "uswds": "^1.6.1" + "chosen-js": "^1.8.7", + "jquery": "^3.3.1", + "node-sass": "^4.9.1", + "uswds": "^1.6.9" } } diff --git a/requirements.txt b/requirements.txt index 5a7ad9ec2..0ac3b5fa9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,32 +1,31 @@ -beautifulsoup4==4.6.0 -bleach==2.1.3 -certifi==2018.4.16 -cfenv==0.5.3 -cg-django-uaa==1.3.0 -chardet==3.0.4 -dj-database-url==0.5.0 -django-webtest==1.9.2 -django==1.11.13 -djangorestframework-csv==2.1.0 -djangorestframework==3.6.4 -furl==1.0.1 -gevent==1.3.0 -greenlet==0.4.13; platform_python_implementation == 'cpython' -gunicorn==19.8.1 -html5lib==1.0.1 -idna==2.6 -markdown==2.6.11 -newrelic==3.2.0.91 -orderedmultidict==0.7.11 -psycopg2-binary==2.7.4 -pyjwt==1.6.1 -pytz==2018.4 -requests==2.18.4 -six==1.11.0 -unicodecsv==0.14.1 -urllib3==1.22 -waitress==1.1.0 -webencodings==0.5.1 -webob==1.8.1 -webtest==2.0.29 -whitenoise==3.3.1 +beautifulsoup4==4.6.3 --hash=sha256:f0abd31228055d698bb392a826528ea08ebb9959e6bea17c606fd9c9009db938 --hash=sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57 --hash=sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10 +bleach==3.0.2 --hash=sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9 --hash=sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718 +certifi==2018.10.15 --hash=sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c --hash=sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a +cfenv==0.5.3 --hash=sha256:7815bffcc4a3db350f92517157fafc577c11b5a7ff172dc5632f1042b93073e8 --hash=sha256:c7a91a4c82431acfc35db664c194d5e6cc7f4df3dcb692d0f836a6ceb0156167 +cg-django-uaa==1.3.0 --hash=sha256:a12e502729b45e2b0d35e37e8fd2c6dadbd448411f883776071dcb91051bd81e +chardet==3.0.4 --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae +dj-database-url==0.5.0 --hash=sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9 --hash=sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163 +django==2.0.9 --hash=sha256:25df265e1fdb74f7e7305a1de620a84681bcc9c05e84a3ed97e4a1a63024f18d --hash=sha256:d6d94554abc82ca37e447c3d28958f5ac39bd7d4adaa285543ae97fb1129fd69 +django-webtest==1.9.3 --hash=sha256:f6a3eb8525c0b8de60484f4d41f3d7a704017b9e010639d206f42a7349cb6904 --hash=sha256:63492bbcabb961311876da9a49cfc282e1668d8d1cf63a5700fc65ca2d1f7c8a +djangorestframework==3.9.0 --hash=sha256:63f76cbe1e7d12b94c357d7e54401103b2e52aef0f7c1650d6c820ad708776e5 --hash=sha256:607865b0bb1598b153793892101d881466bd5a991de12bd6229abb18b1c86136 +djangorestframework-csv==2.1.0 --hash=sha256:2f008b20a44f2d3c37835ea5b5ddfe19f54394f07b9cb267c616a917a7f7e27c +furl==2.0.0 --hash=sha256:f7e90e9f85ef3f2e64485f04c2a80b50af6133942812fd87a44d45305b079018 --hash=sha256:fdcaedc1fb19a63d7d875b0105b0a5b496dd0989330d454a42bcb401fa5454ec +gevent==1.3.7 --hash=sha256:419fd562e4b94b91b58cccb3bd3f17e1a11f6162fca6c591a7822bc8a68f023d --hash=sha256:7c899e5a6f94d6310352716740f05e41eb8c52d995f27fc01e90380913aa8f22 --hash=sha256:a66a26b78d90d7c4e9bf9efb2b2bd0af49234604ac52eaca03ea79ac411e3f6d --hash=sha256:1f277c5cf060b30313c5f9b91588f4c645e11839e14a63c83fcf6f24b1bc9b95 --hash=sha256:a1beea0443d3404c03e069d4c4d9fc13d8ec001771c77f9a23f01911a41f0e49 --hash=sha256:30e9b2878d5b57c68a40b3a08d496bcdaefc79893948989bb9b9fab087b3f3c0 --hash=sha256:99de2e38dde8669dd30a8a1261bdb39caee6bd00a5f928d01dfdb85ab0502562 --hash=sha256:fd23b27387d76410eb6a01ea13efc7d8b4b95974ba212c336e8b1d6ab45a9578 --hash=sha256:51143a479965e3e634252a0f4a1ea07e5307cf8dc773ef6bf9dfe6741785fb4c --hash=sha256:9fa4284b44bc42bef6e437488d000ae37499ccee0d239013465638504c4565b7 --hash=sha256:33533bc5c6522883e4437e901059fe5afa3ea74287eeea27a130494ff130e731 --hash=sha256:6951655cc18b8371d823e81c700883debb0f633aae76f425dfeb240f76b95a67 --hash=sha256:71eeb8d9146e8131b65c3364bb760b097c21b7b9fdbec91bf120685a510f997a --hash=sha256:8465f84ba31aaf52a080837e9c5ddd592ab0a21dfda3212239ce1e1796f4d503 --hash=sha256:5bf9bd1dd4951552d9207af3168f420575e3049016b9c10fe0c96760ce3555b7 --hash=sha256:6004512833707a1877cc1a5aea90fd182f569e089bc9ab22a81d480dad018f1b --hash=sha256:4ea938f44b882e02cca9583069d38eb5f257cc15a03e918980c307e7739b1038 --hash=sha256:a94e197bd9667834f7bb6bd8dff1736fab68619d0f8cd78a9c1cebe3c4944677 --hash=sha256:ac0331d3a3289a3d16627742be9c8969f293740a31efdedcd9087dadd6b2da57 --hash=sha256:640b3b52121ab519e0980cb38b572df0d2bc76941103a697e828c13d76ac8836 --hash=sha256:d26b57c50bf52fb38dadf3df5bbecd2236f49d7ac98f3cf32d6b8a2d25afc27f --hash=sha256:298a04a334fb5e3dcd6f89d063866a09155da56041bc94756da59db412cb45b1 --hash=sha256:3f06f4802824c577272960df003a304ce95b3e82eea01dad2637cc8609c80e2c +greenlet==0.4.15; platform_python_implementation == 'CPython' --hash=sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163 --hash=sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248 --hash=sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87 --hash=sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638 --hash=sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720 --hash=sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28 --hash=sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939 --hash=sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625 --hash=sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490 --hash=sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0 --hash=sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4 --hash=sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6 --hash=sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8 --hash=sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0 --hash=sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656 --hash=sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214 --hash=sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043 --hash=sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304 --hash=sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc +gunicorn==19.9.0 --hash=sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471 --hash=sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3 +idna==2.7 --hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e --hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16 +markdown==3.0.1 --hash=sha256:c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa --hash=sha256:d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c +newrelic==4.4.1.104 --hash=sha256:eb60752a2c2a9904ea1eaf6b25dfbe8181e02fca9c11f895c13469057971b343 +orderedmultidict==1.0 --hash=sha256:24e3b730cf84e4a6a68be5cc760864905cf66abc89851e724bd5b4e849eaa96b --hash=sha256:b89895ba6438038d0bdf88020ceff876cf3eae0d5c66a69b526fab31125db2c5 +psycopg2-binary==2.7.5 --hash=sha256:b5fcf07140219a1f71e18486b8dc28e2e1b76a441c19374805c617aa6d9a9d55 --hash=sha256:5221f5a3f4ca2ddf0d58e8b8a32ca50948be9a43351fda797eb4e72d7a7aa34d --hash=sha256:04afb59bbbd2eab3148e6816beddc74348078b8c02a1113ea7f7822f5be4afe3 --hash=sha256:4a0e38cb30457e70580903367161173d4a7d1381eb2f2cfe4e69b7806623f484 --hash=sha256:89bc65ef3301c74cf32db25334421ea6adbe8f65601ea45dcaaf095abed910bb --hash=sha256:b86f527f00956ecebad6ab3bb30e3a75fedf1160a8716978dd8ce7adddedd86f --hash=sha256:4d6c294c6638a71cafb82a37f182f24321f1163b08b5d5ca076e11fe838a3086 --hash=sha256:789bd89d71d704db2b3d5e67d6d518b158985d791d3b2dec5ab85457cfc9677b --hash=sha256:ecbc6dfff6db06b8b72ae8a2f25ff20fbdcb83cb543811a08f7cb555042aa729 --hash=sha256:0bf855d4a7083e20ead961fda4923887094eaeace0ab2d76eb4aa300f4bbf5bd --hash=sha256:eadbd32b6bc48b67b0457fccc94c86f7ccc8178ab839f684eb285bb592dc143e --hash=sha256:b4c8b0ef3608e59317bfc501df84a61e48b5445d45f24d0391a24802de5f2d84 --hash=sha256:de26ef4787b5e778e8223913a3e50368b44e7480f83c76df1f51d23bd21cea16 --hash=sha256:97521704ac7127d7d8ba22877da3c7bf4a40366587d238ec679ff38e33177498 --hash=sha256:098b18f4d8857a8f9b206d1dc54db56c2255d5d26458917e7bcad61ebfe4338f --hash=sha256:4305aed922c4d9d6163ab3a41d80b5a1cfab54917467da8168552c42cad84d32 --hash=sha256:e70ebcfc5372dc7b699c0110454fc4263967f30c55454397e5769eb72c0eb0ce --hash=sha256:47ee296f704fb8b2a616dec691cdcfd5fa0f11943955e88faa98cbd1dc3b3e3d --hash=sha256:5c6ca0b507540a11eaf9e77dee4f07c131c2ec80ca0cffa146671bf690bc1c02 --hash=sha256:a89ee5c26f72f2d0d74b991ce49e42ddeb4ac0dc2d8c06a0f2770a1ab48f4fe0 --hash=sha256:4f3233c366500730f839f92833194fd8f9a5c4529c8cd8040aa162c3740de8e5 --hash=sha256:a6d32c37f714c3f34158f3fa659f3a8f2658d5f53c4297d45579b9677cc4d852 --hash=sha256:be4c4aa22ba22f70de36c98b06480e2f1697972d49eb20d525f400d204a6d272 --hash=sha256:197dda3ffd02057820be83fe4d84529ea70bf39a9a4daee1d20ffc74eb3d042e --hash=sha256:89d6d3a549f405c20c9ae4dc94d7ed2de2fa77427a470674490a622070732e62 --hash=sha256:3cbf8c4fc8f22f0817220891cf405831559f4d4c12c4f73913730a2ea6c47a47 --hash=sha256:278ef63afb4b3d842b4609f2c05ffbfb76795cf6a184deeb8707cd5ed3c981a5 --hash=sha256:7b94d29239efeaa6a967f3b5971bd0518d2a24edd1511edbf4a2c8b815220d07 --hash=sha256:a395b62d5f44ff6f633231abe568e2203b8fabf9797cd6386aa92497df912d9a --hash=sha256:c2ac7aa1a144d4e0e613ac7286dae85671e99fe7a1353954d4905629c36b811c +pyjwt==1.6.4 --hash=sha256:30b1380ff43b55441283cc2b2676b755cca45693ae3097325dea01f3d110628c --hash=sha256:4ee413b357d53fd3fb44704577afac88e72e878716116270d722723d65b42176 +pytz==2018.5 --hash=sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053 --hash=sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277 +requests==2.20.0 --hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279 --hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c +six==1.11.0 --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 +unicodecsv==0.14.1 --hash=sha256:018c08037d48649a0412063ff4eda26eaa81eff1546dbffa51fa5293276ff7fc +urllib3==1.24 --hash=sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59 --hash=sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae +waitress==1.1.0 --hash=sha256:40b0f297a7f3af61fbfbdc67e59090c70dc150a1601c39ecc9f5f1d283fb931b --hash=sha256:d33cd3d62426c0f1b3cd84ee3d65779c7003aae3fc060dee60524d10a57f05a9 +webencodings==0.5.1 --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 +webob==1.8.3 --hash=sha256:6e231a2952604efd41fd8f76803fe50e1904c81e4619eeb9d9c6991517627721 --hash=sha256:b0853dad347ca3777755b6d0659bb45efbeea71f995d8a395291ef6ad5d4f8b2 +webtest==2.0.32 --hash=sha256:9f1e6faad0b732911793e4d6f54aede292b0c3ee0b3ef7afb2011ec4f4044cc8 --hash=sha256:4221020d502ff414c5fba83c1213985b83219cb1cc611fe58aa4feaf96b5e062 +whitenoise==4.1 --hash=sha256:133a92ff0ab8fb9509f77d4f7d0de493eca19c6fea973f4195d4184f888f2e02 --hash=sha256:32b57d193478908a48acb66bf73e7a3c18679263e3e64bfebcfac1144a430039 diff --git a/tock/api/tests.py b/tock/api/tests.py index 461816b01..1412fd005 100644 --- a/tock/api/tests.py +++ b/tock/api/tests.py @@ -1,34 +1,32 @@ # -*- coding: utf-8 -*- import datetime -import json +from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse from django_webtest import WebTest from api.views import get_timecards, TimecardList -from projects.factories import AccountingCodeFactory, ProjectFactory +from employees.models import UserData, EmployeeGrade from hours.factories import ( UserFactory, ReportingPeriodFactory, TimecardFactory, TimecardObjectFactory, ) - -from django.contrib.auth.models import User -from django.contrib.auth import get_user_model -from employees.models import UserData, EmployeeGrade +from projects.factories import AccountingCodeFactory, ProjectFactory from rest_framework.authtoken.models import Token - from rest_framework.test import APIClient +User = get_user_model() + # common client for all API tests -def client(self): - self.request_user = User.objects.get_or_create(username='aaron.snow')[0] - self.token = Token.objects.get_or_create(user=self.request_user)[0].key - self.client = APIClient() - self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token) - return self.client +def client(): + request_user = User.objects.get_or_create(username='aaron.snow')[0] + token = Token.objects.get_or_create(user=request_user)[0].key + client = APIClient() + client.credentials(HTTP_AUTHORIZATION='Token ' + token) + return client # common fixtures for all API tests FIXTURES = [ @@ -47,7 +45,10 @@ class ProjectInstanceAPITests(WebTest): fixtures = FIXTURES def test_projects_json(self): - res = client(self).get(reverse('ProjectInstanceView', kwargs={'pk': '29'})).data + res = client().get(reverse('ProjectInstanceView', kwargs={'pk': '29'})).data + self.assertTrue('name' in res) + self.assertTrue('start_date' in res) + self.assertTrue('end_date' in res) self.assertEqual(res['name'], "Consulting - Agile BPA") self.assertEqual(res['start_date'], "2016-01-01") self.assertEqual(res['end_date'], None) @@ -66,23 +67,20 @@ class TimecardsAPITests(WebTest): def test_timecards_json(self): """ Check that the timecards are rendered in json format correctly """ - res = client(self).get(reverse('TimecardList')).content - clean_res = json.loads(res.decode()) - self.assertEqual(len(clean_res), 2) + res = client().get(reverse('TimecardList')).data + self.assertEqual(len(res), 2) def test_timecards_grade_is_null_when_absent(self): - res = client(self).get( + res = client().get( reverse('TimecardList'), - data={'date': '2016-06-01'}).content - clean_res = json.loads(res.decode()) - self.assertEqual(clean_res[0]['grade'], None) + kwargs={'date': '2016-06-01'}).data + self.assertEqual(res[1]['grade'], None) def test_timecards_grade_is_populated_when_present(self): - res = client(self).get( + res = client().get( reverse('TimecardList'), - data={'date': '2015-06-01'}).content - clean_res = json.loads(res.decode()) - self.assertEqual(clean_res[0]['grade'], 4) + kwargs={'date': '2015-06-01'}).data + self.assertEqual(res[0]['grade'], 4) # TODO: test with more diverse data def test_get_timecards(self): @@ -143,6 +141,7 @@ def test_get_unsubmitted_timecards(self): ) self.assertEqual(len(queryset), 2) + class TestAggregates(WebTest): def setUp(self): @@ -172,7 +171,7 @@ def setUp(self): ] def test_hours_by_quarter(self): - response = client(self).get(reverse('HoursByQuarter')).data + response = client().get(reverse('HoursByQuarter')).data self.assertEqual(len(response), 1) row = response[0] self.assertEqual(row['billable'], 15) @@ -198,12 +197,12 @@ def test_hours_by_quarter_with_unsubmitted_timecards(self): ), ]) - response = client(self).get(reverse('HoursByQuarter')).data + response = client().get(reverse('HoursByQuarter')).data self.assertEqual(len(self.timecard_objects), 3) self.assertEqual(response[0]['total'], 20) def test_hours_by_quarter_by_user(self): - response = client(self).get(reverse('HoursByQuarterByUser')).data + response = client().get(reverse('HoursByQuarterByUser')).data self.assertEqual(len(response), 1) row = response[0] self.assertEqual(row['username'], str(self.user)) @@ -246,7 +245,7 @@ def test_hours_by_quarter_by_user_with_unsubmitted_timecards(self): ), ]) - response = client(self).get(reverse('HoursByQuarterByUser')).data + response = client().get(reverse('HoursByQuarterByUser')).data row = response[0] self.assertEqual(len(self.timecard_objects), 4) @@ -257,15 +256,15 @@ class ReportingPeriodList(WebTest): def test_ReportingPeriodList_json(self): """ Check that the reporting periods are listed """ - res = client(self).get(reverse('ReportingPeriodList')).json() + res = client().get(reverse('ReportingPeriodList')).json() self.assertGreater(len(res), 0) def test_ReportingPeriodList_json_empty(self): """ Check that the ReportingPeriodList is empty when all users have filled out thier time cards""" - reporting_periods = client(self).get(reverse('ReportingPeriodList')).data + reporting_periods = client().get(reverse('ReportingPeriodList')).data start_date = reporting_periods[0]['start_date'] - res = client(self).get(reverse( + res = client().get(reverse( 'ReportingPeriodAudit', kwargs={'reporting_period_start_date': start_date} ) @@ -276,14 +275,13 @@ def test_ReportingPeriodList_json_missing_timesheet(self): """ Check that the ReportingPeriodList shows users that have missing time cards """ # Create a user - self.regular_user = get_user_model().objects.create( - username='new.user') + self.regular_user = User.objects.create(username='new.user') userdata = UserData(user=self.regular_user) userdata.save() - reporting_periods = client(self).get(reverse('ReportingPeriodList')).data + reporting_periods = client().get(reverse('ReportingPeriodList')).data start_date = reporting_periods[0]['start_date'] - res = client(self).get(reverse( + res = client().get(reverse( 'ReportingPeriodAudit', kwargs={'reporting_period_start_date': start_date} ) @@ -294,15 +292,15 @@ def test_ReportingPeriodList_json_no_longer_employed(self): """ Check that the ReportingPeriodList shows users that have missing time cards """ # Create a user, but set the user as unemployed - self.regular_user = get_user_model().objects.create( + self.regular_user = User.objects.create( username='new.user') userdata = UserData(user=self.regular_user) userdata.current_employee = False userdata.save() - reporting_periods = client(self).get(reverse('ReportingPeriodList')).data + reporting_periods = client().get(reverse('ReportingPeriodList')).data start_date = reporting_periods[0]['start_date'] - res = client(self).get(reverse( + res = client().get(reverse( 'ReportingPeriodAudit', kwargs={'reporting_period_start_date': start_date} ) diff --git a/tock/api/urls.py b/tock/api/urls.py index 7714eeffc..85e4ab14b 100644 --- a/tock/api/urls.py +++ b/tock/api/urls.py @@ -1,22 +1,22 @@ # -*- coding: utf-8 -*- -from django.conf.urls import url -from api import views +from django.urls import path, re_path -# Uncomment the next two lines to enable the admin: -# from django.contrib import admin -# admin.autodiscover() +from api.views import ProjectList, ProjectInstanceView, UserList, ReportingPeriodList, ReportingPeriodAudit, \ + TimecardList, hours_by_quarter, hours_by_quarter_by_user, UserDataView urlpatterns = [ - url(r'^projects.json$', views.ProjectList.as_view(), name='ProjectList'), - url(r'^projects/(?P\d+).json$',view=views.ProjectInstanceView.as_view(),name='ProjectInstanceView'), - url(r'^users.json$', views.UserList.as_view(), name='UserList'), - url(r'^reporting_period_audit.json$', view=views.ReportingPeriodList.as_view(), name='ReportingPeriodList'), - url(r'^reporting_period_audit/(?P[0-9]{4}-[0-9]{2}-[0-9]{2}).json$', - view=views.ReportingPeriodAudit.as_view(), name='ReportingPeriodAudit' + path('projects.json', ProjectList.as_view(), name='ProjectList'), + path('projects/.json', ProjectInstanceView.as_view(), name='ProjectInstanceView'), + path('reporting_period_audit.json', ReportingPeriodList.as_view(), name='ReportingPeriodList'), + re_path( + r'^reporting_period_audit/(?P[0-9]{4}-[0-9]{2}-[0-9]{2}).json$', + ReportingPeriodAudit.as_view(), + name='ReportingPeriodAudit' ), - url(r'^timecards.json$', views.TimecardList.as_view(), name='TimecardList'), - url(r'^hours/by_quarter.json$', views.hours_by_quarter, name='HoursByQuarter'), - url(r'^hours/by_quarter_by_user.json$', views.hours_by_quarter_by_user, name='HoursByQuarterByUser'), - url(r'^user_data.json$', views.UserDataView.as_view(), name='UserDataView'), + path('timecards.json', TimecardList.as_view(), name='TimecardList'), + path('hours/by_quarter.json', hours_by_quarter, name='HoursByQuarter'), + path('hours/by_quarter_by_user.json', hours_by_quarter_by_user, name='HoursByQuarterByUser'), + path('users.json', UserList.as_view(), name='UserList'), + path('user_data.json', UserDataView.as_view(), name='UserDataView'), ] diff --git a/tock/api/views.py b/tock/api/views.py index bfcda6e12..718bc7e0d 100644 --- a/tock/api/views.py +++ b/tock/api/views.py @@ -1,16 +1,16 @@ import collections import datetime +from django.contrib.auth import get_user_model from django.db import connection -from django.contrib.auth.models import User -from django.contrib.auth import get_user_model +from rest_framework import serializers, generics -from projects.models import Project from hours.models import TimecardObject, Timecard, ReportingPeriod +from projects.models import Project from employees.models import UserData -from rest_framework import serializers, generics +User = get_user_model() # Serializers for different models @@ -30,7 +30,7 @@ class Meta: 'organization', ) billable = serializers.BooleanField(source='accounting_code.billable') - profit_loss_account = serializers.CharField(source='profit_loss_account.name') + profit_loss_account = serializers.CharField(source='profit_loss_account.name', allow_null=True) client = serializers.StringRelatedField(source='accounting_code') organization = serializers.StringRelatedField() @@ -72,7 +72,8 @@ class TimecardSerializer(serializers.Serializer): project_id = serializers.CharField(source='project.id') project_name = serializers.CharField(source='project.name') profit_loss_account = serializers.CharField( - source='project.profit_loss_account.name' + source='project.profit_loss_account.name', + allow_null=True ) hours_spent = serializers.DecimalField(max_digits=5, decimal_places=2) start_date = serializers.DateField( @@ -92,16 +93,20 @@ class TimecardSerializer(serializers.Serializer): ) notes = serializers.CharField() revenue_profit_loss_account = serializers.CharField( - source='revenue_profit_loss_account.accounting_string' + source='revenue_profit_loss_account.accounting_string', + allow_null=True ) revenue_profit_loss_account_name = serializers.CharField( - source='revenue_profit_loss_account.name' + source='revenue_profit_loss_account.name', + allow_null=True ) expense_profit_loss_account = serializers.CharField( - source='expense_profit_loss_account.accounting_string' + source='expense_profit_loss_account.accounting_string', + allow_null=True ) expense_profit_loss_account_name = serializers.CharField( - source='expense_profit_loss_account.name' + source='expense_profit_loss_account.name', + allow_null=True ) employee_organization = serializers.CharField( source='timecard.user.user_data.organization_name' @@ -110,7 +115,9 @@ class TimecardSerializer(serializers.Serializer): source='project.organization_name' ) grade = serializers.IntegerField( - source='grade.grade') + source='grade.grade', + allow_null=True + ) # API Views @@ -137,8 +144,10 @@ class ReportingPeriodList(generics.ListAPIView): serializer_class = ReportingPeriodSerializer class ReportingPeriodAudit(generics.ListAPIView): - """ This endpoint retrieves a list of users who have not filled out - their time cards for a given time period """ + """ + Retrieves a list of users who have not filled out + their time cards for a given time period + """ queryset = ReportingPeriod.objects.all() serializer_class = UserSerializer diff --git a/tock/employees/urls.py b/tock/employees/urls.py index 1f45d4cd3..ec829c3f1 100644 --- a/tock/employees/urls.py +++ b/tock/employees/urls.py @@ -1,11 +1,10 @@ -from django.conf.urls import url +from django.urls import path -from . import views +from .views import UserListView, UserDetailView, UserFormView +app_name="employees" urlpatterns = [ - url(regex=r'^$', view=views.UserListView.as_view(), name='UserListView'), - url(regex=r'^(?P[A-Za-z0-9._%+-]*)/$', - view=views.UserDetailView.as_view(), name='UserDetailView'), - url(regex=r'^e/(?P[A-Za-z0-9._%+-]*)/$', - view=views.UserFormView.as_view(), name='UserFormView'), + path('', UserListView.as_view(), name='UserListView'), + path('/', UserDetailView.as_view(), name='UserDetailView'), + path('e//', UserFormView.as_view(), name='UserFormView'), ] diff --git a/tock/employees/views.py b/tock/employees/views.py index 4f953d1c7..82b6633e0 100644 --- a/tock/employees/views.py +++ b/tock/employees/views.py @@ -6,7 +6,6 @@ from django.views.generic.edit import FormView from rest_framework.permissions import IsAuthenticated - from tock.utils import PermissionMixin, IsSuperUserOrSelf from .forms import UserForm diff --git a/tock/hours/tests/test_views.py b/tock/hours/tests/test_views.py index 76514975a..42da8a279 100644 --- a/tock/hours/tests/test_views.py +++ b/tock/hours/tests/test_views.py @@ -32,7 +32,7 @@ def decode_streaming_csv(response, **reader_options): lines = [line.decode('utf-8') for line in response.streaming_content] - return csv.DictReader(lines, **reader_options) + return list(csv.DictReader(lines, **reader_options)) class DashboardReportsListTests(WebTest): fixtures = ['tock/fixtures/prod_user.json',] @@ -341,22 +341,30 @@ class CSVTests(TestCase): fixtures = FIXTURES def test_user_data_csv(self): - """Test that correct fields are returned for user data CSV request.""" - response = client(self).get(reverse('reports:UserDataView')) + """ + Test that CSV is returned and contains the correct fields + for user data CSV request. + """ + response = client().get(reverse('reports:UserDataView')) rows = decode_streaming_csv(response) + # Make sure we even have a response to work with. + self.assertNotEqual(len(rows), 0) + + num_of_fields = 0 for row in rows: num_of_fields = len(row) num_of_expected_fields = len( UserDataSerializer.__dict__['_declared_fields'] ) - self.assertEqual(num_of_expected_fields, num_of_fields) def test_project_csv(self): """Test that correct fields are returned for project data CSV request.""" - response = client(self).get(reverse('reports:ProjectList')) + response = client().get(reverse('reports:ProjectList')) rows = decode_streaming_csv(response) + num_rows = len(rows) + self.assertNotEqual(num_rows, 0) for row in rows: num_of_fields = len(row) num_of_expected_fields = len( @@ -375,7 +383,7 @@ def test_general_snippets(self): tco.notes = 'Some notes about things!' tco.save() - response = client(self).get(reverse('reports:GeneralSnippetsView')) + response = client().get(reverse('reports:GeneralSnippetsView')) rows = decode_streaming_csv(response) entry_found = False for row in rows: @@ -392,7 +400,7 @@ class BulkTimecardsTests(TestCase): fixtures = FIXTURES def test_bulk_timecards(self): - response = client(self).get(reverse('reports:BulkTimecardList')) + response = client().get(reverse('reports:BulkTimecardList')) rows = decode_streaming_csv(response) expected_fields = set(( 'project_name', @@ -422,7 +430,7 @@ def test_bulk_timecards(self): self.assertNotEqual(rows_read, 0, 'no rows read, expecting 1 or more') def test_slim_bulk_timecards(self): - response = client(self).get(reverse('reports:SlimBulkTimecardList')) + response = client().get(reverse('reports:SlimBulkTimecardList')) rows = decode_streaming_csv(response) expected_fields = set(( 'project_name', @@ -482,7 +490,7 @@ class ProjectTimelineTests(WebTest): fixtures = FIXTURES def test_project_timeline(self): - res = client(self).get(reverse('reports:UserTimelineView')) + res = client().get(reverse('reports:UserTimelineView')) self.assertIn( 'aaron.snow,,2015-06-01,2015-06-08,False,20.00', str(res.content)) @@ -739,8 +747,7 @@ def test_reportperiod_updatetimesheet_no_reportperiod(self): """ Tests that a 404 is raised when a reporting period is not found. """ - date = datetime.date(1980, 10, 1) - + date = datetime.date(1980, 10, 1).strftime('%Y-%m-%d') response = self.app.get( reverse( 'reportingperiod:UpdateTimesheet', diff --git a/tock/hours/urls/reports.py b/tock/hours/urls/reports.py index 701af5afa..7cb340b9d 100644 --- a/tock/hours/urls/reports.py +++ b/tock/hours/urls/reports.py @@ -1,35 +1,37 @@ -from django.conf.urls import url +from django.urls import path, re_path from .. import views +app_name='reports' urlpatterns = [ - url(r'^timecards_bulk.csv$', views.bulk_timecard_list, name='BulkTimecardList'), - url(r'^slim_timecard_bulk.csv$', views.slim_bulk_timecard_list, name='SlimBulkTimecardList'), - url(regex=r'^$', view=views.ReportsList.as_view(), name='ListReports'), - url(regex=r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2})/$', - view=views.ReportingPeriodDetailView.as_view(), name='ReportingPeriodDetailView'), - url(regex=r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2}).csv/$', - view=views.ReportingPeriodCSVView, name='ReportingPeriodCSVView'), - url(regex=r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2})/(?P[A-Za-z0-9._%+-]*)/$', - view=views.ReportingPeriodUserDetailView.as_view(), name='ReportingPeriodUserDetailView'), - url(r'^project_timeline.csv$', views.project_timeline_view, name='ProjectTimelineView'), - url(r'^user_timeline.csv$', views.user_timeline_view, name='UserTimelineView'), - url(r'^admin_timecard_bulk.csv$', views.admin_bulk_timecard_list, name='AdminBulkTimecardList'), - url(r'^projects.csv$', views.projects_csv, name='ProjectList'), - url(r'^user_data.csv$', views.user_data_csv, name='UserDataView'), - url( - r'^general_snippets_bulk.csv$', - views.general_snippets_only_timecard_list, - name='GeneralSnippetsView' + path('timecards_bulk.csv', views.bulk_timecard_list, name='BulkTimecardList'), + path('slim_timecard_bulk.csv', views.slim_bulk_timecard_list, name='SlimBulkTimecardList'), + path('', views.ReportsList.as_view(), name='ListReports'), + re_path( + r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2})/$', + view=views.ReportingPeriodDetailView.as_view(), + name='ReportingPeriodDetailView' ), - url( + re_path( + r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2}).csv/$', + view=views.ReportingPeriodCSVView, + name='ReportingPeriodCSVView' + ), + re_path( + r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2})/(?P[A-Za-z0-9._%+-]*)/$', + view=views.ReportingPeriodUserDetailView.as_view(), + name='ReportingPeriodUserDetailView' + ), + path('project_timeline.csv', views.project_timeline_view, name='ProjectTimelineView'), + path('user_timeline.csv', views.user_timeline_view, name='UserTimelineView'), + path('admin_timecard_bulk.csv', views.admin_bulk_timecard_list, name='AdminBulkTimecardList'), + path('projects.csv', views.projects_csv, name='ProjectList'), + path('user_data.csv', views.user_data_csv, name='UserDataView'), + path('general_snippets_bulk.csv', views.general_snippets_only_timecard_list, name='GeneralSnippetsView'), + re_path( r'^dashboard/(?P[0-9]{4}-[0-9]{2}-[0-9]{2})/$', views.DashboardView.as_view(), name='DashboardView' ), - url( - r'^dashboard/list$', - view=views.DashboardReportsList.as_view(), - name='DashboardReportsList' - ), + path('dashboard/list/', views.DashboardReportsList.as_view(), name='DashboardReportsList'), ] diff --git a/tock/hours/urls/timesheets.py b/tock/hours/urls/timesheets.py index d12402113..73d0a4fa3 100644 --- a/tock/hours/urls/timesheets.py +++ b/tock/hours/urls/timesheets.py @@ -1,21 +1,20 @@ -from django.conf.urls import url +from django.urls import path, re_path from .. import views +app_name = 'reportingperiod' urlpatterns = [ - url( - regex=r'^create/$', - view=views.ReportingPeriodCreateView.as_view(), - name='ReportingPeriodCreateView', + path( + 'create/', views.ReportingPeriodCreateView.as_view(), name='ReportingPeriodCreateView', ), - url( - regex=r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2})/$', - view=views.TimecardView.as_view(), + re_path( + r'^(?P[0-9]{4}-[0-9]{2}-[0-9]{2})/$', + views.TimecardView.as_view(), name='UpdateTimesheet', ), - url( - regex=r'^import/$', - view=views.ReportingPeriodBulkImportView.as_view(), + path( + 'import/', + views.ReportingPeriodBulkImportView.as_view(), name='ImportReportingPeriod', ), ] diff --git a/tock/hours/views.py b/tock/hours/views.py index 108ef7f11..4699fef68 100644 --- a/tock/hours/views.py +++ b/tock/hours/views.py @@ -345,16 +345,20 @@ class BulkTimecardSerializer(serializers.Serializer): mbnumber = serializers.CharField(source='project.mbnumber') notes = serializers.CharField() revenue_profit_loss_account = serializers.CharField( - source='revenue_profit_loss_account.accounting_string' + source='revenue_profit_loss_account.accounting_string', + allow_null=True ) revenue_profit_loss_account_name = serializers.CharField( - source='revenue_profit_loss_account.name' + source='revenue_profit_loss_account.name', + allow_null=True ) expense_profit_loss_account = serializers.CharField( - source='expense_profit_loss_account.accounting_string' + source='expense_profit_loss_account.accounting_string', + allow_null=True ) expense_profit_loss_account_name = serializers.CharField( - source='expense_profit_loss_account.name' + source='expense_profit_loss_account.name', + allow_null=True ) employee_organization = serializers.CharField( source='timecard.user.user_data.organization_name' @@ -438,18 +442,22 @@ class AdminBulkTimecardSerializer(serializers.Serializer): active = serializers.BooleanField(source='project.active') mbnumber = serializers.CharField(source='project.mbnumber') notes = serializers.CharField() - grade = serializers.CharField(source='grade.grade') + grade = serializers.CharField(source='grade.grade', allow_null=True) revenue_profit_loss_account = serializers.CharField( - source='revenue_profit_loss_account.accounting_string' + source='revenue_profit_loss_account.accounting_string', + allow_null=True ) revenue_profit_loss_account_name = serializers.CharField( - source='revenue_profit_loss_account.name' + source='revenue_profit_loss_account.name', + allow_null=True ) expense_profit_loss_account = serializers.CharField( - source='expense_profit_loss_account.accounting_string' + source='expense_profit_loss_account.accounting_string', + allow_null=True ) expense_profit_loss_account_name = serializers.CharField( - source='expense_profit_loss_account.name' + source='expense_profit_loss_account.name', + allow_null=True ) employee_organization = serializers.CharField( source='timecard.user.user_data.organization_name' diff --git a/tock/projects/models.py b/tock/projects/models.py index 18ee34316..375d1584c 100644 --- a/tock/projects/models.py +++ b/tock/projects/models.py @@ -234,7 +234,8 @@ def is_billable(self): return self.accounting_code.billable def get_profit_loss_account(self): - return self.profit_loss_account.name + if self.profit_loss_account: + return self.profit_loss_account.name @property def organization_name(self): diff --git a/tock/projects/urls.py b/tock/projects/urls.py index 450f313d5..a8e8f6e3c 100644 --- a/tock/projects/urls.py +++ b/tock/projects/urls.py @@ -1,16 +1,8 @@ -from django.conf.urls import url +from django.urls import path -from . import views +from .views import ProjectListView, ProjectView urlpatterns = [ - url( - regex=r'^$', - view=views.ProjectListView.as_view(), - name='ProjectListView' - ), - url( - regex=r'^(?P\d+)/$', - view=views.ProjectView.as_view(), - name='ProjectView' - ), + path('', ProjectListView.as_view(), name='ProjectListView'), + path('/', ProjectView.as_view(), name='ProjectView'), ] diff --git a/tock/tock/middleware.py b/tock/tock/middleware.py index c34159f4e..3bee765cc 100644 --- a/tock/tock/middleware.py +++ b/tock/tock/middleware.py @@ -12,7 +12,7 @@ def __call__(self, request): fmt = '%Y%m%d%H%M%S' # Check if user exists and is logged in - if request.user and request.user.is_authenticated(): + if request.user and request.user.is_authenticated: logout_time_in_seconds = settings.AUTO_LOGOUT_DELAY_MINUTES * 60 diff --git a/tock/tock/settings/base.py b/tock/tock/settings/base.py index ea48b5c95..bd31d3e1a 100644 --- a/tock/tock/settings/base.py +++ b/tock/tock/settings/base.py @@ -74,7 +74,6 @@ 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'uaa_client.middleware.UaaRefreshMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', diff --git a/tock/tock/tests/test_middleware.py b/tock/tock/tests/test_middleware.py index 7e11112a0..4c83fce55 100644 --- a/tock/tock/tests/test_middleware.py +++ b/tock/tock/tests/test_middleware.py @@ -1,4 +1,5 @@ import time + from django.test import TestCase, override_settings from django.urls import reverse diff --git a/tock/tock/tests/test_url_auth.py b/tock/tock/tests/test_url_auth.py index 4b90525b7..f76bbfabf 100644 --- a/tock/tock/tests/test_url_auth.py +++ b/tock/tock/tests/test_url_auth.py @@ -1,5 +1,6 @@ from django.test import TestCase -from django.urls import RegexURLPattern, RegexURLResolver, reverse +from django.urls import reverse, URLPattern +from django.urls.resolvers import URLResolver import tock.urls @@ -18,6 +19,7 @@ "reporting_period_start_date": "2018-01-02", "username": "bar", "pk": "1", + "id": "1", "content_type_id": "2", "object_id": "3", "app_label": "hours", @@ -41,24 +43,24 @@ def iter_patterns(urlconf, patterns=None, namespace=None): """ Iterate through all patterns in the given Django URLconf. Yields - `(viewname, regex)` tuples, where `viewname` is the fully-qualified view name - (including its namespace, if any), and `regex` is a regular expression that + `(viewname, route)` tuples, where `viewname` is the fully-qualified view name + (including its namespace, if any), and `route` is a regular expression that corresponds to the part of the pattern that contains any capturing groups. """ - if patterns is None: patterns = urlconf.urlpatterns for pattern in patterns: - if isinstance(pattern, RegexURLPattern): + # Resolve if it's a route or an include + if isinstance(pattern, URLPattern): viewname = pattern.name if viewname is None and namespace not in NAMESPACES_WITH_UNNAMED_VIEWS: raise AssertionError(f'namespace {namespace} cannot contain unnamed views') if namespace and viewname is not None: viewname = f"{namespace}:{viewname}" - yield (viewname, pattern.regex) - elif isinstance(pattern, RegexURLResolver): - if pattern.regex.groups > 0: - raise AssertionError('resolvers are not expected to have groups') + yield (viewname, pattern.pattern) + elif isinstance(pattern, URLResolver): + if len(pattern.default_kwargs.keys()) > 0: + raise AssertionError('resolvers are not expected to have kwargs') if pattern.namespace and namespace is not None: raise AssertionError('nested namespaces are not currently supported') if pattern.namespace in IGNORE_NAMESPACES: @@ -75,29 +77,28 @@ def iter_patterns(urlconf, patterns=None, namespace=None): def iter_sample_urls(urlconf): """ Yields sample URLs for all entries in the given Django URLconf. + This gets pretty deep into the muck of RoutePattern + https://docs.djangoproject.com/en/2.1/_modules/django/urls/resolvers/ """ - for viewname, regex in iter_patterns(urlconf): - if viewname is None: + for viewname, route in iter_patterns(urlconf): + if not viewname: continue - - named_groups = regex.groupindex.keys() + if viewname is 'auth_user_password_change': + print(route) + break + named_groups = route.regex.groupindex.keys() kwargs = {} args = () - if len(named_groups) != regex.groups: - if regex.groups != 1: - raise AssertionError('Patterns can contain at most one unnamed group') - if DOT_STAR_GROUP not in regex.pattern: - raise AssertionError(f'Unnamed groups can only contain {DOT_STAR_GROUP}') - args = (SAMPLE_DOT_STAR_ARG,) for kwarg in named_groups: if kwarg not in SAMPLE_KWARGS: raise AssertionError( - f'Sample value for {kwarg} in pattern "{regex.pattern}" not found' + f'Sample value for {kwarg} in pattern "{route}" not found' ) kwargs[kwarg] = SAMPLE_KWARGS[kwarg] - url = reverse(viewname, urlconf=urlconf, args=args, kwargs=kwargs) + + url = reverse(viewname, args=args, kwargs=kwargs) yield (viewname, url) @@ -108,6 +109,7 @@ class URLAuthTests(TestCase): """ # We won't test that the following URLs are protected by auth. + # Note that the trailing slash is wobbly depending on how the URL was defined. IGNORE_URLS = [ # These are the UAA auth endpoints that always need # to be public. @@ -136,7 +138,7 @@ def assertURLIsProtectedByAuth(self, url): # accessed at the time the exception occurred. Python 3 will # also include a full traceback of the original exception, so # we don't need to worry about hiding the original cause. - raise AssertionError(f'Accessing {url} raised "{e}"') + raise AssertionError(f'Accessing {url} raised "{e}"', e) code = response.status_code if code == 302: @@ -151,8 +153,7 @@ def assertURLIsProtectedByAuth(self, url): pass else: raise AssertionError( - f'GET {url} returned HTTP {code}, but should redirect to login or ' - f'deny access' + f'GET {url} returned HTTP {code}, but should redirect to login or deny access', ) @classmethod diff --git a/tock/tock/tests/test_views.py b/tock/tock/tests/test_views.py index aaeb57806..f958b04fc 100644 --- a/tock/tock/tests/test_views.py +++ b/tock/tock/tests/test_views.py @@ -1,16 +1,22 @@ import urllib.parse from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.test import TestCase from tock.remote_user_auth import ACCOUNT_INACTIVE_MSG +User = get_user_model() + class ViewsTests(TestCase): def test_logout_logs_user_out(self): - user = User.objects.create_user(username='foo') + user = User.objects.create_user( + username='foo' + ) self.client.force_login(user) + # Make sure we actually did the above successfully + self.assertTrue(user.is_authenticated) uaa_redirect_url = settings.UAA_LOGOUT_URL uaa_redirect_url += '?' diff --git a/tock/tock/urls.py b/tock/tock/urls.py index d24ee5f9a..c8c0bea2b 100644 --- a/tock/tock/urls.py +++ b/tock/tock/urls.py @@ -1,7 +1,7 @@ -from django.conf import settings -from django.conf.urls import include, url from django.contrib.auth.decorators import user_passes_test +from django.conf import settings from django.core.exceptions import PermissionDenied +from django.urls import include, path # Enable the Django admin. from django.contrib import admin @@ -9,7 +9,7 @@ def check_if_staff(user): - if not user.is_authenticated(): + if not user.is_authenticated: return False if user.is_staff: return True @@ -29,36 +29,22 @@ def check_if_staff(user): handler500 = 'tock.views.handler500' urlpatterns = [ - url(r'^$', - hours.views.ReportingPeriodListView.as_view(), - name='ListReportingPeriods'), - url(r'^reporting_period/', include( - 'hours.urls.timesheets', - namespace='reportingperiod' - )), - url(r'^reports/', include( - 'hours.urls.reports', - namespace='reports' - )), - url(r'^employees/', include( - 'employees.urls', - namespace='employees' - )), - url(r'^utilization/', include( - 'utilization.urls', - namespace='utilization' - )), - url(r'^projects/', include(projects.urls)), + path('', hours.views.ReportingPeriodListView.as_view(), name='ListReportingPeriods'), + path('reporting_period/', include('hours.urls.timesheets', namespace='reportingperiod')), + path('reports/', include('hours.urls.reports', namespace='reports')), + path('employees/', include('employees.urls', namespace='employees')), + path('utilization/', include('utilization.urls', namespace='utilization')), + path('projects/', include(projects.urls)), # TODO: version the API? - url(r'^api/', include(api.urls)), + path('api/', include(api.urls)), - # Enable the Django admin. - url(r'^admin/', include(admin.site.urls)), + path('admin/', admin.site.urls), - url(r'^auth/', include('uaa_client.urls')), + path('auth/', include('uaa_client.urls')), - url(r'^logout$', tock.views.logout, name='logout'), + # Trailing slash here creates unpredictable redirects in tests. + path('logout', tock.views.logout, name='logout'), ] @@ -67,7 +53,7 @@ def check_if_staff(user): import debug_toolbar urlpatterns += [ - url(r'^__debug__/', include(debug_toolbar.urls)), + path('__debug__/', include(debug_toolbar.urls)), ] diff --git a/tock/tock/views.py b/tock/tock/views.py index 7e8e60141..39be43741 100644 --- a/tock/tock/views.py +++ b/tock/tock/views.py @@ -20,7 +20,7 @@ def csrf_failure(request, reason=""): def logout(request): - if request.user.is_authenticated(): + if request.user.is_authenticated: django.contrib.auth.logout(request) tock_logout_url = request.build_absolute_uri('logout') params = urllib.parse.urlencode({ @@ -33,50 +33,18 @@ def logout(request): else: return render(request, 'logout.html') +def handler400(request, exception): + return render(request, '400.html', {}) -# TODO: new function signature for Django 2.0 -# def handler400(request, exception, template_name='400.html'): -def handler400(request): - response = render( - request, - '400.html', - {} - ) - response.status_code = 400 - return response - - -# TODO: new function signature for Django 2.0 -# def handler403(request, exception, template_name='403.html'): def handler403(request, exception): - response = render( - request, - '403.html', - {'exception': exception} - ) + response = render(request, '403.html', {'exception': exception}) response.status_code = 403 return response - -# TODO: new function signature for Django 2.0 -# def handler404(request, exception, template_name='404.html'): -def handler404(request): - response = render( - request, - '404.html', - {} - ) +def handler404(request, exception): + response = render(request, '404.html', {}) response.status_code = 404 return response - -# TODO: new function signature for Django 2.0 -# def handler500(request, exception, template_name='500.html'): -def handler500(request): - response = render( - request, - '500.html', - {} - ) - response.status_code = 500 - return response +def handler500(request, exception=None): + return render(request, '500.html', {}) diff --git a/tock/utilization/tests/test_views.py b/tock/utilization/tests/test_views.py index 924930cc2..d72456eb8 100644 --- a/tock/utilization/tests/test_views.py +++ b/tock/utilization/tests/test_views.py @@ -1,17 +1,17 @@ import datetime -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse from django_webtest import WebTest - from ..utils import get_fy_first_day, get_dates from hours.models import ReportingPeriod, Timecard, TimecardObject from projects.models import Project, AccountingCode, Agency from employees.models import UserData +User = get_user_model() FIXTURES = [ 'tock/fixtures/prod_user.json', @@ -106,7 +106,6 @@ def test_utilization(self): url=reverse('utilization:GroupUtilizationView'), user=self.req_user ) - self.assertEqual(len( response.context['object_list']), len(UserData.UNIT_CHOICES) ) @@ -118,9 +117,6 @@ def test_utilization(self): self.assertTrue(response.context['object_list'][0]['last']) self.assertTrue(response.context['object_list'][0]['fytd']) self.assertTrue(response.context['object_list'][0]['recent']) - self.assertTrue( - response.context['object_list'][0]['billable_staff'][0]._user_cache - ) self.assertEqual( response.context['object_list'][0]['billable_staff'][0].unit, 0 ) diff --git a/tock/utilization/urls.py b/tock/utilization/urls.py index 4f4807759..4a84a524c 100644 --- a/tock/utilization/urls.py +++ b/tock/utilization/urls.py @@ -1,10 +1,8 @@ -from django.conf.urls import url +from django.urls import path -from . import views +from .views import GroupUtilizationView +app_name = 'utilization' urlpatterns = [ - url( - regex=r'^$', - view=views.GroupUtilizationView.as_view(), - name='GroupUtilizationView' - )] \ No newline at end of file + path('', GroupUtilizationView.as_view(), name='GroupUtilizationView') +] \ No newline at end of file