diff --git a/Dockerfile.debian b/Dockerfile.debian index a574f1bc..f7da9542 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -17,7 +17,7 @@ ARG POETRY_VERSION=1.8.3 RUN pip install --root-user-action=ignore poetry==${POETRY_VERSION} COPY poetry.lock pyproject.toml ./ RUN poetry config virtualenvs.create false \ - && poetry install --no-interaction --no-ansi --only main + && poetry install --only main --no-interaction --no-ansi # Create a non-root user. ARG UID=10001 diff --git a/README.md b/README.md index 2c440ff6..61c8d3ca 100644 --- a/README.md +++ b/README.md @@ -117,4 +117,8 @@ install pre-commit hooks locally like so: pre-commit install +To run Python commands in the virtualenv, thereafter run them like so: + + python manage.py + Reference: diff --git a/poetry.lock b/poetry.lock index 260ef57a..613400b7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -763,13 +763,13 @@ typing_extensions = ">=3.10.0.0" [[package]] name = "django" -version = "4.2.15" +version = "4.2.16" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.15-py3-none-any.whl", hash = "sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30"}, - {file = "Django-4.2.15.tar.gz", hash = "sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a"}, + {file = "Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898"}, + {file = "Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad"}, ] [package.dependencies] @@ -879,13 +879,13 @@ sftp = ["paramiko (>=1.15)"] [[package]] name = "django-taggit" -version = "6.0.0" +version = "6.1.0" description = "django-taggit is a reusable Django application for simple tagging." optional = false python-versions = ">=3.8" files = [ - {file = "django_taggit-6.0.0-py3-none-any.whl", hash = "sha256:7094f797b7e5e3727525a0af7bc550860000ddc2248266529d568eca61b39fb1"}, - {file = "django_taggit-6.0.0.tar.gz", hash = "sha256:723d98bd5c536daa3c0e1bdae0965f7005a9b15269816bb4053fafec4ebad57e"}, + {file = "django_taggit-6.1.0-py3-none-any.whl", hash = "sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0"}, + {file = "django_taggit-6.1.0.tar.gz", hash = "sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3"}, ] [package.dependencies] @@ -1669,24 +1669,24 @@ wcwidth = "*" [[package]] name = "psycopg" -version = "3.2.2" +version = "3.2.3" description = "PostgreSQL database adapter for Python" optional = false python-versions = ">=3.8" files = [ - {file = "psycopg-3.2.2-py3-none-any.whl", hash = "sha256:babf565d459d8f72fb65da5e211dd0b58a52c51e4e1fa9cadecff42d6b7619b2"}, - {file = "psycopg-3.2.2.tar.gz", hash = "sha256:8bad2e497ce22d556dac1464738cb948f8d6bab450d965cf1d8a8effd52412e0"}, + {file = "psycopg-3.2.3-py3-none-any.whl", hash = "sha256:644d3973fe26908c73d4be746074f6e5224b03c1101d302d9a53bf565ad64907"}, + {file = "psycopg-3.2.3.tar.gz", hash = "sha256:a5764f67c27bec8bfac85764d23c534af2c27b893550377e37ce59c12aac47a2"}, ] [package.dependencies] -psycopg-binary = {version = "3.2.2", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} +psycopg-binary = {version = "3.2.3", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} psycopg-pool = {version = "*", optional = true, markers = "extra == \"pool\""} typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -binary = ["psycopg-binary (==3.2.2)"] -c = ["psycopg-c (==3.2.2)"] +binary = ["psycopg-binary (==3.2.3)"] +c = ["psycopg-c (==3.2.3)"] dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.11)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] pool = ["psycopg-pool"] @@ -1694,75 +1694,75 @@ test = ["anyio (>=4.0)", "mypy (>=1.11)", "pproxy (>=2.7)", "pytest (>=6.2.5)", [[package]] name = "psycopg-binary" -version = "3.2.2" +version = "3.2.3" description = "PostgreSQL database adapter for Python -- C optimisation distribution" optional = false python-versions = ">=3.8" files = [ - {file = "psycopg_binary-3.2.2-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:8eacbf58d4f8d7bc82e0a60476afa2622b5a58f639a3cc2710e3e37b72aff3cb"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:d07e62476ee8c54853b2b8cfdf3858a574218103b4cd213211f64326c7812437"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c22e615ee0ecfc6687bb8a39a4ed9d6bac030b5e72ac15e7324fd6e48979af71"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec29c7ec136263628e3f09a53e51d0a4b1ad765a6e45135707bfa848b39113f9"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:035753f80cbbf6aceca6386f53e139df70c7aca057b0592711047b5a8cfef8bb"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ee99336151ff7c30682f2ef9cb1174d235bc1471322faabba97f9db1398167"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a60674dff4a4194e88312b463fb84ac80924c2b9e25d0e0460f3176bf1af4a6b"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3c701507a49340de422d77a6ce95918a0019990bbf27daec35aa40050c6eadb6"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1b3c5a04eaf8866e399315cff2e810260cce10b797437a9f49fd71b5f4b94d0a"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ad9c09de4c262f516ae6891d042a4325649b18efa39dd82bbe0f7bc95c37bfb"}, - {file = "psycopg_binary-3.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:bf1d3582185cb43ecc27403bee2f5405b7a45ccaab46c8508d9a9327341574fc"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:554d208757129d34fa47b7c890f9ef922f754e99c6b089cb3a209aa0fe282682"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:71dc3cc10d1fd7d26a3079d0a5b4a8e8ad0d7b89a702ceb7605a52e4395be122"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86f578d63f2e1fdf87c9adaed4ff23d7919bda8791cf1380fa4cf3a857ccb8b"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a4eb737682c02a602a12aa85a492608066f77793dab681b1c4e885fedc160b1"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e120a576e74e4e612c48f4b021e322e320ca102534d78a0ca4db2ffd058ae8d"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849d518e7d4c6186e1e48ea2ac2671912edf7e732fffe6f01dfed61cf0245de4"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ee2b19152bcec8f356f989c31768702be5f139b4d51094273c4a9ddc8c55380"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:00273dd011892e8216fcef76b42f775ddaa6348664a7fffae2a27c9557f45bfa"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4bcb489615d7e56d1de42937e6a0fc13f766505729afdb54c2947a52db295220"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:06963f88916a177df95aaed27101af0989ba206654743b1a0e050b9d8e734686"}, - {file = "psycopg_binary-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed1ad836a0c21890c7f84e73c7ef1ed0950e0e4b0d8e49b609b6fd9c13f2ca21"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:0dd314229885a81f9497875295d8788e651b78945627540f1e78ed71595e614a"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:989acbe2f552769cdb780346cea32d86e7c117044238d5172ac10b025fe47194"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566b1c530898590f0ac9d949cf94351c08d73c89f8800c74c0a63ffd89a383c8"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68d03efab7e2830a0df3aa4c29a708930e3f6b9fd98774ff9c4fd1f33deafecc"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e1f013bfb744023df23750fde51edcb606def8328473361db3c192c392c6060"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a06136aab55a2de7dd4e2555badae276846827cfb023e6ba1b22f7a7b88e3f1b"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:020c5154be144a1440cf87eae012b9004fb414ae4b9e7b1b9fb808fe39e96e83"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef341c556aeaa43a2729b07b04e20bfffdcf3d96c4a96e728ca94fe4ce632d8c"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66de2dd7d37bf66eb234ca9d907f5cd8caca43ff8d8a50dd5c15844d1cf0390c"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2eb6f8f410dbbb71b8c633f283b8588b63bee0a7321f00ab76e9c800c593f732"}, - {file = "psycopg_binary-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b45553c6b614d02e1486585980afdfd18f0000aac668e2e87c6e32da1adb051a"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:1ee891287c2da57e7fee31fbe2fbcdf57125768133d811b02e9523d5a052eb28"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5e95e4a8076ac7611e571623e1113fa84fd48c0459601969ffbf534d7aa236e7"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6269d79a3d7d76b6fcf0fafae8444da00e83777a6c68c43851351a571ad37155"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6dd5d21a298c3c53af20ced8da4ae4cd038c6fe88c80842a8888fa3660b2094"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cf64e41e238620f05aad862f06bc8424f8f320d8075f1499bd85a225d18bd57"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c482c3236ded54add31136a91d5223b233ec301f297fa2db79747404222dca6"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0718be095cefdad712542169d16fa58b3bd9200a3de1b0217ae761cdec1cf569"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fb303b03c243a9041e1873b596e246f7caaf01710b312fafa65b1db5cd77dd6f"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:705da5bc4364bd7529473225fca02b795653bc5bd824dbe43e1df0b1a40fe691"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:05406b96139912574571b1c56bb023839a9146cf4b57c4548f36251dd5909fa1"}, - {file = "psycopg_binary-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:7c357cf87e8d7612cfe781225be7669f35038a765d1b53ec9605f6c5aef9ee85"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:059aa5e8fa119de328b4cb02ee80775443763b25682a02dd7d026b8d4f565834"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05a50f94e1e4fa37a0074b09263b83b0aa038c3c72068a61f1ad61ea449ef9d5"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:951507b3d77a64c907afe893e01e09b41051fd7e27e9462f450fb8bb64bc22b0"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ec4986c4ac2503e865acd3943d179531c3bbfa5a1c8ee81fcfccb551dad645f"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b32b0e838841d5b109d32fc706b8bc64e50c161fee3f1371ccf696e5598bc49"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fdc74a83348477b28bea9e7b391c9fc189b480fe3cd0e46bb989514410b64d60"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9efe0ca78be4a573b4b81226904c711cfadc4783d64bfdf58a3394da7c1a1354"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:51f56ae2898acaa33623adad96ddc5acbb5e2f72f2fc020065c8be05c0e01dce"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:43b209be0424e8abece428a884cb711f504e3526dfbcb0bf51529907a55eda15"}, - {file = "psycopg_binary-3.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:d3c147eea9f3950a34133dc187e8d3534e54ff4a178a4ebd8993b2c97e123200"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:6c7b6a8d4e1b77cdb50192b61235b33fc2f1d28c67627fc93a1d43e9130dd479"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e234edc4bb746d8ac3daae8753ee38eaa7af2ee333a1d35ce6b02a02874aed18"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f12640ba92c538b3b64a199a918d3bb0cc0d7f7123c6ba93cb065e1a2d049f0"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8937dc548621b336b0d8383a3470fb7192b42a108c760a152282909867bf5b26"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4afbb97d64cd8078edec859b07859a18ef3de7261a3a873ba52f32548373ae92"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c432710bdf8ccfdd75b0bc9cdf1fd21ff394363e4daec099c667f3c5f1721e2b"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:366cc4e194f7feb4e3038d6775fd4b69835e7d923972aee5baec986de972abd6"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b286ed65a891928bd457ffa0cd5fec09b9b5208bfd096d087e45369f07c5cb85"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9fee41c99312002e5d1f7462b1954aefed44c6efe5f021c3eac311640c16f6b7"}, - {file = "psycopg_binary-3.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:87cceaf07760a04023596f9ca1d4e929d38ae8d778161cb3e8d27a0f990dd264"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:965455eac8547f32b3181d5ec9ad8b9be500c10fe06193543efaaebe3e4ce70c"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:71adcc8bc80a65b776510bc39992edf942ace35b153ed7a9c6c573a6849ce308"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f73adc05452fb85e7a12ed3f69c81540a8875960739082e6ea5e28c373a30774"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8630943143c6d6ca9aefc88bbe5e76c90553f4e1a3b2dc339e67dc34aa86f7e"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bffb61e198a91f712cc3d7f2d176a697cb05b284b2ad150fb8edb308eba9002"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4fa2240c9fceddaa815a58f29212826fafe43ce80ff666d38c4a03fb036955"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:192a5f8496e6e1243fdd9ac20e117e667c0712f148c5f9343483b84435854c78"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64dc6e9ec64f592f19dc01a784e87267a64a743d34f68488924251253da3c818"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:79498df398970abcee3d326edd1d4655de7d77aa9aecd578154f8af35ce7bbd2"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:949551752930d5e478817e0b49956350d866b26578ced0042a61967e3fcccdea"}, + {file = "psycopg_binary-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:80a2337e2dfb26950894c8301358961430a0304f7bfe729d34cc036474e9c9b1"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:6d8f2144e0d5808c2e2aed40fbebe13869cd00c2ae745aca4b3b16a435edb056"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94253be2b57ef2fea7ffe08996067aabf56a1eb9648342c9e3bad9e10c46e045"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fda0162b0dbfa5eaed6cdc708179fa27e148cb8490c7d62e5cf30713909658ea"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c0419cdad8c70eaeb3116bb28e7b42d546f91baf5179d7556f230d40942dc78"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74fbf5dd3ef09beafd3557631e282f00f8af4e7a78fbfce8ab06d9cd5a789aae"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d784f614e4d53050cbe8abf2ae9d1aaacf8ed31ce57b42ce3bf2a48a66c3a5c"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4e76ce2475ed4885fe13b8254058be710ec0de74ebd8ef8224cf44a9a3358e5f"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5938b257b04c851c2d1e6cb2f8c18318f06017f35be9a5fe761ee1e2e344dfb7"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:257c4aea6f70a9aef39b2a77d0658a41bf05c243e2bf41895eb02220ac6306f3"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:06b5cc915e57621eebf2393f4173793ed7e3387295f07fed93ed3fb6a6ccf585"}, + {file = "psycopg_binary-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:09baa041856b35598d335b1a74e19a49da8500acedf78164600694c0ba8ce21b"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:48f8ca6ee8939bab760225b2ab82934d54330eec10afe4394a92d3f2a0c37dd6"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5361ea13c241d4f0ec3f95e0bf976c15e2e451e9cc7ef2e5ccfc9d170b197a40"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb987f14af7da7c24f803111dbc7392f5070fd350146af3345103f76ea82e339"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0463a11b1cace5a6aeffaf167920707b912b8986a9c7920341c75e3686277920"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b7be9a6c06518967b641fb15032b1ed682fd3b0443f64078899c61034a0bca6"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64a607e630d9f4b2797f641884e52b9f8e239d35943f51bef817a384ec1678fe"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fa33ead69ed133210d96af0c63448b1385df48b9c0247eda735c5896b9e6dbbf"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1f8b0d0e99d8e19923e6e07379fa00570be5182c201a8c0b5aaa9a4d4a4ea20b"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:709447bd7203b0b2debab1acec23123eb80b386f6c29e7604a5d4326a11e5bd6"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5e37d5027e297a627da3551a1e962316d0f88ee4ada74c768f6c9234e26346d9"}, + {file = "psycopg_binary-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:261f0031ee6074765096a19b27ed0f75498a8338c3dcd7f4f0d831e38adf12d1"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:41fdec0182efac66b27478ac15ef54c9ebcecf0e26ed467eb7d6f262a913318b"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:07d019a786eb020c0f984691aa1b994cb79430061065a694cf6f94056c603d26"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c57615791a337378fe5381143259a6c432cdcbb1d3e6428bfb7ce59fff3fb5c"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8eb9a4e394926b93ad919cad1b0a918e9b4c846609e8c1cfb6b743683f64da0"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5905729668ef1418bd36fbe876322dcb0f90b46811bba96d505af89e6fbdce2f"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd65774ed7d65101b314808b6893e1a75b7664f680c3ef18d2e5c84d570fa393"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:700679c02f9348a0d0a2adcd33a0275717cd0d0aee9d4482b47d935023629505"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:96334bb64d054e36fed346c50c4190bad9d7c586376204f50bede21a913bf942"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9099e443d4cc24ac6872e6a05f93205ba1a231b1a8917317b07c9ef2b955f1f4"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1985ab05e9abebfbdf3163a16ebb37fbc5d49aff2bf5b3d7375ff0920bbb54cd"}, + {file = "psycopg_binary-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:e90352d7b610b4693fad0feea48549d4315d10f1eba5605421c92bb834e90170"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:69320f05de8cdf4077ecd7fefdec223890eea232af0d58f2530cbda2871244a0"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4926ea5c46da30bec4a85907aa3f7e4ea6313145b2aa9469fdb861798daf1502"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c64c4cd0d50d5b2288ab1bcb26c7126c772bbdebdfadcd77225a77df01c4a57e"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05a1bdce30356e70a05428928717765f4a9229999421013f41338d9680d03a63"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad357e426b0ea5c3043b8ec905546fa44b734bf11d33b3da3959f6e4447d350"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:967b47a0fd237aa17c2748fdb7425015c394a6fb57cdad1562e46a6eb070f96d"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:71db8896b942770ed7ab4efa59b22eee5203be2dfdee3c5258d60e57605d688c"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2773f850a778575dd7158a6dd072f7925b67f3ba305e2003538e8831fec77a1d"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aeddf7b3b3f6e24ccf7d0edfe2d94094ea76b40e831c16eff5230e040ce3b76b"}, + {file = "psycopg_binary-3.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:824c867a38521d61d62b60aca7db7ca013a2b479e428a0db47d25d8ca5067410"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:9994f7db390c17fc2bd4c09dca722fd792ff8a49bb3bdace0c50a83f22f1767d"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1303bf8347d6be7ad26d1362af2c38b3a90b8293e8d56244296488ee8591058e"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:842da42a63ecb32612bb7f5b9e9f8617eab9bc23bd58679a441f4150fcc51c96"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2bb342a01c76f38a12432848e6013c57eb630103e7556cf79b705b53814c3949"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd40af959173ea0d087b6b232b855cfeaa6738f47cb2a0fd10a7f4fa8b74293f"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9b60b465773a52c7d4705b0a751f7f1cdccf81dd12aee3b921b31a6e76b07b0e"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fc6d87a1c44df8d493ef44988a3ded751e284e02cdf785f746c2d357e99782a6"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f0b018e37608c3bfc6039a1dc4eb461e89334465a19916be0153c757a78ea426"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a29f5294b0b6360bfda69653697eff70aaf2908f58d1073b0acd6f6ab5b5a4f"}, + {file = "psycopg_binary-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:e56b1fd529e5dde2d1452a7d72907b37ed1b4f07fdced5d8fb1e963acfff6749"}, ] [[package]] @@ -1969,18 +1969,18 @@ files = [ [[package]] name = "redis" -version = "5.0.8" +version = "5.1.0" description = "Python client for Redis database and key-value store" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4"}, - {file = "redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870"}, + {file = "redis-5.1.0-py3-none-any.whl", hash = "sha256:fd4fccba0d7f6aa48c58a78d76ddb4afc698f5da4a2c1d03d916e4fd7ab88cdd"}, + {file = "redis-5.1.0.tar.gz", hash = "sha256:b756df1e4a3858fcc0ef861f3fc53623a96c41e2b1f5304e09e0fe758d333d40"}, ] [package.extras] -hiredis = ["hiredis (>1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +hiredis = ["hiredis (>=3.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] [[package]] name = "requests" @@ -2332,4 +2332,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "38d05c5a9700e1d71634d29a93a67f15cfce109a5788debf019e778a7c51c5fc" +content-hash = "dc6b1a361b6d3b8c7b8b8886017fd21667b06ea16a8420430ec98a5ab58f69a5" diff --git a/prs2/referral/management/commands/load_lgas.py b/prs2/referral/management/commands/load_lgas.py deleted file mode 100644 index 3ffad7f0..00000000 --- a/prs2/referral/management/commands/load_lgas.py +++ /dev/null @@ -1,19 +0,0 @@ -import csv -from django.core.management.base import BaseCommand, CommandError -from referral.models import LocalGovernment - - -class Command(BaseCommand): - help = 'Loads LGA names from a CSV (lga.csv)' - - def handle(self, *args, **options): - try: - lga_file = open('lga.csv', 'r') - except IOError: - raise CommandError('lga.csv file not present') - - lga_reader = csv.reader(lga_file) - for row in lga_reader: - LocalGovernment.objects.create(name=row[0]) - - self.stdout.write('Done!') diff --git a/prs2/referral/management/commands/load_region_geom.py b/prs2/referral/management/commands/load_region_geom.py index 3746f726..b827d293 100644 --- a/prs2/referral/management/commands/load_region_geom.py +++ b/prs2/referral/management/commands/load_region_geom.py @@ -1,22 +1,43 @@ from django.contrib.gis.geos import GEOSGeometry -from django.core.management.base import BaseCommand, CommandError -import json +from django.core.management.base import BaseCommand from referral.models import Region +from referral.utils import wfs_getfeature class Command(BaseCommand): - help = 'Loads Region geometry from serialised GeoJSON (dpaw_regions.json)' + help = "Loads Region geometry from a Geoserver layer WFS" + + def add_arguments(self, parser): + parser.add_argument( + "--typename", + action="store", + required=True, + type=str, + dest="typename", + help="typeName value for WFS GetFeature request (namespace:featuretype)", + ) + parser.add_argument( + "--field", + action="store", + required=True, + type=str, + dest="field", + help="GeoJSON property key containing the region name", + ) def handle(self, *args, **options): - try: - regions_json = json.load(open('dpaw_regions.json', 'r')) - except IOError: - raise CommandError('dpaw_regions.json file not present') + type_name = options["typename"] + field = options["field"] + regions_data = wfs_getfeature(type_name) + + if "features" not in regions_data: + self.stdout.write("No data returned") + return - for f in regions_json['features']: - region = Region.objects.get(name__istartswith=f['properties']['region']) - region.region_mpoly = GEOSGeometry(json.dumps(f['geometry'])) + for feature in regions_data["features"]: + region = Region.objects.get(name__iexact=feature["properties"][field]) + region.region_mpoly = GEOSGeometry(str(feature["geometry"])) region.save() - self.stdout.write('{} geometry updated'.format(region)) + self.stdout.write("{} region geometry updated".format(region)) - self.stdout.write('Done!') + self.stdout.write("Completed") diff --git a/prs2/referral/management/commands/overdue_tasks_email.py b/prs2/referral/management/commands/overdue_tasks_email.py index 1c7a57c6..2c99ebf3 100644 --- a/prs2/referral/management/commands/overdue_tasks_email.py +++ b/prs2/referral/management/commands/overdue_tasks_email.py @@ -3,11 +3,11 @@ class Command(BaseCommand): - help = 'Send email to users notifying about overdue tasks' + help = "Send email to users notifying about overdue tasks" def handle(self, *args, **options): try: overdue_task_email() - self.stdout.write('Done') + self.stdout.write("Done") except Exception: - raise CommandError('Unable to send overdue tasks email to users') + raise CommandError("Unable to send overdue tasks email to users") diff --git a/prs2/referral/utils.py b/prs2/referral/utils.py index 6a85f724..4cfadc6f 100644 --- a/prs2/referral/utils.py +++ b/prs2/referral/utils.py @@ -1,4 +1,8 @@ -from datetime import datetime, date +import json +import re +from datetime import date, datetime + +import requests from dbca_utils.utils import env from django.apps import apps from django.conf import settings @@ -8,10 +12,7 @@ from django.db.models.base import ModelBase from django.utils.encoding import smart_str from django.utils.safestring import mark_safe -import json from reversion.models import Version -import re -import requests from unidecode import unidecode @@ -177,9 +178,7 @@ def user_referral_history(user, referral): else: new_ref_history.append(i) # Add the passed-in referral to the end of the new list. - new_ref_history.append( - [referral.id, datetime.strftime(datetime.today(), "%d-%m-%Y")] - ) + new_ref_history.append([referral.id, datetime.strftime(datetime.today(), "%d-%m-%Y")]) # History can be a maximum of 20 referrals; slice the new list accordingly. if len(new_ref_history) > 20: new_ref_history = new_ref_history[-20:] @@ -189,16 +188,13 @@ def user_referral_history(user, referral): def user_task_history(user, task, comment=None): - """Utility function to update the task history in a user's profile. - """ + """Utility function to update the task history in a user's profile.""" profile = user.userprofile if not profile.task_history: task_history = [] else: task_history = json.loads(profile.task_history) - task_history.append( - [task.pk, datetime.strftime(datetime.today(), "%d-%m-%Y"), comment] - ) + task_history.append([task.pk, datetime.strftime(datetime.today(), "%d-%m-%Y"), comment]) profile.task_history = json.dumps(task_history) profile.save() @@ -231,9 +227,7 @@ def is_prs_power_user(request): def prs_user(request): - return ( - is_prs_user(request) or is_prs_power_user(request) or request.user.is_superuser - ) + return is_prs_user(request) or is_prs_power_user(request) or request.user.is_superuser def update_revision_history(app_model): @@ -265,10 +259,10 @@ def update_revision_history(app_model): def overdue_task_email(): - """A utility function to send an email to each user with tasks that are overdue. - """ + """A utility function to send an email to each user with tasks that are overdue.""" from django.contrib.auth.models import Group - from .models import TaskState, Task + + from .models import Task, TaskState prs_grp = Group.objects.get(name=settings.PRS_USER_GROUP) users = prs_grp.user_set.filter(is_active=True) @@ -295,20 +289,14 @@ def overdue_task_email(): assigned to you within PRS are currently overdue:

