mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-16 17:47:02 +02:00
Create a DraftDomain model for requested domains
This commit is contained in:
parent
93427ad072
commit
7a3e1bcb2c
15 changed files with 272 additions and 124 deletions
|
@ -37,7 +37,7 @@ def _domains():
|
||||||
Fetch a file from DOMAIN_FILE_URL, parse the CSV for the domain,
|
Fetch a file from DOMAIN_FILE_URL, parse the CSV for the domain,
|
||||||
lowercase everything and return the list.
|
lowercase everything and return the list.
|
||||||
"""
|
"""
|
||||||
Domain = apps.get_model("registrar.Domain")
|
DraftDomain = apps.get_model("registrar.DraftDomain")
|
||||||
# 5 second timeout
|
# 5 second timeout
|
||||||
file_contents = requests.get(DOMAIN_FILE_URL, timeout=5).text
|
file_contents = requests.get(DOMAIN_FILE_URL, timeout=5).text
|
||||||
domains = set()
|
domains = set()
|
||||||
|
@ -46,7 +46,7 @@ def _domains():
|
||||||
# get the domain before the first comma
|
# get the domain before the first comma
|
||||||
domain = line.split(",", 1)[0]
|
domain = line.split(",", 1)[0]
|
||||||
# sanity-check the string we got from the file here
|
# 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
|
# lowercase everything when we put it in domains
|
||||||
domains.add(domain.lower())
|
domains.add(domain.lower())
|
||||||
return domains
|
return domains
|
||||||
|
@ -75,12 +75,12 @@ def available(request, domain=""):
|
||||||
Response is a JSON dictionary with the key "available" and value true or
|
Response is a JSON dictionary with the key "available" and value true or
|
||||||
false.
|
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
|
# validate that the given domain could be a domain name and fail early if
|
||||||
# not.
|
# not.
|
||||||
if not (
|
if not (
|
||||||
Domain.string_could_be_domain(domain)
|
DraftDomain.string_could_be_domain(domain)
|
||||||
or Domain.string_could_be_domain(domain + ".gov")
|
or DraftDomain.string_could_be_domain(domain + ".gov")
|
||||||
):
|
):
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
|
{"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
|
||||||
|
|
|
@ -5,7 +5,7 @@ from faker import Faker
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
User,
|
User,
|
||||||
DomainApplication,
|
DomainApplication,
|
||||||
Domain,
|
DraftDomain,
|
||||||
Contact,
|
Contact,
|
||||||
Website,
|
Website,
|
||||||
)
|
)
|
||||||
|
@ -216,11 +216,13 @@ class DomainApplicationFixture:
|
||||||
|
|
||||||
if not da.requested_domain:
|
if not da.requested_domain:
|
||||||
if "requested_domain" in app and app["requested_domain"] is not None:
|
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"]
|
name=app["requested_domain"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
da.requested_domain = Domain.objects.create(name=cls.fake_dot_gov())
|
da.requested_domain = DraftDomain.objects.create(
|
||||||
|
name=cls.fake_dot_gov()
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
|
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
|
||||||
|
|
|
@ -11,7 +11,7 @@ from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from api.views import DOMAIN_API_MESSAGES
|
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
|
from registrar.utility import errors
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -453,7 +453,7 @@ class AlternativeDomainForm(RegistrarForm):
|
||||||
"""Validation code for domain names."""
|
"""Validation code for domain names."""
|
||||||
try:
|
try:
|
||||||
requested = self.cleaned_data.get("alternative_domain", None)
|
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:
|
except errors.ExtraDotsError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots"
|
DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots"
|
||||||
|
@ -498,7 +498,7 @@ class BaseAlternativeDomainFormSet(RegistrarFormSet):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def on_fetch(cls, query):
|
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
|
@classmethod
|
||||||
def from_database(cls, obj):
|
def from_database(cls, obj):
|
||||||
|
@ -524,7 +524,7 @@ class DotGovDomainForm(RegistrarForm):
|
||||||
requested_domain.name = f"{domain}.gov"
|
requested_domain.name = f"{domain}.gov"
|
||||||
requested_domain.save()
|
requested_domain.save()
|
||||||
else:
|
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.requested_domain = requested_domain
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
|
@ -535,14 +535,14 @@ class DotGovDomainForm(RegistrarForm):
|
||||||
values = {}
|
values = {}
|
||||||
requested_domain = getattr(obj, "requested_domain", None)
|
requested_domain = getattr(obj, "requested_domain", None)
|
||||||
if requested_domain is not None:
|
if requested_domain is not None:
|
||||||
values["requested_domain"] = requested_domain.sld
|
values["requested_domain"] = Domain.sld(requested_domain.name)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def clean_requested_domain(self):
|
def clean_requested_domain(self):
|
||||||
"""Validation code for domain names."""
|
"""Validation code for domain names."""
|
||||||
try:
|
try:
|
||||||
requested = self.cleaned_data.get("requested_domain", None)
|
requested = self.cleaned_data.get("requested_domain", None)
|
||||||
validated = Domain.validate(requested)
|
validated = DraftDomain.validate(requested)
|
||||||
except errors.BlankValueError:
|
except errors.BlankValueError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
DOMAIN_API_MESSAGES["required"], code="required"
|
DOMAIN_API_MESSAGES["required"], code="required"
|
||||||
|
|
|
@ -54,16 +54,6 @@ class Command(BaseCommand):
|
||||||
domains = []
|
domains = []
|
||||||
for row in reader:
|
for row in reader:
|
||||||
name = row["Name"].lower() # we typically use lowercase domains
|
name = row["Name"].lower() # we typically use lowercase domains
|
||||||
|
domains.append(Domain(name=name))
|
||||||
# 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))
|
|
||||||
logger.info("Creating %d new domains", len(domains))
|
logger.info("Creating %d new domains", len(domains))
|
||||||
Domain.objects.bulk_create(domains)
|
Domain.objects.bulk_create(domains)
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
|
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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,6 +4,7 @@ from .contact import Contact
|
||||||
from .domain_application import DomainApplication
|
from .domain_application import DomainApplication
|
||||||
from .domain_information import DomainInformation
|
from .domain_information import DomainInformation
|
||||||
from .domain import Domain
|
from .domain import Domain
|
||||||
|
from .draft_domain import DraftDomain
|
||||||
from .host_ip import HostIP
|
from .host_ip import HostIP
|
||||||
from .host import Host
|
from .host import Host
|
||||||
from .domain_invitation import DomainInvitation
|
from .domain_invitation import DomainInvitation
|
||||||
|
@ -18,6 +19,7 @@ __all__ = [
|
||||||
"DomainApplication",
|
"DomainApplication",
|
||||||
"DomainInformation",
|
"DomainInformation",
|
||||||
"Domain",
|
"Domain",
|
||||||
|
"DraftDomain",
|
||||||
"DomainInvitation",
|
"DomainInvitation",
|
||||||
"HostIP",
|
"HostIP",
|
||||||
"Host",
|
"Host",
|
||||||
|
@ -31,6 +33,7 @@ __all__ = [
|
||||||
auditlog.register(Contact)
|
auditlog.register(Contact)
|
||||||
auditlog.register(DomainApplication)
|
auditlog.register(DomainApplication)
|
||||||
auditlog.register(Domain)
|
auditlog.register(Domain)
|
||||||
|
auditlog.register(DraftDomain)
|
||||||
auditlog.register(DomainInvitation)
|
auditlog.register(DomainInvitation)
|
||||||
auditlog.register(HostIP)
|
auditlog.register(HostIP)
|
||||||
auditlog.register(Host)
|
auditlog.register(Host)
|
||||||
|
|
|
@ -400,10 +400,19 @@ class DomainApplication(TimeStampedModel):
|
||||||
related_name="current+",
|
related_name="current+",
|
||||||
)
|
)
|
||||||
|
|
||||||
requested_domain = models.OneToOneField(
|
approved_domain = models.OneToOneField(
|
||||||
"Domain",
|
"Domain",
|
||||||
null=True,
|
null=True,
|
||||||
blank=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",
|
help_text="The requested domain",
|
||||||
related_name="domain_application",
|
related_name="domain_application",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -499,8 +508,8 @@ class DomainApplication(TimeStampedModel):
|
||||||
if self.requested_domain is None:
|
if self.requested_domain is None:
|
||||||
raise ValueError("Requested domain is missing.")
|
raise ValueError("Requested domain is missing.")
|
||||||
|
|
||||||
Domain = apps.get_model("registrar.Domain")
|
DraftDomain = apps.get_model("registrar.DraftDomain")
|
||||||
if not Domain.string_could_be_domain(self.requested_domain.name):
|
if not DraftDomain.string_could_be_domain(self.requested_domain.name):
|
||||||
raise ValueError("Requested domain is not a valid domain name.")
|
raise ValueError("Requested domain is not a valid domain name.")
|
||||||
|
|
||||||
# When an application is submitted, we need to send a confirmation email
|
# 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.
|
application into an admin on that domain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# create the domain if it doesn't exist
|
# create the domain
|
||||||
Domain = apps.get_model("registrar.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
|
# copy the information from domainapplication into domaininformation
|
||||||
DomainInformation = apps.get_model("registrar.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
|
# create the permission for the user
|
||||||
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
||||||
|
|
|
@ -211,24 +211,24 @@ class DomainInformation(TimeStampedModel):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@classmethod
|
@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"""
|
"""Takes in a DomainApplication dict and converts it into DomainInformation"""
|
||||||
da_dict = domain_application.to_dict()
|
da_dict = domain_application.to_dict()
|
||||||
# remove the id so one can be assinged on creation
|
# 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
|
# check if we have a record that corresponds with the domain
|
||||||
# application, if so short circuit the create
|
# application, if so short circuit the create
|
||||||
domain_info = cls.objects.filter(domain_application__id=da_id).first()
|
domain_info = cls.objects.filter(domain_application__id=da_id).first()
|
||||||
if domain_info:
|
if domain_info:
|
||||||
return domain_info
|
return domain_info
|
||||||
# the following information below is not needed in the domain information:
|
# the following information below is not needed in the domain information:
|
||||||
da_dict.pop("status")
|
da_dict.pop("status", None)
|
||||||
da_dict.pop("current_websites")
|
da_dict.pop("current_websites", None)
|
||||||
da_dict.pop("investigator")
|
da_dict.pop("investigator", None)
|
||||||
da_dict.pop("alternative_domains")
|
da_dict.pop("alternative_domains", None)
|
||||||
# use the requested_domain to create information for this domain
|
da_dict.pop("requested_domain", None)
|
||||||
da_dict["domain"] = da_dict.pop("requested_domain")
|
da_dict.pop("approved_domain", None)
|
||||||
other_contacts = da_dict.pop("other_contacts")
|
other_contacts = da_dict.pop("other_contacts", [])
|
||||||
domain_info = cls(**da_dict)
|
domain_info = cls(**da_dict)
|
||||||
domain_info.domain_application = domain_application
|
domain_info.domain_application = domain_application
|
||||||
# Save so the object now have PK
|
# Save so the object now have PK
|
||||||
|
@ -237,6 +237,8 @@ class DomainInformation(TimeStampedModel):
|
||||||
|
|
||||||
# Process the remaining "many to many" stuff
|
# Process the remaining "many to many" stuff
|
||||||
domain_info.other_contacts.add(*other_contacts)
|
domain_info.other_contacts.add(*other_contacts)
|
||||||
|
if domain:
|
||||||
|
domain_info.domain = domain
|
||||||
domain_info.save()
|
domain_info.save()
|
||||||
return domain_info
|
return domain_info
|
||||||
|
|
||||||
|
|
22
src/registrar/models/draft_domain.py
Normal file
22
src/registrar/models/draft_domain.py
Normal file
|
@ -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",
|
||||||
|
)
|
64
src/registrar/models/utility/domain_helper.py
Normal file
64
src/registrar/models/utility/domain_helper.py
Normal file
|
@ -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}(?<!-)\.[A-Za-z]{2,6}$")
|
||||||
|
|
||||||
|
# a domain name is alphanumeric or hyphen, has at least 2 dots, doesn't
|
||||||
|
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
|
||||||
|
HOST_REGEX = re.compile(r"^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.){2,}([A-Za-z]){2,6}$")
|
||||||
|
|
||||||
|
# a domain can be no longer than 253 characters in total
|
||||||
|
MAX_LENGTH = 253
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def string_could_be_domain(cls, domain: str | None) -> 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 ""
|
|
@ -1,5 +1,3 @@
|
||||||
from django.apps import apps
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
@ -18,35 +16,5 @@ class Website(TimeStampedModel):
|
||||||
help_text="",
|
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:
|
def __str__(self) -> str:
|
||||||
return str(self.website)
|
return str(self.website)
|
||||||
|
|
|
@ -5,7 +5,7 @@ from unittest.mock import MagicMock
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import TestCase
|
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
|
import boto3_mocking # type: ignore
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class TestEmails(TestCase):
|
||||||
email="testy@town.com",
|
email="testy@town.com",
|
||||||
phone="(555) 555 5555",
|
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")
|
alt, _ = Website.objects.get_or_create(website="city1.gov")
|
||||||
current, _ = Website.objects.get_or_create(website="city.com")
|
current, _ = Website.objects.get_or_create(website="city.com")
|
||||||
you, _ = Contact.objects.get_or_create(
|
you, _ = Contact.objects.get_or_create(
|
||||||
|
|
|
@ -8,6 +8,7 @@ from registrar.models import (
|
||||||
User,
|
User,
|
||||||
Website,
|
Website,
|
||||||
Domain,
|
Domain,
|
||||||
|
DraftDomain,
|
||||||
DomainInvitation,
|
DomainInvitation,
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
)
|
)
|
||||||
|
@ -40,7 +41,7 @@ class TestDomainApplication(TestCase):
|
||||||
contact = Contact.objects.create()
|
contact = Contact.objects.create()
|
||||||
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
||||||
gov_website, _ = Website.objects.get_or_create(website="igorville.gov")
|
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(
|
application = DomainApplication.objects.create(
|
||||||
creator=user,
|
creator=user,
|
||||||
investigator=user,
|
investigator=user,
|
||||||
|
@ -100,7 +101,7 @@ class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
def test_status_fsm_submit_succeed(self):
|
def test_status_fsm_submit_succeed(self):
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
site = Domain.objects.create(name="igorville.gov")
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(
|
||||||
creator=user, requested_domain=site
|
creator=user, requested_domain=site
|
||||||
)
|
)
|
||||||
|
@ -113,7 +114,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application and submit it and see if email was sent."""
|
"""Create an application and submit it and see if email was sent."""
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
contact = Contact.objects.create(email="test@test.gov")
|
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(
|
application = DomainApplication.objects.create(
|
||||||
creator=user,
|
creator=user,
|
||||||
requested_domain=domain,
|
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):
|
class TestPermissions(TestCase):
|
||||||
|
|
||||||
"""Test the User-Domain-Role connection."""
|
"""Test the User-Domain-Role connection."""
|
||||||
|
|
||||||
def test_approval_creates_role(self):
|
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()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(
|
||||||
creator=user, requested_domain=domain
|
creator=user, requested_domain=draft_domain
|
||||||
)
|
)
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
|
||||||
# should be a role for this user
|
# should be a role for this user
|
||||||
|
domain = Domain.objects.get(name="igorville.gov")
|
||||||
self.assertTrue(UserDomainRole.objects.get(user=user, domain=domain))
|
self.assertTrue(UserDomainRole.objects.get(user=user, domain=domain))
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,16 +160,17 @@ class TestDomainInfo(TestCase):
|
||||||
"""Test creation of Domain Information when approved."""
|
"""Test creation of Domain Information when approved."""
|
||||||
|
|
||||||
def test_approval_creates_info(self):
|
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()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(
|
||||||
creator=user, requested_domain=domain
|
creator=user, requested_domain=draft_domain
|
||||||
)
|
)
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
|
||||||
# should be an information present for this domain
|
# should be an information present for this domain
|
||||||
|
domain = Domain.objects.get(name="igorville.gov")
|
||||||
self.assertTrue(DomainInformation.objects.get(domain=domain))
|
self.assertTrue(DomainInformation.objects.get(domain=domain))
|
||||||
|
|
||||||
|
|
||||||
|
|
54
src/registrar/tests/test_models_domain.py
Normal file
54
src/registrar/tests/test_models_domain.py
Normal file
|
@ -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()
|
|
@ -13,6 +13,7 @@ import boto3_mocking # type: ignore
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
DomainApplication,
|
DomainApplication,
|
||||||
Domain,
|
Domain,
|
||||||
|
DraftDomain,
|
||||||
DomainInvitation,
|
DomainInvitation,
|
||||||
Contact,
|
Contact,
|
||||||
Website,
|
Website,
|
||||||
|
@ -75,7 +76,7 @@ class LoggedInTests(TestWithUser):
|
||||||
def test_home_lists_domain_applications(self):
|
def test_home_lists_domain_applications(self):
|
||||||
response = self.client.get("/")
|
response = self.client.get("/")
|
||||||
self.assertNotContains(response, "igorville.gov")
|
self.assertNotContains(response, "igorville.gov")
|
||||||
site = Domain.objects.create(name="igorville.gov")
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(
|
||||||
creator=self.user, requested_domain=site
|
creator=self.user, requested_domain=site
|
||||||
)
|
)
|
||||||
|
@ -1035,6 +1036,8 @@ class TestWithDomainPermissions(TestWithUser):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
try:
|
try:
|
||||||
|
if hasattr(self.domain, "contacts"):
|
||||||
|
self.domain.contacts.all().delete()
|
||||||
self.domain.delete()
|
self.domain.delete()
|
||||||
self.role.delete()
|
self.role.delete()
|
||||||
except ValueError: # pass if already deleted
|
except ValueError: # pass if already deleted
|
||||||
|
@ -1347,7 +1350,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
email="testy@town.com",
|
email="testy@town.com",
|
||||||
phone="(555) 555 5555",
|
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")
|
alt, _ = Website.objects.get_or_create(website="city1.gov")
|
||||||
current, _ = Website.objects.get_or_create(website="city.com")
|
current, _ = Website.objects.get_or_create(website="city.com")
|
||||||
you, _ = Contact.objects.get_or_create(
|
you, _ = Contact.objects.get_or_create(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue