Merge branch 'main' into nmb/field-validation

This commit is contained in:
Neil Martinsen-Burrell 2023-01-03 11:55:34 -06:00
commit 6bda7b7ead
No known key found for this signature in database
GPG key ID: 6A3C818CC10D0184
23 changed files with 549 additions and 223 deletions

View file

@ -6,7 +6,7 @@ name: Reset database
# cf run-task getgov-staging --wait \ # cf run-task getgov-staging --wait \
# --command 'python manage.py flush' --name flush # --command 'python manage.py flush' --name flush
# cf run-task getgov-staging --wait \ # cf run-task getgov-staging --wait \
# --command 'python manage.py loaddata registrar/fixtures/*' --name loaddata # --command 'python manage.py load' --name loaddata
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
@ -24,8 +24,8 @@ jobs:
- name: Delete existing data for staging - name: Delete existing data for staging
uses: 18f/cg-deploy-action@main uses: 18f/cg-deploy-action@main
with: with:
cf_username: ${{ secrets.CF_USERNAME }} cf_username: ${{ secrets.CF_STAGING_USERNAME }}
cf_password: ${{ secrets.CF_PASSWORD }} cf_password: ${{ secrets.CF_STAGING_PASSWORD }}
cf_org: cisa-getgov-prototyping cf_org: cisa-getgov-prototyping
cf_space: staging cf_space: staging
full_command: "cf run-task getgov-staging --wait --command 'python manage.py flush' --name flush" full_command: "cf run-task getgov-staging --wait --command 'python manage.py flush' --name flush"
@ -37,4 +37,4 @@ jobs:
cf_password: ${{ secrets.CF_STAGING_PASSWORD }} cf_password: ${{ secrets.CF_STAGING_PASSWORD }}
cf_org: cisa-getgov-prototyping cf_org: cisa-getgov-prototyping
cf_space: staging cf_space: staging
full_command: "cf run-task getgov-staging --wait --command 'python manage.py loaddata registrar/fixtures/*' --name loaddata" full_command: "cf run-task getgov-staging --wait --command 'python manage.py load' --name loaddata"

View file