This is an automatically-generated email - please do not reply.

" - ) + text_content += "This is an automatically-generated email - please do not reply.\n" + html_content += "

This is an automatically-generated email - please do not reply.

" msg = EmailMultiAlternatives(subject, text_content, from_email, to_email) msg.attach_alternative(html_content, "text/html") # Email should fail gracefully - ie no Exception raised on failure. @@ -317,35 +305,45 @@ def overdue_task_email(): return True -def query_cadastre(cql_filter, crs="EPSG:4326"): - """A utility function to query the internal DBCA Cadastre dataset via the passed-in CQL filter +def wfs_getfeature(type_name, crs="EPSG:4326", cql_filter=None, max_features=50): + """A utility function to perform a GetFeature request on a WFS endpoint and return results as GeoJSON. """ - url = env('GEOSERVER_URL', None) - auth = (env('GEOSERVER_SSO_USER', None), env('GEOSERVER_SSO_PASS', None)) - type_name = env('CADASTRE_LAYER_NAME', '') + url = env("GEOSERVER_URL", None) + auth = (env("GEOSERVER_SSO_USER", None), env("GEOSERVER_SSO_PASS", None)) params = { - 'service': 'WFS', - 'version': '2.0.0', - 'typeName': type_name, - 'request': 'getFeature', - 'outputFormat': 'json', - 'SRSName': f'urn:x-ogc:def:crs:{crs}', - 'cql_filter': cql_filter, + "service": "WFS", + "version": "1.1.0", + "typeName": type_name, + "request": "getFeature", + "outputFormat": "json", + "SRSName": f"urn:x-ogc:def:crs:{crs}", + "maxFeatures": max_features, } + if cql_filter: + params["cql_filter"] = cql_filter resp = requests.get(url, auth=auth, params=params) - resp.raise_for_status() + try: + resp.raise_for_status() + except: + # On exception, return an empty dict. + return {} + return resp.json() def query_caddy(q): - """Utility function to proxy queries to the Caddy geocoder service. - """ - url = env('GEOCODER_URL', None) - auth = (env('GEOSERVER_SSO_USER', None), env('GEOSERVER_SSO_PASS', None)) - params = {'q': q} + """Utility function to proxy queries to the Caddy geocoder service.""" + url = env("GEOCODER_URL", None) + auth = (env("GEOSERVER_SSO_USER", None), env("GEOSERVER_SSO_PASS", None)) + params = {"q": q} resp = requests.get(url, auth=auth, params=params) - resp.raise_for_status() + try: + resp.raise_for_status() + except: + # On exception, return an empty list. + return [] + return resp.json() diff --git a/prs2/referral/views.py b/prs2/referral/views.py index ede3d78c..a3ab4951 100644 --- a/prs2/referral/views.py +++ b/prs2/referral/views.py @@ -1,5 +1,10 @@ +import json +import logging +import re from copy import copy -from datetime import datetime, date, timedelta +from datetime import date, datetime, timedelta + +from dbca_utils.utils import env from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin @@ -10,82 +15,65 @@ from django.core.mail import EmailMultiAlternatives from django.core.paginator import Paginator from django.core.serializers import serialize -from django.urls import reverse -from django.db.models import Q, F -from django.http import ( - HttpResponse, - HttpResponseRedirect, - JsonResponse, - HttpResponseForbidden, - HttpResponseBadRequest, -) +from django.db.models import F, Q +from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect, JsonResponse from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.safestring import mark_safe from django.views.decorators.csrf import csrf_exempt -from django.views.generic import View, ListView, TemplateView, FormView -import json -import logging -import re -from taggit.models import Tag - +from django.views.generic import FormView, ListView, TemplateView, View +from indexer.utils import typesense_client +from referral.forms import ( + ClearanceCreateForm, + IntersectingReferralForm, + LocationForm, + ReferralCreateForm, + TagReplaceForm, + TaskCancelForm, + TaskCompleteForm, + TaskCreateForm, + TaskForm, + TaskInheritForm, + TaskReassignForm, + TaskStartForm, + TaskStopForm, +) from referral.models import ( - Task, + Agency, + Bookmark, Clearance, - Referral, Condition, + Location, Note, + Organisation, Record, - Location, - Bookmark, + Referral, + ReferralType, RelatedReferral, + Task, TaskState, - Organisation, TaskType, - Agency, - ReferralType, ) from referral.utils import ( - is_model_or_string, breadcrumbs_li, - smart_truncate, - user_task_history, - user_referral_history, - prs_user, + is_model_or_string, is_prs_power_user, - query_cadastre, + prs_user, query_caddy, + smart_truncate, + user_referral_history, + user_task_history, + wfs_getfeature, ) -from referral.forms import ( - ReferralCreateForm, - TaskCreateForm, - ClearanceCreateForm, - LocationForm, - TaskForm, - TaskCompleteForm, - TaskStopForm, - TaskStartForm, - TaskReassignForm, - TaskCancelForm, - TaskInheritForm, - IntersectingReferralForm, - TagReplaceForm, -) -from referral.views_base import ( - PrsObjectDetail, - PrsObjectList, - PrsObjectCreate, - PrsObjectUpdate, - PrsObjectDelete, -) -from indexer.utils import typesense_client +from referral.views_base import PrsObjectCreate, PrsObjectDelete, PrsObjectDetail, PrsObjectList, PrsObjectUpdate +from taggit.models import Tag LOGGER = logging.getLogger("prs") class SiteHome(LoginRequiredMixin, ListView): - """Site home page view. Returns an object list of tasks (ongoing or stopped). - """ + """Site home page view. Returns an object list of tasks (ongoing or stopped).""" stopped_tasks = False printable = False @@ -110,9 +98,7 @@ def get_context_data(self, **kwargs): context["headers"] = copy(Task.headers_site_home) if not self.stopped_tasks: context["stopped_tasks_exist"] = ( - Task.objects.current() - .filter(assigned_user=self.request.user, state__name="Stopped") - .exists() + Task.objects.current().filter(assigned_user=self.request.user, state__name="Stopped").exists() ) # Printable view only: pop the last element from 'headers' if "print" in self.request.GET or self.printable: @@ -123,8 +109,7 @@ def get_context_data(self, **kwargs): class HelpPage(LoginRequiredMixin, TemplateView): - """Help page (static template view). - """ + """Help page (static template view).""" template_name = "help_page.html" @@ -161,11 +146,11 @@ def get_context_data(self, **kwargs): client = typesense_client() page = self.request.GET.get("page", 1) search_q = { - 'q': self.request.GET["q"], - 'sort_by': 'created:desc', - 'num_typos': 0, - 'page': page, - 'per_page': 20, + "q": self.request.GET["q"], + "sort_by": "created:desc", + "num_typos": 0, + "page": page, + "per_page": 20, } if collection == "referrals": @@ -196,57 +181,67 @@ def get_context_data(self, **kwargs): # Replace underscores in search field names with spaces. highlights.append((key.replace("_", " "), value["snippet"])) hit["highlights"] = highlights - context["search_result"].append({ - "object": Referral.objects.get(pk=hit["document"]["id"]), - "highlights": highlights, - }) + context["search_result"].append( + { + "object": Referral.objects.get(pk=hit["document"]["id"]), + "highlights": highlights, + } + ) elif collection == "records": for hit in search_result["hits"]: highlights = [] for key, value in hit["highlight"].items(): highlights.append((key.replace("_", " "), value["snippet"])) hit["highlights"] = highlights - context["search_result"].append({ - "object": Record.objects.get(pk=hit["document"]["id"]), - "highlights": highlights, - }) + context["search_result"].append( + { + "object": Record.objects.get(pk=hit["document"]["id"]), + "highlights": highlights, + } + ) elif collection == "notes": for hit in search_result["hits"]: highlights = [] for key, value in hit["highlight"].items(): highlights.append((key.replace("_", " "), value["snippet"])) hit["highlights"] = highlights - context["search_result"].append({ - "object": Note.objects.get(pk=hit["document"]["id"]), - "highlights": highlights, - }) + context["search_result"].append( + { + "object": Note.objects.get(pk=hit["document"]["id"]), + "highlights": highlights, + } + ) elif collection == "tasks": for hit in search_result["hits"]: highlights = [] for key, value in hit["highlight"].items(): highlights.append((key.replace("_", " "), value["snippet"])) hit["highlights"] = highlights - context["search_result"].append({ - "object": Task.objects.get(pk=hit["document"]["id"]), - "highlights": highlights, - }) + context["search_result"].append( + { + "object": Task.objects.get(pk=hit["document"]["id"]), + "highlights": highlights, + } + ) elif collection == "conditions": for hit in search_result["hits"]: highlights = [] for key, value in hit["highlight"].items(): highlights.append((key.replace("_", " "), value["snippet"])) hit["highlights"] = highlights - context["search_result"].append({ - "object": Condition.objects.get(pk=hit["document"]["id"]), - "highlights": highlights, - }) + context["search_result"].append( + { + "object": Condition.objects.get(pk=hit["document"]["id"]), + "highlights": highlights, + } + ) return context class IndexSearchCombined(LoginRequiredMixin, TemplateView): - """A combined version of the index search which returns referrals with linked objects. - """ + """A combined version of the index search which returns referrals with linked objects.""" + template_name = "referral/prs_index_search_combined.html" def get_context_data(self, **kwargs): @@ -264,9 +259,9 @@ def get_context_data(self, **kwargs): context["referral_headers"] = Referral.headers client = typesense_client() search_q = { - 'q': self.request.GET["q"], - 'sort_by': 'created:desc', - 'num_typos': 0, + "q": self.request.GET["q"], + "sort_by": "created:desc", + "num_typos": 0, } referrals = {} @@ -397,8 +392,7 @@ def get_context_data(self, **kwargs): class ReferralCreate(PrsObjectCreate): - """Dedicated create view for new referrals. - """ + """Dedicated create view for new referrals.""" model = Referral form_class = ReferralCreateForm @@ -415,9 +409,7 @@ def get_context_data(self, **kwargs): context["title"] = "CREATE A NEW REFERRAL" context["page_title"] = "PRS | Referrals | Create" # Pass in a serialised list of tag names. - context["tags"] = json.dumps( - [t.name for t in Tag.objects.all().order_by("name")] - ) + context["tags"] = json.dumps([t.name for t in Tag.objects.all().order_by("name")]) return context def get_initial(self): @@ -474,18 +466,14 @@ def form_valid(self, form): This task is attached to referral ID {0}.\n The referrer's reference is {1}.\n The referral address is {2}\n - """.format( - new_ref.id, new_ref.reference, address - ) + """.format(new_ref.id, new_ref.reference, address) html_content = """

