mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-16 17:47:02 +02:00
Merge remote-tracking branch 'origin/main' into rjm/998-domain-growth-export
This commit is contained in:
commit
9e7802c349
40 changed files with 1522 additions and 919 deletions
|
@ -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
499
src/Pipfile.lock
generated
|
@ -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": [
|
||||
|
|
|
@ -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,7 @@ 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.models.user import User
|
||||
from registrar.utility import csv_export
|
||||
from . import models
|
||||
from auditlog.models import LogEntry # type: ignore
|
||||
|
@ -45,7 +46,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):
|
||||
|
@ -58,10 +96,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):
|
||||
|
@ -118,15 +173,6 @@ class ListHeaderAdmin(AuditedAdmin):
|
|||
)
|
||||
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."""
|
||||
|
@ -221,6 +267,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):
|
||||
|
@ -489,12 +539,8 @@ 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)
|
||||
# Table ordering
|
||||
ordering = ["domain__name"]
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
"""Set the read-only state on form elements.
|
||||
|
@ -547,6 +593,27 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
|
||||
"""Custom domain applications admin class."""
|
||||
|
||||
class InvestigatorFilter(admin.SimpleListFilter):
|
||||
"""Custom investigator filter that only displays users with the manager role"""
|
||||
|
||||
title = "investigator"
|
||||
# Match the old param name to avoid unnecessary refactoring
|
||||
parameter_name = "investigator__id__exact"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
"""Lookup reimplementation, gets users of is_staff.
|
||||
Returns a list of tuples consisting of (user.id, user)
|
||||
"""
|
||||
privileged_users = User.objects.filter(is_staff=True).order_by("first_name", "last_name", "email")
|
||||
return [(user.id, user) for user in privileged_users]
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
"""Custom queryset implementation, filters by investigator"""
|
||||
if self.value() is None:
|
||||
return queryset
|
||||
else:
|
||||
return queryset.filter(investigator__id__exact=self.value())
|
||||
|
||||
# Columns
|
||||
list_display = [
|
||||
"requested_domain",
|
||||
|
@ -558,7 +625,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
]
|
||||
|
||||
# Filters
|
||||
list_filter = ("status", "organization_type", "investigator")
|
||||
list_filter = ("status", "organization_type", InvestigatorFilter)
|
||||
|
||||
# Search
|
||||
search_fields = [
|
||||
|
@ -634,6 +701,9 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
|
||||
filter_horizontal = ("current_websites", "alternative_domains", "other_contacts")
|
||||
|
||||
# Table ordering
|
||||
ordering = ["requested_domain__name"]
|
||||
|
||||
# lists in filter_horizontal are not sorted properly, sort them
|
||||
# by website
|
||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
|
@ -641,6 +711,13 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
kwargs["queryset"] = models.Website.objects.all().order_by("website") # Sort websites
|
||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
# Removes invalid investigator options from the investigator dropdown
|
||||
if db_field.name == "investigator":
|
||||
kwargs["queryset"] = User.objects.filter(is_staff=True)
|
||||
return db_field.formfield(**kwargs)
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
# Trigger action when a fieldset is changed
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj and obj.creator.status != models.User.RESTRICTED:
|
||||
|
@ -774,12 +851,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)
|
||||
|
@ -801,6 +893,10 @@ class DomainAdmin(ListHeaderAdmin):
|
|||
"expiration_date",
|
||||
]
|
||||
|
||||
# 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()
|
||||
|
||||
|
@ -815,6 +911,9 @@ class DomainAdmin(ListHeaderAdmin):
|
|||
change_list_template = "django/admin/domain_change_list.html"
|
||||
readonly_fields = ["state", "expiration_date", "first_ready_at", "deleted_at"]
|
||||
|
||||
# Table ordering
|
||||
ordering = ["name"]
|
||||
|
||||
def export_data_type(self, request):
|
||||
# match the CSV example with all the fields
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
|
|
|
@ -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.",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -29,7 +29,7 @@ class Command(BaseCommand):
|
|||
self.update_success = []
|
||||
self.update_skipped = []
|
||||
self.update_failed = []
|
||||
self.expiration_minimum_cutoff = date(2023, 11, 15)
|
||||
self.expiration_minimum_cutoff = date(2023, 11, 1)
|
||||
self.expiration_maximum_cutoff = date(2023, 12, 30)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-13 15:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registrar", "0056_alter_domain_state_alter_domainapplication_status_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="domainapplication",
|
||||
name="submission_date",
|
||||
field=models.DateField(blank=True, default=None, help_text="Date submitted", null=True),
|
||||
),
|
||||
]
|
|
@ -64,7 +64,7 @@ class Contact(TimeStampedModel):
|
|||
super().save(*args, **kwargs)
|
||||
|
||||
# Update the related User object's first_name and last_name
|
||||
if self.user:
|
||||
if self.user and (not self.user.first_name or not self.user.last_name):
|
||||
self.user.first_name = self.first_name
|
||||
self.user.last_name = self.last_name
|
||||
self.user.save()
|
||||
|
|
|
@ -8,6 +8,7 @@ from typing import Optional
|
|||
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
|
@ -201,6 +202,14 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
"""Get the `cr_date` element from the registry."""
|
||||
return self._get_property("cr_date")
|
||||
|
||||
@creation_date.setter # type: ignore
|
||||
def creation_date(self, cr_date: date):
|
||||
"""
|
||||
Direct setting of the creation date in the registry is not implemented.
|
||||
|
||||
Creation date can only be set by registry."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@Cache
|
||||
def last_transferred_date(self) -> date:
|
||||
"""Get the `tr_date` element from the registry."""
|
||||
|
@ -976,6 +985,16 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
def isActive(self):
|
||||
return self.state == Domain.State.CREATED
|
||||
|
||||
def is_expired(self):
|
||||
"""
|
||||
Check if the domain's expiration date is in the past.
|
||||
Returns True if expired, False otherwise.
|
||||
"""
|
||||
if self.expiration_date is None:
|
||||
return True
|
||||
now = timezone.now().date()
|
||||
return self.expiration_date < now
|
||||
|
||||
def map_epp_contact_to_public_contact(self, contact: eppInfo.InfoContactResultData, contact_id, contact_type):
|
||||
"""Maps the Epp contact representation to a PublicContact object.
|
||||
|
||||
|
@ -1601,38 +1620,11 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False):
|
||||
"""Contact registry for info about a domain."""
|
||||
try:
|
||||
# get info from registry
|
||||
data_response = self._get_or_create_domain()
|
||||
cache = self._extract_data_from_response(data_response)
|
||||
|
||||
# remove null properties (to distinguish between "a value of None" and null)
|
||||
cleaned = self._remove_null_properties(cache)
|
||||
|
||||
if "statuses" in cleaned:
|
||||
cleaned["statuses"] = [status.state for status in cleaned["statuses"]]
|
||||
|
||||
cleaned["dnssecdata"] = self._get_dnssec_data(data_response.extensions)
|
||||
|
||||
# Capture and store old hosts and contacts from cache if they exist
|
||||
old_cache_hosts = self._cache.get("hosts")
|
||||
old_cache_contacts = self._cache.get("contacts")
|
||||
|
||||
if fetch_contacts:
|
||||
cleaned["contacts"] = self._get_contacts(cleaned.get("_contacts", []))
|
||||
if old_cache_hosts is not None:
|
||||
logger.debug("resetting cleaned['hosts'] to old_cache_hosts")
|
||||
cleaned["hosts"] = old_cache_hosts
|
||||
|
||||
if fetch_hosts:
|
||||
cleaned["hosts"] = self._get_hosts(cleaned.get("_hosts", []))
|
||||
if old_cache_contacts is not None:
|
||||
cleaned["contacts"] = old_cache_contacts
|
||||
|
||||
# if expiration date from registry does not match what is in db,
|
||||
# update the db
|
||||
if "ex_date" in cleaned and cleaned["ex_date"] != self.expiration_date:
|
||||
self.expiration_date = cleaned["ex_date"]
|
||||
self.save()
|
||||
cleaned = self._clean_cache(cache, data_response)
|
||||
self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts)
|
||||
self._update_dates(cleaned)
|
||||
|
||||
self._cache = cleaned
|
||||
|
||||
|
@ -1640,6 +1632,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
logger.error(e)
|
||||
|
||||
def _extract_data_from_response(self, data_response):
|
||||
"""extract data from response from registry"""
|
||||
data = data_response.res_data[0]
|
||||
return {
|
||||
"auth_info": getattr(data, "auth_info", ...),
|
||||
|
@ -1654,6 +1647,15 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
"up_date": getattr(data, "up_date", ...),
|
||||
}
|
||||
|
||||
def _clean_cache(self, cache, data_response):
|
||||
"""clean up the cache"""
|
||||
# remove null properties (to distinguish between "a value of None" and null)
|
||||
cleaned = self._remove_null_properties(cache)
|
||||
if "statuses" in cleaned:
|
||||
cleaned["statuses"] = [status.state for status in cleaned["statuses"]]
|
||||
cleaned["dnssecdata"] = self._get_dnssec_data(data_response.extensions)
|
||||
return cleaned
|
||||
|
||||
def _remove_null_properties(self, cache):
|
||||
return {k: v for k, v in cache.items() if v is not ...}
|
||||
|
||||
|
@ -1667,6 +1669,42 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
dnssec_data = extension
|
||||
return dnssec_data
|
||||
|
||||
def _update_hosts_and_contacts(self, cleaned, fetch_hosts, fetch_contacts):
|
||||
"""Capture and store old hosts and contacts from cache if they don't exist"""
|
||||
old_cache_hosts = self._cache.get("hosts")
|
||||
old_cache_contacts = self._cache.get("contacts")
|
||||
|
||||
if fetch_contacts:
|
||||
cleaned["contacts"] = self._get_contacts(cleaned.get("_contacts", []))
|
||||
if old_cache_hosts is not None:
|
||||
logger.debug("resetting cleaned['hosts'] to old_cache_hosts")
|
||||
cleaned["hosts"] = old_cache_hosts
|
||||
|
||||
if fetch_hosts:
|
||||
cleaned["hosts"] = self._get_hosts(cleaned.get("_hosts", []))
|
||||
if old_cache_contacts is not None:
|
||||
cleaned["contacts"] = old_cache_contacts
|
||||
|
||||
def _update_dates(self, cleaned):
|
||||
"""Update dates (expiration and creation) from cleaned"""
|
||||
requires_save = False
|
||||
|
||||
# if expiration date from registry does not match what is in db,
|
||||
# update the db
|
||||
if "ex_date" in cleaned and cleaned["ex_date"] != self.expiration_date:
|
||||
self.expiration_date = cleaned["ex_date"]
|
||||
requires_save = True
|
||||
|
||||
# if creation_date from registry does not match what is in db,
|
||||
# update the db
|
||||
if "cr_date" in cleaned and cleaned["cr_date"] != self.created_at:
|
||||
self.created_at = cleaned["cr_date"]
|
||||
requires_save = True
|
||||
|
||||
# if either registration date or creation date need updating
|
||||
if requires_save:
|
||||
self.save()
|
||||
|
||||
def _get_contacts(self, contacts):
|
||||
choices = PublicContact.ContactTypeChoices
|
||||
# We expect that all these fields get populated,
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
from django.apps import apps
|
||||
from django.db import models
|
||||
from django_fsm import FSMField, transition # type: ignore
|
||||
from django.utils import timezone
|
||||
from registrar.models.domain import Domain
|
||||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
|
@ -122,7 +123,7 @@ class DomainApplication(TimeStampedModel):
|
|||
|
||||
FEDERAL = (
|
||||
"federal",
|
||||
"Federal: an agency of the U.S. government's executive, legislative, or judicial branches",
|
||||
"Federal: an agency of the U.S. government’s legislative, executive, or judicial branches",
|
||||
)
|
||||
INTERSTATE = "interstate", "Interstate: an organization of two or more states"
|
||||
STATE_OR_TERRITORY = (
|
||||
|
@ -139,7 +140,7 @@ class DomainApplication(TimeStampedModel):
|
|||
CITY = "city", "City: a city, town, township, village, etc."
|
||||
SPECIAL_DISTRICT = (
|
||||
"special_district",
|
||||
"Special district: an independent organization within a single state",
|
||||
"Special district: an independent government that delivers specialized, essential services",
|
||||
)
|
||||
SCHOOL_DISTRICT = (
|
||||
"school_district",
|
||||
|
@ -547,6 +548,14 @@ class DomainApplication(TimeStampedModel):
|
|||
help_text="Acknowledged .gov acceptable use policy",
|
||||
)
|
||||
|
||||
# submission date records when application is submitted
|
||||
submission_date = models.DateField(
|
||||
null=True,
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Date submitted",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
if self.requested_domain and self.requested_domain.name:
|
||||
|
@ -585,7 +594,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):
|
||||
|
@ -607,13 +621,27 @@ class DomainApplication(TimeStampedModel):
|
|||
if not DraftDomain.string_could_be_domain(self.requested_domain.name):
|
||||
raise ValueError("Requested domain is not a valid domain name.")
|
||||
|
||||
# Update submission_date to today
|
||||
self.submission_date = timezone.now().date()
|
||||
self.save()
|
||||
|
||||
self._send_status_update_email(
|
||||
"submission confirmation",
|
||||
"emails/submission_confirmation.txt",
|
||||
"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 +655,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 +679,8 @@ class DomainApplication(TimeStampedModel):
|
|||
source=[
|
||||
ApplicationStatus.SUBMITTED,
|
||||
ApplicationStatus.IN_REVIEW,
|
||||
ApplicationStatus.ACTION_NEEDED,
|
||||
ApplicationStatus.REJECTED,
|
||||
ApplicationStatus.INELIGIBLE,
|
||||
],
|
||||
target=ApplicationStatus.APPROVED,
|
||||
)
|
||||
|
@ -684,7 +717,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 +730,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,13 +740,17 @@ 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:
|
||||
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.save()
|
||||
self.approved_domain.delete()
|
||||
self.approved_domain = None
|
||||
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.save()
|
||||
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",
|
||||
|
@ -723,7 +760,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],
|
||||
)
|
||||
|
@ -737,13 +779,17 @@ class DomainApplication(TimeStampedModel):
|
|||
and domain_information (will cascade) when they exist."""
|
||||
|
||||
if self.status == self.ApplicationStatus.APPROVED:
|
||||
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.save()
|
||||
self.approved_domain.delete()
|
||||
self.approved_domain = None
|
||||
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.save()
|
||||
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()
|
||||
|
||||
|
|
|
@ -229,6 +229,7 @@ class DomainInformation(TimeStampedModel):
|
|||
da_dict.pop("alternative_domains", None)
|
||||
da_dict.pop("requested_domain", None)
|
||||
da_dict.pop("approved_domain", None)
|
||||
da_dict.pop("submission_date", None)
|
||||
other_contacts = da_dict.pop("other_contacts", [])
|
||||
domain_info = cls(**da_dict)
|
||||
domain_info.domain_application = domain_application
|
||||
|
|
|
@ -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}")
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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>.
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
Who is the authorizing official for your organization?
|
||||
</h2>
|
||||
|
||||
<p>Your authorizing official is a person within your organization who can authorize your domain request. This person must be in a role of significant, executive responsibility within the organization.</p>
|
||||
{% if not is_federal %}
|
||||
<p>Your authorizing official is a person within your organization who can authorize your domain request. This person must be in a role of significant, executive responsibility within the organization.</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="ao_example">
|
||||
{% include "includes/ao_example.html" %}
|
||||
|
@ -25,17 +27,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 %}
|
||||
|
|
|
@ -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>We’ll 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> We’ll review your request. This usually takes 20 business days. During
|
||||
this review we’ll 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> We’ll 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 %}
|
||||
|
|
|
@ -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 won’t be able to make further edits until it’s reviewed by our staff. You’ll 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 won’t be able to edit it until we review it. You’ll only be able to withdraw your request." modal_button=modal_button|safe %}
|
||||
</div>
|
||||
|
||||
{% block after_form_content %}{% endblock %}
|
||||
|
|
|
@ -2,5 +2,7 @@
|
|||
{% load static field_helpers %}
|
||||
|
||||
{% block form_fields %}
|
||||
{% input_with_errors forms.0.no_other_contacts_rationale %}
|
||||
{% with attr_maxlength=1000 %}
|
||||
{% input_with_errors forms.0.no_other_contacts_rationale %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="margin-top-4 tablet:grid-col-10">
|
||||
|
||||
<div
|
||||
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%} dotgov-status-box--action-need{% endif %}"
|
||||
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2{% if not domain.is_expired %}{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} dotgov-status-box--action-need{% endif %}{% endif %}"
|
||||
role="region"
|
||||
aria-labelledby="summary-box-key-information"
|
||||
>
|
||||
|
@ -17,7 +17,9 @@
|
|||
<span class="text-bold text-primary-darker">
|
||||
Status:
|
||||
</span>
|
||||
{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%}
|
||||
{% if domain.is_expired %}
|
||||
Expired
|
||||
{% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%}
|
||||
DNS needed
|
||||
{% else %}
|
||||
{{ domain.state|title }}
|
||||
|
@ -26,13 +28,16 @@
|
|||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
||||
{% include "includes/domain_dates.html" %}
|
||||
|
||||
{% url 'domain-dns-nameservers' pk=domain.id as url %}
|
||||
{% if domain.nameservers|length > 0 %}
|
||||
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value=domain.nameservers list='true' edit_link=url editable=domain.is_editable %}
|
||||
{% else %}
|
||||
{% if domain.is_editable %}
|
||||
<h2 class="margin-top-neg-1"> DNS name servers </h2>
|
||||
<h2 class="margin-top-3"> DNS name servers </h2>
|
||||
<p> No DNS name servers have been added yet. Before your domain can be used we’ll need information about your domain name servers.</p>
|
||||
<a class="usa-button margin-bottom-1" href="{{url}}"> Add DNS name servers </a>
|
||||
{% else %}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<p>You can enter your name servers, as well as other DNS-related information, in the following sections:</p>
|
||||
|
||||
{% url 'domain-dns-nameservers' pk=domain.id as url %}
|
||||
<ul>
|
||||
<ul class="usa-list">
|
||||
<li><a href="{{ url }}">Name servers</a></li>
|
||||
|
||||
{% url 'domain-dns-dnssec' pk=domain.id as url %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
||||
Hi.
|
||||
|
||||
{{ full_name }} has added you as a manager on {{ domain.name }}.
|
||||
{{ requester_email }} has added you as a manager on {{ domain.name }}.
|
||||
|
||||
YOU NEED A LOGIN.GOV ACCOUNT
|
||||
You’ll need a Login.gov account to manage your .gov domain. Login.gov provides
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th data-sortable scope="col" role="columnheader">Domain name</th>
|
||||
<th data-sortable scope="col" role="columnheader">Date created</th>
|
||||
<th data-sortable scope="col" role="columnheader">Expires</th>
|
||||
<th data-sortable scope="col" role="columnheader">Status</th>
|
||||
<th scope="col" role="columnheader"><span class="usa-sr-only">Action</span></th>
|
||||
</tr>
|
||||
|
@ -50,9 +50,11 @@
|
|||
<th th scope="row" role="rowheader" data-label="Domain name">
|
||||
{{ domain.name }}
|
||||
</th>
|
||||
<td data-sort-value="{{ domain.created_time|date:"U" }}" data-label="Date created">{{ domain.created_time|date }}</td>
|
||||
<td data-sort-value="{{ domain.expiration_date|date:"U" }}" data-label="Expires">{{ domain.expiration_date|date }}</td>
|
||||
<td data-label="Status">
|
||||
{% if domain.state == "unknown" or domain.state == "dns needed"%}
|
||||
{% if domain.is_expired %}
|
||||
Expired
|
||||
{% elif domain.state == "unknown" or domain.state == "dns needed"%}
|
||||
DNS needed
|
||||
{% else %}
|
||||
{{ domain.state|title }}
|
||||
|
@ -99,7 +101,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th data-sortable scope="col" role="columnheader">Domain name</th>
|
||||
<th data-sortable scope="col" role="columnheader">Date created</th>
|
||||
<th data-sortable scope="col" role="columnheader">Date submitted</th>
|
||||
<th data-sortable scope="col" role="columnheader">Status</th>
|
||||
<th scope="col" role="columnheader"><span class="usa-sr-only">Action</span></th>
|
||||
</tr>
|
||||
|
@ -110,7 +112,13 @@
|
|||
<th th scope="row" role="rowheader" data-label="Domain name">
|
||||
{{ application.requested_domain.name|default:"New domain request" }}
|
||||
</th>
|
||||
<td data-sort-value="{{ application.created_at|date:"U" }}" data-label="Date created">{{ application.created_at|date }}</td>
|
||||
<td data-sort-value="{{ application.submission_date|date:"U" }}" data-label="Date submitted">
|
||||
{% if application.submission_date %}
|
||||
{{ application.submission_date|date }}
|
||||
{% else %}
|
||||
<span class="text-base">Not submitted</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td data-label="Status">{{ application.get_status_display }}</td>
|
||||
<td>
|
||||
{% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %}
|
||||
|
|
|
@ -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. We’ll 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. We’ll 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?
|
||||
|
|
12
src/registrar/templates/includes/domain_dates.html
Normal file
12
src/registrar/templates/includes/domain_dates.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% if domain.expiration_date or domain.created_at %}
|
||||
<p class="margin-y-0">
|
||||
{% if domain.expiration_date %}
|
||||
<strong class="text-primary-dark">Expires:</strong>
|
||||
{{ domain.expiration_date|date }}
|
||||
{% if domain.is_expired %} <span class="text-error"><strong>(expired)</strong></span>{% endif %}
|
||||
<br/>
|
||||
{% endif %}
|
||||
{% if domain.created_at %}
|
||||
<strong class="text-primary-dark">Date created:</strong> {{ domain.created_at|date }}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
|
@ -462,7 +462,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",
|
||||
|
|
|
@ -15,13 +15,7 @@ from registrar.admin import (
|
|||
ContactAdmin,
|
||||
UserDomainRoleAdmin,
|
||||
)
|
||||
from registrar.models import (
|
||||
Domain,
|
||||
DomainApplication,
|
||||
DomainInformation,
|
||||
User,
|
||||
DomainInvitation,
|
||||
)
|
||||
from registrar.models import Domain, DomainApplication, DomainInformation, User, DomainInvitation, Contact, Website
|
||||
from registrar.models.user_domain_role import UserDomainRole
|
||||
from .common import (
|
||||
completed_application,
|
||||
|
@ -322,6 +316,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
|||
self.admin = DomainApplicationAdmin(model=DomainApplication, admin_site=self.site)
|
||||
self.superuser = create_superuser()
|
||||
self.staffuser = create_user()
|
||||
self.client = Client(HTTP_HOST="localhost:8080")
|
||||
|
||||
def test_short_org_name_in_applications_list(self):
|
||||
"""
|
||||
|
@ -623,6 +618,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
|||
"no_other_contacts_rationale",
|
||||
"anything_else",
|
||||
"is_policy_acknowledged",
|
||||
"submission_date",
|
||||
"current_websites",
|
||||
"other_contacts",
|
||||
"alternative_domains",
|
||||
|
@ -842,12 +838,224 @@ class TestDomainApplicationAdmin(MockEppLib):
|
|||
with self.assertRaises(DomainInformation.DoesNotExist):
|
||||
domain_information.refresh_from_db()
|
||||
|
||||
def test_has_correct_filters(self):
|
||||
"""
|
||||
This test verifies that DomainApplicationAdmin has the correct filters set up.
|
||||
|
||||
It retrieves the current list of filters from DomainApplicationAdmin
|
||||
and checks that it matches the expected list of filters.
|
||||
"""
|
||||
request = self.factory.get("/")
|
||||
request.user = self.superuser
|
||||
|
||||
# Grab the current list of table filters
|
||||
readonly_fields = self.admin.get_list_filter(request)
|
||||
expected_fields = ("status", "organization_type", DomainApplicationAdmin.InvestigatorFilter)
|
||||
|
||||
self.assertEqual(readonly_fields, expected_fields)
|
||||
|
||||
def test_table_sorted_alphabetically(self):
|
||||
"""
|
||||
This test verifies that the DomainApplicationAdmin table is sorted alphabetically
|
||||
by the 'requested_domain__name' field.
|
||||
|
||||
It creates a list of DomainApplication instances in a non-alphabetical order,
|
||||
then retrieves the queryset from the DomainApplicationAdmin and checks
|
||||
that it matches the expected queryset,
|
||||
which is sorted alphabetically by the 'requested_domain__name' field.
|
||||
"""
|
||||
# Creates a list of DomainApplications in scrambled order
|
||||
multiple_unalphabetical_domain_objects("application")
|
||||
|
||||
request = self.factory.get("/")
|
||||
request.user = self.superuser
|
||||
|
||||
# Get the expected list of alphabetically sorted DomainApplications
|
||||
expected_order = DomainApplication.objects.order_by("requested_domain__name")
|
||||
|
||||
# Get the returned queryset
|
||||
queryset = self.admin.get_queryset(request)
|
||||
|
||||
# Check the order
|
||||
self.assertEqual(
|
||||
list(queryset),
|
||||
list(expected_order),
|
||||
)
|
||||
|
||||
def test_displays_investigator_filter(self):
|
||||
"""
|
||||
This test verifies that the investigator filter in the admin interface for
|
||||
the DomainApplication model displays correctly.
|
||||
|
||||
It creates two DomainApplication instances, each with a different investigator.
|
||||
It then simulates a staff user logging in and applying the investigator filter
|
||||
on the DomainApplication admin page.
|
||||
|
||||
We then test if the page displays the filter we expect, but we do not test
|
||||
if we get back the correct response in the table. This is to isolate if
|
||||
the filter displays correctly, when the filter isn't filtering correctly.
|
||||
"""
|
||||
|
||||
# Create a mock DomainApplication object, with a fake investigator
|
||||
application: DomainApplication = generic_domain_object("application", "SomeGuy")
|
||||
investigator_user = User.objects.filter(username=application.investigator.username).get()
|
||||
investigator_user.is_staff = True
|
||||
investigator_user.save()
|
||||
|
||||
p = "userpass"
|
||||
self.client.login(username="staffuser", password=p)
|
||||
response = self.client.get(
|
||||
"/admin/registrar/domainapplication/",
|
||||
{
|
||||
"investigator__id__exact": investigator_user.id,
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
# Then, test if the filter actually exists
|
||||
self.assertIn("filters", response.context)
|
||||
|
||||
# Assert the content of filters and search_query
|
||||
filters = response.context["filters"]
|
||||
|
||||
self.assertEqual(
|
||||
filters,
|
||||
[
|
||||
{
|
||||
"parameter_name": "investigator",
|
||||
"parameter_value": "SomeGuy first_name:investigator SomeGuy last_name:investigator",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def test_investigator_filter_filters_correctly(self):
|
||||
"""
|
||||
This test verifies that the investigator filter in the admin interface for
|
||||
the DomainApplication model works correctly.
|
||||
|
||||
It creates two DomainApplication instances, each with a different investigator.
|
||||
It then simulates a staff user logging in and applying the investigator filter
|
||||
on the DomainApplication admin page.
|
||||
|
||||
It then verifies that it was applied correctly.
|
||||
The test checks that the response contains the expected DomainApplication pbjects
|
||||
in the table.
|
||||
"""
|
||||
|
||||
# Create a mock DomainApplication object, with a fake investigator
|
||||
application: DomainApplication = generic_domain_object("application", "SomeGuy")
|
||||
investigator_user = User.objects.filter(username=application.investigator.username).get()
|
||||
investigator_user.is_staff = True
|
||||
investigator_user.save()
|
||||
|
||||
# Create a second mock DomainApplication object, to test filtering
|
||||
application: DomainApplication = generic_domain_object("application", "BadGuy")
|
||||
another_user = User.objects.filter(username=application.investigator.username).get()
|
||||
another_user.is_staff = True
|
||||
another_user.save()
|
||||
|
||||
p = "userpass"
|
||||
self.client.login(username="staffuser", password=p)
|
||||
response = self.client.get(
|
||||
"/admin/registrar/domainapplication/",
|
||||
{
|
||||
"investigator__id__exact": investigator_user.id,
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
expected_name = "SomeGuy first_name:investigator SomeGuy last_name:investigator"
|
||||
# We expect to see this four times, two of them are from the html for the filter,
|
||||
# and the other two are the html from the list entry in the table.
|
||||
self.assertContains(response, expected_name, count=4)
|
||||
|
||||
# Check that we don't also get the thing we aren't filtering for.
|
||||
# We expect to see this two times in the filter
|
||||
unexpected_name = "BadGuy first_name:investigator BadGuy last_name:investigator"
|
||||
self.assertContains(response, unexpected_name, count=2)
|
||||
|
||||
def test_investigator_dropdown_displays_only_staff(self):
|
||||
"""
|
||||
This test verifies that the dropdown for the 'investigator' field in the DomainApplicationAdmin
|
||||
interface only displays users who are marked as staff.
|
||||
|
||||
It creates two DomainApplication instances, one with an investigator
|
||||
who is a staff user and another with an investigator who is not a staff user.
|
||||
|
||||
It then retrieves the queryset for the 'investigator' dropdown from DomainApplicationAdmin
|
||||
and checks that it matches the expected queryset, which only includes staff users.
|
||||
"""
|
||||
# Create a mock DomainApplication object, with a fake investigator
|
||||
application: DomainApplication = generic_domain_object("application", "SomeGuy")
|
||||
investigator_user = User.objects.filter(username=application.investigator.username).get()
|
||||
investigator_user.is_staff = True
|
||||
investigator_user.save()
|
||||
|
||||
# Create a mock DomainApplication object, with a user that is not staff
|
||||
application_2: DomainApplication = generic_domain_object("application", "SomeOtherGuy")
|
||||
investigator_user_2 = User.objects.filter(username=application_2.investigator.username).get()
|
||||
investigator_user_2.is_staff = False
|
||||
investigator_user_2.save()
|
||||
|
||||
p = "userpass"
|
||||
self.client.login(username="staffuser", password=p)
|
||||
|
||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||
|
||||
# Get the actual field from the model's meta information
|
||||
investigator_field = DomainApplication._meta.get_field("investigator")
|
||||
|
||||
# We should only be displaying staff users, in alphabetical order
|
||||
expected_dropdown = list(User.objects.filter(is_staff=True))
|
||||
current_dropdown = list(self.admin.formfield_for_foreignkey(investigator_field, request).queryset)
|
||||
|
||||
self.assertEqual(expected_dropdown, current_dropdown)
|
||||
|
||||
# Non staff users should not be in the list
|
||||
self.assertNotIn(application_2, current_dropdown)
|
||||
|
||||
def test_investigator_list_is_alphabetically_sorted(self):
|
||||
"""
|
||||
This test verifies that filter list for the 'investigator'
|
||||
is displayed alphabetically
|
||||
"""
|
||||
# Create a mock DomainApplication object, with a fake investigator
|
||||
application: DomainApplication = generic_domain_object("application", "SomeGuy")
|
||||
investigator_user = User.objects.filter(username=application.investigator.username).get()
|
||||
investigator_user.is_staff = True
|
||||
investigator_user.save()
|
||||
|
||||
application_2: DomainApplication = generic_domain_object("application", "AGuy")
|
||||
investigator_user_2 = User.objects.filter(username=application_2.investigator.username).get()
|
||||
investigator_user_2.first_name = "AGuy"
|
||||
investigator_user_2.is_staff = True
|
||||
investigator_user_2.save()
|
||||
|
||||
application_3: DomainApplication = generic_domain_object("application", "FinalGuy")
|
||||
investigator_user_3 = User.objects.filter(username=application_3.investigator.username).get()
|
||||
investigator_user_3.first_name = "FinalGuy"
|
||||
investigator_user_3.is_staff = True
|
||||
investigator_user_3.save()
|
||||
|
||||
p = "userpass"
|
||||
self.client.login(username="staffuser", password=p)
|
||||
request = RequestFactory().get("/")
|
||||
|
||||
expected_list = list(User.objects.filter(is_staff=True).order_by("first_name", "last_name", "email"))
|
||||
|
||||
# Get the actual sorted list of investigators from the lookups method
|
||||
actual_list = [item for _, item in self.admin.InvestigatorFilter.lookups(self, request, self.admin)]
|
||||
|
||||
self.assertEqual(expected_list, actual_list)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
Domain.objects.all().delete()
|
||||
DomainInformation.objects.all().delete()
|
||||
DomainApplication.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
Contact.objects.all().delete()
|
||||
Website.objects.all().delete()
|
||||
|
||||
|
||||
class DomainInvitationAdminTest(TestCase):
|
||||
|
@ -1110,8 +1318,8 @@ class AuditedAdminTest(TestCase):
|
|||
tested_fields = [
|
||||
DomainApplication.authorizing_official.field,
|
||||
DomainApplication.submitter.field,
|
||||
# DomainApplication.investigator.field,
|
||||
# DomainApplication.creator.field,
|
||||
DomainApplication.investigator.field,
|
||||
DomainApplication.creator.field,
|
||||
DomainApplication.requested_domain.field,
|
||||
]
|
||||
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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"""
|
||||
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||
|
||||
with self.assertRaises(TransitionNotAllowed):
|
||||
application.submit()
|
||||
|
||||
def test_transition_not_allowed_in_review_submitted(self):
|
||||
"""Create an application with status in review and call submit
|
||||
against transition rules"""
|
||||
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||
|
||||
with self.assertRaises(TransitionNotAllowed):
|
||||
application.submit()
|
||||
|
||||
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):
|
||||
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"""
|
||||
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||
|
||||
with self.assertRaises(TransitionNotAllowed):
|
||||
application.in_review()
|
||||
|
||||
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):
|
||||
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"""
|
||||
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||
|
||||
with self.assertRaises(TransitionNotAllowed):
|
||||
application.action_needed()
|
||||
|
||||
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):
|
||||
application.approve()
|
||||
|
||||
def test_transition_not_allowed_approved_approved(self):
|
||||
"""Create an application with status approved and call approve
|
||||
against transition rules"""
|
||||
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||
|
||||
with self.assertRaises(TransitionNotAllowed):
|
||||
application.approve()
|
||||
|
||||
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):
|
||||
application.withdraw()
|
||||
|
||||
def test_transition_not_allowed_approved_withdrawn(self):
|
||||
"""Create an application with status approved and call withdraw
|
||||
against transition rules"""
|
||||
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||
|
||||
with self.assertRaises(TransitionNotAllowed):
|
||||
application.withdraw()
|
||||
|
||||
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):
|
||||
application.reject()
|
||||
|
||||
def test_transition_not_allowed_submitted_rejected(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()
|
||||
|
||||
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()
|
||||
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),
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
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_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),
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
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_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),
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
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_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),
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
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_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),
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
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_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),
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
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_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),
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
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):
|
||||
|
@ -672,6 +580,12 @@ class TestUser(TestCase):
|
|||
|
||||
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=""
|
||||
)
|
||||
self.invalid_contact, _ = Contact.objects.get_or_create(user=self.invalid_user)
|
||||
|
||||
self.email = "mayor@igorville.gov"
|
||||
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)
|
||||
|
@ -683,6 +597,31 @@ 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.
|
||||
# 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 (pre-existing data)
|
||||
self.invalid_contact.first_name = "Joey"
|
||||
self.invalid_contact.last_name = "Baloney"
|
||||
self.invalid_contact.save()
|
||||
|
||||
# Refresh the user object to reflect the changes made in the database
|
||||
self.invalid_user.refresh_from_db()
|
||||
|
||||
# Updating the contact's first and last names propagate to the user
|
||||
self.assertEqual(self.invalid_contact.first_name, "Joey")
|
||||
self.assertEqual(self.invalid_contact.last_name, "Baloney")
|
||||
self.assertEqual(self.invalid_user.first_name, "Joey")
|
||||
self.assertEqual(self.invalid_user.last_name, "Baloney")
|
||||
|
||||
def test_saving_contact_does_not_update_user_first_last_names(self):
|
||||
"""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.contact.first_name, "Jeff")
|
||||
self.assertEqual(self.contact.last_name, "Lebowski")
|
||||
|
@ -699,11 +638,11 @@ class TestContact(TestCase):
|
|||
# Updating the contact's first and last names propagate to the user
|
||||
self.assertEqual(self.contact.first_name, "Joey")
|
||||
self.assertEqual(self.contact.last_name, "Baloney")
|
||||
self.assertEqual(self.user.first_name, "Joey")
|
||||
self.assertEqual(self.user.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."""
|
||||
"""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()
|
||||
|
||||
|
@ -713,3 +652,16 @@ class TestContact(TestCase):
|
|||
# Updating the contact's email does not propagate
|
||||
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, 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()
|
||||
|
||||
# Refresh the user object to reflect the changes made in the database
|
||||
self.invalid_user.refresh_from_db()
|
||||
|
||||
# Updating the contact's email does not propagate
|
||||
self.assertEqual(self.invalid_contact.email, "joey.baloney@diaperville.com")
|
||||
self.assertEqual(self.invalid_user.email, "intern@igorville.gov")
|
||||
|
|
|
@ -789,7 +789,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__
|
||||
|
@ -1995,6 +1994,9 @@ class TestExpirationDate(MockEppLib):
|
|||
"""
|
||||
super().setUp()
|
||||
# for the tests, need a domain in the ready state
|
||||
# mock data for self.domain includes the following dates:
|
||||
# cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35)
|
||||
# ex_date=datetime.date(2023, 5, 25)
|
||||
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||
# for the test, need a domain that will raise an exception
|
||||
self.domain_w_error, _ = Domain.objects.get_or_create(name="fake-error.gov", state=Domain.State.READY)
|
||||
|
@ -2020,6 +2022,23 @@ class TestExpirationDate(MockEppLib):
|
|||
with self.assertRaises(RegistryError):
|
||||
self.domain_w_error.renew_domain()
|
||||
|
||||
def test_is_expired(self):
|
||||
"""assert that is_expired returns true for expiration_date in past"""
|
||||
# force fetch_cache to be called
|
||||
self.domain.statuses
|
||||
self.assertTrue(self.domain.is_expired)
|
||||
|
||||
def test_is_not_expired(self):
|
||||
"""assert that is_expired returns false for expiration in future"""
|
||||
# to do this, need to mock value returned from timezone.now
|
||||
# set now to 2023-01-01
|
||||
mocked_datetime = datetime.datetime(2023, 1, 1, 12, 0, 0)
|
||||
# force fetch_cache which sets the expiration date to 2023-05-25
|
||||
self.domain.statuses
|
||||
|
||||
with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime):
|
||||
self.assertFalse(self.domain.is_expired())
|
||||
|
||||
def test_expiration_date_updated_on_info_domain_call(self):
|
||||
"""assert that expiration date in db is updated on info domain call"""
|
||||
# force fetch_cache to be called
|
||||
|
@ -2028,6 +2047,36 @@ class TestExpirationDate(MockEppLib):
|
|||
self.assertEquals(self.domain.expiration_date, test_date)
|
||||
|
||||
|
||||
class TestCreationDate(MockEppLib):
|
||||
"""Created_at in domain model is updated from EPP"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Domain exists in registry
|
||||
"""
|
||||
super().setUp()
|
||||
# for the tests, need a domain with a creation date
|
||||
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||
# creation_date returned from mockDataInfoDomain with creation date:
|
||||
# cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35)
|
||||
self.creation_date = datetime.datetime(2023, 5, 25, 19, 45, 35)
|
||||
|
||||
def tearDown(self):
|
||||
Domain.objects.all().delete()
|
||||
super().tearDown()
|
||||
|
||||
def test_creation_date_setter_not_implemented(self):
|
||||
"""assert that the setter for creation date is not implemented and will raise error"""
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.domain.creation_date = datetime.date.today()
|
||||
|
||||
def test_creation_date_updated_on_info_domain_call(self):
|
||||
"""assert that creation date in db is updated on info domain call"""
|
||||
# force fetch_cache to be called
|
||||
self.domain.statuses
|
||||
self.assertEquals(self.domain.created_at, self.creation_date)
|
||||
|
||||
|
||||
class TestAnalystClientHold(MockEppLib):
|
||||
"""Rule: Analysts may suspend or restore a domain by using client hold"""
|
||||
|
||||
|
|
|
@ -158,7 +158,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
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ from django.test import Client, TestCase
|
|||
from django.urls import reverse
|
||||
from django.contrib.auth import get_user_model
|
||||
from .common import MockEppLib, completed_application, create_user # type: ignore
|
||||
|
||||
from django_webtest import WebTest # type: ignore
|
||||
import boto3_mocking # type: ignore
|
||||
|
||||
|
@ -100,7 +99,7 @@ class LoggedInTests(TestWithUser):
|
|||
response = self.client.get("/")
|
||||
# count = 2 because it is also in screenreader content
|
||||
self.assertContains(response, "igorville.gov", count=2)
|
||||
self.assertContains(response, "DNS needed")
|
||||
self.assertContains(response, "Expired")
|
||||
# clean up
|
||||
role.delete()
|
||||
|
||||
|
@ -257,7 +256,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 +266,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 +455,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 +882,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()
|
||||
|
@ -1335,6 +1330,12 @@ class TestDomainDetail(TestDomainOverview):
|
|||
|
||||
|
||||
class TestDomainManagers(TestDomainOverview):
|
||||
def tearDown(self):
|
||||
"""Ensure that the user has its original permissions"""
|
||||
super().tearDown()
|
||||
self.user.is_staff = False
|
||||
self.user.save()
|
||||
|
||||
def test_domain_managers(self):
|
||||
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
|
||||
self.assertContains(response, "Domain managers")
|
||||
|
@ -1438,6 +1439,7 @@ class TestDomainManagers(TestDomainOverview):
|
|||
add_page.form["email"] = email_address
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
add_page.form.submit()
|
||||
|
||||
# check the mock instance to see if `send_email` was called right
|
||||
mock_client_instance.send_email.assert_called_once_with(
|
||||
FromEmailAddress=settings.DEFAULT_FROM_EMAIL,
|
||||
|
@ -1445,6 +1447,189 @@ class TestDomainManagers(TestDomainOverview):
|
|||
Content=ANY,
|
||||
)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_domain_invitation_email_has_email_as_requester_non_existent(self):
|
||||
"""Inviting a non existent user sends them an email, with email as the name."""
|
||||
# make sure there is no user with this email
|
||||
email_address = "mayor@igorville.gov"
|
||||
User.objects.filter(email=email_address).delete()
|
||||
|
||||
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client_instance = mock_client.return_value
|
||||
|
||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
add_page.form["email"] = email_address
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
add_page.form.submit()
|
||||
|
||||
# check the mock instance to see if `send_email` was called right
|
||||
mock_client_instance.send_email.assert_called_once_with(
|
||||
FromEmailAddress=settings.DEFAULT_FROM_EMAIL,
|
||||
Destination={"ToAddresses": [email_address]},
|
||||
Content=ANY,
|
||||
)
|
||||
|
||||
# Check the arguments passed to send_email method
|
||||
_, kwargs = mock_client_instance.send_email.call_args
|
||||
|
||||
# Extract the email content, and check that the message is as we expect
|
||||
email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||
self.assertIn("info@example.com", email_content)
|
||||
|
||||
# Check that the requesters first/last name do not exist
|
||||
self.assertNotIn("First", email_content)
|
||||
self.assertNotIn("Last", email_content)
|
||||
self.assertNotIn("First Last", email_content)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_domain_invitation_email_has_email_as_requester(self):
|
||||
"""Inviting a user sends them an email, with email as the name."""
|
||||
# Create a fake user object
|
||||
email_address = "mayor@igorville.gov"
|
||||
User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com")
|
||||
|
||||
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client_instance = mock_client.return_value
|
||||
|
||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
add_page.form["email"] = email_address
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
add_page.form.submit()
|
||||
|
||||
# check the mock instance to see if `send_email` was called right
|
||||
mock_client_instance.send_email.assert_called_once_with(
|
||||
FromEmailAddress=settings.DEFAULT_FROM_EMAIL,
|
||||
Destination={"ToAddresses": [email_address]},
|
||||
Content=ANY,
|
||||
)
|
||||
|
||||
# Check the arguments passed to send_email method
|
||||
_, kwargs = mock_client_instance.send_email.call_args
|
||||
|
||||
# Extract the email content, and check that the message is as we expect
|
||||
email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||
self.assertIn("info@example.com", email_content)
|
||||
|
||||
# Check that the requesters first/last name do not exist
|
||||
self.assertNotIn("First", email_content)
|
||||
self.assertNotIn("Last", email_content)
|
||||
self.assertNotIn("First Last", email_content)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_domain_invitation_email_has_email_as_requester_staff(self):
|
||||
"""Inviting a user sends them an email, with email as the name."""
|
||||
# Create a fake user object
|
||||
email_address = "mayor@igorville.gov"
|
||||
User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com")
|
||||
|
||||
# Make sure the user is staff
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
|
||||
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client_instance = mock_client.return_value
|
||||
|
||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
add_page.form["email"] = email_address
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
add_page.form.submit()
|
||||
|
||||
# check the mock instance to see if `send_email` was called right
|
||||
mock_client_instance.send_email.assert_called_once_with(
|
||||
FromEmailAddress=settings.DEFAULT_FROM_EMAIL,
|
||||
Destination={"ToAddresses": [email_address]},
|
||||
Content=ANY,
|
||||
)
|
||||
|
||||
# Check the arguments passed to send_email method
|
||||
_, kwargs = mock_client_instance.send_email.call_args
|
||||
|
||||
# Extract the email content, and check that the message is as we expect
|
||||
email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||
self.assertIn("help@get.gov", email_content)
|
||||
|
||||
# Check that the requesters first/last name do not exist
|
||||
self.assertNotIn("First", email_content)
|
||||
self.assertNotIn("Last", email_content)
|
||||
self.assertNotIn("First Last", email_content)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_domain_invitation_email_displays_error_non_existent(self):
|
||||
"""Inviting a non existent user sends them an email, with email as the name."""
|
||||
# make sure there is no user with this email
|
||||
email_address = "mayor@igorville.gov"
|
||||
User.objects.filter(email=email_address).delete()
|
||||
|
||||
# Give the user who is sending the email an invalid email address
|
||||
self.user.email = ""
|
||||
self.user.save()
|
||||
|
||||
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
||||
|
||||
mock_client = MagicMock()
|
||||
|
||||
mock_error_message = MagicMock()
|
||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||
with patch("django.contrib.messages.error") as mock_error_message:
|
||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
add_page.form["email"] = email_address
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
add_page.form.submit().follow()
|
||||
|
||||
expected_message_content = "Can't send invitation email. No email is associated with your account."
|
||||
|
||||
# Grab the message content
|
||||
returned_error_message = mock_error_message.call_args[0][1]
|
||||
|
||||
# Check that the message content is what we expect
|
||||
self.assertEqual(expected_message_content, returned_error_message)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_domain_invitation_email_displays_error(self):
|
||||
"""When the requesting user has no email, an error is displayed"""
|
||||
# make sure there is no user with this email
|
||||
# Create a fake user object
|
||||
email_address = "mayor@igorville.gov"
|
||||
User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com")
|
||||
|
||||
# Give the user who is sending the email an invalid email address
|
||||
self.user.email = ""
|
||||
self.user.save()
|
||||
|
||||
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
||||
|
||||
mock_client = MagicMock()
|
||||
|
||||
mock_error_message = MagicMock()
|
||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||
with patch("django.contrib.messages.error") as mock_error_message:
|
||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
add_page.form["email"] = email_address
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
add_page.form.submit().follow()
|
||||
|
||||
expected_message_content = "Can't send invitation email. No email is associated with your account."
|
||||
|
||||
# Grab the message content
|
||||
returned_error_message = mock_error_message.call_args[0][1]
|
||||
|
||||
# Check that the message content is what we expect
|
||||
self.assertEqual(expected_message_content, returned_error_message)
|
||||
|
||||
def test_domain_invitation_cancel(self):
|
||||
"""Posting to the delete view deletes an invitation."""
|
||||
email_address = "mayor@igorville.gov"
|
||||
|
@ -2229,6 +2414,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)
|
||||
|
|
|
@ -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 = ""
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ from django.views.generic.edit import FormMixin
|
|||
|
||||
from registrar.models import (
|
||||
Domain,
|
||||
DomainInformation,
|
||||
DomainInvitation,
|
||||
User,
|
||||
UserDomainRole,
|
||||
|
@ -644,21 +643,27 @@ class DomainAddUserView(DomainFormBaseView):
|
|||
"""Get an absolute URL for this domain."""
|
||||
return self.request.build_absolute_uri(reverse("domain", kwargs={"pk": self.object.id}))
|
||||
|
||||
def _send_domain_invitation_email(self, email: str, add_success=True):
|
||||
def _send_domain_invitation_email(self, email: str, requester: User, add_success=True):
|
||||
"""Performs the sending of the domain invitation email,
|
||||
does not make a domain information object
|
||||
email: string- email to send to
|
||||
add_success: bool- default True indicates:
|
||||
adding a success message to the view if the email sending succeeds"""
|
||||
# created a new invitation in the database, so send an email
|
||||
domainInfoResults = DomainInformation.objects.filter(domain=self.object)
|
||||
domainInfo = domainInfoResults.first()
|
||||
first = ""
|
||||
last = ""
|
||||
if domainInfo is not None:
|
||||
first = domainInfo.creator.first_name
|
||||
last = domainInfo.creator.last_name
|
||||
full_name = f"{first} {last}"
|
||||
|
||||
# Set a default email address to send to for staff
|
||||
requester_email = "help@get.gov"
|
||||
|
||||
# Check if the email requester has a valid email address
|
||||
if not requester.is_staff and requester.email is not None and requester.email.strip() != "":
|
||||
requester_email = requester.email
|
||||
elif not requester.is_staff:
|
||||
messages.error(self.request, "Can't send invitation email. No email is associated with your account.")
|
||||
logger.error(
|
||||
f"Can't send email to '{email}' on domain '{self.object}'."
|
||||
f"No email exists for the requester '{requester.username}'.",
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
send_templated_email(
|
||||
|
@ -668,7 +673,7 @@ class DomainAddUserView(DomainFormBaseView):
|
|||
context={
|
||||
"domain_url": self._domain_abs_url(),
|
||||
"domain": self.object,
|
||||
"full_name": full_name,
|
||||
"requester_email": requester_email,
|
||||
},
|
||||
)
|
||||
except EmailSendingError:
|
||||
|
@ -683,7 +688,7 @@ class DomainAddUserView(DomainFormBaseView):
|
|||
if add_success:
|
||||
messages.success(self.request, f"Invited {email} to this domain.")
|
||||
|
||||
def _make_invitation(self, email_address: str):
|
||||
def _make_invitation(self, email_address: str, requester: User):
|
||||
"""Make a Domain invitation for this email and redirect with a message."""
|
||||
invitation, created = DomainInvitation.objects.get_or_create(email=email_address, domain=self.object)
|
||||
if not created:
|
||||
|
@ -693,21 +698,22 @@ class DomainAddUserView(DomainFormBaseView):
|
|||
f"{email_address} has already been invited to this domain.",
|
||||
)
|
||||
else:
|
||||
self._send_domain_invitation_email(email=email_address)
|
||||
self._send_domain_invitation_email(email=email_address, requester=requester)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Add the specified user on this domain."""
|
||||
requested_email = form.cleaned_data["email"]
|
||||
requester = self.request.user
|
||||
# look up a user with that email
|
||||
try:
|
||||
requested_user = User.objects.get(email=requested_email)
|
||||
except User.DoesNotExist:
|
||||
# no matching user, go make an invitation
|
||||
return self._make_invitation(requested_email)
|
||||
return self._make_invitation(requested_email, requester)
|
||||
else:
|
||||
# if user already exists then just send an email
|
||||
self._send_domain_invitation_email(requested_email, add_success=False)
|
||||
self._send_domain_invitation_email(requested_email, requester, add_success=False)
|
||||
|
||||
try:
|
||||
UserDomainRole.objects.create(
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.db.models import F
|
||||
from django.shortcuts import render
|
||||
|
||||
from registrar.models import DomainApplication
|
||||
from registrar.models import DomainApplication, Domain, UserDomainRole
|
||||
|
||||
|
||||
def index(request):
|
||||
|
@ -14,12 +13,9 @@ def index(request):
|
|||
# the active applications table
|
||||
context["domain_applications"] = applications.exclude(status="approved")
|
||||
|
||||
domains = request.user.permissions.values(
|
||||
"role",
|
||||
pk=F("domain__id"),
|
||||
name=F("domain__name"),
|
||||
created_time=F("domain__created_at"),
|
||||
state=F("domain__state"),
|
||||
)
|
||||
user_domain_roles = UserDomainRole.objects.filter(user=request.user)
|
||||
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
|
||||
domains = Domain.objects.filter(id__in=domain_ids)
|
||||
|
||||
context["domains"] = domains
|
||||
return render(request, "home.html", context)
|
||||
|
|
|
@ -4,6 +4,7 @@ from .always_404 import always_404
|
|||
from .permission_views import (
|
||||
DomainPermissionView,
|
||||
DomainApplicationPermissionView,
|
||||
DomainApplicationPermissionWithdrawView,
|
||||
DomainInvitationPermissionDeleteView,
|
||||
ApplicationWizardPermissionView,
|
||||
)
|
||||
|
|
|
@ -26,7 +26,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.
|
||||
|
@ -134,7 +135,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.
|
||||
|
@ -154,9 +156,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.
|
||||
|
@ -173,7 +199,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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue