mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-05 09:21:54 +02:00
355 lines
12 KiB
Python
355 lines
12 KiB
Python
from __future__ import annotations
|
|
from django.db import transaction
|
|
|
|
from registrar.models.utility.domain_helper import DomainHelper
|
|
from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper
|
|
from .domain_request import DomainRequest
|
|
from .utility.time_stamped_model import TimeStampedModel
|
|
|
|
import logging
|
|
|
|
from django.db import models
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DomainInformation(TimeStampedModel):
|
|
"""A registrant's domain information for that domain, exported from
|
|
DomainRequest. We use these field from DomainRequest with few exceptions
|
|
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
|
management's user information are based on domain_request, but we cannot change
|
|
the domain request once approved, so copying them that way we can make changes
|
|
after its approved. Most fields here are copied from DomainRequest."""
|
|
|
|
StateTerritoryChoices = DomainRequest.StateTerritoryChoices
|
|
|
|
# use the short names in Django admin
|
|
OrganizationChoices = DomainRequest.OrganizationChoices
|
|
|
|
BranchChoices = DomainRequest.BranchChoices
|
|
|
|
federal_agency = models.ForeignKey(
|
|
"registrar.FederalAgency",
|
|
on_delete=models.PROTECT,
|
|
help_text="Associated federal agency",
|
|
unique=False,
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
|
|
# This is the domain request user who created this domain request. The contact
|
|
# information that they gave is in the `submitter` field
|
|
creator = models.ForeignKey(
|
|
"registrar.User",
|
|
on_delete=models.PROTECT,
|
|
related_name="information_created",
|
|
help_text="Person who submitted the domain request",
|
|
)
|
|
|
|
domain_request = models.OneToOneField(
|
|
"registrar.DomainRequest",
|
|
on_delete=models.PROTECT,
|
|
blank=True,
|
|
null=True,
|
|
related_name="DomainRequest_info",
|
|
help_text="Request associated with this domain",
|
|
unique=True,
|
|
)
|
|
|
|
# ##### data fields from the initial form #####
|
|
generic_org_type = models.CharField(
|
|
max_length=255,
|
|
choices=OrganizationChoices.choices,
|
|
null=True,
|
|
blank=True,
|
|
help_text="Type of organization",
|
|
)
|
|
|
|
# TODO - Ticket #1911: stub this data from DomainRequest
|
|
is_election_board = models.BooleanField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="election office",
|
|
)
|
|
|
|
# TODO - Ticket #1911: stub this data from DomainRequest
|
|
organization_type = models.CharField(
|
|
max_length=255,
|
|
choices=DomainRequest.OrgChoicesElectionOffice.choices,
|
|
null=True,
|
|
blank=True,
|
|
help_text='"Election" appears after the org type if it\'s an election office.',
|
|
)
|
|
|
|
federally_recognized_tribe = models.BooleanField(
|
|
null=True,
|
|
)
|
|
|
|
state_recognized_tribe = models.BooleanField(
|
|
null=True,
|
|
)
|
|
|
|
tribe_name = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
|
|
federal_type = models.CharField(
|
|
max_length=50,
|
|
choices=BranchChoices.choices,
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
|
|
is_election_board = models.BooleanField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="election office",
|
|
)
|
|
|
|
organization_name = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
db_index=True,
|
|
)
|
|
address_line1 = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="address line 1",
|
|
)
|
|
address_line2 = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="address line 2",
|
|
)
|
|
city = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
state_territory = models.CharField(
|
|
max_length=2,
|
|
choices=StateTerritoryChoices.choices,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="state / territory",
|
|
)
|
|
zipcode = models.CharField(
|
|
max_length=10,
|
|
null=True,
|
|
blank=True,
|
|
db_index=True,
|
|
verbose_name="zip code",
|
|
)
|
|
urbanization = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
help_text="Required for Puerto Rico only",
|
|
verbose_name="urbanization",
|
|
)
|
|
|
|
about_your_organization = models.TextField(
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
|
|
authorizing_official = models.ForeignKey(
|
|
"registrar.Contact",
|
|
null=True,
|
|
blank=True,
|
|
related_name="information_authorizing_official",
|
|
on_delete=models.PROTECT,
|
|
)
|
|
|
|
domain = models.OneToOneField(
|
|
"registrar.Domain",
|
|
on_delete=models.CASCADE,
|
|
blank=True,
|
|
null=True,
|
|
# Access this information via Domain as "domain.domain_info"
|
|
related_name="domain_info",
|
|
)
|
|
|
|
# This is the contact information provided by the domain requestor. The
|
|
# user who created the domain request is in the `creator` field.
|
|
submitter = models.ForeignKey(
|
|
"registrar.Contact",
|
|
null=True,
|
|
blank=True,
|
|
related_name="submitted_domain_requests_information",
|
|
on_delete=models.PROTECT,
|
|
help_text='Person listed under "your contact information" in the request form',
|
|
)
|
|
|
|
purpose = models.TextField(
|
|
null=True,
|
|
blank=True,
|
|
help_text="Purpose of your domain",
|
|
)
|
|
|
|
other_contacts = models.ManyToManyField(
|
|
"registrar.Contact",
|
|
blank=True,
|
|
related_name="contact_domain_requests_information",
|
|
verbose_name="Other employees",
|
|
)
|
|
|
|
no_other_contacts_rationale = models.TextField(
|
|
null=True,
|
|
blank=True,
|
|
help_text="Required if creator does not list other employees",
|
|
)
|
|
|
|
anything_else = models.TextField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="Additional details",
|
|
)
|
|
|
|
cisa_representative_email = models.EmailField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="CISA regional representative email",
|
|
max_length=320,
|
|
)
|
|
|
|
cisa_representative_first_name = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="CISA regional representative first name",
|
|
db_index=True,
|
|
)
|
|
|
|
cisa_representative_last_name = models.CharField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name="CISA regional representative last name",
|
|
db_index=True,
|
|
)
|
|
|
|
is_policy_acknowledged = models.BooleanField(
|
|
null=True,
|
|
blank=True,
|
|
help_text="Acknowledged .gov acceptable use policy",
|
|
)
|
|
|
|
notes = models.TextField(
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
|
|
def __str__(self):
|
|
try:
|
|
if self.domain and self.domain.name:
|
|
return self.domain.name
|
|
else:
|
|
return f"domain info set up and created by {self.creator}"
|
|
except Exception:
|
|
return ""
|
|
|
|
def sync_organization_type(self):
|
|
"""
|
|
Updates the organization_type (without saving) to match
|
|
the is_election_board and generic_organization_type fields.
|
|
"""
|
|
|
|
# Define mappings between generic org and election org.
|
|
# These have to be defined here, as you'd get a cyclical import error
|
|
# otherwise.
|
|
|
|
# For any given organization type, return the "_ELECTION" enum equivalent.
|
|
# For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION
|
|
generic_org_map = DomainRequest.OrgChoicesElectionOffice.get_org_generic_to_org_election()
|
|
|
|
# For any given "_election" variant, return the base org type.
|
|
# For example: STATE_OR_TERRITORY_ELECTION => STATE_OR_TERRITORY
|
|
election_org_map = DomainRequest.OrgChoicesElectionOffice.get_org_election_to_org_generic()
|
|
|
|
# Manages the "organization_type" variable and keeps in sync with
|
|
# "is_election_office" and "generic_organization_type"
|
|
org_type_helper = CreateOrUpdateOrganizationTypeHelper(
|
|
sender=self.__class__,
|
|
instance=self,
|
|
generic_org_to_org_map=generic_org_map,
|
|
election_org_to_generic_org_map=election_org_map,
|
|
)
|
|
|
|
# Actually updates the organization_type field
|
|
org_type_helper.create_or_update_organization_type()
|
|
|
|
return self
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""Save override for custom properties"""
|
|
self.sync_organization_type()
|
|
super().save(*args, **kwargs)
|
|
|
|
@classmethod
|
|
def create_from_da(cls, domain_request: DomainRequest, domain=None):
|
|
"""Takes in a DomainRequest and converts it into DomainInformation"""
|
|
|
|
# Throw an error if we get None - we can't create something from nothing
|
|
if domain_request is None:
|
|
raise ValueError("The provided DomainRequest is None")
|
|
|
|
# Throw an error if the da doesn't have an id
|
|
if not hasattr(domain_request, "id"):
|
|
raise ValueError("The provided DomainRequest has no id")
|
|
|
|
# check if we have a record that corresponds with the domain
|
|
# domain_request, if so short circuit the create
|
|
existing_domain_info = cls.objects.filter(domain_request__id=domain_request.id).first()
|
|
if existing_domain_info:
|
|
return existing_domain_info
|
|
|
|
# Get the fields that exist on both DomainRequest and DomainInformation
|
|
common_fields = DomainHelper.get_common_fields(DomainRequest, DomainInformation)
|
|
|
|
# Get a list of all many_to_many relations on DomainInformation (needs to be saved differently)
|
|
info_many_to_many_fields = DomainInformation._get_many_to_many_fields()
|
|
|
|
# Create a dictionary with only the common fields, and create a DomainInformation from it
|
|
da_dict = {}
|
|
da_many_to_many_dict = {}
|
|
for field in common_fields:
|
|
# If the field isn't many_to_many, populate the da_dict.
|
|
# If it is, populate da_many_to_many_dict as we need to save this later.
|
|
if hasattr(domain_request, field):
|
|
if field not in info_many_to_many_fields:
|
|
da_dict[field] = getattr(domain_request, field)
|
|
else:
|
|
da_many_to_many_dict[field] = getattr(domain_request, field).all()
|
|
|
|
# This will not happen in normal code flow, but having some redundancy doesn't hurt.
|
|
# da_dict should not have "id" under any circumstances.
|
|
# If it does have it, then this indicates that common_fields is overzealous in the data
|
|
# that it is returning. Try looking in DomainHelper.get_common_fields.
|
|
if "id" in da_dict:
|
|
logger.warning("create_from_da() -> Found attribute 'id' when trying to create")
|
|
da_dict.pop("id", None)
|
|
|
|
# Create a placeholder DomainInformation object
|
|
domain_info = DomainInformation(**da_dict)
|
|
|
|
# Add the domain_request and domain fields
|
|
domain_info.domain_request = domain_request
|
|
if domain:
|
|
domain_info.domain = domain
|
|
|
|
# Save the instance and set the many-to-many fields.
|
|
# Lumped under .atomic to ensure we don't make redundant DB calls.
|
|
# This bundles them all together, and then saves it in a single call.
|
|
with transaction.atomic():
|
|
domain_info.save()
|
|
for field, value in da_many_to_many_dict.items():
|
|
getattr(domain_info, field).set(value)
|
|
|
|
return domain_info
|
|
|
|
@staticmethod
|
|
def _get_many_to_many_fields():
|
|
"""Returns a set of each field.name that has the many to many relation"""
|
|
return {field.name for field in DomainInformation._meta.many_to_many} # type: ignore
|
|
|
|
class Meta:
|
|
verbose_name_plural = "Domain information"
|