This is an automated message to let you know that you have been assigned a PRS task by the sending user.

This task is attached to referral ID {0}, at this URL:

{1}

The referrer's reference is: {2}.

The referral address is {3}.

- """.format( - new_ref.pk, ref_url, new_ref.reference, address - ) + """.format(new_ref.pk, ref_url, new_ref.reference, address) msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email]) msg.attach_alternative(html_content, "text/html") # Email should fail gracefully - ie no Exception raised on failure. @@ -497,9 +485,7 @@ def form_valid(self, form): if req.POST.get("save"): return redirect(new_ref.get_absolute_url()) else: - return redirect( - reverse("referral_location_create", kwargs={"pk": new_ref.pk}) - ) + return redirect(reverse("referral_location_create", kwargs={"pk": new_ref.pk})) class ReferralDetail(PrsObjectDetail): @@ -568,9 +554,7 @@ def get_context_data(self, **kwargs): context["rel_model"] = self.related_model # Test if the user has bookmarked this referral. if Bookmark.objects.filter(referral=ref, user=self.request.user).exists(): - context["bookmark"] = Bookmark.objects.filter( - referral=ref, user=self.request.user - )[0] + context["bookmark"] = Bookmark.objects.filter(referral=ref, user=self.request.user)[0] # Add context for each child model type: task_list, note_list, etc. table = """ @@ -589,12 +573,7 @@ def get_context_data(self, **kwargs): headers.remove("Referral ID") headers.append("Actions") thead = "".join(["".format(h) for h in headers]) - rows = [ - "{}{}".format( - o.as_row_minus_referral(), o.as_row_actions() - ) - for o in obj_qs - ] + rows = ["{}{}".format(o.as_row_minus_referral(), o.as_row_actions()) for o in obj_qs] tbody = "".join(rows) obj_tab_html = table.format(thead, tbody) if m == Location: # Append a div for the map viewer. @@ -603,9 +582,7 @@ def get_context_data(self, **kwargs): context[obj_list] = obj_qs else: context["{}_count".format(m._meta.object_name.lower())] = 0 - context[obj_tab] = "No {} found for this referral".format( - m._meta.verbose_name_plural - ) + context[obj_tab] = "No {} found for this referral".format(m._meta.verbose_name_plural) context[obj_list] = None # Add child locations serialised as GeoJSON (if geometry exists). @@ -631,15 +608,11 @@ def get_context_data(self, **kwargs): if "id" in self.kwargs: # Relating to existing object. child_obj = child_model.objects.get(pk=self.kwargs["id"]) if self.kwargs["type"] == "addnote": - context["title"] = "ADD EXISTING NOTE(S) TO {}".format( - child_obj - ).upper() + context["title"] = "ADD EXISTING NOTE(S) TO {}".format(child_obj).upper() context["page_title"] = "PRS | Add note(s) to {}".format(child_obj) last_breadcrumb = "Add note(s) to {}".format(child_obj) elif "addrecord" in self.kwargs.values(): - context["title"] = "ADD EXISTING RECORD(S) TO {}".format( - child_obj - ).upper() + context["title"] = "ADD EXISTING RECORD(S) TO {}".format(child_obj).upper() context["page_title"] = "PRS | Add record(s) to {}".format(child_obj) last_breadcrumb = "Add record(s) to {}".format(child_obj) elif "addnewnote" in self.kwargs.values(): @@ -742,7 +715,7 @@ def form_valid(self, form): # Invalidate any cached referral detail fragment. if settings.REDIS_CACHE_HOST: - key = make_template_fragment_key('referral_detail', [self.object.referral.pk]) + key = make_template_fragment_key("referral_detail", [self.object.referral.pk]) cache.delete(key) redirect_url = redirect_url if redirect_url else self.get_success_url() @@ -771,9 +744,7 @@ def create_existing_note(self, form): note.records.add(obj) messages.success( self.request, - "The note has been added to {} {}.".format( - self.kwargs["model"].capitalize(), self.kwargs["id"] - ), + "The note has been added to {} {}.".format(self.kwargs["model"].capitalize(), self.kwargs["id"]), ) def create_new_note(self, form): @@ -824,9 +795,7 @@ def create_existing_record(self, form): record.note_set.add(obj) messages.success( self.request, - "The record has been added to {} {}.".format( - self.kwargs["model"].capitalize(), self.kwargs["id"] - ), + "The record has been added to {} {}.".format(self.kwargs["model"].capitalize(), self.kwargs["id"]), ) def create_new_record(self, form): @@ -859,37 +828,27 @@ def create_new_record(self, form): return redirect_url def get_condition_choices(self): - """Return conditions with 'approved' text only. - """ - condition_qs = ( - Condition.objects.current() - .filter(referral=self.parent_referral) - .exclude(condition="") - ) + """Return conditions with 'approved' text only.""" + condition_qs = Condition.objects.current().filter(referral=self.parent_referral).exclude(condition="") condition_choices = [] for i in condition_qs: condition_choices.append( ( i.id, - "{0} - {1}".format( - i.identifier or "", smart_truncate(i.condition, 100) - ), + "{0} - {1}".format(i.identifier or "", smart_truncate(i.condition, 100)), ) ) return condition_choices def create_clearance(self, form): - """For each of the chosen conditions in the form, create one clearance task. - """ + """For each of the chosen conditions in the form, create one clearance task.""" request = self.request tasks = [] for i in form.cleaned_data["conditions"]: condition = Condition.objects.get(pk=i) clearance_task = Task() - clearance_task.type = TaskType.objects.get( - name="Conditions clearance request" - ) + clearance_task.type = TaskType.objects.get(name="Conditions clearance request") clearance_task.referral = condition.referral clearance_task.assigned_user = form.cleaned_data["assigned_user"] clearance_task.start_date = form.cleaned_data["start_date"] @@ -901,34 +860,22 @@ def create_clearance(self, form): clearance_task.due_date = date.today() + timedelta(days=clearance_task.type.target_days) clearance_task.creator, clearance_task.modifier = request.user, request.user clearance_task.save() - condition.add_clearance( - task=clearance_task, deposited_plan=form.cleaned_data["deposited_plan"] - ) + condition.add_clearance(task=clearance_task, deposited_plan=form.cleaned_data["deposited_plan"]) # If the user checked the "Email user" box, send them a notification. if request.POST.get("email_user"): - subject = "PRS referral {0} - new clearance request notification".format( - clearance_task.referral.pk - ) + subject = "PRS referral {0} - new clearance request notification".format(clearance_task.referral.pk) from_email = request.user.email to_email = clearance_task.assigned_user.email - referral_url = ( - settings.SITE_URL + clearance_task.referral.get_absolute_url() - ) + referral_url = settings.SITE_URL + clearance_task.referral.get_absolute_url() text_content = """This is an automated message to let you know that you have been assigned PRS clearance request {0} by the sending user.\n This clearance request is attached to referral ID {1}.\n - """.format( - clearance_task.pk, clearance_task.referral.pk - ) + """.format(clearance_task.pk, clearance_task.referral.pk) html_content = """

