Refactor model layout

This commit is contained in:
Seamus Johnston 2022-11-10 08:26:06 -06:00
parent 349659be90
commit 8b8ade428b
No known key found for this signature in database
GPG key ID: 2F21225985069105
11 changed files with 522 additions and 462 deletions

66
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f3c73d2389ee9b1648528a855174d19d20b67f64a2337a660ebeaf613db31488" "sha256": "4e755e3f5778ff572fba5755b966cde05d30a84c4eddb1d63ca5fe1034565283"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -126,7 +126,7 @@
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
], ],
"markers": "python_version >= '3.6'", "markers": "python_full_version >= '3.6.0'",
"version": "==2.1.1" "version": "==2.1.1"
}, },
"cryptography": { "cryptography": {
@ -199,6 +199,14 @@
"index": "pypi", "index": "pypi",
"version": "==0.5.0" "version": "==0.5.0"
}, },
"django-auditlog": {
"hashes": [
"sha256:0ab57a536e02341e27c3d0431ad0e124e674507bd965a0756e29b01cb67c38ce",
"sha256:2f83389f98db4b1a9c2961f17cd9ac4a3ea94304655071f30da45d8debf59688"
],
"index": "pypi",
"version": "==2.2.0"
},
"django-cache-url": { "django-cache-url": {
"hashes": [ "hashes": [
"sha256:6cc9901a99a99751f5458aa7de08ce06e48c1441b1a94c9457d78af74fab9a26", "sha256:6cc9901a99a99751f5458aa7de08ce06e48c1441b1a94c9457d78af74fab9a26",
@ -378,6 +386,7 @@
"sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd", "sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd",
"sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147", "sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147",
"sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c", "sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c",
"sha256:2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903",
"sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba", "sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba",
"sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632", "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632",
"sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577", "sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577",
@ -423,6 +432,7 @@
"sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64", "sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64",
"sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb", "sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb",
"sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882", "sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882",
"sha256:bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720",
"sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896", "sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896",
"sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267", "sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267",
"sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7", "sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7",
@ -497,6 +507,14 @@
"markers": "python_full_version >= '3.6.8'", "markers": "python_full_version >= '3.6.8'",
"version": "==3.0.9" "version": "==3.0.9"
}, },
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.2"
},
"python-dotenv": { "python-dotenv": {
"hashes": [ "hashes": [
"sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5", "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5",
@ -683,7 +701,7 @@
"sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd", "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd",
"sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa" "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==4.0.9" "version": "==4.0.9"
}, },
"gitpython": { "gitpython": {
@ -699,7 +717,7 @@
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==0.7.0" "version": "==0.7.0"
}, },
"mypy": { "mypy": {
@ -782,7 +800,7 @@
"sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785",
"sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b" "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==2.9.1" "version": "==2.9.1"
}, },
"pyflakes": { "pyflakes": {
@ -790,7 +808,7 @@
"sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2",
"sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3" "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==2.5.0" "version": "==2.5.0"
}, },
"pyyaml": { "pyyaml": {
@ -836,7 +854,7 @@
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==6.0" "version": "==6.0"
}, },
"six": { "six": {
@ -852,7 +870,7 @@
"sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
"sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==5.0.0" "version": "==5.0.0"
}, },
"soupsieve": { "soupsieve": {
@ -860,7 +878,7 @@
"sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
"sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==2.3.2.post1" "version": "==2.3.2.post1"
}, },
"sqlparse": { "sqlparse": {
@ -873,18 +891,18 @@
}, },
"stevedore": { "stevedore": {
"hashes": [ "hashes": [
"sha256:02518a8f0d6d29be8a445b7f2ac63753ff29e8f2a2faa01777568d5500d777a6", "sha256:7f8aeb6e3f90f96832c301bff21a7eb5eefbe894c88c506483d355565d88cc1a",
"sha256:3b1cbd592a87315f000d05164941ee5e164899f8fc0ce9a00bb0f321f40ef93e" "sha256:aa6436565c069b2946fe4ebff07f5041e0c8bf18c7376dd29edf80cf7d524e4e"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==4.1.0" "version": "==4.1.1"
}, },
"tomli": { "tomli": {
"hashes": [ "hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
], ],
"markers": "python_version < '3.11'", "markers": "python_full_version < '3.11.0a7'",
"version": "==2.0.1" "version": "==2.0.1"
}, },
"types-cachetools": { "types-cachetools": {
@ -904,25 +922,25 @@
}, },
"types-pyyaml": { "types-pyyaml": {
"hashes": [ "hashes": [
"sha256:70ccaafcf3fb404d57bffc1529fdd86a13e8b4f2cf9fc3ee81a6408ce0ad59d2", "sha256:1e94e80aafee07a7e798addb2a320e32956a373f376655128ae20637adb2655b",
"sha256:aaf5e51444c13bd34104695a89ad9c48412599a4f615d65a60e649109714f608" "sha256:6840819871c92deebe6a2067fb800c11b8a063632eb4e3e755914e7ab3604e83"
], ],
"version": "==6.0.12.1" "version": "==6.0.12.2"
}, },
"types-requests": { "types-requests": {
"hashes": [ "hashes": [
"sha256:14941f8023a80b16441b3b46caffcbfce5265fd14555844d6029697824b5a2ef", "sha256:bdb1f9811e53d0642c8347b09137363eb25e1a516819e190da187c29595a1df3",
"sha256:fdcd7bd148139fb8eef72cf4a41ac7273872cad9e6ada14b11ff5dfdeee60ed3" "sha256:d4f342b0df432262e9e326d17638eeae96a5881e78e7a6aae46d33870d73952e"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.28.11.2" "version": "==2.28.11.4"
}, },
"types-urllib3": { "types-urllib3": {
"hashes": [ "hashes": [
"sha256:a948584944b2412c9a74b9cf64f6c48caf8652cb88b38361316f6d15d8a184cd", "sha256:1807b87b8ee1ae0226813ba2c52330eff20fb2bf6359b1de24df08eb3090e442",
"sha256:f6422596cc9ee5fdf68f9d547f541096a20c2dcfd587e37c804c9ea720bf5cb2" "sha256:a188c24fc61a99658c8c324c8dd7419f5b91a0d89df004e5f576869122c1db55"
], ],
"version": "==1.26.25.1" "version": "==1.26.25.3"
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
@ -937,7 +955,7 @@
"sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a", "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a",
"sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba" "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"
], ],
"markers": "python_version >= '3.7'", "markers": "python_full_version >= '3.7.0'",
"version": "==2.1.2" "version": "==2.1.2"
}, },
"webob": { "webob": {
@ -953,7 +971,7 @@
"sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead", "sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead",
"sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb" "sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"
], ],
"markers": "python_version < '4' and python_full_version >= '3.6.0'", "markers": "python_version >= '3.6' and python_version < '4'",
"version": "==3.0.0" "version": "==3.0.0"
} }
} }