@ -38,6 +38,12 @@ If you run via `docker-compose up -d`, you can get logs with `docker-compose log
You can change the logging verbosity, if needed. Do a web search for "django log level". You can change the logging verbosity, if needed. Do a web search for "django log level".
## Mock data
There is a `post_migrate` signal in [signals.py](../../src/registrar/signals.py) that will load the fixtures from [fixtures.py](../../src/registrar/fixtures.py), giving you some test data to play with while developing.
See the [database-access README](./database-access.md) for information on how to pull data to update these fixtures.
## Running tests ## Running tests
Crash course on Docker's `run` vs `exec`: in order to run the tests inside of a container, a container must be running. If you already have a container running, you can use `exec`. If you do not, you can use `run`, which will attempt to start one. Crash course on Docker's `run` vs `exec`: in order to run the tests inside of a container, a container must be running. If you already have a container running, you can use `exec`. If you do not, you can use `run`, which will attempt to start one.

View file

@ -24,7 +24,7 @@ cf run-task getgov-unstable --command 'python manage.py migrate' --name migrate
Optionally, load data from fixtures as well Optionally, load data from fixtures as well
```shell ```shell
cf run-task getgov-unstable --wait --command 'python manage.py loaddata registrar/fixtures/*' --name loaddata cf run-task getgov-unstable --wait --command 'python manage.py load' --name loaddata
``` ```
For the `staging` environment, developers don't have credentials so we need to For the `staging` environment, developers don't have credentials so we need to

View file

@ -11,6 +11,7 @@ django-allow-cidr = "*"
django-auditlog = "*" django-auditlog = "*"
django-csp = "*" django-csp = "*"
environs = {extras=["django"]} environs = {extras=["django"]}
Faker = "*"
gunicorn = "*" gunicorn = "*"
oic = "*" oic = "*"
pyjwkest = "*" pyjwkest = "*"

137
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "9e2fe58b6282514da5d054147426e561f451da29f3c743c0cd9dccf4d7dba0cc" "sha256": "c9762342448f1a70dbe93cc496e5fb868c67a73f3e51c4440e726f492f7dc5ee"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -16,11 +16,11 @@
"default": { "default": {
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4", "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac",
"sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424" "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==3.5.2" "version": "==3.6.0"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -125,35 +125,32 @@
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd", "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b",
"sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db", "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f",
"sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290", "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190",
"sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744", "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f",
"sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb", "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f",
"sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d", "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb",
"sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70", "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c",
"sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b", "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773",
"sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876", "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72",
"sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083", "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8",
"sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6", "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717",
"sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1", "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9",
"sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00", "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856",
"sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b", "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96",
"sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b", "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288",
"sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285", "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39",
"sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9", "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e",
"sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0", "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce",
"sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d", "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1",
"sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2", "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de",
"sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8", "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df",
"sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee", "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf",
"sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b", "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458"
"sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7",
"sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353",
"sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==38.0.4" "version": "==39.0.0"
}, },
"defusedxml": { "defusedxml": {
"hashes": [ "hashes": [
@ -179,11 +176,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148", "sha256:4b214a05fe4c99476e99e2445c8b978c8369c18d4dea8e22ec412862715ad763",
"sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b" "sha256:ff56ebd7ead0fd5dbe06fe157b0024a7aaea2e0593bb3785fb594cf94dad58ef"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.1.4" "version": "==4.1.5"
}, },
"django-allow-cidr": { "django-allow-cidr": {
"hashes": [ "hashes": [
@ -262,6 +259,14 @@
"index": "pypi", "index": "pypi",
"version": "==9.5.0" "version": "==9.5.0"
}, },
"faker": {
"hashes": [
"sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc",
"sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76"
],
"index": "pypi",
"version": "==15.3.4"
},
"furl": { "furl": {
"hashes": [ "hashes": [
"sha256:5a6188fe2666c484a12159c18be97a1977a71d632ef5bb867ef15f54af39cc4e", "sha256:5a6188fe2666c484a12159c18be97a1977a71d632ef5bb867ef15f54af39cc4e",
@ -379,10 +384,10 @@
}, },
"phonenumberslite": { "phonenumberslite": {
"hashes": [ "hashes": [
"sha256:87218f3f85f67779007b6cfeeca59b1d370b96f9b3867347f0e6d094b1a3df64", "sha256:2b7452fe69c907b7638ff4cf7f8a773a6dfce26bf32d67ebf4dc74d5e31abb79",
"sha256:8daa2fff393e291531aaa5ed4ddc123749b631d48ae7e504e8d7a4e818b3f799" "sha256:b7f56b77711e6b99c7890f20f1aaa705d9fe2514215446b8426c8515c198775f"
], ],
"version": "==8.13.2" "version": "==8.13.3"
}, },
"psycopg2-binary": { "psycopg2-binary": {
"hashes": [ "hashes": [
@ -573,21 +578,21 @@
}, },
"whitenoise": { "whitenoise": {
"hashes": [ "hashes": [
"sha256:8e9c600a5c18bd17655ef668ad55b5edf6c24ce9bdca5bf607649ca4b1e8e2c2", "sha256:cf8ecf56d86ba1c734fdb5ef6127312e39e92ad5947fef9033dc9e43ba2777d9",
"sha256:8fa943c6d4cd9e27673b70c21a07b0aa120873901e099cd46cab40f7cc96d567" "sha256:fe0af31504ab08faa1ec7fc02845432096e40cc1b27e6a7747263d7b30fb51fa"
], ],
"index": "pypi", "index": "pypi",
"version": "==6.2.0" "version": "==6.3.0"
} }
}, },
"develop": { "develop": {
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4", "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac",
"sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424" "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==3.5.2" "version": "==3.6.0"
}, },
"bandit": { "bandit": {
"hashes": [ "hashes": [
@ -602,7 +607,7 @@
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==4.11.1" "version": "==4.11.1"
}, },
"black": { "black": {
@ -641,11 +646,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148", "sha256:4b214a05fe4c99476e99e2445c8b978c8369c18d4dea8e22ec412862715ad763",
"sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b" "sha256:ff56ebd7ead0fd5dbe06fe157b0024a7aaea2e0593bb3785fb594cf94dad58ef"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.1.4" "version": "==4.1.5"
}, },
"django-debug-toolbar": { "django-debug-toolbar": {
"hashes": [ "hashes": [
@ -697,18 +702,18 @@
}, },
"gitpython": { "gitpython": {
"hashes": [ "hashes": [
"sha256:41eea0deec2deea139b459ac03656f0dd28fc4a3387240ec1d3c259a2c47850f", "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8",
"sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd" "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==3.1.29" "version": "==3.1.30"
}, },
"mccabe": { "mccabe": {
"hashes": [ "hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==0.7.0" "version": "==0.7.0"
}, },
"mypy": { "mypy": {
@ -780,18 +785,18 @@
}, },
"platformdirs": { "platformdirs": {
"hashes": [ "hashes": [
"sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca", "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490",
"sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e" "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2.6.0" "version": "==2.6.2"
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
"sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053", "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
"sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610" "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==2.10.0" "version": "==2.10.0"
}, },
"pyflakes": { "pyflakes": {
@ -799,7 +804,7 @@
"sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf", "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf",
"sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd" "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==3.0.1" "version": "==3.0.1"
}, },
"pyyaml": { "pyyaml": {
@ -845,7 +850,7 @@
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==6.0" "version": "==6.0"
}, },
"six": { "six": {
@ -861,7 +866,7 @@
"sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
"sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==5.0.0" "version": "==5.0.0"
}, },
"soupsieve": { "soupsieve": {
@ -869,7 +874,7 @@
"sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
"sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==2.3.2.post1" "version": "==2.3.2.post1"
}, },
"sqlparse": { "sqlparse": {
@ -906,10 +911,10 @@
}, },
"types-pytz": { "types-pytz": {
"hashes": [ "hashes": [
"sha256:bea605ce5d5a5d52a8e1afd7656c9b42476e18a0f888de6be91587355313ddf4", "sha256:1509f182f686ab76e9a8234f22b00b8f50d239974db0cf924b7ae8674bb31a6f",
"sha256:d078196374d1277e9f9984d49373ea043cf2c64d5d5c491fbc86c258557bd46f" "sha256:4f20c2953b3a3a0587e94489ec4c9e02c3d3aedb9ba5cd7e796e12f4cfa7027e"
], ],
"version": "==2022.6.0.1" "version": "==2022.7.0.0"
}, },
"types-pyyaml": { "types-pyyaml": {
"hashes": [ "hashes": [
@ -920,11 +925,11 @@
}, },
"types-requests": { "types-requests": {
"hashes": [ "hashes": [
"sha256:091d4a5a33c1b4f20d8b1b952aa8fa27a6e767c44c3cf65e56580df0b05fd8a9", "sha256:0ae38633734990d019b80f5463dfa164ebd3581998ac8435f526da6fe4d598c3",
"sha256:a7df37cc6fb6187a84097da951f8e21d335448aa2501a6b0a39cbd1d7ca9ee2a" "sha256:b6a2fca8109f4fdba33052f11ed86102bddb2338519e1827387137fefc66a98b"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.28.11.5" "version": "==2.28.11.7"
}, },
"types-urllib3": { "types-urllib3": {
"hashes": [ "hashes": [
@ -962,7 +967,7 @@
"sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead", "sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead",
"sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb" "sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"
], ],
"markers": "python_version >= '3.6' and python_version < '4'", "markers": "python_full_version >= '3.6.0' and python_version < '4'",
"version": "==3.0.0" "version": "==3.0.0"
} }
} }

256
src/registrar/fixtures.py Normal file
View file

@ -0,0 +1,256 @@
import logging
import random
import string
from faker import Faker
from registrar.models import (
User,
UserProfile,
DomainApplication,
Domain,
Contact,
Website,
)
fake = Faker()
logger = logging.getLogger(__name__)
class UserFixture:
"""
Load users into the database.
Make sure this class' `load` method is called from `handle`
in management/commands/load.py, then use `./manage.py load`
to run this code.
"""
ADMINS = [
{
"username": "c4a0e101-73b4-4d7d-9e5e-7f19a726a0fa",
"first_name": "Seamus",
"last_name": "Johnston",
},
{
"username": "d4c3bd84-dc3a-48bc-a3c3-f53111df2ec6",
"first_name": "Igor",
"last_name": "",
},
{
"username": "ee80bfe0-49ad-456d-8d82-e2b608a66517",
"first_name": "Logan",
"last_name": "",
},
]
@classmethod
def load(cls):
logger.info("Going to load %s users" % str(len(cls.ADMINS)))
for admin in cls.ADMINS:
try:
user, _ = User.objects.get_or_create(
username=admin["username"],
)
user.is_superuser = True
user.first_name = admin["first_name"]
user.last_name = admin["last_name"]
user.is_staff = True
user.is_active = True
user.save()
logger.debug("User object created for %s" % admin["first_name"])
UserProfile.objects.get_or_create(user=user)
logger.debug("Profile object created for %s" % admin["first_name"])
except Exception as e:
logger.warning(e)
logger.debug("All users loaded.")
class DomainApplicationFixture:
"""
Load domain applications into the database.
Make sure this class' `load` method is called from `handle`
in management/commands/load.py, then use `./manage.py load`
to run this code.
"""
# any fields not specified here will be filled in with fake data or defaults
# NOTE BENE: each fixture must have `organization_name` for uniqueness!
DA = [
{
"status": "started",
"organization_name": "Example - Finished but not Submitted",
},
{
"status": "started",
"organization_name": "Example - Just started",
"organization_type": "federal",
"federal_agency": None,
"federal_type": None,
"address_line1": None,
"address_line2": None,
"city": None,
"state_territory": None,
"zipcode": None,
"urbanization": None,
"purpose": None,
"security_email": None,
"anything_else": None,
"is_policy_acknowledged": None,
"authorizing_official": None,
"submitter": None,
"other_contacts": [],
"current_websites": [],
"alternative_domains": [],
},
{
"status": "submitted",
"organization_name": "Example - Submitted but pending Investigation",
},
{
"status": "investigating",
"organization_name": "Example - In Investigation",
},
]
@classmethod
def fake_contact(cls):
return {
"first_name": fake.first_name(),
"middle_name": None,
"last_name": fake.last_name(),
"title": fake.job(),
"email": fake.ascii_safe_email(),
"phone": fake.phone_number(),
}
@classmethod
def fake_dot_gov(cls):
return "".join(random.choices(string.ascii_lowercase, k=16)) + ".gov" # nosec
@classmethod
def _set_non_foreign_key_fields(cls, da: DomainApplication, app: dict):
"""Helper method used by `load`."""
da.status = app["status"] if "status" in app else "started"
da.organization_type = (
app["organization_type"] if "organization_type" in app else "federal"
)
da.federal_agency = app["federal_agency"] if "federal_agency" in app else ""
da.federal_type = (
app["federal_type"]
if "federal_type" in app
else random.choice(["executive", "judicial", "legislative"]) # nosec
)
da.address_line1 = (
app["address_line1"] if "address_line1" in app else fake.street_address()
)
da.address_line2 = app["address_line2"] if "address_line2" in app else None
da.city = app["city"] if "city" in app else fake.city()
da.state_territory = (
app["state_territory"] if "state_territory" in app else fake.state_abbr()
)
da.zipcode = app["zipcode"] if "zipcode" in app else fake.postalcode()
da.urbanization = app["urbanization"] if "urbanization" in app else None
da.purpose = app["purpose"] if "purpose" in app else fake.paragraph()
da.security_email = app["security_email"] if "security_email" in app else None
da.anything_else = app["anything_else"] if "anything_else" in app else None
da.is_policy_acknowledged = (
app["is_policy_acknowledged"] if "is_policy_acknowledged" in app else True
)
@classmethod
def _set_foreign_key_fields(cls, da: DomainApplication, app: dict, user: User):
"""Helper method used by `load`."""
if not da.investigator:
da.investigator = (
User.objects.get(username=user.username)
if "investigator" in app
else None
)
if not da.authorizing_official:
if (
"authorizing_official" in app
and app["authorizing_official"] is not None
):
da.authorizing_official, _ = Contact.objects.get_or_create(
**app["authorizing_official"]
)
else:
da.authorizing_official = Contact.objects.create(**cls.fake_contact())
if not da.submitter:
if "submitter" in app and app["submitter"] is not None:
da.submitter, _ = Contact.objects.get_or_create(**app["submitter"])
else:
da.submitter = Contact.objects.create(**cls.fake_contact())
if not da.requested_domain:
if "requested_domain" in app and app["requested_domain"] is not None:
da.requested_domain, _ = Domain.objects.get_or_create(
name=app["requested_domain"]
)
else:
da.requested_domain = Domain.objects.create(name=cls.fake_dot_gov())
@classmethod
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
"""Helper method used by `load`."""
if "other_contacts" in app:
for contact in app["other_contacts"]:
da.other_contacts.add(Contact.objects.get_or_create(**contact)[0])
else:
other_contacts = [
Contact.objects.create(**cls.fake_contact())
for _ in range(random.randint(0, 3)) # nosec
]
da.other_contacts.add(*other_contacts)
if "current_websites" in app:
for website in app["current_websites"]:
da.current_websites.add(
Website.objects.get_or_create(website=website)[0]
)
else:
current_websites = [
Website.objects.create(website=fake.uri())
for _ in range(random.randint(0, 3)) # nosec
]
da.current_websites.add(*current_websites)
if "alternative_domains" in app:
for domain in app["alternative_domains"]:
da.alternative_domains.add(
Website.objects.get_or_create(website=domain)[0]
)
else:
alternative_domains = [
Website.objects.create(website=cls.fake_dot_gov())
for _ in range(random.randint(0, 3)) # nosec
]
da.alternative_domains.add(*alternative_domains)
@classmethod
def load(cls):
"""Creates domain applications for each user in the database."""
logger.info("Going to load %s domain applications" % len(cls.DA))
try:
users = list(User.objects.all()) # force evaluation to catch db errors
except Exception as e:
logger.warning(e)
return
for user in users:
logger.debug("Loading domain applications for %s" % user)
for app in cls.DA:
try:
da, _ = DomainApplication.objects.get_or_create(
creator=user,
organization_name=app["organization_name"],
)
cls._set_non_foreign_key_fields(da, app)
cls._set_foreign_key_fields(da, app, user)
da.save()
cls._set_many_to_many_relations(da, app)
except Exception as e:
logger.warning(e)

View file

@ -1,107 +0,0 @@
[
{
"model": "registrar.user",
"pk": 1,
"fields": {
"password": "",
"last_login": "2022-10-04T15:07:34.590Z",
"is_superuser": true,
"username": "c4a0e101-73b4-4d7d-9e5e-7f19a726a0fa",
"first_name": "Seamus",
"last_name": "Johnston",
"email": "",
"is_staff": true,
"is_active": true,
"date_joined": "2022-09-30T14:14:02.280Z",
"groups": [],
"user_permissions": []
}
},
{
"model": "registrar.userprofile",
"pk": 1,
"fields": {
"created_at": "2022-09-30T14:14:02.284Z",
"updated_at": "2022-10-04T15:07:34.593Z",
"street1": "",
"street2": "",
"street3": "",
"city": "",
"sp": "",
"pc": "",
"cc": "",
"user": 1,
"display_name": ""
}
},
{
"model": "registrar.user",
"pk": 2,
"fields": {
"password": "",
"last_login": "2022-10-04T16:43:07.738Z",
"is_superuser": true,
"username": "d4c3bd84-dc3a-48bc-a3c3-f53111df2ec6",
"first_name": "Igor",
"last_name": "",
"email": "",
"is_staff": true,
"is_active": true,
"date_joined": "2022-10-04T16:39:30.529Z",
"groups": [],
"user_permissions": []
}
},
{
"model": "registrar.userprofile",
"pk": 2,
"fields": {
"created_at": "2022-10-04T16:39:30.531Z",
"updated_at": "2022-10-04T16:43:07.740Z",
"street1": "",
"street2": "",
"street3": "",
"city": "",
"sp": "",
"pc": "",
"cc": "",
"user": 2,
"display_name": ""
}
},
{
"model": "registrar.user",
"pk": 3,
"fields": {
"password": "",
"last_login": "2022-10-13T13:57:52.133Z",
"is_superuser": true,
"username": "ee80bfe0-49ad-456d-8d82-e2b608a66517",
"first_name": "Logan",
"last_name": "",
"email": "",
"is_staff": true,
"is_active": true,
"date_joined": "2022-10-13T13:57:52.124Z",
"groups": [],
"user_permissions": []
}
},
{
"model": "registrar.userprofile",
"pk": 3,
"fields": {
"created_at": "2022-10-04T16:39:30.531Z",
"updated_at": "2022-10-04T16:43:07.740Z",
"street1": "",
"street2": "",
"street3": "",
"city": "",
"sp": "",
"pc": "",
"cc": "",
"user": 3,
"display_name": ""
}
}
]

View file

@ -12,6 +12,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import resolve from django.urls import resolve
from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore
from formtools.wizard.storage.session import SessionStorage # type: ignore
from phonenumber_field.formfields import PhoneNumberField # type: ignore from phonenumber_field.formfields import PhoneNumberField # type: ignore
from registrar.models import Contact, DomainApplication, Domain from registrar.models import Contact, DomainApplication, Domain
@ -232,7 +234,7 @@ class DotGovDomainForm(RegistrarForm):
requested = requested[:-4] requested = requested[:-4]
if "." in requested: if "." in requested:
raise forms.ValidationError( raise forms.ValidationError(
"Please enter a top-level domain name without any periods.", "Please enter a domain without any periods.",
code="invalid", code="invalid",
) )
if not Domain.string_could_be_domain(requested + ".gov"): if not Domain.string_could_be_domain(requested + ".gov"):
@ -334,7 +336,10 @@ 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 requirements for registering "
"and operating .gov domains."
),
required=False, # use field validation to enforce this required=False, # use field validation to enforce this
) )
@ -447,6 +452,19 @@ WIZARD_CONDITIONS = {
} }
class TrackingStorage(SessionStorage):
"""Storage subclass that keeps track of what the current_step has been."""
def _set_current_step(self, step):
super()._set_current_step(step)
step_history = self.extra_data.setdefault("step_history", [])
# can't serialize a set, so keep list entries unique
if step not in step_history:
step_history.append(step)
class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView): class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
"""Multi-page form ("wizard") for new domain applications. """Multi-page form ("wizard") for new domain applications.
@ -462,6 +480,7 @@ class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
""" """
form_list = FORMS form_list = FORMS
storage_name = "registrar.forms.application_wizard.TrackingStorage"
def get_template_names(self): def get_template_names(self):
"""Template for the current step. """Template for the current step.
@ -483,6 +502,14 @@ class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
"""Add title information to the context for all steps.""" """Add title information to the context for all steps."""
context = super().get_context_data(form=form, **kwargs) context = super().get_context_data(form=form, **kwargs)
context["form_titles"] = TITLES context["form_titles"] = TITLES
# Add information about which steps should be unlocked
# TODO: sometimes the first step doesn't get added to the step history
# so add it here
context["visited"] = self.storage.extra_data.get("step_history", []) + [
self.steps.first
]
if self.steps.current == Step.ORGANIZATION_CONTACT: if self.steps.current == Step.ORGANIZATION_CONTACT:
context["is_federal"] = self._is_federal() context["is_federal"] = self._is_federal()
if self.steps.current == Step.REVIEW: if self.steps.current == Step.REVIEW:

View file

@ -0,0 +1,18 @@
import logging
from django.core.management.base import BaseCommand
from auditlog.context import disable_auditlog # type: ignore
from registrar.fixtures import UserFixture, DomainApplicationFixture
logger = logging.getLogger(__name__)
class Command(BaseCommand):
def handle(self, *args, **options):
# django-auditlog has some bugs with fixtures
# https://github.com/jazzband/django-auditlog/issues/17
with disable_auditlog():
UserFixture.load()
DomainApplicationFixture.load()
logger.info("All fixtures loaded.")

View file

@ -1,10 +0,0 @@
from django.core.management.commands import loaddata
from auditlog.context import disable_auditlog # type: ignore
class Command(loaddata.Command):
def handle(self, *args, **options):
# django-auditlog has some bugs with fixtures
# https://github.com/jazzband/django-auditlog/issues/17
with disable_auditlog():
super(Command, self).handle(*args, **options)

View file

@ -93,21 +93,28 @@ class DomainApplication(TimeStampedModel):
ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)" ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)"
class OrganizationChoices(models.TextChoices): class OrganizationChoices(models.TextChoices):
FEDERAL = "federal", "Federal: a federal agency" FEDERAL = (
"federal",
"Federal: an agency of the U.S. government's executive, legislative, "
"or judicial branches",
)
INTERSTATE = "interstate", "Interstate: an organization of two or more states" INTERSTATE = "interstate", "Interstate: an organization of two or more states"
STATE_OR_TERRITORY = "state_or_territory", ( STATE_OR_TERRITORY = "state_or_territory", (
"State or Territory: One of the 50 U.S. states, the District of " "State or territory: one of the 50 U.S. states, the District of "
"Columbia, American Samoa, Guam, Northern Mariana Islands, " "Columbia, American Samoa, Guam, Northern Mariana Islands, "
"Puerto Rico, or the U.S. Virgin Islands" "Puerto Rico, or the U.S. Virgin Islands"
) )
TRIBAL = "tribal", ( TRIBAL = "tribal", (
"Tribal: a tribal government recognized by the federal or " "Tribal: a tribal government recognized by the federal or "
"state government" "a state government"
) )
COUNTY = "county", "County: a county, parish, or borough" COUNTY = "county", "County: a county, parish, or borough"
CITY = "city", "City: a city, town, township, village, etc." CITY = "city", "City: a city, town, township, village, etc."
SPECIAL_DISTRICT = "special_district", ( SPECIAL_DISTRICT = "special_district", (
"Special District: an independent organization within a single state" "Special district: an independent organization within a single state"
)
SCHOOL_DISTRICT = "school_district", (
"School district: a school district that is not part of a local government"
) )
class BranchChoices(models.TextChoices): class BranchChoices(models.TextChoices):
@ -288,7 +295,7 @@ class DomainApplication(TimeStampedModel):
federal_agency = models.TextField( federal_agency = models.TextField(
null=True, null=True,
blank=True, blank=True,
help_text="Top level federal agency", help_text="Federal agency",
) )
federal_type = models.CharField( federal_type = models.CharField(
@ -296,7 +303,7 @@ class DomainApplication(TimeStampedModel):
choices=BranchChoices.choices, choices=BranchChoices.choices,
null=True, null=True,
blank=True, blank=True,
help_text="Branch of federal government", help_text="Federal government branch",
) )
is_election_board = models.BooleanField( is_election_board = models.BooleanField(
@ -314,13 +321,13 @@ class DomainApplication(TimeStampedModel):
address_line1 = models.TextField( address_line1 = models.TextField(
null=True, null=True,
blank=True, blank=True,
help_text="Address line 1", help_text="Street address",
) )
address_line2 = models.CharField( address_line2 = models.CharField(
max_length=15, max_length=15,
null=True, null=True,
blank=True, blank=True,
help_text="Address line 2", help_text="Street address line 2",
) )
city = models.TextField( city = models.TextField(
null=True, null=True,
@ -331,19 +338,19 @@ class DomainApplication(TimeStampedModel):
max_length=2, max_length=2,
null=True, null=True,
blank=True, blank=True,
help_text="State/Territory", help_text="State, territory, or military post",
) )
zipcode = models.CharField( zipcode = models.CharField(
max_length=10, max_length=10,
null=True, null=True,
blank=True, blank=True,
help_text="ZIP code", help_text="Zip code",
db_index=True, db_index=True,
) )
urbanization = models.TextField( urbanization = models.TextField(
null=True, null=True,
blank=True, blank=True,
help_text="Urbanization", help_text="Urbanization (Puerto Rico only)",
) )
authorizing_official = models.ForeignKey( authorizing_official = models.ForeignKey(
@ -388,7 +395,7 @@ class DomainApplication(TimeStampedModel):
purpose = models.TextField( purpose = models.TextField(
null=True, null=True,
blank=True, blank=True,
help_text="Purpose of the domain", help_text="Purpose of your domain",
) )
other_contacts = models.ManyToManyField( other_contacts = models.ManyToManyField(

View file

@ -1,9 +1,16 @@
from django.db.models.signals import post_save import logging
from django.conf import settings
from django.core.management import call_command
from django.db.models.signals import post_save, post_migrate
from django.dispatch import receiver from django.dispatch import receiver
from .models import User, UserProfile from .models import User, UserProfile
logger = logging.getLogger(__name__)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def handle_profile(sender, instance, **kwargs): def handle_profile(sender, instance, **kwargs):
@ -21,3 +28,13 @@ def handle_profile(sender, instance, **kwargs):
instance.userprofile.save() instance.userprofile.save()
else: else:
UserProfile.objects.create(user=instance) UserProfile.objects.create(user=instance)
@receiver(post_migrate)
def handle_loaddata(**kwargs):
"""Attempt to load test fixtures when in DEBUG mode."""
if settings.DEBUG:
try:
call_command("load")
except Exception as e:
logger.warning(e)

View file

@ -6,7 +6,7 @@
{% block form_content %} {% block form_content %}
<h2>Who is the authorizing official for your organization</h2> <h2>Who is the authorizing official for your organization?</h2>
<div id="instructions"> <div id="instructions">
<p>Your authorizing official is the person within your organization who can authorize your domain request. This is generally the highest ranking or highest elected official in your organization. Read more about <a href="#">who can serve as an authorizing official</a>. <p>Your authorizing official is the person within your organization who can authorize your domain request. This is generally the highest ranking or highest elected official in your organization. Read more about <a href="#">who can serve as an authorizing official</a>.
@ -27,7 +27,7 @@
<fieldset class="usa-fieldset"> <fieldset class="usa-fieldset">
<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>
{% input_with_errors wizard.form.first_name %} {% input_with_errors wizard.form.first_name %}

View file

@ -14,7 +14,7 @@ your authorizing official, and any contacts you added.</p>
<ul> <ul>
<li> We'll review your request. This could take up to two weeks. During <li> We'll review your request. This could take up to two weeks. During
this review we'll verify that you're eligible for a .gov domain, that this review we'll verify that your organization is eligible for a .gov domain, that
your authorizing official approves your request, and that your domain your authorizing official approves your request, and that your domain
meets our naming requirements. meets our naming requirements.
</li> </li>

View file

@ -23,7 +23,7 @@
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post"> <form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
<h2> What .gov domain do you want? </h2> <h2> What .gov domain do you want? </h2>
<p class="domain_instructions"> After you enter your domain, well make sure its available and that it meets some of our naming requirements. If your domain passes these initial checks, well verify that it meets all of our requirements once you complete and submit the rest of the domain request form. </p> <p class="domain_instructions"> After you enter your domain, well make sure its available and that it meets some of our naming requirements. If your domain passes these initial checks, well verify that it meets all of our requirements once you complete and submit the rest of this form. </p>
{{ wizard.management_form }} {{ wizard.management_form }}
{% csrf_token %} {% csrf_token %}

View file

@ -8,10 +8,10 @@
<h2>What is the name and mailing address of your organization?</h2> <h2>What is the name and mailing address of your organization?</h2>
<div id="instructions"> <div id="instructions">
<p>Enter the name of the organization your represent. Your organization might be part <p>Enter the name of the organization you represent. Your organization might be part
of a larger entity. If so, enter information about your part of the larger entity.</p> of a larger entity. If so, enter information about your part of the larger entity.</p>
<p>Once your domain is approved, the name of your organization will be publicly listed as the domain registrant. </p> <p>If your domain request is approved, the name of your organization will be publicly listed as the domain registrant. </p>
<p>All fields are required unless they are marked optional.</p> <p>All fields are required unless they are marked optional.</p>
</div> </div>

View file

@ -13,7 +13,7 @@
<fieldset id="organization_type__fieldset" class="usa-fieldset"> <fieldset id="organization_type__fieldset" class="usa-fieldset">
<legend class="usa-legend"> <legend class="usa-legend">
<h2> What kind of government organization do you represent?</h2> <h2> What kind of U.S.-based government organization do you represent?</h2>
</legend> </legend>
{{ wizard.form.organization_type.errors }} {{ wizard.form.organization_type.errors }}
{% for choice in choices.values %} {% for choice in choices.values %}

View file

@ -6,7 +6,7 @@
{% block form_content %} {% block form_content %}
<p id="instructions">We strongly encourage you to have at least two points of contact for your domain. Many organizations have an administrative point of contact and a technical point of contact. We recommend that you add at least one more contact.</p> <p id="instructions">Wed 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. </p>
<p>All fields are required unless they are marked optional.</p> <p>All fields are required unless they are marked optional.</p>
<form class="usa-form usa-form--large" id="step__{{wizard.steps.current}}" method="post"> <form class="usa-form usa-form--large" id="step__{{wizard.steps.current}}" method="post">

View file

@ -4,7 +4,9 @@
{% block form_content %} {% block form_content %}
<p id="instructions">Describe your organizations mission or 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 <a href="#">activities that are prohibited on .gov domains.</a></p> <p id="instructions">.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.</p>
<p id="instructions">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 <a href="#">activities that are prohibited on .gov domains.</a></p>
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post"> <form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
<div class="usa-form-group"> <div class="usa-form-group">

View file

@ -4,9 +4,7 @@
{% block form_content %} {% block form_content %}
<p>The .gov domain exists to support a broad diversity of government missions and public initiatives. Generally, the .gov registry does not review or audit how government organizations use their domains.</p> <p>The .gov domain exists to support a broad diversity of government missions and public initiatives. Generally, the .gov registry does not review or audit how government organizations use their domains. However, misuse of an individual .gov domain can reflect upon the integrity of the entire .gov space. There are categories of misuse that are statutorily prohibited or abusive in nature.</p>
<p>However, misuse of an individual .gov domain can reflect upon the integrity of the entire .gov space. There are categories of misuse that are statutorily prohibited or abusive in nature.</p>
<h2>Prohibited activities for .gov domains</h2> <h2>Prohibited activities for .gov domains</h2>
@ -20,12 +18,12 @@
<p>A .gov domain must not be used to distribute or promote material whose distribution violates applicable law.</p> <p>A .gov domain must not be used to distribute or promote material whose distribution violates applicable law.</p>
<h3>Malicious cyber activity </h3> <h3>Malicious cyber activity </h3>
<p>.gov is a trusted and safe space. .gov domains must not distribute malware, host open redirects, or otherwise engage in malicious cyber activity.</p> <p>.Gov is a trusted and safe space. .Gov domains must not distribute malware, host open redirects, or otherwise engage in malicious cyber activity.</p>
<h2>Required activities for .gov domain registrants </h2> <h2>Required activities for .gov domain registrants </h2>
<h3>Keep your contact information update</h3> <h3>Keep your contact information updated</h3>
<p>As a .gov domain registrant, maintain current and accurate contact information in the .gov registrar. We strongly recommend that you create and use a security contact.</p> <p>As a .gov domain registrant, maintain current and accurate contact information in the .gov registrar. We strongly recommend that you create and use a security contact.</p>
<h3>Be responsive if we contact you</h3> <h3>Be responsive if we contact you</h3>
@ -39,12 +37,87 @@
<li>Emails to domain contacts </li> <li>Emails to domain contacts </li>
<li>Phone calls to domain contacts</li> <li>Phone calls to domain contacts</li>
<li>Email or phone call to the authorizing official</li> <li>Email or phone call to the authorizing official</li>
<li>Email or phone call to the government organization, a parent organization, or affiliated entities</li> <li>Emails or phone calls to the government organization, a parent organization, or affiliated entities</li>
</ul> </ul>
</p> </p>
<p>We understand the critical importance of the availability of .gov domains. Suspending or terminating a .gov domain is reserved only for prolonged, unresolved serious violations where the registrant is non-responsive. We will make extensive efforts to contact registrants and to identify potential solutions, and will make reasonable accommodations for remediation timelines proportional to the severity of the issue.</p> <p>We understand the critical importance of the availability of .gov domains. Suspending or terminating a .gov domain is reserved only for prolonged, unresolved serious violations where the registrant is non-responsive. We will make extensive efforts to contact registrants and to identify potential solutions, and will make reasonable accommodations for remediation timelines proportional to the severity of the issue.</p>
<h2>Requirements for authorizing officials</h2>
<p>Your authorizing official is the person within your organization who can authorize your domain request. This is generally the highest ranking or highest elected official in your organization.</p>
<h3>Executive branch federal agencies</h3>
<p>Domain requests from executive branch agencies must be authorized by CIOs or agency heads.</p>
<p>Domain requests from executive branch agencies are subject to guidance issued by the U.S. Office of Management and Budget.</p>
<h3>Judicial branch federal agencies</h3>
<p>Domain requests for judicial branch agencies, except the U.S. Supreme Court, must be authorized by the director or CIO of the Administrative Office (AO) of the United States Courts.</p>
<p>Domain requests from the U.S. Supreme Court must be authorized by the director of information technology for the U.S. Supreme Court.</p>
<h3>Legislative branch federal agencies</h3>
<h4>U.S. Senate</h4>
<p>Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.</p>
<h4>U.S. House of Representatives</h4>
<p>Domain requests from the U.S. House of Representatives must come from the House Chief Administrative Officer.</p>
<h4>Other legislative branch agencies</h4>
<p>Domain requests from legislative branch agencies must come from the agencys head or CIO.</p>
<p>Domain requests from legislative commissions must come from the head of the commission, or the head or CIO of the parent agency, if there is one.</p>
<h3>Interstate</h3>
<p>Domain requests from interstate organizations must be authorized by the highest-ranking executive (president, director, chair, or equivalent) or one of the states governors or CIOs.</p>
<h3>U.S. states and territories</h3>
<h4>States and territories: executive branch</h4>
<p>Domain requests from states and territories must be authorized by the governor or the state CIO.</p>
<h4>States and territories: judicial and legislative branches</h4>
<p>Domain requests from state legislatures and courts must be authorized by an agencys CIO or highest-ranking executive.</p>
<h3>Tribal governments</h3>
<p>Domain requests from federally-recognized tribal governments must be authorized by tribal chiefs as noted by the <a href="https://www.bia.gov/service/tribal-leaders-directory">Bureau of Indian Affairs</a>.</p>
<h3>Counties</h3>
<p>Domain requests from counties must be authorized by the chair of the county commission or the equivalent highest elected official.</p>
<h3>Cities</h3>
<p>Domain requests from cities must be authorized by the mayor or the equivalent highest elected official.</p>
<h3>Special districts</h3>
<p>Domain requests from special districts must be authorized by the highest-ranking executive (president, director, chair, or equivalent) or state CIOs for state-based organizations.</p>
<h3>School districts</h3>
<p>Domain requests from school district governments must be authorized by the highest-ranking executive (the chair of a school districts board or a superintendent).</p>
<h2>Requirements for .gov domain names</h2>
<p>.Gov domains must:</p>
<ul class="usa-list">
<li>Be available</li>
<li>Be unique</li>
<li>Relate to your organizations name, location, and/or services</li>
<li>Be clear to the general public. Your domain name must not be easily confused with other organizations.</li>
</ul>
<h2>HSTS preloading</h2> <h2>HSTS preloading</h2>
<p>The .gov program will preload all newly registered .gov domains for HTTP Strict Transport Security (HSTS).</p> <p>The .gov program will preload all newly registered .gov domains for HTTP Strict Transport Security (HSTS).</p>

View file

@ -3,7 +3,7 @@
<nav aria-label="Form steps,"> <nav aria-label="Form steps,">
<ul class="usa-sidenav"> <ul class="usa-sidenav">
{% for this_step in wizard.steps.all %} {% for this_step in wizard.steps.all %}
{% if forloop.counter <= wizard.steps.step1 %} {% if this_step in visited %}
<li class="usa-sidenav__item"> <li class="usa-sidenav__item">
<a href="{% url wizard.url_name step=this_step %}" <a href="{% url wizard.url_name step=this_step %}"
{% if this_step == wizard.steps.current %}class="usa-current"{% endif%}> {% if this_step == wizard.steps.current %}class="usa-current"{% endif%}>

View file

@ -40,7 +40,7 @@ class TestFormValidation(TestCase):
form = DotGovDomainForm(data={"requested_domain": "top-level-agency.com"}) form = DotGovDomainForm(data={"requested_domain": "top-level-agency.com"})
self.assertEqual( self.assertEqual(
form.errors["requested_domain"], form.errors["requested_domain"],
["Please enter a top-level domain name without any periods."], ["Please enter a domain without any periods."],
) )
def test_requested_domain_invalid_characters(self): def test_requested_domain_invalid_characters(self):

View file

@ -8,7 +8,7 @@ from django.contrib.auth import get_user_model
from django_webtest import WebTest # type: ignore from django_webtest import WebTest # type: ignore
from registrar.models import DomainApplication, Domain, Contact, Website from registrar.models import DomainApplication, Domain, Contact, Website
from registrar.forms.application_wizard import TITLES from registrar.forms.application_wizard import TITLES, Step
from .common import less_console_noise from .common import less_console_noise
@ -85,7 +85,8 @@ class LoggedInTests(TestWithUser):
def test_application_form_view(self): def test_application_form_view(self):
response = self.client.get("/register/", follow=True) response = self.client.get("/register/", follow=True)
self.assertContains( self.assertContains(
response, "What kind of government organization do you represent?" response,
"What kind of U.S.-based government organization do you represent?",
) )
@ -111,7 +112,9 @@ class DomainApplicationTests(TestWithUser, WebTest):
page = self.app.get(reverse("application")).follow() page = self.app.get(reverse("application")).follow()
# submitting should get back the same page if the required field is empty # submitting should get back the same page if the required field is empty
result = page.form.submit() result = page.form.submit()
self.assertIn("What kind of government organization do you represent?", result) self.assertIn(
"What kind of U.S.-based government organization do you represent?", result
)
def test_application_form_submission(self): def test_application_form_submission(self):
"""Can fill out the entire form and submit. """Can fill out the entire form and submit.
@ -617,6 +620,34 @@ class DomainApplicationTests(TestWithUser, WebTest):
contact_page = election_result.follow() contact_page = election_result.follow()
self.assertNotContains(contact_page, "Federal agency") self.assertNotContains(contact_page, "Federal agency")
def test_application_form_section_skipping(self):
"""Can skip forward and back in sections"""
type_page = self.app.get(reverse("application")).follow()
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.form
type_form["organization_type-organization_type"] = "federal"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_page.form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
federal_page = type_result.follow()
# Now on federal type page, click back to the organization type
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
new_page = federal_page.click(TITLES[Step.ORGANIZATION_TYPE], index=0)
# Should be a link to the organization_federal page
self.assertGreater(
len(new_page.html.find_all("a", href="/register/organization_federal/")),
0,
)
@skip("WIP") @skip("WIP")
def test_application_edit_restore(self): def test_application_edit_restore(self):
""" """