From c924a5fd9a53e42dca70f4e62575a5a140b04fc1 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Mon, 12 Dec 2022 15:53:43 -0600 Subject: [PATCH 1/9] Field validation and form errors in templates --- src/Pipfile | 1 + src/Pipfile.lock | 379 +++++++++--------- src/registrar/config/settings.py | 13 +- src/registrar/forms/application_wizard.py | 81 +++- .../migrations/0005_alter_contact_phone.py | 26 ++ src/registrar/models/contact.py | 4 +- .../application_authorizing_official.html | 22 +- .../templates/application_current_sites.html | 5 +- .../templates/application_dotgov_domain.html | 49 ++- src/registrar/templates/application_form.html | 22 + .../templates/application_org_contact.html | 18 +- .../templates/application_other_contacts.html | 21 +- .../templates/application_requirements.html | 22 +- .../templates/application_security_email.html | 19 +- .../templates/application_your_contact.html | 21 +- .../templates/includes/input_with_errors.html | 21 + src/registrar/templatetags/field_helpers.py | 18 + src/registrar/tests/test_forms.py | 118 ++++++ 18 files changed, 590 insertions(+), 270 deletions(-) create mode 100644 src/registrar/migrations/0005_alter_contact_phone.py create mode 100644 src/registrar/templates/includes/input_with_errors.html create mode 100644 src/registrar/templatetags/field_helpers.py create mode 100644 src/registrar/tests/test_forms.py diff --git a/src/Pipfile b/src/Pipfile index 6aa54ed72..e526ed68c 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -21,6 +21,7 @@ django-widget-tweaks = "*" cachetools = "*" requests = "*" django-fsm = "*" +django-phonenumber-field = {extras = ["phonenumberslite"], version = "*"} [dev-packages] django-debug-toolbar = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index c70ae828b..5705d86e8 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4e755e3f5778ff572fba5755b966cde05d30a84c4eddb1d63ca5fe1034565283" + "sha256": "9e2fe58b6282514da5d054147426e561f451da29f3c743c0cd9dccf4d7dba0cc" }, "pipfile-spec": 6, "requires": {}, @@ -24,9 +24,9 @@ }, "beaker": { "hashes": [ - "sha256:ad5d1c05027ee3be3a482ea39f8cb70339b41e5d6ace0cb861382754076d187e" + "sha256:2d5f427e3b13259c98c934cab0e428fc1c18a4c4b94acbdae930df7e7f51d1ec" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "cachetools": { "hashes": [ @@ -38,11 +38,11 @@ }, "certifi": { "hashes": [ - "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", - "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" + "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", + "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" ], "markers": "python_version >= '3.6'", - "version": "==2022.9.24" + "version": "==2022.12.7" }, "cfenv": { "hashes": [ @@ -126,40 +126,40 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.1.1" }, "cryptography": { "hashes": [ - "sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d", - "sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd", - "sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146", - "sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7", - "sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436", - "sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0", - "sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828", - "sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b", - "sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55", - "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36", - "sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50", - "sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2", - "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a", - "sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8", - "sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0", - "sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548", - "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320", - "sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748", - "sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249", - "sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959", - "sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f", - "sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0", - "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd", - "sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220", - "sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c", - "sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722" + "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd", + "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db", + "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290", + "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744", + "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb", + "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d", + "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70", + "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b", + "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876", + "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083", + "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6", + "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1", + "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00", + "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b", + "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b", + "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285", + "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9", + "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0", + "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d", + "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2", + "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8", + "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee", + "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b", + "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7", + "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353", + "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c" ], "markers": "python_version >= '3.6'", - "version": "==38.0.3" + "version": "==38.0.4" }, "defusedxml": { "hashes": [ @@ -171,10 +171,10 @@ }, "dj-database-url": { "hashes": [ - "sha256:ccf3e8718f75ddd147a1e212fca88eecdaa721759ee48e38b485481c77bca3dc", - "sha256:cd354a3b7a9136d78d64c17b2aec369e2ae5616fbca6bfbe435ef15bb372ce39" + "sha256:5f2f6b3f65786bac5d3b9e749bff1dcac83398d95778576909697f7b16aee6b9", + "sha256:8be4253439d75412aaad4f82af7aecda956893c87fb8d10edc0adb2d34312527" ], - "version": "==1.0.0" + "version": "==1.1.0" }, "dj-email-url": { "hashes": [ @@ -185,11 +185,11 @@ }, "django": { "hashes": [ - "sha256:678bbfc8604eb246ed54e2063f0765f13b321a50526bdc8cb1f943eda7fa31f1", - "sha256:6b1de6886cae14c7c44d188f580f8ba8da05750f544c80ae5ad43375ab293cd5" + "sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148", + "sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b" ], "index": "pypi", - "version": "==4.1.3" + "version": "==4.1.4" }, "django-allow-cidr": { "hashes": [ @@ -201,18 +201,18 @@ }, "django-auditlog": { "hashes": [ - "sha256:0ab57a536e02341e27c3d0431ad0e124e674507bd965a0756e29b01cb67c38ce", - "sha256:2f83389f98db4b1a9c2961f17cd9ac4a3ea94304655071f30da45d8debf59688" + "sha256:51c724f878fb3bc275c498e2e44583f28565135b1c60e6e8f7faf54e030c804f", + "sha256:9ad9a0a04d37aa6dc8956126ceb499d64edf71f8fddb0bc908f1217b0c31ec21" ], "index": "pypi", - "version": "==2.2.0" + "version": "==2.2.1" }, "django-cache-url": { "hashes": [ - "sha256:6cc9901a99a99751f5458aa7de08ce06e48c1441b1a94c9457d78af74fab9a26", - "sha256:c4a62634cffc9d636073cef597a44576d67b07660ab2ef1f02b160ee7ecf0e98" + "sha256:5ca4760b4580b80e41279bc60d1e5c16a822e4e462265faab0a330701bb0ef9a", + "sha256:ef2cfacea361ee22e9b67d6ca941db22e0a9eaf892b67ca71cad52c62a17fd36" ], - "version": "==3.4.2" + "version": "==3.4.4" }, "django-csp": { "hashes": [ @@ -238,6 +238,17 @@ "index": "pypi", "version": "==2.8.1" }, + "django-phonenumber-field": { + "extras": [ + "phonenumberslite" + ], + "hashes": [ + "sha256:969bbcab203d697ea44c38726bdc7d72ac9f1ba397694b9f57422b471ad73590", + "sha256:9e2b302f239e4703fa9030e44833db5eb524a731335fd77b0b703bd352fbe8d0" + ], + "index": "pypi", + "version": "==7.0.1" + }, "django-widget-tweaks": { "hashes": [ "sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e", @@ -289,11 +300,11 @@ }, "mako": { "hashes": [ - "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd", - "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f" + "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818", + "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34" ], "markers": "python_version >= '3.7'", - "version": "==1.2.3" + "version": "==1.2.4" }, "markupsafe": { "hashes": [ @@ -343,11 +354,11 @@ }, "marshmallow": { "hashes": [ - "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104", - "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7" + "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78", + "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b" ], "markers": "python_version >= '3.7'", - "version": "==3.18.0" + "version": "==3.19.0" }, "oic": { "hashes": [ @@ -366,11 +377,18 @@ }, "packaging": { "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3", + "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3" ], - "markers": "python_version >= '3.6'", - "version": "==21.3" + "markers": "python_version >= '3.7'", + "version": "==22.0" + }, + "phonenumberslite": { + "hashes": [ + "sha256:87218f3f85f67779007b6cfeeca59b1d370b96f9b3867347f0e6d094b1a3df64", + "sha256:8daa2fff393e291531aaa5ed4ddc123749b631d48ae7e504e8d7a4e818b3f799" + ], + "version": "==8.13.2" }, "psycopg2-binary": { "hashes": [ @@ -458,39 +476,35 @@ }, "pycryptodomex": { "hashes": [ - "sha256:04cc393045a8f19dd110c975e30f38ed7ab3faf21ede415ea67afebd95a22380", - "sha256:0776bfaf2c48154ab54ea45392847c1283d2fcf64e232e85565f858baedfc1fa", - "sha256:0fadb9f7fa3150577800eef35f62a8a24b9ddf1563ff060d9bd3af22d3952c8c", - "sha256:18e2ab4813883ae63396c0ffe50b13554b32bb69ec56f0afaf052e7a7ae0d55b", - "sha256:191e73bc84a8064ad1874dba0ebadedd7cce4dedee998549518f2c74a003b2e1", - "sha256:35a8f7afe1867118330e2e0e0bf759c409e28557fb1fc2fbb1c6c937297dbe9a", - "sha256:3709f13ca3852b0b07fc04a2c03b379189232b24007c466be0f605dd4723e9d4", - "sha256:4540904c09704b6f831059c0dfb38584acb82cb97b0125cd52688c1f1e3fffa6", - "sha256:463119d7d22d0fc04a0f9122e9d3e6121c6648bcb12a052b51bd1eed1b996aa2", - "sha256:46b3f05f2f7ac7841053da4e0f69616929ca3c42f238c405f6c3df7759ad2780", - "sha256:48697790203909fab02a33226fda546604f4e2653f9d47bc5d3eb40879fa7c64", - "sha256:5676a132169a1c1a3712edf25250722ebc8c9102aa9abd814df063ca8362454f", - "sha256:65204412d0c6a8e3c41e21e93a5e6054a74fea501afa03046a388cf042e3377a", - "sha256:67e1e6a92151023ccdfcfbc0afb3314ad30080793b4c27956ea06ab1fb9bcd8a", - "sha256:6f5b6ba8aefd624834bc177a2ac292734996bb030f9d1b388e7504103b6fcddf", - "sha256:7341f1bb2dadb0d1a0047f34c3a58208a92423cdbd3244d998e4b28df5eac0ed", - "sha256:78d9621cf0ea35abf2d38fa2ca6d0634eab6c991a78373498ab149953787e5e5", - "sha256:8eecdf9cdc7343001d047f951b9cc805cd68cb6cd77b20ea46af5bffc5bd3dfb", - "sha256:94c7b60e1f52e1a87715571327baea0733708ab4723346598beca4a3b6879794", - "sha256:996e1ba717077ce1e6d4849af7a1426f38b07b3d173b879e27d5e26d2e958beb", - "sha256:a07a64709e366c2041cd5cfbca592b43998bf4df88f7b0ca73dca37071ccf1bd", - "sha256:b6306403228edde6e289f626a3908a2f7f67c344e712cf7c0a508bab3ad9e381", - "sha256:b9279adc16e4b0f590ceff581f53a80179b02cba9056010d733eb4196134a870", - "sha256:c4cb9cb492ea7dcdf222a8d19a1d09002798ea516aeae8877245206d27326d86", - "sha256:dd452a5af7014e866206d41751886c9b4bf379a339fdf2dbfc7dd16c0fb4f8e0", - "sha256:e2b12968522a0358b8917fc7b28865acac002f02f4c4c6020fcb264d76bfd06d", - "sha256:e3164a18348bd53c69b4435ebfb4ac8a4076291ffa2a70b54f0c4b80c7834b1d", - "sha256:e47bf8776a7e15576887f04314f5228c6527b99946e6638cf2f16da56d260cab", - "sha256:f8be976cec59b11f011f790b88aca67b4ea2bd286578d0bd3e31bcd19afcd3e4", - "sha256:fc9bc7a9b79fe5c750fc81a307052f8daabb709bdaabb0fb18fb136b66b653b5" + "sha256:04610536921c1ec7adba158ef570348550c9f3a40bc24be9f8da2ef7ab387981", + "sha256:0ba28aa97cdd3ff5ed1a4f2b7f5cd04e721166bd75bd2b929e2734433882b583", + "sha256:0da835af786fdd1c9930994c78b23e88d816dc3f99aa977284a21bbc26d19735", + "sha256:1619087fb5b31510b0b0b058a54f001a5ffd91e6ffee220d9913064519c6a69d", + "sha256:1cda60207be8c1cf0b84b9138f9e3ca29335013d2b690774a5e94678ff29659a", + "sha256:22aed0868622d95179217c298e37ed7410025c7b29dac236d3230617d1e4ed56", + "sha256:231dc8008cbdd1ae0e34645d4523da2dbc7a88c325f0d4a59635a86ee25b41dd", + "sha256:2ad9bb86b355b6104796567dd44c215b3dc953ef2fae5e0bdfb8516731df92cf", + "sha256:4dbbe18cc232b5980c7633972ae5417d0df76fe89e7db246eefd17ef4d8e6d7a", + "sha256:6a465e4f856d2a4f2a311807030c89166529ccf7ccc65bef398de045d49144b6", + "sha256:70288d9bfe16b2fd0d20b6c365db614428f1bcde7b20d56e74cf88ade905d9eb", + "sha256:7993d26dae4d83b8f4ce605bb0aecb8bee330bb3c95475ef06f3694403621e71", + "sha256:8851585ff19871e5d69e1790f4ca5f6fd1699d6b8b14413b472a4c0dbc7ea780", + "sha256:893f8a97d533c66cc3a56e60dd3ed40a3494ddb4aafa7e026429a08772f8a849", + "sha256:8dd2d9e3c617d0712ed781a77efd84ea579e76c5f9b2a4bc0b684ebeddf868b2", + "sha256:a1c0ae7123448ecb034c75c713189cb00ebe2d415b11682865b6c54d200d9c93", + "sha256:b0789a8490114a2936ed77c87792cfe77582c829cb43a6d86ede0f9624ba8aa3", + "sha256:b3d04c00d777c36972b539fb79958790126847d84ec0129fce1efef250bfe3ce", + "sha256:ba57ac7861fd2c837cdb33daf822f2a052ff57dd769a2107807f52a36d0e8d38", + "sha256:ce338a9703f54b2305a408fc9890eb966b727ce72b69f225898bb4e9d9ed3f1f", + "sha256:daa67f5ebb6fbf1ee9c90decaa06ca7fc88a548864e5e484d52b0920a57fe8a5", + "sha256:e2453162f473c1eae4826eb10cd7bce19b5facac86d17fb5f29a570fde145abd", + "sha256:e25a2f5667d91795f9417cb856f6df724ccdb0cdd5cbadb212ee9bf43946e9f8", + "sha256:e5a670919076b71522c7d567a9043f66f14b202414a63c3a078b5831ae342c03", + "sha256:e9ba9d8ed638733c9e95664470b71d624a6def149e2db6cc52c1aca5a6a2df1d", + "sha256:f2b971a7b877348a27dcfd0e772a0343fb818df00b74078e91c008632284137d" ], "index": "pypi", - "version": "==3.15.0" + "version": "==3.16.0" }, "pyjwkest": { "hashes": [ @@ -499,14 +513,6 @@ "index": "pypi", "version": "==1.4.2" }, - "pyparsing": { - "hashes": [ - "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", - "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" - ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.0.9" - }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", @@ -533,11 +539,11 @@ }, "setuptools": { "hashes": [ - "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31", - "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f" + "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54", + "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75" ], "markers": "python_version >= '3.7'", - "version": "==65.5.1" + "version": "==65.6.3" }, "six": { "hashes": [ @@ -565,11 +571,11 @@ }, "urllib3": { "hashes": [ - "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", - "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", + "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", - "version": "==1.26.12" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.13" }, "whitenoise": { "hashes": [ @@ -602,35 +608,26 @@ "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==4.11.1" }, "black": { "hashes": [ - "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7", - "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6", - "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650", - "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb", - "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d", - "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d", - "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de", - "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395", - "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae", - "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa", - "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef", - "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383", - "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66", - "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87", - "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d", - "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0", - "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b", - "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458", - "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4", - "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1", - "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff" + "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320", + "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351", + "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350", + "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f", + "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf", + "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148", + "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4", + "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d", + "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc", + "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d", + "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2", + "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f" ], "index": "pypi", - "version": "==22.10.0" + "version": "==22.12.0" }, "blinker": { "hashes": [ @@ -650,27 +647,27 @@ }, "django": { "hashes": [ - "sha256:678bbfc8604eb246ed54e2063f0765f13b321a50526bdc8cb1f943eda7fa31f1", - "sha256:6b1de6886cae14c7c44d188f580f8ba8da05750f544c80ae5ad43375ab293cd5" + "sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148", + "sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b" ], "index": "pypi", - "version": "==4.1.3" + "version": "==4.1.4" }, "django-debug-toolbar": { "hashes": [ - "sha256:1e3acad24e3d351ba45c6fa2072e4164820307332a776b16c9f06d1f89503465", - "sha256:80de23066b624d3970fd296cf02d61988e5d56c31aa0dc4a428970b46e2883a8" + "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27", + "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478" ], "index": "pypi", - "version": "==3.7.0" + "version": "==3.8.1" }, "django-stubs": { "hashes": [ - "sha256:424fdd1935f859a802365056f9ccf4db12d1d93a5ab3de6d5633dddba0c5fc76", - "sha256:eaecc1fc71532c1148f0c9687556651d880165476d7629bf318ff86a903a150c" + "sha256:bcc618ba353dabc540d982b9dac1d5a1921652f8fc2a13653d545a57d5e3cc0f", + "sha256:fbf2ee6a4bce76c3eb5f6707ccadb4cf1c2f1ec485e8c44701ca8de2d0a5df18" ], "index": "pypi", - "version": "==1.13.0" + "version": "==1.13.1" }, "django-stubs-ext": { "hashes": [ @@ -690,19 +687,19 @@ }, "flake8": { "hashes": [ - "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", - "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248" + "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7", + "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181" ], "index": "pypi", - "version": "==5.0.4" + "version": "==6.0.0" }, "gitdb": { "hashes": [ - "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd", - "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa" + "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a", + "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7" ], - "markers": "python_version >= '3.6'", - "version": "==4.0.9" + "markers": "python_version >= '3.7'", + "version": "==4.0.10" }, "gitpython": { "hashes": [ @@ -722,39 +719,39 @@ }, "mypy": { "hashes": [ - "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013", - "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec", - "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466", - "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c", - "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd", - "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8", - "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff", - "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5", - "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea", - "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af", - "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd", - "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706", - "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2", - "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852", - "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d", - "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0", - "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47", - "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6", - "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9", - "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb", - "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37", - "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa", - "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef", - "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5", - "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db", - "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74", - "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695", - "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46", - "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4", - "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e" + "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d", + "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6", + "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf", + "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f", + "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813", + "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33", + "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad", + "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05", + "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297", + "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06", + "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd", + "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243", + "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305", + "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476", + "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711", + "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70", + "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5", + "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461", + "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab", + "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c", + "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d", + "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135", + "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93", + "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648", + "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a", + "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb", + "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3", + "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372", + "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb", + "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef" ], "index": "pypi", - "version": "==0.990" + "version": "==0.991" }, "mypy-extensions": { "hashes": [ @@ -773,11 +770,11 @@ }, "pathspec": { "hashes": [ - "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93", - "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d" + "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6", + "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6" ], "markers": "python_version >= '3.7'", - "version": "==0.10.1" + "version": "==0.10.3" }, "pbr": { "hashes": [ @@ -789,27 +786,27 @@ }, "platformdirs": { "hashes": [ - "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb", - "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0" + "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca", + "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e" ], "markers": "python_version >= '3.7'", - "version": "==2.5.3" + "version": "==2.6.0" }, "pycodestyle": { "hashes": [ - "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", - "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b" + "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053", + "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610" ], "markers": "python_version >= '3.6'", - "version": "==2.9.1" + "version": "==2.10.0" }, "pyflakes": { "hashes": [ - "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", - "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3" + "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf", + "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd" ], "markers": "python_version >= '3.6'", - "version": "==2.5.0" + "version": "==3.0.1" }, "pyyaml": { "hashes": [ @@ -929,18 +926,18 @@ }, "types-requests": { "hashes": [ - "sha256:bdb1f9811e53d0642c8347b09137363eb25e1a516819e190da187c29595a1df3", - "sha256:d4f342b0df432262e9e326d17638eeae96a5881e78e7a6aae46d33870d73952e" + "sha256:091d4a5a33c1b4f20d8b1b952aa8fa27a6e767c44c3cf65e56580df0b05fd8a9", + "sha256:a7df37cc6fb6187a84097da951f8e21d335448aa2501a6b0a39cbd1d7ca9ee2a" ], "index": "pypi", - "version": "==2.28.11.4" + "version": "==2.28.11.5" }, "types-urllib3": { "hashes": [ - "sha256:1807b87b8ee1ae0226813ba2c52330eff20fb2bf6359b1de24df08eb3090e442", - "sha256:a188c24fc61a99658c8c324c8dd7419f5b91a0d89df004e5f576869122c1db55" + "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49", + "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee" ], - "version": "==1.26.25.3" + "version": "==1.26.25.4" }, "typing-extensions": { "hashes": [ @@ -955,7 +952,7 @@ "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a", "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba" ], - "markers": "python_full_version >= '3.7.0'", + "markers": "python_version >= '3.7'", "version": "==2.1.2" }, "webob": { diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 048dfb108..f1e331a0f 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -90,6 +90,8 @@ INSTALLED_APPS = [ "widget_tweaks", # library for Finite State Machine statuses "django_fsm", + # library for phone numbers + "phonenumber_field", # let's be sure to install our own application! "registrar", # Our internal API application @@ -181,6 +183,8 @@ TEMPLATES = [ }, ] +MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' + # IS_DEMO_SITE controls whether or not we show our big red "TEST SITE" banner # underneath the "this is a real government website" banner. IS_DEMO_SITE = True @@ -296,6 +300,9 @@ USE_L10N = True # make datetimes timezone-aware by default USE_TZ = True +# setting for phonenumber library +PHONENUMBER_DEFAULT_REGION="US" + # endregion # region: Logging-----------------------------------------------------------### @@ -368,7 +375,7 @@ LOGGING = { # Django's template processor "django.template": { "handlers": ["console"], - "level": "INFO", + "level": "DEBUG", }, # Django's runserver "django.server": { @@ -379,13 +386,13 @@ LOGGING = { # Django's runserver requests "django.request": { "handlers": ["django.server"], - "level": "INFO", + "level": "DEBUG", "propagate": False, }, # OpenID Connect logger "oic": { "handlers": ["console"], - "level": "INFO", + "level": "DEBUG", }, # Django wrapper for OpenID Connect "djangooidc": { diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 0975b6810..263c4b5d0 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -10,8 +10,10 @@ from django import forms from django.shortcuts import render from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import resolve +from django.core.validators import RegexValidator from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore +from phonenumber_field.formfields import PhoneNumberField from registrar.models import Contact, DomainApplication, Domain @@ -119,8 +121,10 @@ class AuthorizingOfficialForm(RegistrarForm): ) last_name = forms.CharField(label="Last name/family name") title = forms.CharField(label="Title or role in your organization") - email = forms.EmailField(label="Email") - phone = forms.CharField(label="Phone") + email = forms.EmailField( + label="Email", error_messages={"invalid": "Please enter a valid email address."} + ) + phone = PhoneNumberField(label="Phone") class CurrentSitesForm(RegistrarForm): @@ -146,6 +150,22 @@ class CurrentSitesForm(RegistrarForm): "www.city.com.", ) + def clean_current_site(self): + """This field should be a legal domain name.""" + inputted_site = self.cleaned_data["current_site"] + if not inputted_site: + # empty string is fine + return inputted_site + + # something has been inputted + if Domain.string_could_be_domain(inputted_site): + return inputted_site + else: + # string could not be a domain + raise forms.ValidationError( + "Please enter a valid domain name", code="invalid" + ) + class DotGovDomainForm(RegistrarForm): def to_database(self, obj): @@ -183,13 +203,41 @@ class DotGovDomainForm(RegistrarForm): if alternative_domain is not None: self.initial["alternative_domain"] = alternative_domain.sld - requested_domain = forms.CharField(label="What .gov domain do you want?") + requested_domain = forms.CharField( + label="What .gov domain do you want?", + ) alternative_domain = forms.CharField( required=False, label="Are there other domains you’d like if we can’t give you your first " "choice? Entering alternative domains is optional.", ) + def clean_requested_domain(self): + """Requested domains need to be legal top-level domains, not subdomains. + + If they end with `.gov`, then we can reasonably take that off. If they have + any other dots in them, raise an error. + """ + requested = self.cleaned_data["requested_domain"] + if not requested: + # none or empty string + raise forms.ValidationError( + "Please enter the .gov domain that you are requesting.", code="invalid" + ) + if requested.endswith(".gov"): + requested = requested[:-4] + if "." in requested: + raise forms.ValidationError( + "Please enter a top-level domain name without any periods.", + code="invalid", + ) + if not Domain.string_could_be_domain(requested + ".gov"): + raise forms.ValidationError( + "Please enter a valid domain name using only letters, numbers, and hyphens", + code="invalid", + ) + return requested + class PurposeForm(RegistrarForm): purpose = forms.CharField(label="Purpose", widget=forms.Textarea()) @@ -222,8 +270,10 @@ class YourContactForm(RegistrarForm): ) last_name = forms.CharField(label="Last name/family name") title = forms.CharField(label="Title or role in your organization") - email = forms.EmailField(label="Email") - phone = forms.CharField(label="Phone") + email = forms.EmailField( + label="Email", error_messages={"invalid": "Please enter a valid email address."} + ) + phone = PhoneNumberField(label="Phone") class OtherContactsForm(RegistrarForm): @@ -255,14 +305,17 @@ class OtherContactsForm(RegistrarForm): ) last_name = forms.CharField(label="Last name/family name") title = forms.CharField(label="Title or role in your organization") - email = forms.EmailField(label="Email") - phone = forms.CharField(label="Phone") + email = forms.EmailField( + label="Email", error_messages={"invalid": "Please enter a valid email address."} + ) + phone = PhoneNumberField(label="Phone") class SecurityEmailForm(RegistrarForm): security_email = forms.EmailField( required=False, label="Security email", + error_messages={"invalid": "Please enter a valid email address."}, ) @@ -276,9 +329,21 @@ class AnythingElseForm(RegistrarForm): class RequirementsForm(RegistrarForm): is_policy_acknowledged = forms.BooleanField( - label="I read and agree to the .gov domain requirements." + label="I read and agree to the .gov domain requirements.", + required=False, # use field validation to enforce this ) + def clean_is_policy_acknowledged(self): + """This box must be checked to proceed but offer a clear error.""" + # already converted to a boolean + is_acknowledged = self.cleaned_data["is_policy_acknowledged"] + if not is_acknowledged: + raise forms.ValidationError( + "You must read and agree to the .gov domain requirements to proceed.", + code="invalid", + ) + return is_acknowledged + class ReviewForm(RegistrarForm): """ diff --git a/src/registrar/migrations/0005_alter_contact_phone.py b/src/registrar/migrations/0005_alter_contact_phone.py new file mode 100644 index 000000000..42bc4d889 --- /dev/null +++ b/src/registrar/migrations/0005_alter_contact_phone.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.4 on 2022-12-12 20:43 + +from django.db import migrations +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0004_domainapplication_federal_agency"), + ] + + operations = [ + migrations.AlterField( + model_name="contact", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, + db_index=True, + help_text="Phone", + max_length=128, + null=True, + region=None, + ), + ), + ] diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index 01cdcc769..3cc24e35a 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -1,5 +1,7 @@ from django.db import models +from phonenumber_field.modelfields import PhoneNumberField + class Contact(models.Model): @@ -33,7 +35,7 @@ class Contact(models.Model): help_text="Email", db_index=True, ) - phone = models.TextField( + phone = PhoneNumberField( null=True, blank=True, help_text="Phone", diff --git a/src/registrar/templates/application_authorizing_official.html b/src/registrar/templates/application_authorizing_official.html index 70eb9d617..b26dfde75 100644 --- a/src/registrar/templates/application_authorizing_official.html +++ b/src/registrar/templates/application_authorizing_official.html @@ -2,6 +2,7 @@ {% extends 'application_form.html' %} {% load widget_tweaks %} {% load static %} +{% load field_helpers %} {% block form_content %} @@ -28,23 +29,18 @@ Who is the authorizing official for your organization - {{ wizard.form.first_name|add_label_class:"usa-label" }} - {{ wizard.form.first_name|add_class:"usa-input"}} - {{ wizard.form.middle_name|add_label_class:"usa-label" }} - {{ wizard.form.middle_name|add_class:"usa-input"}} + {% input_with_errors wizard.form.first_name %} - {{ wizard.form.last_name|add_label_class:"usa-label" }} - {{ wizard.form.last_name|add_class:"usa-input"}} - - {{ wizard.form.title|add_label_class:"usa-label" }} - {{ wizard.form.title|add_class:"usa-input"}} + {% input_with_errors wizard.form.middle_name %} - {{ wizard.form.email|add_label_class:"usa-label" }} - {{ wizard.form.email|add_class:"usa-input"}} + {% input_with_errors wizard.form.last_name %} - {{ wizard.form.phone|add_label_class:"usa-label" }} - {{ wizard.form.phone|add_class:"usa-input usa-input--medium" }} + {% input_with_errors wizard.form.title %} + + {% input_with_errors wizard.form.email %} + + {% input_with_errors wizard.form.phone add_class="usa-input--medium" %} diff --git a/src/registrar/templates/application_current_sites.html b/src/registrar/templates/application_current_sites.html index 535a8f5dc..9684a6113 100644 --- a/src/registrar/templates/application_current_sites.html +++ b/src/registrar/templates/application_current_sites.html @@ -1,6 +1,6 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} +{% load widget_tweaks field_helpers %} {% load static %} {% block form_content %} @@ -9,8 +9,7 @@ {{ wizard.management_form }} {% csrf_token %} - {{ wizard.form.current_site|add_label_class:"usa-label" }} - {{ wizard.form.current_site|add_class:"usa-input" }} + {% input_with_errors wizard.form.current_site %} {{ block.super }} diff --git a/src/registrar/templates/application_dotgov_domain.html b/src/registrar/templates/application_dotgov_domain.html index d8e46d3cd..faa1518b0 100644 --- a/src/registrar/templates/application_dotgov_domain.html +++ b/src/registrar/templates/application_dotgov_domain.html @@ -27,23 +27,56 @@ {{ wizard.management_form }} {% csrf_token %} - {{ wizard.form.requested_domain|add_label_class:"usa-label" }} -
- www. - {{ wizard.form.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} - .gov -
+ + {% if wizard.form.requested_domain.errors %} +
+ {{ wizard.form.requested_domain|add_label_class:"usa-label usa-label--error" }} + {% for error in wizard.form.requested_domain.errors %} + + {{ error }} + + {% endfor %} +
+ www. + {{ wizard.form.requested_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions" }} + .gov +
+
+ {% else %} + {{ wizard.form.requested_domain|add_label_class:"usa-label" }} +
+ www. + {{ wizard.form.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} + .gov +
+ {% endif %}

Alternative domains

+ {% if wizard.form.alternative_domain.errors %} +
+ {{ wizard.form.alternative_domain|add_label_class:"usa-label usa-label--error" }} + {% for error in wizard.for.alternative_domain.errors %} + + {{ error }} + + {% endfor %} +
+ www. + {{ wizard.form.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions" }} + .gov +
+
+ {% else %} {{ wizard.form.alternative_domain|add_label_class:"usa-label" }}
www. - {{ wizard.form.alternative_domain|add_class:"usa-input" }} + {{ wizard.form.alternative_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} .gov
+ {% endif %}
@@ -65,7 +65,7 @@ {% endfor %}
www. - {{ wizard.form.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions" }} + {{ wizard.form.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }} .gov
diff --git a/src/registrar/templates/application_requirements.html b/src/registrar/templates/application_requirements.html index 57a713f61..7389f3c46 100644 --- a/src/registrar/templates/application_requirements.html +++ b/src/registrar/templates/application_requirements.html @@ -140,7 +140,7 @@ {% endfor %}
- {{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"|add_class:"usa-input--error" }} + {{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"|add_class:"usa-input--error"|attr:"aria-invalid:true" }} {{ wizard.form.is_policy_acknowledged|add_label_class:"usa-checkbox__label usa-label--error" }}
diff --git a/src/registrar/templates/application_security_email.html b/src/registrar/templates/application_security_email.html index 5fe9e9c1a..ddde6f9bb 100644 --- a/src/registrar/templates/application_security_email.html +++ b/src/registrar/templates/application_security_email.html @@ -19,7 +19,7 @@ {{ error }} {% endfor %} - {{ wizard.form.security_email|add_class:"usa-input"|add_class:"usa-input--error"|attr:"aria-describedby:instructions" }} + {{ wizard.form.security_email|add_class:"usa-input"|add_class:"usa-input--error"|attr:"aria-describedby:instructions"|attr:"aria-invalid:true" }} {% else %} {{ wizard.form.security_email|add_label_class:"usa-label" }} diff --git a/src/registrar/templates/includes/input_with_errors.html b/src/registrar/templates/includes/input_with_errors.html index 7146da807..09b52b190 100644 --- a/src/registrar/templates/includes/input_with_errors.html +++ b/src/registrar/templates/includes/input_with_errors.html @@ -13,7 +13,7 @@ error messages, if necessary. {{ error }} {% endfor %} - {{ field|add_class:input_class|add_class:"usa-input--error"}} + {{ field|add_class:input_class|add_class:"usa-input--error"|attr:"aria-invalid:true" }} {% else %} {{ field|add_label_class:"usa-label" }} From f7e0975e4cd65cab2af954f94e276b8c1bc63b15 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 5 Jan 2023 15:20:50 -0600 Subject: [PATCH 4/9] Review feedback too numerous to mention --- .../_theme/_uswds-theme-custom-styles.scss | 6 + src/registrar/forms/application_wizard.py | 137 +++++++++++++++--- .../application_authorizing_official.html | 4 +- .../templates/application_org_contact.html | 10 +- .../templates/application_org_election.html | 5 +- .../templates/application_org_federal.html | 2 +- .../templates/application_org_type.html | 8 +- .../templates/application_other_contacts.html | 4 +- .../templates/application_purpose.html | 6 +- .../templates/application_requirements.html | 6 +- .../templates/application_security_email.html | 2 +- .../templates/application_your_contact.html | 4 +- .../templates/includes/input_with_errors.html | 12 +- .../templates/includes/radio_button.html | 2 +- .../templates/includes/required_fields.html | 3 + src/registrar/templatetags/field_helpers.py | 24 ++- src/registrar/tests/test_forms.py | 18 +++ 17 files changed, 196 insertions(+), 57 deletions(-) create mode 100644 src/registrar/templates/includes/required_fields.html diff --git a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss index 7d80d5f76..158309f99 100644 --- a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss +++ b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss @@ -100,3 +100,9 @@ footer { //Workaround because USWDS units jump from 10 to 15 margin-top: units(10) + units(2); } + +abbr[title] { + // workaround for underlining abbr element + border-bottom: none; + text-decoration: none; +} diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 0cfbed76c..9a3c7cc27 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -7,9 +7,11 @@ import logging from typing import Union from django import forms +from django.core.validators import RegexValidator from django.shortcuts import render from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import resolve +from django.utils.safestring import mark_safe from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore from formtools.wizard.storage.session import SessionStorage # type: ignore @@ -22,6 +24,11 @@ from registrar.models import Contact, DomainApplication, Domain logger = logging.getLogger(__name__) +REQUIRED_SUFFIX = mark_safe( + ' *' +) + + class RegistrarForm(forms.Form): """Subclass used to remove the default colon suffix from all fields.""" @@ -52,6 +59,7 @@ class OrganizationTypeForm(RegistrarForm): required=True, choices=DomainApplication.OrganizationChoices.choices, widget=forms.RadioSelect, + error_messages={"required": "This question is required."}, ) @@ -59,19 +67,32 @@ class OrganizationFederalForm(RegistrarForm): federal_type = forms.ChoiceField( choices=DomainApplication.BranchChoices.choices, widget=forms.RadioSelect, + error_messages={"required": "This question is required."}, ) class OrganizationElectionForm(RegistrarForm): - is_election_board = forms.BooleanField( + is_election_board = forms.NullBooleanField( widget=forms.RadioSelect( choices=[ (True, "Yes"), (False, "No"), ], ), + required=False, # use field validation to require an answer ) + def clean_is_election_board(self): + """This box must be checked to proceed but offer a clear error.""" + # already converted to a boolean + is_election_board = self.cleaned_data["is_election_board"] + if is_election_board is None: + raise forms.ValidationError( + "Please select Yes or No.", + code="required", + ) + return is_election_board + class OrganizationContactForm(RegistrarForm): # for federal agencies we also want to know the top-level agency. @@ -81,19 +102,35 @@ class OrganizationContactForm(RegistrarForm): # it is a federal agency. required=False, choices=DomainApplication.AGENCY_CHOICES, + label_suffix=REQUIRED_SUFFIX, + ) + organization_name = forms.CharField( + label="Organization Name", label_suffix=REQUIRED_SUFFIX + ) + address_line1 = forms.CharField( + label="Street address", + label_suffix=REQUIRED_SUFFIX, ) - organization_name = forms.CharField(label="Organization Name") - address_line1 = forms.CharField(label="Street address") address_line2 = forms.CharField( required=False, label="Street address line 2", ) - city = forms.CharField(label="City") + city = forms.CharField(label="City", label_suffix=REQUIRED_SUFFIX) state_territory = forms.ChoiceField( label="State, territory, or military post", choices=[("", "--Select--")] + DomainApplication.StateTerritoryChoices.choices, + label_suffix=REQUIRED_SUFFIX, + ) + zipcode = forms.CharField( + label="ZIP code", + label_suffix=REQUIRED_SUFFIX, + validators=[ + RegexValidator( + "^[0-9]{5}(?:-[0-9]{4})?$|^$", + message="Please enter a ZIP code in the form 12345 or 12345-6789", + ) + ], ) - zipcode = forms.CharField(label="ZIP code") urbanization = forms.CharField( required=False, label="Urbanization (Puerto Rico only)", @@ -120,17 +157,31 @@ class AuthorizingOfficialForm(RegistrarForm): if contact is not None: super().from_database(contact) - first_name = forms.CharField(label="First name/given name") + first_name = forms.CharField( + label="First name/given name", + label_suffix=REQUIRED_SUFFIX, + ) middle_name = forms.CharField( required=False, - label="Middle name (optional)", + label="Middle name", + ) + last_name = forms.CharField( + label="Last name/family name", + label_suffix=REQUIRED_SUFFIX, + ) + title = forms.CharField( + label="Title or role in your organization", + label_suffix=REQUIRED_SUFFIX, ) - last_name = forms.CharField(label="Last name/family name") - title = forms.CharField(label="Title or role in your organization") email = forms.EmailField( - label="Email", error_messages={"invalid": "Please enter a valid email address."} + label="Email", + label_suffix=REQUIRED_SUFFIX, + error_messages={"invalid": "Please enter a valid email address."} + ) + phone = PhoneNumberField( + label="Phone", + label_suffix=REQUIRED_SUFFIX, ) - phone = PhoneNumberField(label="Phone") class CurrentSitesForm(RegistrarForm): @@ -164,6 +215,11 @@ class CurrentSitesForm(RegistrarForm): return inputted_site # something has been inputted + + if inputted_site.startswith("http://") or inputted_site.startswith("https://"): + # strip of the protocol that the pasted from their web browser + inputted_site = inputted_site.split("//", 1)[1] + if Domain.string_could_be_domain(inputted_site): return inputted_site else: @@ -247,7 +303,11 @@ class DotGovDomainForm(RegistrarForm): class PurposeForm(RegistrarForm): - purpose = forms.CharField(label="Purpose", widget=forms.Textarea()) + purpose = forms.CharField( + label="Purpose", + widget=forms.Textarea(), + error_messages={"required": "You must enter some information about the purpose of your domain"} + ) class YourContactForm(RegistrarForm): @@ -270,17 +330,31 @@ class YourContactForm(RegistrarForm): if contact is not None: super().from_database(contact) - first_name = forms.CharField(label="First name/given name") + first_name = forms.CharField( + label="First name/given name", + label_suffix=REQUIRED_SUFFIX, + ) middle_name = forms.CharField( required=False, - label="Middle name (optional)", + label="Middle name", + ) + last_name = forms.CharField( + label="Last name/family name", + label_suffix=REQUIRED_SUFFIX, + ) + title = forms.CharField( + label="Title or role in your organization", + label_suffix=REQUIRED_SUFFIX, ) - last_name = forms.CharField(label="Last name/family name") - title = forms.CharField(label="Title or role in your organization") email = forms.EmailField( - label="Email", error_messages={"invalid": "Please enter a valid email address."} + label="Email", + label_suffix=REQUIRED_SUFFIX, + error_messages={"invalid": "Please enter a valid email address."} + ) + phone = PhoneNumberField( + label="Phone", + label_suffix=REQUIRED_SUFFIX, ) - phone = PhoneNumberField(label="Phone") class OtherContactsForm(RegistrarForm): @@ -305,17 +379,32 @@ class OtherContactsForm(RegistrarForm): if other_contacts is not None: super().from_database(other_contacts) - first_name = forms.CharField(label="First name/given name") + + first_name = forms.CharField( + label="First name/given name", + label_suffix=REQUIRED_SUFFIX, + ) middle_name = forms.CharField( required=False, - label="Middle name (optional)", + label="Middle name", + ) + last_name = forms.CharField( + label="Last name/family name", + label_suffix=REQUIRED_SUFFIX, + ) + title = forms.CharField( + label="Title or role in your organization", + label_suffix=REQUIRED_SUFFIX, ) - last_name = forms.CharField(label="Last name/family name") - title = forms.CharField(label="Title or role in your organization") email = forms.EmailField( - label="Email", error_messages={"invalid": "Please enter a valid email address."} + label="Email", + label_suffix=REQUIRED_SUFFIX, + error_messages={"invalid": "Please enter a valid email address."} + ) + phone = PhoneNumberField( + label="Phone", + label_suffix=REQUIRED_SUFFIX, ) - phone = PhoneNumberField(label="Phone") class SecurityEmailForm(RegistrarForm): diff --git a/src/registrar/templates/application_authorizing_official.html b/src/registrar/templates/application_authorizing_official.html index 6af18991a..1265fd5ff 100644 --- a/src/registrar/templates/application_authorizing_official.html +++ b/src/registrar/templates/application_authorizing_official.html @@ -19,9 +19,9 @@

We’ll contact your authorizing official to let them know that you made this request and to double check that they approve it.

-

All fields are required unless they are marked optional.

+{% include "includes/required_fields.html" %} -
+ {{ wizard.management_form }} {% csrf_token %} diff --git a/src/registrar/templates/application_org_contact.html b/src/registrar/templates/application_org_contact.html index c5b0c87ad..4a90244a4 100644 --- a/src/registrar/templates/application_org_contact.html +++ b/src/registrar/templates/application_org_contact.html @@ -13,10 +13,10 @@

If your domain request is approved, the name of your organization will be publicly listed as the domain registrant.

-

All fields are required unless they are marked optional.

+ {% include "includes/required_fields.html" %} - + {{ wizard.management_form }} {% csrf_token %} @@ -24,8 +24,7 @@ What is the name and mailing address of your organization? {% if is_federal %} - {{ wizard.form.federal_agency|add_label_class:"usa-label" }} - {{ wizard.form.federal_agency|add_class:"usa-select" }} + {% select_with_errors wizard.form.federal_agency required=True %} {% endif %} {% input_with_errors wizard.form.organization_name %} @@ -36,8 +35,7 @@ {% input_with_errors wizard.form.city %} - {{ wizard.form.state_territory|add_label_class:"usa-label" }} - {{ wizard.form.state_territory|add_class:"usa-select" }} + {% select_with_errors wizard.form.state_territory %} {% input_with_errors wizard.form.zipcode add_class="usa-input--small" %} diff --git a/src/registrar/templates/application_org_election.html b/src/registrar/templates/application_org_election.html index 877bdb81b..2f61c626a 100644 --- a/src/registrar/templates/application_org_election.html +++ b/src/registrar/templates/application_org_election.html @@ -5,16 +5,17 @@ {% block form_content %} - + {{ wizard.management_form }} {% csrf_token %}

Is your organization an election office?

+

This question is required.

{% radio_buttons_by_value wizard.form.is_election_board as choices %} {% for choice in choices.values %} - {% include "includes/radio_button.html" with choice=choice tile="true" %} + {% include "includes/radio_button.html" with choice=choice tile="true" required="true"%} {% endfor %}
diff --git a/src/registrar/templates/application_org_federal.html b/src/registrar/templates/application_org_federal.html index b4da3b2f0..6640e43f0 100644 --- a/src/registrar/templates/application_org_federal.html +++ b/src/registrar/templates/application_org_federal.html @@ -5,7 +5,7 @@ {% block form_content %} - + {{ wizard.management_form }} {% csrf_token %}
diff --git a/src/registrar/templates/application_org_type.html b/src/registrar/templates/application_org_type.html index 3c2b19a25..30822ca63 100644 --- a/src/registrar/templates/application_org_type.html +++ b/src/registrar/templates/application_org_type.html @@ -5,7 +5,7 @@ {% block form_content %} - + {{ wizard.management_form }} {% csrf_token %} @@ -13,15 +13,15 @@
-

What kind of U.S.-based government organization do you represent?

+

What kind of U.S.-based government organization do you represent?

+

This question is required.

- {{ wizard.form.organization_type.errors }} {% for choice in choices.values %} {% include "includes/radio_button.html" with choice=choice tile="true" %} {% endfor %}
- {{ block.super }} + {{ block.super }} {% endblock %} diff --git a/src/registrar/templates/application_other_contacts.html b/src/registrar/templates/application_other_contacts.html index 39f8957a0..9bb166359 100644 --- a/src/registrar/templates/application_other_contacts.html +++ b/src/registrar/templates/application_other_contacts.html @@ -7,9 +7,9 @@ {% block form_content %}

We’d like to contact other employees with administrative or technical responsibilities in your organization. For example, they could be involved in managing your organization or its technical infrastructure. This information will help us assess your eligibility and understand the purpose of the .gov domain. These contacts should be in addition to you and your authorizing official.

-

All fields are required unless they are marked optional.

+{% include "includes/required_fields.html" %} -
+ {{ wizard.management_form }} {% csrf_token %} diff --git a/src/registrar/templates/application_purpose.html b/src/registrar/templates/application_purpose.html index ce0563036..8dd2d2ab3 100644 --- a/src/registrar/templates/application_purpose.html +++ b/src/registrar/templates/application_purpose.html @@ -5,14 +5,14 @@ {% block form_content %}

.Gov domain names are intended for use on the internet. They should be registered with an intent to deploy services, not simply to reserve a name. .Gov domains should not be registered for primarily internal use.

- +

Describe the reason for your domain request. Explain how you plan to use this domain. Will you use it for a website and/or email? Are you moving your website from another top-level domain (like .com or .org)? Read about activities that are prohibited on .gov domains.

- +
{{ wizard.management_form }} {% csrf_token %} - +
{{ wizard.form.purpose|add_label_class:"usa-label usa-sr-only" }} {{ wizard.form.purpose|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }} diff --git a/src/registrar/templates/application_requirements.html b/src/registrar/templates/application_requirements.html index 7389f3c46..26a905b4b 100644 --- a/src/registrar/templates/application_requirements.html +++ b/src/registrar/templates/application_requirements.html @@ -127,7 +127,7 @@

Acknowledgement of .gov domain requirements

- +
{{ wizard.management_form }} {% csrf_token %} @@ -140,13 +140,13 @@ {% endfor %}
- {{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"|add_class:"usa-input--error"|attr:"aria-invalid:true" }} + {{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"|add_class:"usa-input--error"|attr:"aria-invalid:true"|attr:"required" }} {{ wizard.form.is_policy_acknowledged|add_label_class:"usa-checkbox__label usa-label--error" }}
{% else %}
- {{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"}} + {{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"|attr:"required"}} {{ wizard.form.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }}
{% endif %} diff --git a/src/registrar/templates/application_security_email.html b/src/registrar/templates/application_security_email.html index ddde6f9bb..f7b2726ba 100644 --- a/src/registrar/templates/application_security_email.html +++ b/src/registrar/templates/application_security_email.html @@ -7,7 +7,7 @@

We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. Security emails are made public. We recommend using an alias, like security@<domain.gov>.

- + {{ wizard.management_form }} {% csrf_token %} diff --git a/src/registrar/templates/application_your_contact.html b/src/registrar/templates/application_your_contact.html index 60a1339d5..d3b51650e 100644 --- a/src/registrar/templates/application_your_contact.html +++ b/src/registrar/templates/application_your_contact.html @@ -14,9 +14,9 @@

The contact information you provide here won’t be public and will only be used for the .gov registry.

-

All fields are required unless they are marked optional.

+{% include "includes/required_fields.html" %} - + {{ wizard.management_form }} {% csrf_token %} diff --git a/src/registrar/templates/includes/input_with_errors.html b/src/registrar/templates/includes/input_with_errors.html index 09b52b190..a1662c40b 100644 --- a/src/registrar/templates/includes/input_with_errors.html +++ b/src/registrar/templates/includes/input_with_errors.html @@ -13,9 +13,17 @@ error messages, if necessary. {{ error }} {% endfor %} - {{ field|add_class:input_class|add_class:"usa-input--error"|attr:"aria-invalid:true" }} + {% if required %} + {{ field|add_class:input_class|add_class:"usa-input--error"|attr:"aria-invalid:true"|attr:"required" }} + {% else %} + {{ field|add_class:input_class|add_class:"usa-input--error"|attr:"aria-invalid:true" }} + {% endif %}
{% else %} {{ field|add_label_class:"usa-label" }} - {{ field|add_class:input_class }} + {% if required %} + {{ field|add_class:input_class|attr:"required" }} + {% else %} + {{ field|add_class:input_class }} + {% endif %} {% endif %} diff --git a/src/registrar/templates/includes/radio_button.html b/src/registrar/templates/includes/radio_button.html index 85f64b831..5a3edc9f4 100644 --- a/src/registrar/templates/includes/radio_button.html +++ b/src/registrar/templates/includes/radio_button.html @@ -5,7 +5,7 @@ value="{{ choice.data.value }}" class="usa-radio__input {% if tile %}usa-radio__input--tile {%endif%}" id="{{ choice.id_for_label }}" - {% if choice.data.attrs.required %}required{% endif %} + {% if choice.data.attrs.required or required %}required{% endif %} {% if choice.data.selected %}checked{% endif %} />
- +

What .gov domain do you want?

After you enter your domain, we’ll make sure it’s available and that it meets some of our naming requirements. If your domain passes these initial checks, we’ll verify that it meets all of our requirements once you complete and submit the rest of this form.

+

This question is required.

{{ wizard.management_form }} {% csrf_token %} {% if wizard.form.requested_domain.errors %}
- {{ wizard.form.requested_domain|add_label_class:"usa-label usa-label--error" }} {% for error in wizard.form.requested_domain.errors %} {{ error }} @@ -43,7 +43,6 @@
{% else %} - {{ wizard.form.requested_domain|add_label_class:"usa-label" }}
www. {{ wizard.form.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} diff --git a/src/registrar/templates/application_org_federal.html b/src/registrar/templates/application_org_federal.html index 6640e43f0..6d9b0a2c1 100644 --- a/src/registrar/templates/application_org_federal.html +++ b/src/registrar/templates/application_org_federal.html @@ -10,7 +10,8 @@ {% csrf_token %}
-

Which federal branch is your organization in?

+

Which federal branch is your organization in?

+

This question is required.

{% radio_buttons_by_value wizard.form.federal_type as choices %} {% for choice in choices.values %} diff --git a/src/registrar/templates/application_purpose.html b/src/registrar/templates/application_purpose.html index 8dd2d2ab3..b03228a07 100644 --- a/src/registrar/templates/application_purpose.html +++ b/src/registrar/templates/application_purpose.html @@ -8,14 +8,30 @@

Describe the reason for your domain request. Explain how you plan to use this domain. Will you use it for a website and/or email? Are you moving your website from another top-level domain (like .com or .org)? Read about activities that are prohibited on .gov domains.

+

This question is required.

+
{{ wizard.management_form }} {% csrf_token %}
- {{ wizard.form.purpose|add_label_class:"usa-label usa-sr-only" }} - {{ wizard.form.purpose|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }} + {% with field=wizard.form.purpose %} + {% if field.errors %} +
+ {{ field|add_label_class:"usa-label usa-label--error usa-sr-only" }} + {% for error in field.errors %} + + {{ error }} + + {% endfor %} + {{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500"|attr:"aria-invalid:true" }} +
+ {% else %} + {{ field|add_label_class:"usa-label usa-sr-only" }} + {{ field|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }} + {% endif %} + {% endwith %} You can enter up to 500 characters
diff --git a/src/registrar/templates/application_requirements.html b/src/registrar/templates/application_requirements.html index 26a905b4b..4c91d99ae 100644 --- a/src/registrar/templates/application_requirements.html +++ b/src/registrar/templates/application_requirements.html @@ -127,6 +127,8 @@

Acknowledgement of .gov domain requirements

+

This question is required.

+
{{ wizard.management_form }} From dc7c78205016bd79c6d16e332f6b4dd15bae1b21 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Mon, 9 Jan 2023 14:03:00 -0600 Subject: [PATCH 7/9] Fix issues from merge --- src/registrar/forms/application_wizard.py | 11 +++++++ .../templates/application_current_sites.html | 2 +- .../templates/application_dotgov_domain.html | 20 ++++++------ src/registrar/templates/application_form.html | 32 ++++++++++--------- .../templates/application_security_email.html | 12 +++---- 5 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index fc780b760..f72336cd8 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -446,3 +446,14 @@ class RequirementsForm(RegistrarForm): ), required=False, # use field validation to enforce this ) + + def clean_is_policy_acknowledged(self): + """This box must be checked to proceed but offer a clear error.""" + # already converted to a boolean + is_acknowledged = self.cleaned_data["is_policy_acknowledged"] + if not is_acknowledged: + raise forms.ValidationError( + "You must read and agree to the .gov domain requirements to proceed.", + code="invalid", + ) + return is_acknowledged diff --git a/src/registrar/templates/application_current_sites.html b/src/registrar/templates/application_current_sites.html index 5b3943ef7..c72a7391f 100644 --- a/src/registrar/templates/application_current_sites.html +++ b/src/registrar/templates/application_current_sites.html @@ -8,7 +8,7 @@ {% csrf_token %} - {% input_with_errors forms.0.form.current_site %} + {% input_with_errors forms.0.current_site %} {{ block.super }} diff --git a/src/registrar/templates/application_dotgov_domain.html b/src/registrar/templates/application_dotgov_domain.html index dc968b278..4225764e5 100644 --- a/src/registrar/templates/application_dotgov_domain.html +++ b/src/registrar/templates/application_dotgov_domain.html @@ -28,23 +28,23 @@

This question is required.

{% csrf_token %} - {% if wizard.form.requested_domain.errors %} + {% if forms.0.requested_domain.errors %}
- {% for error in wizard.form.requested_domain.errors %} + {% for error in forms.0.requested_domain.errors %} {{ error }} {% endfor %}
www. - {{ wizard.form.requested_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }} + {{ forms.0.requested_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }} .gov
{% else %}
www. - {{ wizard.form.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} + {{ forms.0.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} .gov
{% endif %} @@ -53,25 +53,25 @@

Alternative domains

- {% if wizard.form.alternative_domain.errors %} + {% if forms.0.alternative_domain.errors %}
- {{ wizard.form.alternative_domain|add_label_class:"usa-label usa-label--error" }} - {% for error in wizard.for.alternative_domain.errors %} + {{ forms.0.alternative_domain|add_label_class:"usa-label usa-label--error" }} + {% for error in forms.0.alternative_domain.errors %} {{ error }} {% endfor %}
www. - {{ wizard.form.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }} + { forms.0.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }} .gov
{% else %} - {{ wizard.form.alternative_domain|add_label_class:"usa-label" }} + {{ forms.0.alternative_domain|add_label_class:"usa-label" }}
www. - {{ wizard.form.alternative_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} + {{ forms.0.alternative_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} .gov
{% endif %} diff --git a/src/registrar/templates/application_form.html b/src/registrar/templates/application_form.html index 52052f536..6117470a4 100644 --- a/src/registrar/templates/application_form.html +++ b/src/registrar/templates/application_form.html @@ -18,24 +18,26 @@ {% endif %} - {% if form.errors %} - {% for error in form.non_field_errors %} -
-
- {{ error|escape }} -
-
- {% endfor %} - {% for field in form %} - {% for error in field.errors %} -
+ {% for form in forms %} + {% if form.errors %} + {% for error in form.non_field_errors %} +
- {{ error|escape }} + {{ error|escape }} +
-
{% endfor %} - {% endfor %} - {% endif %} + {% for field in form %} + {% for error in field.errors %} +
+
+ {{ error|escape }} +
+
+ {% endfor %} + {% endfor %} + {% endif %} + {% endfor %}

{{form_titles|get_item:steps.current}}

{% block form_content %} diff --git a/src/registrar/templates/application_security_email.html b/src/registrar/templates/application_security_email.html index d2a4c6564..36595ad38 100644 --- a/src/registrar/templates/application_security_email.html +++ b/src/registrar/templates/application_security_email.html @@ -10,19 +10,19 @@ {% csrf_token %} - {% if wizard.form.security_email.errors %} + {% if forms.0.security_email.errors %}
- {{ wizard.form.security_email|add_label_class:"usa-label usa-label--error" }} - {% for error in wizard.form.security_email.errors %} + {{ forms.0.security_email|add_label_class:"usa-label usa-label--error" }} + {% for error in forms.0.security_email.errors %} {{ error }} {% endfor %} - {{ wizard.form.security_email|add_class:"usa-input"|add_class:"usa-input--error"|attr:"aria-describedby:instructions"|attr:"aria-invalid:true" }} + {{ forms.0.security_email|add_class:"usa-input"|add_class:"usa-input--error"|attr:"aria-describedby:instructions"|attr:"aria-invalid:true" }}
{% else %} - {{ wizard.form.security_email|add_label_class:"usa-label" }} - {{ wizard.form.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {{ forms.0.security_email|add_label_class:"usa-label" }} + {{ forms.0.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }} {% endif %} {{ block.super }} From 0a3c3ec9f545e754948da625ce8549c968c6725f Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Mon, 9 Jan 2023 15:05:39 -0600 Subject: [PATCH 8/9] Fix failing test --- src/registrar/forms/application_wizard.py | 14 ++++++++++---- src/registrar/tests/test_views.py | 20 +++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index f72336cd8..28118ed53 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -11,7 +11,7 @@ from registrar.models import Contact, DomainApplication, Domain logger = logging.getLogger(__name__) -# nosec because this use of mark_safe does not introduce a cross-site scripting +# no sec because this use of mark_safe does not introduce a cross-site scripting # vulnerability because there is no untrusted content inside. It is # only being used to pass a specific HTML entity into a template. REQUIRED_SUFFIX = mark_safe( # nosec @@ -145,12 +145,18 @@ class OrganizationContactForm(RegistrarForm): federal_agency = self.cleaned_data.get("federal_agency", None) # need the application object to know if this is federal if self.application is None: - # hmm, no saved application object? - raise ValueError("Form has no active application object.") + # hmm, no saved application object?, default require the agency + if not federal_agency: + # no answer was selected + raise forms.ValidationError( + "Please select your federal agency.", code="required" + ) if self.application.is_federal: if not federal_agency: # no answer was selected - raise forms.ValidationError("Please select your federal agency.", code="required") + raise forms.ValidationError( + "Please select your federal agency.", code="required" + ) return federal_agency diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 6f1d7ff6d..f511a0f33 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -51,6 +51,8 @@ class TestWithUser(TestCase): ) def tearDown(self): + # delete any applications too + DomainApplication.objects.all().delete() self.user.delete() @@ -103,11 +105,6 @@ class DomainApplicationTests(TestWithUser, WebTest): self.app.set_user(self.user.username) self.TITLES = ApplicationWizard.TITLES - def tearDown(self): - # delete any applications we made so that users can be deleted - DomainApplication.objects.all().delete() - super().tearDown() - def test_application_form_empty_submit(self): # 302 redirect to the first form page = self.app.get(reverse("application:")).follow() @@ -185,6 +182,10 @@ class DomainApplicationTests(TestWithUser, WebTest): self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) org_contact_page = federal_result.follow() org_contact_form = org_contact_page.form + # federal agency so we have to fill in federal_agency + org_contact_form[ + "organization_contact-federal_agency" + ] = "General Services Administration" org_contact_form["organization_contact-organization_name"] = "Testorg" org_contact_form["organization_contact-address_line1"] = "address 1" org_contact_form["organization_contact-address_line2"] = "address 2" @@ -220,6 +221,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- AUTHORIZING OFFICIAL PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) ao_page = org_contact_result.follow() ao_form = ao_page.form ao_form["authorizing_official-first_name"] = "Testy ATO" @@ -251,6 +253,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- CURRENT SITES PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) current_sites_page = ao_result.follow() current_sites_form = current_sites_page.form current_sites_form["current_sites-current_site"] = "www.city.com" @@ -276,6 +279,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- DOTGOV DOMAIN PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) dotgov_page = current_sites_result.follow() dotgov_form = dotgov_page.form dotgov_form["dotgov_domain-requested_domain"] = "city" @@ -302,6 +306,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- PURPOSE PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) purpose_page = dotgov_result.follow() purpose_form = purpose_page.form purpose_form["purpose-purpose"] = "For all kinds of things." @@ -325,6 +330,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- YOUR CONTACT INFO PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) your_contact_page = purpose_result.follow() your_contact_form = your_contact_page.form @@ -357,6 +363,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- OTHER CONTACTS PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) other_contacts_page = your_contact_result.follow() other_contacts_form = other_contacts_page.form @@ -396,6 +403,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- SECURITY EMAIL PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) security_email_page = other_contacts_result.follow() security_email_form = security_email_page.form @@ -420,6 +428,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- ANYTHING ELSE PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) anything_else_page = security_email_result.follow() anything_else_form = anything_else_page.form @@ -444,6 +453,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- REQUIREMENTS PAGE ---- # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) requirements_page = anything_else_result.follow() requirements_form = requirements_page.form From a3bff304dab0fec8f14fcc22b012173a252b54b1 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 10 Jan 2023 09:15:43 -0600 Subject: [PATCH 9/9] Pipfile re-locked and docstring --- src/Pipfile.lock | 44 +++++++++++++++------ src/registrar/templatetags/field_helpers.py | 6 +++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/Pipfile.lock b/src/Pipfile.lock index 2a531a89b..05e062c70 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1668475ce39851bd84ff7be330afe9766f6823cf9095980ba3b220ced3a284f4" + "sha256": "b75e38d4e723e06a194cb607ba3003ed7a9f0460f23d036d9cd18214341d3e77" }, "pipfile-spec": 6, "requires": {}, @@ -24,11 +24,11 @@ }, "cachetools": { "hashes": [ - "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757", - "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db" + "sha256:5991bc0e08a1319bb618d3195ca5b6bc76646a49c21d55962977197b301cc1fe", + "sha256:8462eebf3a6c15d25430a8c27c56ac61340b2ecf60c9ce57afc2b97e450e47da" ], "index": "pypi", - "version": "==5.2.0" + "version": "==5.2.1" }, "certifi": { "hashes": [ @@ -120,7 +120,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.1.1" }, "cryptography": { @@ -221,6 +221,17 @@ "index": "pypi", "version": "==2.8.1" }, + "django-phonenumber-field": { + "extras": [ + "phonenumberslite" + ], + "hashes": [ + "sha256:9edad2b2602af25f2aefc73c4cf53eaf7abf9e17d73c1c4372bd3052bebb26f9", + "sha256:de3e47b986b4959949762c16fd8fe26b3e462ef3e5531ed00950bd20c698576a" + ], + "index": "pypi", + "version": "==7.0.2" + }, "django-widget-tweaks": { "hashes": [ "sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e", @@ -242,11 +253,11 @@ }, "faker": { "hashes": [ - "sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc", - "sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76" + "sha256:4a8bc3cec832dde1928f8ce0817452bdadf63863d9e4d8307817247a38e51523", + "sha256:e15becbddc3a69a342e03ca6810caab7299e28e48106ae113a07f65c627d6fd7" ], "index": "pypi", - "version": "==15.3.4" + "version": "==16.1.0" }, "furl": { "hashes": [ @@ -357,11 +368,18 @@ }, "packaging": { "hashes": [ - "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3", - "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3" + "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", + "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" ], "markers": "python_version >= '3.7'", - "version": "==22.0" + "version": "==23.0" + }, + "phonenumberslite": { + "hashes": [ + "sha256:44cbb13581122164cd8a83b40f12db854277e8a5f9c6e22bd8dc2d8aa98e3260", + "sha256:469eb263160e243aa02fff643502698f99e77bcb6478e9aaa7115838006be122" + ], + "version": "==8.13.4" }, "psycopg2-binary": { "hashes": [ @@ -581,7 +599,7 @@ "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==4.11.1" }, "black": { @@ -925,7 +943,7 @@ "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a", "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba" ], - "markers": "python_full_version >= '3.7.0'", + "markers": "python_version >= '3.7'", "version": "==2.1.2" }, "webob": { diff --git a/src/registrar/templatetags/field_helpers.py b/src/registrar/templatetags/field_helpers.py index cfc464223..789488e5b 100644 --- a/src/registrar/templatetags/field_helpers.py +++ b/src/registrar/templatetags/field_helpers.py @@ -6,6 +6,12 @@ register = template.Library() def _field_context(field, input_class, add_class, required=False): + """Helper to construct template context. + + input_class is the CSS class to use on the input element, add_class + will be added to that if given, and required will be set if + it is provided and not False. + """ if add_class: input_class += " " + add_class context = {"field": field, "input_class": input_class}