View file

@ -1,4 +1,4 @@
# Generated by Django 4.1.1 on 2022-09-26 15:26 # Generated by Django 4.1.3 on 2022-11-10 14:23
from django.conf import settings from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
@ -6,6 +6,7 @@ import django.contrib.auth.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import django_fsm # type: ignore
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -132,7 +133,65 @@ class Migration(migrations.Migration):
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name="UserProfile", name="Contact",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"first_name",
models.TextField(
blank=True, db_index=True, help_text="First name", null=True
),
),
(
"middle_name",
models.TextField(blank=True, help_text="Middle name", null=True),
),
(
"last_name",
models.TextField(
blank=True, db_index=True, help_text="Last name", null=True
),
),
("title", models.TextField(blank=True, help_text="Title", null=True)),
(
"email",
models.TextField(
blank=True, db_index=True, help_text="Email", null=True
),
),
(
"phone",
models.TextField(
blank=True, db_index=True, help_text="Phone", null=True
),
),
],
),
migrations.CreateModel(
name="Website",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("website", models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name="DomainApplication",
fields=[ fields=[
( (
"id", "id",
@ -145,6 +204,228 @@ class Migration(migrations.Migration):
), ),
("created_at", models.DateTimeField(auto_now_add=True)), ("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)), ("updated_at", models.DateTimeField(auto_now=True)),
(
"status",
django_fsm.FSMField(
choices=[
("started", "started"),
("submitted", "submitted"),
("investigating", "investigating"),
("approved", "approved"),
],
default="started",
max_length=50,
),
),
(
"organization_type",
models.CharField(
blank=True,
choices=[
("federal", "a federal agency"),
("interstate", "an organization of two or more states"),
(
"state_or_territory",
"one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
),
(
"tribal",
"a tribal government recognized by the federal or state government",
),
("county", "a county, parish, or borough"),
("city", "a city, town, township, village, etc."),
(
"special_district",
"an independent organization within a single state",
),
],
help_text="Type of Organization",
max_length=255,
null=True,
),
),
(
"federal_branch",
models.CharField(
blank=True,
choices=[
("Executive", "Executive"),
("Judicial", "Judicial"),
("Legislative", "Legislative"),
],
help_text="Branch of federal government",
max_length=50,
null=True,
),
),
(
"is_election_office",
models.BooleanField(
blank=True,
help_text="Is your ogranization an election office?",
null=True,
),
),
(
"organization_name",
models.TextField(
blank=True,
db_index=True,
help_text="Organization name",
null=True,
),
),
(
"street_address",
models.TextField(blank=True, help_text="Street Address", null=True),
),
(
"unit_type",
models.CharField(
blank=True, help_text="Unit type", max_length=15, null=True
),
),
(
"unit_number",
models.CharField(
blank=True, help_text="Unit number", max_length=255, null=True
),
),
(
"state_territory",
models.CharField(
blank=True, help_text="State/Territory", max_length=2, null=True
),
),
(
"zip_code",
models.CharField(
blank=True,
db_index=True,
help_text="ZIP code",
max_length=10,
null=True,
),
),
(
"purpose",
models.TextField(
blank=True, help_text="Purpose of the domain", null=True
),
),
(
"security_email",
models.CharField(
blank=True,
help_text="Security email for public use",
max_length=320,
null=True,
),
),
(
"anything_else",
models.TextField(
blank=True, help_text="Anything else we should know?", null=True
),
),
(
"acknowledged_policy",
models.BooleanField(
blank=True,
help_text="Acknowledged .gov acceptable use policy",
null=True,
),
),
(
"alternative_domains",
models.ManyToManyField(
blank=True, related_name="alternatives+", to="registrar.website"
),
),
(
"authorizing_official",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="authorizing_official",
to="registrar.contact",
),
),
(
"creator",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="applications_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"current_websites",
models.ManyToManyField(
blank=True, related_name="current+", to="registrar.website"
),
),
(
"investigator",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="applications_investigating",
to=settings.AUTH_USER_MODEL,
),
),
(
"other_contacts",
models.ManyToManyField(
blank=True,
related_name="contact_applications",
to="registrar.contact",
),
),
(
"requested_domain",
models.ForeignKey(
blank=True,
help_text="The requested domain",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="requested+",
to="registrar.website",
),
),
(
"submitter",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="submitted_applications",
to="registrar.contact",
),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="UserProfile",
fields=[
(
"contact_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="registrar.contact",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("street1", models.TextField(blank=True)), ("street1", models.TextField(blank=True)),
("street2", models.TextField(blank=True)), ("street2", models.TextField(blank=True)),
("street3", models.TextField(blank=True)), ("street3", models.TextField(blank=True)),
@ -152,13 +433,11 @@ class Migration(migrations.Migration):
("sp", models.TextField(blank=True)), ("sp", models.TextField(blank=True)),
("pc", models.TextField(blank=True)), ("pc", models.TextField(blank=True)),
("cc", models.TextField(blank=True)), ("cc", models.TextField(blank=True)),
("voice", models.TextField(blank=True)),
("fax", models.TextField(blank=True)),
("email", models.TextField(blank=True)),
("display_name", models.TextField()), ("display_name", models.TextField()),
( (
"user", "user",
models.OneToOneField( models.OneToOneField(
blank=True,
null=True, null=True,
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL, to=settings.AUTH_USER_MODEL,
@ -168,5 +447,6 @@ class Migration(migrations.Migration):
options={ options={
"abstract": False, "abstract": False,
}, },
bases=("registrar.contact", models.Model),
), ),
] ]

View file

@ -1,258 +0,0 @@
# Generated by Django 4.1.3 on 2022-11-08 20:17
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django_fsm # type: ignore
class Migration(migrations.Migration):
dependencies = [
("registrar", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Contact",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"first_name",
models.TextField(db_index=True, help_text="First name", null=True),
),
("middle_name", models.TextField(help_text="Middle name", null=True)),
(
"last_name",
models.TextField(db_index=True, help_text="Last name", null=True),
),
("title", models.TextField(help_text="Title", null=True)),
(
"email",
models.TextField(db_index=True, help_text="Email", null=True),
),
(
"phone",
models.TextField(db_index=True, help_text="Phone", null=True),
),
],
),
migrations.CreateModel(
name="Website",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("website", models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name="DomainApplication",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"status",
django_fsm.FSMField(
choices=[
("started", "started"),
("submitted", "submitted"),
("investigating", "investigating"),
("approved", "approved"),
],
default="started",
max_length=50,
),
),
(
"organization_type",
models.CharField(
choices=[
("federal", "a federal agency"),
("interstate", "an organization of two or more states"),
(
"state_or_territory",
"one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
),
(
"tribal",
"a tribal government recognized by the federal or state government",
),
("county", "a county, parish, or borough"),
("city", "a city, town, township, village, etc."),
(
"special_district",
"an independent organization within a single state",
),
],
help_text="Type of Organization",
max_length=255,
),
),
(
"federal_branch",
models.CharField(
choices=[
("Executive", "Executive"),
("Judicial", "Judicial"),
("Legislative", "Legislative"),
],
help_text="Branch of federal government",
max_length=50,
null=True,
),
),
(
"is_election_office",
models.BooleanField(
help_text="Is your ogranization an election office?", null=True
),
),
(
"organization_name",
models.TextField(
db_index=True, help_text="Organization name", null=True
),
),
(
"street_address",
models.TextField(help_text="Street Address", null=True),
),
(
"unit_type",
models.CharField(help_text="Unit type", max_length=15, null=True),
),
(
"unit_number",
models.CharField(
help_text="Unit number", max_length=255, null=True
),
),
(
"state_territory",
models.CharField(
help_text="State/Territory", max_length=2, null=True
),
),
(
"zip_code",
models.CharField(
db_index=True, help_text="ZIP code", max_length=10, null=True
),
),
(
"purpose",
models.TextField(help_text="Purpose of the domain", null=True),
),
(
"security_email",
models.CharField(
help_text="Security email for public use",
max_length=320,
null=True,
),
),
(
"anything_else",
models.TextField(
help_text="Anything else we should know?", null=True
),
),
(
"acknowledged_policy",
models.BooleanField(
help_text="Acknowledged .gov acceptable use policy", null=True
),
),
(
"alternative_domains",
models.ManyToManyField(
related_name="alternatives+", to="registrar.website"
),
),
(
"authorizing_official",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="authorizing_official",
to="registrar.contact",
),
),
(
"creator",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="applications_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"current_websites",
models.ManyToManyField(
related_name="current+", to="registrar.website"
),
),
(
"investigator",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="applications_investigating",
to=settings.AUTH_USER_MODEL,
),
),
(
"other_contacts",
models.ManyToManyField(
related_name="contact_applications", to="registrar.contact"
),
),
(
"requested_domain",
models.ForeignKey(
help_text="The requested domain",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="requested+",
to="registrar.website",
),
),
(
"submitter",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="submitted_applications",
to="registrar.contact",
),
),
],
options={
"abstract": False,
},
),
]

