diff --git a/.circleci/config.yml b/.circleci/config.yml
index 90e62ff4..706e8e41 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -24,7 +24,10 @@ jobs:
name: Set up virtual environment
command: |
pipenv sync --dev
- - run: pipenv run playwright install --with-deps chromium
+
+ - run: pipenv run ruff check .
+ - run: ./scripts/lint.sh
+ - run: ./scripts/format.sh
- run: make lambda-layers/DependenciesLayer/requirements.txt
- run: npm install
- run: npm run build
diff --git a/Pipfile b/Pipfile
index 2627f521..b6f14d8b 100644
--- a/Pipfile
+++ b/Pipfile
@@ -21,7 +21,6 @@ djangorestframework = ">=3.9.1"
durationpy = "==0.4"
enum34 = "==1.1.6"
fixtures = "==3.0.0"
-flake8 = "==3.8.3"
funcsigs = "==1.0.2"
dj-pagination = "==2.4.0"
linecache2 = "==1.0.0"
@@ -64,7 +63,6 @@ django-static-jquery = "==2.1.4"
setuptools = "*"
faker = "*"
werkzeug = "*"
-black = "==19.10b0"
django-s3file = "*"
psycopg2-binary = "*"
dc_design_system = {version = "0.5.0", git = "https://github.com/DemocracyClub/design-system.git" }
@@ -85,6 +83,7 @@ pytest = "*"
pytest-playwright = "*"
pytest-cov = "*"
moto = "*"
+ruff = "*"
[requires]
python_version = "3.12"
diff --git a/Pipfile.lock b/Pipfile.lock
index 97dfb79a..9376c59b 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "8b44b80f3d9ce26cbcc432466075e098e5e6fc1dadb997923c810ddf5078862a"
+ "sha256": "4853d724475f2f6adb46510bc4dc7dbede2c79eb783762b621447dc3b9ffb88a"
},
"pipfile-spec": 6,
"requires": {
@@ -36,13 +36,6 @@
],
"version": "==1.1.8"
},
- "appdirs": {
- "hashes": [
- "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
- "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
- ],
- "version": "==1.4.4"
- },
"argparse": {
"hashes": [
"sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4",
@@ -58,27 +51,10 @@
"markers": "python_version >= '3.8'",
"version": "==3.8.1"
},
- "attrs": {
- "hashes": [
- "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346",
- "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==24.2.0"
- },
"aws-wsgi": {
"git": "https://github.com/DemocracyClub/awsgi.git",
"ref": "a5b800373c326e75f705d400c48f7df301409724"
},
- "black": {
- "hashes": [
- "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
- "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
- ],
- "index": "pypi",
- "markers": "python_version >= '3.6'",
- "version": "==19.10b0"
- },
"blinker": {
"hashes": [
"sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf",
@@ -89,20 +65,20 @@
},
"boto3": {
"hashes": [
- "sha256:091d6bed1422370987a839bff3f8755df7404fc15e9fac2a48e8505356f07433",
- "sha256:9b26fa31901da7793c1dcd65eee9bab7e897d8aa1ffed0b5e1c3bce93d2aefe4"
+ "sha256:3ed7172b3d4fceb6218bb0ec3668c4d40c03690939c2fca4f22bb875d741a07f",
+ "sha256:e2969a246bb3208122b3c349c49cc6604c6fc3fc2b2f65d99d3e8ccd745b0c16"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
- "version": "==1.35.68"
+ "version": "==1.35.71"
},
"botocore": {
"hashes": [
- "sha256:42c3700583a82f2b5316281a073d644a521d6358837e2b446dc458ba5d990fb4",
- "sha256:599139d5564291f5be873800711f9e4e14a823395ae9ce7b142be775e9849b94"
+ "sha256:f9fa058e0393660c3fe53c1e044751beb64b586def0bd2212448a7c328b0cbba",
+ "sha256:fc46e7ab1df3cef66dfba1633f4da77c75e07365b36f03bd64a3793634be8fc1"
],
"markers": "python_version >= '3.8'",
- "version": "==1.35.68"
+ "version": "==1.35.71"
},
"certifi": {
"hashes": [
@@ -329,105 +305,111 @@
"index": "pypi",
"version": "==0.5.5"
},
- "couleur": {
- "hashes": [
- "sha256:1e96a5972ecd5f88716fe4e4df63d31e85f57d3ff6c02a07e93a226a663961ba"
- ],
- "version": "==0.7.4"
- },
"coverage": {
"extras": [
"toml"
],
"hashes": [
- "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca",
- "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471",
- "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a",
- "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058",
- "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85",
- "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143",
- "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446",
- "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590",
- "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a",
- "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105",
- "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9",
- "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a",
- "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac",
- "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25",
- "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2",
- "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450",
- "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932",
- "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba",
- "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137",
- "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae",
- "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614",
- "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70",
- "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e",
- "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505",
- "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870",
- "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc",
- "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451",
- "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7",
- "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e",
- "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566",
- "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5",
- "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26",
- "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2",
- "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42",
- "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555",
- "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43",
- "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed",
- "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa",
- "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516",
- "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952",
- "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd",
- "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09",
- "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c",
- "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f",
- "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6",
- "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1",
- "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0",
- "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e",
- "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9",
- "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9",
- "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e",
- "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"
+ "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5",
+ "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf",
+ "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb",
+ "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638",
+ "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4",
+ "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc",
+ "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed",
+ "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a",
+ "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d",
+ "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649",
+ "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c",
+ "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b",
+ "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4",
+ "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443",
+ "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83",
+ "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee",
+ "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e",
+ "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e",
+ "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3",
+ "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0",
+ "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb",
+ "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076",
+ "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb",
+ "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787",
+ "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1",
+ "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e",
+ "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce",
+ "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801",
+ "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764",
+ "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365",
+ "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf",
+ "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6",
+ "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71",
+ "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002",
+ "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4",
+ "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c",
+ "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8",
+ "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4",
+ "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146",
+ "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc",
+ "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea",
+ "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4",
+ "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad",
+ "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28",
+ "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451",
+ "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50",
+ "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779",
+ "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63",
+ "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e",
+ "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc",
+ "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022",
+ "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d",
+ "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94",
+ "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b",
+ "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d",
+ "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331",
+ "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a",
+ "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0",
+ "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee",
+ "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92",
+ "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a",
+ "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"
],
- "markers": "python_version >= '3.8'",
- "version": "==7.4.0"
+ "markers": "python_version >= '3.9'",
+ "version": "==7.6.8"
},
"cryptography": {
"hashes": [
- "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362",
- "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4",
- "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa",
- "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83",
- "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff",
- "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805",
- "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6",
- "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664",
- "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08",
- "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e",
- "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18",
- "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f",
- "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73",
- "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5",
- "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984",
- "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd",
- "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3",
- "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e",
- "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405",
- "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2",
- "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c",
- "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995",
- "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73",
- "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16",
- "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7",
- "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd",
- "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==43.0.3"
+ "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7",
+ "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731",
+ "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b",
+ "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc",
+ "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543",
+ "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385",
+ "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c",
+ "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591",
+ "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede",
+ "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb",
+ "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f",
+ "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123",
+ "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c",
+ "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba",
+ "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c",
+ "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285",
+ "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd",
+ "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092",
+ "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa",
+ "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289",
+ "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02",
+ "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64",
+ "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053",
+ "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417",
+ "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e",
+ "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e",
+ "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7",
+ "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756",
+ "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"
+ ],
+ "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
+ "version": "==44.0.0"
},
"cssselect": {
"hashes": [
@@ -537,12 +519,12 @@
},
"django-ses": {
"hashes": [
- "sha256:2344f2650b699417c0f2b6197800580f5022aaccf0083c2a1b640b7ff7366067",
- "sha256:6167401bd3fc2b29a33b35e4f6f124dc738f6612e3a926ff5533143e75c71ff1"
+ "sha256:3f2374368251c3d614d6409d7f477747ebba705df38823eb3bf3b578c916f9c6",
+ "sha256:91ed468ae1d64bbbebb24661c9e324f49e52de4d228744d1d5ace203ffd4e4e6"
],
"index": "pypi",
"markers": "python_version >= '3.8' and python_version < '4.0'",
- "version": "==4.2.0"
+ "version": "==4.3.0"
},
"django-static-jquery": {
"hashes": [
@@ -617,12 +599,12 @@
},
"faker": {
"hashes": [
- "sha256:68e5580cb6b4226710886e595eabc13127149d6e71e9d1db65506a7fbe2c7fce",
- "sha256:9b01019c1ddaf2253ca2308c0472116e993f4ad8fc9905f82fa965e0c6f932e9"
+ "sha256:1c925fc0e86a51fc46648b504078c88d0cd48da1da2595c4e712841cab43a1e4",
+ "sha256:d30c5f0e2796b8970de68978365247657486eb0311c5abe88d0b895b68dff05d"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
- "version": "==33.0.0"
+ "version": "==33.1.0"
},
"fixtures": {
"hashes": [
@@ -632,15 +614,6 @@
"index": "pypi",
"version": "==3.0.0"
},
- "flake8": {
- "hashes": [
- "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c",
- "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"
- ],
- "index": "pypi",
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==3.8.3"
- },
"flask": {
"hashes": [
"sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0",
@@ -1181,14 +1154,6 @@
"markers": "python_version >= '3.8'",
"version": "==24.2"
},
- "pathspec": {
- "hashes": [
- "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08",
- "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"
- ],
- "markers": "python_version >= '3.8'",
- "version": "==0.12.1"
- },
"pbr": {
"hashes": [
"sha256:568f988af109114fbfa0525dcb6836b069838360d11732736ecc82e4c15d5c12",
@@ -1395,14 +1360,6 @@
"markers": "python_version >= '3.8'",
"version": "==2.9.10"
},
- "pycodestyle": {
- "hashes": [
- "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
- "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.6.0"
- },
"pycparser": {
"hashes": [
"sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
@@ -1419,14 +1376,6 @@
"markers": "python_version >= '3.8'",
"version": "==12.0.0"
},
- "pyflakes": {
- "hashes": [
- "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
- "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.2.0"
- },
"pyparsing": {
"hashes": [
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
@@ -1466,12 +1415,12 @@
},
"pytest-cov": {
"hashes": [
- "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652",
- "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"
+ "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35",
+ "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"
],
"index": "pypi",
- "markers": "python_version >= '3.8'",
- "version": "==5.0.0"
+ "markers": "python_version >= '3.9'",
+ "version": "==6.0.0"
},
"pytest-django": {
"hashes": [
@@ -1497,7 +1446,7 @@
"sha256:cbf2f1da5e6083ac2fbfd4da39a25f34312230110440f424a14c7558bb85d82e"
],
"index": "pypi",
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.9.0"
},
"python-levenshtein": {
@@ -1717,106 +1666,6 @@
"markers": "python_version >= '3.9'",
"version": "==3.10.1"
},
- "regex": {
- "hashes": [
- "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c",
- "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60",
- "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d",
- "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d",
- "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67",
- "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773",
- "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0",
- "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef",
- "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad",
- "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe",
- "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3",
- "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114",
- "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4",
- "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39",
- "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e",
- "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3",
- "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7",
- "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d",
- "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e",
- "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a",
- "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7",
- "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f",
- "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0",
- "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54",
- "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b",
- "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c",
- "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd",
- "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57",
- "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34",
- "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d",
- "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f",
- "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b",
- "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519",
- "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4",
- "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a",
- "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638",
- "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b",
- "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839",
- "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07",
- "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf",
- "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff",
- "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0",
- "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f",
- "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95",
- "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4",
- "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e",
- "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13",
- "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519",
- "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2",
- "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008",
- "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9",
- "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc",
- "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48",
- "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20",
- "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89",
- "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e",
- "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf",
- "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b",
- "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd",
- "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84",
- "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29",
- "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b",
- "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3",
- "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45",
- "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3",
- "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983",
- "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e",
- "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7",
- "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4",
- "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e",
- "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467",
- "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577",
- "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001",
- "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0",
- "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55",
- "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9",
- "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf",
- "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6",
- "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e",
- "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde",
- "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62",
- "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df",
- "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51",
- "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5",
- "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86",
- "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2",
- "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2",
- "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0",
- "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c",
- "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f",
- "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6",
- "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2",
- "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9",
- "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"
- ],
- "markers": "python_version >= '3.8'",
- "version": "==2024.11.6"
- },
"requests": {
"hashes": [
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
@@ -1842,6 +1691,31 @@
"markers": "python_version >= '3.8'",
"version": "==0.25.3"
},
+ "ruff": {
+ "hashes": [
+ "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871",
+ "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1",
+ "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d",
+ "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6",
+ "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5",
+ "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f",
+ "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1",
+ "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737",
+ "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790",
+ "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9",
+ "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540",
+ "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26",
+ "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c",
+ "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa",
+ "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087",
+ "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209",
+ "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5",
+ "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.7'",
+ "version": "==0.8.1"
+ },
"s3transfer": {
"hashes": [
"sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e",
@@ -1874,7 +1748,7 @@
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"index": "pypi",
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sorl-thumbnail": {
@@ -1896,10 +1770,10 @@
},
"sure": {
"hashes": [
- "sha256:140bc01837255a8c97a05967d868b38af62dcbca94f400ceba604df4f955cf85"
+ "sha256:c8fc6fabc0e7f6984eeabb942540e45646e5bef0bb99fe59e02da634e4d4b9ca"
],
"index": "pypi",
- "version": "==3.0a2"
+ "version": "==2.0.1"
},
"testtools": {
"hashes": [
@@ -1909,14 +1783,6 @@
"index": "pypi",
"version": "==2.3.0"
},
- "toml": {
- "hashes": [
- "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
- "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
- ],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
- "version": "==0.10.2"
- },
"tqdm": {
"hashes": [
"sha256:14baa7a9ea7723d46f60de5f8c6f20e840baa7e3e193bf0d9ec5fe9103a15254",
@@ -1933,53 +1799,6 @@
"index": "pypi",
"version": "==1.4.0"
},
- "typed-ast": {
- "hashes": [
- "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10",
- "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede",
- "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e",
- "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c",
- "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d",
- "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8",
- "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e",
- "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5",
- "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155",
- "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4",
- "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba",
- "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5",
- "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a",
- "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b",
- "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311",
- "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769",
- "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686",
- "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d",
- "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2",
- "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814",
- "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9",
- "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b",
- "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b",
- "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4",
- "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd",
- "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18",
- "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa",
- "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6",
- "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee",
- "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88",
- "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4",
- "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431",
- "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04",
- "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d",
- "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02",
- "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8",
- "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437",
- "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274",
- "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f",
- "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a",
- "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==1.5.5"
- },
"typing-extensions": {
"hashes": [
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
diff --git a/README.md b/README.md
index f77eaf6e..1da1b413 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,6 @@
[![Coverage Status](https://coveralls.io/repos/DemocracyClub/electionleaflets/badge.svg?branch=master)](https://coveralls.io/r/DemocracyClub/electionleaflets?branch=django_1_7)
[![CI](https://circleci.com/gh/DemocracyClub/electionleaflets.svg?style=shield)](https://app.circleci.com/pipelines/github/DemocracyClub/electionleaflets)
-[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
### Welcome
@@ -70,4 +69,4 @@ The app is deployed to AWS using the [Serverless
Framework](https://serverless.com/). This manages resources in AWS Lambda and
AWS API Gateway.
-The app is deployed through CircleCI. The staging deployment is triggered by temporarily adding the current branch to context in the sam-deploy job. The production deployment is triggered by merging to master.
\ No newline at end of file
+The app is deployed through CircleCI. The staging deployment is triggered by temporarily adding the current branch to context in the sam-deploy job. The production deployment is triggered by merging to master.
diff --git a/electionleaflets/apps/analysis/migrations/0001_initial.py b/electionleaflets/apps/analysis/migrations/0001_initial.py
index 0535de73..8a3d458b 100644
--- a/electionleaflets/apps/analysis/migrations/0001_initial.py
+++ b/electionleaflets/apps/analysis/migrations/0001_initial.py
@@ -2,13 +2,12 @@
# Generated by Django 1.9.5 on 2016-04-03 14:56
-from django.db import migrations, models
import django.utils.timezone
import django_extensions.db.fields
+from django.db import migrations, models
class Migration(migrations.Migration):
-
initial = True
dependencies = []
diff --git a/electionleaflets/apps/analysis/migrations/0002_auto_20160403_1456.py b/electionleaflets/apps/analysis/migrations/0002_auto_20160403_1456.py
index 872673e5..f09431b3 100644
--- a/electionleaflets/apps/analysis/migrations/0002_auto_20160403_1456.py
+++ b/electionleaflets/apps/analysis/migrations/0002_auto_20160403_1456.py
@@ -2,13 +2,12 @@
# Generated by Django 1.9.5 on 2016-04-03 14:56
+import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
-import django.db.models.deletion
class Migration(migrations.Migration):
-
initial = True
dependencies = [
diff --git a/electionleaflets/apps/analysis/migrations/0003_add_missing_people.py b/electionleaflets/apps/analysis/migrations/0003_add_missing_people.py
index 5b97b1f5..7b5ff4a6 100644
--- a/electionleaflets/apps/analysis/migrations/0003_add_missing_people.py
+++ b/electionleaflets/apps/analysis/migrations/0003_add_missing_people.py
@@ -2,7 +2,6 @@
# Generated by Django 1.11.23 on 2019-12-02 23:06
import requests
-
from django.db import migrations
@@ -18,7 +17,6 @@ def add_missing_people(apps, schema_editor):
remote_id=leaflet.publisher_person_id
)
except Person.DoesNotExist:
-
url = "https://candidates.democracyclub.org.uk/api/next/people/{}/".format(
leaflet.publisher_person_id
)
@@ -32,7 +30,6 @@ def add_missing_people(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
("analysis", "0002_auto_20160403_1456"),
("people", "0001_initial"),
diff --git a/electionleaflets/apps/analysis/migrations/0004_auto_20210424_1221.py b/electionleaflets/apps/analysis/migrations/0004_auto_20210424_1221.py
index a4fad777..f5f06792 100644
--- a/electionleaflets/apps/analysis/migrations/0004_auto_20210424_1221.py
+++ b/electionleaflets/apps/analysis/migrations/0004_auto_20210424_1221.py
@@ -1,11 +1,10 @@
# Generated by Django 2.2.20 on 2021-04-24 12:21
-from django.db import migrations
import django_extensions.db.fields
+from django.db import migrations
class Migration(migrations.Migration):
-
dependencies = [
("analysis", "0003_add_missing_people"),
]
diff --git a/electionleaflets/apps/analysis/migrations/0005_alter_leafletproperties_options.py b/electionleaflets/apps/analysis/migrations/0005_alter_leafletproperties_options.py
index fe337e96..def349c9 100644
--- a/electionleaflets/apps/analysis/migrations/0005_alter_leafletproperties_options.py
+++ b/electionleaflets/apps/analysis/migrations/0005_alter_leafletproperties_options.py
@@ -4,14 +4,13 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('analysis', '0004_auto_20210424_1221'),
+ ("analysis", "0004_auto_20210424_1221"),
]
operations = [
migrations.AlterModelOptions(
- name='leafletproperties',
- options={'get_latest_by': 'modified'},
+ name="leafletproperties",
+ options={"get_latest_by": "modified"},
),
]
diff --git a/electionleaflets/apps/analysis/models.py b/electionleaflets/apps/analysis/models.py
index 86ba3e18..0dde6a43 100644
--- a/electionleaflets/apps/analysis/models.py
+++ b/electionleaflets/apps/analysis/models.py
@@ -1,8 +1,6 @@
+from django.contrib.auth.models import User
from django.db import models
from django_extensions.db.models import TimeStampedModel
-
-from django.contrib.auth.models import User
-
from leaflets.models import Leaflet
diff --git a/electionleaflets/apps/api/feeds.py b/electionleaflets/apps/api/feeds.py
index 1ff5b49d..f56afec9 100644
--- a/electionleaflets/apps/api/feeds.py
+++ b/electionleaflets/apps/api/feeds.py
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
-import mimetypes
+# from parties.models import Party
+from constituencies.models import Constituency
from django.contrib.syndication.views import Feed
from django.shortcuts import get_object_or_404
from leaflets.models import Leaflet
-# from parties.models import Party
-from constituencies.models import Constituency
-
class LatestLeafletsFeed(Feed):
title = "electionleaflets.org latest items"
@@ -28,6 +26,7 @@ def item_description(self, item):
d = "{0} – {1}".format(d, item.images.all()[0].image.url)
return d
+
class ConstituencyFeed(Feed):
def get_object(self, request, cons_slug):
obj = get_object_or_404(Constituency, slug=cons_slug)
diff --git a/electionleaflets/apps/api/serializers.py b/electionleaflets/apps/api/serializers.py
index f2907d88..83517a0e 100644
--- a/electionleaflets/apps/api/serializers.py
+++ b/electionleaflets/apps/api/serializers.py
@@ -1,10 +1,8 @@
+from leaflets.models import Leaflet, LeafletImage
+from people.models import Person
from rest_framework import serializers
from sorl.thumbnail import get_thumbnail
-
-from leaflets.models import Leaflet, LeafletImage
-from constituencies.models import Constituency
from uk_political_parties.models import Party
-from people.models import Person
class PartySerializer(serializers.HyperlinkedModelSerializer):
@@ -58,8 +56,9 @@ def get_people(self, obj):
def get_first_page_thumb(self, obj):
image = obj.get_first_image()
- if image:
- return get_thumbnail(obj.get_first_image().image, "350").url
+ if not image:
+ return None
+ return get_thumbnail(obj.get_first_image().image, "350").url
def validate(self, data):
if not data.get("status") or not data.get("images"):
@@ -89,8 +88,9 @@ class LeafletMinSerializer(serializers.ModelSerializer):
def get_first_page_thumb(self, obj):
image = obj.get_first_image()
- if image:
- return get_thumbnail(obj.get_first_image().image, "350").url
+ if not image:
+ return None
+ return get_thumbnail(obj.get_first_image().image, "350").url
class Meta:
model = Leaflet
diff --git a/electionleaflets/apps/api/tests/test_feeds.py b/electionleaflets/apps/api/tests/test_feeds.py
index 2a192ae9..d11837b2 100644
--- a/electionleaflets/apps/api/tests/test_feeds.py
+++ b/electionleaflets/apps/api/tests/test_feeds.py
@@ -1,19 +1,20 @@
import pytest
-from django.contrib.auth.models import User
-
from api.feeds import ConstituencyFeed, LatestLeafletsFeed
from constituencies.models import Constituency
from leaflets.tests.model_factory import LeafletFactory
+
@pytest.fixture(autouse=True)
def create_leaflets():
for _ in range(10):
LeafletFactory()
+
@pytest.fixture
def create_constituency():
return Constituency.objects.create(name="test_name", slug="test_slug")
+
@pytest.mark.django_db
class TestLatestLeafletsFeed:
def test_items_order(self, create_leaflets):
@@ -33,8 +34,13 @@ def test_item_description(self, create_leaflets):
if item.description:
description = item.description
if item.images.all():
- description = "{0} – {1}".format(description, item.images.all()[0].image.url)
- assert LatestLeafletsFeed.item_description(self, item) == description
+ description = "{0} – {1}".format(
+ description, item.images.all()[0].image.url
+ )
+ assert (
+ LatestLeafletsFeed.item_description(self, item) == description
+ )
+
@pytest.mark.django_db
class TestConstituencyFeed:
@@ -43,7 +49,10 @@ def test_get_object(self, create_constituency):
obj = feed.get_object(None, create_constituency.slug)
assert obj.slug == create_constituency.slug
assert feed.link == "/constituencies/%s/" % obj.slug
- assert feed.description == "The most recently uploaded leaflets for %s" % obj.name
+ assert (
+ feed.description
+ == "The most recently uploaded leaflets for %s" % obj.name
+ )
def test_items(self, create_leaflets, create_constituency):
LeafletFactory(constituency=create_constituency)
diff --git a/electionleaflets/apps/api/tests/test_views.py b/electionleaflets/apps/api/tests/test_views.py
index 24510bd0..a5c1fd46 100644
--- a/electionleaflets/apps/api/tests/test_views.py
+++ b/electionleaflets/apps/api/tests/test_views.py
@@ -1,28 +1,29 @@
import pytest
-from electionleaflets.apps.api.views import LeafletFilter
-from leaflets.models import Leaflet
from django.utils import timezone
+from leaflets.models import Leaflet
from uk_political_parties.models import Party
+from electionleaflets.apps.api.views import LeafletFilter
+
+
@pytest.fixture
def all_leaflets():
- return [
- Leaflet.objects.create(
- title=f"Test Leaflet {i}",
- status="live",
- date_uploaded=timezone.now(),
- modified=timezone.now()
- )
- for i in range(10)
- ]
-
+ return [
+ Leaflet.objects.create(
+ title=f"Test Leaflet {i}",
+ status="live",
+ date_uploaded=timezone.now(),
+ modified=timezone.now(),
+ )
+ for i in range(10)
+ ]
+
+
@pytest.fixture
def party():
- return Party.objects.create(
- party_name="Test Party",
- party_id="party:1"
- )
-
+ return Party.objects.create(party_name="Test Party", party_id="party:1")
+
+
@pytest.fixture
def leaflet_with_party(all_leaflets, party):
leaflet = all_leaflets[0]
@@ -30,17 +31,31 @@ def leaflet_with_party(all_leaflets, party):
leaflet.save()
return leaflet
+
@pytest.mark.django_db
class TestLeafletFilter:
def test_ballot_filter(self, all_leaflets):
leaflet = all_leaflets[0]
leaflet.ballots = [{"ballot_paper_id": "test"}]
leaflet.save()
- filter = LeafletFilter(data={"ballot": "test"}, queryset=Leaflet.objects.all())
- assert filter.ballot_filter(Leaflet.objects.all(), "ballot", "test").count() == 1
-
+ filter = LeafletFilter(
+ data={"ballot": "test"}, queryset=Leaflet.objects.all()
+ )
+ assert (
+ filter.ballot_filter(
+ Leaflet.objects.all(), "ballot", "test"
+ ).count()
+ == 1
+ )
+
def test_party_filter(self, leaflet_with_party):
party = Party.objects.get(party_id="party:1")
- filter = LeafletFilter(data={"party": party}, queryset=Leaflet.objects.all())
- assert filter.party_filter(Leaflet.objects.all(), party, party.party_id).count() == 1
-
\ No newline at end of file
+ filter = LeafletFilter(
+ data={"party": party}, queryset=Leaflet.objects.all()
+ )
+ assert (
+ filter.party_filter(
+ Leaflet.objects.all(), party, party.party_id
+ ).count()
+ == 1
+ )
diff --git a/electionleaflets/apps/api/views.py b/electionleaflets/apps/api/views.py
index 9bce524d..7bf52085 100644
--- a/electionleaflets/apps/api/views.py
+++ b/electionleaflets/apps/api/views.py
@@ -2,10 +2,10 @@
from django.db.models import Q
from django_filters import rest_framework as filters
+from leaflets.models import Leaflet
from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination
-from leaflets.models import Leaflet
from .serializers import LeafletSerializer
@@ -17,10 +17,7 @@ class StandardResultsSetPagination(LimitOffsetPagination):
class LeafletFilter(filters.FilterSet):
class Meta:
model = Leaflet
- fields = {
- "date_uploaded": ["gt", "exact"],
- "modified": ["gt", "exact"]
- }
+ fields = {"date_uploaded": ["gt", "exact"], "modified": ["gt", "exact"]}
def ballot_filter(self, queryset, name, value):
return queryset.filter(ballots__contains=[{"ballot_paper_id": value}])
@@ -43,7 +40,9 @@ def party_filter(self, queryset, name, value):
class LeafletViewSet(viewsets.ReadOnlyModelViewSet):
- queryset = Leaflet.objects.all().filter(status="live").prefetch_related("images")
+ queryset = (
+ Leaflet.objects.all().filter(status="live").prefetch_related("images")
+ )
serializer_class = LeafletSerializer
pagination_class = StandardResultsSetPagination
filter_backends = (filters.DjangoFilterBackend,)
diff --git a/electionleaflets/apps/constituencies/migrations/0001_initial.py b/electionleaflets/apps/constituencies/migrations/0001_initial.py
index 2d305a05..f0bac786 100644
--- a/electionleaflets/apps/constituencies/migrations/0001_initial.py
+++ b/electionleaflets/apps/constituencies/migrations/0001_initial.py
@@ -2,12 +2,11 @@
# Generated by Django 1.9.5 on 2016-04-03 14:56
-from django.db import migrations, models
import django_extensions.db.fields
+from django.db import migrations, models
class Migration(migrations.Migration):
-
initial = True
dependencies = []
@@ -51,6 +50,8 @@ class Migration(migrations.Migration):
models.IntegerField(blank=True, null=True),
),
],
- options={"verbose_name_plural": "Constituencies",},
+ options={
+ "verbose_name_plural": "Constituencies",
+ },
),
]
diff --git a/electionleaflets/apps/constituencies/models.py b/electionleaflets/apps/constituencies/models.py
index 539740a0..e6d124c9 100644
--- a/electionleaflets/apps/constituencies/models.py
+++ b/electionleaflets/apps/constituencies/models.py
@@ -27,5 +27,10 @@ def __unicode__(self):
def get_absolute_url(self):
return reverse(
- "constituency-view", (), {"pk": self.pk, "ignored_slug": self.slug,}
+ "constituency-view",
+ (),
+ {
+ "pk": self.pk,
+ "ignored_slug": self.slug,
+ },
)
diff --git a/electionleaflets/apps/core/admin.py b/electionleaflets/apps/core/admin.py
index 3a8c4a09..c46ce044 100644
--- a/electionleaflets/apps/core/admin.py
+++ b/electionleaflets/apps/core/admin.py
@@ -1,5 +1,5 @@
-from django.contrib import admin
from core.models import Country, EmailAlert
+from django.contrib import admin
class CountryOptions(admin.ModelAdmin):
diff --git a/electionleaflets/apps/core/helpers.py b/electionleaflets/apps/core/helpers.py
index 2bdacaf6..0da2dc38 100644
--- a/electionleaflets/apps/core/helpers.py
+++ b/electionleaflets/apps/core/helpers.py
@@ -1,11 +1,9 @@
import requests
-
+from constituencies.models import Constituency
from django.conf import settings
from django.core.cache import cache
from django.utils.cache import patch_response_headers
-from constituencies.models import Constituency
-
def geocode(postcode):
"""
@@ -24,12 +22,11 @@ def geocode(postcode):
res_json = res.json()
if "code" in res_json and res_json["code"] == 404:
return None
- else:
- constituency = Constituency.objects.get(
- constituency_id=str(res_json["shortcuts"]["WMC"])
- )
- lat = res_json["wgs84_lat"]
- lon = res_json["wgs84_lon"]
+ constituency = Constituency.objects.get(
+ constituency_id=str(res_json["shortcuts"]["WMC"])
+ )
+ lat = res_json["wgs84_lat"]
+ lon = res_json["wgs84_lon"]
result = {
"wgs84_lon": lon,
diff --git a/electionleaflets/apps/core/migrations/0001_initial.py b/electionleaflets/apps/core/migrations/0001_initial.py
index 56c1cbb2..4192c946 100644
--- a/electionleaflets/apps/core/migrations/0001_initial.py
+++ b/electionleaflets/apps/core/migrations/0001_initial.py
@@ -6,7 +6,6 @@
class Migration(migrations.Migration):
-
initial = True
dependencies = []
@@ -61,7 +60,9 @@ class Migration(migrations.Migration):
("postcode", models.CharField(max_length=60)),
("delivery_date", models.DateTimeField()),
],
- options={"db_table": "email_que",},
+ options={
+ "db_table": "email_que",
+ },
),
migrations.CreateModel(
name="ImageQue",
@@ -76,7 +77,9 @@ class Migration(migrations.Migration):
("image_key", models.CharField(blank=True, max_length=765)),
("uploaded_date", models.DateTimeField()),
],
- options={"db_table": "image_que",},
+ options={
+ "db_table": "image_que",
+ },
),
migrations.CreateModel(
name="ImageQueSeq",
@@ -86,6 +89,8 @@ class Migration(migrations.Migration):
models.IntegerField(primary_key=True, serialize=False),
),
],
- options={"db_table": "image_que_seq",},
+ options={
+ "db_table": "image_que_seq",
+ },
),
]
diff --git a/electionleaflets/apps/core/s3_thumbnail_store.py b/electionleaflets/apps/core/s3_thumbnail_store.py
index efee6530..d9da8294 100644
--- a/electionleaflets/apps/core/s3_thumbnail_store.py
+++ b/electionleaflets/apps/core/s3_thumbnail_store.py
@@ -1,6 +1,6 @@
+from sorl.thumbnail import default
from sorl.thumbnail.base import ThumbnailBackend
from sorl.thumbnail.images import ImageFile
-from sorl.thumbnail import default
class S3Backend(ThumbnailBackend):
@@ -20,8 +20,7 @@ def get_thumbnail(self, file_, geometry_string, **options):
options.setdefault(key, value)
name = self._get_thumbnail_filename(source, geometry_string, options)
- thumbnail = ImageFile(name, default.storage)
- return thumbnail
+ return ImageFile(name, default.storage)
def _get_thumbnail_filename(self, source, geometry_string, options):
"""
@@ -42,10 +41,11 @@ def _get_thumbnail_filename(self, source, geometry_string, options):
["{}={}".format(k, v) for k, v in list(opts.items())]
)
- thumb_url = "{base_url}/{geometry_string}/{url_kwargs}/{original_path}".format(
- base_url=base_url,
- geometry_string=geometry_string,
- url_kwargs=url_kwargs,
- original_path=source.name,
+ return (
+ "{base_url}/{geometry_string}/{url_kwargs}/{original_path}".format(
+ base_url=base_url,
+ geometry_string=geometry_string,
+ url_kwargs=url_kwargs,
+ original_path=source.name,
+ )
)
- return thumb_url
diff --git a/electionleaflets/apps/core/storage_helpers.py b/electionleaflets/apps/core/storage_helpers.py
index 367ac625..50680ec5 100644
--- a/electionleaflets/apps/core/storage_helpers.py
+++ b/electionleaflets/apps/core/storage_helpers.py
@@ -1,9 +1,4 @@
-from django.core.files.base import ContentFile
-from django.core.files.uploadedfile import UploadedFile
-from django.utils.datastructures import MultiValueDict
-from formtools.wizard.storage import NoFileStorageConfigured
from formtools.wizard.storage.session import SessionStorage
-from storages.backends.s3boto3 import S3Boto3StorageFile
class PreUploadedSessionStorage(SessionStorage):
diff --git a/electionleaflets/apps/core/templatetags/markdown.py b/electionleaflets/apps/core/templatetags/markdown.py
index 332d9fdd..a41e5a69 100644
--- a/electionleaflets/apps/core/templatetags/markdown.py
+++ b/electionleaflets/apps/core/templatetags/markdown.py
@@ -10,4 +10,4 @@ def markdown_filter(text):
return mark_safe(markdown.markdown(text))
-markdown.is_safe = True
\ No newline at end of file
+markdown.is_safe = True
diff --git a/electionleaflets/apps/core/test_forms.py b/electionleaflets/apps/core/test_forms.py
index 7f964aee..72e26cb0 100644
--- a/electionleaflets/apps/core/test_forms.py
+++ b/electionleaflets/apps/core/test_forms.py
@@ -5,27 +5,34 @@
@pytest.mark.django_db
def test_form_valid_data():
- form = ReportAbuseForm(data={
- 'name': 'Sam',
- 'details': 'This is not a spam leaflet.',
- 'email': 'user@example.com',
- })
+ form = ReportAbuseForm(
+ data={
+ "name": "Sam",
+ "details": "This is not a spam leaflet.",
+ "email": "user@example.com",
+ }
+ )
assert form.is_valid()
+
@pytest.mark.django_db
def test_form_invalid_data():
- form = ReportAbuseForm(data={
- 'name': '',
- 'details': 'This is a spam leaflet.',
- 'email': 'user@example.com',
- })
+ form = ReportAbuseForm(
+ data={
+ "name": "",
+ "details": "This is a spam leaflet.",
+ "email": "user@example.com",
+ }
+ )
assert not form.is_valid()
+
@pytest.mark.django_db
def test_form_missing_data():
- form = ReportAbuseForm(data={
- 'name': 'Sam',
- 'details': 'This is a spam leaflet.',
- })
+ form = ReportAbuseForm(
+ data={
+ "name": "Sam",
+ "details": "This is a spam leaflet.",
+ }
+ )
assert not form.is_valid()
-
diff --git a/electionleaflets/apps/core/test_models.py b/electionleaflets/apps/core/test_models.py
index 8e9a6635..3e4ad07d 100644
--- a/electionleaflets/apps/core/test_models.py
+++ b/electionleaflets/apps/core/test_models.py
@@ -37,7 +37,7 @@ class Meta:
name = factory.Sequence(lambda n: "Name %d" % n)
email = factory.Sequence(lambda n: "user%d@example.com" % n)
postcode = factory.Sequence(lambda n: "Postcode %d" % n)
- delivery_date = factory.Faker('date_time')
+ delivery_date = factory.Faker("date_time")
class ImageQueFactory(DjangoModelFactory):
@@ -49,7 +49,7 @@ class Meta:
name = factory.Sequence(lambda n: "Name %d" % n)
email = factory.Sequence(lambda n: "user%d@example.com" % n)
image_key = factory.Sequence(lambda n: "ImageKey%d" % n)
- uploaded_date = factory.Faker('date_time')
+ uploaded_date = factory.Faker("date_time")
class ImageQueSeqFactory(DjangoModelFactory):
@@ -58,6 +58,7 @@ class Meta:
sequence = factory.Sequence(lambda n: n)
+
@pytest.mark.django_db
def test_country_creation():
country = CountryFactory()
diff --git a/electionleaflets/apps/core/views.py b/electionleaflets/apps/core/views.py
index c4760e0a..8744c8a4 100644
--- a/electionleaflets/apps/core/views.py
+++ b/electionleaflets/apps/core/views.py
@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
-import datetime
-from django.http import HttpResponseRedirect
-from django.urls import reverse
-from django.views.decorators.cache import cache_control
-from django.views.generic import FormView, TemplateView, DetailView
+from constituencies.models import Constituency
from django.conf import settings
+from django.contrib.sites.models import Site
from django.core.mail import EmailMultiAlternatives
+from django.http import HttpResponseRedirect
from django.template.loader import render_to_string
-from django.contrib.sites.models import Site
-
-from constituencies.models import Constituency
+from django.urls import reverse
+from django.views.generic import DetailView, FormView, TemplateView
from leaflets.models import Leaflet, LeafletImage
from people.models import Person
from uk_political_parties.models import Party
@@ -24,7 +21,6 @@ class HomeView(CacheControlMixin, TemplateView):
cache_timeout = 60 * 5
def get_context_data(self, **kwargs):
-
leaflet_count = Leaflet.objects.all().count()
context = super(HomeView, self).get_context_data(**kwargs)
@@ -55,7 +51,10 @@ def get(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
self.object = self.get_object()
- context = self.get_context_data(form=form, object=self.object,)
+ context = self.get_context_data(
+ form=form,
+ object=self.object,
+ )
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
@@ -66,7 +65,10 @@ def form_valid(self, form):
domain = Site.objects.get_current().domain
ctx = {
"link": "http://%s%s"
- % (domain, reverse("leaflet", kwargs={"pk": self.object.id}),),
+ % (
+ domain,
+ reverse("leaflet", kwargs={"pk": self.object.id}),
+ ),
"name": form.cleaned_data["name"],
"email": form.cleaned_data["email"],
"details": form.cleaned_data["details"],
diff --git a/electionleaflets/apps/elections/migrations/0001_initial.py b/electionleaflets/apps/elections/migrations/0001_initial.py
index d1a518c4..d903c43d 100644
--- a/electionleaflets/apps/elections/migrations/0001_initial.py
+++ b/electionleaflets/apps/elections/migrations/0001_initial.py
@@ -2,12 +2,11 @@
# Generated by Django 1.9.5 on 2016-04-03 14:56
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
-
initial = True
dependencies = [
diff --git a/electionleaflets/apps/leaflets/admin.py b/electionleaflets/apps/leaflets/admin.py
index 96c14092..1a8d29d4 100644
--- a/electionleaflets/apps/leaflets/admin.py
+++ b/electionleaflets/apps/leaflets/admin.py
@@ -1,8 +1,8 @@
from django.contrib import admin
from leaflets.models import Leaflet, LeafletImage
+from sorl.thumbnail import get_thumbnail
from .admin_widgets import AdminImageMixin
-from sorl.thumbnail import get_thumbnail
class LeafletImageInline(AdminImageMixin, admin.TabularInline):
@@ -59,9 +59,10 @@ def get_leaflet_title(self, obj):
get_leaflet_title.short_description = "Leaflet title"
def thumbnail(self, obj):
- if obj.image:
- thumb = get_thumbnail(obj.image, "100x100", crop="center")
- return "
" % thumb.url
+ if not obj.image:
+ return None
+ thumb = get_thumbnail(obj.image, "100x100", crop="center")
+ return "
" % thumb.url
thumbnail.allow_tags = True
diff --git a/electionleaflets/apps/leaflets/admin_widgets/__init__.py b/electionleaflets/apps/leaflets/admin_widgets/__init__.py
index de42000b..08f23ab6 100644
--- a/electionleaflets/apps/leaflets/admin_widgets/__init__.py
+++ b/electionleaflets/apps/leaflets/admin_widgets/__init__.py
@@ -1,3 +1,4 @@
+import contextlib
import logging
from django import forms
@@ -5,7 +6,6 @@
from sorl.thumbnail.fields import ImageField
from sorl.thumbnail.shortcuts import get_thumbnail
-
logger = logging.getLogger(__name__)
@@ -39,15 +39,13 @@ def render(self, name, value, attrs=None, **kwargs):
except Exception as e:
logger.warning("Unable to get the thumbnail", exc_info=e)
else:
- try:
+ with contextlib.suppress(AttributeError, TypeError):
output = (
'
'
) % (value.url, mini.url, output)
- except (AttributeError, TypeError):
- pass
return mark_safe(output)
diff --git a/electionleaflets/apps/leaflets/features/steps.py b/electionleaflets/apps/leaflets/features/steps.py
index 8fef0c15..6e96863b 100644
--- a/electionleaflets/apps/leaflets/features/steps.py
+++ b/electionleaflets/apps/leaflets/features/steps.py
@@ -2,14 +2,12 @@
import os
import re
-from django.urls import reverse
-from lxml import html
-
-from django.test.client import Client
from aloe import before, step, world
from aloe_django import django_url
-
from constituencies.models import Constituency
+from django.test.client import Client
+from django.urls import reverse
+from lxml import html
@before.all
@@ -22,7 +20,10 @@ def add_constituency(step, name):
Constituency.objects.get_or_create(
slug="camberwell_and_peckham",
pk="65913",
- defaults={"name": "Camberwell and Peckham", "country_name": "England",},
+ defaults={
+ "name": "Camberwell and Peckham",
+ "country_name": "England",
+ },
)
@@ -48,7 +49,8 @@ def set_file(step, name):
os.path.join(os.path.dirname(__file__), "../tests/test_images/", name)
)
if os.path.exists(file_path):
- world.data = {"image": open(file_path, "rb")}
+ with open(file_path, "rb") as f:
+ world.data = {"image": f}
assert True
return
assert False
@@ -89,9 +91,11 @@ def fill_journey(step):
form["leaflet_upload_wizzard-current_step"] = form_name
if "action" in list(form.keys()) and form["action"]:
form[form["action"]] = True
- if "people-people" in list(form.keys()):
- if form["people-people"] == "None":
- form["people-people"] = ""
+ if (
+ "people-people" in list(form.keys())
+ and form["people-people"] == "None"
+ ):
+ form["people-people"] = ""
world.response = world.browser.post(
reverse("upload_step", kwargs={"step": form_name}),
diff --git a/electionleaflets/apps/leaflets/fields.py b/electionleaflets/apps/leaflets/fields.py
index a60c707a..37d9c5e0 100644
--- a/electionleaflets/apps/leaflets/fields.py
+++ b/electionleaflets/apps/leaflets/fields.py
@@ -3,7 +3,7 @@
from django import forms
from django.core.exceptions import ValidationError
-from django.forms.widgets import MultiWidget, NumberInput
+from django.forms.widgets import MultiWidget
__all__ = ("DayMonthYearWidget",)
@@ -29,7 +29,6 @@ def decompress(self, value):
class DCDateField(forms.MultiValueField):
-
widget = DayMonthYearWidget
def __init__(self, *args, **kwargs):
@@ -45,13 +44,13 @@ def __init__(self, *args, **kwargs):
error_messages=error_messages,
fields=fields,
require_all_fields=True,
- **kwargs
+ **kwargs,
)
self.field_class = "form-date"
def compress(self, data_list):
if not data_list:
- return
+ return None
data_list = list(data_list)
data_list.reverse()
return datetime.datetime(*map(int, data_list))
diff --git a/electionleaflets/apps/leaflets/forms.py b/electionleaflets/apps/leaflets/forms.py
index 2594b975..d8ba1434 100644
--- a/electionleaflets/apps/leaflets/forms.py
+++ b/electionleaflets/apps/leaflets/forms.py
@@ -1,20 +1,16 @@
# coding=utf-8
-import datetime
import json
from datetime import timedelta
from urllib.parse import urljoin
import requests
from django import forms
-from django.core.exceptions import ValidationError
-from django.core.signing import Signer
from django.conf import settings
+from django.core.signing import Signer
from django.utils import timezone
-
-from localflavor.gb.forms import GBPostcodeField
-
from leaflets.fields import DCDateField
from leaflets.models import Leaflet, LeafletImage
+from localflavor.gb.forms import GBPostcodeField
class S3UploadedImageField(forms.ImageField):
@@ -25,6 +21,7 @@ def to_python(self, data):
data.name = content
if content.startswith("tmp/s3file"):
return data
+ return None
class ImagesForm(forms.Form):
@@ -35,7 +32,9 @@ def __init__(self, **kwargs):
else:
self.fields["image"] = S3UploadedImageField(
widget=forms.ClearableFileInput(
- attrs={"accept": "image/*",}
+ attrs={
+ "accept": "image/*",
+ }
),
error_messages={
"required": "Please add a photo or skip this step"
@@ -109,7 +108,8 @@ def create_option(
label = "Not Listed"
else:
label = "{0} ({1})".format(
- label["person"]["name"], label["party"]["party_name"],
+ label["person"]["name"],
+ label["party"]["party_name"],
)
return super(PeopleRadioWidget, self).create_option(
name, value, label, selected, index, subindex, attrs
@@ -134,7 +134,7 @@ def get_ballot_data_from_ynr(self, postcode):
:type instance: Leaflet
"""
url = urljoin(settings.YNR_BASE_URL, "/api/next/ballots/")
- auth_token = getattr(settings, 'YNR_API_KEY')
+ auth_token = getattr(settings, "YNR_API_KEY")
params = {"for_postcode": postcode, "auth_token": auth_token}
start, end = self.get_date_range()
params["election_date_range_after"] = start
@@ -188,7 +188,7 @@ def get_people_from_ballot_data(self, ballot_data, party=None):
for ballot in ballot_data:
for candidacy in ballot["candidacies"]:
- if party and not candidacy["party"]["legacy_slug"] in party:
+ if party and candidacy["party"]["legacy_slug"] not in party:
continue
candidacy["ballot"] = {
"ballot_paper_id": ballot["ballot_paper_id"],
@@ -203,14 +203,15 @@ def get_people_from_ballot_data(self, ballot_data, party=None):
class PartyForm(YNRBallotDataMixin, forms.Form):
-
party = forms.ChoiceField(
- choices=[], widget=forms.RadioSelect, required=False,
+ choices=[],
+ widget=forms.RadioSelect,
+ required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- if not "postcode" in self.initial:
+ if "postcode" not in self.initial:
return
self.FOR_DATE = kwargs.get("initial", {}).pop("for_date", None)
diff --git a/electionleaflets/apps/leaflets/management/commands/cache_images.py b/electionleaflets/apps/leaflets/management/commands/cache_images.py
index bfe721d5..6ca7d716 100644
--- a/electionleaflets/apps/leaflets/management/commands/cache_images.py
+++ b/electionleaflets/apps/leaflets/management/commands/cache_images.py
@@ -1,9 +1,8 @@
from django.core.management.base import BaseCommand
+from leaflets.models import LeafletImage
from sorl.thumbnail import get_thumbnail
from tqdm import tqdm
-from leaflets.models import LeafletImage
-
class Command(BaseCommand):
def handle(self, **options):
@@ -13,5 +12,5 @@ def handle(self, **options):
try:
get_thumbnail(leaflet_image.image, "350").url
get_thumbnail(leaflet_image.image, "100x100", crop="center").url
- except:
+ except Exception:
pass
diff --git a/electionleaflets/apps/leaflets/management/commands/export_meata_data.py b/electionleaflets/apps/leaflets/management/commands/export_meata_data.py
index d6ad57bc..3b4a8ecd 100644
--- a/electionleaflets/apps/leaflets/management/commands/export_meata_data.py
+++ b/electionleaflets/apps/leaflets/management/commands/export_meata_data.py
@@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
import csv
-
from django.core.management.base import BaseCommand
from django.utils.encoding import force_text
-
from leaflets.models import LeafletImage
@@ -34,10 +32,8 @@ def handle(self, **options):
"status",
"reviewed",
]
-
- out = csv.DictWriter(
- open("/tmp/meta_data.csv", "wb"), fieldnames=fieldnames
- )
+ with open("/tmp/meta_data.csv", "wb") as f:
+ out = csv.DictWriter(f, fieldnames=fieldnames)
out.writeheader()
for image in LeafletImage.objects.all():
data = {
@@ -77,7 +73,9 @@ def handle(self, **options):
)
if image.leaflet.election:
data.update(
- {"election": image.leaflet.election,}
+ {
+ "election": image.leaflet.election,
+ }
)
for k, v in list(data.items()):
diff --git a/electionleaflets/apps/leaflets/management/commands/leaflets_import_legacy.py b/electionleaflets/apps/leaflets/management/commands/leaflets_import_legacy.py
index 06cb4af9..8dd46de9 100644
--- a/electionleaflets/apps/leaflets/management/commands/leaflets_import_legacy.py
+++ b/electionleaflets/apps/leaflets/management/commands/leaflets_import_legacy.py
@@ -2,14 +2,12 @@
import os
import re
-from django.core.management.base import BaseCommand
+from constituencies.models import Constituency
from django.conf import settings
from django.core.files import File
-
-from legacy.models import legacyLeaflet
-
+from django.core.management.base import BaseCommand
from leaflets.models import Leaflet, LeafletImage
-from constituencies.models import Constituency
+from legacy.models import legacyLeaflet
from uk_political_parties.models import Party
@@ -40,19 +38,20 @@ def clean_legacy_leaflet_image(self, legacy_image):
if os.path.exists(image_path):
print("Exists")
- f = open(image_path, "r")
- data["image"] = File(f)
+ with open(image_path, "r") as f:
+ data["image"] = File(f)
else:
image_path = os.path.join(
settings.IMAGE_LOCAL_CACHE, "large", key
)
if os.path.exists(image_path):
print("Exists")
- f = open(image_path, "r")
- data["image"] = File(f)
+ with open(image_path, "r") as f:
+ data["image"] = File(f)
else:
print("Doesn't exist")
return data
+ return None
def clean_constituency(self, con):
con_name = con.constituency.name
@@ -70,34 +69,30 @@ def clean_constituency(self, con):
return con
def clean_postcode(self, postcode):
- try:
- postcode = postcode.encode("utf-8")
- postcode = postcode.upper()
- postcode = postcode.replace("!", "1")
- postcode = postcode.replace("@", "2")
- postcode = postcode.replace('"', "2")
- postcode = postcode.replace("£", "3")
- postcode = postcode.replace("$", "4")
- postcode = postcode.replace("%", "5")
- postcode = postcode.replace("^", "6")
- postcode = postcode.replace("&", "7")
- postcode = postcode.replace("*", "8")
- postcode = postcode.replace("(", "9")
- postcode = postcode.replace(")", "0")
- postcode = re.sub(r"[^\x00-\x7F]+", " ", postcode)
- postcode = re.sub("^[A-Z0-9\s]", "", postcode)
- postcode = postcode.strip()
- except:
- import ipdb
-
- ipdb.set_trace()
- return postcode
+ postcode = postcode.encode("utf-8")
+ postcode = postcode.upper()
+ postcode = postcode.replace("!", "1")
+ postcode = postcode.replace("@", "2")
+ postcode = postcode.replace('"', "2")
+ postcode = postcode.replace("£", "3")
+ postcode = postcode.replace("$", "4")
+ postcode = postcode.replace("%", "5")
+ postcode = postcode.replace("^", "6")
+ postcode = postcode.replace("&", "7")
+ postcode = postcode.replace("*", "8")
+ postcode = postcode.replace("(", "9")
+ postcode = postcode.replace(")", "0")
+ postcode = re.sub(r"[^\x00-\x7F]+", " ", postcode)
+ postcode = re.sub("^[A-Z0-9\s]", "", postcode)
+ return postcode.strip()
def handle(self, **options):
for legacy_leaflet in legacyLeaflet.objects.all():
- if not legacy_leaflet.date_uploaded:
- if legacy_leaflet.date_delivered:
- legacy_leaflet.date_uploaded = legacy_leaflet.date_delivered
+ if (
+ not legacy_leaflet.date_uploaded
+ and legacy_leaflet.date_delivered
+ ):
+ legacy_leaflet.date_uploaded = legacy_leaflet.date_delivered
if legacy_leaflet.date_uploaded:
if not bool(legacy_leaflet.publisher_party_id):
diff --git a/electionleaflets/apps/leaflets/middleware.py b/electionleaflets/apps/leaflets/middleware.py
index 7aa0966e..2b369cb9 100644
--- a/electionleaflets/apps/leaflets/middleware.py
+++ b/electionleaflets/apps/leaflets/middleware.py
@@ -3,9 +3,7 @@ def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
-
if request.method == "GET" and "source" in request.GET:
request.session["source"] = request.GET.get("source")
- response = self.get_response(request)
- return response
+ return self.get_response(request)
diff --git a/electionleaflets/apps/leaflets/migrations/0001_initial.py b/electionleaflets/apps/leaflets/migrations/0001_initial.py
index e6c49642..6edac060 100644
--- a/electionleaflets/apps/leaflets/migrations/0001_initial.py
+++ b/electionleaflets/apps/leaflets/migrations/0001_initial.py
@@ -1,12 +1,11 @@
# Generated by Django 2.2.20 on 2021-04-24 13:06
-from django.db import migrations, models
import django.db.models.deletion
import sorl.thumbnail.fields
+from django.db import migrations, models
class Migration(migrations.Migration):
-
dependencies = [
("elections", "0001_initial"),
("people", "0001_initial"),
@@ -109,7 +108,9 @@ class Migration(migrations.Migration):
models.CharField(blank=True, max_length=255, null=True),
),
],
- options={"ordering": ("-date_uploaded",),},
+ options={
+ "ordering": ("-date_uploaded",),
+ },
),
migrations.CreateModel(
name="LeafletImage",
@@ -179,6 +180,8 @@ class Migration(migrations.Migration):
),
),
],
- options={"ordering": ["image_type"],},
+ options={
+ "ordering": ["image_type"],
+ },
),
]
diff --git a/electionleaflets/apps/leaflets/migrations/0002_auto_20210501_1607.py b/electionleaflets/apps/leaflets/migrations/0002_auto_20210501_1607.py
index fbd3fb2d..3880d476 100644
--- a/electionleaflets/apps/leaflets/migrations/0002_auto_20210501_1607.py
+++ b/electionleaflets/apps/leaflets/migrations/0002_auto_20210501_1607.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
("leaflets", "0001_initial"),
]
diff --git a/electionleaflets/apps/leaflets/migrations/0003_auto_20210501_1655.py b/electionleaflets/apps/leaflets/migrations/0003_auto_20210501_1655.py
index 86b5244e..e85147c8 100644
--- a/electionleaflets/apps/leaflets/migrations/0003_auto_20210501_1655.py
+++ b/electionleaflets/apps/leaflets/migrations/0003_auto_20210501_1655.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
("leaflets", "0002_auto_20210501_1607"),
]
diff --git a/electionleaflets/apps/leaflets/migrations/0004_auto_20210502_1255.py b/electionleaflets/apps/leaflets/migrations/0004_auto_20210502_1255.py
index 0b5395a0..8f5ac9d3 100644
--- a/electionleaflets/apps/leaflets/migrations/0004_auto_20210502_1255.py
+++ b/electionleaflets/apps/leaflets/migrations/0004_auto_20210502_1255.py
@@ -22,7 +22,6 @@ def change_ballot_data_structure(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
("leaflets", "0003_auto_20210501_1655"),
]
diff --git a/electionleaflets/apps/leaflets/migrations/0005_auto_20240617_1459.py b/electionleaflets/apps/leaflets/migrations/0005_auto_20240617_1459.py
index 47c693ec..bd88f0d2 100644
--- a/electionleaflets/apps/leaflets/migrations/0005_auto_20240617_1459.py
+++ b/electionleaflets/apps/leaflets/migrations/0005_auto_20240617_1459.py
@@ -4,24 +4,33 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('leaflets', '0004_auto_20210502_1255'),
+ ("leaflets", "0004_auto_20210502_1255"),
]
operations = [
migrations.AlterModelOptions(
- name='leafletimage',
- options={'ordering': ['pk', 'image_type']},
+ name="leafletimage",
+ options={"ordering": ["pk", "image_type"]},
),
migrations.AddField(
- model_name='leaflet',
- name='modified',
+ model_name="leaflet",
+ name="modified",
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
- model_name='leaflet',
- name='status',
- field=models.CharField(blank=True, choices=[('live', 'Live'), ('draft', 'Draft'), ('removed', 'Removed')], default='draft', max_length=255, null=True),
+ model_name="leaflet",
+ name="status",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("live", "Live"),
+ ("draft", "Draft"),
+ ("removed", "Removed"),
+ ],
+ default="draft",
+ max_length=255,
+ null=True,
+ ),
),
]
diff --git a/electionleaflets/apps/leaflets/models.py b/electionleaflets/apps/leaflets/models.py
index 3d584ff3..08b67af2 100644
--- a/electionleaflets/apps/leaflets/models.py
+++ b/electionleaflets/apps/leaflets/models.py
@@ -4,25 +4,22 @@
from pathlib import Path
import piexif
-from django.core.files import File
-from django.core.files.storage import default_storage
-from slugify import slugify
-
from constituencies.models import Constituency
-from core.helpers import geocode
from django.core.files.base import ContentFile
+from django.core.files.storage import default_storage
from django.db import models
from django.db.models import JSONField
from django.forms.models import model_to_dict
from django.urls import reverse
-
-from electionleaflets.storages import TempUploadBaseMixin
from elections.models import Election
from people.models import Person
from PIL import Image
+from slugify import slugify
from sorl.thumbnail import ImageField, delete
from uk_political_parties.models import Party
+from electionleaflets.storages import TempUploadBaseMixin
+
from . import constants
@@ -104,10 +101,9 @@ def get_first_image(self):
def get_title(self):
if self.title and len(self.title):
return self.title
- elif self.publisher_party:
+ if self.publisher_party:
return "%s leaflet" % self.publisher_party.party_name
- else:
- "Untitled leaflet"
+ return None
def get_person(self):
if (
@@ -121,7 +117,7 @@ def get_person(self):
),
"name": self.ynr_person_name,
}
- elif self.publisher_person:
+ if self.publisher_person:
return {
"link": reverse(
"person",
@@ -129,6 +125,7 @@ def get_person(self):
),
"name": self.publisher_person.name,
}
+ return None
def get_party(self):
if self.ynr_party_id and self.ynr_party_name:
@@ -137,13 +134,14 @@ def get_party(self):
"link": reverse("party-view", kwargs={"pk": pp_id}),
"name": self.ynr_party_name,
}
- elif self.publisher_party:
+ if self.publisher_party:
return {
"link": reverse(
"party-view", kwargs={"pk": self.publisher_party.pk}
),
"name": self.publisher_party.party_name,
}
+ return None
class LeafletImage(models.Model):
@@ -205,9 +203,9 @@ def _remove_exif_data(self):
def _correctly_orient_image(self, image):
if self.orientation == 3:
return image.transpose(Image.ROTATE_180)
- elif self.orientation == 6:
+ if self.orientation == 6:
return image.transpose(Image.ROTATE_270)
- elif self.orientation == 8:
+ if self.orientation == 8:
return image.transpose(Image.ROTATE_90)
return image
@@ -309,13 +307,17 @@ def set_image_from_temp_file(self, temp_file):
"""
if not self.leaflet_id:
- raise ValueError("Parent Leaflet instance needs to be saved "
- "before a LeafletImage can be created")
+ raise ValueError(
+ "Parent Leaflet instance needs to be saved "
+ "before a LeafletImage can be created"
+ )
if not isinstance(default_storage, TempUploadBaseMixin):
raise ValueError("Storage class needs to use `TempUploadBaseMixin`")
file_name, ext = os.path.basename(temp_file).rsplit(".")
- target_file_path = Path(f"leaflets/{self.leaflet.pk}/{slugify(file_name)}.{ext}")
+ target_file_path = Path(
+ f"leaflets/{self.leaflet.pk}/{slugify(file_name)}.{ext}"
+ )
default_storage.save_from_temp_upload(temp_file, target_file_path)
self.image.name = str(target_file_path)
diff --git a/electionleaflets/apps/leaflets/templatetags/leaflet_tags.py b/electionleaflets/apps/leaflets/templatetags/leaflet_tags.py
index 813e9fa5..42ce538a 100644
--- a/electionleaflets/apps/leaflets/templatetags/leaflet_tags.py
+++ b/electionleaflets/apps/leaflets/templatetags/leaflet_tags.py
@@ -1,7 +1,8 @@
+import os
+
from django import template
from django.conf import settings
from leaflets.models import Leaflet
-import os
register = template.Library()
@@ -15,8 +16,7 @@ def leaflet_carousel():
@register.simple_tag
def get_medium_image_from_upload(file_path):
path = os.path.join(settings.MEDIA_URL, file_path.name)
- path = path.replace("uploads/", "uploads/medium/")
- return path
+ return path.replace("uploads/", "uploads/medium/")
@register.filter
diff --git a/electionleaflets/apps/leaflets/tests/conftest.py b/electionleaflets/apps/leaflets/tests/conftest.py
index 5597b579..cbe4a6c7 100644
--- a/electionleaflets/apps/leaflets/tests/conftest.py
+++ b/electionleaflets/apps/leaflets/tests/conftest.py
@@ -4,13 +4,11 @@
import boto3
import pytest
from django.core.files.storage import default_storage
-
from moto import mock_aws
TEST_IMAGE_LOCATION = Path(__file__).parent / "test_images/front_test.jpg"
-
@pytest.fixture()
def uploaded_temp_file():
"""
@@ -24,19 +22,20 @@ def uploaded_temp_file():
"""
path = "test-leaflet.jpeg"
- with default_storage.open(path, 'wb') as f:
+ with default_storage.open(path, "wb") as f:
f.write(TEST_IMAGE_LOCATION.read_bytes())
return path
+
@pytest.fixture
def mock_get_ballot_data_from_ynr():
def _mock_ynr_value(return_value):
return patch(
"leaflets.forms.PartyForm.get_ballot_data_from_ynr",
- return_value=return_value
+ return_value=return_value,
)
- return _mock_ynr_value
+ return _mock_ynr_value
@pytest.fixture
@@ -47,7 +46,7 @@ def s3_client():
client = boto3.client("s3")
yield client
+
@pytest.fixture
def s3_bucket(s3_client, settings):
- bucket = s3_client.create_bucket(Bucket=settings.AWS_STORAGE_BUCKET_NAME)
- return bucket
+ return s3_client.create_bucket(Bucket=settings.AWS_STORAGE_BUCKET_NAME)
diff --git a/electionleaflets/apps/leaflets/tests/data.py b/electionleaflets/apps/leaflets/tests/data.py
index 9bc2e0de..0bb8efbb 100644
--- a/electionleaflets/apps/leaflets/tests/data.py
+++ b/electionleaflets/apps/leaflets/tests/data.py
@@ -2,10 +2,10 @@
{"wgs84_lat": 51.4599323104553, "coordsyst": "G", "shortcuts": {"WMC": 65913, "ward": 8323, "council": 2491}, "wgs84_lon": -0.0824797738988752, "postcode": "SE22 8DJ", "easting": 533310, "areas": {"900000": {"parent_area": null, "generation_high": 19, "all_names": {}, "id": 900000, "codes": {}, "name": "House of Commons", "country": "", "type_name": "UK Parliament", "generation_low": 1, "country_name": "-", "type": "WMP"}, "900001": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 900001, "codes": {}, "name": "European Parliament", "country": "", "type_name": "European Parliament", "generation_low": 1, "country_name": "-", "type": "EUP"}, "900002": {"parent_area": 900006, "generation_high": 23, "all_names": {}, "id": 900002, "codes": {}, "name": "London Assembly", "country": "E", "type_name": "London Assembly area (shared)", "generation_low": 1, "country_name": "England", "type": "LAE"}, "8323": {"parent_area": 2491, "generation_high": 23, "all_names": {}, "id": 8323, "codes": {"ons": "00BEGW", "gss": "E05000551", "unit_id": "11015"}, "name": "South Camberwell", "country": "E", "type_name": "London borough ward", "generation_low": 1, "country_name": "England", "type": "LBW"}, "900006": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 900006, "codes": {}, "name": "London Assembly", "country": "E", "type_name": "London Assembly area", "generation_low": 1, "country_name": "England", "type": "LAS"}, "2247": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 2247, "codes": {"unit_id": "41441"}, "name": "Greater London Authority", "country": "E", "type_name": "Greater London Authority", "generation_low": 1, "country_name": "England", "type": "GLA"}, "70189": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 70189, "codes": {"ons": "E01004048"}, "name": "Southwark 027C", "country": "E", "type_name": "Lower Layer Super Output Area (Full)", "generation_low": 13, "country_name": "England", "type": "OLF"}, "11822": {"parent_area": 2247, "generation_high": 23, "all_names": {}, "id": 11822, "codes": {"gss": "E32000010", "unit_id": "41446"}, "name": "Lambeth and Southwark", "country": "E", "type_name": "London Assembly constituency", "generation_low": 1, "country_name": "England", "type": "LAC"}, "41906": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 41906, "codes": {"ons": "E02000833"}, "name": "Southwark 027", "country": "E", "type_name": "Middle Layer Super Output Area (Generalised)", "generation_low": 13, "country_name": "England", "type": "OMG"}, "104567": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 104567, "codes": {"ons": "E01004048"}, "name": "Southwark 027C", "country": "E", "type_name": "Lower Layer Super Output Area (Generalised)", "generation_low": 13, "country_name": "England", "type": "OLG"}, "34712": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 34712, "codes": {"ons": "E02000833"}, "name": "Southwark 027", "country": "E", "type_name": "Middle Layer Super Output Area (Full)", "generation_low": 13, "country_name": "England", "type": "OMF"}, "65913": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 65913, "codes": {"gss": "E14000615", "unit_id": "25066"}, "name": "Camberwell and Peckham", "country": "E", "type_name": "UK Parliament constituency", "generation_low": 13, "country_name": "England", "type": "WMC"}, "2491": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 2491, "codes": {"ons": "00BE", "gss": "E09000028", "unit_id": "11013"}, "name": "Southwark Borough Council", "country": "E", "type_name": "London borough", "generation_low": 1, "country_name": "England", "type": "LBO"}, "11806": {"parent_area": null, "generation_high": 23, "all_names": {}, "id": 11806, "codes": {"ons": "07", "gss": "E15000007", "unit_id": "41428"}, "name": "London", "country": "E", "type_name": "European region", "generation_low": 1, "country_name": "England", "type": "EUR"}}, "northing": 175189}"""
LOCAL_BALLOT_WITH_CANDIDATES = {
- "url": "https://candidates.democracyclub.org.uk/api/next/ballots/local.westminster.st-jamess.2022-05-05/",
- "history_url": "https://candidates.democracyclub.org.uk/api/next/ballots/local.westminster.st-jamess.2022-05-05/history/",
- "results_url": "https://candidates.democracyclub.org.uk/api/next/results/local.westminster.st-jamess.2022-05-05/",
- "election": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/ballots/local.westminster.st-jamess.2022-05-05/",
+ "history_url": "https://candidates.democracyclub.org.uk/api/next/ballots/local.westminster.st-jamess.2022-05-05/history/",
+ "results_url": "https://candidates.democracyclub.org.uk/api/next/results/local.westminster.st-jamess.2022-05-05/",
+ "election": {
"election_id": "local.westminster.2022-05-05",
"url": "https://candidates.democracyclub.org.uk/api/next/elections/local.westminster.2022-05-05/",
"name": "Westminster local election",
@@ -13,279 +13,252 @@
"current": False,
"party_lists_in_use": False,
"created": "2022-01-05T11:15:54.488413Z",
- "last_updated": "2024-01-16T18:09:16.371357Z"
- },
- "post": {
+ "last_updated": "2024-01-16T18:09:16.371357Z",
+ },
+ "post": {
"id": "gss:E05013806",
"label": "St James's",
"slug": "st-jamess",
"created": "2022-01-05T11:15:54.795194Z",
- "last_updated": "2023-07-11T15:31:13.176989+01:00"
- },
- "winner_count": 3,
- "ballot_paper_id": "local.westminster.st-jamess.2022-05-05",
- "cancelled": False,
- "sopn": {
+ "last_updated": "2023-07-11T15:31:13.176989+01:00",
+ },
+ "winner_count": 3,
+ "ballot_paper_id": "local.westminster.st-jamess.2022-05-05",
+ "cancelled": False,
+ "sopn": {
"uploaded_file": "https://s3.eu-west-2.amazonaws.com/static-candidates.democracyclub.org.uk/media/official_documents/local.westminster.st-jamess.2022-05-05/statement-of-persons-nominated.pdf",
- "source_url": "https://www.westminster.gov.uk/media/document/st-jamess-ward-statement-of-persons-nominated"
- },
- "candidates_locked": True,
- "candidacies": [
+ "source_url": "https://www.westminster.gov.uk/media/document/st-jamess-ward-statement-of-persons-nominated",
+ },
+ "candidates_locked": True,
+ "candidacies": [
{
- "elected": True,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP52/",
- "ec_id": "PP52",
- "name": "Conservative and Unionist Party",
- "legacy_slug": "party:52",
- "created": "2018-09-17T15:01:08.048032+01:00",
- "modified": "2024-11-19T02:06:24.008964Z"
- },
- "party_name": "Conservative and Unionist Party",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-06T15:06:56.273587+01:00",
- "modified": "2022-05-06T04:12:08.754253+01:00",
- "person": {
- "id": 41112,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/41112/",
- "name": "Louise Hyams"
- },
- "result": {
"elected": True,
- "num_ballots": 979
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP52/",
+ "ec_id": "PP52",
+ "name": "Conservative and Unionist Party",
+ "legacy_slug": "party:52",
+ "created": "2018-09-17T15:01:08.048032+01:00",
+ "modified": "2024-11-19T02:06:24.008964Z",
+ },
+ "party_name": "Conservative and Unionist Party",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-06T15:06:56.273587+01:00",
+ "modified": "2022-05-06T04:12:08.754253+01:00",
+ "person": {
+ "id": 41112,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/41112/",
+ "name": "Louise Hyams",
+ },
+ "result": {"elected": True, "num_ballots": 979},
},
{
- "elected": True,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP52/",
- "ec_id": "PP52",
- "name": "Conservative and Unionist Party",
- "legacy_slug": "party:52",
- "created": "2018-09-17T15:01:08.048032+01:00",
- "modified": "2024-11-19T02:06:24.008964Z"
- },
- "party_name": "Conservative and Unionist Party",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-06T15:06:56.327388+01:00",
- "modified": "2022-05-06T04:12:08.836342+01:00",
- "person": {
- "id": 41114,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/41114/",
- "name": "Tim Mitchell"
- },
- "result": {
"elected": True,
- "num_ballots": 965
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP52/",
+ "ec_id": "PP52",
+ "name": "Conservative and Unionist Party",
+ "legacy_slug": "party:52",
+ "created": "2018-09-17T15:01:08.048032+01:00",
+ "modified": "2024-11-19T02:06:24.008964Z",
+ },
+ "party_name": "Conservative and Unionist Party",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-06T15:06:56.327388+01:00",
+ "modified": "2022-05-06T04:12:08.836342+01:00",
+ "person": {
+ "id": 41114,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/41114/",
+ "name": "Tim Mitchell",
+ },
+ "result": {"elected": True, "num_ballots": 965},
},
{
- "elected": True,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP52/",
- "ec_id": "PP52",
- "name": "Conservative and Unionist Party",
- "legacy_slug": "party:52",
- "created": "2018-09-17T15:01:08.048032+01:00",
- "modified": "2024-11-19T02:06:24.008964Z"
- },
- "party_name": "Conservative and Unionist Party",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-06T15:06:56.389619+01:00",
- "modified": "2022-05-06T04:12:08.912046+01:00",
- "person": {
- "id": 41117,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/41117/",
- "name": "Mark Shearer"
- },
- "result": {
"elected": True,
- "num_ballots": 954
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP52/",
+ "ec_id": "PP52",
+ "name": "Conservative and Unionist Party",
+ "legacy_slug": "party:52",
+ "created": "2018-09-17T15:01:08.048032+01:00",
+ "modified": "2024-11-19T02:06:24.008964Z",
+ },
+ "party_name": "Conservative and Unionist Party",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-06T15:06:56.389619+01:00",
+ "modified": "2022-05-06T04:12:08.912046+01:00",
+ "person": {
+ "id": 41117,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/41117/",
+ "name": "Mark Shearer",
+ },
+ "result": {"elected": True, "num_ballots": 954},
},
{
- "elected": False,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP53/",
- "ec_id": "PP53",
- "name": "Labour Party",
- "legacy_slug": "party:53",
- "created": "2018-09-17T15:01:09.580623+01:00",
- "modified": "2024-11-15T02:06:31.822331Z"
- },
- "party_name": "Labour Party",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-07T16:50:03.294367+01:00",
- "modified": "2022-04-07T16:50:03.296651+01:00",
- "person": {
- "id": 92971,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/92971/",
- "name": "Karina Darbin"
- },
- "result": {
"elected": False,
- "num_ballots": 789
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP53/",
+ "ec_id": "PP53",
+ "name": "Labour Party",
+ "legacy_slug": "party:53",
+ "created": "2018-09-17T15:01:09.580623+01:00",
+ "modified": "2024-11-15T02:06:31.822331Z",
+ },
+ "party_name": "Labour Party",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-07T16:50:03.294367+01:00",
+ "modified": "2022-04-07T16:50:03.296651+01:00",
+ "person": {
+ "id": 92971,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/92971/",
+ "name": "Karina Darbin",
+ },
+ "result": {"elected": False, "num_ballots": 789},
},
{
- "elected": False,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP53/",
- "ec_id": "PP53",
- "name": "Labour Party",
- "legacy_slug": "party:53",
- "created": "2018-09-17T15:01:09.580623+01:00",
- "modified": "2024-11-15T02:06:31.822331Z"
- },
- "party_name": "Labour Party",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-07T16:50:03.479288+01:00",
- "modified": "2022-04-07T16:50:03.481537+01:00",
- "person": {
- "id": 92973,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/92973/",
- "name": "Paul Raphael James Spence"
- },
- "result": {
"elected": False,
- "num_ballots": 701
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP53/",
+ "ec_id": "PP53",
+ "name": "Labour Party",
+ "legacy_slug": "party:53",
+ "created": "2018-09-17T15:01:09.580623+01:00",
+ "modified": "2024-11-15T02:06:31.822331Z",
+ },
+ "party_name": "Labour Party",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-07T16:50:03.479288+01:00",
+ "modified": "2022-04-07T16:50:03.481537+01:00",
+ "person": {
+ "id": 92973,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/92973/",
+ "name": "Paul Raphael James Spence",
+ },
+ "result": {"elected": False, "num_ballots": 701},
},
{
- "elected": False,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP53/",
- "ec_id": "PP53",
- "name": "Labour Party",
- "legacy_slug": "party:53",
- "created": "2018-09-17T15:01:09.580623+01:00",
- "modified": "2024-11-15T02:06:31.822331Z"
- },
- "party_name": "Labour Party",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-07T16:50:03.416071+01:00",
- "modified": "2022-04-07T16:50:03.418536+01:00",
- "person": {
- "id": 92972,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/92972/",
- "name": "Nigel Stephen Medforth"
- },
- "result": {
"elected": False,
- "num_ballots": 700
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP53/",
+ "ec_id": "PP53",
+ "name": "Labour Party",
+ "legacy_slug": "party:53",
+ "created": "2018-09-17T15:01:09.580623+01:00",
+ "modified": "2024-11-15T02:06:31.822331Z",
+ },
+ "party_name": "Labour Party",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-07T16:50:03.416071+01:00",
+ "modified": "2022-04-07T16:50:03.418536+01:00",
+ "person": {
+ "id": 92972,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/92972/",
+ "name": "Nigel Stephen Medforth",
+ },
+ "result": {"elected": False, "num_ballots": 700},
},
{
- "elected": False,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP90/",
- "ec_id": "PP90",
- "name": "Liberal Democrats",
- "legacy_slug": "party:90",
- "created": "2018-09-17T15:01:08.091504+01:00",
- "modified": "2024-11-19T02:06:24.311670Z"
- },
- "party_name": "Liberal Democrats",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-07T16:50:03.221833+01:00",
- "modified": "2022-04-07T16:50:03.224684+01:00",
- "person": {
- "id": 92970,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/92970/",
- "name": "Michael Anthony Ahearne"
- },
- "result": {
"elected": False,
- "num_ballots": 295
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP90/",
+ "ec_id": "PP90",
+ "name": "Liberal Democrats",
+ "legacy_slug": "party:90",
+ "created": "2018-09-17T15:01:08.091504+01:00",
+ "modified": "2024-11-19T02:06:24.311670Z",
+ },
+ "party_name": "Liberal Democrats",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-07T16:50:03.221833+01:00",
+ "modified": "2022-04-07T16:50:03.224684+01:00",
+ "person": {
+ "id": 92970,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/92970/",
+ "name": "Michael Anthony Ahearne",
+ },
+ "result": {"elected": False, "num_ballots": 295},
},
{
- "elected": False,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP90/",
- "ec_id": "PP90",
- "name": "Liberal Democrats",
- "legacy_slug": "party:90",
- "created": "2018-09-17T15:01:08.091504+01:00",
- "modified": "2024-11-19T02:06:24.311670Z"
- },
- "party_name": "Liberal Democrats",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-07T16:50:03.341602+01:00",
- "modified": "2022-04-07T16:50:03.343789+01:00",
- "person": {
- "id": 41110,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/41110/",
- "name": "Paul Diggory"
- },
- "result": {
"elected": False,
- "num_ballots": 281
- }
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP90/",
+ "ec_id": "PP90",
+ "name": "Liberal Democrats",
+ "legacy_slug": "party:90",
+ "created": "2018-09-17T15:01:08.091504+01:00",
+ "modified": "2024-11-19T02:06:24.311670Z",
+ },
+ "party_name": "Liberal Democrats",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-07T16:50:03.341602+01:00",
+ "modified": "2022-04-07T16:50:03.343789+01:00",
+ "person": {
+ "id": 41110,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/41110/",
+ "name": "Paul Diggory",
+ },
+ "result": {"elected": False, "num_ballots": 281},
},
{
- "elected": False,
- "party_list_position": None,
- "party": {
- "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP90/",
- "ec_id": "PP90",
- "name": "Liberal Democrats",
- "legacy_slug": "party:90",
- "created": "2018-09-17T15:01:08.091504+01:00",
- "modified": "2024-11-19T02:06:24.311670Z"
- },
- "party_name": "Liberal Democrats",
- "party_description_text": "",
- "deselected": False,
- "deselected_source": "",
- "created": "2022-04-07T16:50:03.544670+01:00",
- "modified": "2022-04-07T16:50:03.546973+01:00",
- "person": {
- "id": 92974,
- "url": "https://candidates.democracyclub.org.uk/api/next/people/92974/",
- "name": "Alice Anne Wells"
- },
- "result": {
"elected": False,
- "num_ballots": 249
- }
- }
- ],
- "created": "2022-01-05T11:15:54.801506Z",
- "last_updated": "2024-01-16T18:09:16.478549Z",
- "replaces": None,
- "replaced_by": None,
- "uncontested": False,
- "results": {
+ "party_list_position": None,
+ "party": {
+ "url": "https://candidates.democracyclub.org.uk/api/next/parties/PP90/",
+ "ec_id": "PP90",
+ "name": "Liberal Democrats",
+ "legacy_slug": "party:90",
+ "created": "2018-09-17T15:01:08.091504+01:00",
+ "modified": "2024-11-19T02:06:24.311670Z",
+ },
+ "party_name": "Liberal Democrats",
+ "party_description_text": "",
+ "deselected": False,
+ "deselected_source": "",
+ "created": "2022-04-07T16:50:03.544670+01:00",
+ "modified": "2022-04-07T16:50:03.546973+01:00",
+ "person": {
+ "id": 92974,
+ "url": "https://candidates.democracyclub.org.uk/api/next/people/92974/",
+ "name": "Alice Anne Wells",
+ },
+ "result": {"elected": False, "num_ballots": 249},
+ },
+ ],
+ "created": "2022-01-05T11:15:54.801506Z",
+ "last_updated": "2024-01-16T18:09:16.478549Z",
+ "replaces": None,
+ "replaced_by": None,
+ "uncontested": False,
+ "results": {
"num_turnout_reported": 2057,
"turnout_percentage": 30,
"num_spoilt_ballots": None,
"source": "https://www.westminster.gov.uk/about-council/democracy/elections-referendums-and-how-vote/local-elections-5-may-2022/st-jamess",
- "total_electorate": 6943
- },
- "voting_system": "FPTP"
- }
+ "total_electorate": 6943,
+ },
+ "voting_system": "FPTP",
+}
diff --git a/electionleaflets/apps/leaflets/tests/helpers.py b/electionleaflets/apps/leaflets/tests/helpers.py
index 684d968b..24b33f50 100644
--- a/electionleaflets/apps/leaflets/tests/helpers.py
+++ b/electionleaflets/apps/leaflets/tests/helpers.py
@@ -1,14 +1,13 @@
from django.core.files import File
-
from leaflets.models import Leaflet, LeafletImage
from leaflets.tests.conftest import TEST_IMAGE_LOCATION
def create_dummy_leaflets(number=1):
for i in range(number):
- l = Leaflet()
- l.save()
+ leaflet = Leaflet()
+ leaflet.save()
- li = LeafletImage(leaflet=l)
+ li = LeafletImage(leaflet=leaflet)
li.image.save("test_1.jpg", File(TEST_IMAGE_LOCATION.open("rb")))
li.save()
diff --git a/electionleaflets/apps/leaflets/tests/model_factory.py b/electionleaflets/apps/leaflets/tests/model_factory.py
index 177935ed..f2503f30 100644
--- a/electionleaflets/apps/leaflets/tests/model_factory.py
+++ b/electionleaflets/apps/leaflets/tests/model_factory.py
@@ -1,6 +1,5 @@
import factory
from factory.django import DjangoModelFactory
-
from leaflets.models import Leaflet
diff --git a/electionleaflets/apps/leaflets/tests/test_frontend.py b/electionleaflets/apps/leaflets/tests/test_frontend.py
index 0eb37002..5cd90c52 100644
--- a/electionleaflets/apps/leaflets/tests/test_frontend.py
+++ b/electionleaflets/apps/leaflets/tests/test_frontend.py
@@ -1,9 +1,10 @@
-import pytest
from pathlib import Path
-from electionleaflets import settings
-from playwright.sync_api import Page, expect
+import pytest
from leaflets.tests.data import LOCAL_BALLOT_WITH_CANDIDATES
+from playwright.sync_api import Page, expect
+
+from electionleaflets import settings
def console_handler(message):
@@ -21,12 +22,16 @@ def console_handler(message):
return
if "net::ERR_INTERNET_DISCONNECTED" in message.text:
return
- assert not message.text, f"Found browser console output: {message.text}: {message.location}"
+ assert (
+ not message.text
+ ), f"Found browser console output: {message.text}: {message.location}"
-class TestLeafletUpload():
+class TestLeafletUpload:
@pytest.fixture(autouse=True)
- def setup_method(self, page: Page, live_server, mock_get_ballot_data_from_ynr):
+ def setup_method(
+ self, page: Page, live_server, mock_get_ballot_data_from_ynr
+ ):
self.page = page
# Raise if the console contains errors
self.page.on("console", console_handler)
@@ -34,18 +39,21 @@ def setup_method(self, page: Page, live_server, mock_get_ballot_data_from_ynr):
self.mock_get_ballot_data_from_ynr = mock_get_ballot_data_from_ynr
def navigate_to_home_page(self):
-
self.page.goto(self.live_server.url)
- def get_test_image(self, leaflet_file_path="apps/leaflets/tests/test_images/front_test.jpg"):
+ def get_test_image(
+ self, leaflet_file_path="apps/leaflets/tests/test_images/front_test.jpg"
+ ):
project_root = Path(settings.PROJECT_ROOT).resolve()
return [str(project_root / leaflet_file_path)]
def navigate_to_upload_page(self):
- upload_link = self.page.get_by_role('link', name='Upload a leaflet')
+ upload_link = self.page.get_by_role("link", name="Upload a leaflet")
upload_link.click()
- expect(self.page).to_have_url(f'{self.live_server.url}/leaflets/add/images/')
- take_photo_link = self.page.get_by_label('Take a photo of a leaflet')
+ expect(self.page).to_have_url(
+ f"{self.live_server.url}/leaflets/add/images/"
+ )
+ take_photo_link = self.page.get_by_label("Take a photo of a leaflet")
take_photo_link.click()
def upload_leaflet(self):
@@ -53,45 +61,45 @@ def upload_leaflet(self):
self.navigate_to_upload_page()
files = self.get_test_image()
self.page.set_input_files(selector='input[type="file"]', files=files)
- submit_button = self.page.get_by_role('button', name='Continue')
+ submit_button = self.page.get_by_role("button", name="Continue")
submit_button.click()
def upload_multiple_files_for_one_leaflet(self):
self.navigate_to_home_page()
self.navigate_to_upload_page()
files = self.get_test_image()
- self.page.set_input_files(selector='input[type="file"]', files=files)
- add_another_image = self.page.get_by_text('Add another image')
+ self.page.set_input_files(selector='input[type="file"]', files=files)
+ add_another_image = self.page.get_by_text("Add another image")
add_another_image.click()
self.page.set_input_files(selector='input[type="file"]', files=files)
- submit_button = self.page.get_by_role('button', name='Continue')
+ submit_button = self.page.get_by_role("button", name="Continue")
submit_button.click()
- def fill_postcode(self, postcode='SW1A 1AA'):
- postcode_input = self.page.get_by_label('What postcode was this')
+ def fill_postcode(self, postcode="SW1A 1AA"):
+ postcode_input = self.page.get_by_label("What postcode was this")
postcode_input.click()
postcode_input.fill(postcode)
def select_time_and_submit(self):
- time_input = self.page.get_by_text('In the last couple of weeks')
+ time_input = self.page.get_by_text("In the last couple of weeks")
time_input.check()
- submit_button = self.page.get_by_role('button', name='Submit')
+ submit_button = self.page.get_by_role("button", name="Submit")
submit_button.click()
- def select_party_and_submit(self, party='Green Party'):
+ def select_party_and_submit(self, party="Green Party"):
party_option = self.page.get_by_text(party)
party_option.click()
- submit_button = self.page.get_by_role('button', name='Submit')
+ submit_button = self.page.get_by_role("button", name="Submit")
submit_button.click()
def enter_dates_and_submit(self):
- day_input = self.page.get_by_label('day')
- day_input.fill('01')
- month_input = self.page.get_by_label('month')
- month_input.fill('01')
- year_input = self.page.get_by_label('year')
- year_input.fill('2021')
- submit_button = self.page.get_by_role('button', name='Submit')
+ day_input = self.page.get_by_label("day")
+ day_input.fill("01")
+ month_input = self.page.get_by_label("month")
+ month_input.fill("01")
+ year_input = self.page.get_by_label("year")
+ year_input.fill("2021")
+ submit_button = self.page.get_by_role("button", name="Submit")
submit_button.click()
def test_basic_upload(self):
@@ -101,29 +109,30 @@ def test_basic_upload(self):
with self.mock_get_ballot_data_from_ynr([LOCAL_BALLOT_WITH_CANDIDATES]):
self.select_time_and_submit()
self.select_party_and_submit()
- id = self.page.url.split('/')[-2]
- expect(self.page).to_have_url(f'{self.live_server.url}/leaflets/{id}/')
- expect(self.page.locator('h1')).to_have_text(f'Leaflet #{id}')
- expect(self.page.locator('h2')).to_have_text('Leaflet details')
+ id = self.page.url.split("/")[-2]
+ expect(self.page).to_have_url(f"{self.live_server.url}/leaflets/{id}/")
+ expect(self.page.locator("h1")).to_have_text(f"Leaflet #{id}")
+ expect(self.page.locator("h2")).to_have_text("Leaflet details")
def test_upload_leaflet_with_invalid_postcode(self):
self.upload_leaflet()
- self.fill_postcode(postcode='INVALID')
+ self.fill_postcode(postcode="INVALID")
self.select_time_and_submit()
- submit_button = self.page.get_by_role('button', name='Submit')
+ submit_button = self.page.get_by_role("button", name="Submit")
submit_button.click()
- error_message = self.page.get_by_text('Please enter a full UK postcode')
+ error_message = self.page.get_by_text("Please enter a full UK postcode")
assert error_message is not None
-
def test_party_page_content(self):
self.upload_leaflet()
self.fill_postcode()
with self.mock_get_ballot_data_from_ynr([LOCAL_BALLOT_WITH_CANDIDATES]):
self.select_time_and_submit()
self.select_party_and_submit()
- id = self.page.url.split('/')[-2]
- expect(self.page).to_have_url(f'{self.live_server.url}/leaflets/{id}/')
- labour_party_link = self.page.get_by_role('link', name='Green Party')
+ id = self.page.url.split("/")[-2]
+ expect(self.page).to_have_url(f"{self.live_server.url}/leaflets/{id}/")
+ labour_party_link = self.page.get_by_role("link", name="Green Party")
labour_party_link.click()
- expect(self.page.locator('h1')).to_have_text('Election leaflets from Green Party')
+ expect(self.page.locator("h1")).to_have_text(
+ "Election leaflets from Green Party"
+ )
diff --git a/electionleaflets/apps/leaflets/tests/test_image_editing.py b/electionleaflets/apps/leaflets/tests/test_image_editing.py
index ed58efd3..292505f2 100644
--- a/electionleaflets/apps/leaflets/tests/test_image_editing.py
+++ b/electionleaflets/apps/leaflets/tests/test_image_editing.py
@@ -1,9 +1,11 @@
import os
import shutil
+
import pytest
+from leaflets.models import LeafletImage
from electionleaflets import settings
-from leaflets.models import Leaflet, LeafletImage
+
from .helpers import create_dummy_leaflets
@@ -12,11 +14,12 @@ def leaflet_image():
create_dummy_leaflets()
return LeafletImage.objects.last()
+
@pytest.mark.django_db
class TestImageEditing:
def test_dimensions(self, leaflet_image):
assert leaflet_image.dimensions == (419, 300)
-
+
def test_crop(self, leaflet_image):
assert leaflet_image.dimensions == (419, 300)
x = 12
@@ -25,7 +28,7 @@ def test_crop(self, leaflet_image):
y2 = 100
leaflet_image.crop(x, y, x2, y2)
assert leaflet_image.dimensions == (88, 93)
-
+
def tearDown(self):
"""
Remove testing files after tests are run
diff --git a/electionleaflets/apps/leaflets/tests/test_models.py b/electionleaflets/apps/leaflets/tests/test_models.py
index 94174df5..21a9efaf 100644
--- a/electionleaflets/apps/leaflets/tests/test_models.py
+++ b/electionleaflets/apps/leaflets/tests/test_models.py
@@ -2,47 +2,76 @@
import pytest
from leaflets.models import Leaflet, LeafletImage
-from uk_political_parties.models import Party
-
from leaflets.tests.conftest import TEST_IMAGE_LOCATION
+from uk_political_parties.models import Party
@pytest.fixture
def leaflet():
return Leaflet.objects.create(title="Test Leaflet", description=None)
+
@pytest.fixture
def party():
return Party.objects.create(party_name="Labour Party")
+
@pytest.mark.django_db
def test_model_initial():
leaflet = Leaflet()
assert leaflet._initial == {
- 'id': None, 'title': '', 'description': None, 'publisher_party': None, 'ynr_party_id': None, 'ynr_party_name': None, 'publisher_person': None, 'ynr_person_id': None, 'ynr_person_name': None, 'ballot_id': None, 'ballots': [], 'people': {}, 'person_ids': [], 'election': None, 'constituency': None, 'imprint': None, 'postcode': '', 'name': '', 'email': '', 'date_delivered': None, 'status': 'draft', 'reviewed': False
+ "id": None,
+ "title": "",
+ "description": None,
+ "publisher_party": None,
+ "ynr_party_id": None,
+ "ynr_party_name": None,
+ "publisher_person": None,
+ "ynr_person_id": None,
+ "ynr_person_name": None,
+ "ballot_id": None,
+ "ballots": [],
+ "people": {},
+ "person_ids": [],
+ "election": None,
+ "constituency": None,
+ "imprint": None,
+ "postcode": "",
+ "name": "",
+ "email": "",
+ "date_delivered": None,
+ "status": "draft",
+ "reviewed": False,
}
assert leaflet._initial["status"] == "draft"
+
@pytest.mark.django_db
def test_markdown_error(client, leaflet):
response = client.get(leaflet.get_absolute_url())
assert response.status_code == 200
+
@pytest.mark.django_db
def test_leaflet_detail(client, party):
leaflet = Leaflet.objects.create(
- title="Test Leaflet", description=None, ynr_party_id="party:1", ynr_party_name="Labour Party", publisher_party=party
+ title="Test Leaflet",
+ description=None,
+ ynr_party_id="party:1",
+ ynr_party_name="Labour Party",
+ publisher_party=party,
)
response = client.get(leaflet.get_absolute_url())
assert response.status_code == 200
assert "Test Leaflet" in response.content.decode()
assert "Labour Party" in response.content.decode()
+
@pytest.mark.django_db
def test_raw_image_field():
- l = Leaflet()
- l.save()
- li = LeafletImage(leaflet=l)
+ leaflet = Leaflet()
+ leaflet.save()
+ li = LeafletImage(leaflet=leaflet)
assert li.raw_image.name == ""
with TEST_IMAGE_LOCATION.open("rb") as img_file:
li.image.save("front_test.jpg", img_file)
@@ -50,12 +79,13 @@ def test_raw_image_field():
def test_save_leaflet_image_from_temp_file(db):
-
leaflet_image = LeafletImage()
with pytest.raises(ValueError) as e_info:
- leaflet_image.set_image_from_temp_file("/not/a/path")
- assert str(e_info.value) == ("Parent Leaflet instance needs to be saved "
- "before a LeafletImage can be created")
+ leaflet_image.set_image_from_temp_file("/not/a/path")
+ assert str(e_info.value) == (
+ "Parent Leaflet instance needs to be saved "
+ "before a LeafletImage can be created"
+ )
def test_image_moved_from_temp_upload(db, uploaded_temp_file):
@@ -67,21 +97,35 @@ def test_image_moved_from_temp_upload(db, uploaded_temp_file):
leaflet_image.save()
leaflet_image.refresh_from_db()
- path = Path(leaflet_image.image.storage.base_location) / f"leaflets/{leaflet.pk}/test-leaflet.jpeg"
+ path = (
+ Path(leaflet_image.image.storage.base_location)
+ / f"leaflets/{leaflet.pk}/test-leaflet.jpeg"
+ )
assert str(path) == leaflet_image.image.path
-def test_image_moved_from_temp_upload_s3_backend(db, settings, s3_bucket, s3_client):
- settings.DEFAULT_FILE_STORAGE = "electionleaflets.storages.TempUploadS3MediaStorage"
+
+def test_image_moved_from_temp_upload_s3_backend(
+ db, settings, s3_bucket, s3_client
+):
+ settings.DEFAULT_FILE_STORAGE = (
+ "electionleaflets.storages.TempUploadS3MediaStorage"
+ )
leaflet = Leaflet()
leaflet.save()
leaflet.refresh_from_db()
leaflet_image = LeafletImage(leaflet=leaflet)
- s3_client.put_object(Key="test_images/test_leaflet.jpeg", Body="", Bucket=settings.AWS_STORAGE_BUCKET_NAME)
+ s3_client.put_object(
+ Key="test_images/test_leaflet.jpeg",
+ Body="",
+ Bucket=settings.AWS_STORAGE_BUCKET_NAME,
+ )
leaflet_image.set_image_from_temp_file("test_images/test_leaflet.jpeg")
leaflet_image.save()
leaflet_image.refresh_from_db()
key = f"leaflets/{leaflet.pk}/test-leaflet.jpeg"
- assert s3_client.head_object(Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=key)
+ assert s3_client.head_object(
+ Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=key
+ )
assert str(key) == leaflet_image.image.file.name
diff --git a/electionleaflets/apps/leaflets/urls.py b/electionleaflets/apps/leaflets/urls.py
index ca671c13..41d68fef 100644
--- a/electionleaflets/apps/leaflets/urls.py
+++ b/electionleaflets/apps/leaflets/urls.py
@@ -1,12 +1,20 @@
from django.contrib.auth.decorators import login_required
from django.urls import re_path
from django.views.decorators.cache import never_cache
-from leaflets.views import (AllImageView, ImageCropView, ImageRotateView,
- ImageView, LatestLeaflets, LeafletModeration,
- LeafletUpdatePublisherView, LeafletUploadWizzard,
- LeafletView, LegacyImageView,
- should_show_date_form, should_show_party_form,
- should_show_person_form)
+from leaflets.views import (
+ AllImageView,
+ ImageCropView,
+ ImageRotateView,
+ ImageView,
+ LatestLeaflets,
+ LeafletModeration,
+ LeafletUpdatePublisherView,
+ LeafletUploadWizzard,
+ LeafletView,
+ LegacyImageView,
+ should_show_date_form,
+ should_show_person_form,
+)
from .forms import DateForm, ImagesForm, PartyForm, PeopleForm, PostcodeForm
@@ -41,7 +49,9 @@
LegacyImageView.as_view(),
name="full_image_legacy",
),
- re_path(r"^(?P\d+)/images/$", AllImageView.as_view(), name="all_images"),
+ re_path(
+ r"^(?P\d+)/images/$", AllImageView.as_view(), name="all_images"
+ ),
re_path(r"^crop/(?P.+)/$", ImageCropView.as_view(), name="crop"),
re_path(r"^rotate/(?P.+)/$", ImageRotateView.as_view(), name="rotate"),
re_path(r"^(?P\d+)/$", LeafletView.as_view(), name="leaflet"),
@@ -51,5 +61,9 @@
name="leaflet_update_publisher_details",
),
re_path(r"^$", LatestLeaflets.as_view(), name="leaflets"),
- re_path(r"^moderate$", login_required(LeafletModeration.as_view()), name="moderate"),
+ re_path(
+ r"^moderate$",
+ login_required(LeafletModeration.as_view()),
+ name="moderate",
+ ),
]
diff --git a/electionleaflets/apps/leaflets/views.py b/electionleaflets/apps/leaflets/views.py
index fbd40c4b..d71a934f 100644
--- a/electionleaflets/apps/leaflets/views.py
+++ b/electionleaflets/apps/leaflets/views.py
@@ -1,30 +1,29 @@
-import json
import datetime
+import json
import random
from urllib.parse import urljoin
-from django.core.files.storage import default_storage
+from braces.views import LoginRequiredMixin, StaffuserRequiredMixin
+from core.helpers import CacheControlMixin
+from django.conf import settings
+from django.contrib import messages
+from django.core.signing import Signer
from django.db import transaction
+from django.http import HttpResponseRedirect
from django.shortcuts import redirect
-from django.http import HttpResponseRedirect, HttpResponseForbidden
-from django.contrib import messages
from django.urls import reverse
-from formtools.wizard.views import NamedUrlSessionWizardView
-from django.core.signing import Signer
-from django.conf import settings
-from django.views.generic import DetailView, ListView, UpdateView, RedirectView
+from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.views.generic.detail import SingleObjectMixin
-from braces.views import StaffuserRequiredMixin, LoginRequiredMixin
+from formtools.wizard.views import NamedUrlSessionWizardView
+from people.models import Person
+from storages.backends.s3boto3 import S3Boto3Storage
-from core.helpers import CacheControlMixin
-from .models import Leaflet, LeafletImage
from .forms import (
LeafletDetailsFrom,
SingleLeafletImageForm,
UpdatePublisherDetails,
)
-from people.models import Person
-from storages.backends.s3boto3 import S3Boto3Storage
+from .models import Leaflet, LeafletImage
class ImageView(CacheControlMixin, UpdateView):
@@ -97,6 +96,7 @@ class LeafletView(CacheControlMixin, DetailView):
template_name = "leaflets/leaflet.html"
model = Leaflet
+
def should_show_party_form(wizard):
party_form = wizard.get_form("party")
postcode_dict = wizard.get_cleaned_data_for_step("postcode")
@@ -104,10 +104,7 @@ def should_show_party_form(wizard):
if not postcode_dict:
return False
postcode = postcode_dict.get("postcode")
- ballot_data = party_form.get_ballot_data_from_ynr(postcode)
- if not ballot_data:
- return False
- return True
+ return party_form.get_ballot_data_from_ynr(postcode)
def should_show_person_form(wizard):
@@ -118,14 +115,14 @@ def should_show_person_form(wizard):
signer = Signer()
selected_party = json.loads(signer.unsign(cleaned_data["party"]))
return selected_party.get("has_candidates", False)
+ return None
def should_show_date_form(wizard):
cleaned_data = wizard.get_cleaned_data_for_step("postcode") or {}
if not cleaned_data:
return False
- if cleaned_data.get("delivered") == "before":
- return True
+ return cleaned_data.get("delivered") == "before"
class LeafletUploadWizzard(NamedUrlSessionWizardView):
@@ -150,14 +147,14 @@ def get_form_initial(self, step):
if step in ["party", "people"]:
postcode = self.get_cleaned_data_for_step("postcode")
if not postcode:
- return
+ return None
ret = {"postcode": postcode.get("postcode")}
try:
date = self.get_cleaned_data_for_step("date")["date"]
except (KeyError, TypeError):
date = datetime.datetime.now()
ret["for_date"] = date
-
+
party_data = self.storage.get_step_data("party")
if not party_data:
return ret
@@ -167,6 +164,7 @@ def get_form_initial(self, step):
"party-party"
]
return ret
+ return None
def get_context_data(self, **kwargs):
context = super(LeafletUploadWizzard, self).get_context_data(**kwargs)
@@ -179,9 +177,6 @@ def get(self, *args, **kwargs):
return HttpResponseRedirect("/")
return super(LeafletUploadWizzard, self).get(*args, **kwargs)
-
-
-
@transaction.atomic
def done(self, form_list, **kwargs):
# Create a new leaflet
@@ -222,38 +217,35 @@ def done(self, form_list, **kwargs):
leaflet.ynr_party_id = party_data["party_id"]
leaflet.ynr_party_name = party_data["party_name"]
- if form_prefix == "people":
- if (
- "people" in form.cleaned_data
- and isinstance(form.cleaned_data["people"], list)
- and form.cleaned_data["people"] != ""
- ):
- leaflet_people = {}
- for person in form.cleaned_data["people"]:
-
- person_data = json.loads(signer.unsign(person))
- if not person_data:
- continue
- leaflet_people[
- person_data["person"]["id"]
- ] = person_data
- person, _ = Person.objects.get_or_create(
- remote_id=person_data["person"]["id"],
- defaults={
- "name": person_data["person"]["name"],
- "source_url": urljoin(
- settings.YNR_BASE_URL,
- f"/person/{person_data['person']['id']}"
- ),
- "source_name": "YNR2017",
- },
- )
-
- leaflet.people = leaflet_people
- leaflet.person_ids = list(leaflet_people.keys())
- leaflet.ballots = [
- c["ballot"] for ynr_id, c in leaflet_people.items()
- ]
+ if (
+ form_prefix == "people"
+ and "people" in form.cleaned_data
+ and isinstance(form.cleaned_data["people"], list)
+ and form.cleaned_data["people"] != ""
+ ):
+ leaflet_people = {}
+ for person in form.cleaned_data["people"]:
+ person_data = json.loads(signer.unsign(person))
+ if not person_data:
+ continue
+ leaflet_people[person_data["person"]["id"]] = person_data
+ person, _ = Person.objects.get_or_create(
+ remote_id=person_data["person"]["id"],
+ defaults={
+ "name": person_data["person"]["name"],
+ "source_url": urljoin(
+ settings.YNR_BASE_URL,
+ f"/person/{person_data['person']['id']}",
+ ),
+ "source_name": "YNR2017",
+ },
+ )
+
+ leaflet.people = leaflet_people
+ leaflet.person_ids = list(leaflet_people.keys())
+ leaflet.ballots = [
+ c["ballot"] for ynr_id, c in leaflet_people.items()
+ ]
leaflet.save()
messages.success(
@@ -276,7 +268,6 @@ def form_valid(self, form):
signer = Signer()
leaflet_people = {}
for person in form.cleaned_data["people"]:
-
person_data = json.loads(signer.unsign(person))
if not person_data:
continue
@@ -287,7 +278,7 @@ def form_valid(self, form):
"name": person_data["person"]["name"],
"source_url": urljoin(
settings.YNR_BASE_URL,
- f"/person/{person_data['person']['id']}"
+ f"/person/{person_data['person']['id']}",
),
"source_name": "YNR2017",
},
@@ -311,7 +302,6 @@ class LeafletModeration(ListView):
queryset = Leaflet.objects.filter(status="draft")[:10]
template_name = "leaflets/moderation_queue.html"
-
def post(self, request):
leaflet = Leaflet.objects.get(pk=self.request.POST.get("leaflet"))
leaflet.status = "live"
diff --git a/electionleaflets/apps/legacy/routers.py b/electionleaflets/apps/legacy/routers.py
index 2f8c245d..6464a69a 100644
--- a/electionleaflets/apps/legacy/routers.py
+++ b/electionleaflets/apps/legacy/routers.py
@@ -35,6 +35,6 @@ def allow_migrate(self, db, model):
"""
if db == "auth_db":
return model._meta.app_label == "legacy"
- elif model._meta.app_label == "legacy":
+ if model._meta.app_label == "legacy":
return False
return None
diff --git a/electionleaflets/apps/parties/views.py b/electionleaflets/apps/parties/views.py
index 9eb32afc..68a399d5 100644
--- a/electionleaflets/apps/parties/views.py
+++ b/electionleaflets/apps/parties/views.py
@@ -1,28 +1,24 @@
-from datetime import datetime
import re
+from datetime import datetime
-from django.http import Http404
-from django.views.generic import DetailView, ListView, TemplateView
+from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Count, Q
-from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-
-from uk_political_parties.models import Party
+from django.http import Http404
+from django.views.generic import ListView, TemplateView
from leaflets.models import Leaflet
+from uk_political_parties.models import Party
class PartyList(ListView):
def get_queryset(self):
-
- queryset = Party.objects.annotate(
- num_leaflets=Count("leaflet")
- ).order_by("-num_leaflets", "party_name")
- return queryset
+ return Party.objects.annotate(num_leaflets=Count("leaflet")).order_by(
+ "-num_leaflets", "party_name"
+ )
template_name = "parties/party_list.html"
class PartyView(TemplateView):
-
def get_context_data(self, **kwargs):
context = super(PartyView, self).get_context_data(**kwargs)
id = re.sub(r"[^0-9]", "", self.kwargs["pk"])
@@ -39,11 +35,10 @@ def get_context_data(self, **kwargs):
paginator = Paginator(qs, 60)
page = self.request.GET.get("page")
- if not page or page == 1:
- if qs:
- context["last_leaflet_days"] = (
- datetime.now() - qs[0].date_uploaded
- ).days
+ if not page or page == 1 and qs:
+ context["last_leaflet_days"] = (
+ datetime.now() - qs[0].date_uploaded
+ ).days
try:
context["party_leaflets"] = paginator.page(page)
diff --git a/electionleaflets/apps/people/management/commands/people_import_ynmp_2015.py b/electionleaflets/apps/people/management/commands/people_import_ynmp_2015.py
index 15d12758..80e4c251 100644
--- a/electionleaflets/apps/people/management/commands/people_import_ynmp_2015.py
+++ b/electionleaflets/apps/people/management/commands/people_import_ynmp_2015.py
@@ -1,15 +1,13 @@
import csv
-import io
import datetime
+import io
import requests
-
+from constituencies.models import Constituency
from django.core.management.base import BaseCommand
-
from elections.models import Election
-from constituencies.models import Constituency
+from people.models import PartyMemberships, Person, PersonConstituencies
from uk_political_parties.models import Party
-from people.models import Person, PartyMemberships, PersonConstituencies
SOURCE = "YNMP2015"
diff --git a/electionleaflets/apps/people/management/commands/people_import_ynr_parl_2017.py b/electionleaflets/apps/people/management/commands/people_import_ynr_parl_2017.py
index 11b7d2dd..931bc4cf 100644
--- a/electionleaflets/apps/people/management/commands/people_import_ynr_parl_2017.py
+++ b/electionleaflets/apps/people/management/commands/people_import_ynr_parl_2017.py
@@ -2,13 +2,11 @@
import datetime
import requests
-
+from constituencies.models import Constituency
from django.core.management.base import BaseCommand
-
from elections.models import Election
-from constituencies.models import Constituency
+from people.models import PartyMemberships, Person, PersonConstituencies
from uk_political_parties.models import Party
-from people.models import Person, PartyMemberships, PersonConstituencies
SOURCE = "YNR2017"
diff --git a/electionleaflets/apps/people/migrations/0001_initial.py b/electionleaflets/apps/people/migrations/0001_initial.py
index 49468356..f73ec0c1 100644
--- a/electionleaflets/apps/people/migrations/0001_initial.py
+++ b/electionleaflets/apps/people/migrations/0001_initial.py
@@ -2,12 +2,11 @@
# Generated by Django 1.9.5 on 2016-04-03 14:56
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
-
initial = True
dependencies = [
diff --git a/electionleaflets/apps/people/migrations/0002_import_ynr_people.py b/electionleaflets/apps/people/migrations/0002_import_ynr_people.py
index f658676e..4f04be12 100644
--- a/electionleaflets/apps/people/migrations/0002_import_ynr_people.py
+++ b/electionleaflets/apps/people/migrations/0002_import_ynr_people.py
@@ -30,7 +30,6 @@ def import_people(apps, _):
class Migration(migrations.Migration):
-
dependencies = [
("leaflets", "0001_initial"),
("people", "0001_initial"),
diff --git a/electionleaflets/apps/people/migrations/0003_remove_duplicates.py b/electionleaflets/apps/people/migrations/0003_remove_duplicates.py
index 28ade828..205d015d 100644
--- a/electionleaflets/apps/people/migrations/0003_remove_duplicates.py
+++ b/electionleaflets/apps/people/migrations/0003_remove_duplicates.py
@@ -26,13 +26,12 @@ def remove_dupes(apps, schema_editor):
collector.collect([person])
collected = collector.nested()
assert len(collected) < 2 or not any(
- [isinstance(x, Leaflet) for x in collected[1]]
+ isinstance(x, Leaflet) for x in collected[1]
)
person.delete()
class Migration(migrations.Migration):
-
dependencies = [
("people", "0001_initial"),
]
diff --git a/electionleaflets/apps/people/migrations/0004_unique_remote_id.py b/electionleaflets/apps/people/migrations/0004_unique_remote_id.py
index 72f963dc..22ed3b4c 100644
--- a/electionleaflets/apps/people/migrations/0004_unique_remote_id.py
+++ b/electionleaflets/apps/people/migrations/0004_unique_remote_id.py
@@ -6,7 +6,6 @@
class Migration(migrations.Migration):
-
dependencies = [
("people", "0003_remove_duplicates"),
]
diff --git a/electionleaflets/apps/people/migrations/0005_merge_20210425_1928.py b/electionleaflets/apps/people/migrations/0005_merge_20210425_1928.py
index f5b21cac..ce62cbf1 100644
--- a/electionleaflets/apps/people/migrations/0005_merge_20210425_1928.py
+++ b/electionleaflets/apps/people/migrations/0005_merge_20210425_1928.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
("people", "0002_import_ynr_people"),
("people", "0004_unique_remote_id"),
diff --git a/electionleaflets/apps/people/models.py b/electionleaflets/apps/people/models.py
index ae2e62e4..3881f4b7 100644
--- a/electionleaflets/apps/people/models.py
+++ b/electionleaflets/apps/people/models.py
@@ -1,8 +1,7 @@
-from django.db import models
-
from constituencies.models import Constituency
-from uk_political_parties.models import Party
+from django.db import models
from elections.models import Election
+from uk_political_parties.models import Party
class Person(models.Model):
@@ -24,6 +23,7 @@ def current_party(self):
parties = self.partymemberships_set.filter(membership_end=None)
if parties:
return parties[0]
+ return None
@property
def current_election(self):
diff --git a/electionleaflets/apps/people/tests/test_models.py b/electionleaflets/apps/people/tests/test_models.py
index 2696903b..c1bd3754 100644
--- a/electionleaflets/apps/people/tests/test_models.py
+++ b/electionleaflets/apps/people/tests/test_models.py
@@ -1,57 +1,76 @@
import pytest
-from people.models import Person, PartyMemberships, PersonConstituencies
from constituencies.models import Constituency
-from uk_political_parties.models import Party
from elections.models import Election
+from people.models import PartyMemberships, Person, PersonConstituencies
+from uk_political_parties.models import Party
+
@pytest.fixture
def person():
return Person.objects.create(name="John Doe")
+
@pytest.fixture
def party():
return Party.objects.create(party_name="Test Party")
+
@pytest.fixture
def constituency():
return Constituency.objects.create(name="Test Constituency")
+
@pytest.fixture
def election():
- return Election.objects.create(name="Test Election", live_date="2023-01-01", dead_date="2023-01-01")
+ return Election.objects.create(
+ name="Test Election", live_date="2023-01-01", dead_date="2023-01-01"
+ )
@pytest.mark.django_db
def test_create_party_membership(person, party):
- membership = PartyMemberships.objects.create(person=person, party=party, membership_start="2023-01-01")
+ membership = PartyMemberships.objects.create(
+ person=person, party=party, membership_start="2023-01-01"
+ )
assert membership.person == person
assert membership.party == party
assert membership.membership_start == "2023-01-01"
assert membership.membership_end is None
+
@pytest.mark.django_db
def test_create_person_constituency(person, constituency, election):
- person_constituency = PersonConstituencies.objects.create(person=person, constituency=constituency, election=election)
+ person_constituency = PersonConstituencies.objects.create(
+ person=person, constituency=constituency, election=election
+ )
assert person_constituency.person == person
assert person_constituency.constituency == constituency
assert person_constituency.election == election
+
@pytest.mark.django_db
def test_person_current_party(person, party):
- PartyMemberships.objects.create(person=person, party=party, membership_start="2023-01-01")
+ PartyMemberships.objects.create(
+ person=person, party=party, membership_start="2023-01-01"
+ )
assert person.current_party.party == party
+
@pytest.mark.django_db
def test_person_current_election(person, election):
person.elections.add(election)
assert person.current_election == election
+
@pytest.mark.django_db
def test_person_current_constituency(person, constituency, election):
- PersonConstituencies.objects.create(person=person, constituency=constituency, election=election)
+ PersonConstituencies.objects.create(
+ person=person, constituency=constituency, election=election
+ )
person.elections.add(election)
assert person.current_constituency == constituency
+
@pytest.mark.django_db
def test_person_unicode(person):
- assert str(person.name) == "John Doe"
\ No newline at end of file
+ assert str(person.name) == "John Doe"
diff --git a/electionleaflets/apps/people/views.py b/electionleaflets/apps/people/views.py
index 818be64b..a42f1a92 100644
--- a/electionleaflets/apps/people/views.py
+++ b/electionleaflets/apps/people/views.py
@@ -1,11 +1,9 @@
from datetime import datetime
+from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.views.generic import DetailView
-from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-
-
-from people.models import Person
from leaflets.models import Leaflet
+from people.models import Person
class PersonView(DetailView):
@@ -21,11 +19,10 @@ def get_context_data(self, **kwargs):
paginator = Paginator(qs, 60)
page = self.request.GET.get("page")
- if not page or page == 1:
- if qs:
- context["last_leaflet_days"] = (
- datetime.now() - qs[0].date_uploaded
- ).days
+ if not page or page == 1 and qs:
+ context["last_leaflet_days"] = (
+ datetime.now() - qs[0].date_uploaded
+ ).days
try:
context["person_leaflets"] = paginator.page(page)
diff --git a/electionleaflets/lambda_awsgi.py b/electionleaflets/lambda_awsgi.py
index 56cfdddb..286ff75a 100644
--- a/electionleaflets/lambda_awsgi.py
+++ b/electionleaflets/lambda_awsgi.py
@@ -9,7 +9,9 @@
path.append(SITE_ROOT)
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "electionleaflets.settings.base_lambda")
+os.environ.setdefault(
+ "DJANGO_SETTINGS_MODULE", "electionleaflets.settings.base_lambda"
+)
application = get_wsgi_application()
@@ -27,4 +29,4 @@ def handler(event, context):
"image/jpg",
"font/woff2",
},
- )
\ No newline at end of file
+ )
diff --git a/electionleaflets/settings/base.py b/electionleaflets/settings/base.py
index c9d5eb00..d5bc0dbb 100644
--- a/electionleaflets/settings/base.py
+++ b/electionleaflets/settings/base.py
@@ -1,9 +1,12 @@
+import os
import sys
from os import environ
-import os
# PATH vars
-from os.path import join, abspath, dirname
+from os.path import abspath, dirname, join
+
+import dc_design_system
+
def here(x):
return join(abspath(dirname(__file__)), x)
@@ -23,7 +26,7 @@ def root(x):
template_DEBUG = DEBUG
TEMPLATE_DEBUG = DEBUG
-DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# DATABASES define in environment specific settings file
DATABASES = {
@@ -39,7 +42,9 @@ def root(x):
ALLOWED_HOSTS = ["*"]
-MEDIA_ROOT = root("media",)
+MEDIA_ROOT = root(
+ "media",
+)
MEDIA_URL = "/media/"
STATIC_ROOT = root("static")
STATIC_URL = "/static/"
@@ -67,7 +72,9 @@ def root(x):
"scss/vendor/filepond-plugin-image-preview.css",
],
"output_filename": "scss/styles.css",
- "extra_context": {"media": "screen,projection",},
+ "extra_context": {
+ "media": "screen,projection",
+ },
},
},
"JAVASCRIPT": {
@@ -89,7 +96,6 @@ def root(x):
PIPELINE["CSS_COMPRESSOR"] = "pipeline.compressors.NoopCompressor"
PIPELINE["JS_COMPRESSOR"] = "pipeline.compressors.NoopCompressor"
-import dc_design_system
PIPELINE["SASS_ARGUMENTS"] = (
" -I " + dc_design_system.DC_SYSTEM_PATH + "/system"
@@ -200,7 +206,9 @@ def setup_sentry(environment=None):
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
- "DIRS": [root("templates"),],
+ "DIRS": [
+ root("templates"),
+ ],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
@@ -244,7 +252,7 @@ def setup_sentry(environment=None):
YNR_API_KEY = None
YNR_BASE_URL = "https://candidates.democracyclub.org.uk"
-if not "testing" in environ.get("DJANGO_SETTINGS_MODULE", ""):
+if "testing" not in environ.get("DJANGO_SETTINGS_MODULE", ""):
# .local.py overrides all the common settings.
try:
from .local import * # noqa: F401,F403
diff --git a/electionleaflets/settings/base_lambda.py b/electionleaflets/settings/base_lambda.py
index 297bda9e..40ec0e6f 100644
--- a/electionleaflets/settings/base_lambda.py
+++ b/electionleaflets/settings/base_lambda.py
@@ -1,9 +1,10 @@
import os
from django.urls import set_urlconf
+
from .base import * # noqa: F401,F403
-ALLOWED_HOSTS = [os.environ.get('APP_DOMAIN')]
+ALLOWED_HOSTS = [os.environ.get("APP_DOMAIN")]
DATABASES = {
"default": {
@@ -30,11 +31,13 @@
STATIC_URL = WHITENOISE_STATIC_PREFIX
STATICFILES_STORAGE = "electionleaflets.storages.StaticStorage"
-STATICFILES_DIRS = (root("assets"),)
-STATIC_ROOT = root("static")
-MEDIA_ROOT = root("media",)
+STATICFILES_DIRS = (root("assets"),) # noqa: F405
+STATIC_ROOT = root("static") # noqa: F405
+MEDIA_ROOT = root( # noqa: F405
+ "media",
+)
MEDIA_URL = "/media/"
-set_urlconf(ROOT_URLCONF)
+set_urlconf(ROOT_URLCONF) # noqa: F405
AWS_DEFAULT_ACL = "public-read"
@@ -63,4 +66,4 @@
CSRF_TRUSTED_ORIGINS = ["https://electionleaflets.org"]
USE_X_FORWARDED_HOST = True
-setup_sentry()
+setup_sentry() # noqa: F405
diff --git a/electionleaflets/settings/testing.py b/electionleaflets/settings/testing.py
index bb477ca4..36055460 100644
--- a/electionleaflets/settings/testing.py
+++ b/electionleaflets/settings/testing.py
@@ -2,6 +2,7 @@
from tempfile import mkdtemp
from .base import * # noqa: F403
+
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
DEFAULT_FILE_STORAGE = "electionleaflets.storages.TempUploadLocalMediaStorage"
diff --git a/electionleaflets/storages.py b/electionleaflets/storages.py
index 81dca31c..255a38b6 100644
--- a/electionleaflets/storages.py
+++ b/electionleaflets/storages.py
@@ -1,6 +1,6 @@
import abc
from pathlib import Path
-from urllib.parse import urlsplit, unquote, urlunsplit
+from urllib.parse import unquote, urlsplit, urlunsplit
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
from django.core.files.storage import FileSystemStorage, default_storage
@@ -53,8 +53,7 @@ class TempUploadBaseMixin(abc.ABC):
"""
@abc.abstractmethod
- def save_from_temp_upload(self, source_path, target_file_path):
- ...
+ def save_from_temp_upload(self, source_path, target_file_path): ...
class TempUploadS3MediaStorage(TempUploadBaseMixin, S3Boto3Storage):
@@ -70,10 +69,13 @@ def copy_file(self, source_path, target_file_path):
moved_file.copy(copy_source)
return moved_file
+
class TempUploadLocalMediaStorage(TempUploadBaseMixin, FileSystemStorage):
def save_from_temp_upload(self, source_path, target_file_path: Path):
target_file_path = Path(default_storage.path(target_file_path))
target_file_path.parent.mkdir(parents=True, exist_ok=True)
- with open(default_storage.path(source_path), "rb") as source_file:
- with target_file_path.open("wb") as dest_file:
- dest_file.write(source_file.read())
+ with (
+ open(default_storage.path(source_path), "rb") as source_file,
+ target_file_path.open("wb") as dest_file,
+ ):
+ dest_file.write(source_file.read())
diff --git a/electionleaflets/urls.py b/electionleaflets/urls.py
index 7ace6280..e3f4496c 100644
--- a/electionleaflets/urls.py
+++ b/electionleaflets/urls.py
@@ -1,17 +1,21 @@
import debug_toolbar
from django.conf import settings
-from django.contrib import admin
from django.conf.urls.static import static
-from django.urls import path, re_path, include
-from django.views.generic import TemplateView, RedirectView
+from django.contrib import admin
+from django.urls import include, path, re_path
+from django.views.generic import RedirectView, TemplateView
from electionleaflets.apps.api.feeds import ConstituencyFeed, LatestLeafletsFeed
-from electionleaflets.apps.core.views import HomeView, MaintenanceView, ReportThanksView, ReportView
+from electionleaflets.apps.core.views import (
+ HomeView,
+ MaintenanceView,
+ ReportThanksView,
+ ReportView,
+)
admin.autodiscover()
-
MAINTENANCE_MODE = getattr(settings, "MAINTENANCE_MODE", False)
if MAINTENANCE_MODE:
urlpatterns = [
@@ -26,9 +30,7 @@
re_path(r"^person/", include("people.urls")),
re_path(r"^api/", include("api.urls")),
# Feeds
- re_path(
- r"^feeds/latest/$", LatestLeafletsFeed(), name="latest_feed"
- ),
+ re_path(r"^feeds/latest/$", LatestLeafletsFeed(), name="latest_feed"),
re_path(
r"^feeds/constituency/(?P[\w_\-\.]+)/$",
ConstituencyFeed(),
@@ -60,16 +62,18 @@
),
# Administration URLS
path("admin/", admin.site.urls),
- path('__debug__/', include(debug_toolbar.urls)),
+ path("__debug__/", include(debug_toolbar.urls)),
]
# Old redirects
+
class HomePageRedirectView(RedirectView):
"""
Just redirect to the home page.
"""
+
permanent = True
query_string = True
pattern_name = "home"
@@ -77,9 +81,7 @@ class HomePageRedirectView(RedirectView):
redirect_urls = (
re_path(r"^analysis", HomePageRedirectView.as_view(), name="analysis"),
- re_path(
- r"^start/$", HomePageRedirectView.as_view(), name="analysis_start"
- ),
+ re_path(r"^start/$", HomePageRedirectView.as_view(), name="analysis_start"),
re_path(
r"^tag_candidates/$",
HomePageRedirectView.as_view(),
diff --git a/electionleaflets/wsgi.py b/electionleaflets/wsgi.py
index ad92db6c..c389ff28 100644
--- a/electionleaflets/wsgi.py
+++ b/electionleaflets/wsgi.py
@@ -8,7 +8,9 @@
path.append(SITE_ROOT)
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "electionleaflets.settings.base_lambda")
+os.environ.setdefault(
+ "DJANGO_SETTINGS_MODULE", "electionleaflets.settings.base_lambda"
+)
-application = get_wsgi_application()
\ No newline at end of file
+application = get_wsgi_application()
diff --git a/pyproject.toml b/pyproject.toml
index f5a516fe..52de6319 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,3 +12,14 @@ DJANGO_SETTINGS_MODULE = "electionleaflets.settings.testing"
python_files = ["test_*.py", "*_test.py"]
addopts = "--reuse-db --tb=short -p no:warnings"
django_debug_mode = true
+
+[tool.ruff]
+line-length = 80
+lint.ignore = ["E501"]
+lint.extend-select = [
+ "I",
+ "C4",
+ "SIM",
+ "Q003",
+ "RET",
+]
diff --git a/scripts/format.sh b/scripts/format.sh
new file mode 100755
index 00000000..0f425227
--- /dev/null
+++ b/scripts/format.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -euxo pipefail
+
+pipenv run ruff format . --check
diff --git a/scripts/lint.sh b/scripts/lint.sh
new file mode 100755
index 00000000..a7e4d875
--- /dev/null
+++ b/scripts/lint.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -euxo pipefail
+
+pipenv run ruff check .
diff --git a/thumbs/attach_lambda_triggers.py b/thumbs/attach_lambda_triggers.py
index c9be3632..9026cbba 100644
--- a/thumbs/attach_lambda_triggers.py
+++ b/thumbs/attach_lambda_triggers.py
@@ -1,7 +1,7 @@
import os
import boto3
-from botocore.exceptions import BotoCoreError
+from botocore.exceptions import ClientError
IMAGES_BUCKET_NAME = os.environ.get("LEAFLET_IMAGES_BUCKET_NAME")
IMAGES_URL = f"images.{os.environ.get('PUBLIC_FQDN')}"
@@ -17,12 +17,13 @@ def get_thumbs_function(lambda_client):
f"ElectionLeafletsThumbs-{ENVIRONMENT}"
):
return function
+ return None
def policy_exists(arn):
try:
policy = lambda_client.get_policy(FunctionName=function_arn)
- except:
+ except ClientError:
return False
return "s3_thumbs" in policy["Policy"]
@@ -48,7 +49,10 @@ def policy_exists(arn):
"Filter": {
"Key": {
"FilterRules": [
- {"Name": "prefix", "Value": "leaflets/",},
+ {
+ "Name": "prefix",
+ "Value": "leaflets/",
+ },
{"Name": "Suffix", "Value": ""},
]
}
@@ -67,6 +71,7 @@ def get_dist(cf_client):
for dist in cf_client.list_distributions()["DistributionList"]["Items"]:
if IMAGES_URL in dist["Aliases"]["Items"]:
return dist
+ return None
dist = get_dist(cf_client)
diff --git a/thumbs/handler.py b/thumbs/handler.py
index 23610db2..3487ed66 100644
--- a/thumbs/handler.py
+++ b/thumbs/handler.py
@@ -1,31 +1,30 @@
import base64
-import os
-from io import BytesIO
-from os.path import basename, dirname, splitext, sep
-from os import makedirs
import re
import sys
+from io import BytesIO
+from os import makedirs
+from os.path import dirname, sep
sys.path.append("")
from typing import Tuple
from urllib.parse import unquote
import boto3
-
import django
from django.conf import settings
-settings.configure(THUMBNAIL_KVSTORE="thumbs.PassthruKVStore",)
-BUCKET_NAME = open("LEAFLET_IMAGES_BUCKET_NAME").read().strip()
+settings.configure(
+ THUMBNAIL_KVSTORE="thumbs.PassthruKVStore",
+)
+with open("LEAFLET_IMAGES_BUCKET_NAME") as f:
+ BUCKET_NAME = f.read().strip()
django.setup()
-from PIL import Image, ImageOps
-from sorl.thumbnail import get_thumbnail
-from sorl.thumbnail import conf as sorl_conf
-from sorl.thumbnail.base import ThumbnailBackend
-from sorl.thumbnail.engines import pil_engine
-from sorl.thumbnail.parsers import parse_crop, parse_geometry
-
+# These need to be imported after Django has bootstrapped
+from PIL import Image # noqa: E402
+from sorl.thumbnail.base import ThumbnailBackend # noqa: E402
+from sorl.thumbnail.engines import pil_engine # noqa: E402
+from sorl.thumbnail.parsers import parse_geometry # noqa: E402
client = boto3.client("s3")
engine = pil_engine.Engine()
@@ -34,8 +33,9 @@
def handle(event, context):
if "s3" in event["Records"][0]:
return handle_s3(event, context)
- elif "cf" in event["Records"][0]:
+ if "cf" in event["Records"][0]:
return handle_cf(event, context)
+ return None
def handle_cf(event, context):