This is an automated message to let you know that you have been assigned PRS clearance request {0} by the sending user.

This task is attached to referral ID {1}, at this URL:

-

{2}

""".format( - clearance_task.pk, clearance_task.referral.pk, referral_url - ) - msg = EmailMultiAlternatives( - subject, text_content, from_email, [to_email] - ) +

{2}

""".format(clearance_task.pk, clearance_task.referral.pk, referral_url) + msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email]) msg.attach_alternative(html_content, "text/html") # Email should fail gracefully - ie no Exception raised on failure. msg.send(fail_silently=True) @@ -958,18 +905,10 @@ def create_condition(self, obj): html_content += '

Condition ID {}

'.format( settings.SITE_URL + obj.get_absolute_url(), obj.pk ) - text_content += "The condition was created by {}.\n".format( - obj.creator.get_full_name() - ) - html_content += "

The condition was created by {}.

".format( - obj.creator.get_full_name() - ) - text_content += ( - "This is an automatically-generated email - please do not reply.\n" - ) - html_content += ( - "

This is an automatically-generated email - please do not reply.

" - ) + text_content += "The condition was created by {}.\n".format(obj.creator.get_full_name()) + html_content += "

The condition was created by {}.

".format(obj.creator.get_full_name()) + text_content += "This is an automatically-generated email - please do not reply.\n" + html_content += "

