diff --git a/docs/architecture/diagrams/model_timeline.md b/docs/architecture/diagrams/model_timeline.md
index f2089ce55..cc7dddc6c 100644
--- a/docs/architecture/diagrams/model_timeline.md
+++ b/docs/architecture/diagrams/model_timeline.md
@@ -44,7 +44,8 @@ class DomainApplication {
authorizing_official (Contact)
submitter (Contact)
other_contacts (Contacts)
- requested_domain (Domain)
+ approved_domain (Domain)
+ requested_domain (DraftDomain)
current_websites (Websites)
alternative_domains (Websites)
--
@@ -81,11 +82,19 @@ class Contact {
DomainApplication *-r-* Contact : authorizing_official, submitter, other_contacts
+class DraftDomain {
+ Requested domain
+ --
+ name
+ --
+}
+
+DomainApplication -l- DraftDomain : requested_domain
+
class Domain {
Approved domain
--
name
- is_active
--
EPP methods
}
diff --git a/docs/architecture/diagrams/model_timeline.svg b/docs/architecture/diagrams/model_timeline.svg
index cf2eea238..4e0400bb0 100644
--- a/docs/architecture/diagrams/model_timeline.svg
+++ b/docs/architecture/diagrams/model_timeline.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/architecture/diagrams/models_diagram.md b/docs/architecture/diagrams/models_diagram.md
index 77fa36707..000d480c7 100644
--- a/docs/architecture/diagrams/models_diagram.md
+++ b/docs/architecture/diagrams/models_diagram.md
@@ -65,6 +65,7 @@ class "registrar.DomainApplication " as registrar.DomainApplication #
+ type_of_work (TextField)
+ more_organization_information (TextField)
~ authorizing_official (ForeignKey)
+ ~ approved_domain (OneToOneField)
~ requested_domain (OneToOneField)
~ submitter (ForeignKey)
+ purpose (TextField)
@@ -79,6 +80,7 @@ class "registrar.DomainApplication " as registrar.DomainApplication #
registrar.DomainApplication -- registrar.User
registrar.DomainApplication -- registrar.User
registrar.DomainApplication -- registrar.Contact
+registrar.DomainApplication -- registrar.DraftDomain
registrar.DomainApplication -- registrar.Domain
registrar.DomainApplication -- registrar.Contact
registrar.DomainApplication *--* registrar.Website
@@ -129,6 +131,17 @@ registrar.DomainInformation -- registrar.Contact
registrar.DomainInformation *--* registrar.Contact
+class "registrar.DraftDomain " as registrar.DraftDomain #d6f4e9 {
+ draft domain
+ --
+ + id (BigAutoField)
+ + created_at (DateTimeField)
+ + updated_at (DateTimeField)
+ + name (CharField)
+ --
+}
+
+
class "registrar.Domain " as registrar.Domain #d6f4e9 {
domain
--
@@ -136,7 +149,6 @@ class "registrar.Domain " as registrar.Domain #d6f4e9 {
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ name (CharField)
- + is_active (FSMField)
--
}
@@ -218,6 +230,8 @@ class "registrar.PublicContact " as registrar.PublicContact #d6f4e9 {
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ contact_type (CharField)
+ + registry_id (CharField)
+ ~ domain (ForeignKey)
+ name (TextField)
+ org (TextField)
+ street1 (TextField)
@@ -234,6 +248,8 @@ class "registrar.PublicContact " as registrar.PublicContact #d6f4e9 {
--
}
+registrar.PublicContact -- registrar.Domain
+
class "registrar.User " as registrar.User #d6f4e9 {
user
diff --git a/docs/architecture/diagrams/models_diagram.svg b/docs/architecture/diagrams/models_diagram.svg
index e0cdd355f..0075c44cb 100644
--- a/docs/architecture/diagrams/models_diagram.svg
+++ b/docs/architecture/diagrams/models_diagram.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/api/views.py b/src/api/views.py
index ab68a42d3..e19e060ef 100644
--- a/src/api/views.py
+++ b/src/api/views.py
@@ -37,7 +37,7 @@ def _domains():
Fetch a file from DOMAIN_FILE_URL, parse the CSV for the domain,
lowercase everything and return the list.
"""
- Domain = apps.get_model("registrar.Domain")
+ DraftDomain = apps.get_model("registrar.DraftDomain")
# 5 second timeout
file_contents = requests.get(DOMAIN_FILE_URL, timeout=5).text
domains = set()
@@ -46,7 +46,7 @@ def _domains():
# get the domain before the first comma
domain = line.split(",", 1)[0]
# sanity-check the string we got from the file here
- if Domain.string_could_be_domain(domain):
+ if DraftDomain.string_could_be_domain(domain):
# lowercase everything when we put it in domains
domains.add(domain.lower())
return domains
@@ -75,12 +75,12 @@ def available(request, domain=""):
Response is a JSON dictionary with the key "available" and value true or
false.
"""
- Domain = apps.get_model("registrar.Domain")
+ DraftDomain = apps.get_model("registrar.DraftDomain")
# validate that the given domain could be a domain name and fail early if
# not.
if not (
- Domain.string_could_be_domain(domain)
- or Domain.string_could_be_domain(domain + ".gov")
+ DraftDomain.string_could_be_domain(domain)
+ or DraftDomain.string_could_be_domain(domain + ".gov")
):
return JsonResponse(
{"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
diff --git a/src/registrar/fixtures.py b/src/registrar/fixtures.py
index 0161527e1..438f6723b 100644
--- a/src/registrar/fixtures.py
+++ b/src/registrar/fixtures.py
@@ -5,7 +5,7 @@ from faker import Faker
from registrar.models import (
User,
DomainApplication,
- Domain,
+ DraftDomain,
Contact,
Website,
)
@@ -216,11 +216,13 @@ class DomainApplicationFixture:
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(
+ da.requested_domain, _ = DraftDomain.objects.get_or_create(
name=app["requested_domain"]
)
else:
- da.requested_domain = Domain.objects.create(name=cls.fake_dot_gov())
+ da.requested_domain = DraftDomain.objects.create(
+ name=cls.fake_dot_gov()
+ )
@classmethod
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py
index 333c9c6b1..f04a3488e 100644
--- a/src/registrar/forms/application_wizard.py
+++ b/src/registrar/forms/application_wizard.py
@@ -11,7 +11,7 @@ from django.utils.safestring import mark_safe
from api.views import DOMAIN_API_MESSAGES
-from registrar.models import Contact, DomainApplication, Domain
+from registrar.models import Contact, DomainApplication, DraftDomain, Domain
from registrar.utility import errors
logger = logging.getLogger(__name__)
@@ -453,7 +453,7 @@ class AlternativeDomainForm(RegistrarForm):
"""Validation code for domain names."""
try:
requested = self.cleaned_data.get("alternative_domain", None)
- validated = Domain.validate(requested, blank_ok=True)
+ validated = DraftDomain.validate(requested, blank_ok=True)
except errors.ExtraDotsError:
raise forms.ValidationError(
DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots"
@@ -498,7 +498,7 @@ class BaseAlternativeDomainFormSet(RegistrarFormSet):
@classmethod
def on_fetch(cls, query):
- return [{"alternative_domain": domain.sld} for domain in query]
+ return [{"alternative_domain": Domain.sld(domain.name)} for domain in query]
@classmethod
def from_database(cls, obj):
@@ -524,7 +524,7 @@ class DotGovDomainForm(RegistrarForm):
requested_domain.name = f"{domain}.gov"
requested_domain.save()
else:
- requested_domain = Domain.objects.create(name=f"{domain}.gov")
+ requested_domain = DraftDomain.objects.create(name=f"{domain}.gov")
obj.requested_domain = requested_domain
obj.save()
@@ -535,14 +535,14 @@ class DotGovDomainForm(RegistrarForm):
values = {}
requested_domain = getattr(obj, "requested_domain", None)
if requested_domain is not None:
- values["requested_domain"] = requested_domain.sld
+ values["requested_domain"] = Domain.sld(requested_domain.name)
return values
def clean_requested_domain(self):
"""Validation code for domain names."""
try:
requested = self.cleaned_data.get("requested_domain", None)
- validated = Domain.validate(requested)
+ validated = DraftDomain.validate(requested)
except errors.BlankValueError:
raise forms.ValidationError(
DOMAIN_API_MESSAGES["required"], code="required"
diff --git a/src/registrar/management/commands/load_domains_data.py b/src/registrar/management/commands/load_domains_data.py
index 358a4113e..b205e562e 100644
--- a/src/registrar/management/commands/load_domains_data.py
+++ b/src/registrar/management/commands/load_domains_data.py
@@ -54,16 +54,6 @@ class Command(BaseCommand):
domains = []
for row in reader:
name = row["Name"].lower() # we typically use lowercase domains
-
- # Ensure that there is a `Domain` object for each domain name in
- # this file and that it is active. There is a uniqueness
- # constraint for active Domain objects, so we are going to account
- # for that here with this check so that our later bulk_create
- # should succeed
- if Domain.objects.filter(name=name, is_active=True).exists():
- # don't do anything, this domain is here and active
- continue
- else:
- domains.append(Domain(name=name, is_active=True))
+ domains.append(Domain(name=name))
logger.info("Creating %d new domains", len(domains))
Domain.objects.bulk_create(domains)
diff --git a/src/registrar/migrations/0022_draftdomain_domainapplication_approved_domain_and_more.py b/src/registrar/migrations/0022_draftdomain_domainapplication_approved_domain_and_more.py
new file mode 100644
index 000000000..fb89e0eb2
--- /dev/null
+++ b/src/registrar/migrations/0022_draftdomain_domainapplication_approved_domain_and_more.py
@@ -0,0 +1,66 @@
+# Generated by Django 4.2.1 on 2023-05-26 13:14
+
+from django.db import migrations, models
+import django.db.models.deletion
+import registrar.models.utility.domain_helper
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("registrar", "0021_publiccontact_domain_publiccontact_registry_id_and_more"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="DraftDomain",
+ 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)),
+ (
+ "name",
+ models.CharField(
+ default=None,
+ help_text="Fully qualified domain name",
+ max_length=253,
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ bases=(models.Model, registrar.models.utility.domain_helper.DomainHelper), # type: ignore
+ ),
+ migrations.AddField(
+ model_name="domainapplication",
+ name="approved_domain",
+ field=models.OneToOneField(
+ blank=True,
+ help_text="The approved domain",
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="domain_application",
+ to="registrar.domain",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="domainapplication",
+ name="requested_domain",
+ field=models.OneToOneField(
+ blank=True,
+ help_text="The requested domain",
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="domain_application",
+ to="registrar.draftdomain",
+ ),
+ ),
+ ]
diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py
index 9351e312d..542cb00e1 100644
--- a/src/registrar/models/__init__.py
+++ b/src/registrar/models/__init__.py
@@ -4,6 +4,7 @@ from .contact import Contact
from .domain_application import DomainApplication
from .domain_information import DomainInformation
from .domain import Domain
+from .draft_domain import DraftDomain
from .host_ip import HostIP
from .host import Host
from .domain_invitation import DomainInvitation
@@ -18,6 +19,7 @@ __all__ = [
"DomainApplication",
"DomainInformation",
"Domain",
+ "DraftDomain",
"DomainInvitation",
"HostIP",
"Host",
@@ -31,6 +33,7 @@ __all__ = [
auditlog.register(Contact)
auditlog.register(DomainApplication)
auditlog.register(Domain)
+auditlog.register(DraftDomain)
auditlog.register(DomainInvitation)
auditlog.register(HostIP)
auditlog.register(Host)
diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py
index 44c93892c..3859e04a7 100644
--- a/src/registrar/models/domain_application.py
+++ b/src/registrar/models/domain_application.py
@@ -400,10 +400,19 @@ class DomainApplication(TimeStampedModel):
related_name="current+",
)
- requested_domain = models.OneToOneField(
+ approved_domain = models.OneToOneField(
"Domain",
null=True,
blank=True,
+ help_text="The approved domain",
+ related_name="domain_application",
+ on_delete=models.PROTECT,
+ )
+
+ requested_domain = models.OneToOneField(
+ "DraftDomain",
+ null=True,
+ blank=True,
help_text="The requested domain",
related_name="domain_application",
on_delete=models.PROTECT,
@@ -499,8 +508,8 @@ class DomainApplication(TimeStampedModel):
if self.requested_domain is None:
raise ValueError("Requested domain is missing.")
- Domain = apps.get_model("registrar.Domain")
- if not Domain.string_could_be_domain(self.requested_domain.name):
+ DraftDomain = apps.get_model("registrar.DraftDomain")
+ if not DraftDomain.string_could_be_domain(self.requested_domain.name):
raise ValueError("Requested domain is not a valid domain name.")
# When an application is submitted, we need to send a confirmation email
@@ -516,13 +525,16 @@ class DomainApplication(TimeStampedModel):
application into an admin on that domain.
"""
- # create the domain if it doesn't exist
+ # create the domain
Domain = apps.get_model("registrar.Domain")
- created_domain, _ = Domain.objects.get_or_create(name=self.requested_domain)
+ if Domain.objects.filter(name=self.requested_domain.name).exists():
+ raise ValueError("Cannot approve. Requested domain is already in use.")
+ created_domain = Domain.objects.create(name=self.requested_domain.name)
+ self.approved_domain = created_domain
# copy the information from domainapplication into domaininformation
DomainInformation = apps.get_model("registrar.DomainInformation")
- DomainInformation.create_from_da(self)
+ DomainInformation.create_from_da(self, domain=created_domain)
# create the permission for the user
UserDomainRole = apps.get_model("registrar.UserDomainRole")
diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py
index c7832266b..c86a6c04f 100644
--- a/src/registrar/models/domain_information.py
+++ b/src/registrar/models/domain_information.py
@@ -211,24 +211,24 @@ class DomainInformation(TimeStampedModel):
return ""
@classmethod
- def create_from_da(cls, domain_application):
+ def create_from_da(cls, domain_application, domain=None):
"""Takes in a DomainApplication dict and converts it into DomainInformation"""
da_dict = domain_application.to_dict()
# remove the id so one can be assinged on creation
- da_id = da_dict.pop("id")
+ da_id = da_dict.pop("id", None)
# check if we have a record that corresponds with the domain
# application, if so short circuit the create
domain_info = cls.objects.filter(domain_application__id=da_id).first()
if domain_info:
return domain_info
# the following information below is not needed in the domain information:
- da_dict.pop("status")
- da_dict.pop("current_websites")
- da_dict.pop("investigator")
- da_dict.pop("alternative_domains")
- # use the requested_domain to create information for this domain
- da_dict["domain"] = da_dict.pop("requested_domain")
- other_contacts = da_dict.pop("other_contacts")
+ da_dict.pop("status", None)
+ da_dict.pop("current_websites", None)
+ da_dict.pop("investigator", None)
+ da_dict.pop("alternative_domains", None)
+ da_dict.pop("requested_domain", None)
+ da_dict.pop("approved_domain", None)
+ other_contacts = da_dict.pop("other_contacts", [])
domain_info = cls(**da_dict)
domain_info.domain_application = domain_application
# Save so the object now have PK
@@ -237,6 +237,8 @@ class DomainInformation(TimeStampedModel):
# Process the remaining "many to many" stuff
domain_info.other_contacts.add(*other_contacts)
+ if domain:
+ domain_info.domain = domain
domain_info.save()
return domain_info
diff --git a/src/registrar/models/draft_domain.py b/src/registrar/models/draft_domain.py
new file mode 100644
index 000000000..fc70a18f3
--- /dev/null
+++ b/src/registrar/models/draft_domain.py
@@ -0,0 +1,22 @@
+import logging
+
+from django.db import models
+
+from .utility.domain_helper import DomainHelper
+from .utility.time_stamped_model import TimeStampedModel
+
+logger = logging.getLogger(__name__)
+
+
+class DraftDomain(TimeStampedModel, DomainHelper):
+ """Store domain names which registrants have requested."""
+
+ def __str__(self) -> str:
+ return self.name
+
+ name = models.CharField(
+ max_length=253,
+ blank=False,
+ default=None, # prevent saving without a value
+ help_text="Fully qualified domain name",
+ )
diff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py
new file mode 100644
index 000000000..8f5737915
--- /dev/null
+++ b/src/registrar/models/utility/domain_helper.py
@@ -0,0 +1,64 @@
+import re
+
+from api.views import in_domains
+from registrar.utility import errors
+
+
+class DomainHelper:
+ """Utility functions and constants for domain names."""
+
+ # 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}(? bool:
+ """Return True if the string could be a domain name, otherwise False."""
+ if not isinstance(domain, str):
+ return False
+ return bool(cls.DOMAIN_REGEX.match(domain))
+
+ @classmethod
+ def validate(cls, domain: str | None, blank_ok=False) -> str:
+ """Attempt to determine if a domain name could be requested."""
+ if domain is None:
+ raise errors.BlankValueError()
+ if not isinstance(domain, str):
+ raise ValueError("Domain name must be a string")
+ domain = domain.lower().strip()
+ if domain == "":
+ if blank_ok:
+ return domain
+ else:
+ raise errors.BlankValueError()
+ if domain.endswith(".gov"):
+ domain = domain[:-4]
+ if "." in domain:
+ raise errors.ExtraDotsError()
+ if not DomainHelper.string_could_be_domain(domain + ".gov"):
+ raise ValueError()
+ if in_domains(domain):
+ raise errors.DomainUnavailableError()
+ return domain
+
+ @classmethod
+ def sld(cls, domain: str):
+ """
+ Get the second level domain. Example: `gsa.gov` -> `gsa`.
+
+ If no TLD is present, returns the original string.
+ """
+ return domain.split(".")[0]
+
+ @classmethod
+ def tld(cls, domain: str):
+ """Get the top level domain. Example: `gsa.gov` -> `gov`."""
+ parts = domain.rsplit(".")
+ return parts[-1] if len(parts) > 1 else ""
diff --git a/src/registrar/models/website.py b/src/registrar/models/website.py
index 65d86ddf1..d21564531 100644
--- a/src/registrar/models/website.py
+++ b/src/registrar/models/website.py
@@ -1,5 +1,3 @@
-from django.apps import apps
-from django.core.exceptions import ValidationError
from django.db import models
from .utility.time_stamped_model import TimeStampedModel
@@ -18,35 +16,5 @@ class Website(TimeStampedModel):
help_text="",
)
- @property
- def sld(self):
- """Get or set the second level domain string."""
- return self.website.split(".")[0]
-
- @sld.setter
- def sld(self, value: str):
- Domain = apps.get_model("registrar.Domain")
- parts = self.website.split(".")
- tld = parts[1] if len(parts) > 1 else ""
- if Domain.string_could_be_domain(f"{value}.{tld}"):
- self.website = f"{value}.{tld}"
- else:
- raise ValidationError("%s is not a valid second level domain" % value)
-
- @property
- def tld(self):
- """Get or set the top level domain string."""
- parts = self.website.split(".")
- return parts[1] if len(parts) > 1 else ""
-
- @tld.setter
- def tld(self, value: str):
- Domain = apps.get_model("registrar.Domain")
- sld = self.website.split(".")[0]
- if Domain.string_could_be_domain(f"{sld}.{value}"):
- self.website = f"{sld}.{value}"
- else:
- raise ValidationError("%s is not a valid top level domain" % value)
-
def __str__(self) -> str:
return str(self.website)
diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py
index 44cb565e2..4d7b22d01 100644
--- a/src/registrar/tests/test_emails.py
+++ b/src/registrar/tests/test_emails.py
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
from django.contrib.auth import get_user_model
from django.test import TestCase
-from registrar.models import Contact, Domain, Website, DomainApplication
+from registrar.models import Contact, DraftDomain, Website, DomainApplication
import boto3_mocking # type: ignore
@@ -28,7 +28,7 @@ class TestEmails(TestCase):
email="testy@town.com",
phone="(555) 555 5555",
)
- domain, _ = Domain.objects.get_or_create(name="city.gov")
+ domain, _ = DraftDomain.objects.get_or_create(name="city.gov")
alt, _ = Website.objects.get_or_create(website="city1.gov")
current, _ = Website.objects.get_or_create(website="city.com")
you, _ = Contact.objects.get_or_create(
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
index 2ebca68d7..97fe51a92 100644
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -8,6 +8,7 @@ from registrar.models import (
User,
Website,
Domain,
+ DraftDomain,
DomainInvitation,
UserDomainRole,
)
@@ -40,7 +41,7 @@ class TestDomainApplication(TestCase):
contact = Contact.objects.create()
com_website, _ = Website.objects.get_or_create(website="igorville.com")
gov_website, _ = Website.objects.get_or_create(website="igorville.gov")
- domain, _ = Domain.objects.get_or_create(name="igorville.gov")
+ domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
application = DomainApplication.objects.create(
creator=user,
investigator=user,
@@ -100,7 +101,7 @@ class TestDomainApplication(TestCase):
def test_status_fsm_submit_succeed(self):
user, _ = User.objects.get_or_create()
- site = Domain.objects.create(name="igorville.gov")
+ site = DraftDomain.objects.create(name="igorville.gov")
application = DomainApplication.objects.create(
creator=user, requested_domain=site
)
@@ -113,7 +114,7 @@ class TestDomainApplication(TestCase):
"""Create an application and submit it and see if email was sent."""
user, _ = User.objects.get_or_create()
contact = Contact.objects.create(email="test@test.gov")
- domain, _ = Domain.objects.get_or_create(name="igorville.gov")
+ domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
application = DomainApplication.objects.create(
creator=user,
requested_domain=domain,
@@ -135,62 +136,22 @@ class TestDomainApplication(TestCase):
)
-class TestDomain(TestCase):
- def test_empty_create_fails(self):
- """Can't create a completely empty domain."""
- with self.assertRaisesRegex(IntegrityError, "name"):
- Domain.objects.create()
-
- def test_minimal_create(self):
- """Can create with just a name."""
- domain = Domain.objects.create(name="igorville.gov")
- self.assertEqual(domain.is_active, False)
-
- @skip("cannot activate a domain without mock registry")
- def test_get_status(self):
- """Returns proper status based on `is_active`."""
- domain = Domain.objects.create(name="igorville.gov")
- domain.save()
- self.assertEqual(None, domain.status)
- domain.activate()
- domain.save()
- self.assertIn("ok", domain.status)
-
- def test_fsm_activate_fail_unique(self):
- """Can't activate domain if name is not unique."""
- d1, _ = Domain.objects.get_or_create(name="igorville.gov")
- d2, _ = Domain.objects.get_or_create(name="igorville.gov")
- d1.activate()
- d1.save()
- with self.assertRaises(ValueError):
- d2.activate()
-
- def test_fsm_activate_fail_unapproved(self):
- """Can't activate domain if application isn't approved."""
- d1, _ = Domain.objects.get_or_create(name="igorville.gov")
- user, _ = User.objects.get_or_create()
- application = DomainApplication.objects.create(creator=user)
- d1.domain_application = application
- d1.save()
- with self.assertRaises(ValueError):
- d1.activate()
-
-
class TestPermissions(TestCase):
"""Test the User-Domain-Role connection."""
def test_approval_creates_role(self):
- domain, _ = Domain.objects.get_or_create(name="igorville.gov")
+ draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(
- creator=user, requested_domain=domain
+ creator=user, requested_domain=draft_domain
)
# skip using the submit method
application.status = DomainApplication.SUBMITTED
application.approve()
# should be a role for this user
+ domain = Domain.objects.get(name="igorville.gov")
self.assertTrue(UserDomainRole.objects.get(user=user, domain=domain))
@@ -199,16 +160,17 @@ class TestDomainInfo(TestCase):
"""Test creation of Domain Information when approved."""
def test_approval_creates_info(self):
- domain, _ = Domain.objects.get_or_create(name="igorville.gov")
+ draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(
- creator=user, requested_domain=domain
+ creator=user, requested_domain=draft_domain
)
# skip using the submit method
application.status = DomainApplication.SUBMITTED
application.approve()
# should be an information present for this domain
+ domain = Domain.objects.get(name="igorville.gov")
self.assertTrue(DomainInformation.objects.get(domain=domain))
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
new file mode 100644
index 000000000..25b7be2d2
--- /dev/null
+++ b/src/registrar/tests/test_models_domain.py
@@ -0,0 +1,54 @@
+from django.test import TestCase
+from django.db.utils import IntegrityError
+
+from registrar.models import (
+ DomainApplication,
+ User,
+ Domain,
+)
+from unittest import skip
+
+
+class TestDomain(TestCase):
+ def test_empty_create_fails(self):
+ """Can't create a completely empty domain."""
+ with self.assertRaisesRegex(IntegrityError, "name"):
+ Domain.objects.create()
+
+ def test_minimal_create(self):
+ """Can create with just a name."""
+ Domain.objects.create(name="igorville.gov")
+ # this assertion will not work -- for now, the fact that the
+ # above command didn't error out is proof enough
+ # self.assertEquals(domain.state, Domain.State.DRAFTED)
+
+ @skip("cannot activate a domain without mock registry")
+ def test_get_status(self):
+ """Returns proper status based on `state`."""
+ domain = Domain.objects.create(name="igorville.gov")
+ domain.save()
+ self.assertEqual(None, domain.status)
+ domain.activate()
+ domain.save()
+ self.assertIn("ok", domain.status)
+
+ @skip("cannot activate a domain without mock registry")
+ def test_fsm_activate_fail_unique(self):
+ """Can't activate domain if name is not unique."""
+ d1, _ = Domain.objects.get_or_create(name="igorville.gov")
+ d2, _ = Domain.objects.get_or_create(name="igorville.gov")
+ d1.activate()
+ d1.save()
+ with self.assertRaises(ValueError):
+ d2.activate()
+
+ @skip("cannot activate a domain without mock registry")
+ def test_fsm_activate_fail_unapproved(self):
+ """Can't activate domain if application isn't approved."""
+ d1, _ = Domain.objects.get_or_create(name="igorville.gov")
+ user, _ = User.objects.get_or_create()
+ application = DomainApplication.objects.create(creator=user)
+ d1.domain_application = application
+ d1.save()
+ with self.assertRaises(ValueError):
+ d1.activate()
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index 928ba2b6f..b89575357 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -13,6 +13,7 @@ import boto3_mocking # type: ignore
from registrar.models import (
DomainApplication,
Domain,
+ DraftDomain,
DomainInvitation,
Contact,
Website,
@@ -75,7 +76,7 @@ class LoggedInTests(TestWithUser):
def test_home_lists_domain_applications(self):
response = self.client.get("/")
self.assertNotContains(response, "igorville.gov")
- site = Domain.objects.create(name="igorville.gov")
+ site = DraftDomain.objects.create(name="igorville.gov")
application = DomainApplication.objects.create(
creator=self.user, requested_domain=site
)
@@ -1035,6 +1036,8 @@ class TestWithDomainPermissions(TestWithUser):
def tearDown(self):
try:
+ if hasattr(self.domain, "contacts"):
+ self.domain.contacts.all().delete()
self.domain.delete()
self.role.delete()
except ValueError: # pass if already deleted
@@ -1364,7 +1367,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
email="testy@town.com",
phone="(555) 555 5555",
)
- domain, _ = Domain.objects.get_or_create(name="citystatus.gov")
+ domain, _ = DraftDomain.objects.get_or_create(name="citystatus.gov")
alt, _ = Website.objects.get_or_create(website="city1.gov")
current, _ = Website.objects.get_or_create(website="city.com")
you, _ = Contact.objects.get_or_create(