Merge branch 'main' into za/1848-copy-contact-email-to-clipboard

This commit is contained in:
zandercymatics 2024-04-04 08:34:01 -06:00
commit 48410c7aef
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
22 changed files with 672 additions and 172 deletions

View file

@ -31,6 +31,7 @@ gevent = "*"
fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"}
pyzipper="*" pyzipper="*"
tblib = "*" tblib = "*"
django-admin-multiple-choice-list-filter = "*"
[dev-packages] [dev-packages]
django-debug-toolbar = "*" django-debug-toolbar = "*"

247
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "082a951f15bb26a28f2dca7e0840fdf61518b3d90c42d77a310f982344cbd1dc" "sha256": "52143c73ccc59cd3dd6a1294a9352dbae009ebfc6e3ca5d018b8484275e2b6f8"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -24,28 +24,28 @@
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47",
"sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed" "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.8'",
"version": "==3.7.2" "version": "==3.8.1"
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:300888f0c1b6f32f27f85a9aa876f50f46514ec619647af7e4d20db74d339714", "sha256:7ce8c9a50af2f8a159a0dd86b40011d8dfdaba35005a118e51cd3ac72dc630f1",
"sha256:b26928f9a21cf3649cea20a59061340f3294c6e7785ceb6e1a953eb8010dc3ba" "sha256:d786e7fbe3c4152866199786468a625dc77b9f27294cd7ad4f63cd2e0c927287"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.34.56" "version": "==1.34.71"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:bffeb71ab21d47d4ecf947d9bdb2fbd1b0bbd0c27742cea7cf0b77b701c41d9f", "sha256:3bc9e23aee73fe6f097823d61f79a8877790436038101a83fa96c7593e8109f8",
"sha256:fff66e22a5589c2d58fba57d1d95c334ce771895e831f80365f6cff6453285ec" "sha256:c58f9ed71af2ea53d24146187130541222d7de8c27eb87d23f15457e7b83d88b"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.34.56" "version": "==1.34.71"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -295,6 +295,14 @@
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==4.2.10" "version": "==4.2.10"
}, },
"django-admin-multiple-choice-list-filter": {
"hashes": [
"sha256:a5b82682ff752cf74bbc4fa3d562c99b9f9f80389961017e1ac63e08f22d8b9f",
"sha256:eab2ad5fad6df8ed8436cb6c1d0e67863453a9d30282b7fdcb7e45b2eb37ab6f"
],
"index": "pypi",
"version": "==0.1.1"
},
"django-allow-cidr": { "django-allow-cidr": {
"hashes": [ "hashes": [
"sha256:11126c5bb9df3a61ff9d97304856ba7e5b26d46c6d456709a6d9e28483bff47f", "sha256:11126c5bb9df3a61ff9d97304856ba7e5b26d46c6d456709a6d9e28483bff47f",
@ -384,12 +392,12 @@
}, },
"faker": { "faker": {
"hashes": [ "hashes": [
"sha256:2456d674f40bd51eb3acbf85221277027822e529a90cc826453d9a25dff932b1", "sha256:998c29ee7d64429bd59204abffa9ba11f784fb26c7b9df4def78d1a70feb36a7",
"sha256:ea6f784c40730de0f77067e49e78cdd590efb00bec3d33f577492262206c17fc" "sha256:a5ddccbe97ab691fad6bd8036c31f5697cfaa550e62e000078d1935fa8a7ec2e"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==24.0.0" "version": "==24.4.0"
}, },
"fred-epplib": { "fred-epplib": {
"git": "https://github.com/cisagov/epplib.git", "git": "https://github.com/cisagov/epplib.git",
@ -732,18 +740,18 @@
}, },
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
"sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==23.2" "version": "==24.0"
}, },
"phonenumberslite": { "phonenumberslite": {
"hashes": [ "hashes": [
"sha256:137d53d5d78dca30bc2becf81a3e2ac74deb8f0997e9bbe44de515ece4bd92bd", "sha256:4d92f4f9079bb83588dde45fd8a414bc13e4962886aa4d23576984196f4d83c2",
"sha256:e1f4359bff90c86d1b52db0e726d3334df00cc7d9c9c2ef66561d5f7a774d4ba" "sha256:7426bc46af3de5a800a4c8f33ab13e33225d2c8ed4fc52aa3c0380dadd8d7381"
], ],
"version": "==8.13.31" "version": "==8.13.33"
}, },
"psycopg2-binary": { "psycopg2-binary": {
"hashes": [ "hashes": [
@ -872,11 +880,11 @@
}, },
"pydantic": { "pydantic": {
"hashes": [ "hashes": [
"sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a", "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6",
"sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f" "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==2.6.3" "version": "==2.6.4"
}, },
"pydantic-core": { "pydantic-core": {
"hashes": [ "hashes": [
@ -1014,19 +1022,19 @@
}, },
"s3transfer": { "s3transfer": {
"hashes": [ "hashes": [
"sha256:3cdb40f5cfa6966e812209d0994f2a4709b561c88e90cf00c2696d2df4e56b2e", "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19",
"sha256:d0c8bbf672d5eebbe4e57945e23b972d963f07d82f661cabf678a5c88831595b" "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==0.10.0" "version": "==0.10.1"
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56", "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e",
"sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8" "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==69.1.1" "version": "==69.2.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -1064,11 +1072,11 @@
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.8'",
"version": "==2.0.7" "version": "==2.2.1"
}, },
"whitenoise": { "whitenoise": {
"hashes": [ "hashes": [
@ -1133,20 +1141,20 @@
"develop": { "develop": {
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47",
"sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed" "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.8'",
"version": "==3.7.2" "version": "==3.8.1"
}, },
"bandit": { "bandit": {
"hashes": [ "hashes": [
"sha256:17e60786a7ea3c9ec84569fd5aee09936d116cb0cb43151023258340dbffb7ed", "sha256:36de50f720856ab24a24dbaa5fee2c66050ed97c1477e0a1159deab1775eab6b",
"sha256:527906bec6088cb499aae31bc962864b4e77569e9d529ee51df3a93b4b8ab28a" "sha256:509f7af645bc0cd8fd4587abc1a038fc795636671ee8204d502b933aee44f381"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.7.7" "version": "==1.7.8"
}, },
"beautifulsoup4": { "beautifulsoup4": {
"hashes": [ "hashes": [
@ -1158,32 +1166,32 @@
}, },
"black": { "black": {
"hashes": [ "hashes": [
"sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8", "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f",
"sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8", "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93",
"sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd", "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11",
"sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9", "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0",
"sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31", "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9",
"sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92", "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5",
"sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f", "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213",
"sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29", "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d",
"sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4", "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7",
"sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693", "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837",
"sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218", "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f",
"sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a", "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395",
"sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23", "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995",
"sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0", "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f",
"sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982", "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597",
"sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894", "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959",
"sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540", "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5",
"sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430", "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb",
"sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b", "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4",
"sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2", "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7",
"sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6", "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd",
"sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d" "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==24.2.0" "version": "==24.3.0"
}, },
"blinker": { "blinker": {
"hashes": [ "hashes": [
@ -1195,12 +1203,12 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:300888f0c1b6f32f27f85a9aa876f50f46514ec619647af7e4d20db74d339714", "sha256:7ce8c9a50af2f8a159a0dd86b40011d8dfdaba35005a118e51cd3ac72dc630f1",
"sha256:b26928f9a21cf3649cea20a59061340f3294c6e7785ceb6e1a953eb8010dc3ba" "sha256:d786e7fbe3c4152866199786468a625dc77b9f27294cd7ad4f63cd2e0c927287"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.34.56" "version": "==1.34.71"
}, },
"boto3-mocking": { "boto3-mocking": {
"hashes": [ "hashes": [
@ -1213,28 +1221,28 @@
}, },
"boto3-stubs": { "boto3-stubs": {
"hashes": [ "hashes": [
"sha256:627f8eca69d832581ee1676d39df099a2a2e3a86d6b3ebd21c81c5f11ed6a6fa", "sha256:1be579780a39a75394db0c413594aec380afde86dbc7eb5eb086a97a25d3c995",
"sha256:a87e7ecbab6235ec371b4363027e57483bca349a9cd5c891f40db81dadfa273e" "sha256:c9959c48ee1b53e9d19e424898caacf365aaee34cee4c70f40692e2b47bc8099"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.34.56" "version": "==1.34.71"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:bffeb71ab21d47d4ecf947d9bdb2fbd1b0bbd0c27742cea7cf0b77b701c41d9f", "sha256:3bc9e23aee73fe6f097823d61f79a8877790436038101a83fa96c7593e8109f8",
"sha256:fff66e22a5589c2d58fba57d1d95c334ce771895e831f80365f6cff6453285ec" "sha256:c58f9ed71af2ea53d24146187130541222d7de8c27eb87d23f15457e7b83d88b"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.34.56" "version": "==1.34.71"
}, },
"botocore-stubs": { "botocore-stubs": {
"hashes": [ "hashes": [
"sha256:018e001e3add5eb1828ef444b45fb8c9faf695e08334031bf2d96853cd9af703", "sha256:0c3835c775db1387246c1ba8063b197604462fba8603d9b36b5dc60297197b2f",
"sha256:25468ba6983987b704b1856bb155f297f576e6d4a690b021ab0c7122889ba907" "sha256:463248fd1d6e7b68a0c57bdd758d04c6bd0c5c2c3bfa81afdf9d64f0930b59bc"
], ],
"markers": "python_version >= '3.8' and python_version < '4.0'", "markers": "python_version >= '3.8' and python_version < '4.0'",
"version": "==1.34.56" "version": "==1.34.69"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -1337,37 +1345,37 @@
}, },
"mypy": { "mypy": {
"hashes": [ "hashes": [
"sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6", "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6",
"sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d", "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913",
"sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02", "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129",
"sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d", "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc",
"sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3", "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974",
"sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3", "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374",
"sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3", "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150",
"sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66", "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03",
"sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259", "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9",
"sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835", "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02",
"sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd", "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89",
"sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d", "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2",
"sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8", "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d",
"sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07", "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3",
"sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b", "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612",
"sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e", "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e",
"sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6", "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3",
"sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae", "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e",
"sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9", "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd",
"sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d", "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04",
"sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a", "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed",
"sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592", "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185",
"sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218", "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf",
"sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817", "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b",
"sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4", "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4",
"sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410", "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f",
"sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55" "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.8.0" "version": "==1.9.0"
}, },
"mypy-extensions": { "mypy-extensions": {
"hashes": [ "hashes": [
@ -1387,11 +1395,11 @@
}, },
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
"sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==23.2" "version": "==24.0"
}, },
"pathspec": { "pathspec": {
"hashes": [ "hashes": [
@ -1516,11 +1524,11 @@
}, },
"s3transfer": { "s3transfer": {
"hashes": [ "hashes": [
"sha256:3cdb40f5cfa6966e812209d0994f2a4709b561c88e90cf00c2696d2df4e56b2e", "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19",
"sha256:d0c8bbf672d5eebbe4e57945e23b972d963f07d82f661cabf678a5c88831595b" "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==0.10.0" "version": "==0.10.1"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -1589,19 +1597,20 @@
}, },
"types-pyyaml": { "types-pyyaml": {
"hashes": [ "hashes": [
"sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062", "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342",
"sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24" "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6"
], ],
"version": "==6.0.12.12" "markers": "python_version >= '3.8'",
"version": "==6.0.12.20240311"
}, },
"types-requests": { "types-requests": {
"hashes": [ "hashes": [
"sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b", "sha256:47872893d65a38e282ee9f277a4ee50d1b28bd592040df7d1fdaffdf3779937d",
"sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5" "sha256:b1c1b66abfb7fa79aae09097a811c4aa97130eb8831c60e47aee4ca344731ca5"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==2.31.0.20240218" "version": "==2.31.0.20240311"
}, },
"types-s3transfer": { "types-s3transfer": {
"hashes": [ "hashes": [
@ -1622,11 +1631,11 @@
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.8'",
"version": "==2.0.7" "version": "==2.2.1"
}, },
"waitress": { "waitress": {
"hashes": [ "hashes": [

View file

@ -27,6 +27,7 @@ from django_fsm import TransitionNotAllowed # type: ignore
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.html import escape from django.utils.html import escape
from django.contrib.auth.forms import UserChangeForm, UsernameField from django.contrib.auth.forms import UserChangeForm, UsernameField
from django_admin_multiple_choice_list_filter.list_filters import MultipleChoiceListFilter
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -994,6 +995,18 @@ class DomainRequestAdmin(ListHeaderAdmin):
"""Custom domain requests admin class.""" """Custom domain requests admin class."""
form = DomainRequestAdminForm form = DomainRequestAdminForm
change_form_template = "django/admin/domain_request_change_form.html"
class StatusListFilter(MultipleChoiceListFilter):
"""Custom status filter which is a multiple choice filter"""
title = "Status"
parameter_name = "status__in"
template = "django/admin/multiple_choice_list_filter.html"
def lookups(self, request, model_admin):
return DomainRequest.DomainRequestStatus.choices
class InvestigatorFilter(admin.SimpleListFilter): class InvestigatorFilter(admin.SimpleListFilter):
"""Custom investigator filter that only displays users with the manager role""" """Custom investigator filter that only displays users with the manager role"""
@ -1055,8 +1068,6 @@ class DomainRequestAdmin(ListHeaderAdmin):
if self.value() == "0": if self.value() == "0":
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None)) return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
change_form_template = "django/admin/domain_request_change_form.html"
# Columns # Columns
list_display = [ list_display = [
"requested_domain", "requested_domain",
@ -1087,7 +1098,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
# Filters # Filters
list_filter = ( list_filter = (
"status", StatusListFilter,
"generic_org_type", "generic_org_type",
"federal_type", "federal_type",
ElectionOfficeFilter, ElectionOfficeFilter,
@ -1370,6 +1381,23 @@ class DomainRequestAdmin(ListHeaderAdmin):
"Cannot edit a domain request with a restricted creator.", "Cannot edit a domain request with a restricted creator.",
) )
def changelist_view(self, request, extra_context=None):
"""
Override changelist_view to set the selected value of status filter.
"""
# use http_referer in order to distinguish between request as a link from another page
# and request as a removal of all filters
http_referer = request.META.get("HTTP_REFERER", "")
# if there are no query parameters in the request
# and the request is the initial request for this view
if not bool(request.GET) and request.path not in http_referer:
# modify the GET of the request to set the selected filter
modified_get = copy.deepcopy(request.GET)
modified_get["status__in"] = "submitted,in review,action needed"
request.GET = modified_get
response = super().changelist_view(request, extra_context=extra_context)
return response
def change_view(self, request, object_id, form_url="", extra_context=None): def change_view(self, request, object_id, form_url="", extra_context=None):
obj = self.get_object(request, object_id) obj = self.get_object(request, object_id)
self.display_restricted_warning(request, obj) self.display_restricted_warning(request, obj)
@ -1425,6 +1453,13 @@ class DomainInformationInline(admin.StackedInline):
"submitter", "submitter",
] ]
def has_change_permission(self, request, obj=None):
"""Custom has_change_permission override so that we can specify that
analysts can edit this through this inline, but not through the model normally"""
if request.user.has_perm("registrar.analyst_access_permission"):
return True
return super().has_change_permission(request, obj)
def formfield_for_manytomany(self, db_field, request, **kwargs): def formfield_for_manytomany(self, db_field, request, **kwargs):
"""customize the behavior of formfields with manytomany relationships. the customized """customize the behavior of formfields with manytomany relationships. the customized
behavior includes sorting of objects in lists as well as customizing helper text""" behavior includes sorting of objects in lists as well as customizing helper text"""

View file

@ -487,3 +487,60 @@ function enableRelatedWidgetButtons(changeLink, deleteLink, viewLink, elementPk,
}); });
observer.observe({ type: "navigation" }); observer.observe({ type: "navigation" });
})(); })();
/** An IIFE for toggling the submit bar on domain request forms
*/
(function (){
// Get a reference to the button element
const toggleButton = document.getElementById('submitRowToggle');
const submitRowWrapper = document.querySelector('.submit-row-wrapper');
if (toggleButton) {
// Add event listener to toggle the class and update content on click
toggleButton.addEventListener('click', function() {
// Toggle the 'collapsed' class on the bar
submitRowWrapper.classList.toggle('submit-row-wrapper--collapsed');
// Get a reference to the span element inside the button
const spanElement = this.querySelector('span');
// Get a reference to the use element inside the button
const useElement = this.querySelector('use');
// Check if the span element text is 'Hide'
if (spanElement.textContent.trim() === 'Hide') {
// Update the span element text to 'Show'
spanElement.textContent = 'Show';
// Update the xlink:href attribute to expand_more
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_less');
} else {
// Update the span element text to 'Hide'
spanElement.textContent = 'Hide';
// Update the xlink:href attribute to expand_less
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_more');
}
});
// We have a scroll indicator at the end of the page.
// Observe it. Once it gets on screen, test to see if the row is collapsed.
// If it is, expand it.
const targetElement = document.querySelector(".scroll-indicator");
const options = {
threshold: 1
};
// Create a new Intersection Observer
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Refresh reference to submit row wrapper and check if it's collapsed
if (document.querySelector('.submit-row-wrapper').classList.contains('submit-row-wrapper--collapsed')) {
toggleButton.click();
}
}
});
}, options);
observer.observe(targetElement);
}
})();

View file

@ -42,6 +42,7 @@ html[data-theme="light"] {
--error-fg: #{$theme-color-error}; --error-fg: #{$theme-color-error};
--message-success-bg: #{$theme-color-success-lighter}; --message-success-bg: #{$theme-color-success-lighter};
--checkbox-green: #{$theme-color-success-light};
// $theme-color-warning-lighter - yellow-5 // $theme-color-warning-lighter - yellow-5
--message-warning-bg: #faf3d1; --message-warning-bg: #faf3d1;
--message-error-bg: #{$theme-color-error-lighter}; --message-error-bg: #{$theme-color-error-lighter};
@ -93,6 +94,7 @@ html[data-theme="light"] {
--error-fg: #e35f5f; --error-fg: #e35f5f;
--message-success-bg: #006b1b; --message-success-bg: #006b1b;
--checkbox-green: #006b1b;
--message-warning-bg: #583305; --message-warning-bg: #583305;
--message-error-bg: #570808; --message-error-bg: #570808;
@ -424,6 +426,102 @@ address.dja-address-contact-list {
border: 1px solid var(--error-fg) !important; border: 1px solid var(--error-fg) !important;
} }
.choice-filter {
position: relative;
padding-left: 20px;
svg {
top: 4px;
}
}
.choice-filter--checked {
svg:nth-child(1) {
background: var(--checkbox-green);
fill: var(--checkbox-green);
}
svg:nth-child(2) {
color: var(--body-loud-color);
}
}
// Let's define this block of code once and use it for analysts over a certain screen size,
// superusers over another screen size.
@mixin submit-row-wrapper--collapsed-one-line(){
&.submit-row-wrapper--collapsed {
transform: translate3d(0, 42px, 0);
}
.submit-row {
clear: none;
}
}
// Sticky submit bar for domain requests on desktop
@media screen and (min-width:768px) {
.submit-row-wrapper {
position: fixed;
bottom: 0;
right: 0;
left: 338px;
background: var(--darkened-bg);
border-top-left-radius: 6px;
transition: transform .2s ease-out;
.submit-row {
margin-bottom: 0;
}
}
.submit-row-wrapper--collapsed {
// translate3d is more performant than translateY
// https://stackoverflow.com/questions/22111256/translate3d-vs-translate-performance
transform: translate3d(0, 88px, 0);
}
.submit-row-wrapper--collapsed-one-line {
@include submit-row-wrapper--collapsed-one-line();
}
.submit-row {
clear: both;
}
.submit-row-toggle{
display: inline-block;
position: absolute;
top: -30px;
right: 0;
background: var(--darkened-bg);
}
#submitRowToggle {
color: var(--body-fg);
}
.requested-domain-sticky {
max-width: 325px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.visible-768 {
display: none;
}
@media screen and (min-width:768px) {
.visible-768 {
display: block;
}
}
@media screen and (min-width:935px) {
// Analyst only class
.submit-row-wrapper--analyst-view {
@include submit-row-wrapper--collapsed-one-line();
}
}
@media screen and (min-width:1256px) {
.submit-row-wrapper {
@include submit-row-wrapper--collapsed-one-line();
}
}
// Collapse button styles for fieldsets
.module.collapse { .module.collapse {
margin-top: -35px; margin-top: -35px;
padding-top: 0; padding-top: 0;

View file

@ -117,6 +117,10 @@ abbr[title] {
} }
} }
.visible-desktop {
display: none;
}
@include at-media(desktop) { @include at-media(desktop) {
.float-right-desktop { .float-right-desktop {
float: right; float: right;
@ -124,33 +128,15 @@ abbr[title] {
.float-left-desktop { .float-left-desktop {
float: left; float: left;
} }
.visible-desktop {
display: block;
}
} }
.flex-end { .flex-end {
align-items: flex-end; align-items: flex-end;
} }
// Only apply this custom wrapping to desktop .cursor-pointer {
@include at-media(desktop) { cursor: pointer;
.usa-tooltip__body {
width: 350px;
white-space: normal;
text-align: center;
}
}
@include at-media(tablet) {
.usa-tooltip__body {
width: 250px !important;
white-space: normal !important;
text-align: center !important;
}
}
@include at-media(mobile) {
.usa-tooltip__body {
width: 250px !important;
white-space: normal !important;
text-align: center !important;
}
} }

View file

@ -138,6 +138,13 @@ a.usa-button--unstyled:visited {
} }
} }
.usa-button--unstyled--white,
.usa-button--unstyled--white:hover,
.usa-button--unstyled--white:focus,
.usa-button--unstyled--white:active {
color: color('white');
}
// Cancel button used on the // Cancel button used on the
// DNSSEC main page // DNSSEC main page
// We want to center this button on mobile // We want to center this button on mobile

View file

@ -0,0 +1,26 @@
@use "uswds-core" as *;
// Only apply this custom wrapping to desktop
@include at-media(desktop) {
.usa-tooltip__body {
width: 350px;
white-space: normal;
text-align: center;
}
}
@include at-media(tablet) {
.usa-tooltip__body {
width: 250px !important;
white-space: normal !important;
text-align: center !important;
}
}
@include at-media(mobile) {
.usa-tooltip__body {
width: 250px !important;
white-space: normal !important;
text-align: center !important;
}
}

View file

@ -13,6 +13,7 @@
@forward "lists"; @forward "lists";
@forward "buttons"; @forward "buttons";
@forward "forms"; @forward "forms";
@forward "tooltips";
@forward "fieldsets"; @forward "fieldsets";
@forward "alerts"; @forward "alerts";
@forward "tables"; @forward "tables";

View file

@ -146,6 +146,8 @@ INSTALLED_APPS = [
# "puml_generator", # "puml_generator",
# supports necessary headers for Django cross origin # supports necessary headers for Django cross origin
"corsheaders", "corsheaders",
# library for multiple choice filters in django admin
"django_admin_multiple_choice_list_filter",
] ]
# Middleware are routines for processing web requests. # Middleware are routines for processing web requests.

View file

@ -0,0 +1,37 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
# It is dependent on 0079 (which populates federal agencies)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
# step 3: fake run the latest migration in the migrations list
# [RECOMMENDED]
# Alternatively:
# step 1: duplicate the migration that loads data
# step 2: docker-compose exec app ./manage.py migrate
from django.db import migrations
from registrar.models import UserGroup
from typing import Any
# For linting: RunPython expects a function reference,
# so let's give it one
def create_groups(apps, schema_editor) -> Any:
UserGroup.create_cisa_analyst_group(apps, schema_editor)
UserGroup.create_full_access_group(apps, schema_editor)
class Migration(migrations.Migration):
dependencies = [
("registrar", "0080_create_groups_v09"),
]
operations = [
migrations.RunPython(
create_groups,
reverse_code=migrations.RunPython.noop,
atomic=True,
),
]

View file

@ -26,11 +26,6 @@ class UserGroup(Group):
"model": "contact", "model": "contact",
"permissions": ["change_contact"], "permissions": ["change_contact"],
}, },
{
"app_label": "registrar",
"model": "domaininformation",
"permissions": ["change_domaininformation"],
},
{ {
"app_label": "registrar", "app_label": "registrar",
"model": "domainrequest", "model": "domainrequest",

View file

@ -21,6 +21,8 @@
{% else %} {% else %}
election office = Yes election office = Yes
{% endif %} {% endif %}
{% elif filter_param.parameter_name == 'status__in' %}
status in [{{ filter_param.parameter_value }}]
{% else %} {% else %}
{{ filter_param.parameter_name }} = {{ filter_param.parameter_value }} {{ filter_param.parameter_name }} = {{ filter_param.parameter_value }}
{% endif %} {% endif %}

View file

@ -1,4 +1,5 @@
{% extends 'admin/change_form.html' %} {% extends 'admin/change_form.html' %}
{% load custom_filters %}
{% load i18n static %} {% load i18n static %}
{% block field_sets %} {% block field_sets %}
@ -102,5 +103,25 @@
</button> </button>
</div> </div>
</div> </div>
{{ block.super }}
{# submit-row-wrapper--analyst-view is a class that manages layout on certain screens for analysts only #}
<div class="submit-row-wrapper{% if not request.user|has_permission:'registrar.full_access_permission' %} submit-row-wrapper--analyst-view{% endif %}">
<span class="submit-row-toggle padding-1 padding-right-2 visible-desktop">
<button type="button" class="usa-button usa-button--unstyled" id="submitRowToggle">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#expand_more"></use>
</svg>
<span>Hide</span>
</button>
</span>
<p class="text-right margin-top-2 padding-right-2 margin-bottom-0 requested-domain-sticky float-right visible-768">
<strong>Requested domain:</strong> {{ original.requested_domain.name }}
</p>
{{ block.super }}
</div>
<span class="scroll-indicator"></span>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,37 @@
{% load i18n %}
{% load static field_helpers url_helpers %}
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul class="mulitple-choice">
{% for choice in choices %}
{% if choice.reset %}
<li{% if choice.selected %} class="selected"{% endif %}>
<a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}">{{ choice.display }}</a>
</li>
{% endif %}
{% endfor %}
{% for choice in choices %}
{% if not choice.reset %}
<li{% if choice.selected %} class="selected"{% endif %}>
{% if choice.selected and choice.exclude_query_string %}
<a class="choice-filter choice-filter--checked" href="{{ choice.exclude_query_string|iriencode }}">{{ choice.display }}
<svg class="usa-icon position-absolute z-0 left-0" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#check_box_outline_blank"></use>
</svg>
<svg class="usa-icon position-absolute z-100 left-0" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#check"></use>
</svg>
</a>
{% endif %}
{% if not choice.selected and choice.include_query_string %}
<a class="choice-filter" href="{{ choice.include_query_string|iriencode }}">{{ choice.display }}
<svg class="usa-icon position-absolute z-0 left-0" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#check_box_outline_blank"></use>
</svg>
</a>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>

View file

@ -41,7 +41,7 @@
</div> </div>
</div> </div>
<br> <br>
<p> <b class="review__step__name">Last updated:</b> {{DomainRequest.updated_at|date:"F j, Y"}}<br></p> <p><b class="review__step__name">Last updated:</b> {{DomainRequest.updated_at|date:"F j, Y"}}</p>
<p>{% include "includes/domain_request.html" %}</p> <p>{% include "includes/domain_request.html" %}</p>
<p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline"> <p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
Withdraw request</a> Withdraw request</a>

View file

@ -135,7 +135,7 @@
<td> <td>
{% if invitation.status == invitation.DomainInvitationStatus.INVITED %} {% if invitation.status == invitation.DomainInvitationStatus.INVITED %}
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}"> <form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline" value="Cancel"> {% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline cursor-pointer" value="Cancel">
</form> </form>
{% endif %} {% endif %}
</td> </td>

View file

@ -62,3 +62,8 @@ def get_organization_long_name(generic_org_type):
return "Error" return "Error"
return long_form_type return long_form_type
@register.filter(name="has_permission")
def has_permission(user, permission):
return user.has_perm(permission)

View file

@ -129,6 +129,83 @@ class TestDomainAdmin(MockEppLib, WebTest):
) )
mock_add_message.assert_has_calls([expected_call], 1) mock_add_message.assert_has_calls([expected_call], 1)
@less_console_noise_decorator
def test_analyst_can_see_inline_domain_information_in_domain_change_form(self):
"""Tests if an analyst can still see the inline domain information form"""
# Create fake creator
_creator = User.objects.create(
username="MrMeoward",
first_name="Meoward",
last_name="Jones",
)
# Create a fake domain request
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
# Creates a Domain and DomainInformation object
_domain_request.approve()
domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
domain_information.organization_name = "MonkeySeeMonkeyDo"
domain_information.save()
# We use filter here rather than just domain_information.domain just to get the latest data.
domain = Domain.objects.filter(domain_info=domain_information).get()
p = "userpass"
self.client.login(username="staffuser", password=p)
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Test for data. We only need to test one since its all interconnected.
expected_organization_name = "MonkeySeeMonkeyDo"
self.assertContains(response, expected_organization_name)
@less_console_noise_decorator
def test_admin_can_see_inline_domain_information_in_domain_change_form(self):
"""Tests if an admin can still see the inline domain information form"""
# Create fake creator
_creator = User.objects.create(
username="MrMeoward",
first_name="Meoward",
last_name="Jones",
)
# Create a fake domain request
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
# Creates a Domain and DomainInformation object
_domain_request.approve()
domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
domain_information.organization_name = "MonkeySeeMonkeyDo"
domain_information.save()
# We use filter here rather than just domain_information.domain just to get the latest data.
domain = Domain.objects.filter(domain_info=domain_information).get()
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Test for data. We only need to test one since its all interconnected.
expected_organization_name = "MonkeySeeMonkeyDo"
self.assertContains(response, expected_organization_name)
@patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1)) @patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
def test_extend_expiration_date_button_epp(self, mock_date_today): def test_extend_expiration_date_button_epp(self, mock_date_today):
""" """
@ -706,15 +783,26 @@ class TestDomainRequestAdmin(MockEppLib):
with less_console_noise(): with less_console_noise():
self.client.force_login(self.superuser) self.client.force_login(self.superuser)
completed_domain_request() completed_domain_request()
response = self.client.get("/admin/registrar/domainrequest/") response = self.client.get("/admin/registrar/domainrequest/?generic_org_type__exact=federal")
# There are 4 template references to Federal (4) plus two references in the table # There are 2 template references to Federal (4) and two in the results data
# for our actual domain request # of the request
self.assertContains(response, "Federal", count=34) self.assertContains(response, "Federal", count=34)
# This may be a bit more robust # This may be a bit more robust
self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1) self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1)
# Now let's make sure the long description does not exist # Now let's make sure the long description does not exist
self.assertNotContains(response, "Federal: an agency of the U.S. government") self.assertNotContains(response, "Federal: an agency of the U.S. government")
def test_default_status_in_domain_requests_list(self):
"""
Make sure the default status in admin is selected on the domain requests list page
"""
with less_console_noise():
self.client.force_login(self.superuser)
completed_domain_request()
response = self.client.get("/admin/registrar/domainrequest/")
# The results are filtered by "status in [submitted,in review,action needed]"
self.assertContains(response, "status in [submitted,in review,action needed]", count=1)
def transition_state_and_send_email(self, domain_request, status, rejection_reason=None): def transition_state_and_send_email(self, domain_request, status, rejection_reason=None):
"""Helper method for the email test cases.""" """Helper method for the email test cases."""
@ -1213,6 +1301,61 @@ class TestDomainRequestAdmin(MockEppLib):
self.assertEqual(domain_request.requested_domain.name, domain_request.approved_domain.name) self.assertEqual(domain_request.requested_domain.name, domain_request.approved_domain.name)
@less_console_noise_decorator @less_console_noise_decorator
def test_sticky_submit_row(self):
"""Test that the change_form template contains strings indicative of the customization
of the sticky submit bar.
Also test that it does NOT contain a CSS class meant for analysts only when logged in as superuser."""
# make sure there is no user with this email
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
self.client.force_login(self.superuser)
# Create a sample domain request
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
# Create a mock request
request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
# Since we're using client to mock the request, we can only test against
# non-interpolated values
expected_content = "<strong>Requested domain:</strong>"
expected_content2 = '<span class="scroll-indicator"></span>'
expected_content3 = '<div class="submit-row-wrapper">'
not_expected_content = "submit-row-wrapper--analyst-view>"
self.assertContains(request, expected_content)
self.assertContains(request, expected_content2)
self.assertContains(request, expected_content3)
self.assertNotContains(request, not_expected_content)
@less_console_noise_decorator
def test_sticky_submit_row_has_extra_class_for_analysts(self):
"""Test that the change_form template contains strings indicative of the customization
of the sticky submit bar.
Also test that it DOES contain a CSS class meant for analysts only when logged in as analyst."""
# make sure there is no user with this email
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
self.client.force_login(self.staffuser)
# Create a sample domain request
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
# Create a mock request
request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
# Since we're using client to mock the request, we can only test against
# non-interpolated values
expected_content = "<strong>Requested domain:</strong>"
expected_content2 = '<span class="scroll-indicator"></span>'
expected_content3 = '<div class="submit-row-wrapper submit-row-wrapper--analyst-view">'
self.assertContains(request, expected_content)
self.assertContains(request, expected_content2)
self.assertContains(request, expected_content3)
def test_other_contacts_has_readonly_link(self): def test_other_contacts_has_readonly_link(self):
"""Tests if the readonly other_contacts field has links""" """Tests if the readonly other_contacts field has links"""
@ -1666,7 +1809,7 @@ class TestDomainRequestAdmin(MockEppLib):
# Grab the current list of table filters # Grab the current list of table filters
readonly_fields = self.admin.get_list_filter(request) readonly_fields = self.admin.get_list_filter(request)
expected_fields = ( expected_fields = (
"status", DomainRequestAdmin.StatusListFilter,
"generic_org_type", "generic_org_type",
"federal_type", "federal_type",
DomainRequestAdmin.ElectionOfficeFilter, DomainRequestAdmin.ElectionOfficeFilter,
@ -1962,8 +2105,8 @@ class TestDomainInformationAdmin(TestCase):
# Get the other contact # Get the other contact
other_contact = domain_info.other_contacts.all().first() other_contact = domain_info.other_contacts.all().first()
p = "userpass" p = "adminpass"
self.client.login(username="staffuser", password=p) self.client.login(username="superuser", password=p)
response = self.client.get( response = self.client.get(
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk), "/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
@ -1984,6 +2127,44 @@ class TestDomainInformationAdmin(TestCase):
expected_url = "Testy Tester</a>" expected_url = "Testy Tester</a>"
self.assertContains(response, expected_url) self.assertContains(response, expected_url)
@less_console_noise_decorator
def test_analyst_cant_access_domain_information(self):
"""Ensures that analysts can't directly access the DomainInformation page through /admin"""
# Create fake creator
_creator = User.objects.create(
username="MrMeoward",
first_name="Meoward",
last_name="Jones",
)
# Create a fake domain request
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
domain_request.approve()
domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
p = "userpass"
self.client.login(username="staffuser", password=p)
response = self.client.get(
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
follow=True,
)
# Make sure that we're denied access
self.assertEqual(response.status_code, 403)
# To make sure that its not a fluke, swap to an admin user
# and try to access the same page. This should succeed.
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain_info.domain.name)
@less_console_noise_decorator @less_console_noise_decorator
def test_contact_fields_have_detail_table(self): def test_contact_fields_have_detail_table(self):
"""Tests if the contact fields have the detail table which displays title, email, and phone""" """Tests if the contact fields have the detail table which displays title, email, and phone"""
@ -2007,8 +2188,8 @@ class TestDomainInformationAdmin(TestCase):
domain_request.approve() domain_request.approve()
domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get() domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
p = "userpass" p = "adminpass"
self.client.login(username="staffuser", password=p) self.client.login(username="superuser", password=p)
response = self.client.get( response = self.client.get(
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk), "/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
follow=True, follow=True,

View file

@ -34,7 +34,6 @@ class TestGroups(TestCase):
"view_logentry", "view_logentry",
"change_contact", "change_contact",
"view_domain", "view_domain",
"change_domaininformation",
"add_domaininvitation", "add_domaininvitation",
"view_domaininvitation", "view_domaininvitation",
"change_domainrequest", "change_domainrequest",

View file

@ -1,8 +1,8 @@
-i https://pypi.python.org/simple -i https://pypi.python.org/simple
annotated-types==0.6.0; python_version >= '3.8' annotated-types==0.6.0; python_version >= '3.8'
asgiref==3.7.2; python_version >= '3.7' asgiref==3.8.1; python_version >= '3.8'
boto3==1.34.56; python_version >= '3.8' boto3==1.34.71; python_version >= '3.8'
botocore==1.34.56; python_version >= '3.8' botocore==1.34.71; python_version >= '3.8'
cachetools==5.3.3; python_version >= '3.7' cachetools==5.3.3; python_version >= '3.7'
certifi==2024.2.2; python_version >= '3.6' certifi==2024.2.2; python_version >= '3.6'
cfenv==0.5.3 cfenv==0.5.3
@ -13,6 +13,7 @@ defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1,
dj-database-url==2.1.0 dj-database-url==2.1.0
dj-email-url==1.0.6 dj-email-url==1.0.6
django==4.2.10; python_version >= '3.8' django==4.2.10; python_version >= '3.8'
django-admin-multiple-choice-list-filter==0.1.1
django-allow-cidr==0.7.1 django-allow-cidr==0.7.1
django-auditlog==2.3.0; python_version >= '3.7' django-auditlog==2.3.0; python_version >= '3.7'
django-cache-url==3.4.5 django-cache-url==3.4.5
@ -23,7 +24,7 @@ django-login-required-middleware==0.9.0
django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8' django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8'
django-widget-tweaks==1.5.0; python_version >= '3.8' django-widget-tweaks==1.5.0; python_version >= '3.8'
environs[django]==11.0.0; python_version >= '3.8' environs[django]==11.0.0; python_version >= '3.8'
faker==24.0.0; python_version >= '3.8' faker==24.4.0; python_version >= '3.8'
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
furl==2.1.3 furl==2.1.3
future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
@ -38,12 +39,12 @@ markupsafe==2.1.5; python_version >= '3.7'
marshmallow==3.21.1; python_version >= '3.8' marshmallow==3.21.1; python_version >= '3.8'
oic==1.6.1; python_version ~= '3.7' oic==1.6.1; python_version ~= '3.7'
orderedmultidict==1.0.1 orderedmultidict==1.0.1
packaging==23.2; python_version >= '3.7' packaging==24.0; python_version >= '3.7'
phonenumberslite==8.13.31 phonenumberslite==8.13.33
psycopg2-binary==2.9.9; python_version >= '3.7' psycopg2-binary==2.9.9; python_version >= '3.7'
pycparser==2.21 pycparser==2.21
pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
pydantic==2.6.3; python_version >= '3.8' pydantic==2.6.4; python_version >= '3.8'
pydantic-core==2.16.3; python_version >= '3.8' pydantic-core==2.16.3; python_version >= '3.8'
pydantic-settings==2.2.1; python_version >= '3.8' pydantic-settings==2.2.1; python_version >= '3.8'
pyjwkest==1.4.2 pyjwkest==1.4.2
@ -51,13 +52,13 @@ python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in
python-dotenv==1.0.1; python_version >= '3.8' python-dotenv==1.0.1; python_version >= '3.8'
pyzipper==0.3.6; python_version >= '3.4' pyzipper==0.3.6; python_version >= '3.4'
requests==2.31.0; python_version >= '3.7' requests==2.31.0; python_version >= '3.7'
s3transfer==0.10.0; python_version >= '3.8' s3transfer==0.10.1; python_version >= '3.8'
setuptools==69.1.1; python_version >= '3.8' setuptools==69.2.0; python_version >= '3.8'
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
sqlparse==0.4.4; python_version >= '3.5' sqlparse==0.4.4; python_version >= '3.5'
tblib==3.0.0; python_version >= '3.8' tblib==3.0.0; python_version >= '3.8'
typing-extensions==4.10.0; python_version >= '3.8' typing-extensions==4.10.0; python_version >= '3.8'
urllib3==2.0.7; python_version >= '3.7' urllib3==2.2.1; python_version >= '3.8'
whitenoise==6.6.0; python_version >= '3.8' whitenoise==6.6.0; python_version >= '3.8'
zope.event==5.0; python_version >= '3.7' zope.event==5.0; python_version >= '3.7'
zope.interface==6.2; python_version >= '3.7' zope.interface==6.2; python_version >= '3.7'