This is an automatically-generated email - please do not reply.

" msg = EmailMultiAlternatives(subject, text_content, from_email, to_email) msg.attach_alternative(html_content, "text/html") # Email should fail gracefully - ie no Exception raised on failure. @@ -1101,8 +1040,7 @@ def post(self, request, *args, **kwargs): return HttpResponseRedirect(self.get_success_url()) def polygon_intersects(self, locations): - """ Check to see if the location polygon intersects with any other locations. - """ + """Check to see if the location polygon intersects with any other locations.""" intersecting_locations = [] for location in locations: if location.poly: @@ -1159,9 +1097,7 @@ def form_valid(self, form): messages.success( self.request, - "{} Referral relationship(s) created.".format( - len(form.cleaned_data["related_refs"]) - ), + "{} Referral relationship(s) created.".format(len(form.cleaned_data["related_refs"])), ) return redirect(self.get_success_url()) @@ -1173,11 +1109,7 @@ def referral_intersecting_locations(self): for loc_id in loc_ids: location = get_object_or_404(Location, pk=loc_id) geom = location.poly.wkt - intersects = ( - Location.objects.current() - .exclude(id=location.id) - .filter(poly__isnull=False) - ) + intersects = Location.objects.current().exclude(id=location.id).filter(poly__isnull=False) # Get a qs of intersecting locations intersects = intersects.filter(poly__intersects=geom) # Get a qs of referrals @@ -1240,7 +1172,7 @@ def post(self, request, *args, **kargs): # Invalidate the cached referral detail fragment. if settings.REDIS_CACHE_HOST: referral = rec.referral - key = make_template_fragment_key('referral_detail', [referral.pk]) + key = make_template_fragment_key("referral_detail", [referral.pk]) cache.delete(key) return JsonResponse( @@ -1273,9 +1205,7 @@ def get(self, request, *args, **kwargs): task = self.get_object() if action == "update" and task.stop_date and not task.restart_date: - messages.error( - request, "You can't edit a stopped task - restart the task first!" - ) + messages.error(request, "You can't edit a stopped task - restart the task first!") return redirect(task.get_absolute_url()) if action == "stop" and task.complete_date: messages.error(request, "You can't stop a completed task!") @@ -1318,11 +1248,7 @@ def get(self, request, *args, **kwargs): "LSU - s121 diversification permits", ] ) - if ( - action == "complete" - and task.referral.type in trigger_ref_type - and not task.referral.has_location - ): + if action == "complete" and task.referral.type in trigger_ref_type and not task.referral.has_location: msg = """You are unable to complete this task without first recording location(s) on the referral. Click here to create location(s).""".format( @@ -1356,9 +1282,7 @@ def get_context_data(self, **kwargs): # Create a breadcrumb trail: Home[URL] > Tasks[URL] > ID[URL] > Action action = self.kwargs["action"] obj = self.get_object() - context["page_title"] = "PRS | Tasks | {} | {}".format( - obj.pk, action.capitalize() - ) + context["page_title"] = "PRS | Tasks | {} | {}".format(obj.pk, action.capitalize()) links = [ (reverse("site_home"), "Home"), (reverse("prs_object_list", kwargs={"model": "tasks"}), "Tasks"), @@ -1371,9 +1295,7 @@ def get_context_data(self, **kwargs): context["breadcrumb_trail"] = breadcrumbs_li(links) context["title"] = action.upper() + " TASK" # Pass in a serialised list of tag names. - context["tags"] = json.dumps( - [t.name for t in Tag.objects.all().order_by("name")] - ) + context["tags"] = json.dumps([t.name for t in Tag.objects.all().order_by("name")]) return context def get_success_url(self): @@ -1430,9 +1352,7 @@ def form_valid(self, form): elif action == "complete": if obj.type.name == "Assess a referral": # Rule: proposed condition is mandatory for some 'Assess' task outcomes. - trigger_outcome = TaskState.objects.filter( - name__in=["Response with condition"] - ) + trigger_outcome = TaskState.objects.filter(name__in=["Response with condition"]) trigger_ref_type = ReferralType.objects.filter( name__in=[ "Development application", @@ -1494,9 +1414,7 @@ def get_queryset(self): # UserProfile referral_history is a list of lists ([pk, date]). try: # Empty history fails. history_list = json.loads(self.request.user.userprofile.referral_history) - return Referral.objects.current().filter( - pk__in=[i[0] for i in history_list] - ) + return Referral.objects.current().filter(pk__in=[i[0] for i in history_list]) except Exception: return Referral.objects.none() @@ -1544,8 +1462,7 @@ def get_context_data(self, **kwargs): class TagList(PrsObjectList): - """Custom view to return a readonly list of tags (rendered HTML or JSON). - """ + """Custom view to return a readonly list of tags (rendered HTML or JSON).""" model = Tag template_name = "referral/tag_list.html" @@ -1574,16 +1491,12 @@ class TagReplace(LoginRequiredMixin, FormView): def dispatch(self, request, *args, **kwargs): if not request.user.is_superuser and not is_prs_power_user(request): - return HttpResponseForbidden( - "You do not have permission to use this function." - ) + return HttpResponseForbidden("You do not have permission to use this function.") return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["page_title"] = " | ".join( - [settings.APPLICATION_ACRONYM, "Replace tag"] - ) + context["page_title"] = " | ".join([settings.APPLICATION_ACRONYM, "Replace tag"]) context["title"] = "REPLACE TAG" return context @@ -1605,15 +1518,12 @@ def form_valid(self, form): obj.tags.add(new) # Finally, delete the old tag old.delete() - messages.success( - self.request, 'All "{}" tags have been replaced by "{}"'.format(old, new) - ) + messages.success(self.request, 'All "{}" tags have been replaced by "{}"'.format(old, new)) return HttpResponseRedirect(reverse("tag_list")) class ReferralTagged(PrsObjectList): - """Override the Referral model list view to filter tagged objects. - """ + """Override the Referral model list view to filter tagged objects.""" model = Referral @@ -1645,8 +1555,7 @@ def get_context_data(self, **kwargs): class BookmarkList(PrsObjectList): - """Override to default object list to only show current user bookmarks. - """ + """Override to default object list to only show current user bookmarks.""" model = Bookmark @@ -1679,9 +1588,7 @@ def post(self, request, *args, **kwargs): # Delete referral relationships # We can just call delete on this queryset. - RelatedReferral.objects.filter( - Q(from_referral=ref) | Q(to_referral=ref) - ).delete() + RelatedReferral.objects.filter(Q(from_referral=ref) | Q(to_referral=ref)).delete() # Delete any tags on the referral ref.tags.clear() # Delete tasks @@ -1719,8 +1626,7 @@ def post(self, request, *args, **kwargs): class ReferralRelate(PrsObjectList): - """Custom list view to search referrals to relate together. - """ + """Custom list view to search referrals to relate together.""" model = Referral template_name = "referral/referral_relate.html" @@ -1753,14 +1659,14 @@ def post(self, request, *args, **kwargs): # NOTE: query parameters always live in request.GET. if not self.request.GET.get("ref_pk", None): raise AttributeError( - "Relate view {} must be called with a " - "ref_pk query parameter.".format(self.__class__.__name__) + "Relate view {} must be called with a " "ref_pk query parameter.".format(self.__class__.__name__) ) if "create" not in self.request.GET and "delete" not in self.request.GET: raise AttributeError( - "Relate view {} must be called with either " - "create or delete query parameters.".format(self.__class__.__name__) + "Relate view {} must be called with either " "create or delete query parameters.".format( + self.__class__.__name__ + ) ) ref1 = self.get_object() @@ -1776,9 +1682,9 @@ def post(self, request, *args, **kwargs): # Invalidate the cached referral detail fragments. if settings.REDIS_CACHE_HOST: - key = make_template_fragment_key('referral_detail', [ref1.pk]) + key = make_template_fragment_key("referral_detail", [ref1.pk]) cache.delete(key) - key = make_template_fragment_key('referral_detail', [ref2.pk]) + key = make_template_fragment_key("referral_detail", [ref2.pk]) cache.delete(key) return redirect(ref1.get_absolute_url()) @@ -1833,15 +1739,11 @@ def post(self, request, *args, **kwargs): # On Cancel, redirect to the Condition URL. if request.POST.get("cancel"): - return HttpResponseRedirect( - reverse( - "prs_object_detail", kwargs={"pk": obj.pk, "model": "conditions"} - ) - ) + return HttpResponseRedirect(reverse("prs_object_detail", kwargs={"pk": obj.pk, "model": "conditions"})) # Invalidate any cached referral detail fragment. if settings.REDIS_CACHE_HOST: - key = make_template_fragment_key('referral_detail', [obj.referral.pk]) + key = make_template_fragment_key("referral_detail", [obj.referral.pk]) cache.delete(key) return super().post(request, *args, **kwargs) @@ -1861,64 +1763,48 @@ def form_valid(self, form): self.request.user, ) clearance_task.save() - obj.add_clearance( - task=clearance_task, deposited_plan=form.cleaned_data["deposited_plan"] - ) + obj.add_clearance(task=clearance_task, deposited_plan=form.cleaned_data["deposited_plan"]) messages.success(self.request, "New clearance request created successfully.") # If the user check the "Email user" box, send them a notification. if self.request.POST.get("email_user"): - subject = "PRS referral {0} - new clearance request notification".format( - clearance_task.referral.pk - ) + subject = "PRS referral {0} - new clearance request notification".format(clearance_task.referral.pk) from_email = self.request.user.email to_email = clearance_task.assigned_user.email - referral_url = ( - settings.SITE_URL + clearance_task.referral.get_absolute_url() - ) + referral_url = settings.SITE_URL + clearance_task.referral.get_absolute_url() text_content = """This is an automated message to let you know that you have been assigned PRS clearance request {0} by the sending user.\n This clearance request is attached to referral ID {1}.\n - """.format( - clearance_task.pk, clearance_task.referral.pk - ) + """.format(clearance_task.pk, clearance_task.referral.pk) html_content = """

This is an automated message to let you know that you have been assigned PRS clearance request {0} by the sending user.

This task is attached to referral ID {1}, at this URL:

-

{2}

""".format( - clearance_task.pk, clearance_task.referral.pk, referral_url - ) +

