Field validation and form errors in templates

This commit is contained in:
Neil Martinsen-Burrell 2022-12-12 15:53:43 -06:00
parent 5eaf92caa5
commit c924a5fd9a
No known key found for this signature in database
GPG key ID: 6A3C818CC10D0184
18 changed files with 590 additions and 270 deletions

View file

@ -21,6 +21,7 @@ django-widget-tweaks = "*"
cachetools = "*" cachetools = "*"
requests = "*" requests = "*"
django-fsm = "*" django-fsm = "*"
django-phonenumber-field = {extras = ["phonenumberslite"], version = "*"}
[dev-packages] [dev-packages]
django-debug-toolbar = "*" django-debug-toolbar = "*"

379
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "4e755e3f5778ff572fba5755b966cde05d30a84c4eddb1d63ca5fe1034565283" "sha256": "9e2fe58b6282514da5d054147426e561f451da29f3c743c0cd9dccf4d7dba0cc"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -24,9 +24,9 @@
}, },
"beaker": { "beaker": {
"hashes": [ "hashes": [
"sha256:ad5d1c05027ee3be3a482ea39f8cb70339b41e5d6ace0cb861382754076d187e" "sha256:2d5f427e3b13259c98c934cab0e428fc1c18a4c4b94acbdae930df7e7f51d1ec"
], ],
"version": "==1.11.0" "version": "==1.12.0"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -38,11 +38,11 @@
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
"sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2022.9.24" "version": "==2022.12.7"
}, },
"cfenv": { "cfenv": {
"hashes": [ "hashes": [
@ -126,40 +126,40 @@
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==2.1.1" "version": "==2.1.1"
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d", "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd",
"sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd", "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db",
"sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146", "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290",
"sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7", "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744",
"sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436", "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb",
"sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0", "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d",
"sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828", "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70",
"sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b", "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b",
"sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55", "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876",
"sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36", "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083",
"sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50", "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6",
"sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2", "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1",
"sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a", "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00",
"sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8", "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b",
"sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0", "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b",
"sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548", "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285",
"sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320", "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9",
"sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748", "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0",
"sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249", "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d",
"sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959", "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2",
"sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f", "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8",
"sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0", "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee",
"sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd", "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b",
"sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220", "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7",
"sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c", "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353",
"sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722" "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==38.0.3" "version": "==38.0.4"
}, },
"defusedxml": { "defusedxml": {
"hashes": [ "hashes": [
@ -171,10 +171,10 @@
}, },
"dj-database-url": { "dj-database-url": {
"hashes": [ "hashes": [
"sha256:ccf3e8718f75ddd147a1e212fca88eecdaa721759ee48e38b485481c77bca3dc", "sha256:5f2f6b3f65786bac5d3b9e749bff1dcac83398d95778576909697f7b16aee6b9",
"sha256:cd354a3b7a9136d78d64c17b2aec369e2ae5616fbca6bfbe435ef15bb372ce39" "sha256:8be4253439d75412aaad4f82af7aecda956893c87fb8d10edc0adb2d34312527"
], ],
"version": "==1.0.0" "version": "==1.1.0"
}, },
"dj-email-url": { "dj-email-url": {
"hashes": [ "hashes": [
@ -185,11 +185,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:678bbfc8604eb246ed54e2063f0765f13b321a50526bdc8cb1f943eda7fa31f1", "sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148",
"sha256:6b1de6886cae14c7c44d188f580f8ba8da05750f544c80ae5ad43375ab293cd5" "sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.1.3" "version": "==4.1.4"
}, },
"django-allow-cidr": { "django-allow-cidr": {
"hashes": [ "hashes": [
@ -201,18 +201,18 @@
}, },
"django-auditlog": { "django-auditlog": {
"hashes": [ "hashes": [
"sha256:0ab57a536e02341e27c3d0431ad0e124e674507bd965a0756e29b01cb67c38ce", "sha256:51c724f878fb3bc275c498e2e44583f28565135b1c60e6e8f7faf54e030c804f",
"sha256:2f83389f98db4b1a9c2961f17cd9ac4a3ea94304655071f30da45d8debf59688" "sha256:9ad9a0a04d37aa6dc8956126ceb499d64edf71f8fddb0bc908f1217b0c31ec21"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.2.0" "version": "==2.2.1"
}, },
"django-cache-url": { "django-cache-url": {
"hashes": [ "hashes": [
"sha256:6cc9901a99a99751f5458aa7de08ce06e48c1441b1a94c9457d78af74fab9a26", "sha256:5ca4760b4580b80e41279bc60d1e5c16a822e4e462265faab0a330701bb0ef9a",
"sha256:c4a62634cffc9d636073cef597a44576d67b07660ab2ef1f02b160ee7ecf0e98" "sha256:ef2cfacea361ee22e9b67d6ca941db22e0a9eaf892b67ca71cad52c62a17fd36"
], ],
"version": "==3.4.2" "version": "==3.4.4"
}, },
"django-csp": { "django-csp": {
"hashes": [ "hashes": [
@ -238,6 +238,17 @@
"index": "pypi", "index": "pypi",
"version": "==2.8.1" "version": "==2.8.1"
}, },
"django-phonenumber-field": {
"extras": [
"phonenumberslite"
],
"hashes": [
"sha256:969bbcab203d697ea44c38726bdc7d72ac9f1ba397694b9f57422b471ad73590",
"sha256:9e2b302f239e4703fa9030e44833db5eb524a731335fd77b0b703bd352fbe8d0"
],
"index": "pypi",
"version": "==7.0.1"
},
"django-widget-tweaks": { "django-widget-tweaks": {
"hashes": [ "hashes": [
"sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e", "sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e",
@ -289,11 +300,11 @@
}, },
"mako": { "mako": {
"hashes": [ "hashes": [
"sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd", "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818",
"sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f" "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==1.2.3" "version": "==1.2.4"
}, },
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
@ -343,11 +354,11 @@
}, },
"marshmallow": { "marshmallow": {
"hashes": [ "hashes": [
"sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104", "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78",
"sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7" "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==3.18.0" "version": "==3.19.0"
}, },
"oic": { "oic": {
"hashes": [ "hashes": [
@ -366,11 +377,18 @@
}, },
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3",
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.7'",
"version": "==21.3" "version": "==22.0"
},
"phonenumberslite": {
"hashes": [
"sha256:87218f3f85f67779007b6cfeeca59b1d370b96f9b3867347f0e6d094b1a3df64",
"sha256:8daa2fff393e291531aaa5ed4ddc123749b631d48ae7e504e8d7a4e818b3f799"
],
"version": "==8.13.2"
}, },
"psycopg2-binary": { "psycopg2-binary": {
"hashes": [ "hashes": [
@ -458,39 +476,35 @@
}, },
"pycryptodomex": { "pycryptodomex": {
"hashes": [ "hashes": [
"sha256:04cc393045a8f19dd110c975e30f38ed7ab3faf21ede415ea67afebd95a22380", "sha256:04610536921c1ec7adba158ef570348550c9f3a40bc24be9f8da2ef7ab387981",
"sha256:0776bfaf2c48154ab54ea45392847c1283d2fcf64e232e85565f858baedfc1fa", "sha256:0ba28aa97cdd3ff5ed1a4f2b7f5cd04e721166bd75bd2b929e2734433882b583",
"sha256:0fadb9f7fa3150577800eef35f62a8a24b9ddf1563ff060d9bd3af22d3952c8c", "sha256:0da835af786fdd1c9930994c78b23e88d816dc3f99aa977284a21bbc26d19735",
"sha256:18e2ab4813883ae63396c0ffe50b13554b32bb69ec56f0afaf052e7a7ae0d55b", "sha256:1619087fb5b31510b0b0b058a54f001a5ffd91e6ffee220d9913064519c6a69d",
"sha256:191e73bc84a8064ad1874dba0ebadedd7cce4dedee998549518f2c74a003b2e1", "sha256:1cda60207be8c1cf0b84b9138f9e3ca29335013d2b690774a5e94678ff29659a",
"sha256:35a8f7afe1867118330e2e0e0bf759c409e28557fb1fc2fbb1c6c937297dbe9a", "sha256:22aed0868622d95179217c298e37ed7410025c7b29dac236d3230617d1e4ed56",
"sha256:3709f13ca3852b0b07fc04a2c03b379189232b24007c466be0f605dd4723e9d4", "sha256:231dc8008cbdd1ae0e34645d4523da2dbc7a88c325f0d4a59635a86ee25b41dd",
"sha256:4540904c09704b6f831059c0dfb38584acb82cb97b0125cd52688c1f1e3fffa6", "sha256:2ad9bb86b355b6104796567dd44c215b3dc953ef2fae5e0bdfb8516731df92cf",
"sha256:463119d7d22d0fc04a0f9122e9d3e6121c6648bcb12a052b51bd1eed1b996aa2", "sha256:4dbbe18cc232b5980c7633972ae5417d0df76fe89e7db246eefd17ef4d8e6d7a",
"sha256:46b3f05f2f7ac7841053da4e0f69616929ca3c42f238c405f6c3df7759ad2780", "sha256:6a465e4f856d2a4f2a311807030c89166529ccf7ccc65bef398de045d49144b6",
"sha256:48697790203909fab02a33226fda546604f4e2653f9d47bc5d3eb40879fa7c64", "sha256:70288d9bfe16b2fd0d20b6c365db614428f1bcde7b20d56e74cf88ade905d9eb",
"sha256:5676a132169a1c1a3712edf25250722ebc8c9102aa9abd814df063ca8362454f", "sha256:7993d26dae4d83b8f4ce605bb0aecb8bee330bb3c95475ef06f3694403621e71",
"sha256:65204412d0c6a8e3c41e21e93a5e6054a74fea501afa03046a388cf042e3377a", "sha256:8851585ff19871e5d69e1790f4ca5f6fd1699d6b8b14413b472a4c0dbc7ea780",
"sha256:67e1e6a92151023ccdfcfbc0afb3314ad30080793b4c27956ea06ab1fb9bcd8a", "sha256:893f8a97d533c66cc3a56e60dd3ed40a3494ddb4aafa7e026429a08772f8a849",
"sha256:6f5b6ba8aefd624834bc177a2ac292734996bb030f9d1b388e7504103b6fcddf", "sha256:8dd2d9e3c617d0712ed781a77efd84ea579e76c5f9b2a4bc0b684ebeddf868b2",
"sha256:7341f1bb2dadb0d1a0047f34c3a58208a92423cdbd3244d998e4b28df5eac0ed", "sha256:a1c0ae7123448ecb034c75c713189cb00ebe2d415b11682865b6c54d200d9c93",
"sha256:78d9621cf0ea35abf2d38fa2ca6d0634eab6c991a78373498ab149953787e5e5", "sha256:b0789a8490114a2936ed77c87792cfe77582c829cb43a6d86ede0f9624ba8aa3",
"sha256:8eecdf9cdc7343001d047f951b9cc805cd68cb6cd77b20ea46af5bffc5bd3dfb", "sha256:b3d04c00d777c36972b539fb79958790126847d84ec0129fce1efef250bfe3ce",
"sha256:94c7b60e1f52e1a87715571327baea0733708ab4723346598beca4a3b6879794", "sha256:ba57ac7861fd2c837cdb33daf822f2a052ff57dd769a2107807f52a36d0e8d38",
"sha256:996e1ba717077ce1e6d4849af7a1426f38b07b3d173b879e27d5e26d2e958beb", "sha256:ce338a9703f54b2305a408fc9890eb966b727ce72b69f225898bb4e9d9ed3f1f",
"sha256:a07a64709e366c2041cd5cfbca592b43998bf4df88f7b0ca73dca37071ccf1bd", "sha256:daa67f5ebb6fbf1ee9c90decaa06ca7fc88a548864e5e484d52b0920a57fe8a5",
"sha256:b6306403228edde6e289f626a3908a2f7f67c344e712cf7c0a508bab3ad9e381", "sha256:e2453162f473c1eae4826eb10cd7bce19b5facac86d17fb5f29a570fde145abd",
"sha256:b9279adc16e4b0f590ceff581f53a80179b02cba9056010d733eb4196134a870", "sha256:e25a2f5667d91795f9417cb856f6df724ccdb0cdd5cbadb212ee9bf43946e9f8",
"sha256:c4cb9cb492ea7dcdf222a8d19a1d09002798ea516aeae8877245206d27326d86", "sha256:e5a670919076b71522c7d567a9043f66f14b202414a63c3a078b5831ae342c03",
"sha256:dd452a5af7014e866206d41751886c9b4bf379a339fdf2dbfc7dd16c0fb4f8e0", "sha256:e9ba9d8ed638733c9e95664470b71d624a6def149e2db6cc52c1aca5a6a2df1d",
"sha256:e2b12968522a0358b8917fc7b28865acac002f02f4c4c6020fcb264d76bfd06d", "sha256:f2b971a7b877348a27dcfd0e772a0343fb818df00b74078e91c008632284137d"
"sha256:e3164a18348bd53c69b4435ebfb4ac8a4076291ffa2a70b54f0c4b80c7834b1d",
"sha256:e47bf8776a7e15576887f04314f5228c6527b99946e6638cf2f16da56d260cab",
"sha256:f8be976cec59b11f011f790b88aca67b4ea2bd286578d0bd3e31bcd19afcd3e4",
"sha256:fc9bc7a9b79fe5c750fc81a307052f8daabb709bdaabb0fb18fb136b66b653b5"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.15.0" "version": "==3.16.0"
}, },
"pyjwkest": { "pyjwkest": {
"hashes": [ "hashes": [
@ -499,14 +513,6 @@
"index": "pypi", "index": "pypi",
"version": "==1.4.2" "version": "==1.4.2"
}, },
"pyparsing": {
"hashes": [
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
],
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.9"
},
"python-dateutil": { "python-dateutil": {
"hashes": [ "hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
@ -533,11 +539,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31", "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54",
"sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f" "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==65.5.1" "version": "==65.6.3"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -565,11 +571,11 @@
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
"sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" "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'", "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.12" "version": "==1.26.13"
}, },
"whitenoise": { "whitenoise": {
"hashes": [ "hashes": [
@ -602,35 +608,26 @@
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==4.11.1" "version": "==4.11.1"
}, },
"black": { "black": {
"hashes": [ "hashes": [
"sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7", "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320",
"sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6", "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351",
"sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650", "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350",
"sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb", "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f",
"sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d", "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf",
"sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d", "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148",
"sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de", "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4",
"sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395", "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d",
"sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae", "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc",
"sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa", "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d",
"sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef", "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2",
"sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383", "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"
"sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66",
"sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87",
"sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d",
"sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0",
"sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b",
"sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458",
"sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4",
"sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1",
"sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"
], ],
"index": "pypi", "index": "pypi",
"version": "==22.10.0" "version": "==22.12.0"
}, },
"blinker": { "blinker": {
"hashes": [ "hashes": [
@ -650,27 +647,27 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:678bbfc8604eb246ed54e2063f0765f13b321a50526bdc8cb1f943eda7fa31f1", "sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148",
"sha256:6b1de6886cae14c7c44d188f580f8ba8da05750f544c80ae5ad43375ab293cd5" "sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.1.3" "version": "==4.1.4"
}, },
"django-debug-toolbar": { "django-debug-toolbar": {
"hashes": [ "hashes": [
"sha256:1e3acad24e3d351ba45c6fa2072e4164820307332a776b16c9f06d1f89503465", "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27",
"sha256:80de23066b624d3970fd296cf02d61988e5d56c31aa0dc4a428970b46e2883a8" "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.7.0" "version": "==3.8.1"
}, },
"django-stubs": { "django-stubs": {
"hashes": [ "hashes": [
"sha256:424fdd1935f859a802365056f9ccf4db12d1d93a5ab3de6d5633dddba0c5fc76", "sha256:bcc618ba353dabc540d982b9dac1d5a1921652f8fc2a13653d545a57d5e3cc0f",
"sha256:eaecc1fc71532c1148f0c9687556651d880165476d7629bf318ff86a903a150c" "sha256:fbf2ee6a4bce76c3eb5f6707ccadb4cf1c2f1ec485e8c44701ca8de2d0a5df18"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.13.0" "version": "==1.13.1"
}, },
"django-stubs-ext": { "django-stubs-ext": {
"hashes": [ "hashes": [
@ -690,19 +687,19 @@
}, },
"flake8": { "flake8": {
"hashes": [ "hashes": [
"sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7",
"sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248" "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.0.4" "version": "==6.0.0"
}, },
"gitdb": { "gitdb": {
"hashes": [ "hashes": [
"sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd", "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a",
"sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa" "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.7'",
"version": "==4.0.9" "version": "==4.0.10"
}, },
"gitpython": { "gitpython": {
"hashes": [ "hashes": [
@ -722,39 +719,39 @@
}, },
"mypy": { "mypy": {
"hashes": [ "hashes": [
"sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013", "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d",
"sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec", "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6",
"sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466", "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf",
"sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c", "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f",
"sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd", "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813",
"sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8", "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33",
"sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff", "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad",
"sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5", "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05",
"sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea", "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297",
"sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af", "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06",
"sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd", "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd",
"sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706", "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243",
"sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2", "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305",
"sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852", "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476",
"sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d", "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711",
"sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0", "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70",
"sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47", "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5",
"sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6", "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461",
"sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9", "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab",
"sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb", "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c",
"sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37", "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d",
"sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa", "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135",
"sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef", "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93",
"sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5", "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648",
"sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db", "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a",
"sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74", "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb",
"sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695", "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3",
"sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46", "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372",
"sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4", "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb",
"sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e" "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.990" "version": "==0.991"
}, },
"mypy-extensions": { "mypy-extensions": {
"hashes": [ "hashes": [
@ -773,11 +770,11 @@
}, },
"pathspec": { "pathspec": {
"hashes": [ "hashes": [
"sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93", "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6",
"sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d" "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.10.1" "version": "==0.10.3"
}, },
"pbr": { "pbr": {
"hashes": [ "hashes": [
@ -789,27 +786,27 @@
}, },
"platformdirs": { "platformdirs": {
"hashes": [ "hashes": [
"sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb", "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca",
"sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0" "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2.5.3" "version": "==2.6.0"
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
"sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
"sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b" "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2.9.1" "version": "==2.10.0"
}, },
"pyflakes": { "pyflakes": {
"hashes": [ "hashes": [
"sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf",
"sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3" "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2.5.0" "version": "==3.0.1"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
@ -929,18 +926,18 @@
}, },
"types-requests": { "types-requests": {
"hashes": [ "hashes": [
"sha256:bdb1f9811e53d0642c8347b09137363eb25e1a516819e190da187c29595a1df3", "sha256:091d4a5a33c1b4f20d8b1b952aa8fa27a6e767c44c3cf65e56580df0b05fd8a9",
"sha256:d4f342b0df432262e9e326d17638eeae96a5881e78e7a6aae46d33870d73952e" "sha256:a7df37cc6fb6187a84097da951f8e21d335448aa2501a6b0a39cbd1d7ca9ee2a"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.28.11.4" "version": "==2.28.11.5"
}, },
"types-urllib3": { "types-urllib3": {
"hashes": [ "hashes": [
"sha256:1807b87b8ee1ae0226813ba2c52330eff20fb2bf6359b1de24df08eb3090e442", "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49",
"sha256:a188c24fc61a99658c8c324c8dd7419f5b91a0d89df004e5f576869122c1db55" "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee"
], ],
"version": "==1.26.25.3" "version": "==1.26.25.4"
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
@ -955,7 +952,7 @@
"sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a", "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a",
"sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba" "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"
], ],
"markers": "python_full_version >= '3.7.0'", "markers": "python_version >= '3.7'",
"version": "==2.1.2" "version": "==2.1.2"
}, },
"webob": { "webob": {

View file

@ -90,6 +90,8 @@ INSTALLED_APPS = [
"widget_tweaks", "widget_tweaks",
# library for Finite State Machine statuses # library for Finite State Machine statuses
"django_fsm", "django_fsm",
# library for phone numbers
"phonenumber_field",
# let's be sure to install our own application! # let's be sure to install our own application!
"registrar", "registrar",
# Our internal API application # 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 # IS_DEMO_SITE controls whether or not we show our big red "TEST SITE" banner
# underneath the "this is a real government website" banner. # underneath the "this is a real government website" banner.
IS_DEMO_SITE = True IS_DEMO_SITE = True
@ -296,6 +300,9 @@ USE_L10N = True
# make datetimes timezone-aware by default # make datetimes timezone-aware by default
USE_TZ = True USE_TZ = True
# setting for phonenumber library
PHONENUMBER_DEFAULT_REGION="US"
# endregion # endregion
# region: Logging-----------------------------------------------------------### # region: Logging-----------------------------------------------------------###
@ -368,7 +375,7 @@ LOGGING = {
# Django's template processor # Django's template processor
"django.template": { "django.template": {
"handlers": ["console"], "handlers": ["console"],
"level": "INFO", "level": "DEBUG",
}, },
# Django's runserver # Django's runserver
"django.server": { "django.server": {
@ -379,13 +386,13 @@ LOGGING = {
# Django's runserver requests # Django's runserver requests
"django.request": { "django.request": {
"handlers": ["django.server"], "handlers": ["django.server"],
"level": "INFO", "level": "DEBUG",
"propagate": False, "propagate": False,
}, },
# OpenID Connect logger # OpenID Connect logger
"oic": { "oic": {
"handlers": ["console"], "handlers": ["console"],
"level": "INFO", "level": "DEBUG",
}, },
# Django wrapper for OpenID Connect # Django wrapper for OpenID Connect
"djangooidc": { "djangooidc": {

View file

@ -10,8 +10,10 @@ from django import forms
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import resolve from django.urls import resolve
from django.core.validators import RegexValidator
from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore
from phonenumber_field.formfields import PhoneNumberField
from registrar.models import Contact, DomainApplication, Domain from registrar.models import Contact, DomainApplication, Domain
@ -119,8 +121,10 @@ class AuthorizingOfficialForm(RegistrarForm):
) )
last_name = forms.CharField(label="Last name/family name") last_name = forms.CharField(label="Last name/family name")
title = forms.CharField(label="Title or role in your organization") title = forms.CharField(label="Title or role in your organization")
email = forms.EmailField(label="Email") email = forms.EmailField(
phone = forms.CharField(label="Phone") label="Email", error_messages={"invalid": "Please enter a valid email address."}
)
phone = PhoneNumberField(label="Phone")
class CurrentSitesForm(RegistrarForm): class CurrentSitesForm(RegistrarForm):
@ -146,6 +150,22 @@ class CurrentSitesForm(RegistrarForm):
"www.city.com.", "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): class DotGovDomainForm(RegistrarForm):
def to_database(self, obj): def to_database(self, obj):
@ -183,13 +203,41 @@ class DotGovDomainForm(RegistrarForm):
if alternative_domain is not None: if alternative_domain is not None:
self.initial["alternative_domain"] = alternative_domain.sld 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( alternative_domain = forms.CharField(
required=False, required=False,
label="Are there other domains youd like if we cant give you your first " label="Are there other domains youd like if we cant give you your first "
"choice? Entering alternative domains is optional.", "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): class PurposeForm(RegistrarForm):
purpose = forms.CharField(label="Purpose", widget=forms.Textarea()) purpose = forms.CharField(label="Purpose", widget=forms.Textarea())
@ -222,8 +270,10 @@ class YourContactForm(RegistrarForm):
) )
last_name = forms.CharField(label="Last name/family name") last_name = forms.CharField(label="Last name/family name")
title = forms.CharField(label="Title or role in your organization") title = forms.CharField(label="Title or role in your organization")
email = forms.EmailField(label="Email") email = forms.EmailField(
phone = forms.CharField(label="Phone") label="Email", error_messages={"invalid": "Please enter a valid email address."}
)
phone = PhoneNumberField(label="Phone")
class OtherContactsForm(RegistrarForm): class OtherContactsForm(RegistrarForm):
@ -255,14 +305,17 @@ class OtherContactsForm(RegistrarForm):
) )
last_name = forms.CharField(label="Last name/family name") last_name = forms.CharField(label="Last name/family name")
title = forms.CharField(label="Title or role in your organization") title = forms.CharField(label="Title or role in your organization")
email = forms.EmailField(label="Email") email = forms.EmailField(
phone = forms.CharField(label="Phone") label="Email", error_messages={"invalid": "Please enter a valid email address."}
)
phone = PhoneNumberField(label="Phone")
class SecurityEmailForm(RegistrarForm): class SecurityEmailForm(RegistrarForm):
security_email = forms.EmailField( security_email = forms.EmailField(
required=False, required=False,
label="Security email", label="Security email",
error_messages={"invalid": "Please enter a valid email address."},
) )
@ -276,9 +329,21 @@ class AnythingElseForm(RegistrarForm):
class RequirementsForm(RegistrarForm): class RequirementsForm(RegistrarForm):
is_policy_acknowledged = forms.BooleanField( 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): class ReviewForm(RegistrarForm):
""" """

View file

@ -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,
),
),
]

View file

@ -1,5 +1,7 @@
from django.db import models from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
class Contact(models.Model): class Contact(models.Model):
@ -33,7 +35,7 @@ class Contact(models.Model):
help_text="Email", help_text="Email",
db_index=True, db_index=True,
) )
phone = models.TextField( phone = PhoneNumberField(
null=True, null=True,
blank=True, blank=True,
help_text="Phone", help_text="Phone",

View file

@ -2,6 +2,7 @@
{% extends 'application_form.html' %} {% extends 'application_form.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load field_helpers %}
{% block form_content %} {% block form_content %}
@ -28,23 +29,18 @@
<legend class="usa-sr-only"> <legend class="usa-sr-only">
Who is the authorizing official for your organization Who is the authorizing official for your organization
</legend> </legend>
{{ 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" }} {% input_with_errors wizard.form.first_name %}
{{ wizard.form.middle_name|add_class:"usa-input"}}
{{ wizard.form.last_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.middle_name %}
{{ wizard.form.last_name|add_class:"usa-input"}}
{{ wizard.form.title|add_label_class:"usa-label" }} {% input_with_errors wizard.form.last_name %}
{{ wizard.form.title|add_class:"usa-input"}}
{{ wizard.form.email|add_label_class:"usa-label" }} {% input_with_errors wizard.form.title %}
{{ wizard.form.email|add_class:"usa-input"}}
{{ wizard.form.phone|add_label_class:"usa-label" }} {% input_with_errors wizard.form.email %}
{{ wizard.form.phone|add_class:"usa-input usa-input--medium" }}
{% input_with_errors wizard.form.phone add_class="usa-input--medium" %}
</fieldset> </fieldset>

View file

@ -1,6 +1,6 @@
<!-- Test page --> <!-- Test page -->
{% extends 'application_form.html' %} {% extends 'application_form.html' %}
{% load widget_tweaks %} {% load widget_tweaks field_helpers %}
{% load static %} {% load static %}
{% block form_content %} {% block form_content %}
@ -9,8 +9,7 @@
{{ wizard.management_form }} {{ wizard.management_form }}
{% csrf_token %} {% csrf_token %}
{{ wizard.form.current_site|add_label_class:"usa-label" }} {% input_with_errors wizard.form.current_site %}
{{ wizard.form.current_site|add_class:"usa-input" }}
{{ block.super }} {{ block.super }}

View file

@ -27,23 +27,56 @@
{{ wizard.management_form }} {{ wizard.management_form }}
{% csrf_token %} {% csrf_token %}
{{ wizard.form.requested_domain|add_label_class:"usa-label" }}
<div class="display-flex flex-align-center"> {% if wizard.form.requested_domain.errors %}
<span class="padding-top-05 padding-right-2px">www.</span> <div class="usa-form-group usa-form-group--error">
{{ wizard.form.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} {{ wizard.form.requested_domain|add_label_class:"usa-label usa-label--error" }}
<span class="padding-top-05 padding-left-2px">.gov </span> {% for error in wizard.form.requested_domain.errors %}
</div> <span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{{ wizard.form.requested_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
</div>
{% else %}
{{ wizard.form.requested_domain|add_label_class:"usa-label" }}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{{ wizard.form.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
{% endif %}
<button type="button" class="usa-button">Check availability </button> <button type="button" class="usa-button">Check availability </button>
<h2>Alternative domains</h2> <h2>Alternative domains</h2>
<div> <div>
{% if wizard.form.alternative_domain.errors %}
<div class="usa-form-group usa-form-group--error">
{{ wizard.form.alternative_domain|add_label_class:"usa-label usa-label--error" }}
{% for error in wizard.for.alternative_domain.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{{ wizard.form.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
</div>
{% else %}
{{ wizard.form.alternative_domain|add_label_class:"usa-label" }} {{ wizard.form.alternative_domain|add_label_class:"usa-label" }}
<div class="display-flex flex-align-center"> <div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span> <span class="padding-top-05 padding-right-2px">www.</span>
{{ wizard.form.alternative_domain|add_class:"usa-input" }} {{ wizard.form.alternative_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span> <span class="padding-top-05 padding-left-2px">.gov </span>
</div> </div>
{% endif %}
<button type="button" class="usa-button usa-button--unstyled"> <button type="button" class="usa-button usa-button--unstyled">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img"> <svg class="usa-icon" aria-hidden="true" focusable="false" role="img">

View file

@ -17,8 +17,30 @@
</svg><span class="margin-left-05">Previous step </span> </svg><span class="margin-left-05">Previous step </span>
</a> </a>
{% endif %} {% endif %}
{% if form.errors %}
{% for error in form.non_field_errors %}
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2">
<div class="usa-alert__body">
{{ error|escape }}
</div>
</div>
{% endfor %}
{% for field in form %}
{% for error in field.errors %}
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2">
<div class="usa-alert__body">
{{ error|escape }}
</div>
</div>
{% endfor %}
{% endfor %}
{% endif %}
<h1> {{form_titles|get_item:wizard.steps.current}} </h1> <h1> {{form_titles|get_item:wizard.steps.current}} </h1>
{% block form_content %} {% block form_content %}
<div class="stepnav"> <div class="stepnav">
{% if wizard.steps.next %} {% if wizard.steps.next %}
<button <button

View file

@ -1,6 +1,6 @@
<!-- Test page --> <!-- Test page -->
{% extends 'application_form.html' %} {% extends 'application_form.html' %}
{% load widget_tweaks %} {% load widget_tweaks field_helpers %}
{% block form_content %} {% block form_content %}
@ -28,16 +28,16 @@
{{ wizard.form.federal_agency|add_class:"usa-select" }} {{ wizard.form.federal_agency|add_class:"usa-select" }}
{% endif %} {% endif %}
{{ wizard.form.organization_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.organization_name %}
{{ wizard.form.organization_name|add_class:"usa-input" }}
{{ wizard.form.address_line1|add_label_class:"usa-label" }} {% input_with_errors wizard.form.address_line1 %}
{{ wizard.form.address_line1|add_class:"usa-input" }}
{{ wizard.form.address_line2|add_label_class:"usa-label" }} {% input_with_errors wizard.form.address_line2 %}
{{ wizard.form.address_line2|add_class:"usa-input" }}
{{ wizard.form.state_territory|add_label_class:"usa-label" }} {{ wizard.form.state_territory|add_label_class:"usa-label" }}
{{ wizard.form.state_territory|add_class:"usa-select" }} {{ wizard.form.state_territory|add_class:"usa-select" }}
{{ wizard.form.zipcode|add_label_class:"usa-label" }}
{{ wizard.form.zipcode|add_class:"usa-input usa-input--small" }} {% input_with_errors wizard.form.zipcode add_class="usa-input--small" %}
</fieldset> </fieldset>

View file

@ -2,6 +2,7 @@
{% extends 'application_form.html' %} {% extends 'application_form.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load field_helpers %}
{% block form_content %} {% block form_content %}
@ -16,23 +17,17 @@
<legend> <legend>
<h2 class="margin-bottom-05"> Contact 2 </h2> <h2 class="margin-bottom-05"> Contact 2 </h2>
</legend> </legend>
{{ wizard.form.first_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.first_name %}
{{ wizard.form.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.middle_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.middle_name %}
{{ wizard.form.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.last_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.last_name %}
{{ wizard.form.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.title|add_label_class:"usa-label" }} {% input_with_errors wizard.form.title %}
{{ wizard.form.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.email|add_label_class:"usa-label" }} {% input_with_errors wizard.form.email %}
{{ wizard.form.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.phone|add_label_class:"usa-label" }} {% input_with_errors wizard.form.phone add_class="usa-input--medium" %}
{{ wizard.form.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }}
</fieldset> </fieldset>
<div> <div>

View file

@ -59,10 +59,24 @@
{{ wizard.management_form }} {{ wizard.management_form }}
{% csrf_token %} {% csrf_token %}
<div class="usa-checkbox"> {% if wizard.form.is_policy_acknowledged.errors %}
{{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"}} <div class="usa-form-group usa-form-group--error">
{{ wizard.form.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }} {% for error in wizard.form.is_policy_acknowledged.errors %}
</div> <span class="usa-error-message margin-bottom-1" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
<div class="usa-checkbox">
{{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"|add_class:"usa-input--error" }}
{{ wizard.form.is_policy_acknowledged|add_label_class:"usa-checkbox__label usa-label--error" }}
</div>
</div>
{% else %}
<div class="usa-checkbox">
{{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"}}
{{ wizard.form.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }}
</div>
{% endif %}
</div> </div>
{{ block.super }} {{ block.super }}

View file

@ -11,10 +11,21 @@
{{ wizard.management_form }} {{ wizard.management_form }}
{% csrf_token %} {% csrf_token %}
{{ wizard.form.security_email|add_label_class:"usa-label" }} {% if wizard.form.security_email.errors %}
{{ wizard.form.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }} <div class="usa-form-group usa-form-group--error">
{{ wizard.form.security_email|add_label_class:"usa-label usa-label--error" }}
{{ block.super }} {% for error in wizard.form.security_email.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
{{ wizard.form.security_email|add_class:"usa-input"|add_class:"usa-input--error"|attr:"aria-describedby:instructions" }}
</div>
{% else %}
{{ wizard.form.security_email|add_label_class:"usa-label" }}
{{ wizard.form.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% endif %}
{{ block.super }}
</form> </form>

View file

@ -2,6 +2,7 @@
{% extends 'application_form.html' %} {% extends 'application_form.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load field_helpers %}
{% block form_content %} {% block form_content %}
@ -23,23 +24,17 @@
<legend class="usa-sr-only"> <legend class="usa-sr-only">
Your contact information Your contact information
</legend> </legend>
{{ wizard.form.first_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.first_name %}
{{ wizard.form.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.middle_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.middle_name %}
{{ wizard.form.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.last_name|add_label_class:"usa-label" }} {% input_with_errors wizard.form.last_name %}
{{ wizard.form.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.title|add_label_class:"usa-label" }} {% input_with_errors wizard.form.title %}
{{ wizard.form.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.email|add_label_class:"usa-label" }} {% input_with_errors wizard.form.email %}
{{ wizard.form.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ wizard.form.phone|add_label_class:"usa-label" }} {% input_with_errors wizard.form.phone add_class="usa-input--medium" %}
{{ wizard.form.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }}
</fieldset> </fieldset>

View file

@ -0,0 +1,21 @@
{% comment %}
Template include for form fields with classes and their corresponding
error messages, if necessary.
{% endcomment %}
{% load widget_tweaks %}
{% if field.errors %}
<div class="usa-form-group usa-form-group--error">
{{ field|add_label_class:"usa-label usa-label--error" }}
{% for error in field.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
{{ field|add_class:input_class|add_class:"usa-input--error"}}
</div>
{% else %}
{{ field|add_label_class:"usa-label" }}
{{ field|add_class:input_class }}
{% endif %}

View file

@ -0,0 +1,18 @@
"""Custom field helpers for our inputs."""
from django import template
register = template.Library()
@register.inclusion_tag('includes/input_with_errors.html')
def input_with_errors(field, add_class=None):
"""Make an input field along with error handling.
field is a form field instance. add_class is a string of additional
classes (space separated) to add to "usa-input" on the <input> field.
"""
input_class = "usa-input"
if add_class:
input_class += " " + add_class
return {"field": field, "input_class": input_class}

View file

@ -0,0 +1,118 @@
"""Test form validation requirements."""
from django.test import TestCase
from registrar.forms.application_wizard import (
CurrentSitesForm,
DotGovDomainForm,
AuthorizingOfficialForm,
YourContactForm,
OtherContactsForm,
SecurityEmailForm,
RequirementsForm,
)
class TestFormValidation(TestCase):
def test_current_site_invalid(self):
form = CurrentSitesForm(data={"current_site": "nah"})
self.assertEqual(
form.errors["current_site"], ["Please enter a valid domain name"]
)
def test_current_site_valid(self):
form = CurrentSitesForm(data={"current_site": "hyphens-rule.gov.uk"})
self.assertEqual(len(form.errors), 0)
def test_requested_domain_valid(self):
"""Just a valid domain name with no .gov at the end."""
form = DotGovDomainForm(data={"requested_domain": "top-level-agency"})
self.assertEqual(len(form.errors), 0)
def test_requested_domain_ending_dotgov(self):
"""Just a valid domain name with .gov at the end."""
form = DotGovDomainForm(data={"requested_domain": "top-level-agency.gov"})
self.assertEqual(len(form.errors), 0)
self.assertEqual(form.cleaned_data["requested_domain"], "top-level-agency")
def test_requested_domain_ending_dotcom_invalid(self):
"""don't accept domains ending other than .gov."""
form = DotGovDomainForm(data={"requested_domain": "top-level-agency.com"})
self.assertEqual(
form.errors["requested_domain"],
["Please enter a top-level domain name without any periods."],
)
def test_requested_domain_invalid_characters(self):
"""must be a valid .gov domain name."""
form = DotGovDomainForm(data={"requested_domain": "underscores_forever"})
self.assertEqual(
form.errors["requested_domain"],
[
"Please enter a valid domain name using only letters, numbers, and hyphens"
],
)
def test_authorizing_official_email_invalid(self):
"""must be a valid email address."""
form = AuthorizingOfficialForm(data={"email": "boss@boss"})
self.assertEqual(form.errors["email"], ["Please enter a valid email address."])
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"})
self.assertEqual(form.errors["email"], ["Please enter a valid email address."])
def test_your_contact_phone_invalid(self):
"""Must be a valid phone number."""
form = YourContactForm(data={"phone": "boss@boss"})
self.assertTrue(
form.errors["phone"][0].startswith("Enter a valid phone number")
)
def test_other_contact_email_invalid(self):
"""must be a valid email address."""
form = OtherContactsForm(data={"email": "boss@boss"})
self.assertEqual(form.errors["email"], ["Please enter a valid email address."])
def test_other_contact_phone_invalid(self):
"""Must be a valid phone number."""
form = OtherContactsForm(data={"phone": "boss@boss"})
self.assertTrue(
form.errors["phone"][0].startswith("Enter a valid phone number")
)
def test_security_email_form_blank(self):
"""Can leave the security_email field blank."""
form = SecurityEmailForm(data={})
self.assertEqual(len(form.errors), 0)
def test_security_email_form_invalid(self):
"""Can leave the security_email field blank."""
form = SecurityEmailForm(data={"security_email": "boss@boss"})
self.assertEqual(
form.errors["security_email"], ["Please enter a valid email address."]
)
def test_requirements_form_blank(self):
"""Requirements box unchecked is an error."""
form = RequirementsForm(data={})
self.assertEqual(
form.errors["is_policy_acknowledged"], ["You must read and agree to the .gov domain requirements to proceed."]
)
def test_requirements_form_unchecked(self):
"""Requirements box unchecked is an error."""
form = RequirementsForm(data={"is_policy_acknowledged": False})
self.assertEqual(
form.errors["is_policy_acknowledged"], ["You must read and agree to the .gov domain requirements to proceed."]
)