Merge branch 'main' into za/1456-sorting-not-working-correctly

This commit is contained in:
zandercymatics 2023-12-21 15:40:26 -07:00
commit fc814fdb43
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
28 changed files with 840 additions and 862 deletions

View file

@ -13,7 +13,9 @@
It is necessary to use `bash -c` because `run pipenv requirements` will not recognize that it is running non-interactively and will include garbage formatting characters.
The requirements.txt is used by Cloud.gov. It is needed to work around a bug in the CloudFoundry buildpack version of Pipenv that breaks on installing from a git repository.
3. (optional) Run `docker-compose stop` and `docker-compose build` to build a new image for local development with the updated dependencies.
3. Change geventconnpool back to what it was originally within the Pipfile.lock and requirements.txt.
This is done by either saving what it was originally or opening a PR and using that as a reference to undo changes to any mention of geventconnpool.
Geventconnpool, when set as a requirement without the reference portion, is defaulting to get a commit from 2014 which then breaks the code, as we want the newest version from them.
4. (optional) Run `docker-compose stop` and `docker-compose build` to build a new image for local development with the updated dependencies.
The reason for de-coupling the `build` and `lock` steps is to increase consistency between builds--a run of `build` will always get exactly the dependencies listed in `Pipfile.lock`, nothing more, nothing less.

499
src/Pipfile.lock generated
View file