View file

@ -1,6 +1,10 @@
from auditlog.registry import auditlog # type: ignore from auditlog.registry import auditlog # type: ignore
from .models import User, UserProfile, Contact, Website, DomainApplication from .contact import Contact
from .domain_application import DomainApplication
from .user_profile import UserProfile
from .user import User
from .website import Website
__all__ = [ __all__ = [
"Contact", "Contact",

View file

@ -0,0 +1,51 @@
from django.db import models
class Contact(models.Model):
"""Contact information follows a similar pattern for each contact."""
first_name = models.TextField(
null=True,
blank=True,
help_text="First name",
db_index=True,
)
middle_name = models.TextField(
null=True,
blank=True,
help_text="Middle name",
)
last_name = models.TextField(
null=True,
blank=True,
help_text="Last name",
db_index=True,
)
title = models.TextField(
null=True,
blank=True,
help_text="Title",
)
email = models.TextField(
null=True,
blank=True,
help_text="Email",
db_index=True,
)
phone = models.TextField(
null=True,
blank=True,
help_text="Phone",
db_index=True,
)
def __str__(self):
if self.first_name or self.last_name:
return f"{self.title or ''} {self.first_name or ''} {self.last_name or ''}"
elif self.email:
return self.email
elif self.pk:
return str(self.pk)
else:
return ""

View file

@ -1,174 +1,10 @@
import re
from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django_fsm import FSMField, transition # type: ignore from django_fsm import FSMField, transition # type: ignore
from .utility.time_stamped_model import TimeStampedModel
class User(AbstractUser): from .contact import Contact
""" from .user import User
A custom user model that performs identically to the default user model from .website import Website
but can be customized later.
"""
def __str__(self):
# this info is pulled from Login.gov
if self.first_name or self.last_name:
return f"{self.first_name or ''} {self.last_name or ''}"
elif self.email:
return self.email
else:
return self.username
class TimeStampedModel(models.Model):
"""
An abstract base model that provides self-updating
`created_at` and `updated_at` fields.
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
# don't put anything else here, it will be ignored
class AddressModel(models.Model):
"""
An abstract base model that provides common fields
for postal addresses.
"""
# contact's street (null ok)
street1 = models.TextField(blank=True)
# contact's street (null ok)
street2 = models.TextField(blank=True)
# contact's street (null ok)
street3 = models.TextField(blank=True)
# contact's city
city = models.TextField(blank=True)
# contact's state or province (null ok)
sp = models.TextField(blank=True)
# contact's postal code (null ok)
pc = models.TextField(blank=True)
# contact's country code
cc = models.TextField(blank=True)
class Meta:
abstract = True
# don't put anything else here, it will be ignored
class Website(models.Model):
"""Keep domain names in their own table so that applications can refer to
many of them."""
# domain names have strictly limited lengths, 255 characters is more than
# enough.
website = models.CharField(
max_length=255,
null=False,
help_text="",
)
# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
@classmethod
def string_could_be_domain(cls, domain: str) -> bool:
"""Return True if the string could be a domain name, otherwise False.
TODO: when we have a Domain class, this could be a classmethod there.
"""
if cls.DOMAIN_REGEX.match(domain):
return True
return False
def could_be_domain(self) -> bool:
"""Could this instance be a domain?"""
# short-circuit if self.website is null/None
if not self.website:
return False
return self.string_could_be_domain(str(self.website))
def __str__(self) -> str:
return str(self.website)
class Contact(models.Model):
"""Contact information follows a similar pattern for each contact."""
first_name = models.TextField(
null=True,
blank=True,
help_text="First name",
db_index=True,
)
middle_name = models.TextField(
null=True,
blank=True,
help_text="Middle name",
)
last_name = models.TextField(
null=True,
blank=True,
help_text="Last name",
db_index=True,
)
title = models.TextField(
null=True,
blank=True,
help_text="Title",
)
email = models.TextField(
null=True,
blank=True,
help_text="Email",
db_index=True,
)
phone = models.TextField(
null=True,
blank=True,
help_text="Phone",
db_index=True,
)
def __str__(self):
if self.first_name or self.last_name:
return f"{self.title or ''} {self.first_name or ''} {self.last_name or ''}"
elif self.email:
return self.email
elif self.pk:
return str(self.pk)
else:
return ""
class UserProfile(TimeStampedModel, Contact, AddressModel):
"""User information, unrelated to their login/auth details."""
user = models.OneToOneField(
User,
null=True,
blank=True,
on_delete=models.CASCADE,
)
display_name = models.TextField()
def __str__(self):
# use info stored in User rather than Contact,
# because Contact is user-editable while User
# pulls from identity-verified Login.gov
if self.user:
return str(self.user)
else:
return "Orphaned account"
class DomainApplication(TimeStampedModel): class DomainApplication(TimeStampedModel):
@ -367,10 +203,10 @@ class DomainApplication(TimeStampedModel):
) )
def __str__(self): def __str__(self):
try:
if self.requested_domain and self.requested_domain.website: if self.requested_domain and self.requested_domain.website:
return self.requested_domain.website return self.requested_domain.website
else: else:
try:
return f"{self.status} application created by {self.creator}" return f"{self.status} application created by {self.creator}"
except Exception: except Exception:
return "" return ""

View file

@ -0,0 +1,17 @@
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
"""
A custom user model that performs identically to the default user model
but can be customized later.
"""
def __str__(self):
# this info is pulled from Login.gov
if self.first_name or self.last_name:
return f"{self.first_name or ''} {self.last_name or ''}"
elif self.email:
return self.email
else:
return self.username

View file

@ -0,0 +1,29 @@
from django.db import models
from .utility.time_stamped_model import TimeStampedModel
from .utility.address_model import AddressModel
from .contact import Contact
from .user import User
class UserProfile(TimeStampedModel, Contact, AddressModel):
"""User information, unrelated to their login/auth details."""
user = models.OneToOneField(
User,
null=True,
blank=True,
on_delete=models.CASCADE,
)
display_name = models.TextField()
def __str__(self):
# use info stored in User rather than Contact,
# because Contact is user-editable while User
# pulls from identity-verified Login.gov
try:
return str(self.user)
except Exception:
return "Orphaned account"

View file

@ -0,0 +1,27 @@
from django.db import models
class AddressModel(models.Model):
"""
An abstract base model that provides common fields
for postal addresses.
"""
# contact's street (null ok)
street1 = models.TextField(blank=True)
# contact's street (null ok)
street2 = models.TextField(blank=True)
# contact's street (null ok)
street3 = models.TextField(blank=True)
# contact's city
city = models.TextField(blank=True)
# contact's state or province (null ok)
sp = models.TextField(blank=True)
# contact's postal code (null ok)
pc = models.TextField(blank=True)
# contact's country code
cc = models.TextField(blank=True)
class Meta:
abstract = True
# don't put anything else here, it will be ignored

View file

@ -0,0 +1,15 @@
from django.db import models
class TimeStampedModel(models.Model):
"""
An abstract base model that provides self-updating
`created_at` and `updated_at` fields.
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
# don't put anything else here, it will be ignored

View file

@ -0,0 +1,41 @@
import re
from django.db import models
class Website(models.Model):
"""Keep domain names in their own table so that applications can refer to
many of them."""
# domain names have strictly limited lengths, 255 characters is more than
# enough.
website = models.CharField(
max_length=255,
null=False,
help_text="",
)
# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
@classmethod
def string_could_be_domain(cls, domain: str) -> bool:
"""Return True if the string could be a domain name, otherwise False.
TODO: when we have a Domain class, this could be a classmethod there.
"""
if cls.DOMAIN_REGEX.match(domain):
return True
return False
def could_be_domain(self) -> bool:
"""Could this instance be a domain?"""
# short-circuit if self.website is null/None
if not self.website:
return False
return self.string_could_be_domain(str(self.website))
def __str__(self) -> str:
return str(self.website)