diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 30c8e8b89..439dfd9f9 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -55,6 +55,7 @@ admin.site.register(models.UserDomainRole, AuditedAdmin) admin.site.register(models.Contact, AuditedAdmin) admin.site.register(models.DomainInvitation, AuditedAdmin) admin.site.register(models.DomainApplication, AuditedAdmin) +admin.site.register(models.DomainInformation, AuditedAdmin) admin.site.register(models.Domain, AuditedAdmin) admin.site.register(models.Host, MyHostAdmin) admin.site.register(models.Nameserver, MyHostAdmin) diff --git a/src/registrar/migrations/0018_domaininformation.py b/src/registrar/migrations/0018_domaininformation.py index ea91de52a..c85943990 100644 --- a/src/registrar/migrations/0018_domaininformation.py +++ b/src/registrar/migrations/0018_domaininformation.py @@ -1,5 +1,6 @@ -# Generated by Django 4.1.6 on 2023-05-02 19:38 +# Generated by Django 4.1.6 on 2023-05-08 12:48 +from django.conf import settings from django.db import migrations, models import django.db.models.deletion @@ -14,14 +15,187 @@ class Migration(migrations.Migration): name="DomainInformation", fields=[ ( - "domainapplication_ptr", - models.OneToOneField( + "id", + models.BigAutoField( auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, primary_key=True, serialize=False, - to="registrar.domainapplication", + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "organization_type", + models.CharField( + blank=True, + choices=[ + ( + "federal", + "Federal: an agency of the U.S. government's executive, legislative, or judicial branches", + ), + ( + "interstate", + "Interstate: an organization of two or more states", + ), + ( + "state_or_territory", + "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", + "Tribal: a tribal government recognized by the federal or a state government", + ), + ("county", "County: a county, parish, or borough"), + ("city", "City: a city, town, township, village, etc."), + ( + "special_district", + "Special district: an independent organization within a single state", + ), + ( + "school_district", + "School district: a school district that is not part of a local government", + ), + ], + help_text="Type of Organization", + max_length=255, + null=True, + ), + ), + ( + "federally_recognized_tribe", + models.BooleanField( + help_text="Is the tribe federally recognized", null=True + ), + ), + ( + "state_recognized_tribe", + models.BooleanField( + help_text="Is the tribe recognized by a state", null=True + ), + ), + ( + "tribe_name", + models.TextField(blank=True, help_text="Name of tribe", null=True), + ), + ( + "federal_agency", + models.TextField(blank=True, help_text="Federal agency", null=True), + ), + ( + "federal_type", + models.CharField( + blank=True, + choices=[ + ("executive", "Executive"), + ("judicial", "Judicial"), + ("legislative", "Legislative"), + ], + help_text="Federal government branch", + max_length=50, + null=True, + ), + ), + ( + "is_election_board", + models.BooleanField( + blank=True, + help_text="Is your organization an election office?", + null=True, + ), + ), + ( + "organization_name", + models.TextField( + blank=True, + db_index=True, + help_text="Organization name", + null=True, + ), + ), + ( + "address_line1", + models.TextField(blank=True, help_text="Street address", null=True), + ), + ( + "address_line2", + models.CharField( + blank=True, + help_text="Street address line 2", + max_length=15, + null=True, + ), + ), + ("city", models.TextField(blank=True, help_text="City", null=True)), + ( + "state_territory", + models.CharField( + blank=True, + help_text="State, territory, or military post", + max_length=2, + null=True, + ), + ), + ( + "zipcode", + models.CharField( + blank=True, + db_index=True, + help_text="Zip code", + max_length=10, + null=True, + ), + ), + ( + "urbanization", + models.TextField( + blank=True, + help_text="Urbanization (Puerto Rico only)", + null=True, + ), + ), + ( + "type_of_work", + models.TextField( + blank=True, + help_text="Type of work of the organization", + null=True, + ), + ), + ( + "more_organization_information", + models.TextField( + blank=True, + help_text="Further information about the government organization", + null=True, + ), + ), + ( + "purpose", + models.TextField( + blank=True, help_text="Purpose of your domain", null=True + ), + ), + ( + "no_other_contacts_rationale", + models.TextField( + blank=True, + help_text="Reason for listing no additional contacts", + null=True, + ), + ), + ( + "anything_else", + models.TextField( + blank=True, help_text="Anything else we should know?", null=True + ), + ), + ( + "is_policy_acknowledged", + models.BooleanField( + blank=True, + help_text="Acknowledged .gov acceptable use policy", + null=True, ), ), ( @@ -33,10 +207,72 @@ class Migration(migrations.Migration): 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="information_authorizing_official", + to="registrar.contact", + ), + ), + ( + "creator", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="information_created", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "domain", + models.ForeignKey( + blank=True, + help_text="Domain to which this information belongs", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="domain_info", + to="registrar.domain", + ), + ), + ( + "investigator", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="information_investigating", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "other_contacts", + models.ManyToManyField( + blank=True, + related_name="contact_applications_information", + to="registrar.contact", + ), + ), + ( + "submitter", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="submitted_applications_information", + to="registrar.contact", + ), + ), ], options={ - "abstract": False, + "verbose_name_plural": "Domain Information", }, - bases=("registrar.domainapplication",), ), ] diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 898615e27..5e50c88e9 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -9,11 +9,10 @@ from django_fsm import FSMField, transition # type: ignore from .utility.time_stamped_model import TimeStampedModel from ..utility.email import send_templated_email, EmailSendingError - +from itertools import chain logger = logging.getLogger(__name__) - class DomainApplication(TimeStampedModel): """A registrant's application for a new domain.""" @@ -520,6 +519,16 @@ class DomainApplication(TimeStampedModel): Domain = apps.get_model("registrar.Domain") created_domain, _ = Domain.objects.get_or_create(name=self.requested_domain) + + + # copy the information from domainapplication into domaininformation + DomainInformation = apps.get_model("registrar.DomainInformation") + domain_info = self.to_dict() + # remove PK from domainapplication as it use different PK + # for domain/domaininformation + + domain_info, _ = DomainInformation.create_from_da_dict(domain_info) + # create the permission for the user UserDomainRole = apps.get_model("registrar.UserDomainRole") UserDomainRole.objects.get_or_create( @@ -577,3 +586,24 @@ class DomainApplication(TimeStampedModel): if self.organization_type == DomainApplication.OrganizationChoices.FEDERAL: return True return False + + def to_dict(instance): + """This is to process to_dict for Domain Information, making it friendly to "copy" it """ + opts = instance._meta + data = {} + for field in chain(opts.concrete_fields, opts.private_fields): + # import pdb; pdb.set_trace() + if field.get_internal_type() in ("ForeignKey", "OneToOneField"): + # get the related instance of the FK value + print(f"{field.name}: ID: {field.value_from_object(instance)}") + fk_id = field.value_from_object(instance) + if fk_id: + data[field.name] = field.related_model.objects.get(id=fk_id) + else: + data[field.name] = None + else: + data[field.name] = field.value_from_object(instance) + for field in opts.many_to_many: + data[field.name] = field.value_from_object(instance) + return data + diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py index 16a0f6236..b6adc6c52 100644 --- a/src/registrar/models/domain_information.py +++ b/src/registrar/models/domain_information.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Union from .domain_application import DomainApplication +from .utility.time_stamped_model import TimeStampedModel import logging @@ -10,11 +11,458 @@ from django.db import models logger = logging.getLogger(__name__) -class DomainInformation(DomainApplication): +class DomainInformation(TimeStampedModel): + + """A registrant's application for a new domain.""" + + class StateTerritoryChoices(models.TextChoices): + ALABAMA = "AL", "Alabama (AL)" + ALASKA = "AK", "Alaska (AK)" + AMERICAN_SAMOA = "AS", "American Samoa (AS)" + ARIZONA = "AZ", "Arizona (AZ)" + ARKANSAS = "AR", "Arkansas (AR)" + CALIFORNIA = "CA", "California (CA)" + COLORADO = "CO", "Colorado (CO)" + CONNECTICUT = "CT", "Connecticut (CT)" + DELAWARE = "DE", "Delaware (DE)" + DISTRICT_OF_COLUMBIA = "DC", "District of Columbia (DC)" + FLORIDA = "FL", "Florida (FL)" + GEORGIA = "GA", "Georgia (GA)" + GUAM = "GU", "Guam (GU)" + HAWAII = "HI", "Hawaii (HI)" + IDAHO = "ID", "Idaho (ID)" + ILLINOIS = "IL", "Illinois (IL)" + INDIANA = "IN", "Indiana (IN)" + IOWA = "IA", "Iowa (IA)" + KANSAS = "KS", "Kansas (KS)" + KENTUCKY = "KY", "Kentucky (KY)" + LOUISIANA = "LA", "Louisiana (LA)" + MAINE = "ME", "Maine (ME)" + MARYLAND = "MD", "Maryland (MD)" + MASSACHUSETTS = "MA", "Massachusetts (MA)" + MICHIGAN = "MI", "Michigan (MI)" + MINNESOTA = "MN", "Minnesota (MN)" + MISSISSIPPI = "MS", "Mississippi (MS)" + MISSOURI = "MO", "Missouri (MO)" + MONTANA = "MT", "Montana (MT)" + NEBRASKA = "NE", "Nebraska (NE)" + NEVADA = "NV", "Nevada (NV)" + NEW_HAMPSHIRE = "NH", "New Hampshire (NH)" + NEW_JERSEY = "NJ", "New Jersey (NJ)" + NEW_MEXICO = "NM", "New Mexico (NM)" + NEW_YORK = "NY", "New York (NY)" + NORTH_CAROLINA = "NC", "North Carolina (NC)" + NORTH_DAKOTA = "ND", "North Dakota (ND)" + NORTHERN_MARIANA_ISLANDS = "MP", "Northern Mariana Islands (MP)" + OHIO = "OH", "Ohio (OH)" + OKLAHOMA = "OK", "Oklahoma (OK)" + OREGON = "OR", "Oregon (OR)" + PENNSYLVANIA = "PA", "Pennsylvania (PA)" + PUERTO_RICO = "PR", "Puerto Rico (PR)" + RHODE_ISLAND = "RI", "Rhode Island (RI)" + SOUTH_CAROLINA = "SC", "South Carolina (SC)" + SOUTH_DAKOTA = "SD", "South Dakota (SD)" + TENNESSEE = "TN", "Tennessee (TN)" + TEXAS = "TX", "Texas (TX)" + UNITED_STATES_MINOR_OUTLYING_ISLANDS = ( + "UM", + "United States Minor Outlying Islands (UM)", + ) + UTAH = "UT", "Utah (UT)" + VERMONT = "VT", "Vermont (VT)" + VIRGIN_ISLANDS = "VI", "Virgin Islands (VI)" + VIRGINIA = "VA", "Virginia (VA)" + WASHINGTON = "WA", "Washington (WA)" + WEST_VIRGINIA = "WV", "West Virginia (WV)" + WISCONSIN = "WI", "Wisconsin (WI)" + WYOMING = "WY", "Wyoming (WY)" + ARMED_FORCES_AA = "AA", "Armed Forces Americas (AA)" + ARMED_FORCES_AE = "AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)" + ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)" + + class OrganizationChoices(models.TextChoices): + FEDERAL = ( + "federal", + "Federal: an agency of the U.S. government's executive, legislative, " + "or judicial branches", + ) + INTERSTATE = "interstate", "Interstate: an organization of two or more states" + STATE_OR_TERRITORY = "state_or_territory", ( + "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 = "tribal", ( + "Tribal: a tribal government recognized by the federal or " + "a state government" + ) + COUNTY = "county", "County: a county, parish, or borough" + CITY = "city", "City: a city, town, township, village, etc." + SPECIAL_DISTRICT = "special_district", ( + "Special district: an independent organization within a single state" + ) + SCHOOL_DISTRICT = "school_district", ( + "School district: a school district that is not part of a local government" + ) + + class BranchChoices(models.TextChoices): + EXECUTIVE = "executive", "Executive" + JUDICIAL = "judicial", "Judicial" + LEGISLATIVE = "legislative", "Legislative" + + AGENCIES = [ + "Administrative Conference of the United States", + "Advisory Council on Historic Preservation", + "American Battle Monuments Commission", + "Appalachian Regional Commission", + ( + "Appraisal Subcommittee of the Federal Financial " + "Institutions Examination Council" + ), + "Armed Forces Retirement Home", + "Barry Goldwater Scholarship and Excellence in Education Program", + "Central Intelligence Agency", + "Christopher Columbus Fellowship Foundation", + "Commission for the Preservation of America's Heritage Abroad", + "Commission of Fine Arts", + "Committee for Purchase From People Who Are Blind or Severely Disabled", + "Commodity Futures Trading Commission", + "Consumer Financial Protection Bureau", + "Consumer Product Safety Commission", + "Corporation for National and Community Service", + "Council of Inspectors General on Integrity and Efficiency", + "DC Court Services and Offender Supervision Agency", + "DC Pre-trial Services", + "Defense Nuclear Facilities Safety Board", + "Delta Regional Authority", + "Denali Commission", + "Department of Agriculture", + "Department of Commerce", + "Department of Defense", + "Department of Education", + "Department of Energy", + "Department of Health and Human Services", + "Department of Homeland Security", + "Department of Housing and Urban Development", + "Department of Justice", + "Department of Labor", + "Department of State", + "Department of the Interior", + "Department of the Treasury", + "Department of Transportation", + "Department of Veterans Affairs", + "Director of National Intelligence", + "Dwight D. Eisenhower Memorial Commission", + "Election Assistance Commission", + "Environmental Protection Agency", + "Equal Employment Opportunity Commission", + "Export-Import Bank of the United States", + "Farm Credit Administration", + "Farm Credit System Insurance Corporation", + "Federal Communications Commission", + "Federal Deposit Insurance Corporation", + "Federal Election Commission", + "Federal Financial Institutions Examination Council", + "Federal Housing Finance Agency", + "Federal Judiciary", + "Federal Labor Relations Authority", + "Federal Maritime Commission", + "Federal Mediation and Conciliation Service", + "Federal Mine Safety and Health Review Commission", + "Federal Reserve System", + "Federal Trade Commission", + "General Services Administration", + "Gulf Coast Ecosystem Restoration Council", + "Harry S Truman Scholarship Foundation", + "Institute of Peace", + "Inter-American Foundation", + "International Boundary and Water Commission: United States and Mexico", + "International Boundary Commission: United States and Canada", + "International Joint Commission: United States and Canada", + "James Madison Memorial Fellowship Foundation", + "Japan-United States Friendship Commission", + "John F. Kennedy Center for the Performing Arts", + "Legal Services Corporation", + "Legislative Branch", + "Marine Mammal Commission", + "Medicare Payment Advisory Commission", + "Merit Systems Protection Board", + "Millennium Challenge Corporation", + "National Aeronautics and Space Administration", + "National Archives and Records Administration", + "National Capital Planning Commission", + "National Council on Disability", + "National Credit Union Administration", + "National Foundation on the Arts and the Humanities", + "National Gallery of Art", + "National Labor Relations Board", + "National Mediation Board", + "National Science Foundation", + "National Transportation Safety Board", + "Northern Border Regional Commission", + "Nuclear Regulatory Commission", + "Nuclear Safety Oversight Committee", + "Nuclear Waste Technical Review Board", + "Occupational Safety and Health Review Commission", + "Office of Compliance", + "Office of Government Ethics", + "Office of Navajo and Hopi Indian Relocation", + "Office of Personnel Management", + "Overseas Private Investment Corporation", + "Peace Corps", + "Pension Benefit Guaranty Corporation", + "Postal Regulatory Commission", + "Privacy and Civil Liberties Oversight Board", + "Public Defender Service for the District of Columbia", + "Railroad Retirement Board", + "Securities and Exchange Commission", + "Selective Service System", + "Small Business Administration", + "Smithsonian Institution", + "Social Security Administration", + "State Justice Institute", + "State, Local, and Tribal Government", + "Stennis Center for Public Service", + "Surface Transportation Board", + "Tennessee Valley Authority", + "The Executive Office of the President", + "U.S. Access Board", + "U.S. Agency for Global Media", + "U.S. Agency for International Development", + "U.S. Chemical Safety Board", + "U.S. China Economic and Security Review Commission", + "U.S. Commission on Civil Rights", + "U.S. Commission on International Religious Freedom", + "U.S. Interagency Council on Homelessness", + "U.S. International Trade Commission", + "U.S. Office of Special Counsel", + "U.S. Postal Service", + "U.S. Trade and Development Agency", + "Udall Foundation", + "United States African Development Foundation", + "United States Arctic Research Commission", + "United States Holocaust Memorial Museum", + "Utah Reclamation Mitigation and Conservation Commission", + "Vietnam Education Foundation", + "Woodrow Wilson International Center for Scholars", + "World War I Centennial Commission", + ] + AGENCY_CHOICES = [(v, v) for v in AGENCIES] + + # This is the application user who created this application. The contact + # information that they gave is in the `submitter` field + creator = models.ForeignKey( + "registrar.User", + on_delete=models.PROTECT, + related_name="information_created", + ) + investigator = models.ForeignKey( + "registrar.User", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="information_investigating", + ) + + # ##### data fields from the initial form ##### + organization_type = models.CharField( + max_length=255, + choices=OrganizationChoices.choices, + null=True, + blank=True, + help_text="Type of Organization", + ) + + federally_recognized_tribe = models.BooleanField( + null=True, + help_text="Is the tribe federally recognized", + ) + + state_recognized_tribe = models.BooleanField( + null=True, + help_text="Is the tribe recognized by a state", + ) + + tribe_name = models.TextField( + null=True, + blank=True, + help_text="Name of tribe", + ) + + federal_agency = models.TextField( + null=True, + blank=True, + help_text="Federal agency", + ) + + federal_type = models.CharField( + max_length=50, + choices=BranchChoices.choices, + null=True, + blank=True, + help_text="Federal government branch", + ) + + is_election_board = models.BooleanField( + null=True, + blank=True, + help_text="Is your organization an election office?", + ) + + organization_name = models.TextField( + null=True, + blank=True, + help_text="Organization name", + db_index=True, + ) + address_line1 = models.TextField( + null=True, + blank=True, + help_text="Street address", + ) + address_line2 = models.CharField( + max_length=15, + null=True, + blank=True, + help_text="Street address line 2", + ) + city = models.TextField( + null=True, + blank=True, + help_text="City", + ) + state_territory = models.CharField( + max_length=2, + null=True, + blank=True, + help_text="State, territory, or military post", + ) + zipcode = models.CharField( + max_length=10, + null=True, + blank=True, + help_text="Zip code", + db_index=True, + ) + urbanization = models.TextField( + null=True, + blank=True, + help_text="Urbanization (Puerto Rico only)", + ) + + type_of_work = models.TextField( + null=True, + blank=True, + help_text="Type of work of the organization", + ) + + more_organization_information = models.TextField( + null=True, + blank=True, + help_text="Further information about the government organization", + ) + + authorizing_official = models.ForeignKey( + "registrar.Contact", + null=True, + blank=True, + related_name="information_authorizing_official", + on_delete=models.PROTECT, + ) + + domain = models.ForeignKey( + "registrar.Domain", + on_delete=models.PROTECT, + blank=True, + null=True, + related_name="domain_info", #Access this information via Domain as "domain.info" + help_text="Domain to which this information belongs" + ) + alternative_domains = models.ManyToManyField( + "registrar.Website", + blank=True, + related_name="alternatives+", + ) + + # This is the contact information provided by the applicant. The + # application user who created it is in the `creator` field. + submitter = models.ForeignKey( + "registrar.Contact", + null=True, + blank=True, + related_name="submitted_applications_information", + on_delete=models.PROTECT, + ) + + purpose = models.TextField( + null=True, + blank=True, + help_text="Purpose of your domain", + ) + + other_contacts = models.ManyToManyField( + "registrar.Contact", + blank=True, + related_name="contact_applications_information", + ) + + no_other_contacts_rationale = models.TextField( + null=True, + blank=True, + help_text="Reason for listing no additional contacts", + ) + + anything_else = models.TextField( + null=True, + blank=True, + help_text="Anything else we should know?", + ) + + is_policy_acknowledged = models.BooleanField( + null=True, + blank=True, + help_text="Acknowledged .gov acceptable use policy", + ) security_email = models.EmailField( max_length=320, null=True, blank=True, help_text="Security email for public use", ) + + def __str__(self): + try: + if self.domain and self.domain.name: + return self.domain.name + else: + return f"application created by {self.creator}" + except Exception: + return "" + + @classmethod + def create_from_da_dict(cls, da_dict): + """Takes in a DomainApplication dict and converts it into DomainInformation""" + # we don't want to pass the id to avoid conflicts + da_dict.pop("id") + # the following information below is not needed in the domain information: + da_dict.pop("status") + da_dict.pop("current_websites") + # 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") + alternative_domains = da_dict.pop("alternative_domains") #just in case + domain_info = cls(**da_dict) + + #Save so the object now have PK (needed to process the manytomany below before first) + domain_info.save() + + #Process the remaining "many to many" stuff + domain_info.other_contacts.add(*other_contacts) + domain_info.alternative_domains.add(*alternative_domains) + domain_info.save() + return domain_info + + + class Meta: + verbose_name_plural = "Domain Information" \ No newline at end of file