@ -32,20 +32,20 @@
},
"boto3": {
"hashes": [
"sha256:02ce7dcad2d3b054cd99e7ca6df7a708e016a31b1c98b46d8df3b3891070c121",
"sha256:b8acb57a124434284d6ab69c61d32d70e84e13e2c27c33b4ad3c32f15ad407d3"
"sha256:d12467fb3a64d359b0bda0570a8163a5859fcac13e786f2a3db0392523178556",
"sha256:eed0f7df91066b6ac63a53d16459ac082458d57061bedf766135d9e1c2b75a6b"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==1.28.79"
"version": "==1.33.7"
},
"botocore": {
"hashes": [
"sha256:07ecb93833475dde68e5c0e02a7ccf8ca22caf68cdc892651c300529894133e1",
"sha256:6f1fc49e9e12f9772b4fef577837670bc84d772a7c946b4d08fe2890e34a4305"
"sha256:71ec0e85b996cf9def3dd8f4ca6cb4a9fd3a614aa4c9c7cbf33f2f68e1d0649a",
"sha256:b2299bc13bb8c0928edc98bf4594deb14cba2357536120f63772027a16ce7374"
],
"markers": "python_version >= '3.7'",
"version": "==1.31.79"
"version": "==1.33.7"
},
"cachetools": {
"hashes": [
@ -58,11 +58,11 @@
},
"certifi": {
"hashes": [
"sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
"sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
"sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1",
"sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"
],
"markers": "python_version >= '3.6'",
"version": "==2023.7.22"
"version": "==2023.11.17"
},
"cfenv": {
"hashes": [
@ -228,32 +228,32 @@
},
"cryptography": {
"hashes": [
"sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf",
"sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84",
"sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e",
"sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8",
"sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7",
"sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1",
"sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88",
"sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86",
"sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179",
"sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81",
"sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20",
"sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548",
"sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d",
"sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d",
"sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5",
"sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1",
"sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147",
"sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936",
"sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797",
"sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696",
"sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72",
"sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da",
"sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"
"sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960",
"sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a",
"sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc",
"sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a",
"sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf",
"sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1",
"sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39",
"sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406",
"sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a",
"sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a",
"sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c",
"sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be",
"sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15",
"sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2",
"sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d",
"sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157",
"sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003",
"sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248",
"sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a",
"sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec",
"sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309",
"sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7",
"sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"
],
"markers": "python_version >= '3.7'",
"version": "==41.0.5"
"version": "==41.0.7"
},
"defusedxml": {
"hashes": [
@ -305,19 +305,19 @@
},
"django-cache-url": {
"hashes": [
"sha256:5ca4760b4580b80e41279bc60d1e5c16a822e4e462265faab0a330701bb0ef9a",
"sha256:ef2cfacea361ee22e9b67d6ca941db22e0a9eaf892b67ca71cad52c62a17fd36"
"sha256:5f350759978483ab85dc0e3e17b3d53eed3394a28148f6bf0f53d11d0feb5b3c",
"sha256:eb9fb194717524348c95cad9905b70b647452741c1d9e481fac6d2125f0ad917"
],
"version": "==3.4.4"
"version": "==3.4.5"
},
"django-cors-headers": {
"hashes": [
"sha256:25aabc94d4837678c1edf442c7f68a5f5fd151f6767b0e0b01c61a2179d02711",
"sha256:bd36c7aea0d070e462f3383f0dc9ef717e5fdc2b10a99c98c285f16da84ffba2"
"sha256:0b1fd19297e37417fc9f835d39e45c8c642938ddba1acce0c1753d3edef04f36",
"sha256:0bf65ef45e606aff1994d35503e6b677c0b26cafff6506f8fd7187f3be840207"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==4.3.0"
"version": "==4.3.1"
},
"django-csp": {
"hashes": [
@ -375,11 +375,11 @@
},
"faker": {
"hashes": [
"sha256:14ccb0aec342d33aa3889a864a56e5b3c2d56bce1b89f9189f4fbc128b9afc1e",
"sha256:da880a76322db7a879c848a0771e129338e0a680a9f695fd9a3e7a6ac82b45e1"
"sha256:562a3a09c3ed3a1a7b20e13d79f904dfdfc5e740f72813ecf95e4cf71e5a2f52",
"sha256:aeb3e26742863d1e387f9d156f1c36e14af63bf5e6f36fb39b8c27f6a903be38"
],
"markers": "python_version >= '3.8'",
"version": "==19.13.0"
"version": "==20.1.0"
},
"fred-epplib": {
"git": "https://github.com/cisagov/epplib.git",
@ -525,11 +525,11 @@
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
"sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca",
"sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"
],
"markers": "python_version >= '3.5'",
"version": "==3.4"
"version": "==3.6"
},
"jmespath": {
"hashes": [
@ -639,11 +639,11 @@
},
"mako": {
"hashes": [
"sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818",
"sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"
"sha256:57d4e997349f1a92035aa25c17ace371a4213f2ca42f99bee9a602500cfd54d9",
"sha256:e3a9d388fd00e87043edbe8792f45880ac0114e9c4adc69f6e9bfb2c55e3b11b"
],
"markers": "python_version >= '3.7'",
"version": "==1.2.4"
"markers": "python_version >= '3.8'",
"version": "==1.3.0"
},
"markupsafe": {
"hashes": [
@ -745,10 +745,10 @@
},
"phonenumberslite": {
"hashes": [
"sha256:1e03f7076ab2f010088b1b8041ebdc42acd3b797e8f45997ab1861cdaea76851",
"sha256:adce353ee15b75f2deccf0eff77bada2a3d036f49ccfb30b8c172dd814fd51e9"
"sha256:305736b1b489e2bc6831710a2f34a9324f2bf96a1e77c8e0b3136dfaf9ca7753",
"sha256:6356f2728fa1d2c2bc9e79c3bfcfedc91a36537df7a134f150731a821a469a96"
],
"version": "==8.13.24"
"version": "==8.13.26"
},
"psycopg2-binary": {
"hashes": [
@ -877,131 +877,130 @@
},
"pydantic": {
"hashes": [
"sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7",
"sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"
"sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0",
"sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"
],
"markers": "python_version >= '3.7'",
"version": "==2.4.2"
"version": "==2.5.2"
},
"pydantic-core": {
"hashes": [
"sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e",
"sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33",
"sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7",
"sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7",
"sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea",
"sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4",
"sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0",
"sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7",
"sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94",
"sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff",
"sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82",
"sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd",
"sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893",
"sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e",
"sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d",
"sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901",
"sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9",
"sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c",
"sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7",
"sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891",
"sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f",
"sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a",
"sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9",
"sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5",
"sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e",
"sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a",
"sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c",
"sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f",
"sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514",
"sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b",
"sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302",
"sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096",
"sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0",
"sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27",
"sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884",
"sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a",
"sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357",
"sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430",
"sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221",
"sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325",
"sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4",
"sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05",
"sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55",
"sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875",
"sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970",
"sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc",
"sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6",
"sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f",
"sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b",
"sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d",
"sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15",
"sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118",
"sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee",
"sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e",
"sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6",
"sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208",
"sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede",
"sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3",
"sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e",
"sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada",
"sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175",
"sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a",
"sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c",
"sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f",
"sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58",
"sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f",
"sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a",
"sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a",
"sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921",
"sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e",
"sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904",
"sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776",
"sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52",
"sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf",
"sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8",
"sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f",
"sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b",
"sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63",
"sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c",
"sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f",
"sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468",
"sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e",
"sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab",
"sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2",
"sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb",
"sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb",
"sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132",
"sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b",
"sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607",
"sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934",
"sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698",
"sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e",
"sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561",
"sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de",
"sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b",
"sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a",
"sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595",
"sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402",
"sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881",
"sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429",
"sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5",
"sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7",
"sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c",
"sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531",
"sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6",
"sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"
"sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b",
"sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b",
"sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d",
"sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8",
"sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124",
"sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189",
"sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c",
"sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d",
"sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f",
"sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520",
"sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4",
"sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6",
"sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955",
"sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3",
"sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b",
"sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a",
"sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68",
"sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3",
"sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd",
"sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de",
"sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b",
"sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634",
"sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7",
"sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459",
"sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7",
"sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3",
"sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331",
"sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf",
"sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d",
"sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36",
"sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59",
"sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937",
"sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc",
"sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093",
"sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753",
"sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706",
"sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca",
"sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260",
"sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997",
"sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588",
"sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71",
"sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb",
"sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e",
"sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69",
"sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5",
"sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07",
"sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1",
"sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0",
"sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd",
"sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8",
"sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944",
"sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26",
"sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda",
"sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4",
"sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9",
"sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00",
"sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe",
"sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6",
"sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada",
"sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4",
"sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7",
"sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325",
"sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4",
"sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b",
"sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88",
"sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04",
"sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863",
"sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0",
"sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911",
"sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b",
"sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e",
"sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144",
"sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5",
"sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720",
"sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab",
"sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d",
"sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789",
"sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec",
"sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2",
"sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db",
"sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f",
"sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef",
"sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3",
"sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209",
"sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc",
"sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651",
"sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8",
"sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e",
"sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66",
"sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7",
"sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550",
"sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd",
"sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405",
"sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27",
"sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093",
"sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077",
"sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113",
"sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3",
"sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6",
"sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf",
"sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed",
"sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88",
"sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe",
"sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18",
"sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"
],
"markers": "python_version >= '3.7'",
"version": "==2.10.1"
"version": "==2.14.5"
},
"pydantic-settings": {
"hashes": [
"sha256:962dc3672495aad6ae96a4390fac7e593591e144625e5112d359f8f67fb75945",
"sha256:ddd907b066622bd67603b75e2ff791875540dc485b7307c4fffc015719da8625"
"sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c",
"sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.3"
"markers": "python_version >= '3.8'",
"version": "==2.1.0"
},
"pyjwkest": {
"hashes": [
@ -1037,19 +1036,19 @@
},
"s3transfer": {
"hashes": [
"sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a",
"sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"
"sha256:368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283",
"sha256:c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76"
],
"markers": "python_version >= '3.7'",
"version": "==0.7.0"
"version": "==0.8.2"
},
"setuptools": {
"hashes": [
"sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87",
"sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"
"sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2",
"sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"
],
"markers": "python_version >= '3.8'",
"version": "==68.2.2"
"version": "==69.0.2"
},
"six": {
"hashes": [
@ -1172,28 +1171,28 @@
},
"black": {
"hashes": [
"sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884",
"sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916",
"sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258",
"sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1",
"sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce",
"sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d",
"sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982",
"sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7",
"sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173",
"sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9",
"sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb",
"sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad",
"sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc",
"sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0",
"sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a",
"sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe",
"sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace",
"sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"
"sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4",
"sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b",
"sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f",
"sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07",
"sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187",
"sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6",
"sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05",
"sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06",
"sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e",
"sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5",
"sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244",
"sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f",
"sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221",
"sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055",
"sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479",
"sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394",
"sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911",
"sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==23.10.1"
"version": "==23.11.0"
},
"blinker": {
"hashes": [
@ -1205,12 +1204,12 @@
},
"boto3": {
"hashes": [
"sha256:02ce7dcad2d3b054cd99e7ca6df7a708e016a31b1c98b46d8df3b3891070c121",
"sha256:b8acb57a124434284d6ab69c61d32d70e84e13e2c27c33b4ad3c32f15ad407d3"
"sha256:d12467fb3a64d359b0bda0570a8163a5859fcac13e786f2a3db0392523178556",
"sha256:eed0f7df91066b6ac63a53d16459ac082458d57061bedf766135d9e1c2b75a6b"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==1.28.79"
"version": "==1.33.7"
},
"boto3-mocking": {
"hashes": [
@ -1223,28 +1222,28 @@
},
"boto3-stubs": {
"hashes": [
"sha256:621e229ef9b394cd1f6cd5caa58a17347440b14423b01435d9f2a50031a427fc",
"sha256:f5986d1b09d516f58780100a3a86bfa75114370dd5dd0bdea67bfe8cda255723"
"sha256:0461f6fec92d96aa2ea3a207329bd020a62a7aaa86f284e5cf054d9b0c7f03c2",
"sha256:449b91060cd953e08980d76a3b67d7eb4246e663b37ecba4ec625b54619e1c22"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==1.28.79"
"version": "==1.33.7"
},
"botocore": {
"hashes": [
"sha256:07ecb93833475dde68e5c0e02a7ccf8ca22caf68cdc892651c300529894133e1",
"sha256:6f1fc49e9e12f9772b4fef577837670bc84d772a7c946b4d08fe2890e34a4305"
"sha256:71ec0e85b996cf9def3dd8f4ca6cb4a9fd3a614aa4c9c7cbf33f2f68e1d0649a",
"sha256:b2299bc13bb8c0928edc98bf4594deb14cba2357536120f63772027a16ce7374"
],
"markers": "python_version >= '3.7'",
"version": "==1.31.79"
"version": "==1.33.7"
},
"botocore-stubs": {
"hashes": [
"sha256:64488b9f38905f8a60041998f9dc945754222d900a3345b449059667890c2c17",
"sha256:e4d8e782d774f45dbfc36d922a0a0edfffbacca2ce66bccaba02a893a38359f2"
"sha256:ca5de1ad4dc384f919387bb96eececb70900fda9219acfcf4b473b35a4834ec9",
"sha256:f73e4728a4a391f0407cd9403f6935343aac5687fb1e0eab7c3351d3419e853b"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==1.31.79"
"version": "==1.33.7"
},
"click": {
"hashes": [
@ -1363,37 +1362,37 @@
},
"mypy": {
"hashes": [
"sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7",
"sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e",
"sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c",
"sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169",
"sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208",
"sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0",
"sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1",
"sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1",
"sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7",
"sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45",
"sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143",
"sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5",
"sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f",
"sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd",
"sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245",
"sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f",
"sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332",
"sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30",
"sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183",
"sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f",
"sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85",
"sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46",
"sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71",
"sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660",
"sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb",
"sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c",
"sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"
"sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340",
"sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49",
"sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82",
"sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce",
"sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb",
"sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51",
"sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5",
"sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e",
"sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7",
"sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33",
"sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9",
"sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1",
"sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6",
"sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a",
"sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe",
"sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7",
"sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200",
"sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7",
"sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a",
"sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28",
"sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea",
"sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120",
"sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d",
"sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42",
"sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea",
"sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2",
"sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.6.1"
"version": "==1.7.1"
},
"mypy-extensions": {
"hashes": [
@ -1437,11 +1436,11 @@
},
"platformdirs": {
"hashes": [
"sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3",
"sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"
"sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380",
"sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"
],
"markers": "python_version >= '3.7'",
"version": "==3.11.0"
"markers": "python_version >= '3.8'",
"version": "==4.1.0"
},
"pycodestyle": {
"hashes": [
@ -1461,11 +1460,11 @@
},
"pygments": {
"hashes": [
"sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692",
"sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"
"sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c",
"sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"
],
"markers": "python_version >= '3.7'",
"version": "==2.16.1"
"version": "==2.17.2"
},
"python-dateutil": {
"hashes": [
@ -1533,19 +1532,19 @@
},
"rich": {
"hashes": [
"sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245",
"sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"
"sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa",
"sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==13.6.0"
"version": "==13.7.0"
},
"s3transfer": {
"hashes": [
"sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a",
"sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"
"sha256:368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283",
"sha256:c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76"
],
"markers": "python_version >= '3.7'",
"version": "==0.7.0"
"version": "==0.8.2"
},
"six": {
"hashes": [
@ -1597,11 +1596,11 @@
},
"types-awscrt": {
"hashes": [
"sha256:4eb4f3bd0c41a2710cacda13098374da9faa76c5a0fb901aa5659e0fd48ceda1",
"sha256:a2d534b7017c3476ee69a44bd8aeaf3b588c42baa8322473d100a45ee67510d7"
"sha256:850d5ad95d8f337b15fb154790f39af077faf5c08d43758fd750f379a87d5f73",
"sha256:a577c4d60a7fb7e21b436a73207a66f6ba50329d578b347934c5d99d4d612901"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==0.19.8"
"version": "==0.19.19"
},
"types-cachetools": {
"hashes": [
@ -1637,11 +1636,11 @@
},
"types-s3transfer": {
"hashes": [
"sha256:aca0f2486d0a3a5037cd5b8f3e20a4522a29579a8dd183281ff0aa1c4e2c8aa7",
"sha256:ae9ed9273465d9f43da8b96307383da410c6b59c3b2464c88d20b578768e97c6"
"sha256:2e41756fcf94775a9949afa856489ac4570308609b0493dfbd7b4d333eb423e6",
"sha256:5e084ebcf2704281c71b19d5da6e1544b50859367d034b50080d5316a76a9418"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==0.7.0"
"version": "==0.8.2"
},
"typing-extensions": {
"hashes": [

View file

@ -1,5 +1,6 @@
import logging
from django import forms
from django.db.models.functions import Concat
from django.http import HttpResponse
from django.shortcuts import redirect
from django_fsm import get_available_FIELD_transitions
@ -11,7 +12,6 @@ from django.http.response import HttpResponseRedirect
from django.urls import reverse
from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.domain import Domain
from registrar.models.utility.admin_sort_fields import AdminSortFields
from registrar.utility import csv_export
from registrar.views.utility.mixins import OrderableFieldsMixin
from django.contrib.admin.views.main import ORDER_VAR
@ -114,7 +114,44 @@ class CustomLogEntryAdmin(LogEntryAdmin):
add_form_template = "admin/change_form_no_submit.html"
class AuditedAdmin(admin.ModelAdmin, AdminSortFields):
class AdminSortFields:
def get_queryset(db_field):
"""This is a helper function for formfield_for_manytomany and formfield_for_foreignkey"""
# customize sorting
if db_field.name in (
"other_contacts",
"authorizing_official",
"submitter",
):
# Sort contacts by first_name, then last_name, then email
return models.Contact.objects.all().order_by(Concat("first_name", "last_name", "email"))
elif db_field.name in ("current_websites", "alternative_domains"):
# sort web sites
return models.Website.objects.all().order_by("website")
elif db_field.name in (
"creator",
"user",
"investigator",
):
# Sort users by first_name, then last_name, then email
return models.User.objects.all().order_by(Concat("first_name", "last_name", "email"))
elif db_field.name in (
"domain",
"approved_domain",
):
# Sort domains by name
return models.Domain.objects.all().order_by("name")
elif db_field.name in ("requested_domain",):
# Sort draft domains by name
return models.DraftDomain.objects.all().order_by("name")
elif db_field.name in ("domain_application",):
# Sort domain applications by name
return models.DomainApplication.objects.all().order_by("requested_domain__name")
else:
return None
class AuditedAdmin(admin.ModelAdmin):
"""Custom admin to make auditing easier."""
def history_view(self, request, object_id, extra_context=None):
@ -127,10 +164,27 @@ class AuditedAdmin(admin.ModelAdmin, AdminSortFields):
)
)
def formfield_for_manytomany(self, db_field, request, **kwargs):
"""customize the behavior of formfields with manytomany relationships. the customized
behavior includes sorting of objects in lists as well as customizing helper text"""
queryset = AdminSortFields.get_queryset(db_field)
if queryset:
kwargs["queryset"] = queryset
formfield = super().formfield_for_manytomany(db_field, request, **kwargs)
# customize the help text for all formfields for manytomany
formfield.help_text = (
formfield.help_text
+ " If more than one value is selected, the change/delete/view actions will be disabled."
)
return formfield
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""Used to sort dropdown fields alphabetically but can be expanded upon"""
form_field = super().formfield_for_foreignkey(db_field, request, **kwargs)
return self.form_field_order_helper(form_field, db_field)
"""customize the behavior of formfields with foreign key relationships. this will customize
the behavior of selects. customized behavior includes sorting of objects in list"""
queryset = AdminSortFields.get_queryset(db_field)
if queryset:
kwargs["queryset"] = queryset
return super().formfield_for_foreignkey(db_field, request, **kwargs)
class ListHeaderAdmin(AuditedAdmin, OrderableFieldsMixin):
@ -198,15 +252,6 @@ class ListHeaderAdmin(AuditedAdmin, OrderableFieldsMixin):
)
return filters
# customize the help_text for all formfields for manytomany
def formfield_for_manytomany(self, db_field, request, **kwargs):
formfield = super().formfield_for_manytomany(db_field, request, **kwargs)
formfield.help_text = (
formfield.help_text
+ " If more than one value is selected, the change/delete/view actions will be disabled."
)
return formfield
class UserContactInline(admin.StackedInline):
"""Edit a user's profile on the user page."""
@ -301,6 +346,10 @@ class MyUserAdmin(BaseUserAdmin):
"groups",
)
# this ordering effects the ordering of results
# in autocomplete_fields for user
ordering = ["first_name", "last_name", "email"]
# Let's define First group
# (which should in theory be the ONLY group)
def group(self, obj):
@ -579,13 +628,6 @@ class DomainInformationAdmin(ListHeaderAdmin):
# to activate the edit/delete/view buttons
filter_horizontal = ("other_contacts",)
# lists in filter_horizontal are not sorted properly, sort them
# by first_name
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name in ("other_contacts",):
kwargs["queryset"] = models.Contact.objects.all().order_by("first_name") # Sort contacts
return super().formfield_for_manytomany(db_field, request, **kwargs)
def get_readonly_fields(self, request, obj=None):
"""Set the read-only state on form elements.
We have 1 conditions that determine which fields are read-only:
@ -870,12 +912,27 @@ class DomainInformationInline(admin.StackedInline):
# to activate the edit/delete/view buttons
filter_horizontal = ("other_contacts",)
# lists in filter_horizontal are not sorted properly, sort them
# by first_name
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name in ("other_contacts",):
kwargs["queryset"] = models.Contact.objects.all().order_by("first_name") # Sort contacts
return super().formfield_for_manytomany(db_field, request, **kwargs)
"""customize the behavior of formfields with manytomany relationships. the customized
behavior includes sorting of objects in lists as well as customizing helper text"""
queryset = AdminSortFields.get_queryset(db_field)
if queryset:
kwargs["queryset"] = queryset
formfield = super().formfield_for_manytomany(db_field, request, **kwargs)
# customize the help text for all formfields for manytomany
formfield.help_text = (
formfield.help_text
+ " If more than one value is selected, the change/delete/view actions will be disabled."
)
return formfield
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""customize the behavior of formfields with foreign key relationships. this will customize
the behavior of selects. customized behavior includes sorting of objects in list"""
queryset = AdminSortFields.get_queryset(db_field)
if queryset:
kwargs["queryset"] = queryset
return super().formfield_for_foreignkey(db_field, request, **kwargs)
def get_readonly_fields(self, request, obj=None):
return DomainInformationAdmin.get_readonly_fields(self, request, obj=None)
@ -893,6 +950,10 @@ class DomainAdmin(ListHeaderAdmin):
"state",
]
# this ordering effects the ordering of results
# in autocomplete_fields for domain
ordering = ["name"]
def organization_type(self, obj):
return obj.domain_info.get_organization_type_display()

View file

@ -329,10 +329,6 @@ class AuthorizingOfficialForm(RegistrarForm):
label="First name / given name",
error_messages={"required": ("Enter the first name / given name of your authorizing official.")},
)
middle_name = forms.CharField(
required=False,
label="Middle name (optional)",
)
last_name = forms.CharField(
label="Last name / family name",
error_messages={"required": ("Enter the last name / family name of your authorizing official.")},
@ -350,10 +346,6 @@ class AuthorizingOfficialForm(RegistrarForm):
label="Email",
error_messages={"invalid": ("Enter an email address in the required format, like name@example.com.")},
)
phone = PhoneNumberField(
label="Phone",
error_messages={"required": "Enter the phone number for your authorizing official."},
)
class CurrentSitesForm(RegistrarForm):
@ -618,6 +610,12 @@ class NoOtherContactsForm(RegistrarForm):
"we can contact to help us assess your eligibility for a .gov domain."
),
widget=forms.Textarea(),
validators=[
MaxLengthValidator(
1000,
message="Response must be less than 1000 characters.",
)
],
)

View file

@ -213,6 +213,9 @@ class AuthorizingOfficialContactForm(ContactForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Overriding bc phone not required in this form
self.fields["phone"] = forms.IntegerField(required=False)
# Set custom error messages
self.fields["first_name"].error_messages = {
"required": "Enter the first name / given name of your authorizing official."
@ -227,7 +230,6 @@ class AuthorizingOfficialContactForm(ContactForm):
self.fields["email"].error_messages = {
"required": "Enter an email address in the required format, like name@example.com."
}
self.fields["phone"].error_messages["required"] = "Enter a phone number for your authorizing official."
class DomainSecurityEmailForm(forms.Form):

View file

@ -585,7 +585,12 @@ class DomainApplication(TimeStampedModel):
@transition(
field="status",
source=[ApplicationStatus.STARTED, ApplicationStatus.ACTION_NEEDED, ApplicationStatus.WITHDRAWN],
source=[
ApplicationStatus.STARTED,
ApplicationStatus.IN_REVIEW,
ApplicationStatus.ACTION_NEEDED,
ApplicationStatus.WITHDRAWN,
],
target=ApplicationStatus.SUBMITTED,
)
def submit(self):
@ -613,7 +618,17 @@ class DomainApplication(TimeStampedModel):
"emails/submission_confirmation_subject.txt",
)
@transition(field="status", source=ApplicationStatus.SUBMITTED, target=ApplicationStatus.IN_REVIEW)
@transition(
field="status",
source=[
ApplicationStatus.SUBMITTED,
ApplicationStatus.ACTION_NEEDED,
ApplicationStatus.APPROVED,
ApplicationStatus.REJECTED,
ApplicationStatus.INELIGIBLE,
],
target=ApplicationStatus.IN_REVIEW,
)
def in_review(self):
"""Investigate an application that has been submitted.
@ -627,7 +642,12 @@ class DomainApplication(TimeStampedModel):
@transition(
field="status",
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.REJECTED],
source=[
ApplicationStatus.IN_REVIEW,
ApplicationStatus.APPROVED,
ApplicationStatus.REJECTED,
ApplicationStatus.INELIGIBLE,
],
target=ApplicationStatus.ACTION_NEEDED,
)
def action_needed(self):
@ -646,8 +666,8 @@ class DomainApplication(TimeStampedModel):
source=[
ApplicationStatus.SUBMITTED,
ApplicationStatus.IN_REVIEW,
ApplicationStatus.ACTION_NEEDED,
ApplicationStatus.REJECTED,
ApplicationStatus.INELIGIBLE,
],
target=ApplicationStatus.APPROVED,
)
@ -684,7 +704,7 @@ class DomainApplication(TimeStampedModel):
@transition(
field="status",
source=[ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW],
source=[ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW, ApplicationStatus.ACTION_NEEDED],
target=ApplicationStatus.WITHDRAWN,
)
def withdraw(self):
@ -697,7 +717,7 @@ class DomainApplication(TimeStampedModel):
@transition(
field="status",
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED],
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.ACTION_NEEDED, ApplicationStatus.APPROVED],
target=ApplicationStatus.REJECTED,
conditions=[domain_is_not_active],
)
@ -707,12 +727,16 @@ class DomainApplication(TimeStampedModel):
As side effects this will delete the domain and domain_information
(will cascade), and send an email notification."""
if self.status == self.ApplicationStatus.APPROVED:
try:
domain_state = self.approved_domain.state
# Only reject if it exists on EPP
if domain_state != Domain.State.UNKNOWN:
self.approved_domain.deletedInEpp()
self.approved_domain.delete()
self.approved_domain = None
except Exception as err:
logger.error(err)
logger.error("Can't query an approved domain while attempting a DA reject()")
self._send_status_update_email(
"action needed",
@ -722,7 +746,12 @@ class DomainApplication(TimeStampedModel):
@transition(
field="status",
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED],
source=[
ApplicationStatus.IN_REVIEW,
ApplicationStatus.ACTION_NEEDED,
ApplicationStatus.APPROVED,
ApplicationStatus.REJECTED,
],
target=ApplicationStatus.INELIGIBLE,
conditions=[domain_is_not_active],
)
@ -736,12 +765,16 @@ class DomainApplication(TimeStampedModel):
and domain_information (will cascade) when they exist."""
if self.status == self.ApplicationStatus.APPROVED:
try:
domain_state = self.approved_domain.state
# Only reject if it exists on EPP
if domain_state != Domain.State.UNKNOWN:
self.approved_domain.deletedInEpp()
self.approved_domain.delete()
self.approved_domain = None
except Exception as err:
logger.error(err)
logger.error("Can't query an approved domain while attempting a DA reject_with_prejudice()")
self.creator.restrict_user()

View file

@ -114,7 +114,7 @@ class UserGroup(Group):
)
cisa_analysts_group.save()
logger.debug("CISA Analyt permissions added to group " + cisa_analysts_group.name)
logger.debug("CISA Analyst permissions added to group " + cisa_analysts_group.name)
except Exception as e:
logger.error(f"Error creating analyst permissions group: {e}")

View file

@ -1,63 +0,0 @@
import logging
from typing import Dict
from django.forms import ModelChoiceField
logger = logging.getLogger(__name__)
class SortingDict:
"""Stores a sorting dictionary object"""
_sorting_dict: Dict[type, type] = {}
def __init__(self, model_list, sort_list):
self._sorting_dict = {
"dropDownSelected": self.convert_list_to_dict(model_list),
"sortBy": sort_list,
}
# Used in __init__ for model_list for performance reasons
def convert_list_to_dict(self, value_list):
"""Used internally to convert model_list to a dictionary"""
return {item: item for item in value_list}
def get_dict(self):
"""Grabs the associated dictionary item,
has two fields: 'dropDownSelected': model_list and 'sortBy': sort_list"""
# This should never happen so we need to log this
if self._sorting_dict is None:
raise ValueError("_sorting_dict was None")
return self._sorting_dict
class AdminFormOrderHelper:
"""A helper class to order a dropdown field in Django Admin,
takes the fields you want to order by as an array"""
# Used to keep track of how we want to order_by certain FKs
_sorting_list: list[SortingDict] = []
def __init__(self, sort: list[SortingDict]):
self._sorting_list = sort
def get_ordered_form_field(self, form_field, db_field) -> ModelChoiceField | None:
"""Orders the queryset for a ModelChoiceField
based on the order_by_dict dictionary"""
_order_by_list = []
for item in self._sorting_list:
item_dict = item.get_dict()
drop_down_selected = item_dict.get("dropDownSelected")
sort_by = item_dict.get("sortBy")
if db_field.name in drop_down_selected:
_order_by_list = sort_by
# Exit loop when order_by_list is found
break
# Only order if we choose to do so
# noqa for the linter... reduces readability otherwise
if _order_by_list is not None and _order_by_list != []: # noqa
form_field.queryset = form_field.queryset.order_by(*_order_by_list)
return form_field

View file

@ -1,27 +0,0 @@
from registrar.models.utility.admin_form_order_helper import (
AdminFormOrderHelper,
SortingDict,
)
class AdminSortFields:
# Used to keep track of how we want to order_by certain FKs
foreignkey_orderby_dict: list[SortingDict] = [
# foreign_key - order_by
# Handles fields that are sorted by 'first_name / last_name
SortingDict(
["submitter", "authorizing_official", "investigator", "creator", "user"],
["first_name", "last_name"],
),
# Handles fields that are sorted by 'name'
SortingDict(["domain", "requested_domain"], ["name"]),
SortingDict(["domain_application"], ["requested_domain__name"]),
]
# For readability purposes, but can be replaced with a one liner
def form_field_order_helper(self, form_field, db_field):
"""A shorthand for AdminFormOrderHelper(foreignkey_orderby_dict)
.get_ordered_form_field(form_field, db_field)"""
form = AdminFormOrderHelper(self.foreignkey_orderby_dict)
return form.get_ordered_form_field(form_field, db_field)

View file

@ -23,7 +23,7 @@
{% endif %}
<p>
You must be an authorized user and need to be signed in to view this page.
Would you like to <a href="{% url 'login' %}"> try logging in again</a>?
<a href="{% url 'login' %}"> Try logging in again</a>.
</p>
<p>
If you'd like help with this error <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">contact us</a>.

View file

@ -25,17 +25,11 @@
{% input_with_errors forms.0.first_name %}
{% input_with_errors forms.0.middle_name %}
{% input_with_errors forms.0.last_name %}
{% input_with_errors forms.0.title %}
{% input_with_errors forms.0.email %}
{% with add_class="usa-input--medium" %}
{% input_with_errors forms.0.phone %}
{% endwith %}
</fieldset>
{% endblock %}

View file

@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% load static %}
{% load url_helpers %}
{% block title %}Thanks for your domain request! | {% endblock %}
@ -14,21 +15,22 @@
/>
<h1>Thanks for your domain request!</h1>
</span>
<p>We'll email a copy of your request to you.</p>
<p>Well email a copy of your request to you.</p>
<h2>Next steps in this process</h2>
<p> We'll review your request. This usually takes 20 business days. During
this review we'll verify that your:</p>
<p> Well review your request. This usually takes 20 business days. During
this review well verify that:</p>
<ul class="usa-list">
<li>Organization is eligible for a .gov domain</li>
<li>Authorizing official approves your request</li>
<li>Domain meets our naming requirements</li>
<li>Your organization is eligible for a .gov domain.</li>
<li>You work at the organization and/or can make requests on its behalf.</li>
<li>Your requested domain meets our naming requirements.</li>
</ul>
<p> You can <a href="{% url 'home' %}">check the status</a>
of your request at any time. We'll email you with any questions or when we
complete our review.</p>
<p> Well email you if we have questions and when we complete our review. You can <a href="{% url 'home' %}">check the status</a>
of your request at any time on the registrar homepage.</p>
<p> <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">Contact us if you need help during this process</a>.</p>
</main>
{% endblock %}

View file

@ -105,7 +105,7 @@
aria-describedby="Are you sure you want to submit a domain request?"
data-force-action
>
{% include 'includes/modal.html' with modal_heading=modal_heading|safe modal_description="Once you submit this request, you wont be able to make further edits until its reviewed by our staff. Youll only be able to withdraw your request." modal_button=modal_button|safe %}
{% include 'includes/modal.html' with modal_heading=modal_heading|safe modal_description="Once you submit this request, you wont be able to edit it until we review it. Youll only be able to withdraw your request." modal_button=modal_button|safe %}
</div>
{% block after_form_content %}{% endblock %}

View file

@ -2,5 +2,7 @@
{% load static field_helpers %}
{% block form_fields %}
{% with attr_maxlength=1000 %}
{% input_with_errors forms.0.no_other_contacts_rationale %}
{% endwith %}
{% endblock %}

View file

@ -19,18 +19,12 @@
{% input_with_errors form.first_name %}
{% input_with_errors form.middle_name %}
{% input_with_errors form.last_name %}
{% input_with_errors form.title %}
{% input_with_errors form.email %}
{% input_with_errors form.phone %}
<button
type="submit"
class="usa-button"

View file

@ -1,7 +1,9 @@
{% load url_helpers %}
<h2 class="margin-top-0 margin-bottom-2 text-primary-darker text-semibold" >
Next steps
Next steps in this process
</h2>
<p>We received your .gov domain request. Our next step is to review your request. This usually takes two weeks. Well email you with questions or when we complete our review. Contact us with any questions.</p>
<p>We received your .gov domain request. Our next step is to review your request. This usually takes 20 business days. Well email you if we have questions and when we complete our review. <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">Contact us with any questions</a>.</p>
<h2 class="margin-top-0 margin-bottom-2 text-primary-darker text-semibold">
Need to make changes?

View file

@ -530,7 +530,7 @@ def completed_application(
):
"""A completed domain application."""
if not user:
user = get_user_model().objects.create(username="username")
user = get_user_model().objects.create(username="username" + str(uuid.uuid4())[:8])
ao, _ = Contact.objects.get_or_create(
first_name="Testy",
last_name="Tester",

View file

@ -1371,7 +1371,6 @@ class AuditedAdminTest(TestCase):
)
def test_alphabetically_sorted_fk_fields_domain_information(self):
self.maxDiff = None
tested_fields = [
DomainInformation.authorizing_official.field,
DomainInformation.submitter.field,

View file

@ -196,11 +196,6 @@ class TestFormValidation(MockEppLib):
["Response must be less than 1000 characters."],
)
def test_authorizing_official_phone_invalid(self):
"""Must be a valid phone number."""
form = AuthorizingOfficialForm(data={"phone": "boss@boss"})
self.assertTrue(form.errors["phone"][0].startswith("Enter a valid phone number "))
def test_your_contact_email_invalid(self):
"""must be a valid email address."""
form = YourContactForm(data={"email": "boss@boss"})

View file

@ -26,20 +26,52 @@ boto3_mocking.clients.register_handler("sesv2", MockSESClient)
# with AWS SES, so mock that out in all of these test cases
@boto3_mocking.patching
class TestDomainApplication(TestCase):
def setUp(self):
self.started_application = completed_application(
status=DomainApplication.ApplicationStatus.STARTED, name="started.gov"
)
self.submitted_application = completed_application(
status=DomainApplication.ApplicationStatus.SUBMITTED, name="submitted.gov"
)
self.in_review_application = completed_application(
status=DomainApplication.ApplicationStatus.IN_REVIEW, name="in-review.gov"
)
self.action_needed_application = completed_application(
status=DomainApplication.ApplicationStatus.ACTION_NEEDED, name="action-needed.gov"
)
self.approved_application = completed_application(
status=DomainApplication.ApplicationStatus.APPROVED, name="approved.gov"
)
self.withdrawn_application = completed_application(
status=DomainApplication.ApplicationStatus.WITHDRAWN, name="withdrawn.gov"
)
self.rejected_application = completed_application(
status=DomainApplication.ApplicationStatus.REJECTED, name="rejected.gov"
)
self.ineligible_application = completed_application(
status=DomainApplication.ApplicationStatus.INELIGIBLE, name="ineligible.gov"
)
def assertNotRaises(self, exception_type):
"""Helper method for testing allowed transitions."""
return self.assertRaises(Exception, None, exception_type)
def test_empty_create_fails(self):
"""Can't create a completely empty domain application."""
"""Can't create a completely empty domain application.
NOTE: something about theexception this test raises messes up with the
atomic block in a custom tearDown method for the parent test class."""
with self.assertRaisesRegex(IntegrityError, "creator"):
DomainApplication.objects.create()
def test_minimal_create(self):
"""Can create with just a creator."""
user, _ = User.objects.get_or_create()
user, _ = User.objects.get_or_create(username="testy")
application = DomainApplication.objects.create(creator=user)
self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED)
def test_full_create(self):
"""Can create with all fields."""
user, _ = User.objects.get_or_create()
user, _ = User.objects.get_or_create(username="testy")
contact = Contact.objects.create()
com_website, _ = Website.objects.get_or_create(website="igorville.com")
gov_website, _ = Website.objects.get_or_create(website="igorville.gov")
@ -69,7 +101,7 @@ class TestDomainApplication(TestCase):
def test_domain_info(self):
"""Can create domain info with all fields."""
user, _ = User.objects.get_or_create()
user, _ = User.objects.get_or_create(username="testy")
contact = Contact.objects.create()
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
information = DomainInformation.objects.create(
@ -95,14 +127,14 @@ class TestDomainApplication(TestCase):
self.assertEqual(information.id, domain.domain_info.id)
def test_status_fsm_submit_fail(self):
user, _ = User.objects.get_or_create()
user, _ = User.objects.get_or_create(username="testy")
application = DomainApplication.objects.create(creator=user)
with self.assertRaises(ValueError):
# can't submit an application with a null domain name
application.submit()
def test_status_fsm_submit_succeed(self):
user, _ = User.objects.get_or_create()
user, _ = User.objects.get_or_create(username="testy")
site = DraftDomain.objects.create(name="igorville.gov")
application = DomainApplication.objects.create(creator=user, requested_domain=site)
# no submitter email so this emits a log warning
@ -112,7 +144,7 @@ class TestDomainApplication(TestCase):
def test_submit_sends_email(self):
"""Create an application and submit it and see if email was sent."""
user, _ = User.objects.get_or_create()
user, _ = User.objects.get_or_create(username="testy")
contact = Contact.objects.create(email="test@test.gov")
domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
application = DomainApplication.objects.create(
@ -135,320 +167,251 @@ class TestDomainApplication(TestCase):
0,
)
def test_transition_not_allowed_submitted_submitted(self):
"""Create an application with status submitted and call submit
against transition rules"""
def test_submit_transition_allowed(self):
"""
Test that calling submit from allowable statuses does raises TransitionNotAllowed.
"""
test_cases = [
(self.started_application, TransitionNotAllowed),
(self.in_review_application, TransitionNotAllowed),
(self.action_needed_application, TransitionNotAllowed),
(self.withdrawn_application, TransitionNotAllowed),
]
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.submit()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with self.assertRaises(TransitionNotAllowed):
def test_submit_transition_not_allowed(self):
"""
Test that calling submit against transition rules raises TransitionNotAllowed.
"""
test_cases = [
(self.submitted_application, TransitionNotAllowed),
(self.approved_application, TransitionNotAllowed),
(self.rejected_application, TransitionNotAllowed),
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.submit()
def test_transition_not_allowed_in_review_submitted(self):
"""Create an application with status in review and call submit
against transition rules"""
def test_in_review_transition_allowed(self):
"""
Test that calling in_review from allowable statuses does raises TransitionNotAllowed.
"""
test_cases = [
(self.submitted_application, TransitionNotAllowed),
(self.action_needed_application, TransitionNotAllowed),
(self.approved_application, TransitionNotAllowed),
(self.rejected_application, TransitionNotAllowed),
(self.ineligible_application, TransitionNotAllowed),
]
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.in_review()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with self.assertRaises(TransitionNotAllowed):
application.submit()
def test_in_review_transition_not_allowed(self):
"""
Test that calling in_review against transition rules raises TransitionNotAllowed.
"""
test_cases = [
(self.started_application, TransitionNotAllowed),
(self.in_review_application, TransitionNotAllowed),
(self.withdrawn_application, TransitionNotAllowed),
]
def test_transition_not_allowed_approved_submitted(self):
"""Create an application with status approved and call submit
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
with self.assertRaises(TransitionNotAllowed):
application.submit()
def test_transition_not_allowed_rejected_submitted(self):
"""Create an application with status rejected and call submit
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
with self.assertRaises(TransitionNotAllowed):
application.submit()
def test_transition_not_allowed_ineligible_submitted(self):
"""Create an application with status ineligible and call submit
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
with self.assertRaises(TransitionNotAllowed):
application.submit()
def test_transition_not_allowed_started_in_review(self):
"""Create an application with status started and call in_review
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
with self.assertRaises(TransitionNotAllowed):
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.in_review()
def test_transition_not_allowed_in_review_in_review(self):
"""Create an application with status in review and call in_review
against transition rules"""
def test_action_needed_transition_allowed(self):
"""
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
"""
test_cases = [
(self.in_review_application, TransitionNotAllowed),
(self.approved_application, TransitionNotAllowed),
(self.rejected_application, TransitionNotAllowed),
(self.ineligible_application, TransitionNotAllowed),
]
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.action_needed()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with self.assertRaises(TransitionNotAllowed):
application.in_review()
def test_action_needed_transition_not_allowed(self):
"""
Test that calling action_needed against transition rules raises TransitionNotAllowed.
"""
test_cases = [
(self.started_application, TransitionNotAllowed),
(self.submitted_application, TransitionNotAllowed),
(self.action_needed_application, TransitionNotAllowed),
(self.withdrawn_application, TransitionNotAllowed),
]
def test_transition_not_allowed_approved_in_review(self):
"""Create an application with status approved and call in_review
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
with self.assertRaises(TransitionNotAllowed):
application.in_review()
def test_transition_not_allowed_action_needed_in_review(self):
"""Create an application with status action needed and call in_review
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
with self.assertRaises(TransitionNotAllowed):
application.in_review()
def test_transition_not_allowed_rejected_in_review(self):
"""Create an application with status rejected and call in_review
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
with self.assertRaises(TransitionNotAllowed):
application.in_review()
def test_transition_not_allowed_withdrawn_in_review(self):
"""Create an application with status withdrawn and call in_review
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
with self.assertRaises(TransitionNotAllowed):
application.in_review()
def test_transition_not_allowed_ineligible_in_review(self):
"""Create an application with status ineligible and call in_review
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
with self.assertRaises(TransitionNotAllowed):
application.in_review()
def test_transition_not_allowed_started_action_needed(self):
"""Create an application with status started and call action_needed
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
with self.assertRaises(TransitionNotAllowed):
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.action_needed()
def test_transition_not_allowed_submitted_action_needed(self):
"""Create an application with status submitted and call action_needed
against transition rules"""
def test_approved_transition_allowed(self):
"""
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
"""
test_cases = [
(self.submitted_application, TransitionNotAllowed),
(self.in_review_application, TransitionNotAllowed),
(self.action_needed_application, TransitionNotAllowed),
(self.rejected_application, TransitionNotAllowed),
]
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.approve()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with self.assertRaises(TransitionNotAllowed):
application.action_needed()
def test_approved_transition_not_allowed(self):
"""
Test that calling action_needed against transition rules raises TransitionNotAllowed.
"""
test_cases = [
(self.started_application, TransitionNotAllowed),
(self.approved_application, TransitionNotAllowed),
(self.withdrawn_application, TransitionNotAllowed),
(self.ineligible_application, TransitionNotAllowed),
]
def test_transition_not_allowed_action_needed_action_needed(self):
"""Create an application with status action needed and call action_needed
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
with self.assertRaises(TransitionNotAllowed):
application.action_needed()
def test_transition_not_allowed_approved_action_needed(self):
"""Create an application with status approved and call action_needed
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
with self.assertRaises(TransitionNotAllowed):
application.action_needed()
def test_transition_not_allowed_withdrawn_action_needed(self):
"""Create an application with status withdrawn and call action_needed
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
with self.assertRaises(TransitionNotAllowed):
application.action_needed()
def test_transition_not_allowed_ineligible_action_needed(self):
"""Create an application with status ineligible and call action_needed
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
with self.assertRaises(TransitionNotAllowed):
application.action_needed()
def test_transition_not_allowed_started_approved(self):
"""Create an application with status started and call approve
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
with self.assertRaises(TransitionNotAllowed):
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.approve()
def test_transition_not_allowed_approved_approved(self):
"""Create an application with status approved and call approve
against transition rules"""
def test_withdraw_transition_allowed(self):
"""
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
"""
test_cases = [
(self.submitted_application, TransitionNotAllowed),
(self.in_review_application, TransitionNotAllowed),
(self.action_needed_application, TransitionNotAllowed),
]
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.withdraw()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with self.assertRaises(TransitionNotAllowed):
application.approve()
def test_withdraw_transition_not_allowed(self):
"""
Test that calling action_needed against transition rules raises TransitionNotAllowed.
"""
test_cases = [
(self.started_application, TransitionNotAllowed),
(self.approved_application, TransitionNotAllowed),
(self.withdrawn_application, TransitionNotAllowed),
(self.rejected_application, TransitionNotAllowed),
(self.ineligible_application, TransitionNotAllowed),
]
def test_transition_not_allowed_action_needed_approved(self):
"""Create an application with status action needed and call approve
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
with self.assertRaises(TransitionNotAllowed):
application.approve()
def test_transition_not_allowed_withdrawn_approved(self):
"""Create an application with status withdrawn and call approve
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
with self.assertRaises(TransitionNotAllowed):
application.approve()
def test_transition_not_allowed_started_withdrawn(self):
"""Create an application with status started and call withdraw
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
with self.assertRaises(TransitionNotAllowed):
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.withdraw()
def test_transition_not_allowed_approved_withdrawn(self):
"""Create an application with status approved and call withdraw
against transition rules"""
def test_reject_transition_allowed(self):
"""
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
"""
test_cases = [
(self.in_review_application, TransitionNotAllowed),
(self.action_needed_application, TransitionNotAllowed),
(self.approved_application, TransitionNotAllowed),
]
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.reject()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with self.assertRaises(TransitionNotAllowed):
application.withdraw()
def test_reject_transition_not_allowed(self):
"""
Test that calling action_needed against transition rules raises TransitionNotAllowed.
"""
test_cases = [
(self.started_application, TransitionNotAllowed),
(self.submitted_application, TransitionNotAllowed),
(self.withdrawn_application, TransitionNotAllowed),
(self.rejected_application, TransitionNotAllowed),
(self.ineligible_application, TransitionNotAllowed),
]
def test_transition_not_allowed_action_needed_withdrawn(self):
"""Create an application with status action needed and call withdraw
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
with self.assertRaises(TransitionNotAllowed):
application.withdraw()
def test_transition_not_allowed_rejected_withdrawn(self):
"""Create an application with status rejected and call withdraw
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
with self.assertRaises(TransitionNotAllowed):
application.withdraw()
def test_transition_not_allowed_withdrawn_withdrawn(self):
"""Create an application with status withdrawn and call withdraw
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
with self.assertRaises(TransitionNotAllowed):
application.withdraw()
def test_transition_not_allowed_ineligible_withdrawn(self):
"""Create an application with status ineligible and call withdraw
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
with self.assertRaises(TransitionNotAllowed):
application.withdraw()
def test_transition_not_allowed_started_rejected(self):
"""Create an application with status started and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
with self.assertRaises(TransitionNotAllowed):
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.reject()
def test_transition_not_allowed_submitted_rejected(self):
"""Create an application with status submitted and call reject
against transition rules"""
def test_reject_with_prejudice_transition_allowed(self):
"""
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
"""
test_cases = [
(self.in_review_application, TransitionNotAllowed),
(self.action_needed_application, TransitionNotAllowed),
(self.approved_application, TransitionNotAllowed),
(self.rejected_application, TransitionNotAllowed),
]
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.reject_with_prejudice()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with self.assertRaises(TransitionNotAllowed):
application.reject()
def test_reject_with_prejudice_transition_not_allowed(self):
"""
Test that calling action_needed against transition rules raises TransitionNotAllowed.
"""
test_cases = [
(self.started_application, TransitionNotAllowed),
(self.submitted_application, TransitionNotAllowed),
(self.withdrawn_application, TransitionNotAllowed),
(self.ineligible_application, TransitionNotAllowed),
]
def test_transition_not_allowed_action_needed_rejected(self):
"""Create an application with status action needed and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
with self.assertRaises(TransitionNotAllowed):
application.reject()
def test_transition_not_allowed_withdrawn_rejected(self):
"""Create an application with status withdrawn and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
with self.assertRaises(TransitionNotAllowed):
application.reject()
def test_transition_not_allowed_rejected_rejected(self):
"""Create an application with status rejected and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
with self.assertRaises(TransitionNotAllowed):
application.reject()
def test_transition_not_allowed_ineligible_rejected(self):
"""Create an application with status ineligible and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
with self.assertRaises(TransitionNotAllowed):
application.reject()
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.reject_with_prejudice()
def test_transition_not_allowed_approved_rejected_when_domain_is_active(self):
"""Create an application with status approved, create a matching domain that
is active, and call reject against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
domain = Domain.objects.create(name=application.requested_domain.name)
application.approved_domain = domain
application.save()
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
self.approved_application.approved_domain = domain
self.approved_application.save()
# Define a custom implementation for is_active
def custom_is_active(self):
@ -458,70 +421,15 @@ class TestDomainApplication(TestCase):
with patch.object(Domain, "is_active", custom_is_active):
# Now, when you call is_active on Domain, it will return True
with self.assertRaises(TransitionNotAllowed):
application.reject()
def test_transition_not_allowed_started_ineligible(self):
"""Create an application with status started and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
with self.assertRaises(TransitionNotAllowed):
application.reject_with_prejudice()
def test_transition_not_allowed_submitted_ineligible(self):
"""Create an application with status submitted and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
with self.assertRaises(TransitionNotAllowed):
application.reject_with_prejudice()
def test_transition_not_allowed_action_needed_ineligible(self):
"""Create an application with status action needed and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
with self.assertRaises(TransitionNotAllowed):
application.reject_with_prejudice()
def test_transition_not_allowed_withdrawn_ineligible(self):
"""Create an application with status withdrawn and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
with self.assertRaises(TransitionNotAllowed):
application.reject_with_prejudice()
def test_transition_not_allowed_rejected_ineligible(self):
"""Create an application with status rejected and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
with self.assertRaises(TransitionNotAllowed):
application.reject_with_prejudice()
def test_transition_not_allowed_ineligible_ineligible(self):
"""Create an application with status ineligible and call reject
against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
with self.assertRaises(TransitionNotAllowed):
application.reject_with_prejudice()
self.approved_application.reject()
def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self):
"""Create an application with status approved, create a matching domain that
is active, and call reject_with_prejudice against transition rules"""
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
domain = Domain.objects.create(name=application.requested_domain.name)
application.approved_domain = domain
application.save()
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
self.approved_application.approved_domain = domain
self.approved_application.save()
# Define a custom implementation for is_active
def custom_is_active(self):
@ -531,7 +439,7 @@ class TestDomainApplication(TestCase):
with patch.object(Domain, "is_active", custom_is_active):
# Now, when you call is_active on Domain, it will return True
with self.assertRaises(TransitionNotAllowed):
application.reject_with_prejudice()
self.approved_application.reject_with_prejudice()
class TestPermissions(TestCase):
@ -674,12 +582,13 @@ class TestContact(TestCase):
def setUp(self):
self.email_for_invalid = "intern@igorville.gov"
self.invalid_user, _ = User.objects.get_or_create(
username=self.email_for_invalid, email=self.email_for_invalid, first_name="", last_name="")
username=self.email_for_invalid, email=self.email_for_invalid, first_name="", last_name=""
)
self.invalid_contact, _ = Contact.objects.get_or_create(user=self.invalid_user)
self.email = "mayor@igorville.gov"
self.existing_user, _ = User.objects.get_or_create(email=self.email, first_name="Jeff", last_name="Lebowski")
self.existing_contact, _ = Contact.objects.get_or_create(user=self.existing_user)
self.user, _ = User.objects.get_or_create(email=self.email, first_name="Jeff", last_name="Lebowski")
self.contact, _ = Contact.objects.get_or_create(user=self.user)
def tearDown(self):
super().tearDown()
@ -689,13 +598,14 @@ class TestContact(TestCase):
def test_saving_contact_updates_user_first_last_names(self):
"""When a contact is updated, we propagate the changes to the linked user if it exists."""
# User and Contact are created and linked as expected
# User and Contact are created and linked as expected.
# An empty User object should create an empty contact.
self.assertEqual(self.invalid_contact.first_name, "")
self.assertEqual(self.invalid_contact.last_name, "")
self.assertEqual(self.invalid_user.first_name, "")
self.assertEqual(self.invalid_user.last_name, "")
# Manually update the contact - mimicking production
# Manually update the contact - mimicking production (pre-existing data)
self.invalid_contact.first_name = "Joey"
self.invalid_contact.last_name = "Baloney"
self.invalid_contact.save()
@ -713,38 +623,39 @@ class TestContact(TestCase):
"""When a contact is updated, we avoid propagating the changes to the linked user if it already has a value"""
# User and Contact are created and linked as expected
self.assertEqual(self.existing_contact.first_name, "Jeff")
self.assertEqual(self.existing_contact.last_name, "Lebowski")
self.assertEqual(self.existing_user.first_name, "Jeff")
self.assertEqual(self.existing_user.last_name, "Lebowski")
self.assertEqual(self.contact.first_name, "Jeff")
self.assertEqual(self.contact.last_name, "Lebowski")
self.assertEqual(self.user.first_name, "Jeff")
self.assertEqual(self.user.last_name, "Lebowski")
self.existing_contact.first_name = "Joey"
self.existing_contact.last_name = "Baloney"
self.existing_contact.save()
self.contact.first_name = "Joey"
self.contact.last_name = "Baloney"
self.contact.save()
# Refresh the user object to reflect the changes made in the database
self.existing_user.refresh_from_db()
self.user.refresh_from_db()
# Updating the contact's first and last names propagate to the user
self.assertEqual(self.existing_contact.first_name, "Joey")
self.assertEqual(self.existing_contact.last_name, "Baloney")
self.assertEqual(self.existing_user.first_name, "Jeff")
self.assertEqual(self.existing_user.last_name, "Lebowski")
self.assertEqual(self.contact.first_name, "Joey")
self.assertEqual(self.contact.last_name, "Baloney")
self.assertEqual(self.user.first_name, "Jeff")
self.assertEqual(self.user.last_name, "Lebowski")
def test_saving_contact_does_not_update_user_email(self):
"""When a contact's email is updated, the change is not propagated to the lined user."""
self.existing_contact.email = "joey.baloney@diaperville.com"
self.existing_contact.save()
"""When a contact's email is updated, the change is not propagated to the user."""
self.contact.email = "joey.baloney@diaperville.com"
self.contact.save()
# Refresh the user object to reflect the changes made in the database
self.existing_user.refresh_from_db()
self.user.refresh_from_db()
# Updating the contact's email does not propagate
self.assertEqual(self.existing_contact.email, "joey.baloney@diaperville.com")
self.assertEqual(self.existing_user.email, "mayor@igorville.gov")
self.assertEqual(self.contact.email, "joey.baloney@diaperville.com")
self.assertEqual(self.user.email, "mayor@igorville.gov")
def test_saving_contact_does_not_update_user_email_when_none(self):
"""When a contact's email is updated, the change is not propagated to the lined user."""
"""When a contact's email is updated, and the first/last name is none,
the change is not propagated to the user."""
self.invalid_contact.email = "joey.baloney@diaperville.com"
self.invalid_contact.save()

View file

@ -761,7 +761,6 @@ class TestRegistrantContacts(MockEppLib):
self.assertEqual(expected_contact.email, actual_contact.email)
def test_convert_public_contact_to_epp(self):
self.maxDiff = None
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
dummy_contact = domain.get_default_security_contact()
test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__

View file

@ -152,7 +152,6 @@ class CsvReportsTest(TestCase):
@boto3_mocking.patching
def test_load_federal_report(self):
"""Tests the get_current_federal api endpoint"""
self.maxDiff = None
mock_client = MagicMock()
mock_client_instance = mock_client.return_value

View file

@ -257,7 +257,6 @@ class DomainApplicationTests(TestWithUser, WebTest):
ao_form["authorizing_official-last_name"] = "Tester ATO"
ao_form["authorizing_official-title"] = "Chief Tester"
ao_form["authorizing_official-email"] = "testy@town.com"
ao_form["authorizing_official-phone"] = "(201) 555 5555"
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -268,7 +267,6 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertEqual(application.authorizing_official.last_name, "Tester ATO")
self.assertEqual(application.authorizing_official.title, "Chief Tester")
self.assertEqual(application.authorizing_official.email, "testy@town.com")
self.assertEqual(application.authorizing_official.phone, "(201) 555 5555")
# the post request should return a redirect to the next form in
# the application
self.assertEqual(ao_result.status_code, 302)
@ -458,7 +456,6 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertContains(review_page, "Tester ATO")
self.assertContains(review_page, "Chief Tester")
self.assertContains(review_page, "testy@town.com")
self.assertContains(review_page, "(201) 555-5555")
self.assertContains(review_page, "city.com")
self.assertContains(review_page, "city.gov")
self.assertContains(review_page, "city1.gov")
@ -886,7 +883,6 @@ class DomainApplicationTests(TestWithUser, WebTest):
ao_form["authorizing_official-last_name"] = "Tester ATO"
ao_form["authorizing_official-title"] = "Chief Tester"
ao_form["authorizing_official-email"] = "testy@town.com"
ao_form["authorizing_official-phone"] = "(201) 555 5555"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
ao_result = ao_form.submit()
@ -2229,6 +2225,33 @@ class TestApplicationStatus(TestWithUser, WebTest):
home_page = self.app.get("/")
self.assertContains(home_page, "Withdrawn")
def test_application_withdraw_no_permissions(self):
"""Can't withdraw applications as a restricted user."""
self.user.status = User.RESTRICTED
self.user.save()
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)
application.save()
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Manage" link
detail_page = home_page.click("Manage", index=0)
self.assertContains(detail_page, "city.gov")
self.assertContains(detail_page, "city1.gov")
self.assertContains(detail_page, "Chief Tester")
self.assertContains(detail_page, "testy@town.com")
self.assertContains(detail_page, "Admin Tester")
self.assertContains(detail_page, "Status:")
# Restricted user trying to withdraw results in 403 error
with less_console_noise():
for url_name in [
"application-withdraw-confirmation",
"application-withdrawn",
]:
with self.subTest(url_name=url_name):
page = self.client.get(reverse(url_name, kwargs={"pk": application.pk}))
self.assertEqual(page.status_code, 403)
def test_application_status_no_permissions(self):
"""Can't access applications without being the creator."""
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)

View file

@ -13,7 +13,11 @@ from registrar.models import DomainApplication
from registrar.utility import StrEnum
from registrar.views.utility import StepsHelper
from .utility import DomainApplicationPermissionView, ApplicationWizardPermissionView
from .utility import (
DomainApplicationPermissionView,
DomainApplicationPermissionWithdrawView,
ApplicationWizardPermissionView,
)
logger = logging.getLogger(__name__)
@ -544,7 +548,7 @@ class ApplicationStatus(DomainApplicationPermissionView):
template_name = "application_status.html"
class ApplicationWithdrawConfirmation(DomainApplicationPermissionView):
class ApplicationWithdrawConfirmation(DomainApplicationPermissionWithdrawView):
"""This page will ask user to confirm if they want to withdraw
The DomainApplicationPermissionView restricts access so that only the
@ -554,7 +558,7 @@ class ApplicationWithdrawConfirmation(DomainApplicationPermissionView):
template_name = "application_withdraw_confirmation.html"
class ApplicationWithdrawn(DomainApplicationPermissionView):
class ApplicationWithdrawn(DomainApplicationPermissionWithdrawView):
# this view renders no template
template_name = ""

View file

@ -4,6 +4,7 @@ from .always_404 import always_404
from .permission_views import (
DomainPermissionView,
DomainApplicationPermissionView,
DomainApplicationPermissionWithdrawView,
DomainInvitationPermissionDeleteView,
ApplicationWizardPermissionView,
)

View file

@ -149,7 +149,8 @@ class PermissionsLoginMixin(PermissionRequiredMixin):
class DomainPermission(PermissionsLoginMixin):
"""Does the logged-in user have access to this domain?"""
"""Permission mixin that redirects to domain if user has access,
otherwise 403"""
def has_permission(self):
"""Check if this user has access to this domain.
@ -257,7 +258,8 @@ class DomainPermission(PermissionsLoginMixin):
class DomainApplicationPermission(PermissionsLoginMixin):
"""Does the logged-in user have access to this domain application?"""
"""Permission mixin that redirects to domain application if user
has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to this domain application.
@ -277,9 +279,33 @@ class DomainApplicationPermission(PermissionsLoginMixin):
return True
class DomainApplicationPermissionWithdraw(PermissionsLoginMixin):
"""Permission mixin that redirects to withdraw action on domain application
if user has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to withdraw this domain application."""
if not self.request.user.is_authenticated:
return False
# user needs to be the creator of the application
# this query is empty if there isn't a domain application with this
# id and this user as creator
if not DomainApplication.objects.filter(creator=self.request.user, id=self.kwargs["pk"]).exists():
return False
# Restricted users should not be able to withdraw domain requests
if self.request.user.is_restricted():
return False
return True
class ApplicationWizardPermission(PermissionsLoginMixin):
"""Does the logged-in user have permission to start or edit an application?"""
"""Permission mixin that redirects to start or edit domain application if
user has access, otherwise 403"""
def has_permission(self):
"""Check if this user has permission to start or edit an application.
@ -296,7 +322,8 @@ class ApplicationWizardPermission(PermissionsLoginMixin):
class DomainInvitationPermission(PermissionsLoginMixin):
"""Does the logged-in user have access to this domain invitation?
"""Permission mixin that redirects to domain invitation if user has
access, otherwise 403"
A user has access to a domain invitation if they have a role on the
associated domain.

View file

@ -8,6 +8,7 @@ from registrar.models import Domain, DomainApplication, DomainInvitation
from .mixins import (
DomainPermission,
DomainApplicationPermission,
DomainApplicationPermissionWithdraw,
DomainInvitationPermission,
ApplicationWizardPermission,
)
@ -74,6 +75,26 @@ class DomainApplicationPermissionView(DomainApplicationPermission, DetailView, a
raise NotImplementedError
class DomainApplicationPermissionWithdrawView(DomainApplicationPermissionWithdraw, DetailView, abc.ABC):
"""Abstract base view for domain application withdraw function
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
# DetailView property for what model this is viewing
model = DomainApplication
# variable name in template context for the model object
context_object_name = "domainapplication"
# Abstract property enforces NotImplementedError on an attribute.
@property
@abc.abstractmethod
def template_name(self):
raise NotImplementedError
class ApplicationWizardPermissionView(ApplicationWizardPermission, TemplateView, abc.ABC):
"""Abstract base view for the application form that enforces permissions

View file

@ -1,29 +1,29 @@
-i https://pypi.python.org/simple
annotated-types==0.6.0; python_version >= '3.8'
asgiref==3.7.2; python_version >= '3.7'
boto3==1.28.79; python_version >= '3.7'
botocore==1.31.79; python_version >= '3.7'
boto3==1.33.7; python_version >= '3.7'
botocore==1.33.7; python_version >= '3.7'
cachetools==5.3.2; python_version >= '3.7'
certifi==2023.7.22; python_version >= '3.6'
certifi==2023.11.17; python_version >= '3.6'
cfenv==0.5.3
cffi==1.16.0; python_version >= '3.8'
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
cryptography==41.0.6; python_version >= '3.7'
cryptography==41.0.7; python_version >= '3.7'
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
dj-database-url==2.1.0
dj-email-url==1.0.6
django==4.2.7; python_version >= '3.8'
django-allow-cidr==0.7.1
django-auditlog==2.3.0; python_version >= '3.7'
django-cache-url==3.4.4
django-cors-headers==4.3.0; python_version >= '3.8'
django-cache-url==3.4.5
django-cors-headers==4.3.1; python_version >= '3.8'
django-csp==3.7
django-fsm==2.8.1
django-login-required-middleware==0.9.0
django-phonenumber-field[phonenumberslite]==7.2.0; python_version >= '3.8'
django-widget-tweaks==1.5.0; python_version >= '3.8'
environs[django]==9.5.0; python_version >= '3.6'
faker==19.13.0; python_version >= '3.8'
faker==20.1.0; python_version >= '3.8'
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
furl==2.1.3
future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
@ -31,28 +31,28 @@ gevent==23.9.1; python_version >= '3.8'
geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4
greenlet==3.0.1; python_version >= '3.7'
gunicorn==21.2.0; python_version >= '3.5'
idna==3.4; python_version >= '3.5'
idna==3.6; python_version >= '3.5'
jmespath==1.0.1; python_version >= '3.7'
lxml==4.9.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
mako==1.2.4; python_version >= '3.7'
mako==1.3.0; python_version >= '3.8'
markupsafe==2.1.3; python_version >= '3.7'
marshmallow==3.20.1; python_version >= '3.8'
oic==1.6.1; python_version ~= '3.7'
orderedmultidict==1.0.1
packaging==23.2; python_version >= '3.7'
phonenumberslite==8.13.24
phonenumberslite==8.13.26
psycopg2-binary==2.9.9; python_version >= '3.7'
pycparser==2.21
pycryptodomex==3.19.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
pydantic==2.4.2; python_version >= '3.7'
pydantic-core==2.10.1; python_version >= '3.7'
pydantic-settings==2.0.3; python_version >= '3.7'
pydantic==2.5.2; python_version >= '3.7'
pydantic-core==2.14.5; python_version >= '3.7'
pydantic-settings==2.1.0; python_version >= '3.8'
pyjwkest==1.4.2
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
python-dotenv==1.0.0; python_version >= '3.8'
requests==2.31.0; python_version >= '3.7'
s3transfer==0.7.0; python_version >= '3.7'
setuptools==68.2.2; python_version >= '3.8'
s3transfer==0.8.2; python_version >= '3.7'
setuptools==69.0.2; 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'
sqlparse==0.4.4; python_version >= '3.5'
typing-extensions==4.8.0; python_version >= '3.8'