{2}

""".format(clearance_task.pk, clearance_task.referral.pk, referral_url) msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email]) msg.attach_alternative(html_content, "text/html") # Email should fail gracefully - ie no Exception raised on failure. msg.send(fail_silently=True) # Business rule: for each new clearance request, email users in the PRS power users group. - subject = "PRS referral {} - new condition clearance request notification".format( - clearance_task.referral.pk - ) + subject = "PRS referral {} - new condition clearance request notification".format(clearance_task.referral.pk) from_email = "PRS-Alerts@dbca.wa.gov.au" pu_group = Group.objects.get(name=settings.PRS_POWER_USER_GROUP) to_email = [user.email for user in pu_group.user_set.filter(is_active=True)] - text_content = "This is an automated message to let you know that the following clearance request was just created:\n" - html_content = "

This is an automated message to let you know that the following clearance request was just created:

" + text_content = ( + "This is an automated message to let you know that the following clearance request was just created:\n" + ) + html_content = ( + "

This is an automated message to let you know that the following clearance request was just created:

" + ) text_content += "* Task ID {}\n".format(clearance_task.pk) html_content += '

Task ID {}

'.format( settings.SITE_URL + clearance_task.get_absolute_url(), clearance_task.pk ) - text_content += "The clearance task was created by {}.\n".format( - clearance_task.creator.get_full_name() - ) - html_content += "

The clearance task was created by {}.

".format( - clearance_task.creator.get_full_name() - ) - text_content += ( - "This is an automatically-generated email - please do not reply.\n" - ) - html_content += ( - "

This is an automatically-generated email - please do not reply.

" - ) + text_content += "The clearance task was created by {}.\n".format(clearance_task.creator.get_full_name()) + html_content += "

The clearance task was created by {}.

".format(clearance_task.creator.get_full_name()) + text_content += "This is an automatically-generated email - please do not reply.\n" + html_content += "

This is an automatically-generated email - please do not reply.

" msg = EmailMultiAlternatives(subject, text_content, from_email, to_email) msg.attach_alternative(html_content, "text/html") # Email should fail gracefully - i.e. no Exception raised on failure. @@ -1937,40 +1823,38 @@ def get(self, request, *args, **kwargs): record = get_object_or_404(Record, pk=self.kwargs["pk"]) if record.infobase_id: response = HttpResponse(content_type="application/octet-stream") - response[ - "Content-Disposition" - ] = "attachment; filename=infobase_{}.obr".format(record.infobase_id) + response["Content-Disposition"] = "attachment; filename=infobase_{}.obr".format(record.infobase_id) # The HttpResponse is a file-like object; write the Infobase ID and return it. response.write(record.infobase_id) return response else: - messages.warning( - request, "That record is not associated with an InfoBase object ID." - ) + messages.warning(request, "That record is not associated with an InfoBase object ID.") return HttpResponseRedirect(record.get_absolute_url()) class CadastreQuery(View): - """Basic view endpoint to send a CQL filter query to the Cadastre spatial service. - """ + """Basic view endpoint to send a CQL filter query to the Cadastre spatial service.""" + http_method_names = ["get"] def get(self, request, *args, **kwargs): cql_filter = request.GET.get("cql_filter", None) if not cql_filter: + # This view requires a CQL filter value. return HttpResponseBadRequest("Bad request") crs = request.GET.get("crs", None) + type_name = env("CADASTRE_LAYER_NAME") if crs: - resp = query_cadastre(cql_filter, crs) + resp = wfs_getfeature(type_name, cql_filter, crs) else: - resp = query_cadastre(cql_filter) + resp = wfs_getfeature(type_name, cql_filter) return JsonResponse(resp) class ReferralMap(LoginRequiredMixin, TemplateView): - """A map view displaying all referral locations. - """ + """A map view displaying all referral locations.""" + template_name = "referral/referral_map.html" def get_context_data(self, **kwargs): @@ -1980,8 +1864,8 @@ def get_context_data(self, **kwargs): class GeocodeQuery(View): - """Basic view endpoint to send a geocode query to the Caddy spatial service. - """ + """Basic view endpoint to send a geocode query to the Caddy spatial service.""" + http_method_names = ["get"] def get(self, request, *args, **kwargs): diff --git a/prs2/settings.py b/prs2/settings.py index 29cdd958..f7197eda 100644 --- a/prs2/settings.py +++ b/prs2/settings.py @@ -8,6 +8,7 @@ from dbca_utils.utils import env from django.core.exceptions import DisallowedHost from django.db.utils import OperationalError +from redis.exceptions import ConnectionError # Project paths # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -47,9 +48,7 @@ AZURE_ACCOUNT_NAME = env("AZURE_ACCOUNT_NAME", "name") AZURE_ACCOUNT_KEY = env("AZURE_ACCOUNT_KEY", "key") AZURE_CONTAINER = env("AZURE_CONTAINER", "container") - AZURE_URL_EXPIRATION_SECS = env( - "AZURE_URL_EXPIRATION_SECS", 3600 - ) # Default one hour. + AZURE_URL_EXPIRATION_SECS = env("AZURE_URL_EXPIRATION_SECS", 3600) # Default one hour. # PRS may deploy its own instance of Geoserver. KMI_GEOSERVER_URL = env("KMI_GEOSERVER_URL", "") @@ -298,6 +297,9 @@ def sentry_excluded_exceptions(event, hint): # Exclude exceptions related to host requests not in ALLOWED_HOSTS. elif hint["exc_info"][0] is DisallowedHost: return None + # Exclude Redis service connection errors. + elif hint["exc_info"][0] is ConnectionError: + return None return event @@ -305,12 +307,8 @@ def sentry_excluded_exceptions(event, hint): # Sentry config SENTRY_DSN = env("SENTRY_DSN", None) SENTRY_SAMPLE_RATE = env("SENTRY_SAMPLE_RATE", 1.0) # Error sampling rate -SENTRY_TRANSACTION_SAMPLE_RATE = env( - "SENTRY_TRANSACTION_SAMPLE_RATE", 0.0 -) # Transaction sampling -SENTRY_PROFILES_SAMPLE_RATE = env( - "SENTRY_PROFILES_SAMPLE_RATE", 0.0 -) # Proportion of sampled transactions to profile. +SENTRY_TRANSACTION_SAMPLE_RATE = env("SENTRY_TRANSACTION_SAMPLE_RATE", 0.0) # Transaction sampling +SENTRY_PROFILES_SAMPLE_RATE = env("SENTRY_PROFILES_SAMPLE_RATE", 0.0) # Proportion of sampled transactions to profile. SENTRY_ENVIRONMENT = env("SENTRY_ENVIRONMENT", None) if SENTRY_DSN and SENTRY_ENVIRONMENT: import sentry_sdk diff --git a/pyproject.toml b/pyproject.toml index f12444a7..af07a4e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,15 +8,15 @@ package-mode = false [tool.poetry.dependencies] python = "^3.12" -django = "4.2.15" -psycopg = { version = "3.2.2", extras = ["binary", "pool"] } +django = "4.2.16" +psycopg = {version = "3.2.3", extras = ["binary", "pool"]} dbca-utils = "2.0.2" python-dotenv = "1.0.1" dj-database-url = "2.2.0" gunicorn = "23.0.0" django-crispy-forms = "2.3" django-reversion = "5.1.0" -django-taggit = "6.0.0" +django-taggit = "6.1.0" unidecode = "1.3.8" pillow = "10.4.0" python-magic = "0.4.27" @@ -37,7 +37,7 @@ whitenoise = { version = "6.7.0", extras = ["brotli"] } django-crum = "0.7.9" sentry-sdk = { version = "2.14.0", extras = ["django"] } crispy-bootstrap5 = "2024.2" -redis = "5.0.8" +redis = "5.1.0" xlsxwriter = "3.2.0" django-storages = { version = "1.14.4", extras = ["azure"] }
{}