From 71edc0ba6dc80b182aa13af190a796f429dd16bf Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:47:24 -0600 Subject: [PATCH 01/58] Getters --- src/registrar/models/domain.py | 230 +++++++++++++++++++++++++++++++-- 1 file changed, 221 insertions(+), 9 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 59563d3d8..41b033d47 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -11,7 +11,7 @@ from epplibwrapper import ( commands, common as epp, RegistryError, - ErrorCode, + ErrorCode ) from .utility.domain_field import DomainField @@ -251,8 +251,8 @@ class Domain(TimeStampedModel, DomainHelper): @Cache def registrant_contact(self) -> PublicContact: - """Get or set the registrant for this domain.""" - raise NotImplementedError() + registrant = PublicContact.ContactTypeChoices.REGISTRANT + return self.generic_contact_getter(registrant) @registrant_contact.setter # type: ignore def registrant_contact(self, contact: PublicContact): @@ -263,7 +263,8 @@ class Domain(TimeStampedModel, DomainHelper): @Cache def administrative_contact(self) -> PublicContact: """Get or set the admin contact for this domain.""" - raise NotImplementedError() + admin = PublicContact.ContactTypeChoices.ADMINISTRATIVE + return self.generic_contact_getter(admin) @administrative_contact.setter # type: ignore def administrative_contact(self, contact: PublicContact): @@ -277,10 +278,8 @@ class Domain(TimeStampedModel, DomainHelper): def security_contact(self) -> PublicContact: """Get or set the security contact for this domain.""" # TODO: replace this with a real implementation - contact = PublicContact.get_default_security() - contact.domain = self - contact.email = "mayor@igorville.gov" - return contact + security = PublicContact.ContactTypeChoices.SECURITY + return self.generic_contact_getter(security) @security_contact.setter # type: ignore def security_contact(self, contact: PublicContact): @@ -290,7 +289,8 @@ class Domain(TimeStampedModel, DomainHelper): @Cache def technical_contact(self) -> PublicContact: """Get or set the tech contact for this domain.""" - raise NotImplementedError() + tech = PublicContact.ContactTypeChoices.TECHNICAL + return self.generic_contact_getter(tech) @technical_contact.setter # type: ignore def technical_contact(self, contact: PublicContact): @@ -341,6 +341,218 @@ class Domain(TimeStampedModel, DomainHelper): def isActive(self): return self.state == Domain.State.CREATED + # Q: I don't like this function name much, + # what would be better here? + def map_DomainContact_to_PublicContact(self, contact: epp.DomainContact, only_map_domain_contact = False): + """Maps the Epps DomainContact Object to a PublicContact object + + contact -> DomainContact: the returned contact for InfoDomain + + only_map_domain_contact -> bool: DomainContact doesn't give enough information on + its own to fully qualify PublicContact, but if you only want the contact_type + and registry_id fields, then set this to True. + """ + if(contact is None or contact == {}): + raise ValueError("Contact cannot be empty or none") + + if(contact.contact is None or contact.contact == ""): + raise ValueError("No contact id was provided") + + if(contact.type is None or contact.type == ""): + raise ValueError("no contact_type was provided") + + if(contact.type not in PublicContact.ContactTypeChoice.values()): + raise ValueError(f"Invalid contact_type of '{contact.type}' for object {contact}. Must exist within PublicContact.ContactTypeChoice") + + mapped_contact: PublicContact = PublicContact( + # todo - check contact is valid type + domain=self, + contact_type=contact.type, + registry_id=contact.contact + ) + + if only_map_domain_contact: + return mapped_contact + + extra_contact_info: epp.InfoContactResultData = self._request_contact_info(mapped_contact) + + # For readability + return self.map_InfoContactResultData_to_PublicContact(extra_contact_info) + + def map_InfoContactResultData_to_PublicContact(self, contact): + """Maps the Epps InfoContactResultData Object to a PublicContact object""" + if(contact is None or contact == {}): + raise ValueError("Contact cannot be empty or none") + + if(contact.id is None or contact.id == ""): + raise ValueError("No contact id was provided") + + if(contact.type is None or contact.type == ""): + raise ValueError("no contact_type was provided") + + if(contact.type not in PublicContact.ContactTypeChoice.values()): + raise ValueError(f"Invalid contact_type of '{contact.type}' for object {contact}. Must exist within PublicContact.ContactTypeChoice") + + postal_info = contact.postal_info + return PublicContact( + domain = self, + contact_type=contact.type, + registry_id=contact.id, + email=contact.email, + voice=contact.voice, + fax=contact.fax, + pw=contact.auth_info.pw or None, + name = postal_info.name or None, + org = postal_info.org or None, + # TODO - street is a Sequence[str] + #street = postal_info.street, + city = postal_info.addr.city or None, + pc = postal_info.addr.pc or None, + cc = postal_info.addr.cc or None, + sp = postal_info.addr.sp or None + ) + + def map_to_public_contact(self, contact): + """ Maps epp contact types to PublicContact. Can handle two types: + epp.DomainContact or epp.InfoContactResultData""" + if(isinstance(contact, epp.InfoContactResultData)): + return self.map_InfoContactResultData_to_PublicContact(contact) + # If contact is of type epp.DomainContact, + # grab as much data as possible. + elif(isinstance(contact, epp.DomainContact)): + # Runs command.InfoDomain, as epp.DomainContact + # on its own doesn't return enough data. + try: + return self.map_DomainContact_to_PublicContact(contact) + except RegistryError as error: + logger.warning(f"Contact {contact} does not exist on the registry") + logger.warning(error) + return self.map_DomainContact_to_PublicContact(contact, only_map_domain_contact=True) + else: + raise ValueError("Contact is not of the correct type. Must be epp.DomainContact or epp.InfoContactResultData") + + + def _request_contact_info(self, contact: PublicContact): + try: + req = commands.InfoContact(id=contact.registry_id) + return registry.send(req, cleaned=True).res_data[0] + except RegistryError as error: + logger.error( + "Registry threw error for contact id %s contact type is %s, error code is\n %s full error is %s", + contact.registry_id, + contact.contact_type, + error.code, + error, + ) + raise error + + def generic_contact_getter(self, contact_type_choice: PublicContact.ContactTypeChoices) -> PublicContact: + """ Abstracts the cache logic on EppLib contact items + + contact_type_choice is a literal in PublicContact.ContactTypeChoices, + for instance: PublicContact.ContactTypeChoices.SECURITY. + + If you wanted to setup getter logic for Security, you would call: + cache_contact_helper(PublicContact.ContactTypeChoices.SECURITY), + or cache_contact_helper("security") + """ + try: + contacts = self._get_property("contacts") + except KeyError as error: + logger.error("Contact does not exist") + raise error + else: + cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) + if(cached_contact is None): + raise ValueError("No contact was found in cache or the registry") + # TODO - below line never executes with current logic + return cached_contact + + def get_default_security_contact(self): + """ Gets the default security contact. """ + contact = PublicContact.get_default_security() + contact.domain = self + # if you invert the logic in get_contact_default + # such that the match statement calls from PublicContact, + # you can reduce these to one liners: + # self.get_contact_default(PublicContact.ContactTypeChoices.SECURITY) + return contact + + def get_default_administrative_contact(self): + """ Gets the default administrative contact. """ + contact = PublicContact.get_default_administrative() + contact.domain = self + return contact + + def get_default_technical_contact(self): + """ Gets the default administrative contact. """ + contact = PublicContact.get_default_technical() + contact.domain = self + return contact + + def get_default_registrant_contact(self): + """ Gets the default administrative contact. """ + contact = PublicContact.get_default_registrant() + contact.domain = self + return contact + + def get_contact_default(self, contact_type_choice: PublicContact.ContactTypeChoices) -> PublicContact: + """ Returns a default contact based off the contact_type_choice. + Used + + contact_type_choice is a literal in PublicContact.ContactTypeChoices, + for instance: PublicContact.ContactTypeChoices.SECURITY. + + If you wanted to get the default contact for Security, you would call: + get_contact_default(PublicContact.ContactTypeChoices.SECURITY), + or get_contact_default("security") + """ + choices = PublicContact.ContactTypeChoices + contact: PublicContact + match(contact_type_choice): + case choices.ADMINISTRATIVE: + contact = self.get_default_administrative_contact() + case choices.SECURITY: + contact = self.get_default_security_contact() + case choices.TECHNICAL: + contact = self.get_default_technical_contact() + case choices.REGISTRANT: + contact = self.get_default_registrant_contact() + return contact + + def grab_contact_in_keys(self, contacts, check_type, get_from_registry=True): + """ Grabs a contact object. + Returns None if nothing is found. + check_type compares contact["type"] == check_type. + + For example, check_type = 'security' + + get_from_registry --> bool which specifies if + a InfoContact command should be send to the + registry when grabbing the object. + If it is set to false, we just grab from cache. + Otherwise, we grab from the registry. + """ + for contact in contacts: + if ( + isinstance(contact, dict) + and "type" in contact.keys() + and "contact" in contact.keys() + and contact["type"] == check_type + ): + ##TODO - Test / Finish this implementation + if(get_from_registry): + request = commands.InfoContact(id=contact.get("contact")) + # TODO - Additional error checking + # Does this have performance implications? + # Expecting/sending a response for every object + # seems potentially taxing + contact_info = registry.send(request, cleaned=True) + logger.debug(f"grab_contact_in_keys -> rest data is {contact_info.res_data[0]}") + return self.map_to_public_contact(contact_info.res_data[0]) + + return contact["contact"] + # ForeignKey on UserDomainRole creates a "permissions" member for # all of the user-roles that are in place for this domain From 7abc9bdf7d49d69bca6f929c2711731c216aef50 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 13 Sep 2023 09:41:31 -0600 Subject: [PATCH 02/58] Small changes --- src/registrar/models/domain.py | 42 ++-------------------------------- src/registrar/views/domain.py | 14 ++++++++++-- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 69d7bac7a..357c28e99 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -114,12 +114,6 @@ class Domain(TimeStampedModel, DomainHelper): # the state is indeterminate UNKNOWN = "unknown" - # the ready state for a domain object - READY = "ready" - - # when a domain is on hold - ONHOLD = "onhold" - class Cache(property): """ Python descriptor to turn class methods into properties. @@ -317,17 +311,13 @@ class Domain(TimeStampedModel, DomainHelper): """Time to renew. Not implemented.""" raise NotImplementedError() - @transition(field="state", source=[State.READY], target=State.ONHOLD) def place_client_hold(self): """This domain should not be active.""" - # This method is changing the state of the domain in registrar - # TODO: implement EPP call + raise NotImplementedError("This is not implemented yet.") - @transition(field="state", source=[State.ONHOLD], target=State.READY) def remove_client_hold(self): """This domain is okay to be active.""" - # This method is changing the state of the domain in registrar - # TODO: implement EPP call + raise NotImplementedError() def __str__(self) -> str: return self.name @@ -482,10 +472,6 @@ class Domain(TimeStampedModel, DomainHelper): """ Gets the default security contact. """ contact = PublicContact.get_default_security() contact.domain = self - # if you invert the logic in get_contact_default - # such that the match statement calls from PublicContact, - # you can reduce these to one liners: - # self.get_contact_default(PublicContact.ContactTypeChoices.SECURITY) return contact def get_default_administrative_contact(self): @@ -506,30 +492,6 @@ class Domain(TimeStampedModel, DomainHelper): contact.domain = self return contact - def get_contact_default(self, contact_type_choice: PublicContact.ContactTypeChoices) -> PublicContact: - """ Returns a default contact based off the contact_type_choice. - Used - - contact_type_choice is a literal in PublicContact.ContactTypeChoices, - for instance: PublicContact.ContactTypeChoices.SECURITY. - - If you wanted to get the default contact for Security, you would call: - get_contact_default(PublicContact.ContactTypeChoices.SECURITY), - or get_contact_default("security") - """ - choices = PublicContact.ContactTypeChoices - contact: PublicContact - match(contact_type_choice): - case choices.ADMINISTRATIVE: - contact = self.get_default_administrative_contact() - case choices.SECURITY: - contact = self.get_default_security_contact() - case choices.TECHNICAL: - contact = self.get_default_technical_contact() - case choices.REGISTRANT: - contact = self.get_default_registrant_contact() - return contact - def grab_contact_in_keys(self, contacts, check_type, get_from_registry=True): """ Grabs a contact object. Returns None if nothing is found. diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index f945bc443..4863d1bc2 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -20,6 +20,7 @@ from registrar.models import ( User, UserDomainRole, ) +from registrar.models.public_contact import PublicContact from ..forms import ( ContactForm, @@ -246,7 +247,10 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): """The initial value for the form.""" domain = self.get_object() initial = super().get_initial() - initial["security_email"] = domain.security_contact.email + security_email = "" + if(domain.security_contact.email is not None): + security_email = domain.security_contact.email + initial["security_email"] = security_email return initial def get_success_url(self): @@ -269,7 +273,13 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): # Set the security email from the form new_email = form.cleaned_data.get("security_email", "") domain = self.get_object() - contact = domain.security_contact + + contact: PublicContact + if domain.security_contact is not None: + contact = domain.security_contact + else: + contact = domain.get_default_security_contact() + contact.email = new_email contact.save() From aad0aa676e18ef4073ad387c3f78b4ac95051656 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:57:10 -0600 Subject: [PATCH 03/58] Funky merging --- src/registrar/models/domain.py | 117 ++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 958875536..5ad1cd516 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -444,9 +444,124 @@ class Domain(TimeStampedModel, DomainHelper): @Cache def security_contact(self) -> PublicContact: """Get or set the security contact for this domain.""" - # TODO: replace this with a real implementation security = PublicContact.ContactTypeChoices.SECURITY return self.generic_contact_getter(security) + + def _add_registrant_to_existing_domain(self, contact: PublicContact): + """Used to change the registrant contact on an existing domain""" + updateDomain = commands.UpdateDomain( + name=self.name, registrant=contact.registry_id + ) + try: + registry.send(updateDomain, cleaned=True) + except RegistryError as e: + logger.error( + "Error changing to new registrant error code is %s, error is %s" + % (e.code, e) + ) + # TODO-error handling better here? + + def _set_singleton_contact(self, contact: PublicContact, expectedType: str): # noqa + """Sets the contacts by adding them to the registry as new contacts, + updates the contact if it is already in epp, + deletes any additional contacts of the matching type for this domain + does not create the PublicContact object, this should be made beforehand + (call save() on a public contact to trigger the contact setters + which inturn call this function) + Will throw error if contact type is not the same as expectType + Raises ValueError if expected type doesn't match the contact type""" + if expectedType != contact.contact_type: + raise ValueError( + "Cannot set a contact with a different contact type," + " expected type was %s" % expectedType + ) + + isRegistrant = contact.contact_type == contact.ContactTypeChoices.REGISTRANT + isEmptySecurity = ( + contact.contact_type == contact.ContactTypeChoices.SECURITY + and contact.email == "" + ) + + # get publicContact objects that have the matching + # domain and type but a different id + # like in highlander we there can only be one + hasOtherContact = ( + PublicContact.objects.exclude(registry_id=contact.registry_id) + .filter(domain=self, contact_type=contact.contact_type) + .exists() + ) + + # if no record exists with this contact type + # make contact in registry, duplicate and errors handled there + errorCode = self._make_contact_in_registry(contact) + + # contact is already added to the domain, but something may have changed on it + alreadyExistsInRegistry = errorCode == ErrorCode.OBJECT_EXISTS + # if an error occured besides duplication, stop + if ( + not alreadyExistsInRegistry + and errorCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY + ): + # TODO- ticket #433 look here for error handling + raise Exception("Unable to add contact to registry") + + # contact doesn't exist on the domain yet + logger.info("_set_singleton_contact()-> contact has been added to the registry") + + # if has conflicting contacts in our db remove them + if hasOtherContact: + logger.info( + "_set_singleton_contact()-> updating domain, removing old contact" + ) + + existing_contact = ( + PublicContact.objects.exclude(registry_id=contact.registry_id) + .filter(domain=self, contact_type=contact.contact_type) + .get() + ) + if isRegistrant: + # send update domain only for registant contacts + existing_contact.delete() + self._add_registrant_to_existing_domain(contact) + else: + # remove the old contact and add a new one + try: + self._update_domain_with_contact(contact=existing_contact, rem=True) + existing_contact.delete() + except Exception as err: + logger.error( + "Raising error after removing and adding a new contact" + ) + raise (err) + + # update domain with contact or update the contact itself + if not isEmptySecurity: + if not alreadyExistsInRegistry and not isRegistrant: + self._update_domain_with_contact(contact=contact, rem=False) + # if already exists just update + elif alreadyExistsInRegistry: + current_contact = PublicContact.objects.filter( + registry_id=contact.registry_id + ).get() + + if current_contact.email != contact.email: + self._update_epp_contact(contact=contact) + else: + logger.info("removing security contact and setting default again") + + # get the current contact registry id for security + current_contact = PublicContact.objects.filter( + registry_id=contact.registry_id + ).get() + + # don't let user delete the default without adding a new email + if current_contact.email != PublicContact.get_default_security().email: + # remove the contact + self._update_domain_with_contact(contact=current_contact, rem=True) + current_contact.delete() + # add new contact + security_contact = self.get_default_security_contact() + security_contact.save() @security_contact.setter # type: ignore def security_contact(self, contact: PublicContact): From 7643a63d97369f9d872e9e9d7841ce89f143296c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:02:46 -0600 Subject: [PATCH 04/58] Update domain.py --- src/registrar/models/domain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 5ad1cd516..65798a19c 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -823,7 +823,6 @@ class Domain(TimeStampedModel, DomainHelper): and "contact" in contact.keys() and contact["type"] == check_type ): - ##TODO - Test / Finish this implementation if(get_from_registry): request = commands.InfoContact(id=contact.get("contact")) # TODO - Additional error checking From e5037cd9531f3cfa911094666ccb559885de8aac Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:59:33 -0600 Subject: [PATCH 05/58] Change logic flow in grab_contact_keys --- src/registrar/models/domain.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 65798a19c..5b75fd1c6 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -803,14 +803,14 @@ class Domain(TimeStampedModel, DomainHelper): contact.domain = self return contact - def grab_contact_in_keys(self, contacts, check_type, get_from_registry=True): + def grab_contact_in_keys(self, contacts, check_type, get_latest_from_registry=True): """ Grabs a contact object. Returns None if nothing is found. check_type compares contact["type"] == check_type. For example, check_type = 'security' - get_from_registry --> bool which specifies if + get_latest_from_registry --> bool which specifies if a InfoContact command should be send to the registry when grabbing the object. If it is set to false, we just grab from cache. @@ -823,17 +823,23 @@ class Domain(TimeStampedModel, DomainHelper): and "contact" in contact.keys() and contact["type"] == check_type ): - if(get_from_registry): + # + if(get_latest_from_registry): request = commands.InfoContact(id=contact.get("contact")) - # TODO - Additional error checking - # Does this have performance implications? - # Expecting/sending a response for every object - # seems potentially taxing + # TODO - Maybe have this return contact instead, + # Then create a global timer which eventually returns + # the requested content.... And updates it! contact_info = registry.send(request, cleaned=True) logger.debug(f"grab_contact_in_keys -> rest data is {contact_info.res_data[0]}") return self.map_to_public_contact(contact_info.res_data[0]) return contact["contact"] + + # If nothing is found in cache, then grab from registry + request = commands.InfoContact(id=contact.get("contact")) + contact_info = registry.send(request, cleaned=True) + logger.debug(f"grab_contact_in_keys -> rest data is {contact_info.res_data[0]}") + return self.map_to_public_contact(contact_info.res_data[0]) # ForeignKey on UserDomainRole creates a "permissions" member for # all of the user-roles that are in place for this domain From 43a636b28694b603721c542e0a2cf5c3d5716922 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:02:37 -0600 Subject: [PATCH 06/58] Finished getters --- src/registrar/models/domain.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 5b75fd1c6..604d17d00 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -773,6 +773,9 @@ class Domain(TimeStampedModel, DomainHelper): logger.error("Contact does not exist") raise error else: + # TODO - is this even needed??????? + print(f"generic_contact_getter -> contacts?? {contacts}") + # --> Map to public contact cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) if(cached_contact is None): raise ValueError("No contact was found in cache or the registry") @@ -817,29 +820,14 @@ class Domain(TimeStampedModel, DomainHelper): Otherwise, we grab from the registry. """ for contact in contacts: + print(f"grab_contact_in_keys -> contact item {contact}") + print(f"grab_contact_in_keys -> isInstace {isinstance(contact, dict)}") if ( isinstance(contact, dict) and "type" in contact.keys() - and "contact" in contact.keys() and contact["type"] == check_type ): - # - if(get_latest_from_registry): - request = commands.InfoContact(id=contact.get("contact")) - # TODO - Maybe have this return contact instead, - # Then create a global timer which eventually returns - # the requested content.... And updates it! - contact_info = registry.send(request, cleaned=True) - logger.debug(f"grab_contact_in_keys -> rest data is {contact_info.res_data[0]}") - return self.map_to_public_contact(contact_info.res_data[0]) - - return contact["contact"] - - # If nothing is found in cache, then grab from registry - request = commands.InfoContact(id=contact.get("contact")) - contact_info = registry.send(request, cleaned=True) - logger.debug(f"grab_contact_in_keys -> rest data is {contact_info.res_data[0]}") - return self.map_to_public_contact(contact_info.res_data[0]) + return contact # ForeignKey on UserDomainRole creates a "permissions" member for # all of the user-roles that are in place for this domain From 2e6a8198ac20656fc68744286d32d7b78c36fc10 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 14 Sep 2023 15:54:54 -0600 Subject: [PATCH 07/58] Test cases / Mapping --- src/epplibwrapper/__init__.py | 3 +- src/registrar/models/domain.py | 210 ++++++++++------------ src/registrar/tests/common.py | 66 ++++++- src/registrar/tests/test_models_domain.py | 63 +++++++ 4 files changed, 221 insertions(+), 121 deletions(-) diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index b306dbd0e..1997b422e 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -44,7 +44,7 @@ except NameError: try: from .client import CLIENT, commands from .errors import RegistryError, ErrorCode - from epplib.models import common + from epplib.models import common, info except ImportError: pass @@ -52,6 +52,7 @@ __all__ = [ "CLIENT", "commands", "common", + "info", "ErrorCode", "RegistryError", ] diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 604d17d00..f534dd7d3 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1,3 +1,4 @@ +from itertools import zip_longest import logging from datetime import date @@ -10,8 +11,9 @@ from epplibwrapper import ( CLIENT as registry, commands, common as epp, + info as eppInfo, RegistryError, - ErrorCode + ErrorCode, ) from .utility.domain_field import DomainField @@ -446,7 +448,7 @@ class Domain(TimeStampedModel, DomainHelper): """Get or set the security contact for this domain.""" security = PublicContact.ContactTypeChoices.SECURITY return self.generic_contact_getter(security) - + def _add_registrant_to_existing_domain(self, contact: PublicContact): """Used to change the registrant contact on an existing domain""" updateDomain = commands.UpdateDomain( @@ -654,95 +656,57 @@ class Domain(TimeStampedModel, DomainHelper): # Q: I don't like this function name much, # what would be better here? - def map_DomainContact_to_PublicContact(self, contact: epp.DomainContact, only_map_domain_contact = False): - """Maps the Epps DomainContact Object to a PublicContact object - - contact -> DomainContact: the returned contact for InfoDomain + def map_epp_contact_to_public_contact( + self, contact: eppInfo.InfoContactResultData, contact_type + ): + """Maps the Epp contact representation to a PublicContact object""" - only_map_domain_contact -> bool: DomainContact doesn't give enough information on - its own to fully qualify PublicContact, but if you only want the contact_type - and registry_id fields, then set this to True. - """ - if(contact is None or contact == {}): - raise ValueError("Contact cannot be empty or none") - - if(contact.contact is None or contact.contact == ""): - raise ValueError("No contact id was provided") - - if(contact.type is None or contact.type == ""): - raise ValueError("no contact_type was provided") + if contact is None: + return None - if(contact.type not in PublicContact.ContactTypeChoice.values()): - raise ValueError(f"Invalid contact_type of '{contact.type}' for object {contact}. Must exist within PublicContact.ContactTypeChoice") - - mapped_contact: PublicContact = PublicContact( - # todo - check contact is valid type - domain=self, - contact_type=contact.type, - registry_id=contact.contact - ) + if contact_type is None: + raise ValueError(f"contact_type is None") - if only_map_domain_contact: - return mapped_contact - - extra_contact_info: epp.InfoContactResultData = self._request_contact_info(mapped_contact) - - # For readability - return self.map_InfoContactResultData_to_PublicContact(extra_contact_info) - - def map_InfoContactResultData_to_PublicContact(self, contact): - """Maps the Epps InfoContactResultData Object to a PublicContact object""" - if(contact is None or contact == {}): - raise ValueError("Contact cannot be empty or none") - - if(contact.id is None or contact.id == ""): - raise ValueError("No contact id was provided") - - if(contact.type is None or contact.type == ""): - raise ValueError("no contact_type was provided") + logger.debug(f"map_epp_contact_to_public_contact contact -> {contact}") + logger.debug(f"What is the type? {type(contact)}") + if not isinstance(contact, eppInfo.InfoContactResultData): + raise ValueError("Contact must be of type InfoContactResultData") - if(contact.type not in PublicContact.ContactTypeChoice.values()): - raise ValueError(f"Invalid contact_type of '{contact.type}' for object {contact}. Must exist within PublicContact.ContactTypeChoice") - + auth_info = contact.auth_info postal_info = contact.postal_info - return PublicContact( - domain = self, - contact_type=contact.type, + addr = postal_info.addr + streets = {} + if addr is not None and addr.street is not None: + # 'zips' two lists together. For instance, (('street1', 'some_value_here'), ('street2', 'some_value_here')) + # Dict then converts this to a useable kwarg which we can pass in + streets = dict( + zip_longest( + ["street1", "street2", "street3"], + addr.street, + fillvalue=None, + ) + ) + + desired_contact = PublicContact( + domain=self, + contact_type=contact_type, registry_id=contact.id, email=contact.email, voice=contact.voice, fax=contact.fax, - pw=contact.auth_info.pw or None, - name = postal_info.name or None, - org = postal_info.org or None, - # TODO - street is a Sequence[str] - #street = postal_info.street, - city = postal_info.addr.city or None, - pc = postal_info.addr.pc or None, - cc = postal_info.addr.cc or None, - sp = postal_info.addr.sp or None + pw=auth_info.pw, + name=postal_info.name, + org=postal_info.org, + city=addr.city, + pc=addr.pc, + cc=addr.cc, + sp=addr.sp, + **streets, ) + logger.debug("lazy") + logger.debug(desired_contact.__dict__) + return desired_contact - def map_to_public_contact(self, contact): - """ Maps epp contact types to PublicContact. Can handle two types: - epp.DomainContact or epp.InfoContactResultData""" - if(isinstance(contact, epp.InfoContactResultData)): - return self.map_InfoContactResultData_to_PublicContact(contact) - # If contact is of type epp.DomainContact, - # grab as much data as possible. - elif(isinstance(contact, epp.DomainContact)): - # Runs command.InfoDomain, as epp.DomainContact - # on its own doesn't return enough data. - try: - return self.map_DomainContact_to_PublicContact(contact) - except RegistryError as error: - logger.warning(f"Contact {contact} does not exist on the registry") - logger.warning(error) - return self.map_DomainContact_to_PublicContact(contact, only_map_domain_contact=True) - else: - raise ValueError("Contact is not of the correct type. Must be epp.DomainContact or epp.InfoContactResultData") - - def _request_contact_info(self, contact: PublicContact): try: req = commands.InfoContact(id=contact.registry_id) @@ -756,79 +720,86 @@ class Domain(TimeStampedModel, DomainHelper): error, ) raise error - - def generic_contact_getter(self, contact_type_choice: PublicContact.ContactTypeChoices) -> PublicContact: - """ Abstracts the cache logic on EppLib contact items - + + def generic_contact_getter( + self, contact_type_choice: PublicContact.ContactTypeChoices + ) -> PublicContact: + """Abstracts the cache logic on EppLib contact items + contact_type_choice is a literal in PublicContact.ContactTypeChoices, for instance: PublicContact.ContactTypeChoices.SECURITY. - If you wanted to setup getter logic for Security, you would call: + If you wanted to setup getter logic for Security, you would call: cache_contact_helper(PublicContact.ContactTypeChoices.SECURITY), or cache_contact_helper("security") """ try: - contacts = self._get_property("contacts") + desired_property = "contacts" + # The contact type 'registrant' is stored under a different property + if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: + desired_property = "registrant" + contacts = self._get_property(desired_property) except KeyError as error: logger.error("Contact does not exist") raise error else: - # TODO - is this even needed??????? print(f"generic_contact_getter -> contacts?? {contacts}") # --> Map to public contact cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) - if(cached_contact is None): + if cached_contact is None: raise ValueError("No contact was found in cache or the registry") - # TODO - below line never executes with current logic - return cached_contact - + + # Convert it from an EppLib object to PublicContact + return self.map_epp_contact_to_public_contact( + cached_contact, contact_type_choice + ) + def get_default_security_contact(self): - """ Gets the default security contact. """ + """Gets the default security contact.""" contact = PublicContact.get_default_security() contact.domain = self return contact - + def get_default_administrative_contact(self): - """ Gets the default administrative contact. """ + """Gets the default administrative contact.""" contact = PublicContact.get_default_administrative() contact.domain = self return contact - + def get_default_technical_contact(self): - """ Gets the default administrative contact. """ + """Gets the default administrative contact.""" contact = PublicContact.get_default_technical() contact.domain = self return contact - + def get_default_registrant_contact(self): - """ Gets the default administrative contact. """ + """Gets the default administrative contact.""" contact = PublicContact.get_default_registrant() contact.domain = self return contact - def grab_contact_in_keys(self, contacts, check_type, get_latest_from_registry=True): - """ Grabs a contact object. + def grab_contact_in_keys(self, contacts, check_type): + """Grabs a contact object. Returns None if nothing is found. check_type compares contact["type"] == check_type. For example, check_type = 'security' - - get_latest_from_registry --> bool which specifies if - a InfoContact command should be send to the - registry when grabbing the object. - If it is set to false, we just grab from cache. - Otherwise, we grab from the registry. """ for contact in contacts: print(f"grab_contact_in_keys -> contact item {contact}") - print(f"grab_contact_in_keys -> isInstace {isinstance(contact, dict)}") if ( isinstance(contact, dict) + and "id" in contact.keys() and "type" in contact.keys() and contact["type"] == check_type ): - return contact - + item = PublicContact( + registry_id=contact["id"], + contact_type=contact["type"], + ) + full_contact = self._request_contact_info(item) + return full_contact + # ForeignKey on UserDomainRole creates a "permissions" member for # all of the user-roles that are in place for this domain @@ -927,12 +898,10 @@ class Domain(TimeStampedModel, DomainHelper): security_contact = self.get_default_security_contact() security_contact.save() - technical_contact = PublicContact.get_default_technical() - technical_contact.domain = self + technical_contact = self.get_default_technical_contact() technical_contact.save() - administrative_contact = PublicContact.get_default_administrative() - administrative_contact.domain = self + administrative_contact = self.get_default_administrative_contact() administrative_contact.save() @transition(field="state", source=State.READY, target=State.ON_HOLD) @@ -1058,10 +1027,6 @@ class Domain(TimeStampedModel, DomainHelper): ) return err.code - def _request_contact_info(self, contact: PublicContact): - req = commands.InfoContact(id=contact.registry_id) - return registry.send(req, cleaned=True).res_data[0] - def _get_or_create_contact(self, contact: PublicContact): """Try to fetch info about a contact. Create it if it does not exist.""" @@ -1122,6 +1087,17 @@ class Domain(TimeStampedModel, DomainHelper): # statuses can just be a list no need to keep the epp object if "statuses" in cleaned.keys(): cleaned["statuses"] = [status.state for status in cleaned["statuses"]] + + # Registrant should be of type PublicContact + if "registrant" in cleaned.keys(): + try: + contact = PublicContact( + registry_id=cleaned["registrant"], + contact_type=PublicContact.ContactTypeChoices.REGISTRANT, + ) + cleaned["registrant"] = self._request_contact_info(contact) + except RegistryError: + cleaned["registrant"] = None # get contact info, if there are any if ( # fetch_contacts and diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index c312acca0..e264ffac9 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass import datetime import os import logging @@ -26,6 +27,7 @@ from registrar.models import ( from epplibwrapper import ( commands, common, + info, RegistryError, ErrorCode, ) @@ -547,11 +549,48 @@ class MockEppLib(TestCase): class fakedEppObject(object): """""" - def __init__(self, auth_info=..., cr_date=..., contacts=..., hosts=...): + def __init__( + self, auth_info=..., cr_date=..., contacts=..., hosts=..., registrant=... + ): self.auth_info = auth_info self.cr_date = cr_date self.contacts = contacts self.hosts = hosts + self.registrant = registrant + + def dummyInfoContactResultData(self, id, email, contact_type): + fake = info.InfoContactResultData( + id=id, + postal_info=common.PostalInfo( + name="Robert The Villain", + addr=common.ContactAddr( + street=["street1", "street2", "street3"], + city="city", + pc="pc", + cc="cc", + sp="sp", + ), + org="Skim Milk", + type="type", + ), + voice="voice", + fax="+1-212-9876543", + email=email, + auth_info=common.ContactAuthInfo(pw="fakepw"), + roid=..., + statuses=[], + cl_id=..., + cr_id=..., + cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + up_id=..., + up_date=..., + tr_date=..., + disclose=..., + vat=..., + ident=..., + notify_email=..., + ) + return fake mockDataInfoDomain = fakedEppObject( "fakepw", @@ -559,6 +598,17 @@ class MockEppLib(TestCase): contacts=[common.DomainContact(contact="123", type="security")], hosts=["fake.host.com"], ) + InfoDomainWithContacts = fakedEppObject( + "fakepw", + cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + contacts=[ + common.DomainContact(contact="security", type="security"), + common.DomainContact(contact="admin", type="admin"), + common.DomainContact(contact="tech", type="tech"), + ], + hosts=["fake.host.com"], + registrant="registrant", + ) infoDomainNoContact = fakedEppObject( "security", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), @@ -580,9 +630,19 @@ class MockEppLib(TestCase): if isinstance(_request, commands.InfoDomain): if getattr(_request, "name", None) == "security.gov": return MagicMock(res_data=[self.infoDomainNoContact]) - return MagicMock(res_data=[self.mockDataInfoDomain]) + elif getattr(_request, "name", None) == "freeman.gov": + return MagicMock(res_data=[self.InfoDomainWithContacts]) elif isinstance(_request, commands.InfoContact): - return MagicMock(res_data=[self.mockDataInfoContact]) + mocked_result = self.mockDataInfoContact + l = getattr(_request, "contact_type", None) + logger.debug(f"unuiquq {_request.__dict__}") + if getattr(_request, "id", None) in PublicContact.ContactTypeChoices: + desired_type = getattr(_request, "id", None) + mocked_result = self.dummyInfoContactResultData( + id=desired_type, email=f"{desired_type}@mail.gov" + ) + + return MagicMock(res_data=[mocked_result]) elif ( isinstance(_request, commands.CreateContact) and getattr(_request, "id", None) == "fail" diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 9aaac7321..72c439c2a 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -21,6 +21,9 @@ from epplibwrapper import ( commands, common, ) +import logging + +logger = logging.getLogger(__name__) class TestDomainCache(MockEppLib): @@ -445,6 +448,66 @@ class TestRegistrantContacts(MockEppLib): """ raise + def test_contact_getters_cache(self): + """ + Scenario: A user is grabbing a domain, which is cached, that has multiple contact objects + When each contact is retrieved from cache + Then the user retrieves the correct contact objects + """ + domain, _ = Domain.objects.get_or_create(name="freeman.gov") + + # the cached contacts and hosts should be dictionaries of what is passed to them + # expectedPublicContactDict = {'id': None, 'created_at': None, 'updated_at': None, 'contact_type': PublicContact.ContactTypeChoices.SECURITY, 'registry_id': 'freeman', 'domain_id': 2, 'name': 'Robert The Villain', 'org': 'Skim Milk', 'street1': 'Evil street1', 'street2': 'Evil street2', 'street3': 'evil street3', 'city': 'Cityofdoom', 'sp': 'sp', 'pc': 'pc', 'cc': 'cc', 'email': 'awful@skimmilk.com', 'voice': 'voice', 'fax': '+1-212-9876543', 'pw': 'fakepw'} + + security = PublicContact.get_default_security() + security.email = "security@mail.gov" + security.domain = domain + security.save() + # expected_security_contact = PublicContact(**expectedPublicContactDict) + expected_security_contact = security + domain.security_contact = security + + technical = PublicContact.get_default_technical() + technical.email = "technical@mail.gov" + technical.domain = domain + technical.save() + expected_technical_contact = technical + domain.technical_contact = technical + + administrative = PublicContact.get_default_administrative() + administrative.email = "administrative@mail.gov" + administrative.domain = domain + administrative.save() + expected_administrative_contact = administrative + domain.administrative_contact = administrative + + registrant = PublicContact.get_default_registrant() + registrant.email = "registrant@mail.gov" + registrant.domain = domain + registrant.save() + expected_registrant_contact = registrant + domain.registrant_contact = registrant + + logger.debug(f"domain obj: {domain.security_contact.__dict__}") + logger.debug(f"expected: {expected_security_contact.__dict__}") + self.assertEqual(domain.security_contact, expected_security_contact) + self.assertEqual(domain.technical_contact, expected_technical_contact) + self.assertEqual(domain.administrative_contact, expected_administrative_contact) + self.assertEqual(domain.registrant_contact, expected_registrant_contact) + + @skip("not implemented yet") + def test_contact_getters_registry(self): + """ + Scenario: A user is grabbing a domain, which does not exist in cache, that has multiple contact objects + When the domain is retrieved from cache + Then the user retrieves the correct domain object + """ + # Create something using infocontact for that domain + # Then just grab the domain object normally + # That 'something' doesn't exist on the local domain, + # so registry should be called + raise + class TestRegistrantNameservers(TestCase): """Rule: Registrants may modify their nameservers""" From 1edc21330d9471461b6207e7925c8e5188c7ee1a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:31:21 -0600 Subject: [PATCH 08/58] Cleanup --- src/registrar/models/domain.py | 11 +++-------- src/registrar/tests/common.py | 3 --- src/registrar/tests/test_models_domain.py | 8 ++------ src/registrar/views/domain.py | 14 ++------------ 4 files changed, 7 insertions(+), 29 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index f534dd7d3..88d0f8467 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -385,12 +385,6 @@ class Domain(TimeStampedModel, DomainHelper): self._make_contact_in_registry(contact=contact) self._update_domain_with_contact(contact, rem=False) - def get_default_security_contact(self): - logger.info("getting default sec contact") - contact = PublicContact.get_default_security() - contact.domain = self - return contact - def _update_epp_contact(self, contact: PublicContact): """Sends UpdateContact to update the actual contact object, domain object remains unaffected @@ -665,7 +659,7 @@ class Domain(TimeStampedModel, DomainHelper): return None if contact_type is None: - raise ValueError(f"contact_type is None") + raise ValueError("contact_type is None") logger.debug(f"map_epp_contact_to_public_contact contact -> {contact}") logger.debug(f"What is the type? {type(contact)}") @@ -677,7 +671,8 @@ class Domain(TimeStampedModel, DomainHelper): addr = postal_info.addr streets = {} if addr is not None and addr.street is not None: - # 'zips' two lists together. For instance, (('street1', 'some_value_here'), ('street2', 'some_value_here')) + # 'zips' two lists together. + # For instance, (('street1', 'some_value_here'), ('street2', 'some_value_here')) # Dict then converts this to a useable kwarg which we can pass in streets = dict( zip_longest( diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index e15f57bbf..12efb0241 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -1,4 +1,3 @@ -from dataclasses import dataclass import datetime import os import logging @@ -634,8 +633,6 @@ class MockEppLib(TestCase): return MagicMock(res_data=[self.InfoDomainWithContacts]) elif isinstance(_request, commands.InfoContact): mocked_result = self.mockDataInfoContact - l = getattr(_request, "contact_type", None) - logger.debug(f"unuiquq {_request.__dict__}") if getattr(_request, "id", None) in PublicContact.ContactTypeChoices: desired_type = getattr(_request, "id", None) mocked_result = self.dummyInfoContactResultData( diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 72c439c2a..f5b506d8b 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -450,20 +450,16 @@ class TestRegistrantContacts(MockEppLib): def test_contact_getters_cache(self): """ - Scenario: A user is grabbing a domain, which is cached, that has multiple contact objects + Scenario: A user is grabbing a domain that has multiple contact objects When each contact is retrieved from cache Then the user retrieves the correct contact objects """ domain, _ = Domain.objects.get_or_create(name="freeman.gov") - # the cached contacts and hosts should be dictionaries of what is passed to them - # expectedPublicContactDict = {'id': None, 'created_at': None, 'updated_at': None, 'contact_type': PublicContact.ContactTypeChoices.SECURITY, 'registry_id': 'freeman', 'domain_id': 2, 'name': 'Robert The Villain', 'org': 'Skim Milk', 'street1': 'Evil street1', 'street2': 'Evil street2', 'street3': 'evil street3', 'city': 'Cityofdoom', 'sp': 'sp', 'pc': 'pc', 'cc': 'cc', 'email': 'awful@skimmilk.com', 'voice': 'voice', 'fax': '+1-212-9876543', 'pw': 'fakepw'} - security = PublicContact.get_default_security() security.email = "security@mail.gov" security.domain = domain security.save() - # expected_security_contact = PublicContact(**expectedPublicContactDict) expected_security_contact = security domain.security_contact = security @@ -498,7 +494,7 @@ class TestRegistrantContacts(MockEppLib): @skip("not implemented yet") def test_contact_getters_registry(self): """ - Scenario: A user is grabbing a domain, which does not exist in cache, that has multiple contact objects + Scenario: A user is grabbing a domain that has multiple contact objects When the domain is retrieved from cache Then the user retrieves the correct domain object """ diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 9716c01c2..3da4de3fa 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -20,7 +20,6 @@ from registrar.models import ( User, UserDomainRole, ) -from registrar.models.public_contact import PublicContact from ..forms import ( ContactForm, @@ -251,10 +250,7 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): """The initial value for the form.""" domain = self.get_object() initial = super().get_initial() - security_email = "" - if(domain.security_contact.email is not None): - security_email = domain.security_contact.email - initial["security_email"] = security_email + initial["security_email"] = domain.security_contact.email return initial def get_success_url(self): @@ -278,13 +274,7 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): new_email = form.cleaned_data.get("security_email", "") domain = self.get_object() - - contact: PublicContact - if domain.security_contact is not None: - contact = domain.security_contact - else: - contact = domain.get_default_security_contact() - + contact = domain.security_contact contact.email = new_email contact.save() From aec32ca2edea80b556340c670c010eb07c730296 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:37:30 -0600 Subject: [PATCH 09/58] Test cases and the like --- .../0031_transitiondomain_and_more.py | 2 +- src/registrar/models/domain.py | 114 ++++++++++------ src/registrar/tests/common.py | 52 ++++--- src/registrar/tests/test_models_domain.py | 128 +++++++++++++----- 4 files changed, 197 insertions(+), 99 deletions(-) diff --git a/src/registrar/migrations/0031_transitiondomain_and_more.py b/src/registrar/migrations/0031_transitiondomain_and_more.py index 79bf7eab4..41c130717 100644 --- a/src/registrar/migrations/0031_transitiondomain_and_more.py +++ b/src/registrar/migrations/0031_transitiondomain_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.1 on 2023-09-13 22:25 +# Generated by Django 4.2.1 on 2023-09-15 13:59 from django.db import migrations, models import django_fsm diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 88d0f8467..ec1d73aa9 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -651,9 +651,16 @@ class Domain(TimeStampedModel, DomainHelper): # Q: I don't like this function name much, # what would be better here? def map_epp_contact_to_public_contact( - self, contact: eppInfo.InfoContactResultData, contact_type + self, contact: eppInfo.InfoContactResultData, contact_id, contact_type ): - """Maps the Epp contact representation to a PublicContact object""" + """Maps the Epp contact representation to a PublicContact object. + + contact -> eppInfo.InfoContactResultData: The converted contact object + + contact_id -> str: The given registry_id of the object (i.e "cheese@cia.gov") + + contact_type -> str: The given contact type, (i.e. "tech" or "registrant") + """ if contact is None: return None @@ -661,6 +668,9 @@ class Domain(TimeStampedModel, DomainHelper): if contact_type is None: raise ValueError("contact_type is None") + if contact_id is None: + raise ValueError("contact_id is None") + logger.debug(f"map_epp_contact_to_public_contact contact -> {contact}") logger.debug(f"What is the type? {type(contact)}") if not isinstance(contact, eppInfo.InfoContactResultData): @@ -671,7 +681,7 @@ class Domain(TimeStampedModel, DomainHelper): addr = postal_info.addr streets = {} if addr is not None and addr.street is not None: - # 'zips' two lists together. + # 'zips' two lists together. # For instance, (('street1', 'some_value_here'), ('street2', 'some_value_here')) # Dict then converts this to a useable kwarg which we can pass in streets = dict( @@ -685,7 +695,7 @@ class Domain(TimeStampedModel, DomainHelper): desired_contact = PublicContact( domain=self, contact_type=contact_type, - registry_id=contact.id, + registry_id=contact_id, email=contact.email, voice=contact.voice, fax=contact.fax, @@ -698,8 +708,6 @@ class Domain(TimeStampedModel, DomainHelper): sp=addr.sp, **streets, ) - logger.debug("lazy") - logger.debug(desired_contact.__dict__) return desired_contact def _request_contact_info(self, contact: PublicContact): @@ -716,6 +724,32 @@ class Domain(TimeStampedModel, DomainHelper): ) raise error + def get_contact_default( + self, contact_type_choice: PublicContact.ContactTypeChoices + ) -> PublicContact: + """Returns a default contact based off the contact_type_choice. + Used + + contact_type_choice is a literal in PublicContact.ContactTypeChoices, + for instance: PublicContact.ContactTypeChoices.SECURITY. + + If you wanted to get the default contact for Security, you would call: + get_contact_default(PublicContact.ContactTypeChoices.SECURITY), + or get_contact_default("security") + """ + choices = PublicContact.ContactTypeChoices + contact: PublicContact + match (contact_type_choice): + case choices.ADMINISTRATIVE: + contact = self.get_default_administrative_contact() + case choices.SECURITY: + contact = self.get_default_security_contact() + case choices.TECHNICAL: + contact = self.get_default_technical_contact() + case choices.REGISTRANT: + contact = self.get_default_registrant_contact() + return contact + def generic_contact_getter( self, contact_type_choice: PublicContact.ContactTypeChoices ) -> PublicContact: @@ -734,9 +768,12 @@ class Domain(TimeStampedModel, DomainHelper): if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: desired_property = "registrant" contacts = self._get_property(desired_property) + if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: + contacts = [contacts] except KeyError as error: - logger.error("Contact does not exist") - raise error + logger.warning("generic_contact_getter -> Contact does not exist") + logger.warning(error) + return self.get_contact_default(contact_type_choice) else: print(f"generic_contact_getter -> contacts?? {contacts}") # --> Map to public contact @@ -745,9 +782,7 @@ class Domain(TimeStampedModel, DomainHelper): raise ValueError("No contact was found in cache or the registry") # Convert it from an EppLib object to PublicContact - return self.map_epp_contact_to_public_contact( - cached_contact, contact_type_choice - ) + return cached_contact def get_default_security_contact(self): """Gets the default security contact.""" @@ -781,19 +816,17 @@ class Domain(TimeStampedModel, DomainHelper): For example, check_type = 'security' """ for contact in contacts: - print(f"grab_contact_in_keys -> contact item {contact}") + print(f"grab_contact_in_keys -> contact item {contact.__dict__}") if ( - isinstance(contact, dict) - and "id" in contact.keys() - and "type" in contact.keys() - and contact["type"] == check_type + isinstance(contact, PublicContact) + and contact.registry_id is not None + and contact.contact_type is not None + and contact.contact_type == check_type ): - item = PublicContact( - registry_id=contact["id"], - contact_type=contact["type"], - ) - full_contact = self._request_contact_info(item) - return full_contact + return contact + + # If the for loop didn't do a return, + # then we know that it doesn't exist within cache # ForeignKey on UserDomainRole creates a "permissions" member for # all of the user-roles that are in place for this domain @@ -1075,10 +1108,11 @@ class Domain(TimeStampedModel, DomainHelper): "tr_date": getattr(data, "tr_date", ...), "up_date": getattr(data, "up_date", ...), } - + print(f"precleaned stuff {cache}") # remove null properties (to distinguish between "a value of None" and null) cleaned = {k: v for k, v in cache.items() if v is not ...} - + l = getattr(data, "contacts", ...) + logger.debug(f"here are the contacts {l}") # statuses can just be a list no need to keep the epp object if "statuses" in cleaned.keys(): cleaned["statuses"] = [status.state for status in cleaned["statuses"]] @@ -1090,7 +1124,12 @@ class Domain(TimeStampedModel, DomainHelper): registry_id=cleaned["registrant"], contact_type=PublicContact.ContactTypeChoices.REGISTRANT, ) - cleaned["registrant"] = self._request_contact_info(contact) + # Grabs the expanded contact + full_object = self._request_contact_info(contact) + # Maps it to type PublicContact + cleaned["registrant"] = self.map_epp_contact_to_public_contact( + full_object, contact.registry_id, contact.contact_type + ) except RegistryError: cleaned["registrant"] = None # get contact info, if there are any @@ -1100,6 +1139,7 @@ class Domain(TimeStampedModel, DomainHelper): and isinstance(cleaned["_contacts"], list) and len(cleaned["_contacts"]) ): + logger.debug("hit!") cleaned["contacts"] = [] for domainContact in cleaned["_contacts"]: # we do not use _get_or_create_* because we expect the object we @@ -1111,26 +1151,10 @@ class Domain(TimeStampedModel, DomainHelper): req = commands.InfoContact(id=domainContact.contact) data = registry.send(req, cleaned=True).res_data[0] - # extract properties from response - # (Ellipsis is used to mean "null") - # convert this to use PublicContactInstead - contact = { - "id": domainContact.contact, - "type": domainContact.type, - "auth_info": getattr(data, "auth_info", ...), - "cr_date": getattr(data, "cr_date", ...), - "disclose": getattr(data, "disclose", ...), - "email": getattr(data, "email", ...), - "fax": getattr(data, "fax", ...), - "postal_info": getattr(data, "postal_info", ...), - "statuses": getattr(data, "statuses", ...), - "tr_date": getattr(data, "tr_date", ...), - "up_date": getattr(data, "up_date", ...), - "voice": getattr(data, "voice", ...), - } - cleaned["contacts"].append( - {k: v for k, v in contact.items() if v is not ...} + self.map_epp_contact_to_public_contact( + data, domainContact.contact, domainContact.type + ) ) # get nameserver info, if there are any @@ -1182,6 +1206,8 @@ class Domain(TimeStampedModel, DomainHelper): ) if property in self._cache: + logger.debug("hit here also!!") + logger.debug(self._cache[property]) return self._cache[property] else: raise KeyError( diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 12efb0241..d527c1ef2 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -557,25 +557,25 @@ class MockEppLib(TestCase): self.hosts = hosts self.registrant = registrant - def dummyInfoContactResultData(self, id, email, contact_type): + def dummyInfoContactResultData(id, email): fake = info.InfoContactResultData( id=id, postal_info=common.PostalInfo( - name="Robert The Villain", + name="Registry Customer Service", addr=common.ContactAddr( - street=["street1", "street2", "street3"], - city="city", - pc="pc", - cc="cc", - sp="sp", + street=["4200 Wilson Blvd."], + city="Arlington", + pc="VA", + cc="US", + sp="22201", ), - org="Skim Milk", + org="Cybersecurity and Infrastructure Security Agency", type="type", ), - voice="voice", + voice="+1.8882820870", fax="+1-212-9876543", email=email, - auth_info=common.ContactAuthInfo(pw="fakepw"), + auth_info=common.ContactAuthInfo(pw="thisisnotapassword"), roid=..., statuses=[], cl_id=..., @@ -591,9 +591,13 @@ class MockEppLib(TestCase): ) return fake + mockSecurityContact = dummyInfoContactResultData("securityContact", "security@mail.gov") + mockTechnicalContact = dummyInfoContactResultData("technicalContact", "tech@mail.gov") + mockAdministrativeContact = dummyInfoContactResultData("administrativeContact", "admin@mail.gov") + mockRegistrantContact = dummyInfoContactResultData("registrantContact", "registrant@mail.gov") mockDataInfoDomain = fakedEppObject( "fakepw", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35), contacts=[common.DomainContact(contact="123", type="security")], hosts=["fake.host.com"], ) @@ -601,12 +605,12 @@ class MockEppLib(TestCase): "fakepw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), contacts=[ - common.DomainContact(contact="security", type="security"), - common.DomainContact(contact="admin", type="admin"), - common.DomainContact(contact="tech", type="tech"), + common.DomainContact(contact="securityContact", type="security"), + common.DomainContact(contact="administrativeContact", type="admin"), + common.DomainContact(contact="technicalContact", type="tech"), ], hosts=["fake.host.com"], - registrant="registrant", + registrant="registrantContact", ) infoDomainNoContact = fakedEppObject( "security", @@ -632,12 +636,20 @@ class MockEppLib(TestCase): elif getattr(_request, "name", None) == "freeman.gov": return MagicMock(res_data=[self.InfoDomainWithContacts]) elif isinstance(_request, commands.InfoContact): + # Default contact return mocked_result = self.mockDataInfoContact - if getattr(_request, "id", None) in PublicContact.ContactTypeChoices: - desired_type = getattr(_request, "id", None) - mocked_result = self.dummyInfoContactResultData( - id=desired_type, email=f"{desired_type}@mail.gov" - ) + # For testing contact types... + l = getattr(_request, "id", None) + logger.debug(f"get l'd {l}") + match getattr(_request, "id", None): + case "securityContact": + mocked_result = self.mockSecurityContact + case "technicalContact": + mocked_result = self.mockTechnicalContact + case "administrativeContact": + mocked_result = self.mockAdministrativeContact + case "registrantContact": + mocked_result = self.mockRegistrantContact return MagicMock(res_data=[mocked_result]) elif ( diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index f5b506d8b..960f019b8 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -185,11 +185,13 @@ class TestDomainCreation(TestCase): DomainInformation.objects.all().delete() DomainApplication.objects.all().delete() Domain.objects.all().delete() + User.objects.all().delete() + DraftDomain.objects.all().delete() class TestRegistrantContacts(MockEppLib): """Rule: Registrants may modify their WHOIS data""" - + def setUp(self): """ Background: @@ -201,6 +203,10 @@ class TestRegistrantContacts(MockEppLib): def tearDown(self): super().tearDown() + PublicContact.objects.all().delete() + DomainInformation.objects.all().delete() + DomainApplication.objects.all().delete() + Domain.objects.all().delete() # self.contactMailingAddressPatch.stop() # self.createContactPatch.stop() @@ -447,62 +453,116 @@ class TestRegistrantContacts(MockEppLib): Then a user-friendly error message is returned for displaying on the web """ raise - + + @skip("not implemented yet") def test_contact_getters_cache(self): """ Scenario: A user is grabbing a domain that has multiple contact objects When each contact is retrieved from cache Then the user retrieves the correct contact objects """ - domain, _ = Domain.objects.get_or_create(name="freeman.gov") + @skip("not implemented yet") + def test_epp_public_contact_mapper(self): + pass + def test_contact_getter_security(self): + domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") + + self.maxDiff = None security = PublicContact.get_default_security() security.email = "security@mail.gov" - security.domain = domain + security.domain = domain_contacts security.save() - expected_security_contact = security - domain.security_contact = security + expected_security_contact = security + + expected_security_contact = domain_contacts.map_epp_contact_to_public_contact( + self.mockSecurityContact, "securityContact", "security" + ) + + domain_contacts.security_contact = security + + contact_dict = domain_contacts.security_contact.__dict__ + expected_dict = expected_security_contact.__dict__ + + contact_dict.pop('_state') + expected_dict.pop('_state') + + self.assertEqual(contact_dict, expected_dict) + + def test_contact_getter_technical(self): + domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") + technical = PublicContact.get_default_technical() - technical.email = "technical@mail.gov" - technical.domain = domain + technical.email = "tech@mail.gov" + technical.domain = domain_contacts technical.save() - expected_technical_contact = technical - domain.technical_contact = technical + + expected_technical_contact = domain_contacts.map_epp_contact_to_public_contact( + self.mockTechnicalContact, "technicalContact", "tech" + ) + + domain_contacts.technical_contact = technical + + contact_dict = domain_contacts.technical_contact.__dict__ + expected_dict = expected_technical_contact.__dict__ + + # There has to be a better way to do this. + # Since Cache creates a new object, it causes + # a desync between each instance. Basically, + # these two objects will never be the same. + contact_dict.pop('_state') + expected_dict.pop('_state') + + self.assertEqual(contact_dict, expected_dict) + + def test_contact_getter_administrative(self): + self.maxDiff = None + domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") administrative = PublicContact.get_default_administrative() - administrative.email = "administrative@mail.gov" - administrative.domain = domain + administrative.email = "admin@mail.gov" + administrative.domain = domain_contacts administrative.save() - expected_administrative_contact = administrative - domain.administrative_contact = administrative + + expected_administrative_contact = domain_contacts.map_epp_contact_to_public_contact( + self.mockAdministrativeContact, "administrativeContact", "admin" + ) + + domain_contacts.administrative_contact = administrative + + contact_dict = domain_contacts.administrative_contact.__dict__ + expected_dict = expected_administrative_contact.__dict__ + + contact_dict.pop('_state') + expected_dict.pop('_state') + + self.assertEqual(contact_dict, expected_dict) + + def test_contact_getter_registrant(self): + domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") registrant = PublicContact.get_default_registrant() registrant.email = "registrant@mail.gov" - registrant.domain = domain + registrant.domain = domain_contacts registrant.save() + expected_registrant_contact = registrant - domain.registrant_contact = registrant + domain_contacts.registrant_contact = registrant - logger.debug(f"domain obj: {domain.security_contact.__dict__}") - logger.debug(f"expected: {expected_security_contact.__dict__}") - self.assertEqual(domain.security_contact, expected_security_contact) - self.assertEqual(domain.technical_contact, expected_technical_contact) - self.assertEqual(domain.administrative_contact, expected_administrative_contact) - self.assertEqual(domain.registrant_contact, expected_registrant_contact) + expected_registrant_contact = domain_contacts.map_epp_contact_to_public_contact( + self.mockRegistrantContact, "registrantContact", "registrant" + ) + + domain_contacts.registrant_contact = registrant - @skip("not implemented yet") - def test_contact_getters_registry(self): - """ - Scenario: A user is grabbing a domain that has multiple contact objects - When the domain is retrieved from cache - Then the user retrieves the correct domain object - """ - # Create something using infocontact for that domain - # Then just grab the domain object normally - # That 'something' doesn't exist on the local domain, - # so registry should be called - raise + contact_dict = domain_contacts.registrant_contact.__dict__ + expected_dict = expected_registrant_contact.__dict__ + + contact_dict.pop('_state') + expected_dict.pop('_state') + + self.assertEqual(contact_dict, expected_dict) class TestRegistrantNameservers(TestCase): From c8eca67ac8ce4ef193c793b27b6bc4970d1d5c99 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:21:54 -0600 Subject: [PATCH 10/58] Security email tests / bug fixes Still running into racing test conditions... Works when you run TestRegistrantContacts on its own, but when running the entire file something is happening --- src/registrar/models/domain.py | 13 +- src/registrar/tests/common.py | 18 +- src/registrar/tests/test_models_domain.py | 191 +++++++++++++++++++--- 3 files changed, 184 insertions(+), 38 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index ec1d73aa9..2cc1c2504 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -149,6 +149,7 @@ class Domain(TimeStampedModel, DomainHelper): """Called during set. Example: `domain.registrant = 'abc123'`.""" super().__set__(obj, value) # always invalidate cache after sending updates to the registry + logger.debug("cache was invalidateds") obj._invalidate_cache() def __delete__(self, obj): @@ -650,6 +651,13 @@ class Domain(TimeStampedModel, DomainHelper): # Q: I don't like this function name much, # what would be better here? + # Note for reviewers: + # This can likely be done without passing in + # contact_id and contact_type and instead embedding it inside of + # contact, but the tradeoff for that is that it unnecessarily complicates using this + # (as you'd have to create a custom dictionary), and type checking becomes weaker. + # I'm sure though that there is an easier alternative... + # TLDR: This doesn't look as pretty, but it makes using this function easier def map_epp_contact_to_public_contact( self, contact: eppInfo.InfoContactResultData, contact_id, contact_type ): @@ -767,6 +775,7 @@ class Domain(TimeStampedModel, DomainHelper): # The contact type 'registrant' is stored under a different property if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: desired_property = "registrant" + logger.debug(f"generic domain getter was called. Wanting contacts on {contact_type_choice}") contacts = self._get_property(desired_property) if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: contacts = [contacts] @@ -873,6 +882,7 @@ class Domain(TimeStampedModel, DomainHelper): while not exitEarly and count < 3: try: logger.info("Getting domain info from epp") + logger.debug(f"domain info name is... {self.__dict__}") req = commands.InfoDomain(name=self.name) domainInfo = registry.send(req, cleaned=True).res_data[0] exitEarly = True @@ -1195,6 +1205,7 @@ class Domain(TimeStampedModel, DomainHelper): def _invalidate_cache(self): """Remove cache data when updates are made.""" + logger.debug(f"cache was cleared! {self.__dict__}") self._cache = {} def _get_property(self, property): @@ -1206,7 +1217,7 @@ class Domain(TimeStampedModel, DomainHelper): ) if property in self._cache: - logger.debug("hit here also!!") + logger.debug(f"hit here also!! {property}") logger.debug(self._cache[property]) return self._cache[property] else: diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index d527c1ef2..6e562ffb1 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -557,7 +557,7 @@ class MockEppLib(TestCase): self.hosts = hosts self.registrant = registrant - def dummyInfoContactResultData(id, email): + def dummyInfoContactResultData(id, email, cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), pw="thisisnotapassword"): fake = info.InfoContactResultData( id=id, postal_info=common.PostalInfo( @@ -575,12 +575,12 @@ class MockEppLib(TestCase): voice="+1.8882820870", fax="+1-212-9876543", email=email, - auth_info=common.ContactAuthInfo(pw="thisisnotapassword"), + auth_info=common.ContactAuthInfo(pw=pw), roid=..., statuses=[], cl_id=..., cr_id=..., - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=cr_date, up_id=..., up_date=..., tr_date=..., @@ -596,8 +596,8 @@ class MockEppLib(TestCase): mockAdministrativeContact = dummyInfoContactResultData("administrativeContact", "admin@mail.gov") mockRegistrantContact = dummyInfoContactResultData("registrantContact", "registrant@mail.gov") mockDataInfoDomain = fakedEppObject( - "fakepw", - cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35), + "lastPw", + cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), contacts=[common.DomainContact(contact="123", type="security")], hosts=["fake.host.com"], ) @@ -618,11 +618,9 @@ class MockEppLib(TestCase): contacts=[], hosts=["fake.host.com"], ) - mockDataInfoContact = fakedEppObject( - "anotherPw", cr_date=datetime.datetime(2023, 7, 25, 19, 45, 35) - ) + mockDataInfoContact = dummyInfoContactResultData("123", "123@mail.gov", datetime.datetime(2023, 5, 25, 19, 45, 35), "lastPw") mockDataInfoHosts = fakedEppObject( - "lastPw", cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35) + "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35) ) def mockSend(self, _request, cleaned): @@ -639,8 +637,6 @@ class MockEppLib(TestCase): # Default contact return mocked_result = self.mockDataInfoContact # For testing contact types... - l = getattr(_request, "id", None) - logger.debug(f"get l'd {l}") match getattr(_request, "id", None): case "securityContact": mocked_result = self.mockSecurityContact diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 960f019b8..976164038 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -29,14 +29,14 @@ logger = logging.getLogger(__name__) class TestDomainCache(MockEppLib): def test_cache_sets_resets(self): """Cache should be set on getter and reset on setter calls""" - domain, _ = Domain.objects.get_or_create(name="igorville.gov") + domain, _ = Domain.objects.get_or_create(name="freeman.gov") # trigger getter _ = domain.creation_date - + domain._get_property("contacts") # getter should set the domain cache with a InfoDomain object # (see InfoDomainResult) - self.assertEquals(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) - self.assertEquals(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) + self.assertEquals(domain._cache["auth_info"], self.InfoDomainWithContacts.auth_info) + self.assertEquals(domain._cache["cr_date"], self.InfoDomainWithContacts.cr_date) self.assertFalse("avail" in domain._cache.keys()) # using a setter should clear the cache @@ -47,10 +47,13 @@ class TestDomainCache(MockEppLib): self.mockedSendFunction.assert_has_calls( [ call( - commands.InfoDomain(name="igorville.gov", auth_info=None), + commands.InfoDomain(name="freeman.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id="123", auth_info=None), cleaned=True), + call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) @@ -80,30 +83,57 @@ class TestDomainCache(MockEppLib): def test_cache_nested_elements(self): """Cache works correctly with the nested objects cache and hosts""" - domain, _ = Domain.objects.get_or_create(name="igorville.gov") + domain, _ = Domain.objects.get_or_create(name="freeman.gov") - # the cached contacts and hosts should be dictionaries of what is passed to them - expectedContactsDict = { - "id": self.mockDataInfoDomain.contacts[0].contact, - "type": self.mockDataInfoDomain.contacts[0].type, - "auth_info": self.mockDataInfoContact.auth_info, - "cr_date": self.mockDataInfoContact.cr_date, - } + self.maxDiff = None + # The contact list will initally contain objects of type 'DomainContact' + # this is then transformed into PublicContact, and cache should NOT + # hold onto the DomainContact object + expectedUnfurledContactsList = [ + common.DomainContact(contact="securityContact", type="security"), + common.DomainContact(contact="administrativeContact", type="admin"), + common.DomainContact(contact="technicalContact", type="tech"), + ] + expectedContactsList = [ + domain.map_epp_contact_to_public_contact( + self.mockSecurityContact, "securityContact", "security" + ), + domain.map_epp_contact_to_public_contact( + self.mockAdministrativeContact, "administrativeContact", "admin" + ), + domain.map_epp_contact_to_public_contact( + self.mockTechnicalContact, "technicalContact", "tech" + ), + ] expectedHostsDict = { - "name": self.mockDataInfoDomain.hosts[0], - "cr_date": self.mockDataInfoHosts.cr_date, + "name": self.InfoDomainWithContacts.hosts[0], + "cr_date": self.InfoDomainWithContacts.cr_date, } # this can be changed when the getter for contacts is implemented domain._get_property("contacts") - + # check domain info is still correct and not overridden - self.assertEqual(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) - self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) + self.assertEqual(domain._cache["auth_info"], self.InfoDomainWithContacts.auth_info) + self.assertEqual(domain._cache["cr_date"], self.InfoDomainWithContacts.cr_date) # check contacts - self.assertEqual(domain._cache["_contacts"], self.mockDataInfoDomain.contacts) - self.assertEqual(domain._cache["contacts"], [expectedContactsDict]) + self.assertEqual(domain._cache["_contacts"], self.InfoDomainWithContacts.contacts) + # The contact list should not contain what is sent by the registry by default, + # as _fetch_cache will transform the type to PublicContact + self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) + # Assert that what we get from cache is inline with our mock + # Since our cache creates new items inside of our contact list, + # as we need to map DomainContact -> PublicContact, our mocked items + # will point towards a different location in memory (as they are different objects). + # This should be a problem only exclusive to our mocks, since we are not + # replicating the same item twice outside this context. That said, we want to check + # for data integrity, but do not care if they are of the same _state or not + for cached_contact, expected_contact in zip(domain._cache["contacts"], expectedContactsList): + self.assertEqual( + {k: v for k, v in vars(cached_contact).items() if k != '_state'}, + {k: v for k, v in vars(expected_contact).items() if k != '_state'} + ) # get and check hosts is set correctly domain._get_property("hosts") @@ -207,6 +237,7 @@ class TestRegistrantContacts(MockEppLib): DomainInformation.objects.all().delete() DomainApplication.objects.all().delete() Domain.objects.all().delete() + self.domain._cache = {} # self.contactMailingAddressPatch.stop() # self.createContactPatch.stop() @@ -468,19 +499,16 @@ class TestRegistrantContacts(MockEppLib): def test_contact_getter_security(self): domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") - self.maxDiff = None security = PublicContact.get_default_security() security.email = "security@mail.gov" security.domain = domain_contacts security.save() - - expected_security_contact = security + domain_contacts.security_contact = security expected_security_contact = domain_contacts.map_epp_contact_to_public_contact( self.mockSecurityContact, "securityContact", "security" ) - domain_contacts.security_contact = security contact_dict = domain_contacts.security_contact.__dict__ expected_dict = expected_security_contact.__dict__ @@ -488,8 +516,77 @@ class TestRegistrantContacts(MockEppLib): contact_dict.pop('_state') expected_dict.pop('_state') + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="freeman.gov", auth_info=None), + cleaned=True, + ), + call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call(commands.InfoHost(name="fake.host.com"), cleaned=True), + ] + ) + self.assertEqual(contact_dict, expected_dict) - + + def test_setter_getter_security_email(self): + domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") + + expected_security_contact = domain_contacts.map_epp_contact_to_public_contact( + self.mockSecurityContact, "securityContact", "security" + ) + + + contact_dict = domain_contacts.security_contact.__dict__ + expected_dict = expected_security_contact.__dict__ + + contact_dict.pop('_state') + expected_dict.pop('_state') + + # Getter functions properly... + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="freeman.gov", auth_info=None), + cleaned=True, + ), + call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call(commands.InfoHost(name="fake.host.com"), cleaned=True), + ] + ) + + self.assertEqual(contact_dict, expected_dict) + + # Setter functions properly... + domain_contacts.security_contact.email = "converge@mail.com" + expected_security_contact.email = "converge@mail.com" + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="freeman.gov", auth_info=None), + cleaned=True, + ), + call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call(commands.InfoHost(name="fake.host.com"), cleaned=True), + ] + ) + self.assertEqual(domain_contacts.security_contact.email, expected_security_contact.email) + + @skip("not implemented yet") + def test_setter_getter_security_email_mock_user(self): + # TODO - grab the HTML content of the page, + # and verify that things have changed as expected + raise + def test_contact_getter_technical(self): domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") @@ -514,6 +611,20 @@ class TestRegistrantContacts(MockEppLib): contact_dict.pop('_state') expected_dict.pop('_state') + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="freeman.gov", auth_info=None), + cleaned=True, + ), + call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call(commands.InfoHost(name="fake.host.com"), cleaned=True), + ] + ) + self.assertEqual(contact_dict, expected_dict) def test_contact_getter_administrative(self): @@ -537,6 +648,20 @@ class TestRegistrantContacts(MockEppLib): contact_dict.pop('_state') expected_dict.pop('_state') + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="freeman.gov", auth_info=None), + cleaned=True, + ), + call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call(commands.InfoHost(name="fake.host.com"), cleaned=True), + ] + ) + self.assertEqual(contact_dict, expected_dict) def test_contact_getter_registrant(self): @@ -562,6 +687,20 @@ class TestRegistrantContacts(MockEppLib): contact_dict.pop('_state') expected_dict.pop('_state') + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="freeman.gov", auth_info=None), + cleaned=True, + ), + call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call(commands.InfoHost(name="fake.host.com"), cleaned=True), + ] + ) + self.assertEqual(contact_dict, expected_dict) From bf36b5a5e34848718a0490b4181a5192d44c34be Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:56:14 -0600 Subject: [PATCH 11/58] Deleting migrations locally --- src/registrar/migrations/0001_initial.py | 451 ------------------ ..._domain_host_nameserver_hostip_and_more.py | 164 ------- ...napplication_is_election_board_and_more.py | 98 ---- .../0004_domainapplication_federal_agency.py | 20 - .../0005_domainapplication_city_and_more.py | 29 -- .../migrations/0006_alter_contact_phone.py | 25 - ..._more_organization_information_and_more.py | 130 ----- ..._remove_userprofile_created_at_and_more.py | 47 -- ...ion_federally_recognized_tribe_and_more.py | 31 -- ...application_no_other_contacts_rationale.py | 21 - ...remove_domainapplication_security_email.py | 16 - .../migrations/0012_delete_userprofile.py | 17 - .../0013_publiccontact_contact_user.py | 68 --- .../0014_user_phone_alter_contact_user.py | 27 -- ...rs_userdomainrole_user_domains_and_more.py | 66 --- .../migrations/0016_domaininvitation.py | 51 -- ...alter_domainapplication_status_and_more.py | 38 -- .../migrations/0018_domaininformation.py | 273 ----------- ...ter_domainapplication_organization_type.py | 47 -- ...remove_domaininformation_security_email.py | 16 - ...main_publiccontact_registry_id_and_more.py | 122 ----- ...ainapplication_approved_domain_and_more.py | 66 --- ...t_name_alter_contact_last_name_and_more.py | 44 -- .../migrations/0024_alter_contact_email.py | 19 - ...unique_domain_name_in_registry_and_more.py | 47 -- ...omainapplication_address_line2_and_more.py | 26 - ...omaininformation_address_line1_and_more.py | 53 -- .../0028_alter_domainapplication_status.py | 32 -- ...r_status_alter_domainapplication_status.py | 42 -- .../migrations/0030_alter_user_status.py | 23 - .../0031_transitiondomain_and_more.py | 122 ----- src/registrar/migrations/__init__.py | 0 32 files changed, 2231 deletions(-) delete mode 100644 src/registrar/migrations/0001_initial.py delete mode 100644 src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py delete mode 100644 src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py delete mode 100644 src/registrar/migrations/0004_domainapplication_federal_agency.py delete mode 100644 src/registrar/migrations/0005_domainapplication_city_and_more.py delete mode 100644 src/registrar/migrations/0006_alter_contact_phone.py delete mode 100644 src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py delete mode 100644 src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py delete mode 100644 src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py delete mode 100644 src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py delete mode 100644 src/registrar/migrations/0011_remove_domainapplication_security_email.py delete mode 100644 src/registrar/migrations/0012_delete_userprofile.py delete mode 100644 src/registrar/migrations/0013_publiccontact_contact_user.py delete mode 100644 src/registrar/migrations/0014_user_phone_alter_contact_user.py delete mode 100644 src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py delete mode 100644 src/registrar/migrations/0016_domaininvitation.py delete mode 100644 src/registrar/migrations/0017_alter_domainapplication_status_and_more.py delete mode 100644 src/registrar/migrations/0018_domaininformation.py delete mode 100644 src/registrar/migrations/0019_alter_domainapplication_organization_type.py delete mode 100644 src/registrar/migrations/0020_remove_domaininformation_security_email.py delete mode 100644 src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py delete mode 100644 src/registrar/migrations/0022_draftdomain_domainapplication_approved_domain_and_more.py delete mode 100644 src/registrar/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py delete mode 100644 src/registrar/migrations/0024_alter_contact_email.py delete mode 100644 src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py delete mode 100644 src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py delete mode 100644 src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py delete mode 100644 src/registrar/migrations/0028_alter_domainapplication_status.py delete mode 100644 src/registrar/migrations/0029_user_status_alter_domainapplication_status.py delete mode 100644 src/registrar/migrations/0030_alter_user_status.py delete mode 100644 src/registrar/migrations/0031_transitiondomain_and_more.py delete mode 100644 src/registrar/migrations/__init__.py diff --git a/src/registrar/migrations/0001_initial.py b/src/registrar/migrations/0001_initial.py deleted file mode 100644 index 78f0c5b66..000000000 --- a/src/registrar/migrations/0001_initial.py +++ /dev/null @@ -1,451 +0,0 @@ -# Generated by Django 4.1.3 on 2022-11-10 14:23 - -from django.conf import settings -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import django_fsm # type: ignore - - -class Migration(migrations.Migration): - initial = True - - dependencies = [ - ("auth", "0012_alter_user_first_name_max_length"), - ] - - operations = [ - migrations.CreateModel( - name="User", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("password", models.CharField(max_length=128, verbose_name="password")), - ( - "last_login", - models.DateTimeField( - blank=True, null=True, verbose_name="last login" - ), - ), - ( - "is_superuser", - models.BooleanField( - default=False, - help_text="Designates that this user has all permissions without explicitly assigning them.", - verbose_name="superuser status", - ), - ), - ( - "username", - models.CharField( - error_messages={ - "unique": "A user with that username already exists." - }, - help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", - max_length=150, - unique=True, - validators=[ - django.contrib.auth.validators.UnicodeUsernameValidator() - ], - verbose_name="username", - ), - ), - ( - "first_name", - models.CharField( - blank=True, max_length=150, verbose_name="first name" - ), - ), - ( - "last_name", - models.CharField( - blank=True, max_length=150, verbose_name="last name" - ), - ), - ( - "email", - models.EmailField( - blank=True, max_length=254, verbose_name="email address" - ), - ), - ( - "is_staff", - models.BooleanField( - default=False, - help_text="Designates whether the user can log into this admin site.", - verbose_name="staff status", - ), - ), - ( - "is_active", - models.BooleanField( - default=True, - help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", - verbose_name="active", - ), - ), - ( - "date_joined", - models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" - ), - ), - ( - "groups", - models.ManyToManyField( - blank=True, - help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", - related_name="user_set", - related_query_name="user", - to="auth.group", - verbose_name="groups", - ), - ), - ( - "user_permissions", - models.ManyToManyField( - blank=True, - help_text="Specific permissions for this user.", - related_name="user_set", - related_query_name="user", - to="auth.permission", - verbose_name="user permissions", - ), - ), - ], - options={ - "verbose_name": "user", - "verbose_name_plural": "users", - "abstract": False, - }, - managers=[ - ("objects", django.contrib.auth.models.UserManager()), - ], - ), - migrations.CreateModel( - name="Contact", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "first_name", - models.TextField( - blank=True, db_index=True, help_text="First name", null=True - ), - ), - ( - "middle_name", - models.TextField(blank=True, help_text="Middle name", null=True), - ), - ( - "last_name", - models.TextField( - blank=True, db_index=True, help_text="Last name", null=True - ), - ), - ("title", models.TextField(blank=True, help_text="Title", null=True)), - ( - "email", - models.TextField( - blank=True, db_index=True, help_text="Email", null=True - ), - ), - ( - "phone", - models.TextField( - blank=True, db_index=True, help_text="Phone", null=True - ), - ), - ], - ), - migrations.CreateModel( - name="Website", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("website", models.CharField(max_length=255)), - ], - ), - migrations.CreateModel( - name="DomainApplication", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "status", - django_fsm.FSMField( - choices=[ - ("started", "started"), - ("submitted", "submitted"), - ("investigating", "investigating"), - ("approved", "approved"), - ], - default="started", - max_length=50, - ), - ), - ( - "organization_type", - models.CharField( - blank=True, - choices=[ - ("federal", "a federal agency"), - ("interstate", "an organization of two or more states"), - ( - "state_or_territory", - "one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands", - ), - ( - "tribal", - "a tribal government recognized by the federal or state government", - ), - ("county", "a county, parish, or borough"), - ("city", "a city, town, township, village, etc."), - ( - "special_district", - "an independent organization within a single state", - ), - ], - help_text="Type of Organization", - max_length=255, - null=True, - ), - ), - ( - "federal_branch", - models.CharField( - blank=True, - choices=[ - ("Executive", "Executive"), - ("Judicial", "Judicial"), - ("Legislative", "Legislative"), - ], - help_text="Branch of federal government", - max_length=50, - null=True, - ), - ), - ( - "is_election_office", - models.BooleanField( - blank=True, - help_text="Is your organization an election office?", - null=True, - ), - ), - ( - "organization_name", - models.TextField( - blank=True, - db_index=True, - help_text="Organization name", - null=True, - ), - ), - ( - "street_address", - models.TextField(blank=True, help_text="Street Address", null=True), - ), - ( - "unit_type", - models.CharField( - blank=True, help_text="Unit type", max_length=15, null=True - ), - ), - ( - "unit_number", - models.CharField( - blank=True, help_text="Unit number", max_length=255, null=True - ), - ), - ( - "state_territory", - models.CharField( - blank=True, help_text="State/Territory", max_length=2, null=True - ), - ), - ( - "zip_code", - models.CharField( - blank=True, - db_index=True, - help_text="ZIP code", - max_length=10, - null=True, - ), - ), - ( - "purpose", - models.TextField( - blank=True, help_text="Purpose of the domain", null=True - ), - ), - ( - "security_email", - models.CharField( - blank=True, - help_text="Security email for public use", - max_length=320, - null=True, - ), - ), - ( - "anything_else", - models.TextField( - blank=True, help_text="Anything else we should know?", null=True - ), - ), - ( - "acknowledged_policy", - models.BooleanField( - blank=True, - help_text="Acknowledged .gov acceptable use policy", - null=True, - ), - ), - ( - "alternative_domains", - models.ManyToManyField( - blank=True, related_name="alternatives+", to="registrar.website" - ), - ), - ( - "authorizing_official", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="authorizing_official", - to="registrar.contact", - ), - ), - ( - "creator", - models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - related_name="applications_created", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "current_websites", - models.ManyToManyField( - blank=True, related_name="current+", to="registrar.website" - ), - ), - ( - "investigator", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="applications_investigating", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "other_contacts", - models.ManyToManyField( - blank=True, - related_name="contact_applications", - to="registrar.contact", - ), - ), - ( - "requested_domain", - models.ForeignKey( - blank=True, - help_text="The requested domain", - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="requested+", - to="registrar.website", - ), - ), - ( - "submitter", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="submitted_applications", - to="registrar.contact", - ), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.CreateModel( - name="UserProfile", - fields=[ - ( - "contact_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="registrar.contact", - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ("street1", models.TextField(blank=True)), - ("street2", models.TextField(blank=True)), - ("street3", models.TextField(blank=True)), - ("city", models.TextField(blank=True)), - ("sp", models.TextField(blank=True)), - ("pc", models.TextField(blank=True)), - ("cc", models.TextField(blank=True)), - ("display_name", models.TextField()), - ( - "user", - models.OneToOneField( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "abstract": False, - }, - bases=("registrar.contact", models.Model), - ), - ] diff --git a/src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py b/src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py deleted file mode 100644 index f1049c252..000000000 --- a/src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py +++ /dev/null @@ -1,164 +0,0 @@ -# Generated by Django 4.1.3 on 2022-11-28 19:07 - -from django.conf import settings -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import django_fsm # type: ignore - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0001_initial"), - ] - - operations = [ - migrations.CreateModel( - name="Domain", - 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, - ), - ), - ( - "is_active", - django_fsm.FSMField( - choices=[(True, "Yes"), (False, "No")], - default=False, - help_text="Domain is live in the registry", - max_length=50, - ), - ), - ("owners", models.ManyToManyField(to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name="Host", - 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, - unique=True, - ), - ), - ( - "domain", - models.ForeignKey( - help_text="Domain to which this host belongs", - on_delete=django.db.models.deletion.PROTECT, - related_name="host", - to="registrar.domain", - ), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.CreateModel( - name="Nameserver", - fields=[ - ( - "host_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="registrar.host", - ), - ), - ], - options={ - "abstract": False, - }, - bases=("registrar.host",), - ), - migrations.CreateModel( - name="HostIP", - 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)), - ( - "address", - models.CharField( - default=None, - help_text="IP address", - max_length=46, - validators=[django.core.validators.validate_ipv46_address], - ), - ), - ( - "host", - models.ForeignKey( - help_text="Host to which this IP address belongs", - on_delete=django.db.models.deletion.PROTECT, - related_name="ip", - to="registrar.host", - ), - ), - ], - options={ - "abstract": False, - }, - ), - 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.domain", - ), - ), - migrations.AddConstraint( - model_name="domain", - constraint=models.UniqueConstraint( - condition=models.Q(("is_active", True)), - fields=("name",), - name="unique_domain_name_in_registry", - ), - ), - ] diff --git a/src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py b/src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py deleted file mode 100644 index c12ca9d34..000000000 --- a/src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py +++ /dev/null @@ -1,98 +0,0 @@ -# Generated by Django 4.1.3 on 2022-12-02 21:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0002_domain_host_nameserver_hostip_and_more"), - ] - - operations = [ - migrations.RenameField( - model_name="domainapplication", - old_name="is_election_office", - new_name="is_election_board", - ), - migrations.RenameField( - model_name="domainapplication", - old_name="acknowledged_policy", - new_name="is_policy_acknowledged", - ), - migrations.RenameField( - model_name="domainapplication", - old_name="zip_code", - new_name="zipcode", - ), - migrations.RemoveField( - model_name="domainapplication", - name="federal_branch", - ), - migrations.RemoveField( - model_name="domainapplication", - name="street_address", - ), - migrations.RemoveField( - model_name="domainapplication", - name="unit_number", - ), - migrations.RemoveField( - model_name="domainapplication", - name="unit_type", - ), - migrations.AddField( - model_name="domainapplication", - name="address_line1", - field=models.TextField(blank=True, help_text="Address line 1", null=True), - ), - migrations.AddField( - model_name="domainapplication", - name="address_line2", - field=models.CharField( - blank=True, help_text="Address line 2", max_length=15, null=True - ), - ), - migrations.AddField( - model_name="domainapplication", - name="federal_type", - field=models.CharField( - blank=True, - choices=[ - ("executive", "Executive"), - ("judicial", "Judicial"), - ("legislative", "Legislative"), - ], - help_text="Branch of federal government", - max_length=50, - null=True, - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="organization_type", - field=models.CharField( - blank=True, - choices=[ - ("federal", "Federal: a federal agency"), - ("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 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", - ), - ], - help_text="Type of Organization", - max_length=255, - null=True, - ), - ), - ] diff --git a/src/registrar/migrations/0004_domainapplication_federal_agency.py b/src/registrar/migrations/0004_domainapplication_federal_agency.py deleted file mode 100644 index a00d46ac2..000000000 --- a/src/registrar/migrations/0004_domainapplication_federal_agency.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.1.3 on 2022-12-07 15:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ( - "registrar", - "0003_rename_is_election_office_domainapplication_is_election_board_and_more", - ), - ] - - operations = [ - migrations.AddField( - model_name="domainapplication", - name="federal_agency", - field=models.TextField(help_text="Top level federal agency", null=True), - ), - ] diff --git a/src/registrar/migrations/0005_domainapplication_city_and_more.py b/src/registrar/migrations/0005_domainapplication_city_and_more.py deleted file mode 100644 index 3d1fc1de1..000000000 --- a/src/registrar/migrations/0005_domainapplication_city_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 4.1.3 on 2022-12-12 21:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0004_domainapplication_federal_agency"), - ] - - operations = [ - migrations.AddField( - model_name="domainapplication", - name="city", - field=models.TextField(blank=True, help_text="City", null=True), - ), - migrations.AddField( - model_name="domainapplication", - name="urbanization", - field=models.TextField(blank=True, help_text="Urbanization", null=True), - ), - migrations.AlterField( - model_name="domainapplication", - name="federal_agency", - field=models.TextField( - blank=True, help_text="Top level federal agency", null=True - ), - ), - ] diff --git a/src/registrar/migrations/0006_alter_contact_phone.py b/src/registrar/migrations/0006_alter_contact_phone.py deleted file mode 100644 index 1e055694f..000000000 --- a/src/registrar/migrations/0006_alter_contact_phone.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.1.4 on 2022-12-14 20:48 - -from django.db import migrations -import phonenumber_field.modelfields # type: ignore - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0005_domainapplication_city_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="contact", - name="phone", - field=phonenumber_field.modelfields.PhoneNumberField( - blank=True, - db_index=True, - help_text="Phone", - max_length=128, - null=True, - region=None, - ), - ), - ] diff --git a/src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py b/src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py deleted file mode 100644 index 909b301ab..000000000 --- a/src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py +++ /dev/null @@ -1,130 +0,0 @@ -# Generated by Django 4.1.5 on 2023-01-10 20:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0006_alter_contact_phone"), - ] - - operations = [ - migrations.AddField( - model_name="domainapplication", - name="more_organization_information", - field=models.TextField( - blank=True, - help_text="More information about your organization", - null=True, - ), - ), - migrations.AddField( - model_name="domainapplication", - name="type_of_work", - field=models.TextField( - blank=True, help_text="Type of work of the organization", null=True - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="address_line1", - field=models.TextField(blank=True, help_text="Street address", null=True), - ), - migrations.AlterField( - model_name="domainapplication", - name="address_line2", - field=models.CharField( - blank=True, help_text="Street address line 2", max_length=15, null=True - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="federal_agency", - field=models.TextField(blank=True, help_text="Federal agency", null=True), - ), - migrations.AlterField( - model_name="domainapplication", - name="federal_type", - field=models.CharField( - blank=True, - choices=[ - ("executive", "Executive"), - ("judicial", "Judicial"), - ("legislative", "Legislative"), - ], - help_text="Federal government branch", - max_length=50, - null=True, - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="organization_type", - field=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, - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="purpose", - field=models.TextField( - blank=True, help_text="Purpose of your domain", null=True - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="state_territory", - field=models.CharField( - blank=True, - help_text="State, territory, or military post", - max_length=2, - null=True, - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="urbanization", - field=models.TextField( - blank=True, help_text="Urbanization (Puerto Rico only)", null=True - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="zipcode", - field=models.CharField( - blank=True, - db_index=True, - help_text="Zip code", - max_length=10, - null=True, - ), - ), - ] diff --git a/src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py b/src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py deleted file mode 100644 index bb52e4320..000000000 --- a/src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 4.1.5 on 2023-01-13 01:54 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0007_domainapplication_more_organization_information_and_more"), - ] - - operations = [ - migrations.RemoveField( - model_name="userprofile", - name="created_at", - ), - migrations.RemoveField( - model_name="userprofile", - name="updated_at", - ), - migrations.AddField( - model_name="contact", - name="created_at", - field=models.DateTimeField( - auto_now_add=True, default=django.utils.timezone.now - ), - preserve_default=False, - ), - migrations.AddField( - model_name="contact", - name="updated_at", - field=models.DateTimeField(auto_now=True), - ), - migrations.AddField( - model_name="website", - name="created_at", - field=models.DateTimeField( - auto_now_add=True, default=django.utils.timezone.now - ), - preserve_default=False, - ), - migrations.AddField( - model_name="website", - name="updated_at", - field=models.DateTimeField(auto_now=True), - ), - ] diff --git a/src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py b/src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py deleted file mode 100644 index 3010b6cd4..000000000 --- a/src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.1.5 on 2023-01-25 17:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0008_remove_userprofile_created_at_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="domainapplication", - name="federally_recognized_tribe", - field=models.BooleanField( - help_text="Is the tribe federally recognized", null=True - ), - ), - migrations.AddField( - model_name="domainapplication", - name="state_recognized_tribe", - field=models.BooleanField( - help_text="Is the tribe recognized by a state", null=True - ), - ), - migrations.AddField( - model_name="domainapplication", - name="tribe_name", - field=models.TextField(blank=True, help_text="Name of tribe", null=True), - ), - ] diff --git a/src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py b/src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py deleted file mode 100644 index bf8d83d60..000000000 --- a/src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.1.5 on 2023-02-06 14:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0009_domainapplication_federally_recognized_tribe_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="domainapplication", - name="no_other_contacts_rationale", - field=models.TextField( - blank=True, - help_text="Reason for listing no additional contacts", - null=True, - ), - ), - ] diff --git a/src/registrar/migrations/0011_remove_domainapplication_security_email.py b/src/registrar/migrations/0011_remove_domainapplication_security_email.py deleted file mode 100644 index c717408da..000000000 --- a/src/registrar/migrations/0011_remove_domainapplication_security_email.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 4.1.6 on 2023-02-27 18:35 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0010_domainapplication_no_other_contacts_rationale"), - ] - - operations = [ - migrations.RemoveField( - model_name="domainapplication", - name="security_email", - ), - ] diff --git a/src/registrar/migrations/0012_delete_userprofile.py b/src/registrar/migrations/0012_delete_userprofile.py deleted file mode 100644 index b5bcebb95..000000000 --- a/src/registrar/migrations/0012_delete_userprofile.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.1.6 on 2023-03-07 14:31 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0011_remove_domainapplication_security_email"), - ] - - operations = [ - migrations.DeleteModel( - name="UserProfile", - ), - ] diff --git a/src/registrar/migrations/0013_publiccontact_contact_user.py b/src/registrar/migrations/0013_publiccontact_contact_user.py deleted file mode 100644 index 29a9385cd..000000000 --- a/src/registrar/migrations/0013_publiccontact_contact_user.py +++ /dev/null @@ -1,68 +0,0 @@ -# Generated by Django 4.1.6 on 2023-03-07 14:31 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0012_delete_userprofile"), - ] - - operations = [ - migrations.CreateModel( - name="PublicContact", - 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)), - ( - "contact_type", - models.CharField( - choices=[ - ("registrant", "Registrant"), - ("administrative", "Administrative"), - ("technical", "Technical"), - ("security", "Security"), - ], - max_length=14, - ), - ), - ("name", models.TextField()), - ("org", models.TextField(null=True)), - ("street1", models.TextField()), - ("street2", models.TextField(null=True)), - ("street3", models.TextField(null=True)), - ("city", models.TextField()), - ("sp", models.TextField()), - ("pc", models.TextField()), - ("cc", models.TextField()), - ("email", models.TextField()), - ("voice", models.TextField()), - ("fax", models.TextField(null=True)), - ("pw", models.TextField()), - ], - options={ - "abstract": False, - }, - ), - migrations.AddField( - model_name="contact", - name="user", - field=models.OneToOneField( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), - ), - ] diff --git a/src/registrar/migrations/0014_user_phone_alter_contact_user.py b/src/registrar/migrations/0014_user_phone_alter_contact_user.py deleted file mode 100644 index 452b2b9d5..000000000 --- a/src/registrar/migrations/0014_user_phone_alter_contact_user.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.1.6 on 2023-03-07 16:39 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import phonenumber_field.modelfields # type: ignore - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0013_publiccontact_contact_user"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="phone", - field=phonenumber_field.modelfields.PhoneNumberField( - blank=True, - db_index=True, - help_text="Phone", - max_length=128, - null=True, - region=None, - ), - ), - ] diff --git a/src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py b/src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py deleted file mode 100644 index 2fbc5ab19..000000000 --- a/src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by Django 4.1.6 on 2023-03-10 15:32 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0014_user_phone_alter_contact_user"), - ] - - operations = [ - migrations.RemoveField( - model_name="domain", - name="owners", - ), - migrations.CreateModel( - name="UserDomainRole", - 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)), - ("role", models.TextField(choices=[("admin", "Admin")])), - ( - "domain", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="permissions", - to="registrar.domain", - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="permissions", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - migrations.AddField( - model_name="user", - name="domains", - field=models.ManyToManyField( - related_name="users", - through="registrar.UserDomainRole", - to="registrar.domain", - ), - ), - migrations.AddConstraint( - model_name="userdomainrole", - constraint=models.UniqueConstraint( - fields=("user", "domain"), name="unique_user_domain_role" - ), - ), - ] diff --git a/src/registrar/migrations/0016_domaininvitation.py b/src/registrar/migrations/0016_domaininvitation.py deleted file mode 100644 index f7756ef1d..000000000 --- a/src/registrar/migrations/0016_domaininvitation.py +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by Django 4.1.6 on 2023-03-24 16:56 - -from django.db import migrations, models -import django.db.models.deletion -import django_fsm # type: ignore - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0015_remove_domain_owners_userdomainrole_user_domains_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="DomainInvitation", - 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)), - ("email", models.EmailField(max_length=254)), - ( - "status", - django_fsm.FSMField( - choices=[("sent", "sent"), ("retrieved", "retrieved")], - default="sent", - max_length=50, - protected=True, - ), - ), - ( - "domain", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="invitations", - to="registrar.domain", - ), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/src/registrar/migrations/0017_alter_domainapplication_status_and_more.py b/src/registrar/migrations/0017_alter_domainapplication_status_and_more.py deleted file mode 100644 index 5d20551d7..000000000 --- a/src/registrar/migrations/0017_alter_domainapplication_status_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.1.6 on 2023-04-13 18:38 - -from django.db import migrations -import django_fsm # type: ignore - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0016_domaininvitation"), - ] - - operations = [ - migrations.AlterField( - model_name="domainapplication", - name="status", - field=django_fsm.FSMField( - choices=[ - ("started", "started"), - ("submitted", "submitted"), - ("investigating", "investigating"), - ("approved", "approved"), - ("withdrawn", "withdrawn"), - ], - default="started", - max_length=50, - ), - ), - migrations.AlterField( - model_name="domaininvitation", - name="status", - field=django_fsm.FSMField( - choices=[("invited", "invited"), ("retrieved", "retrieved")], - default="invited", - max_length=50, - protected=True, - ), - ), - ] diff --git a/src/registrar/migrations/0018_domaininformation.py b/src/registrar/migrations/0018_domaininformation.py deleted file mode 100644 index 408fa048b..000000000 --- a/src/registrar/migrations/0018_domaininformation.py +++ /dev/null @@ -1,273 +0,0 @@ -# Generated by Django 4.1.6 on 2023-05-08 15:30 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0017_alter_domainapplication_status_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="DomainInformation", - 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)), - ( - "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, - ), - ), - ( - "security_email", - models.EmailField( - blank=True, - help_text="Security email for public use", - max_length=320, - null=True, - ), - ), - ( - "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.OneToOneField( - 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", - ), - ), - ( - "domain_application", - models.OneToOneField( - blank=True, - help_text="Associated domain application", - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="domainapplication_info", - to="registrar.domainapplication", - ), - ), - ( - "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={ - "verbose_name_plural": "Domain Information", - }, - ), - ] diff --git a/src/registrar/migrations/0019_alter_domainapplication_organization_type.py b/src/registrar/migrations/0019_alter_domainapplication_organization_type.py deleted file mode 100644 index 1a7397255..000000000 --- a/src/registrar/migrations/0019_alter_domainapplication_organization_type.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 4.1.6 on 2023-05-09 19:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0018_domaininformation"), - ] - - operations = [ - migrations.AlterField( - model_name="domainapplication", - name="organization_type", - field=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, - ), - ), - ] diff --git a/src/registrar/migrations/0020_remove_domaininformation_security_email.py b/src/registrar/migrations/0020_remove_domaininformation_security_email.py deleted file mode 100644 index 9742c294a..000000000 --- a/src/registrar/migrations/0020_remove_domaininformation_security_email.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 4.2 on 2023-05-17 17:05 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0019_alter_domainapplication_organization_type"), - ] - - operations = [ - migrations.RemoveField( - model_name="domaininformation", - name="security_email", - ), - ] diff --git a/src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py b/src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py deleted file mode 100644 index 35e07fe71..000000000 --- a/src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py +++ /dev/null @@ -1,122 +0,0 @@ -# Generated by Django 4.2.1 on 2023-05-25 15:03 - -from django.db import migrations, models -import django.db.models.deletion -import registrar.models.public_contact - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0020_remove_domaininformation_security_email"), - ] - - operations = [ - migrations.AddField( - model_name="publiccontact", - name="domain", - field=models.ForeignKey( - default=1, - on_delete=django.db.models.deletion.PROTECT, - related_name="contacts", - to="registrar.domain", - ), - preserve_default=False, - ), - migrations.AddField( - model_name="publiccontact", - name="registry_id", - field=models.CharField( - default=registrar.models.public_contact.get_id, - help_text="Auto generated ID to track this contact in the registry", - max_length=16, - ), - ), - migrations.AlterField( - model_name="publiccontact", - name="cc", - field=models.TextField(help_text="Contact's country code"), - ), - migrations.AlterField( - model_name="publiccontact", - name="city", - field=models.TextField(help_text="Contact's city"), - ), - migrations.AlterField( - model_name="publiccontact", - name="contact_type", - field=models.CharField( - choices=[ - ("registrant", "Registrant"), - ("administrative", "Administrative"), - ("technical", "Technical"), - ("security", "Security"), - ], - help_text="For which type of WHOIS contact", - max_length=14, - ), - ), - migrations.AlterField( - model_name="publiccontact", - name="email", - field=models.TextField(help_text="Contact's email address"), - ), - migrations.AlterField( - model_name="publiccontact", - name="fax", - field=models.TextField( - help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.", - null=True, - ), - ), - migrations.AlterField( - model_name="publiccontact", - name="name", - field=models.TextField(help_text="Contact's full name"), - ), - migrations.AlterField( - model_name="publiccontact", - name="org", - field=models.TextField( - help_text="Contact's organization (null ok)", null=True - ), - ), - migrations.AlterField( - model_name="publiccontact", - name="pc", - field=models.TextField(help_text="Contact's postal code"), - ), - migrations.AlterField( - model_name="publiccontact", - name="pw", - field=models.TextField( - help_text="Contact's authorization code. 16 characters minimum." - ), - ), - migrations.AlterField( - model_name="publiccontact", - name="sp", - field=models.TextField(help_text="Contact's state or province"), - ), - migrations.AlterField( - model_name="publiccontact", - name="street1", - field=models.TextField(help_text="Contact's street"), - ), - migrations.AlterField( - model_name="publiccontact", - name="street2", - field=models.TextField(help_text="Contact's street (null ok)", null=True), - ), - migrations.AlterField( - model_name="publiccontact", - name="street3", - field=models.TextField(help_text="Contact's street (null ok)", null=True), - ), - migrations.AlterField( - model_name="publiccontact", - name="voice", - field=models.TextField( - help_text="Contact's phone number. Must be in ITU.E164.2005 format" - ), - ), - ] 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 deleted file mode 100644 index fb89e0eb2..000000000 --- a/src/registrar/migrations/0022_draftdomain_domainapplication_approved_domain_and_more.py +++ /dev/null @@ -1,66 +0,0 @@ -# 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/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py b/src/registrar/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py deleted file mode 100644 index b2259f650..000000000 --- a/src/registrar/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 4.2.1 on 2023-05-31 23:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0022_draftdomain_domainapplication_approved_domain_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="contact", - name="first_name", - field=models.TextField( - blank=True, - db_index=True, - help_text="First name", - null=True, - verbose_name="first name / given name", - ), - ), - migrations.AlterField( - model_name="contact", - name="last_name", - field=models.TextField( - blank=True, - db_index=True, - help_text="Last name", - null=True, - verbose_name="last name / family name", - ), - ), - migrations.AlterField( - model_name="contact", - name="title", - field=models.TextField( - blank=True, - help_text="Title", - null=True, - verbose_name="title or role in your organization", - ), - ), - ] diff --git a/src/registrar/migrations/0024_alter_contact_email.py b/src/registrar/migrations/0024_alter_contact_email.py deleted file mode 100644 index f512d5d82..000000000 --- a/src/registrar/migrations/0024_alter_contact_email.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.1 on 2023-06-01 19:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0023_alter_contact_first_name_alter_contact_last_name_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="contact", - name="email", - field=models.EmailField( - blank=True, db_index=True, help_text="Email", max_length=254, null=True - ), - ), - ] diff --git a/src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py b/src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py deleted file mode 100644 index f9f5876b1..000000000 --- a/src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 4.2.1 on 2023-06-01 21:47 - -from django.db import migrations -import django_fsm # type: ignore -import registrar.models.utility.domain_field - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0024_alter_contact_email"), - ] - - operations = [ - migrations.RemoveConstraint( - model_name="domain", - name="unique_domain_name_in_registry", - ), - migrations.RemoveField( - model_name="domain", - name="is_active", - ), - migrations.AddField( - model_name="domain", - name="state", - field=django_fsm.FSMField( - choices=[ - ("created", "Created"), - ("deleted", "Deleted"), - ("unknown", "Unknown"), - ], - default="unknown", - help_text="Very basic info about the lifecycle of this domain object", - max_length=21, - protected=True, - ), - ), - migrations.AlterField( - model_name="domain", - name="name", - field=registrar.models.utility.domain_field.DomainField( - default=None, - help_text="Fully qualified domain name", - max_length=253, - unique=True, - ), - ), - ] diff --git a/src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py b/src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py deleted file mode 100644 index 6e28f5cbb..000000000 --- a/src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 4.2.1 on 2023-06-02 17:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0025_remove_domain_unique_domain_name_in_registry_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="domainapplication", - name="address_line2", - field=models.TextField( - blank=True, help_text="Street address line 2", null=True - ), - ), - migrations.AlterField( - model_name="domaininformation", - name="address_line2", - field=models.TextField( - blank=True, help_text="Street address line 2", null=True - ), - ), - ] diff --git a/src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py b/src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py deleted file mode 100644 index 9f362c956..000000000 --- a/src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 4.2.1 on 2023-06-09 16:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0026_alter_domainapplication_address_line2_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="domaininformation", - name="address_line1", - field=models.TextField( - blank=True, - help_text="Street address", - null=True, - verbose_name="Street address", - ), - ), - migrations.AlterField( - model_name="domaininformation", - name="address_line2", - field=models.TextField( - blank=True, - help_text="Street address line 2", - null=True, - verbose_name="Street address line 2", - ), - ), - migrations.AlterField( - model_name="domaininformation", - name="state_territory", - field=models.CharField( - blank=True, - help_text="State, territory, or military post", - max_length=2, - null=True, - verbose_name="State, territory, or military post", - ), - ), - migrations.AlterField( - model_name="domaininformation", - name="urbanization", - field=models.TextField( - blank=True, - help_text="Urbanization (Puerto Rico only)", - null=True, - verbose_name="Urbanization (Puerto Rico only)", - ), - ), - ] diff --git a/src/registrar/migrations/0028_alter_domainapplication_status.py b/src/registrar/migrations/0028_alter_domainapplication_status.py deleted file mode 100644 index 61b1c0505..000000000 --- a/src/registrar/migrations/0028_alter_domainapplication_status.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-12 21:31 -# Generated by Django 4.2.2 on 2023-07-13 17:56 -# hand merged - -from django.db import migrations -import django_fsm - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0027_alter_domaininformation_address_line1_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="domainapplication", - name="status", - field=django_fsm.FSMField( - choices=[ - ("started", "started"), - ("submitted", "submitted"), - ("in review", "in review"), - ("action needed", "action needed"), - ("approved", "approved"), - ("withdrawn", "withdrawn"), - ("rejected", "rejected"), - ], - default="started", - max_length=50, - ), - ), - ] diff --git a/src/registrar/migrations/0029_user_status_alter_domainapplication_status.py b/src/registrar/migrations/0029_user_status_alter_domainapplication_status.py deleted file mode 100644 index 504358665..000000000 --- a/src/registrar/migrations/0029_user_status_alter_domainapplication_status.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 4.2.1 on 2023-08-18 16:59 - -from django.db import migrations, models -import django_fsm - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0028_alter_domainapplication_status"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="status", - field=models.CharField( - blank=True, - choices=[("ineligible", "ineligible")], - default=None, - max_length=10, - null=True, - ), - ), - migrations.AlterField( - model_name="domainapplication", - name="status", - field=django_fsm.FSMField( - choices=[ - ("started", "started"), - ("submitted", "submitted"), - ("in review", "in review"), - ("action needed", "action needed"), - ("approved", "approved"), - ("withdrawn", "withdrawn"), - ("rejected", "rejected"), - ("ineligible", "ineligible"), - ], - default="started", - max_length=50, - ), - ), - ] diff --git a/src/registrar/migrations/0030_alter_user_status.py b/src/registrar/migrations/0030_alter_user_status.py deleted file mode 100644 index 7dd27bfa4..000000000 --- a/src/registrar/migrations/0030_alter_user_status.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.1 on 2023-08-29 17:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0029_user_status_alter_domainapplication_status"), - ] - - operations = [ - migrations.AlterField( - model_name="user", - name="status", - field=models.CharField( - blank=True, - choices=[("restricted", "restricted")], - default=None, - max_length=10, - null=True, - ), - ), - ] diff --git a/src/registrar/migrations/0031_transitiondomain_and_more.py b/src/registrar/migrations/0031_transitiondomain_and_more.py deleted file mode 100644 index 41c130717..000000000 --- a/src/registrar/migrations/0031_transitiondomain_and_more.py +++ /dev/null @@ -1,122 +0,0 @@ -# Generated by Django 4.2.1 on 2023-09-15 13:59 - -from django.db import migrations, models -import django_fsm - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0030_alter_user_status"), - ] - - operations = [ - migrations.CreateModel( - name="TransitionDomain", - 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)), - ( - "username", - models.TextField( - help_text="Username - this will be an email address", - verbose_name="Username", - ), - ), - ( - "domain_name", - models.TextField(blank=True, null=True, verbose_name="Domain name"), - ), - ( - "status", - models.CharField( - blank=True, - choices=[("created", "Created"), ("hold", "Hold")], - help_text="domain status during the transfer", - max_length=255, - verbose_name="Status", - ), - ), - ( - "email_sent", - models.BooleanField( - default=False, - help_text="indicates whether email was sent", - verbose_name="email sent", - ), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.RemoveField( - model_name="domainapplication", - name="more_organization_information", - ), - migrations.RemoveField( - model_name="domainapplication", - name="type_of_work", - ), - migrations.RemoveField( - model_name="domaininformation", - name="more_organization_information", - ), - migrations.RemoveField( - model_name="domaininformation", - name="type_of_work", - ), - migrations.AddField( - model_name="domainapplication", - name="about_your_organization", - field=models.TextField( - blank=True, help_text="Information about your organization", null=True - ), - ), - migrations.AddField( - model_name="domaininformation", - name="about_your_organization", - field=models.TextField( - blank=True, help_text="Information about your organization", null=True - ), - ), - migrations.AlterField( - model_name="domain", - name="state", - field=django_fsm.FSMField( - choices=[ - ("unknown", "Unknown"), - ("dns needed", "Dns Needed"), - ("ready", "Ready"), - ("on hold", "On Hold"), - ("deleted", "Deleted"), - ], - default="unknown", - help_text="Very basic info about the lifecycle of this domain object", - max_length=21, - protected=True, - ), - ), - migrations.AlterField( - model_name="publiccontact", - name="contact_type", - field=models.CharField( - choices=[ - ("registrant", "Registrant"), - ("admin", "Administrative"), - ("tech", "Technical"), - ("security", "Security"), - ], - help_text="For which type of WHOIS contact", - max_length=14, - ), - ), - ] diff --git a/src/registrar/migrations/__init__.py b/src/registrar/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 From 4a561ad4c65cf4509b78754e1dc45b241230f519 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:04:08 -0600 Subject: [PATCH 12/58] Add correct migration files --- src/registrar/migrations/0001_initial.py | 451 ++++++++++++++++++ ..._domain_host_nameserver_hostip_and_more.py | 164 +++++++ ...napplication_is_election_board_and_more.py | 98 ++++ .../0004_domainapplication_federal_agency.py | 20 + .../0005_domainapplication_city_and_more.py | 29 ++ .../migrations/0006_alter_contact_phone.py | 25 + ..._more_organization_information_and_more.py | 130 +++++ ..._remove_userprofile_created_at_and_more.py | 47 ++ ...ion_federally_recognized_tribe_and_more.py | 31 ++ ...application_no_other_contacts_rationale.py | 21 + ...remove_domainapplication_security_email.py | 16 + .../migrations/0012_delete_userprofile.py | 17 + .../0013_publiccontact_contact_user.py | 68 +++ .../0014_user_phone_alter_contact_user.py | 27 ++ ...rs_userdomainrole_user_domains_and_more.py | 66 +++ .../migrations/0016_domaininvitation.py | 51 ++ ...alter_domainapplication_status_and_more.py | 38 ++ .../migrations/0018_domaininformation.py | 273 +++++++++++ ...ter_domainapplication_organization_type.py | 47 ++ ...remove_domaininformation_security_email.py | 16 + ...main_publiccontact_registry_id_and_more.py | 122 +++++ ...ainapplication_approved_domain_and_more.py | 66 +++ ...t_name_alter_contact_last_name_and_more.py | 44 ++ .../migrations/0024_alter_contact_email.py | 19 + ...unique_domain_name_in_registry_and_more.py | 47 ++ ...omainapplication_address_line2_and_more.py | 26 + ...omaininformation_address_line1_and_more.py | 53 ++ .../0028_alter_domainapplication_status.py | 32 ++ ...r_status_alter_domainapplication_status.py | 42 ++ .../migrations/0030_alter_user_status.py | 23 + src/registrar/migrations/__init__.py | 0 31 files changed, 2109 insertions(+) create mode 100644 src/registrar/migrations/0001_initial.py create mode 100644 src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py create mode 100644 src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py create mode 100644 src/registrar/migrations/0004_domainapplication_federal_agency.py create mode 100644 src/registrar/migrations/0005_domainapplication_city_and_more.py create mode 100644 src/registrar/migrations/0006_alter_contact_phone.py create mode 100644 src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py create mode 100644 src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py create mode 100644 src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py create mode 100644 src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py create mode 100644 src/registrar/migrations/0011_remove_domainapplication_security_email.py create mode 100644 src/registrar/migrations/0012_delete_userprofile.py create mode 100644 src/registrar/migrations/0013_publiccontact_contact_user.py create mode 100644 src/registrar/migrations/0014_user_phone_alter_contact_user.py create mode 100644 src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py create mode 100644 src/registrar/migrations/0016_domaininvitation.py create mode 100644 src/registrar/migrations/0017_alter_domainapplication_status_and_more.py create mode 100644 src/registrar/migrations/0018_domaininformation.py create mode 100644 src/registrar/migrations/0019_alter_domainapplication_organization_type.py create mode 100644 src/registrar/migrations/0020_remove_domaininformation_security_email.py create mode 100644 src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py create mode 100644 src/registrar/migrations/0022_draftdomain_domainapplication_approved_domain_and_more.py create mode 100644 src/registrar/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py create mode 100644 src/registrar/migrations/0024_alter_contact_email.py create mode 100644 src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py create mode 100644 src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py create mode 100644 src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py create mode 100644 src/registrar/migrations/0028_alter_domainapplication_status.py create mode 100644 src/registrar/migrations/0029_user_status_alter_domainapplication_status.py create mode 100644 src/registrar/migrations/0030_alter_user_status.py create mode 100644 src/registrar/migrations/__init__.py diff --git a/src/registrar/migrations/0001_initial.py b/src/registrar/migrations/0001_initial.py new file mode 100644 index 000000000..78f0c5b66 --- /dev/null +++ b/src/registrar/migrations/0001_initial.py @@ -0,0 +1,451 @@ +# Generated by Django 4.1.3 on 2022-11-10 14:23 + +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import django_fsm # type: ignore + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, + }, + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name="Contact", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "first_name", + models.TextField( + blank=True, db_index=True, help_text="First name", null=True + ), + ), + ( + "middle_name", + models.TextField(blank=True, help_text="Middle name", null=True), + ), + ( + "last_name", + models.TextField( + blank=True, db_index=True, help_text="Last name", null=True + ), + ), + ("title", models.TextField(blank=True, help_text="Title", null=True)), + ( + "email", + models.TextField( + blank=True, db_index=True, help_text="Email", null=True + ), + ), + ( + "phone", + models.TextField( + blank=True, db_index=True, help_text="Phone", null=True + ), + ), + ], + ), + migrations.CreateModel( + name="Website", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("website", models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name="DomainApplication", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "status", + django_fsm.FSMField( + choices=[ + ("started", "started"), + ("submitted", "submitted"), + ("investigating", "investigating"), + ("approved", "approved"), + ], + default="started", + max_length=50, + ), + ), + ( + "organization_type", + models.CharField( + blank=True, + choices=[ + ("federal", "a federal agency"), + ("interstate", "an organization of two or more states"), + ( + "state_or_territory", + "one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands", + ), + ( + "tribal", + "a tribal government recognized by the federal or state government", + ), + ("county", "a county, parish, or borough"), + ("city", "a city, town, township, village, etc."), + ( + "special_district", + "an independent organization within a single state", + ), + ], + help_text="Type of Organization", + max_length=255, + null=True, + ), + ), + ( + "federal_branch", + models.CharField( + blank=True, + choices=[ + ("Executive", "Executive"), + ("Judicial", "Judicial"), + ("Legislative", "Legislative"), + ], + help_text="Branch of federal government", + max_length=50, + null=True, + ), + ), + ( + "is_election_office", + models.BooleanField( + blank=True, + help_text="Is your organization an election office?", + null=True, + ), + ), + ( + "organization_name", + models.TextField( + blank=True, + db_index=True, + help_text="Organization name", + null=True, + ), + ), + ( + "street_address", + models.TextField(blank=True, help_text="Street Address", null=True), + ), + ( + "unit_type", + models.CharField( + blank=True, help_text="Unit type", max_length=15, null=True + ), + ), + ( + "unit_number", + models.CharField( + blank=True, help_text="Unit number", max_length=255, null=True + ), + ), + ( + "state_territory", + models.CharField( + blank=True, help_text="State/Territory", max_length=2, null=True + ), + ), + ( + "zip_code", + models.CharField( + blank=True, + db_index=True, + help_text="ZIP code", + max_length=10, + null=True, + ), + ), + ( + "purpose", + models.TextField( + blank=True, help_text="Purpose of the domain", null=True + ), + ), + ( + "security_email", + models.CharField( + blank=True, + help_text="Security email for public use", + max_length=320, + null=True, + ), + ), + ( + "anything_else", + models.TextField( + blank=True, help_text="Anything else we should know?", null=True + ), + ), + ( + "acknowledged_policy", + models.BooleanField( + blank=True, + help_text="Acknowledged .gov acceptable use policy", + null=True, + ), + ), + ( + "alternative_domains", + models.ManyToManyField( + blank=True, related_name="alternatives+", to="registrar.website" + ), + ), + ( + "authorizing_official", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="authorizing_official", + to="registrar.contact", + ), + ), + ( + "creator", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="applications_created", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "current_websites", + models.ManyToManyField( + blank=True, related_name="current+", to="registrar.website" + ), + ), + ( + "investigator", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="applications_investigating", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "other_contacts", + models.ManyToManyField( + blank=True, + related_name="contact_applications", + to="registrar.contact", + ), + ), + ( + "requested_domain", + models.ForeignKey( + blank=True, + help_text="The requested domain", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="requested+", + to="registrar.website", + ), + ), + ( + "submitter", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="submitted_applications", + to="registrar.contact", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="UserProfile", + fields=[ + ( + "contact_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="registrar.contact", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("street1", models.TextField(blank=True)), + ("street2", models.TextField(blank=True)), + ("street3", models.TextField(blank=True)), + ("city", models.TextField(blank=True)), + ("sp", models.TextField(blank=True)), + ("pc", models.TextField(blank=True)), + ("cc", models.TextField(blank=True)), + ("display_name", models.TextField()), + ( + "user", + models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + bases=("registrar.contact", models.Model), + ), + ] diff --git a/src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py b/src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py new file mode 100644 index 000000000..f1049c252 --- /dev/null +++ b/src/registrar/migrations/0002_domain_host_nameserver_hostip_and_more.py @@ -0,0 +1,164 @@ +# Generated by Django 4.1.3 on 2022-11-28 19:07 + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import django_fsm # type: ignore + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Domain", + 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, + ), + ), + ( + "is_active", + django_fsm.FSMField( + choices=[(True, "Yes"), (False, "No")], + default=False, + help_text="Domain is live in the registry", + max_length=50, + ), + ), + ("owners", models.ManyToManyField(to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name="Host", + 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, + unique=True, + ), + ), + ( + "domain", + models.ForeignKey( + help_text="Domain to which this host belongs", + on_delete=django.db.models.deletion.PROTECT, + related_name="host", + to="registrar.domain", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Nameserver", + fields=[ + ( + "host_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="registrar.host", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("registrar.host",), + ), + migrations.CreateModel( + name="HostIP", + 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)), + ( + "address", + models.CharField( + default=None, + help_text="IP address", + max_length=46, + validators=[django.core.validators.validate_ipv46_address], + ), + ), + ( + "host", + models.ForeignKey( + help_text="Host to which this IP address belongs", + on_delete=django.db.models.deletion.PROTECT, + related_name="ip", + to="registrar.host", + ), + ), + ], + options={ + "abstract": False, + }, + ), + 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.domain", + ), + ), + migrations.AddConstraint( + model_name="domain", + constraint=models.UniqueConstraint( + condition=models.Q(("is_active", True)), + fields=("name",), + name="unique_domain_name_in_registry", + ), + ), + ] diff --git a/src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py b/src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py new file mode 100644 index 000000000..c12ca9d34 --- /dev/null +++ b/src/registrar/migrations/0003_rename_is_election_office_domainapplication_is_election_board_and_more.py @@ -0,0 +1,98 @@ +# Generated by Django 4.1.3 on 2022-12-02 21:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0002_domain_host_nameserver_hostip_and_more"), + ] + + operations = [ + migrations.RenameField( + model_name="domainapplication", + old_name="is_election_office", + new_name="is_election_board", + ), + migrations.RenameField( + model_name="domainapplication", + old_name="acknowledged_policy", + new_name="is_policy_acknowledged", + ), + migrations.RenameField( + model_name="domainapplication", + old_name="zip_code", + new_name="zipcode", + ), + migrations.RemoveField( + model_name="domainapplication", + name="federal_branch", + ), + migrations.RemoveField( + model_name="domainapplication", + name="street_address", + ), + migrations.RemoveField( + model_name="domainapplication", + name="unit_number", + ), + migrations.RemoveField( + model_name="domainapplication", + name="unit_type", + ), + migrations.AddField( + model_name="domainapplication", + name="address_line1", + field=models.TextField(blank=True, help_text="Address line 1", null=True), + ), + migrations.AddField( + model_name="domainapplication", + name="address_line2", + field=models.CharField( + blank=True, help_text="Address line 2", max_length=15, null=True + ), + ), + migrations.AddField( + model_name="domainapplication", + name="federal_type", + field=models.CharField( + blank=True, + choices=[ + ("executive", "Executive"), + ("judicial", "Judicial"), + ("legislative", "Legislative"), + ], + help_text="Branch of federal government", + max_length=50, + null=True, + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal: a federal agency"), + ("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 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", + ), + ], + help_text="Type of Organization", + max_length=255, + null=True, + ), + ), + ] diff --git a/src/registrar/migrations/0004_domainapplication_federal_agency.py b/src/registrar/migrations/0004_domainapplication_federal_agency.py new file mode 100644 index 000000000..a00d46ac2 --- /dev/null +++ b/src/registrar/migrations/0004_domainapplication_federal_agency.py @@ -0,0 +1,20 @@ +# Generated by Django 4.1.3 on 2022-12-07 15:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "registrar", + "0003_rename_is_election_office_domainapplication_is_election_board_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="domainapplication", + name="federal_agency", + field=models.TextField(help_text="Top level federal agency", null=True), + ), + ] diff --git a/src/registrar/migrations/0005_domainapplication_city_and_more.py b/src/registrar/migrations/0005_domainapplication_city_and_more.py new file mode 100644 index 000000000..3d1fc1de1 --- /dev/null +++ b/src/registrar/migrations/0005_domainapplication_city_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.1.3 on 2022-12-12 21:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0004_domainapplication_federal_agency"), + ] + + operations = [ + migrations.AddField( + model_name="domainapplication", + name="city", + field=models.TextField(blank=True, help_text="City", null=True), + ), + migrations.AddField( + model_name="domainapplication", + name="urbanization", + field=models.TextField(blank=True, help_text="Urbanization", null=True), + ), + migrations.AlterField( + model_name="domainapplication", + name="federal_agency", + field=models.TextField( + blank=True, help_text="Top level federal agency", null=True + ), + ), + ] diff --git a/src/registrar/migrations/0006_alter_contact_phone.py b/src/registrar/migrations/0006_alter_contact_phone.py new file mode 100644 index 000000000..1e055694f --- /dev/null +++ b/src/registrar/migrations/0006_alter_contact_phone.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.4 on 2022-12-14 20:48 + +from django.db import migrations +import phonenumber_field.modelfields # type: ignore + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0005_domainapplication_city_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="contact", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, + db_index=True, + help_text="Phone", + max_length=128, + null=True, + region=None, + ), + ), + ] diff --git a/src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py b/src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py new file mode 100644 index 000000000..909b301ab --- /dev/null +++ b/src/registrar/migrations/0007_domainapplication_more_organization_information_and_more.py @@ -0,0 +1,130 @@ +# Generated by Django 4.1.5 on 2023-01-10 20:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0006_alter_contact_phone"), + ] + + operations = [ + migrations.AddField( + model_name="domainapplication", + name="more_organization_information", + field=models.TextField( + blank=True, + help_text="More information about your organization", + null=True, + ), + ), + migrations.AddField( + model_name="domainapplication", + name="type_of_work", + field=models.TextField( + blank=True, help_text="Type of work of the organization", null=True + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="address_line1", + field=models.TextField(blank=True, help_text="Street address", null=True), + ), + migrations.AlterField( + model_name="domainapplication", + name="address_line2", + field=models.CharField( + blank=True, help_text="Street address line 2", max_length=15, null=True + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="federal_agency", + field=models.TextField(blank=True, help_text="Federal agency", null=True), + ), + migrations.AlterField( + model_name="domainapplication", + name="federal_type", + field=models.CharField( + blank=True, + choices=[ + ("executive", "Executive"), + ("judicial", "Judicial"), + ("legislative", "Legislative"), + ], + help_text="Federal government branch", + max_length=50, + null=True, + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="organization_type", + field=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, + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="purpose", + field=models.TextField( + blank=True, help_text="Purpose of your domain", null=True + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="state_territory", + field=models.CharField( + blank=True, + help_text="State, territory, or military post", + max_length=2, + null=True, + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="urbanization", + field=models.TextField( + blank=True, help_text="Urbanization (Puerto Rico only)", null=True + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="zipcode", + field=models.CharField( + blank=True, + db_index=True, + help_text="Zip code", + max_length=10, + null=True, + ), + ), + ] diff --git a/src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py b/src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py new file mode 100644 index 000000000..bb52e4320 --- /dev/null +++ b/src/registrar/migrations/0008_remove_userprofile_created_at_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.1.5 on 2023-01-13 01:54 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0007_domainapplication_more_organization_information_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="userprofile", + name="created_at", + ), + migrations.RemoveField( + model_name="userprofile", + name="updated_at", + ), + migrations.AddField( + model_name="contact", + name="created_at", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.AddField( + model_name="contact", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="website", + name="created_at", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.AddField( + model_name="website", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py b/src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py new file mode 100644 index 000000000..3010b6cd4 --- /dev/null +++ b/src/registrar/migrations/0009_domainapplication_federally_recognized_tribe_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.5 on 2023-01-25 17:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0008_remove_userprofile_created_at_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="domainapplication", + name="federally_recognized_tribe", + field=models.BooleanField( + help_text="Is the tribe federally recognized", null=True + ), + ), + migrations.AddField( + model_name="domainapplication", + name="state_recognized_tribe", + field=models.BooleanField( + help_text="Is the tribe recognized by a state", null=True + ), + ), + migrations.AddField( + model_name="domainapplication", + name="tribe_name", + field=models.TextField(blank=True, help_text="Name of tribe", null=True), + ), + ] diff --git a/src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py b/src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py new file mode 100644 index 000000000..bf8d83d60 --- /dev/null +++ b/src/registrar/migrations/0010_domainapplication_no_other_contacts_rationale.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.5 on 2023-02-06 14:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0009_domainapplication_federally_recognized_tribe_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="domainapplication", + name="no_other_contacts_rationale", + field=models.TextField( + blank=True, + help_text="Reason for listing no additional contacts", + null=True, + ), + ), + ] diff --git a/src/registrar/migrations/0011_remove_domainapplication_security_email.py b/src/registrar/migrations/0011_remove_domainapplication_security_email.py new file mode 100644 index 000000000..c717408da --- /dev/null +++ b/src/registrar/migrations/0011_remove_domainapplication_security_email.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.6 on 2023-02-27 18:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0010_domainapplication_no_other_contacts_rationale"), + ] + + operations = [ + migrations.RemoveField( + model_name="domainapplication", + name="security_email", + ), + ] diff --git a/src/registrar/migrations/0012_delete_userprofile.py b/src/registrar/migrations/0012_delete_userprofile.py new file mode 100644 index 000000000..b5bcebb95 --- /dev/null +++ b/src/registrar/migrations/0012_delete_userprofile.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.6 on 2023-03-07 14:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0011_remove_domainapplication_security_email"), + ] + + operations = [ + migrations.DeleteModel( + name="UserProfile", + ), + ] diff --git a/src/registrar/migrations/0013_publiccontact_contact_user.py b/src/registrar/migrations/0013_publiccontact_contact_user.py new file mode 100644 index 000000000..29a9385cd --- /dev/null +++ b/src/registrar/migrations/0013_publiccontact_contact_user.py @@ -0,0 +1,68 @@ +# Generated by Django 4.1.6 on 2023-03-07 14:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0012_delete_userprofile"), + ] + + operations = [ + migrations.CreateModel( + name="PublicContact", + 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)), + ( + "contact_type", + models.CharField( + choices=[ + ("registrant", "Registrant"), + ("administrative", "Administrative"), + ("technical", "Technical"), + ("security", "Security"), + ], + max_length=14, + ), + ), + ("name", models.TextField()), + ("org", models.TextField(null=True)), + ("street1", models.TextField()), + ("street2", models.TextField(null=True)), + ("street3", models.TextField(null=True)), + ("city", models.TextField()), + ("sp", models.TextField()), + ("pc", models.TextField()), + ("cc", models.TextField()), + ("email", models.TextField()), + ("voice", models.TextField()), + ("fax", models.TextField(null=True)), + ("pw", models.TextField()), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="contact", + name="user", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/src/registrar/migrations/0014_user_phone_alter_contact_user.py b/src/registrar/migrations/0014_user_phone_alter_contact_user.py new file mode 100644 index 000000000..452b2b9d5 --- /dev/null +++ b/src/registrar/migrations/0014_user_phone_alter_contact_user.py @@ -0,0 +1,27 @@ +# Generated by Django 4.1.6 on 2023-03-07 16:39 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import phonenumber_field.modelfields # type: ignore + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0013_publiccontact_contact_user"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, + db_index=True, + help_text="Phone", + max_length=128, + null=True, + region=None, + ), + ), + ] diff --git a/src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py b/src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py new file mode 100644 index 000000000..2fbc5ab19 --- /dev/null +++ b/src/registrar/migrations/0015_remove_domain_owners_userdomainrole_user_domains_and_more.py @@ -0,0 +1,66 @@ +# Generated by Django 4.1.6 on 2023-03-10 15:32 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0014_user_phone_alter_contact_user"), + ] + + operations = [ + migrations.RemoveField( + model_name="domain", + name="owners", + ), + migrations.CreateModel( + name="UserDomainRole", + 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)), + ("role", models.TextField(choices=[("admin", "Admin")])), + ( + "domain", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="permissions", + to="registrar.domain", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="permissions", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddField( + model_name="user", + name="domains", + field=models.ManyToManyField( + related_name="users", + through="registrar.UserDomainRole", + to="registrar.domain", + ), + ), + migrations.AddConstraint( + model_name="userdomainrole", + constraint=models.UniqueConstraint( + fields=("user", "domain"), name="unique_user_domain_role" + ), + ), + ] diff --git a/src/registrar/migrations/0016_domaininvitation.py b/src/registrar/migrations/0016_domaininvitation.py new file mode 100644 index 000000000..f7756ef1d --- /dev/null +++ b/src/registrar/migrations/0016_domaininvitation.py @@ -0,0 +1,51 @@ +# Generated by Django 4.1.6 on 2023-03-24 16:56 + +from django.db import migrations, models +import django.db.models.deletion +import django_fsm # type: ignore + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0015_remove_domain_owners_userdomainrole_user_domains_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="DomainInvitation", + 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)), + ("email", models.EmailField(max_length=254)), + ( + "status", + django_fsm.FSMField( + choices=[("sent", "sent"), ("retrieved", "retrieved")], + default="sent", + max_length=50, + protected=True, + ), + ), + ( + "domain", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="invitations", + to="registrar.domain", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/src/registrar/migrations/0017_alter_domainapplication_status_and_more.py b/src/registrar/migrations/0017_alter_domainapplication_status_and_more.py new file mode 100644 index 000000000..5d20551d7 --- /dev/null +++ b/src/registrar/migrations/0017_alter_domainapplication_status_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.6 on 2023-04-13 18:38 + +from django.db import migrations +import django_fsm # type: ignore + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0016_domaininvitation"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="status", + field=django_fsm.FSMField( + choices=[ + ("started", "started"), + ("submitted", "submitted"), + ("investigating", "investigating"), + ("approved", "approved"), + ("withdrawn", "withdrawn"), + ], + default="started", + max_length=50, + ), + ), + migrations.AlterField( + model_name="domaininvitation", + name="status", + field=django_fsm.FSMField( + choices=[("invited", "invited"), ("retrieved", "retrieved")], + default="invited", + max_length=50, + protected=True, + ), + ), + ] diff --git a/src/registrar/migrations/0018_domaininformation.py b/src/registrar/migrations/0018_domaininformation.py new file mode 100644 index 000000000..408fa048b --- /dev/null +++ b/src/registrar/migrations/0018_domaininformation.py @@ -0,0 +1,273 @@ +# Generated by Django 4.1.6 on 2023-05-08 15:30 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0017_alter_domainapplication_status_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="DomainInformation", + 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)), + ( + "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, + ), + ), + ( + "security_email", + models.EmailField( + blank=True, + help_text="Security email for public use", + max_length=320, + null=True, + ), + ), + ( + "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.OneToOneField( + 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", + ), + ), + ( + "domain_application", + models.OneToOneField( + blank=True, + help_text="Associated domain application", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="domainapplication_info", + to="registrar.domainapplication", + ), + ), + ( + "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={ + "verbose_name_plural": "Domain Information", + }, + ), + ] diff --git a/src/registrar/migrations/0019_alter_domainapplication_organization_type.py b/src/registrar/migrations/0019_alter_domainapplication_organization_type.py new file mode 100644 index 000000000..1a7397255 --- /dev/null +++ b/src/registrar/migrations/0019_alter_domainapplication_organization_type.py @@ -0,0 +1,47 @@ +# Generated by Django 4.1.6 on 2023-05-09 19:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0018_domaininformation"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="organization_type", + field=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, + ), + ), + ] diff --git a/src/registrar/migrations/0020_remove_domaininformation_security_email.py b/src/registrar/migrations/0020_remove_domaininformation_security_email.py new file mode 100644 index 000000000..9742c294a --- /dev/null +++ b/src/registrar/migrations/0020_remove_domaininformation_security_email.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2 on 2023-05-17 17:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0019_alter_domainapplication_organization_type"), + ] + + operations = [ + migrations.RemoveField( + model_name="domaininformation", + name="security_email", + ), + ] diff --git a/src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py b/src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py new file mode 100644 index 000000000..35e07fe71 --- /dev/null +++ b/src/registrar/migrations/0021_publiccontact_domain_publiccontact_registry_id_and_more.py @@ -0,0 +1,122 @@ +# Generated by Django 4.2.1 on 2023-05-25 15:03 + +from django.db import migrations, models +import django.db.models.deletion +import registrar.models.public_contact + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0020_remove_domaininformation_security_email"), + ] + + operations = [ + migrations.AddField( + model_name="publiccontact", + name="domain", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.PROTECT, + related_name="contacts", + to="registrar.domain", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="publiccontact", + name="registry_id", + field=models.CharField( + default=registrar.models.public_contact.get_id, + help_text="Auto generated ID to track this contact in the registry", + max_length=16, + ), + ), + migrations.AlterField( + model_name="publiccontact", + name="cc", + field=models.TextField(help_text="Contact's country code"), + ), + migrations.AlterField( + model_name="publiccontact", + name="city", + field=models.TextField(help_text="Contact's city"), + ), + migrations.AlterField( + model_name="publiccontact", + name="contact_type", + field=models.CharField( + choices=[ + ("registrant", "Registrant"), + ("administrative", "Administrative"), + ("technical", "Technical"), + ("security", "Security"), + ], + help_text="For which type of WHOIS contact", + max_length=14, + ), + ), + migrations.AlterField( + model_name="publiccontact", + name="email", + field=models.TextField(help_text="Contact's email address"), + ), + migrations.AlterField( + model_name="publiccontact", + name="fax", + field=models.TextField( + help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.", + null=True, + ), + ), + migrations.AlterField( + model_name="publiccontact", + name="name", + field=models.TextField(help_text="Contact's full name"), + ), + migrations.AlterField( + model_name="publiccontact", + name="org", + field=models.TextField( + help_text="Contact's organization (null ok)", null=True + ), + ), + migrations.AlterField( + model_name="publiccontact", + name="pc", + field=models.TextField(help_text="Contact's postal code"), + ), + migrations.AlterField( + model_name="publiccontact", + name="pw", + field=models.TextField( + help_text="Contact's authorization code. 16 characters minimum." + ), + ), + migrations.AlterField( + model_name="publiccontact", + name="sp", + field=models.TextField(help_text="Contact's state or province"), + ), + migrations.AlterField( + model_name="publiccontact", + name="street1", + field=models.TextField(help_text="Contact's street"), + ), + migrations.AlterField( + model_name="publiccontact", + name="street2", + field=models.TextField(help_text="Contact's street (null ok)", null=True), + ), + migrations.AlterField( + model_name="publiccontact", + name="street3", + field=models.TextField(help_text="Contact's street (null ok)", null=True), + ), + migrations.AlterField( + model_name="publiccontact", + name="voice", + field=models.TextField( + help_text="Contact's phone number. Must be in ITU.E164.2005 format" + ), + ), + ] 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/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py b/src/registrar/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py new file mode 100644 index 000000000..b2259f650 --- /dev/null +++ b/src/registrar/migrations/0023_alter_contact_first_name_alter_contact_last_name_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 4.2.1 on 2023-05-31 23:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0022_draftdomain_domainapplication_approved_domain_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="contact", + name="first_name", + field=models.TextField( + blank=True, + db_index=True, + help_text="First name", + null=True, + verbose_name="first name / given name", + ), + ), + migrations.AlterField( + model_name="contact", + name="last_name", + field=models.TextField( + blank=True, + db_index=True, + help_text="Last name", + null=True, + verbose_name="last name / family name", + ), + ), + migrations.AlterField( + model_name="contact", + name="title", + field=models.TextField( + blank=True, + help_text="Title", + null=True, + verbose_name="title or role in your organization", + ), + ), + ] diff --git a/src/registrar/migrations/0024_alter_contact_email.py b/src/registrar/migrations/0024_alter_contact_email.py new file mode 100644 index 000000000..f512d5d82 --- /dev/null +++ b/src/registrar/migrations/0024_alter_contact_email.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.1 on 2023-06-01 19:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0023_alter_contact_first_name_alter_contact_last_name_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="contact", + name="email", + field=models.EmailField( + blank=True, db_index=True, help_text="Email", max_length=254, null=True + ), + ), + ] diff --git a/src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py b/src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py new file mode 100644 index 000000000..f9f5876b1 --- /dev/null +++ b/src/registrar/migrations/0025_remove_domain_unique_domain_name_in_registry_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.1 on 2023-06-01 21:47 + +from django.db import migrations +import django_fsm # type: ignore +import registrar.models.utility.domain_field + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0024_alter_contact_email"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="domain", + name="unique_domain_name_in_registry", + ), + migrations.RemoveField( + model_name="domain", + name="is_active", + ), + migrations.AddField( + model_name="domain", + name="state", + field=django_fsm.FSMField( + choices=[ + ("created", "Created"), + ("deleted", "Deleted"), + ("unknown", "Unknown"), + ], + default="unknown", + help_text="Very basic info about the lifecycle of this domain object", + max_length=21, + protected=True, + ), + ), + migrations.AlterField( + model_name="domain", + name="name", + field=registrar.models.utility.domain_field.DomainField( + default=None, + help_text="Fully qualified domain name", + max_length=253, + unique=True, + ), + ), + ] diff --git a/src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py b/src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py new file mode 100644 index 000000000..6e28f5cbb --- /dev/null +++ b/src/registrar/migrations/0026_alter_domainapplication_address_line2_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.1 on 2023-06-02 17:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0025_remove_domain_unique_domain_name_in_registry_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="address_line2", + field=models.TextField( + blank=True, help_text="Street address line 2", null=True + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="address_line2", + field=models.TextField( + blank=True, help_text="Street address line 2", null=True + ), + ), + ] diff --git a/src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py b/src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py new file mode 100644 index 000000000..9f362c956 --- /dev/null +++ b/src/registrar/migrations/0027_alter_domaininformation_address_line1_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2.1 on 2023-06-09 16:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0026_alter_domainapplication_address_line2_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="domaininformation", + name="address_line1", + field=models.TextField( + blank=True, + help_text="Street address", + null=True, + verbose_name="Street address", + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="address_line2", + field=models.TextField( + blank=True, + help_text="Street address line 2", + null=True, + verbose_name="Street address line 2", + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="state_territory", + field=models.CharField( + blank=True, + help_text="State, territory, or military post", + max_length=2, + null=True, + verbose_name="State, territory, or military post", + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="urbanization", + field=models.TextField( + blank=True, + help_text="Urbanization (Puerto Rico only)", + null=True, + verbose_name="Urbanization (Puerto Rico only)", + ), + ), + ] diff --git a/src/registrar/migrations/0028_alter_domainapplication_status.py b/src/registrar/migrations/0028_alter_domainapplication_status.py new file mode 100644 index 000000000..61b1c0505 --- /dev/null +++ b/src/registrar/migrations/0028_alter_domainapplication_status.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.2 on 2023-07-12 21:31 +# Generated by Django 4.2.2 on 2023-07-13 17:56 +# hand merged + +from django.db import migrations +import django_fsm + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0027_alter_domaininformation_address_line1_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="status", + field=django_fsm.FSMField( + choices=[ + ("started", "started"), + ("submitted", "submitted"), + ("in review", "in review"), + ("action needed", "action needed"), + ("approved", "approved"), + ("withdrawn", "withdrawn"), + ("rejected", "rejected"), + ], + default="started", + max_length=50, + ), + ), + ] diff --git a/src/registrar/migrations/0029_user_status_alter_domainapplication_status.py b/src/registrar/migrations/0029_user_status_alter_domainapplication_status.py new file mode 100644 index 000000000..504358665 --- /dev/null +++ b/src/registrar/migrations/0029_user_status_alter_domainapplication_status.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.1 on 2023-08-18 16:59 + +from django.db import migrations, models +import django_fsm + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0028_alter_domainapplication_status"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="status", + field=models.CharField( + blank=True, + choices=[("ineligible", "ineligible")], + default=None, + max_length=10, + null=True, + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="status", + field=django_fsm.FSMField( + choices=[ + ("started", "started"), + ("submitted", "submitted"), + ("in review", "in review"), + ("action needed", "action needed"), + ("approved", "approved"), + ("withdrawn", "withdrawn"), + ("rejected", "rejected"), + ("ineligible", "ineligible"), + ], + default="started", + max_length=50, + ), + ), + ] diff --git a/src/registrar/migrations/0030_alter_user_status.py b/src/registrar/migrations/0030_alter_user_status.py new file mode 100644 index 000000000..7dd27bfa4 --- /dev/null +++ b/src/registrar/migrations/0030_alter_user_status.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.1 on 2023-08-29 17:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0029_user_status_alter_domainapplication_status"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="status", + field=models.CharField( + blank=True, + choices=[("restricted", "restricted")], + default=None, + max_length=10, + null=True, + ), + ), + ] diff --git a/src/registrar/migrations/__init__.py b/src/registrar/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb From d9679cf01048f6a28cd5160d4fe071388e061262 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:19:09 -0600 Subject: [PATCH 13/58] Fix test interference issue --- src/registrar/models/domain.py | 109 +++++--- src/registrar/tests/common.py | 31 ++- src/registrar/tests/test_models_domain.py | 317 ++++++++++++++-------- 3 files changed, 288 insertions(+), 169 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index ec8c5a2a0..339c5e765 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -695,7 +695,8 @@ class Domain(TimeStampedModel, DomainHelper): streets = {} if addr is not None and addr.street is not None: # 'zips' two lists together. - # For instance, (('street1', 'some_value_here'), ('street2', 'some_value_here')) + # For instance, (('street1', 'some_value_here'), + # ('street2', 'some_value_here')) # Dict then converts this to a useable kwarg which we can pass in streets = dict( zip_longest( @@ -734,7 +735,7 @@ class Domain(TimeStampedModel, DomainHelper): contact.contact_type, error.code, error, - ) + ) # noqa raise error def get_contact_default( @@ -776,17 +777,18 @@ class Domain(TimeStampedModel, DomainHelper): or cache_contact_helper("security") """ try: + # TODO - refactor desired_property = "contacts" # The contact type 'registrant' is stored under a different property if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: desired_property = "registrant" - logger.debug(f"generic domain getter was called. Wanting contacts on {contact_type_choice}") contacts = self._get_property(desired_property) if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: contacts = [contacts] except KeyError as error: logger.warning("generic_contact_getter -> Contact does not exist") logger.warning(error) + # Should we just raise an error instead? return self.get_contact_default(contact_type_choice) else: print(f"generic_contact_getter -> contacts?? {contacts}") @@ -1123,38 +1125,27 @@ class Domain(TimeStampedModel, DomainHelper): "tr_date": getattr(data, "tr_date", ...), "up_date": getattr(data, "up_date", ...), } - print(f"precleaned stuff {cache}") # remove null properties (to distinguish between "a value of None" and null) cleaned = {k: v for k, v in cache.items() if v is not ...} - l = getattr(data, "contacts", ...) - logger.debug(f"here are the contacts {l}") + # statuses can just be a list no need to keep the epp object if "statuses" in cleaned.keys(): cleaned["statuses"] = [status.state for status in cleaned["statuses"]] # Registrant should be of type PublicContact if "registrant" in cleaned.keys(): - try: - contact = PublicContact( - registry_id=cleaned["registrant"], - contact_type=PublicContact.ContactTypeChoices.REGISTRANT, - ) - # Grabs the expanded contact - full_object = self._request_contact_info(contact) - # Maps it to type PublicContact - cleaned["registrant"] = self.map_epp_contact_to_public_contact( - full_object, contact.registry_id, contact.contact_type - ) - except RegistryError: - cleaned["registrant"] = None - # get contact info, if there are any + # For linter... + _ = cleaned["registrant"] + # Registrant, if it exists, should always exist in EppLib. + # If it doesn't, that is bad. We expect this to exist, always. + cleaned["registrant"] = self._registrant_to_public_contact(_) + if ( # fetch_contacts and - "_contacts" in cleaned + "_contacts" in cleaned.keys() and isinstance(cleaned["_contacts"], list) - and len(cleaned["_contacts"]) + and len(cleaned["_contacts"]) > 0 ): - logger.debug("hit!") cleaned["contacts"] = [] for domainContact in cleaned["_contacts"]: # we do not use _get_or_create_* because we expect the object we @@ -1162,7 +1153,7 @@ class Domain(TimeStampedModel, DomainHelper): # if not, that's a problem # TODO- discuss-should we check if contact is in public contacts - # and add it if not- this is really to keep in mine the transisiton + # and add it if not- this is really to keep in mind for the transition req = commands.InfoContact(id=domainContact.contact) data = registry.send(req, cleaned=True).res_data[0] @@ -1183,31 +1174,63 @@ class Domain(TimeStampedModel, DomainHelper): # no point in removing cleaned["hosts"] = [] for name in cleaned["_hosts"]: - # we do not use _get_or_create_* because we expect the object we - # just asked the registry for still exists -- - # if not, that's a problem - req = commands.InfoHost(name=name) - data = registry.send(req, cleaned=True).res_data[0] - # extract properties from response - # (Ellipsis is used to mean "null") - host = { - "name": name, - "addrs": getattr(data, "addrs", ...), - "cr_date": getattr(data, "cr_date", ...), - "statuses": getattr(data, "statuses", ...), - "tr_date": getattr(data, "tr_date", ...), - "up_date": getattr(data, "up_date", ...), - } - cleaned["hosts"].append( - {k: v for k, v in host.items() if v is not ...} - ) - + # For reviewers - slight refactor here + # as we may need this for future hosts tickets + # (potential host mapper?). + # Can remove if unnecessary + cleaned["hosts"].append(self._get_host_as_dict(name)) # replace the prior cache with new data self._cache = cleaned except RegistryError as e: logger.error(e) + def _registrant_to_public_contact(self, registry_id: str): + """ EPPLib returns the registrant as a string, + which is the registrants associated registry_id. This function is used to + convert that id to a useable object by calling commands.InfoContact + on that ID, then mapping that object to type PublicContact. """ + contact = PublicContact( + registry_id=registry_id, + contact_type=PublicContact.ContactTypeChoices.REGISTRANT, + ) + # Grabs the expanded contact + full_object = self._request_contact_info(contact) + # Maps it to type PublicContact + return self.map_epp_contact_to_public_contact( + full_object, contact.registry_id, contact.contact_type + ) + + def _get_host_as_dict(self, host_name): + """Returns the result of commands.InfoHost as a dictionary + + Returns the following, excluding null fields: + { + "name": name, + "addrs": addr, + "cr_date": cr_date, + "statuses": statuses, + "tr_date": tr_date, + "up_date": up_date, + } + """ + # we do not use _get_or_create_* because we expect the object we + # just asked the registry for still exists -- + # if not, that's a problem + req = commands.InfoHost(name=host_name) + data = registry.send(req, cleaned=True).res_data[0] + # extract properties from response + # (Ellipsis is used to mean "null") + host = { + "name": host_name, + "addrs": getattr(data, "addrs", ...), + "cr_date": getattr(data, "cr_date", ...), + "statuses": getattr(data, "statuses", ...), + "tr_date": getattr(data, "tr_date", ...), + "up_date": getattr(data, "up_date", ...), + } + return {k: v for k, v in host.items() if v is not ...} + def _invalidate_cache(self): """Remove cache data when updates are made.""" logger.debug(f"cache was cleared! {self.__dict__}") diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 6e562ffb1..8baf60640 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -557,7 +557,12 @@ class MockEppLib(TestCase): self.hosts = hosts self.registrant = registrant - def dummyInfoContactResultData(id, email, cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), pw="thisisnotapassword"): + def dummyInfoContactResultData( + id, + email, + cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + pw="thisisnotapassword", + ): fake = info.InfoContactResultData( id=id, postal_info=common.PostalInfo( @@ -591,10 +596,18 @@ class MockEppLib(TestCase): ) return fake - mockSecurityContact = dummyInfoContactResultData("securityContact", "security@mail.gov") - mockTechnicalContact = dummyInfoContactResultData("technicalContact", "tech@mail.gov") - mockAdministrativeContact = dummyInfoContactResultData("administrativeContact", "admin@mail.gov") - mockRegistrantContact = dummyInfoContactResultData("registrantContact", "registrant@mail.gov") + mockSecurityContact = dummyInfoContactResultData( + "securityContact", "security@mail.gov" + ) + mockTechnicalContact = dummyInfoContactResultData( + "technicalContact", "tech@mail.gov" + ) + mockAdministrativeContact = dummyInfoContactResultData( + "administrativeContact", "admin@mail.gov" + ) + mockRegistrantContact = dummyInfoContactResultData( + "registrantContact", "registrant@mail.gov" + ) mockDataInfoDomain = fakedEppObject( "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), @@ -618,7 +631,9 @@ class MockEppLib(TestCase): contacts=[], hosts=["fake.host.com"], ) - mockDataInfoContact = dummyInfoContactResultData("123", "123@mail.gov", datetime.datetime(2023, 5, 25, 19, 45, 35), "lastPw") + mockDataInfoContact = dummyInfoContactResultData( + "123", "123@mail.gov", datetime.datetime(2023, 5, 25, 19, 45, 35), "lastPw" + ) mockDataInfoHosts = fakedEppObject( "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35) ) @@ -633,6 +648,8 @@ class MockEppLib(TestCase): return MagicMock(res_data=[self.infoDomainNoContact]) elif getattr(_request, "name", None) == "freeman.gov": return MagicMock(res_data=[self.InfoDomainWithContacts]) + else: + return MagicMock(res_data=[self.mockDataInfoDomain]) elif isinstance(_request, commands.InfoContact): # Default contact return mocked_result = self.mockDataInfoContact @@ -646,6 +663,8 @@ class MockEppLib(TestCase): mocked_result = self.mockAdministrativeContact case "registrantContact": mocked_result = self.mockRegistrantContact + case "123": + mocked_result = self.mockDataInfoContact return MagicMock(res_data=[mocked_result]) elif ( diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 976164038..70ceeb812 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -29,14 +29,15 @@ logger = logging.getLogger(__name__) class TestDomainCache(MockEppLib): def test_cache_sets_resets(self): """Cache should be set on getter and reset on setter calls""" - domain, _ = Domain.objects.get_or_create(name="freeman.gov") + domain, _ = Domain.objects.get_or_create(name="igorville.gov") # trigger getter _ = domain.creation_date + logger.debug(f"what is the cache here? {domain._cache}") domain._get_property("contacts") # getter should set the domain cache with a InfoDomain object # (see InfoDomainResult) - self.assertEquals(domain._cache["auth_info"], self.InfoDomainWithContacts.auth_info) - self.assertEquals(domain._cache["cr_date"], self.InfoDomainWithContacts.cr_date) + self.assertEquals(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) + self.assertEquals(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) self.assertFalse("avail" in domain._cache.keys()) # using a setter should clear the cache @@ -47,16 +48,15 @@ class TestDomainCache(MockEppLib): self.mockedSendFunction.assert_has_calls( [ call( - commands.InfoDomain(name="freeman.gov", auth_info=None), + commands.InfoDomain(name="igorville.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call(commands.InfoContact(id="123", auth_info=None), cleaned=True), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) + # Clear the cache + domain._invalidate_cache() def test_cache_used_when_avail(self): """Cache is pulled from if the object has already been accessed""" @@ -80,64 +80,67 @@ class TestDomainCache(MockEppLib): ] self.mockedSendFunction.assert_has_calls(expectedCalls) + # Clear the cache + domain._invalidate_cache() def test_cache_nested_elements(self): """Cache works correctly with the nested objects cache and hosts""" - domain, _ = Domain.objects.get_or_create(name="freeman.gov") + domain, _ = Domain.objects.get_or_create(name="igorville.gov") self.maxDiff = None # The contact list will initally contain objects of type 'DomainContact' # this is then transformed into PublicContact, and cache should NOT # hold onto the DomainContact object expectedUnfurledContactsList = [ - common.DomainContact(contact="securityContact", type="security"), - common.DomainContact(contact="administrativeContact", type="admin"), - common.DomainContact(contact="technicalContact", type="tech"), + common.DomainContact(contact="123", type="security"), ] expectedContactsList = [ domain.map_epp_contact_to_public_contact( - self.mockSecurityContact, "securityContact", "security" - ), - domain.map_epp_contact_to_public_contact( - self.mockAdministrativeContact, "administrativeContact", "admin" - ), - domain.map_epp_contact_to_public_contact( - self.mockTechnicalContact, "technicalContact", "tech" - ), + self.mockDataInfoContact, "123", "security" + ) ] expectedHostsDict = { - "name": self.InfoDomainWithContacts.hosts[0], - "cr_date": self.InfoDomainWithContacts.cr_date, + "name": self.mockDataInfoDomain.hosts[0], + "cr_date": self.mockDataInfoDomain.cr_date, } # this can be changed when the getter for contacts is implemented domain._get_property("contacts") - + # check domain info is still correct and not overridden - self.assertEqual(domain._cache["auth_info"], self.InfoDomainWithContacts.auth_info) - self.assertEqual(domain._cache["cr_date"], self.InfoDomainWithContacts.cr_date) + self.assertEqual(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) + self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) # check contacts - self.assertEqual(domain._cache["_contacts"], self.InfoDomainWithContacts.contacts) + self.assertEqual(domain._cache["_contacts"], self.mockDataInfoDomain.contacts) # The contact list should not contain what is sent by the registry by default, - # as _fetch_cache will transform the type to PublicContact + # as _fetch_cache will transform the type to PublicContact self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) # Assert that what we get from cache is inline with our mock # Since our cache creates new items inside of our contact list, # as we need to map DomainContact -> PublicContact, our mocked items # will point towards a different location in memory (as they are different objects). - # This should be a problem only exclusive to our mocks, since we are not + # This should be a problem only exclusive to our mocks, since we are not # replicating the same item twice outside this context. That said, we want to check # for data integrity, but do not care if they are of the same _state or not - for cached_contact, expected_contact in zip(domain._cache["contacts"], expectedContactsList): + for cached_contact, expected_contact in zip( + domain._cache["contacts"], expectedContactsList + ): self.assertEqual( - {k: v for k, v in vars(cached_contact).items() if k != '_state'}, - {k: v for k, v in vars(expected_contact).items() if k != '_state'} + {k: v for k, v in vars(cached_contact).items() if k != "_state"}, + {k: v for k, v in vars(expected_contact).items() if k != "_state"}, ) # get and check hosts is set correctly domain._get_property("hosts") self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) + # Clear the cache + domain._invalidate_cache() + + @skip("Not implemented yet") + def test_map_epp_contact_to_public_contact(self): + # Tests that the mapper is working how we expect + raise class TestDomainCreation(TestCase): @@ -171,6 +174,7 @@ class TestDomainCreation(TestCase): domain = Domain.objects.get(name="igorville.gov") self.assertTrue(domain) mocked_domain_creation.assert_not_called() + patcher.stop() @skip("not implemented yet") def test_accessing_domain_properties_creates_domain_in_registry(self): @@ -221,7 +225,7 @@ class TestDomainCreation(TestCase): class TestRegistrantContacts(MockEppLib): """Rule: Registrants may modify their WHOIS data""" - + def setUp(self): """ Background: @@ -229,15 +233,15 @@ class TestRegistrantContacts(MockEppLib): And the registrant is the admin on a domain """ super().setUp() + # Creates a domain with no contact associated to it self.domain, _ = Domain.objects.get_or_create(name="security.gov") + # Creates a domain with an associated contact + self.domain_contact, _ = Domain.objects.get_or_create(name="freeman.gov") def tearDown(self): super().tearDown() - PublicContact.objects.all().delete() - DomainInformation.objects.all().delete() - DomainApplication.objects.all().delete() - Domain.objects.all().delete() - self.domain._cache = {} + self.domain._invalidate_cache() + self.domain_contact._invalidate_cache() # self.contactMailingAddressPatch.stop() # self.createContactPatch.stop() @@ -484,7 +488,7 @@ class TestRegistrantContacts(MockEppLib): Then a user-friendly error message is returned for displaying on the web """ raise - + @skip("not implemented yet") def test_contact_getters_cache(self): """ @@ -492,29 +496,29 @@ class TestRegistrantContacts(MockEppLib): When each contact is retrieved from cache Then the user retrieves the correct contact objects """ + @skip("not implemented yet") def test_epp_public_contact_mapper(self): pass def test_contact_getter_security(self): - domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") - security = PublicContact.get_default_security() security.email = "security@mail.gov" - security.domain = domain_contacts + security.domain = self.domain_contact security.save() - domain_contacts.security_contact = security + self.domain_contact.security_contact = security - expected_security_contact = domain_contacts.map_epp_contact_to_public_contact( - self.mockSecurityContact, "securityContact", "security" - ) - + expected_security_contact = ( + self.domain_contact.map_epp_contact_to_public_contact( + self.mockSecurityContact, "securityContact", "security" + ) + ) - contact_dict = domain_contacts.security_contact.__dict__ + contact_dict = self.domain_contact.security_contact.__dict__ expected_dict = expected_security_contact.__dict__ - contact_dict.pop('_state') - expected_dict.pop('_state') + contact_dict.pop("_state") + expected_dict.pop("_state") self.mockedSendFunction.assert_has_calls( [ @@ -522,10 +526,22 @@ class TestRegistrantContacts(MockEppLib): commands.InfoDomain(name="freeman.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call( + commands.InfoContact(id="registrantContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="securityContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="administrativeContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="technicalContact", auth_info=None), + cleaned=True, + ), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) @@ -533,18 +549,17 @@ class TestRegistrantContacts(MockEppLib): self.assertEqual(contact_dict, expected_dict) def test_setter_getter_security_email(self): - domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") + expected_security_contact = ( + self.domain_contact.map_epp_contact_to_public_contact( + self.mockSecurityContact, "securityContact", "security" + ) + ) - expected_security_contact = domain_contacts.map_epp_contact_to_public_contact( - self.mockSecurityContact, "securityContact", "security" - ) - - - contact_dict = domain_contacts.security_contact.__dict__ + contact_dict = self.domain_contact.security_contact.__dict__ expected_dict = expected_security_contact.__dict__ - contact_dict.pop('_state') - expected_dict.pop('_state') + contact_dict.pop("_state") + expected_dict.pop("_state") # Getter functions properly... self.mockedSendFunction.assert_has_calls( @@ -553,10 +568,22 @@ class TestRegistrantContacts(MockEppLib): commands.InfoDomain(name="freeman.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call( + commands.InfoContact(id="registrantContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="securityContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="administrativeContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="technicalContact", auth_info=None), + cleaned=True, + ), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) @@ -564,7 +591,7 @@ class TestRegistrantContacts(MockEppLib): self.assertEqual(contact_dict, expected_dict) # Setter functions properly... - domain_contacts.security_contact.email = "converge@mail.com" + self.domain_contact.security_contact.email = "converge@mail.com" expected_security_contact.email = "converge@mail.com" self.mockedSendFunction.assert_has_calls( [ @@ -572,14 +599,28 @@ class TestRegistrantContacts(MockEppLib): commands.InfoDomain(name="freeman.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call( + commands.InfoContact(id="registrantContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="securityContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="administrativeContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="technicalContact", auth_info=None), + cleaned=True, + ), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) - self.assertEqual(domain_contacts.security_contact.email, expected_security_contact.email) + self.assertEqual( + self.domain_contact.security_contact.email, expected_security_contact.email + ) @skip("not implemented yet") def test_setter_getter_security_email_mock_user(self): @@ -588,28 +629,28 @@ class TestRegistrantContacts(MockEppLib): raise def test_contact_getter_technical(self): - domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") - technical = PublicContact.get_default_technical() technical.email = "tech@mail.gov" - technical.domain = domain_contacts + technical.domain = self.domain_contact technical.save() - expected_technical_contact = domain_contacts.map_epp_contact_to_public_contact( - self.mockTechnicalContact, "technicalContact", "tech" - ) - - domain_contacts.technical_contact = technical + expected_technical_contact = ( + self.domain_contact.map_epp_contact_to_public_contact( + self.mockTechnicalContact, "technicalContact", "tech" + ) + ) - contact_dict = domain_contacts.technical_contact.__dict__ + self.domain_contact.technical_contact = technical + + contact_dict = self.domain_contact.technical_contact.__dict__ expected_dict = expected_technical_contact.__dict__ # There has to be a better way to do this. - # Since Cache creates a new object, it causes + # Since Cache creates a new object, it causes # a desync between each instance. Basically, # these two objects will never be the same. - contact_dict.pop('_state') - expected_dict.pop('_state') + contact_dict.pop("_state") + expected_dict.pop("_state") self.mockedSendFunction.assert_has_calls( [ @@ -617,10 +658,22 @@ class TestRegistrantContacts(MockEppLib): commands.InfoDomain(name="freeman.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call( + commands.InfoContact(id="registrantContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="securityContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="administrativeContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="technicalContact", auth_info=None), + cleaned=True, + ), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) @@ -629,24 +682,24 @@ class TestRegistrantContacts(MockEppLib): def test_contact_getter_administrative(self): self.maxDiff = None - domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") - administrative = PublicContact.get_default_administrative() administrative.email = "admin@mail.gov" - administrative.domain = domain_contacts + administrative.domain = self.domain_contact administrative.save() - expected_administrative_contact = domain_contacts.map_epp_contact_to_public_contact( - self.mockAdministrativeContact, "administrativeContact", "admin" - ) - - domain_contacts.administrative_contact = administrative + expected_administrative_contact = ( + self.domain_contact.map_epp_contact_to_public_contact( + self.mockAdministrativeContact, "administrativeContact", "admin" + ) + ) - contact_dict = domain_contacts.administrative_contact.__dict__ + self.domain_contact.administrative_contact = administrative + + contact_dict = self.domain_contact.administrative_contact.__dict__ expected_dict = expected_administrative_contact.__dict__ - contact_dict.pop('_state') - expected_dict.pop('_state') + contact_dict.pop("_state") + expected_dict.pop("_state") self.mockedSendFunction.assert_has_calls( [ @@ -654,38 +707,50 @@ class TestRegistrantContacts(MockEppLib): commands.InfoDomain(name="freeman.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call( + commands.InfoContact(id="registrantContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="securityContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="administrativeContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="technicalContact", auth_info=None), + cleaned=True, + ), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) self.assertEqual(contact_dict, expected_dict) - - def test_contact_getter_registrant(self): - domain_contacts, _ = Domain.objects.get_or_create(name="freeman.gov") + def test_contact_getter_registrant(self): registrant = PublicContact.get_default_registrant() registrant.email = "registrant@mail.gov" - registrant.domain = domain_contacts + registrant.domain = self.domain_contact registrant.save() expected_registrant_contact = registrant - domain_contacts.registrant_contact = registrant + self.domain_contact.registrant_contact = registrant - expected_registrant_contact = domain_contacts.map_epp_contact_to_public_contact( - self.mockRegistrantContact, "registrantContact", "registrant" - ) - - domain_contacts.registrant_contact = registrant + expected_registrant_contact = ( + self.domain_contact.map_epp_contact_to_public_contact( + self.mockRegistrantContact, "registrantContact", "registrant" + ) + ) - contact_dict = domain_contacts.registrant_contact.__dict__ + self.domain_contact.registrant_contact = registrant + + contact_dict = self.domain_contact.registrant_contact.__dict__ expected_dict = expected_registrant_contact.__dict__ - contact_dict.pop('_state') - expected_dict.pop('_state') + contact_dict.pop("_state") + expected_dict.pop("_state") self.mockedSendFunction.assert_has_calls( [ @@ -693,10 +758,22 @@ class TestRegistrantContacts(MockEppLib): commands.InfoDomain(name="freeman.gov", auth_info=None), cleaned=True, ), - call(commands.InfoContact(id='registrantContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='securityContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='administrativeContact', auth_info=None), cleaned=True), - call(commands.InfoContact(id='technicalContact', auth_info=None), cleaned=True), + call( + commands.InfoContact(id="registrantContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="securityContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="administrativeContact", auth_info=None), + cleaned=True, + ), + call( + commands.InfoContact(id="technicalContact", auth_info=None), + cleaned=True, + ), call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) From 83d1a278f796f11f2dbfd0a37b8e62e96cd29335 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:15:00 -0600 Subject: [PATCH 14/58] TEMP - For push to sandbox --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 339c5e765..70e186b82 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -656,7 +656,7 @@ class Domain(TimeStampedModel, DomainHelper): # Q: I don't like this function name much, # what would be better here? - # Note for reviewers: + # Q2: # This can likely be done without passing in # contact_id and contact_type and instead embedding it inside of # contact, but the tradeoff for that is that it unnecessarily complicates using this From 4fea8555f0e3c21aada2a171b6ca23086e1494c2 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:51:47 -0600 Subject: [PATCH 15/58] Bug fix for duplicate PublicContacts --- src/registrar/models/domain.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 70e186b82..630c737d2 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -537,6 +537,10 @@ class Domain(TimeStampedModel, DomainHelper): self._update_domain_with_contact(contact=contact, rem=False) # if already exists just update elif alreadyExistsInRegistry: + old_contact = PublicContact.objects.filter(registry_id=contact.registry_id, contact_type=contact.contact_type).exclude(domain=self) + if(old_contact.count > 0){ + old_contact.delete() + } current_contact = PublicContact.objects.filter( registry_id=contact.registry_id ).get() From dc73d81b45c7ef1960174673a77ff58587bbbe56 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:55:47 -0600 Subject: [PATCH 16/58] This is not java! --- src/registrar/models/domain.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 630c737d2..8a5b3ac39 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -538,9 +538,8 @@ class Domain(TimeStampedModel, DomainHelper): # if already exists just update elif alreadyExistsInRegistry: old_contact = PublicContact.objects.filter(registry_id=contact.registry_id, contact_type=contact.contact_type).exclude(domain=self) - if(old_contact.count > 0){ + if(old_contact.count > 0): old_contact.delete() - } current_contact = PublicContact.objects.filter( registry_id=contact.registry_id ).get() From 803137b05169d95b71dfcef635813fa3dc9686ad Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:04:06 -0600 Subject: [PATCH 17/58] Typo --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 8a5b3ac39..f36624bee 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -538,7 +538,7 @@ class Domain(TimeStampedModel, DomainHelper): # if already exists just update elif alreadyExistsInRegistry: old_contact = PublicContact.objects.filter(registry_id=contact.registry_id, contact_type=contact.contact_type).exclude(domain=self) - if(old_contact.count > 0): + if(old_contact.count() > 0): old_contact.delete() current_contact = PublicContact.objects.filter( registry_id=contact.registry_id From 3835293b237db002b9f5225440722f8a969cbd6c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:42:20 -0600 Subject: [PATCH 18/58] Improved test cases / Fixed PublicContact persistence --- src/registrar/models/domain.py | 21 +- .../templates/domain_security_email.html | 2 +- src/registrar/tests/common.py | 22 +- src/registrar/tests/test_models_domain.py | 294 +++++------------- 4 files changed, 107 insertions(+), 232 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index f36624bee..20a95092b 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -516,6 +516,7 @@ class Domain(TimeStampedModel, DomainHelper): .filter(domain=self, contact_type=contact.contact_type) .get() ) + logger.info(f"_set_singleton_contact() -> existing contact is... {existing_contact.__dict__}") if isRegistrant: # send update domain only for registant contacts existing_contact.delete() @@ -537,9 +538,8 @@ class Domain(TimeStampedModel, DomainHelper): self._update_domain_with_contact(contact=contact, rem=False) # if already exists just update elif alreadyExistsInRegistry: - old_contact = PublicContact.objects.filter(registry_id=contact.registry_id, contact_type=contact.contact_type).exclude(domain=self) - if(old_contact.count() > 0): - old_contact.delete() + logger.debug(f"aaaa12 {contact.__dict__}") + current_contact = PublicContact.objects.filter( registry_id=contact.registry_id ).get() @@ -667,7 +667,7 @@ class Domain(TimeStampedModel, DomainHelper): # I'm sure though that there is an easier alternative... # TLDR: This doesn't look as pretty, but it makes using this function easier def map_epp_contact_to_public_contact( - self, contact: eppInfo.InfoContactResultData, contact_id, contact_type + self, contact: eppInfo.InfoContactResultData, contact_id, contact_type, create_object=True ): """Maps the Epp contact representation to a PublicContact object. @@ -676,6 +676,8 @@ class Domain(TimeStampedModel, DomainHelper): contact_id -> str: The given registry_id of the object (i.e "cheese@cia.gov") contact_type -> str: The given contact type, (i.e. "tech" or "registrant") + + create_object -> bool: Flag for if this object is saved or not """ if contact is None: @@ -686,6 +688,12 @@ class Domain(TimeStampedModel, DomainHelper): if contact_id is None: raise ValueError("contact_id is None") + + if len(contact_id) > 16 or len(contact_id) < 1: + raise ValueError( + "contact_id is of invalid length. " + f"Cannot exceed 16 characters, got {contact_id} with a length of {len(contact_id)}" + ) logger.debug(f"map_epp_contact_to_public_contact contact -> {contact}") logger.debug(f"What is the type? {type(contact)}") @@ -708,7 +716,7 @@ class Domain(TimeStampedModel, DomainHelper): fillvalue=None, ) ) - + logger.debug(f"WHAT IS CONTACT {contact_id} {len(contact_id)}") desired_contact = PublicContact( domain=self, contact_type=contact_type, @@ -725,6 +733,9 @@ class Domain(TimeStampedModel, DomainHelper): sp=addr.sp, **streets, ) + # Saves to DB + if(create_object): + desired_contact.save() return desired_contact def _request_contact_info(self, contact: PublicContact): diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index e20d67355..c7633638c 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -21,7 +21,7 @@ + >{% if domain.security_email is None %}Add security email{% else %}Save{% endif %} {% endblock %} {# domain_content #} diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 8baf60640..70e4b5cc1 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -570,9 +570,9 @@ class MockEppLib(TestCase): addr=common.ContactAddr( street=["4200 Wilson Blvd."], city="Arlington", - pc="VA", + pc="22201", cc="US", - sp="22201", + sp="VA", ), org="Cybersecurity and Infrastructure Security Agency", type="type", @@ -603,27 +603,27 @@ class MockEppLib(TestCase): "technicalContact", "tech@mail.gov" ) mockAdministrativeContact = dummyInfoContactResultData( - "administrativeContact", "admin@mail.gov" + "adminContact", "admin@mail.gov" ) mockRegistrantContact = dummyInfoContactResultData( - "registrantContact", "registrant@mail.gov" + "regContact", "registrant@mail.gov" ) mockDataInfoDomain = fakedEppObject( "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), - contacts=[common.DomainContact(contact="123", type="security")], + contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)], hosts=["fake.host.com"], ) InfoDomainWithContacts = fakedEppObject( "fakepw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), contacts=[ - common.DomainContact(contact="securityContact", type="security"), - common.DomainContact(contact="administrativeContact", type="admin"), - common.DomainContact(contact="technicalContact", type="tech"), + common.DomainContact(contact="securityContact", type=PublicContact.ContactTypeChoices.SECURITY), + common.DomainContact(contact="technicalContact", type=PublicContact.ContactTypeChoices.TECHNICAL), + common.DomainContact(contact="adminContact", type=PublicContact.ContactTypeChoices.ADMINISTRATIVE), ], hosts=["fake.host.com"], - registrant="registrantContact", + registrant="regContact", ) infoDomainNoContact = fakedEppObject( "security", @@ -659,9 +659,9 @@ class MockEppLib(TestCase): mocked_result = self.mockSecurityContact case "technicalContact": mocked_result = self.mockTechnicalContact - case "administrativeContact": + case "adminContact": mocked_result = self.mockAdministrativeContact - case "registrantContact": + case "regContact": mocked_result = self.mockRegistrantContact case "123": mocked_result = self.mockDataInfoContact diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 70ceeb812..7aed8c9ec 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -225,7 +225,7 @@ class TestDomainCreation(TestCase): class TestRegistrantContacts(MockEppLib): """Rule: Registrants may modify their WHOIS data""" - + def setUp(self): """ Background: @@ -449,7 +449,9 @@ class TestRegistrantContacts(MockEppLib): security_contact = self.domain.get_default_security_contact() security_contact.email = "originalUserEmail@gmail.com" security_contact.registry_id = "fail" - security_contact.save() + security_contact.domain = self.domain + self.domain.security_contact = security_contact + expectedCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) @@ -462,23 +464,29 @@ class TestRegistrantContacts(MockEppLib): ) ], ) - security_contact.email = "changedEmail@email.com" - security_contact.save() + self.domain.security_contact.email = "changedEmail@email.com" + #self.domain.security_contact.email = "changedEmail@email.com" expectedSecondCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) updateContact = self._convertPublicContactToEpp( security_contact, disclose_email=True, createContact=False ) + self.domain.security_contact.email = "changedEmailAgain@email.com" - expected_calls = [ - call(expectedCreateCommand, cleaned=True), - call(expectedUpdateDomain, cleaned=True), - call(expectedSecondCreateCommand, cleaned=True), - call(updateContact, cleaned=True), - ] - self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) + # Check if security_contact is what we expect... + self.assertEqual(self.domain.security_contact.email, "changedEmailAgain@email.com") + # If the item in PublicContact is as expected... + current_item = PublicContact.objects.filter(domain=self.domain).get() + self.assertEqual(current_item.email, "changedEmailAgain@email.com") + + # Check if cache stored it correctly... + self.assertEqual("contacts" in self.domain._cache) + cached_item = self.domain._cache["contacts"] + self.assertTrue(cached_item[0]) + + @skip("not implemented yet") def test_update_is_unsuccessful(self): @@ -502,122 +510,58 @@ class TestRegistrantContacts(MockEppLib): pass def test_contact_getter_security(self): + # Create prexisting object... security = PublicContact.get_default_security() security.email = "security@mail.gov" security.domain = self.domain_contact - security.save() self.domain_contact.security_contact = security - expected_security_contact = ( - self.domain_contact.map_epp_contact_to_public_contact( - self.mockSecurityContact, "securityContact", "security" - ) - ) - - contact_dict = self.domain_contact.security_contact.__dict__ - expected_dict = expected_security_contact.__dict__ - - contact_dict.pop("_state") - expected_dict.pop("_state") + expected_security_contact = PublicContact.objects.filter( + registry_id=self.domain_contact.security_contact.registry_id, + contact_type = PublicContact.ContactTypeChoices.SECURITY + ).get() + # Checks if we grab the correct PublicContact... + self.assertEqual(self.domain_contact.security_contact, expected_security_contact) self.mockedSendFunction.assert_has_calls( [ - call( - commands.InfoDomain(name="freeman.gov", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="registrantContact", auth_info=None), - cleaned=True, - ), call( commands.InfoContact(id="securityContact", auth_info=None), cleaned=True, ), - call( - commands.InfoContact(id="administrativeContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="technicalContact", auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) - - self.assertEqual(contact_dict, expected_dict) + # Checks if we are recieving the cache we expect... + self.assertEqual(self.domain_contact._cache["contacts"][0], expected_security_contact) def test_setter_getter_security_email(self): - expected_security_contact = ( - self.domain_contact.map_epp_contact_to_public_contact( - self.mockSecurityContact, "securityContact", "security" - ) - ) + security = PublicContact.get_default_security() + security.email = "security@mail.gov" + security.domain = self.domain_contact + self.domain_contact.security_contact = security - contact_dict = self.domain_contact.security_contact.__dict__ - expected_dict = expected_security_contact.__dict__ + expected_security_contact = PublicContact.objects.filter( + registry_id=self.domain_contact.security_contact.registry_id, + contact_type = PublicContact.ContactTypeChoices.SECURITY + ).get() - contact_dict.pop("_state") - expected_dict.pop("_state") - - # Getter functions properly... + # Checks if we grab the correct PublicContact... + self.assertEqual(self.domain_contact.security_contact, expected_security_contact) self.mockedSendFunction.assert_has_calls( [ - call( - commands.InfoDomain(name="freeman.gov", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="registrantContact", auth_info=None), - cleaned=True, - ), call( commands.InfoContact(id="securityContact", auth_info=None), cleaned=True, ), - call( - commands.InfoContact(id="administrativeContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="technicalContact", auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) - - self.assertEqual(contact_dict, expected_dict) + # Checks if we are recieving the cache we expect... + self.assertEqual(self.domain_contact._cache["contacts"][0], expected_security_contact) # Setter functions properly... self.domain_contact.security_contact.email = "converge@mail.com" expected_security_contact.email = "converge@mail.com" - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoDomain(name="freeman.gov", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="registrantContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="securityContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="administrativeContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="technicalContact", auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="fake.host.com"), cleaned=True), - ] - ) + self.assertEqual( self.domain_contact.security_contact.email, expected_security_contact.email ) @@ -629,156 +573,76 @@ class TestRegistrantContacts(MockEppLib): raise def test_contact_getter_technical(self): - technical = PublicContact.get_default_technical() - technical.email = "tech@mail.gov" - technical.domain = self.domain_contact - technical.save() + contact = PublicContact.get_default_technical() + contact.email = "technical@mail.gov" + contact.domain = self.domain_contact + self.domain_contact.technical_contact = contact - expected_technical_contact = ( - self.domain_contact.map_epp_contact_to_public_contact( - self.mockTechnicalContact, "technicalContact", "tech" - ) - ) - - self.domain_contact.technical_contact = technical - - contact_dict = self.domain_contact.technical_contact.__dict__ - expected_dict = expected_technical_contact.__dict__ - - # There has to be a better way to do this. - # Since Cache creates a new object, it causes - # a desync between each instance. Basically, - # these two objects will never be the same. - contact_dict.pop("_state") - expected_dict.pop("_state") + expected_contact = PublicContact.objects.filter( + registry_id=self.domain_contact.technical_contact.registry_id, + contact_type = PublicContact.ContactTypeChoices.TECHNICAL + ).get() + # Checks if we grab the correct PublicContact... + self.assertEqual(self.domain_contact.technical_contact, expected_contact) self.mockedSendFunction.assert_has_calls( [ - call( - commands.InfoDomain(name="freeman.gov", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="registrantContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="securityContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="administrativeContact", auth_info=None), - cleaned=True, - ), call( commands.InfoContact(id="technicalContact", auth_info=None), cleaned=True, ), - call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) - - self.assertEqual(contact_dict, expected_dict) + # Checks if we are recieving the cache we expect... + self.assertEqual(self.domain_contact._cache["contacts"][1], expected_contact) def test_contact_getter_administrative(self): - self.maxDiff = None - administrative = PublicContact.get_default_administrative() - administrative.email = "admin@mail.gov" - administrative.domain = self.domain_contact - administrative.save() + contact = PublicContact.get_default_administrative() + contact.email = "admin@mail.gov" + contact.domain = self.domain_contact + self.domain_contact.administrative_contact = contact - expected_administrative_contact = ( - self.domain_contact.map_epp_contact_to_public_contact( - self.mockAdministrativeContact, "administrativeContact", "admin" - ) - ) - - self.domain_contact.administrative_contact = administrative - - contact_dict = self.domain_contact.administrative_contact.__dict__ - expected_dict = expected_administrative_contact.__dict__ - - contact_dict.pop("_state") - expected_dict.pop("_state") + expected_contact = PublicContact.objects.filter( + registry_id=self.domain_contact.administrative_contact.registry_id, + contact_type = PublicContact.ContactTypeChoices.ADMINISTRATIVE + ).get() + # Checks if we grab the correct PublicContact... + self.assertEqual(self.domain_contact.administrative_contact, expected_contact) self.mockedSendFunction.assert_has_calls( [ call( - commands.InfoDomain(name="freeman.gov", auth_info=None), + commands.InfoContact(id="adminContact", auth_info=None), cleaned=True, ), - call( - commands.InfoContact(id="registrantContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="securityContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="administrativeContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="technicalContact", auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) - - self.assertEqual(contact_dict, expected_dict) + # Checks if we are recieving the cache we expect... + self.assertEqual(self.domain_contact._cache["contacts"][2], expected_contact) def test_contact_getter_registrant(self): - registrant = PublicContact.get_default_registrant() - registrant.email = "registrant@mail.gov" - registrant.domain = self.domain_contact - registrant.save() + contact = PublicContact.get_default_registrant() + contact.email = "registrant@mail.gov" + contact.domain = self.domain_contact + self.domain_contact.registrant_contact = contact - expected_registrant_contact = registrant - self.domain_contact.registrant_contact = registrant - - expected_registrant_contact = ( - self.domain_contact.map_epp_contact_to_public_contact( - self.mockRegistrantContact, "registrantContact", "registrant" - ) - ) - - self.domain_contact.registrant_contact = registrant - - contact_dict = self.domain_contact.registrant_contact.__dict__ - expected_dict = expected_registrant_contact.__dict__ - - contact_dict.pop("_state") - expected_dict.pop("_state") + expected_contact = PublicContact.objects.filter( + registry_id=self.domain_contact.registrant_contact.registry_id, + contact_type = PublicContact.ContactTypeChoices.REGISTRANT + ).get() + # Checks if we grab the correct PublicContact... + self.assertEqual(self.domain_contact.registrant_contact, expected_contact) self.mockedSendFunction.assert_has_calls( [ call( - commands.InfoDomain(name="freeman.gov", auth_info=None), + commands.InfoContact(id="regContact", auth_info=None), cleaned=True, ), - call( - commands.InfoContact(id="registrantContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="securityContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="administrativeContact", auth_info=None), - cleaned=True, - ), - call( - commands.InfoContact(id="technicalContact", auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="fake.host.com"), cleaned=True), ] ) - - self.assertEqual(contact_dict, expected_dict) + # Checks if we are recieving the cache we expect... + self.assertEqual(self.domain_contact._cache["registrant"], expected_contact) class TestRegistrantNameservers(TestCase): From 917d19d1442b8f5fad0eb00f3e3268ca0b8d5c78 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:31:03 -0600 Subject: [PATCH 19/58] Temp duplicate fix Temporary fix for the duplicate issue... Need to find a better solution --- src/registrar/models/domain.py | 72 ++++++++----------- .../templates/domain_security_email.html | 2 +- src/registrar/tests/test_models_domain.py | 48 ++++++++----- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 20a95092b..89fa4ba71 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -149,7 +149,6 @@ class Domain(TimeStampedModel, DomainHelper): """Called during set. Example: `domain.registrant = 'abc123'`.""" super().__set__(obj, value) # always invalidate cache after sending updates to the registry - logger.debug("cache was invalidateds") obj._invalidate_cache() def __delete__(self, obj): @@ -538,11 +537,14 @@ class Domain(TimeStampedModel, DomainHelper): self._update_domain_with_contact(contact=contact, rem=False) # if already exists just update elif alreadyExistsInRegistry: - logger.debug(f"aaaa12 {contact.__dict__}") - - current_contact = PublicContact.objects.filter( + filtered_contacts = PublicContact.objects.filter( registry_id=contact.registry_id - ).get() + ) + if(filtered_contacts.count() > 1): + filtered_contacts.order_by('id').first().delete() + + current_contact = filtered_contacts.get() + logger.debug(f"current contact was accessed {current_contact}") if current_contact.email != contact.email: self._update_epp_contact(contact=contact) @@ -716,7 +718,6 @@ class Domain(TimeStampedModel, DomainHelper): fillvalue=None, ) ) - logger.debug(f"WHAT IS CONTACT {contact_id} {len(contact_id)}") desired_contact = PublicContact( domain=self, contact_type=contact_type, @@ -735,7 +736,10 @@ class Domain(TimeStampedModel, DomainHelper): ) # Saves to DB if(create_object): - desired_contact.save() + create = PublicContact.objects.filter(registry_id=contact_id, contact_type=contact_type, domain=self) + if(create.count() == 0): + desired_contact.save() + return desired_contact def _request_contact_info(self, contact: PublicContact): @@ -803,7 +807,6 @@ class Domain(TimeStampedModel, DomainHelper): logger.warning("generic_contact_getter -> Contact does not exist") logger.warning(error) # Should we just raise an error instead? - return self.get_contact_default(contact_type_choice) else: print(f"generic_contact_getter -> contacts?? {contacts}") # --> Map to public contact @@ -1188,11 +1191,22 @@ class Domain(TimeStampedModel, DomainHelper): # no point in removing cleaned["hosts"] = [] for name in cleaned["_hosts"]: - # For reviewers - slight refactor here - # as we may need this for future hosts tickets - # (potential host mapper?). - # Can remove if unnecessary - cleaned["hosts"].append(self._get_host_as_dict(name)) + # we do not use _get_or_create_* because we expect the object we + # just asked the registry for still exists -- + # if not, that's a problem + req = commands.InfoHost(name=name) + data = registry.send(req, cleaned=True).res_data[0] + # extract properties from response + # (Ellipsis is used to mean "null") + host = { + "name": name, + "addrs": getattr(data, "addrs", ...), + "cr_date": getattr(data, "cr_date", ...), + "statuses": getattr(data, "statuses", ...), + "tr_date": getattr(data, "tr_date", ...), + "up_date": getattr(data, "up_date", ...), + } + cleaned["hosts"].append({k: v for k, v in host.items() if v is not ...}) # replace the prior cache with new data self._cache = cleaned @@ -1215,36 +1229,6 @@ class Domain(TimeStampedModel, DomainHelper): full_object, contact.registry_id, contact.contact_type ) - def _get_host_as_dict(self, host_name): - """Returns the result of commands.InfoHost as a dictionary - - Returns the following, excluding null fields: - { - "name": name, - "addrs": addr, - "cr_date": cr_date, - "statuses": statuses, - "tr_date": tr_date, - "up_date": up_date, - } - """ - # we do not use _get_or_create_* because we expect the object we - # just asked the registry for still exists -- - # if not, that's a problem - req = commands.InfoHost(name=host_name) - data = registry.send(req, cleaned=True).res_data[0] - # extract properties from response - # (Ellipsis is used to mean "null") - host = { - "name": host_name, - "addrs": getattr(data, "addrs", ...), - "cr_date": getattr(data, "cr_date", ...), - "statuses": getattr(data, "statuses", ...), - "tr_date": getattr(data, "tr_date", ...), - "up_date": getattr(data, "up_date", ...), - } - return {k: v for k, v in host.items() if v is not ...} - def _invalidate_cache(self): """Remove cache data when updates are made.""" logger.debug(f"cache was cleared! {self.__dict__}") @@ -1252,6 +1236,7 @@ class Domain(TimeStampedModel, DomainHelper): def _get_property(self, property): """Get some piece of info about a domain.""" + logger.info(f"_get_property() -> prop is... {property} prop in cache... {property not in self._cache} cache is {self._cache}") if property not in self._cache: self._fetch_cache( fetch_hosts=(property == "hosts"), @@ -1259,7 +1244,6 @@ class Domain(TimeStampedModel, DomainHelper): ) if property in self._cache: - logger.debug(f"hit here also!! {property}") logger.debug(self._cache[property]) return self._cache[property] else: diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index c7633638c..fb791ad86 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -21,7 +21,7 @@ + >{% if domain.security_email is None and domain.security_email.email != 'g'%}Add security email{% else %}Save{% endif %} {% endblock %} {# domain_content #} diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 7aed8c9ec..1d5222d79 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -43,16 +43,30 @@ class TestDomainCache(MockEppLib): # using a setter should clear the cache domain.expiration_date = datetime.date.today() self.assertEquals(domain._cache, {}) - + expectedCreateContact = self._convertPublicContactToEpp(domain.security_contact, False, createContact=True) # send should have been called only once self.mockedSendFunction.assert_has_calls( [ - call( - commands.InfoDomain(name="igorville.gov", auth_info=None), - cleaned=True, + call(commands.InfoDomain(name='igorville.gov', auth_info=None), cleaned=True), + call(commands.InfoContact(id='123', auth_info=None), cleaned=True), + call(expectedCreateContact), + call(commands.UpdateDomain( + name='igorville.gov', + add=[ + common.DomainContact( + contact='123', + type=PublicContact.ContactTypeChoices.SECURITY + ) + ], + rem=[], + nsset=None, + keyset=None, + registrant=None, + auth_info=None + ), + cleaned=True ), - call(commands.InfoContact(id="123", auth_info=None), cleaned=True), - call(commands.InfoHost(name="fake.host.com"), cleaned=True), + call(commands.InfoHost(name='fake.host.com'), cleaned=True) ] ) # Clear the cache @@ -95,9 +109,7 @@ class TestDomainCache(MockEppLib): common.DomainContact(contact="123", type="security"), ] expectedContactsList = [ - domain.map_epp_contact_to_public_contact( - self.mockDataInfoContact, "123", "security" - ) + domain.security_contact ] expectedHostsDict = { "name": self.mockDataInfoDomain.hosts[0], @@ -449,9 +461,7 @@ class TestRegistrantContacts(MockEppLib): security_contact = self.domain.get_default_security_contact() security_contact.email = "originalUserEmail@gmail.com" security_contact.registry_id = "fail" - security_contact.domain = self.domain - self.domain.security_contact = security_contact - + security_contact.save() expectedCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) @@ -464,22 +474,28 @@ class TestRegistrantContacts(MockEppLib): ) ], ) - self.domain.security_contact.email = "changedEmail@email.com" - #self.domain.security_contact.email = "changedEmail@email.com" + security_contact.email = "changedEmail@email.com" + security_contact.save() expectedSecondCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) updateContact = self._convertPublicContactToEpp( security_contact, disclose_email=True, createContact=False ) - self.domain.security_contact.email = "changedEmailAgain@email.com" + expected_calls = [ + call(expectedCreateCommand, cleaned=True), + call(expectedUpdateDomain, cleaned=True), + call(expectedSecondCreateCommand, cleaned=True), + call(updateContact, cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) # Check if security_contact is what we expect... self.assertEqual(self.domain.security_contact.email, "changedEmailAgain@email.com") # If the item in PublicContact is as expected... current_item = PublicContact.objects.filter(domain=self.domain).get() - self.assertEqual(current_item.email, "changedEmailAgain@email.com") + self.assertEqual(current_item.email, "changedEmail@email.com") # Check if cache stored it correctly... self.assertEqual("contacts" in self.domain._cache) From 4fed857ea70d553160ab76fcb96b45d125fb4f52 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 19 Sep 2023 21:06:48 -0600 Subject: [PATCH 20/58] Add/Save button --- src/registrar/templates/domain_security_email.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index fb791ad86..93a8e1997 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -21,7 +21,7 @@ + >{% if domain.security_email is None or domain.security_email.email != 'dotgov@cisa.dhs.gov'%}Add security email{% else %}Save{% endif %} {% endblock %} {# domain_content #} From 2f9c37e8ee2f284cc91882536adf26069b0b0dd6 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:54:33 -0600 Subject: [PATCH 21/58] Logic rewrite (performance) / Duplicates --- src/registrar/models/domain.py | 83 +++++++++++++------ .../templates/domain_security_email.html | 2 +- src/registrar/tests/test_models_domain.py | 9 +- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 89fa4ba71..65237453b 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1,6 +1,7 @@ from itertools import zip_longest import logging - +from queue import Queue +from threading import Thread from datetime import date from string import digits from django_fsm import FSMField, transition # type: ignore @@ -22,6 +23,7 @@ from .utility.time_stamped_model import TimeStampedModel from .public_contact import PublicContact + logger = logging.getLogger(__name__) @@ -530,6 +532,13 @@ class Domain(TimeStampedModel, DomainHelper): "Raising error after removing and adding a new contact" ) raise (err) + elif alreadyExistsInRegistry: + filtered_contacts = PublicContact.objects.filter( + registry_id=contact.registry_id + ) + + if(filtered_contacts.count() > 1): + filtered_contacts.order_by('id').first().delete() # update domain with contact or update the contact itself if not isEmptySecurity: @@ -537,12 +546,6 @@ class Domain(TimeStampedModel, DomainHelper): self._update_domain_with_contact(contact=contact, rem=False) # if already exists just update elif alreadyExistsInRegistry: - filtered_contacts = PublicContact.objects.filter( - registry_id=contact.registry_id - ) - if(filtered_contacts.count() > 1): - filtered_contacts.order_by('id').first().delete() - current_contact = filtered_contacts.get() logger.debug(f"current contact was accessed {current_contact}") @@ -698,7 +701,6 @@ class Domain(TimeStampedModel, DomainHelper): ) logger.debug(f"map_epp_contact_to_public_contact contact -> {contact}") - logger.debug(f"What is the type? {type(contact)}") if not isinstance(contact, eppInfo.InfoContactResultData): raise ValueError("Contact must be of type InfoContactResultData") @@ -794,28 +796,47 @@ class Domain(TimeStampedModel, DomainHelper): cache_contact_helper(PublicContact.ContactTypeChoices.SECURITY), or cache_contact_helper("security") """ + items = PublicContact.objects.filter(domain=self, contact_type=contact_type_choice) + if(items.count() > 1): + raise ValueError(f"Multiple contacts exist for {contact_type_choice}") + + # Grab the first item in an array of size 1. + # We use this instead of .get() as we can expect + # values of 'None' occasionally (such as when an object + # only exists on the registry) + current_contact = items.first() + # If we have an item in our DB, + # and if contacts hasn't been cleared (meaning data was set)... + if(current_contact is not None): + if("contacts" not in self._cache): + logger.info("Contact was not found in cache but was found in DB") + return current_contact + try: - # TODO - refactor + # registrant_contact(s) are an edge case. They exist on + # the "registrant" property as opposed to contacts. desired_property = "contacts" - # The contact type 'registrant' is stored under a different property if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: desired_property = "registrant" + + # If it for some reason doesn't exist in our local DB, + # but exists in our cache, grab that + if(self._cache and desired_property in self._cache): + return self.grab_contact_in_keys(self._cache[desired_property], contact_type_choice) + + # Finally, if all else fails, grab from the registry contacts = self._get_property(desired_property) - if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: - contacts = [contacts] - except KeyError as error: - logger.warning("generic_contact_getter -> Contact does not exist") - logger.warning(error) - # Should we just raise an error instead? - else: - print(f"generic_contact_getter -> contacts?? {contacts}") - # --> Map to public contact + + # Grab from cache after its been created cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) if cached_contact is None: raise ValueError("No contact was found in cache or the registry") - # Convert it from an EppLib object to PublicContact return cached_contact + except RegistryError as error: + # Q: Should we be raising an error instead? + logger.error(error) + return None def get_default_security_contact(self): """Gets the default security contact.""" @@ -848,14 +869,28 @@ class Domain(TimeStampedModel, DomainHelper): For example, check_type = 'security' """ + # Registrant doesn't exist as an array + if(check_type == PublicContact.ContactTypeChoices.REGISTRANT): + if ( + isinstance(contacts, PublicContact) + and contacts.contact_type is not None + and contacts.contact_type == check_type + ): + if(contacts.registry_id is None): + raise ValueError("registry_id cannot be None") + return contacts + else: + raise ValueError("Invalid contact object for registrant_contact") + for contact in contacts: print(f"grab_contact_in_keys -> contact item {contact.__dict__}") if ( isinstance(contact, PublicContact) - and contact.registry_id is not None and contact.contact_type is not None and contact.contact_type == check_type ): + if(contact.registry_id is None): + raise ValueError("registry_id cannot be None") return contact # If the for loop didn't do a return, @@ -1151,11 +1186,9 @@ class Domain(TimeStampedModel, DomainHelper): # Registrant should be of type PublicContact if "registrant" in cleaned.keys(): - # For linter... - _ = cleaned["registrant"] # Registrant, if it exists, should always exist in EppLib. - # If it doesn't, that is bad. We expect this to exist, always. - cleaned["registrant"] = self._registrant_to_public_contact(_) + # If it doesn't, that is bad. We expect this to exist + cleaned["registrant"] = self._registrant_to_public_contact(cleaned["registrant"]) if ( # fetch_contacts and diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index 93a8e1997..bab2e1846 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -21,7 +21,7 @@ + >{% if domain.security_email is None or domain.security_email.email == 'dotgov@cisa.dhs.gov'%}Add security email{% else %}Save{% endif %} {% endblock %} {# domain_content #} diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 1d5222d79..e11fe7cfd 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -492,15 +492,16 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) # Check if security_contact is what we expect... - self.assertEqual(self.domain.security_contact.email, "changedEmailAgain@email.com") + self.assertEqual(self.domain.security_contact.email, "changedEmail@email.com") + self.assertEqual(self.domain.security_contact, security_contact) # If the item in PublicContact is as expected... current_item = PublicContact.objects.filter(domain=self.domain).get() self.assertEqual(current_item.email, "changedEmail@email.com") # Check if cache stored it correctly... - self.assertEqual("contacts" in self.domain._cache) + self.assertTrue("contacts" in self.domain._cache) cached_item = self.domain._cache["contacts"] - self.assertTrue(cached_item[0]) + self.assertTrue(cached_item[0] == current_item) @@ -593,7 +594,7 @@ class TestRegistrantContacts(MockEppLib): contact.email = "technical@mail.gov" contact.domain = self.domain_contact self.domain_contact.technical_contact = contact - + logger.debug(f"here is the reason {self.domain_contact.technical_contact}") expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.technical_contact.registry_id, contact_type = PublicContact.ContactTypeChoices.TECHNICAL From dd57cf2ffd2deeb4fcd9276e31a3e8051dcb0304 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:28:29 -0600 Subject: [PATCH 22/58] Cleanup --- src/registrar/models/domain.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 65237453b..b134afbd9 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1,7 +1,5 @@ from itertools import zip_longest import logging -from queue import Queue -from threading import Thread from datetime import date from string import digits from django_fsm import FSMField, transition # type: ignore @@ -533,6 +531,9 @@ class Domain(TimeStampedModel, DomainHelper): ) raise (err) elif alreadyExistsInRegistry: + # If this item already exists in the registry, + # but doesn't have other contacts, we want to + # delete the old value filtered_contacts = PublicContact.objects.filter( registry_id=contact.registry_id ) @@ -739,7 +740,7 @@ class Domain(TimeStampedModel, DomainHelper): # Saves to DB if(create_object): create = PublicContact.objects.filter(registry_id=contact_id, contact_type=contact_type, domain=self) - if(create.count() == 0): + if(create.count() == 0 and contact_type != PublicContact.ContactTypeChoices.REGISTRANT): desired_contact.save() return desired_contact From 39d4646369ed06313792b311f7732297d934e2ff Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:02:25 -0600 Subject: [PATCH 23/58] Fix logic bug / tests --- src/registrar/models/domain.py | 61 ++++---- .../templates/domain_security_email.html | 2 +- src/registrar/tests/test_models_domain.py | 143 +++++++++--------- 3 files changed, 107 insertions(+), 99 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index b134afbd9..02660a35d 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -530,16 +530,6 @@ class Domain(TimeStampedModel, DomainHelper): "Raising error after removing and adding a new contact" ) raise (err) - elif alreadyExistsInRegistry: - # If this item already exists in the registry, - # but doesn't have other contacts, we want to - # delete the old value - filtered_contacts = PublicContact.objects.filter( - registry_id=contact.registry_id - ) - - if(filtered_contacts.count() > 1): - filtered_contacts.order_by('id').first().delete() # update domain with contact or update the contact itself if not isEmptySecurity: @@ -547,7 +537,9 @@ class Domain(TimeStampedModel, DomainHelper): self._update_domain_with_contact(contact=contact, rem=False) # if already exists just update elif alreadyExistsInRegistry: - current_contact = filtered_contacts.get() + current_contact = PublicContact.objects.filter( + registry_id=contact.registry_id + ).get() logger.debug(f"current contact was accessed {current_contact}") if current_contact.email != contact.email: @@ -737,14 +729,21 @@ class Domain(TimeStampedModel, DomainHelper): sp=addr.sp, **streets, ) + db_contact = PublicContact.objects.filter(registry_id=contact_id, contact_type=contact_type, domain=self) # Saves to DB - if(create_object): - create = PublicContact.objects.filter(registry_id=contact_id, contact_type=contact_type, domain=self) - if(create.count() == 0 and contact_type != PublicContact.ContactTypeChoices.REGISTRANT): - desired_contact.save() - - return desired_contact + if(create_object and db_contact.count() == 0): + desired_contact.save() + logger.debug(f"Created a new PublicContact: {desired_contact}") + return desired_contact + if(db_contact.count() == 1): + #if(desired_contact != db_contact): + #current = desired_contact + return db_contact.get() + # If it doesn't exist and we don't + # want to create it... + return desired_contact + def _request_contact_info(self, contact: PublicContact): try: req = commands.InfoContact(id=contact.registry_id) @@ -797,6 +796,17 @@ class Domain(TimeStampedModel, DomainHelper): cache_contact_helper(PublicContact.ContactTypeChoices.SECURITY), or cache_contact_helper("security") """ + # registrant_contact(s) are an edge case. They exist on + # the "registrant" property as opposed to contacts. + desired_property = "contacts" + if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: + desired_property = "registrant" + + # If it exists in our cache, grab that + if(self._cache and desired_property in self._cache): + return self.grab_contact_in_keys(self._cache[desired_property], contact_type_choice) + + # If not, check in our DB items = PublicContact.objects.filter(domain=self, contact_type=contact_type_choice) if(items.count() > 1): raise ValueError(f"Multiple contacts exist for {contact_type_choice}") @@ -809,22 +819,13 @@ class Domain(TimeStampedModel, DomainHelper): # If we have an item in our DB, # and if contacts hasn't been cleared (meaning data was set)... if(current_contact is not None): - if("contacts" not in self._cache): - logger.info("Contact was not found in cache but was found in DB") + # TODO - Should we sync with EppLib in this event? + # map_epp_contact_to_public_contact will grab any changes + # made in the setter, + logger.info("Contact was not found in cache but was found in DB") return current_contact try: - # registrant_contact(s) are an edge case. They exist on - # the "registrant" property as opposed to contacts. - desired_property = "contacts" - if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: - desired_property = "registrant" - - # If it for some reason doesn't exist in our local DB, - # but exists in our cache, grab that - if(self._cache and desired_property in self._cache): - return self.grab_contact_in_keys(self._cache[desired_property], contact_type_choice) - # Finally, if all else fails, grab from the registry contacts = self._get_property(desired_property) diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index bab2e1846..dbd257d86 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -21,7 +21,7 @@ + >{% if domain.security_email is None or domain.security_email.email == 'testdotgov@cisa.dhs.gov'%}Add security email{% else %}Save{% endif %} {% endblock %} {# domain_content #} diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index e11fe7cfd..6217f7610 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -27,12 +27,14 @@ logger = logging.getLogger(__name__) class TestDomainCache(MockEppLib): + def tearDown(self): + PublicContact.objects.all().delete() def test_cache_sets_resets(self): """Cache should be set on getter and reset on setter calls""" domain, _ = Domain.objects.get_or_create(name="igorville.gov") + self.maxDiff = None # trigger getter _ = domain.creation_date - logger.debug(f"what is the cache here? {domain._cache}") domain._get_property("contacts") # getter should set the domain cache with a InfoDomain object # (see InfoDomainResult) @@ -43,29 +45,14 @@ class TestDomainCache(MockEppLib): # using a setter should clear the cache domain.expiration_date = datetime.date.today() self.assertEquals(domain._cache, {}) - expectedCreateContact = self._convertPublicContactToEpp(domain.security_contact, False, createContact=True) + expectedCreateContact = self._convertPublicContactToEpp(domain.security_contact, True, createContact=True) # send should have been called only once self.mockedSendFunction.assert_has_calls( [ call(commands.InfoDomain(name='igorville.gov', auth_info=None), cleaned=True), call(commands.InfoContact(id='123', auth_info=None), cleaned=True), - call(expectedCreateContact), - call(commands.UpdateDomain( - name='igorville.gov', - add=[ - common.DomainContact( - contact='123', - type=PublicContact.ContactTypeChoices.SECURITY - ) - ], - rem=[], - nsset=None, - keyset=None, - registrant=None, - auth_info=None - ), - cleaned=True - ), + call(expectedCreateContact, cleaned=True), + call(commands.UpdateDomain(name='igorville.gov', add=[common.DomainContact(contact='123', type=PublicContact.ContactTypeChoices.SECURITY)], rem=[], nsset=None, keyset=None, registrant=None, auth_info=None), cleaned=True), call(commands.InfoHost(name='fake.host.com'), cleaned=True) ] ) @@ -83,7 +70,8 @@ class TestDomainCache(MockEppLib): # value should still be set correctly self.assertEqual(cr_date, self.mockDataInfoDomain.cr_date) self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) - + d = domain._cache["contacts"] + logger.debug(f"????? questions {d}") # send was only called once & not on the second getter call expectedCalls = [ call( @@ -100,8 +88,6 @@ class TestDomainCache(MockEppLib): def test_cache_nested_elements(self): """Cache works correctly with the nested objects cache and hosts""" domain, _ = Domain.objects.get_or_create(name="igorville.gov") - - self.maxDiff = None # The contact list will initally contain objects of type 'DomainContact' # this is then transformed into PublicContact, and cache should NOT # hold onto the DomainContact object @@ -128,20 +114,11 @@ class TestDomainCache(MockEppLib): # The contact list should not contain what is sent by the registry by default, # as _fetch_cache will transform the type to PublicContact self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) - # Assert that what we get from cache is inline with our mock - # Since our cache creates new items inside of our contact list, - # as we need to map DomainContact -> PublicContact, our mocked items - # will point towards a different location in memory (as they are different objects). - # This should be a problem only exclusive to our mocks, since we are not - # replicating the same item twice outside this context. That said, we want to check - # for data integrity, but do not care if they are of the same _state or not - for cached_contact, expected_contact in zip( - domain._cache["contacts"], expectedContactsList - ): - self.assertEqual( - {k: v for k, v in vars(cached_contact).items() if k != "_state"}, - {k: v for k, v in vars(expected_contact).items() if k != "_state"}, - ) + + self.assertEqual( + domain._cache["contacts"], + expectedContactsList + ) # get and check hosts is set correctly domain._get_property("hosts") @@ -149,10 +126,60 @@ class TestDomainCache(MockEppLib): # Clear the cache domain._invalidate_cache() - @skip("Not implemented yet") def test_map_epp_contact_to_public_contact(self): + self.maxDiff = None # Tests that the mapper is working how we expect - raise + domain, _ = Domain.objects.get_or_create(name="registry.gov") + mapped = domain.map_epp_contact_to_public_contact( + self.mockDataInfoContact, + self.mockDataInfoContact.id, + PublicContact.ContactTypeChoices.SECURITY + ) + expected_contact = PublicContact( + id=1, + domain=domain, + contact_type=PublicContact.ContactTypeChoices.SECURITY, + registry_id="123", + email="123@mail.gov", + voice="+1.8882820870", + fax="+1-212-9876543", + pw="lastPw", + name="Registry Customer Service", + org="Cybersecurity and Infrastructure Security Agency", + city="Arlington", + pc="22201", + cc="US", + sp="VA", + street1="4200 Wilson Blvd." + ) + # Match when these both were updated/created + expected_contact.updated_at = mapped.updated_at + expected_contact.created_at = mapped.created_at + # Mapped object is what we expect + self.assertEqual(mapped, expected_contact) + + in_db = PublicContact.objects.filter( + registry_id=domain.security_contact.registry_id, + contact_type = PublicContact.ContactTypeChoices.SECURITY + ).get() + # DB Object is the same as the mapped object + self.assertEqual(mapped, in_db) + + mapped_second = domain.map_epp_contact_to_public_contact( + self.mockDataInfoContact, + self.mockDataInfoContact.id, + PublicContact.ContactTypeChoices.SECURITY + ) + + in_db_once = PublicContact.objects.filter( + registry_id=domain.security_contact.registry_id, + contact_type = PublicContact.ContactTypeChoices.SECURITY + ) + self.assertEqual(mapped_second, in_db) + # If mapper is called a second time, + # it just grabs existing data rather than + # a new object + self.assertTrue(in_db_once.count() == 1) class TestDomainCreation(TestCase): @@ -462,6 +489,8 @@ class TestRegistrantContacts(MockEppLib): security_contact.email = "originalUserEmail@gmail.com" security_contact.registry_id = "fail" security_contact.save() + self.domain.security_contact = security_contact + expectedCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) @@ -476,6 +505,7 @@ class TestRegistrantContacts(MockEppLib): ) security_contact.email = "changedEmail@email.com" security_contact.save() + self.domain.security_contact = security_contact expectedSecondCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) @@ -498,13 +528,6 @@ class TestRegistrantContacts(MockEppLib): current_item = PublicContact.objects.filter(domain=self.domain).get() self.assertEqual(current_item.email, "changedEmail@email.com") - # Check if cache stored it correctly... - self.assertTrue("contacts" in self.domain._cache) - cached_item = self.domain._cache["contacts"] - self.assertTrue(cached_item[0] == current_item) - - - @skip("not implemented yet") def test_update_is_unsuccessful(self): """ @@ -528,11 +551,6 @@ class TestRegistrantContacts(MockEppLib): def test_contact_getter_security(self): # Create prexisting object... - security = PublicContact.get_default_security() - security.email = "security@mail.gov" - security.domain = self.domain_contact - self.domain_contact.security_contact = security - expected_security_contact = PublicContact.objects.filter( registry_id=self.domain_contact.security_contact.registry_id, contact_type = PublicContact.ContactTypeChoices.SECURITY @@ -556,7 +574,7 @@ class TestRegistrantContacts(MockEppLib): security.email = "security@mail.gov" security.domain = self.domain_contact self.domain_contact.security_contact = security - + expected_security_contact = PublicContact.objects.filter( registry_id=self.domain_contact.security_contact.registry_id, contact_type = PublicContact.ContactTypeChoices.SECURITY @@ -572,12 +590,16 @@ class TestRegistrantContacts(MockEppLib): ), ] ) + # Call getter... + _ = self.domain_contact.security_contact # Checks if we are recieving the cache we expect... self.assertEqual(self.domain_contact._cache["contacts"][0], expected_security_contact) # Setter functions properly... - self.domain_contact.security_contact.email = "converge@mail.com" - expected_security_contact.email = "converge@mail.com" + security.email = "123@mail.com" + security.save() + self.domain_contact.security_contact = security + expected_security_contact.email = "123@mail.com" self.assertEqual( self.domain_contact.security_contact.email, expected_security_contact.email @@ -590,11 +612,6 @@ class TestRegistrantContacts(MockEppLib): raise def test_contact_getter_technical(self): - contact = PublicContact.get_default_technical() - contact.email = "technical@mail.gov" - contact.domain = self.domain_contact - self.domain_contact.technical_contact = contact - logger.debug(f"here is the reason {self.domain_contact.technical_contact}") expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.technical_contact.registry_id, contact_type = PublicContact.ContactTypeChoices.TECHNICAL @@ -614,11 +631,6 @@ class TestRegistrantContacts(MockEppLib): self.assertEqual(self.domain_contact._cache["contacts"][1], expected_contact) def test_contact_getter_administrative(self): - contact = PublicContact.get_default_administrative() - contact.email = "admin@mail.gov" - contact.domain = self.domain_contact - self.domain_contact.administrative_contact = contact - expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.administrative_contact.registry_id, contact_type = PublicContact.ContactTypeChoices.ADMINISTRATIVE @@ -638,11 +650,6 @@ class TestRegistrantContacts(MockEppLib): self.assertEqual(self.domain_contact._cache["contacts"][2], expected_contact) def test_contact_getter_registrant(self): - contact = PublicContact.get_default_registrant() - contact.email = "registrant@mail.gov" - contact.domain = self.domain_contact - self.domain_contact.registrant_contact = contact - expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.registrant_contact.registry_id, contact_type = PublicContact.ContactTypeChoices.REGISTRANT From 13c950aae96e3c77ac9d7946a134f830fb9f2ded Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:21:50 -0600 Subject: [PATCH 24/58] Added context on logger --- src/registrar/models/domain.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 02660a35d..edf6d5c4b 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -822,7 +822,10 @@ class Domain(TimeStampedModel, DomainHelper): # TODO - Should we sync with EppLib in this event? # map_epp_contact_to_public_contact will grab any changes # made in the setter, - logger.info("Contact was not found in cache but was found in DB") + logger.info( + "Contact was not found in cache but was found in DB." + "Was this item added recently?" + ) return current_contact try: From fa8887d7b8b618218cd1c394a2a6b0c92562e52a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 22 Sep 2023 08:18:14 -0600 Subject: [PATCH 25/58] Bug fixes for test cases / Removed duplicate --- src/registrar/models/domain.py | 44 +++------------ src/registrar/models/public_contact.py | 3 +- src/registrar/tests/common.py | 4 ++ src/registrar/tests/test_models_domain.py | 66 +++-------------------- 4 files changed, 19 insertions(+), 98 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index c8a13ba51..8eb09c9ca 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -731,16 +731,13 @@ class Domain(TimeStampedModel, DomainHelper): db_contact = PublicContact.objects.filter(registry_id=contact_id, contact_type=contact_type, domain=self) # Saves to DB if(create_object and db_contact.count() == 0): - desired_contact.save() + desired_contact.save(skip_epp_save=True) logger.debug(f"Created a new PublicContact: {desired_contact}") return desired_contact if(db_contact.count() == 1): - #if(desired_contact != db_contact): - #current = desired_contact return db_contact.get() - # If it doesn't exist and we don't - # want to create it... + return desired_contact def _request_contact_info(self, contact: PublicContact): @@ -801,46 +798,19 @@ class Domain(TimeStampedModel, DomainHelper): if contact_type_choice == PublicContact.ContactTypeChoices.REGISTRANT: desired_property = "registrant" - # If it exists in our cache, grab that - if(self._cache and desired_property in self._cache): - return self.grab_contact_in_keys(self._cache[desired_property], contact_type_choice) - - # If not, check in our DB - items = PublicContact.objects.filter(domain=self, contact_type=contact_type_choice) - if(items.count() > 1): - raise ValueError(f"Multiple contacts exist for {contact_type_choice}") - - # Grab the first item in an array of size 1. - # We use this instead of .get() as we can expect - # values of 'None' occasionally (such as when an object - # only exists on the registry) - current_contact = items.first() - # If we have an item in our DB, - # and if contacts hasn't been cleared (meaning data was set)... - if(current_contact is not None): - # TODO - Should we sync with EppLib in this event? - # map_epp_contact_to_public_contact will grab any changes - # made in the setter, - logger.info( - "Contact was not found in cache but was found in DB." - "Was this item added recently?" - ) - return current_contact - try: - # Finally, if all else fails, grab from the registry contacts = self._get_property(desired_property) - + except KeyError as error: + # Q: Should we be raising an error instead? + logger.error(error) + return None + else: # Grab from cache after its been created cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) if cached_contact is None: raise ValueError("No contact was found in cache or the registry") return cached_contact - except RegistryError as error: - # Q: Should we be raising an error instead? - logger.error(error) - return None def get_default_security_contact(self): """Gets the default security contact.""" diff --git a/src/registrar/models/public_contact.py b/src/registrar/models/public_contact.py index d9ddecad4..6d6890cdb 100644 --- a/src/registrar/models/public_contact.py +++ b/src/registrar/models/public_contact.py @@ -29,7 +29,8 @@ class PublicContact(TimeStampedModel): def save(self, *args, **kwargs): """Save to the registry and also locally in the registrar database.""" - if hasattr(self, "domain"): + skip_epp_save = kwargs.pop('skip_epp_save', False) + if hasattr(self, "domain") and not skip_epp_save: match self.contact_type: case PublicContact.ContactTypeChoices.REGISTRANT: self.domain.registrant_contact = self diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 152080254..2fe51b713 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -621,6 +621,10 @@ class MockEppLib(TestCase): cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)], hosts=["fake.host.com"], + statuses=[ + common.Status(state="serverTransferProhibited", description="", lang="en"), + common.Status(state="inactive", description="", lang="en"), + ], ) InfoDomainWithContacts = fakedEppObject( "fakepw", diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index ced1b4d60..da6f6f6bf 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -22,14 +22,13 @@ from epplibwrapper import ( common, ) import logging - logger = logging.getLogger(__name__) class TestDomainCache(MockEppLib): def tearDown(self): - Domain.objects.all().delete() PublicContact.objects.all().delete() + Domain.objects.all().delete() super().tearDown() def test_cache_sets_resets(self): """Cache should be set on getter and reset on setter calls""" @@ -48,21 +47,16 @@ class TestDomainCache(MockEppLib): # using a setter should clear the cache domain.expiration_date = datetime.date.today() - self.assertEquals(domain._cache, {}) - expectedCreateContact = self._convertPublicContactToEpp(domain.security_contact, True, createContact=True) + # send should have been called only once self.mockedSendFunction.assert_has_calls( [ call(commands.InfoDomain(name='igorville.gov', auth_info=None), cleaned=True), call(commands.InfoContact(id='123', auth_info=None), cleaned=True), - call(expectedCreateContact, cleaned=True), - call(commands.UpdateDomain(name='igorville.gov', add=[common.DomainContact(contact='123', type=PublicContact.ContactTypeChoices.SECURITY)], rem=[], nsset=None, keyset=None, registrant=None, auth_info=None), cleaned=True), call(commands.InfoHost(name='fake.host.com'), cleaned=True) ], any_order=False, # Ensure calls are in the specified order ) - # Clear the cache - domain._invalidate_cache() def test_cache_used_when_avail(self): """Cache is pulled from if the object has already been accessed""" @@ -75,8 +69,7 @@ class TestDomainCache(MockEppLib): # value should still be set correctly self.assertEqual(cr_date, self.mockDataInfoDomain.cr_date) self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) - d = domain._cache["contacts"] - logger.debug(f"????? questions {d}") + # send was only called once & not on the second getter call expectedCalls = [ call( @@ -87,8 +80,6 @@ class TestDomainCache(MockEppLib): ] self.mockedSendFunction.assert_has_calls(expectedCalls) - # Clear the cache - domain._invalidate_cache() def test_cache_nested_elements(self): """Cache works correctly with the nested objects cache and hosts""" @@ -128,11 +119,8 @@ class TestDomainCache(MockEppLib): # get and check hosts is set correctly domain._get_property("hosts") self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) - # Clear the cache - domain._invalidate_cache() def test_map_epp_contact_to_public_contact(self): - self.maxDiff = None # Tests that the mapper is working how we expect domain, _ = Domain.objects.get_or_create(name="registry.gov") mapped = domain.map_epp_contact_to_public_contact( @@ -210,7 +198,6 @@ class TestDomainCreation(MockEppLib): domain = Domain.objects.get(name="igorville.gov") self.assertTrue(domain) self.mockedSendFunction.assert_not_called() - patcher.stop() def test_accessing_domain_properties_creates_domain_in_registry(self): """ @@ -271,6 +258,7 @@ class TestDomainCreation(MockEppLib): def tearDown(self) -> None: DomainInformation.objects.all().delete() DomainApplication.objects.all().delete() + PublicContact.objects.all().delete() Domain.objects.all().delete() User.objects.all().delete() DraftDomain.objects.all().delete() @@ -287,7 +275,7 @@ class TestDomainStatuses(MockEppLib): _ = domain.statuses status_list = [status.state for status in self.mockDataInfoDomain.statuses] self.assertEquals(domain._cache["statuses"], status_list) - + expectedCreateContact = self._convertPublicContactToEpp(domain.security_contact, True, createContact=True) # Called in _fetch_cache self.mockedSendFunction.assert_has_calls( [ @@ -333,6 +321,7 @@ class TestDomainStatuses(MockEppLib): raise def tearDown(self) -> None: + PublicContact.objects.all().delete() Domain.objects.all().delete() super().tearDown() @@ -579,7 +568,6 @@ class TestRegistrantContacts(MockEppLib): ], ) security_contact.email = "changedEmail@email.com" - security_contact.save() self.domain.security_contact = security_contact expectedSecondCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True @@ -644,48 +632,6 @@ class TestRegistrantContacts(MockEppLib): # Checks if we are recieving the cache we expect... self.assertEqual(self.domain_contact._cache["contacts"][0], expected_security_contact) - def test_setter_getter_security_email(self): - security = PublicContact.get_default_security() - security.email = "security@mail.gov" - security.domain = self.domain_contact - self.domain_contact.security_contact = security - - expected_security_contact = PublicContact.objects.filter( - registry_id=self.domain_contact.security_contact.registry_id, - contact_type = PublicContact.ContactTypeChoices.SECURITY - ).get() - - # Checks if we grab the correct PublicContact... - self.assertEqual(self.domain_contact.security_contact, expected_security_contact) - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoContact(id="securityContact", auth_info=None), - cleaned=True, - ), - ] - ) - # Call getter... - _ = self.domain_contact.security_contact - # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["contacts"][0], expected_security_contact) - - # Setter functions properly... - security.email = "123@mail.com" - security.save() - self.domain_contact.security_contact = security - expected_security_contact.email = "123@mail.com" - - self.assertEqual( - self.domain_contact.security_contact.email, expected_security_contact.email - ) - - @skip("not implemented yet") - def test_setter_getter_security_email_mock_user(self): - # TODO - grab the HTML content of the page, - # and verify that things have changed as expected - raise - def test_contact_getter_technical(self): expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.technical_contact.registry_id, From df1d61b965b7979a6c0d1449662b15aa4124cd6c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 22 Sep 2023 09:24:58 -0600 Subject: [PATCH 26/58] Running black / linter --- ops/scripts/manifest-sandbox-template.yaml | 1 + src/registrar/models/domain.py | 49 +++++++++------ src/registrar/models/public_contact.py | 2 +- .../templates/domain_security_email.html | 2 +- src/registrar/tests/common.py | 24 ++++++-- src/registrar/tests/test_models_domain.py | 60 ++++++++++--------- 6 files changed, 85 insertions(+), 53 deletions(-) diff --git a/ops/scripts/manifest-sandbox-template.yaml b/ops/scripts/manifest-sandbox-template.yaml index 1bf979c9f..a521aab09 100644 --- a/ops/scripts/manifest-sandbox-template.yaml +++ b/ops/scripts/manifest-sandbox-template.yaml @@ -11,6 +11,7 @@ applications: command: ./run.sh health-check-type: http health-check-http-endpoint: /health + health-check-invocation-timeout: 30 env: # Send stdout and stderr straight to the terminal without buffering PYTHONUNBUFFERED: yup diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 8eb09c9ca..7c044f86b 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -514,7 +514,9 @@ class Domain(TimeStampedModel, DomainHelper): .filter(domain=self, contact_type=contact.contact_type) .get() ) - logger.info(f"_set_singleton_contact() -> existing contact is... {existing_contact.__dict__}") + logger.info( + f"_set_singleton_contact() -> existing contact is... {existing_contact.__dict__}" + ) if isRegistrant: # send update domain only for registant contacts existing_contact.delete() @@ -664,7 +666,11 @@ class Domain(TimeStampedModel, DomainHelper): # I'm sure though that there is an easier alternative... # TLDR: This doesn't look as pretty, but it makes using this function easier def map_epp_contact_to_public_contact( - self, contact: eppInfo.InfoContactResultData, contact_id, contact_type, create_object=True + self, + contact: eppInfo.InfoContactResultData, + contact_id, + contact_type, + create_object=True, ): """Maps the Epp contact representation to a PublicContact object. @@ -685,7 +691,7 @@ class Domain(TimeStampedModel, DomainHelper): if contact_id is None: raise ValueError("contact_id is None") - + if len(contact_id) > 16 or len(contact_id) < 1: raise ValueError( "contact_id is of invalid length. " @@ -728,18 +734,21 @@ class Domain(TimeStampedModel, DomainHelper): sp=addr.sp, **streets, ) - db_contact = PublicContact.objects.filter(registry_id=contact_id, contact_type=contact_type, domain=self) + db_contact = PublicContact.objects.filter( + registry_id=contact_id, contact_type=contact_type, domain=self + ) # Saves to DB - if(create_object and db_contact.count() == 0): + if create_object and db_contact.count() == 0: + # Doesn't run custom save logic, just saves to DB desired_contact.save(skip_epp_save=True) logger.debug(f"Created a new PublicContact: {desired_contact}") return desired_contact - if(db_contact.count() == 1): + if db_contact.count() == 1: return db_contact.get() return desired_contact - + def _request_contact_info(self, contact: PublicContact): try: req = commands.InfoContact(id=contact.registry_id) @@ -751,7 +760,7 @@ class Domain(TimeStampedModel, DomainHelper): contact.contact_type, error.code, error, - ) # noqa + ) # noqa raise error def get_contact_default( @@ -844,13 +853,13 @@ class Domain(TimeStampedModel, DomainHelper): For example, check_type = 'security' """ # Registrant doesn't exist as an array - if(check_type == PublicContact.ContactTypeChoices.REGISTRANT): + if check_type == PublicContact.ContactTypeChoices.REGISTRANT: if ( isinstance(contacts, PublicContact) and contacts.contact_type is not None and contacts.contact_type == check_type ): - if(contacts.registry_id is None): + if contacts.registry_id is None: raise ValueError("registry_id cannot be None") return contacts else: @@ -863,7 +872,7 @@ class Domain(TimeStampedModel, DomainHelper): and contact.contact_type is not None and contact.contact_type == check_type ): - if(contact.registry_id is None): + if contact.registry_id is None: raise ValueError("registry_id cannot be None") return contact @@ -1162,7 +1171,9 @@ class Domain(TimeStampedModel, DomainHelper): if "registrant" in cleaned.keys(): # Registrant, if it exists, should always exist in EppLib. # If it doesn't, that is bad. We expect this to exist - cleaned["registrant"] = self._registrant_to_public_contact(cleaned["registrant"]) + cleaned["registrant"] = self._registrant_to_public_contact( + cleaned["registrant"] + ) if ( # fetch_contacts and @@ -1213,7 +1224,9 @@ class Domain(TimeStampedModel, DomainHelper): "tr_date": getattr(data, "tr_date", ...), "up_date": getattr(data, "up_date", ...), } - cleaned["hosts"].append({k: v for k, v in host.items() if v is not ...}) + cleaned["hosts"].append( + {k: v for k, v in host.items() if v is not ...} + ) # replace the prior cache with new data self._cache = cleaned @@ -1221,10 +1234,10 @@ class Domain(TimeStampedModel, DomainHelper): logger.error(e) def _registrant_to_public_contact(self, registry_id: str): - """ EPPLib returns the registrant as a string, + """EPPLib returns the registrant as a string, which is the registrants associated registry_id. This function is used to - convert that id to a useable object by calling commands.InfoContact - on that ID, then mapping that object to type PublicContact. """ + convert that id to a useable object by calling commands.InfoContact + on that ID, then mapping that object to type PublicContact.""" contact = PublicContact( registry_id=registry_id, contact_type=PublicContact.ContactTypeChoices.REGISTRANT, @@ -1243,7 +1256,9 @@ class Domain(TimeStampedModel, DomainHelper): def _get_property(self, property): """Get some piece of info about a domain.""" - logger.info(f"_get_property() -> prop is... {property} prop in cache... {property not in self._cache} cache is {self._cache}") + logger.info( + f"_get_property() -> prop is... {property} prop in cache... {property not in self._cache} cache is {self._cache}" + ) if property not in self._cache: self._fetch_cache( fetch_hosts=(property == "hosts"), diff --git a/src/registrar/models/public_contact.py b/src/registrar/models/public_contact.py index 6d6890cdb..b99bd1098 100644 --- a/src/registrar/models/public_contact.py +++ b/src/registrar/models/public_contact.py @@ -29,7 +29,7 @@ class PublicContact(TimeStampedModel): def save(self, *args, **kwargs): """Save to the registry and also locally in the registrar database.""" - skip_epp_save = kwargs.pop('skip_epp_save', False) + skip_epp_save = kwargs.pop("skip_epp_save", False) if hasattr(self, "domain") and not skip_epp_save: match self.contact_type: case PublicContact.ContactTypeChoices.REGISTRANT: diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index dbd257d86..deb54764e 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -21,7 +21,7 @@ + >{% if domain.security_contact is None or domain.security_contact.email == 'dotgov@cisa.dhs.gov'%}Add security email{% else %}Save{% endif %} {% endblock %} {# domain_content #} diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 2fe51b713..a8d919c9b 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -549,14 +549,13 @@ class MockEppLib(TestCase): """""" def __init__( - self, auth_info=..., cr_date=..., contacts=..., hosts=..., statuses=..., - registrant=... + registrant=..., ): self.auth_info = auth_info self.cr_date = cr_date @@ -619,7 +618,11 @@ class MockEppLib(TestCase): mockDataInfoDomain = fakedEppObject( "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), - contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)], + contacts=[ + common.DomainContact( + contact="123", type=PublicContact.ContactTypeChoices.SECURITY + ) + ], hosts=["fake.host.com"], statuses=[ common.Status(state="serverTransferProhibited", description="", lang="en"), @@ -630,9 +633,18 @@ class MockEppLib(TestCase): "fakepw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), contacts=[ - common.DomainContact(contact="securityContact", type=PublicContact.ContactTypeChoices.SECURITY), - common.DomainContact(contact="technicalContact", type=PublicContact.ContactTypeChoices.TECHNICAL), - common.DomainContact(contact="adminContact", type=PublicContact.ContactTypeChoices.ADMINISTRATIVE), + common.DomainContact( + contact="securityContact", + type=PublicContact.ContactTypeChoices.SECURITY, + ), + common.DomainContact( + contact="technicalContact", + type=PublicContact.ContactTypeChoices.TECHNICAL, + ), + common.DomainContact( + contact="adminContact", + type=PublicContact.ContactTypeChoices.ADMINISTRATIVE, + ), ], hosts=["fake.host.com"], statuses=[ diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index da6f6f6bf..272b5936e 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -22,6 +22,7 @@ from epplibwrapper import ( common, ) import logging + logger = logging.getLogger(__name__) @@ -30,10 +31,10 @@ class TestDomainCache(MockEppLib): PublicContact.objects.all().delete() Domain.objects.all().delete() super().tearDown() + def test_cache_sets_resets(self): """Cache should be set on getter and reset on setter calls""" domain, _ = Domain.objects.get_or_create(name="igorville.gov") - self.maxDiff = None # trigger getter _ = domain.creation_date domain._get_property("contacts") @@ -51,9 +52,12 @@ class TestDomainCache(MockEppLib): # send should have been called only once self.mockedSendFunction.assert_has_calls( [ - call(commands.InfoDomain(name='igorville.gov', auth_info=None), cleaned=True), - call(commands.InfoContact(id='123', auth_info=None), cleaned=True), - call(commands.InfoHost(name='fake.host.com'), cleaned=True) + call( + commands.InfoDomain(name="igorville.gov", auth_info=None), + cleaned=True, + ), + call(commands.InfoContact(id="123", auth_info=None), cleaned=True), + call(commands.InfoHost(name="fake.host.com"), cleaned=True), ], any_order=False, # Ensure calls are in the specified order ) @@ -69,7 +73,7 @@ class TestDomainCache(MockEppLib): # value should still be set correctly self.assertEqual(cr_date, self.mockDataInfoDomain.cr_date) self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) - + # send was only called once & not on the second getter call expectedCalls = [ call( @@ -90,9 +94,7 @@ class TestDomainCache(MockEppLib): expectedUnfurledContactsList = [ common.DomainContact(contact="123", type="security"), ] - expectedContactsList = [ - domain.security_contact - ] + expectedContactsList = [domain.security_contact] expectedHostsDict = { "name": self.mockDataInfoDomain.hosts[0], "cr_date": self.mockDataInfoDomain.cr_date, @@ -111,10 +113,7 @@ class TestDomainCache(MockEppLib): # as _fetch_cache will transform the type to PublicContact self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) - self.assertEqual( - domain._cache["contacts"], - expectedContactsList - ) + self.assertEqual(domain._cache["contacts"], expectedContactsList) # get and check hosts is set correctly domain._get_property("hosts") @@ -126,10 +125,11 @@ class TestDomainCache(MockEppLib): mapped = domain.map_epp_contact_to_public_contact( self.mockDataInfoContact, self.mockDataInfoContact.id, - PublicContact.ContactTypeChoices.SECURITY + PublicContact.ContactTypeChoices.SECURITY, ) + expected_contact = PublicContact( - id=1, + id=4, domain=domain, contact_type=PublicContact.ContactTypeChoices.SECURITY, registry_id="123", @@ -143,7 +143,7 @@ class TestDomainCache(MockEppLib): pc="22201", cc="US", sp="VA", - street1="4200 Wilson Blvd." + street1="4200 Wilson Blvd.", ) # Match when these both were updated/created expected_contact.updated_at = mapped.updated_at @@ -153,7 +153,7 @@ class TestDomainCache(MockEppLib): in_db = PublicContact.objects.filter( registry_id=domain.security_contact.registry_id, - contact_type = PublicContact.ContactTypeChoices.SECURITY + contact_type=PublicContact.ContactTypeChoices.SECURITY, ).get() # DB Object is the same as the mapped object self.assertEqual(mapped, in_db) @@ -161,12 +161,12 @@ class TestDomainCache(MockEppLib): mapped_second = domain.map_epp_contact_to_public_contact( self.mockDataInfoContact, self.mockDataInfoContact.id, - PublicContact.ContactTypeChoices.SECURITY + PublicContact.ContactTypeChoices.SECURITY, ) in_db_once = PublicContact.objects.filter( registry_id=domain.security_contact.registry_id, - contact_type = PublicContact.ContactTypeChoices.SECURITY + contact_type=PublicContact.ContactTypeChoices.SECURITY, ) self.assertEqual(mapped_second, in_db) # If mapper is called a second time, @@ -275,7 +275,9 @@ class TestDomainStatuses(MockEppLib): _ = domain.statuses status_list = [status.state for status in self.mockDataInfoDomain.statuses] self.assertEquals(domain._cache["statuses"], status_list) - expectedCreateContact = self._convertPublicContactToEpp(domain.security_contact, True, createContact=True) + expectedCreateContact = self._convertPublicContactToEpp( + domain.security_contact, True, createContact=True + ) # Called in _fetch_cache self.mockedSendFunction.assert_has_calls( [ @@ -328,7 +330,7 @@ class TestDomainStatuses(MockEppLib): class TestRegistrantContacts(MockEppLib): """Rule: Registrants may modify their WHOIS data""" - + def setUp(self): """ Background: @@ -552,7 +554,6 @@ class TestRegistrantContacts(MockEppLib): security_contact = self.domain.get_default_security_contact() security_contact.email = "originalUserEmail@gmail.com" security_contact.registry_id = "fail" - security_contact.save() self.domain.security_contact = security_contact expectedCreateCommand = self._convertPublicContactToEpp( @@ -575,7 +576,6 @@ class TestRegistrantContacts(MockEppLib): updateContact = self._convertPublicContactToEpp( security_contact, disclose_email=True, createContact=False ) - expected_calls = [ call(expectedCreateCommand, cleaned=True), call(expectedUpdateDomain, cleaned=True), @@ -616,11 +616,13 @@ class TestRegistrantContacts(MockEppLib): # Create prexisting object... expected_security_contact = PublicContact.objects.filter( registry_id=self.domain_contact.security_contact.registry_id, - contact_type = PublicContact.ContactTypeChoices.SECURITY + contact_type=PublicContact.ContactTypeChoices.SECURITY, ).get() # Checks if we grab the correct PublicContact... - self.assertEqual(self.domain_contact.security_contact, expected_security_contact) + self.assertEqual( + self.domain_contact.security_contact, expected_security_contact + ) self.mockedSendFunction.assert_has_calls( [ call( @@ -630,12 +632,14 @@ class TestRegistrantContacts(MockEppLib): ] ) # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["contacts"][0], expected_security_contact) + self.assertEqual( + self.domain_contact._cache["contacts"][0], expected_security_contact + ) def test_contact_getter_technical(self): expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.technical_contact.registry_id, - contact_type = PublicContact.ContactTypeChoices.TECHNICAL + contact_type=PublicContact.ContactTypeChoices.TECHNICAL, ).get() # Checks if we grab the correct PublicContact... @@ -654,7 +658,7 @@ class TestRegistrantContacts(MockEppLib): def test_contact_getter_administrative(self): expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.administrative_contact.registry_id, - contact_type = PublicContact.ContactTypeChoices.ADMINISTRATIVE + contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE, ).get() # Checks if we grab the correct PublicContact... @@ -673,7 +677,7 @@ class TestRegistrantContacts(MockEppLib): def test_contact_getter_registrant(self): expected_contact = PublicContact.objects.filter( registry_id=self.domain_contact.registrant_contact.registry_id, - contact_type = PublicContact.ContactTypeChoices.REGISTRANT + contact_type=PublicContact.ContactTypeChoices.REGISTRANT, ).get() # Checks if we grab the correct PublicContact... From 84f2b5d5b23bf825271e2bcaf059bd5cb7b978ce Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:09:32 -0600 Subject: [PATCH 27/58] Don't display default email --- src/registrar/models/domain.py | 22 +++++++++++----------- src/registrar/tests/test_models_domain.py | 3 --- src/registrar/views/domain.py | 3 +++ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 7c044f86b..54679c209 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -514,9 +514,7 @@ class Domain(TimeStampedModel, DomainHelper): .filter(domain=self, contact_type=contact.contact_type) .get() ) - logger.info( - f"_set_singleton_contact() -> existing contact is... {existing_contact.__dict__}" - ) + if isRegistrant: # send update domain only for registant contacts existing_contact.delete() @@ -695,7 +693,8 @@ class Domain(TimeStampedModel, DomainHelper): if len(contact_id) > 16 or len(contact_id) < 1: raise ValueError( "contact_id is of invalid length. " - f"Cannot exceed 16 characters, got {contact_id} with a length of {len(contact_id)}" + "Cannot exceed 16 characters, " + f"got {contact_id} with a length of {len(contact_id)}" ) logger.debug(f"map_epp_contact_to_public_contact contact -> {contact}") @@ -755,12 +754,12 @@ class Domain(TimeStampedModel, DomainHelper): return registry.send(req, cleaned=True).res_data[0] except RegistryError as error: logger.error( - "Registry threw error for contact id %s contact type is %s, error code is\n %s full error is %s", + "Registry threw error for contact id %s contact type is %s, error code is\n %s full error is %s", # noqa contact.registry_id, contact.contact_type, error.code, error, - ) # noqa + ) raise error def get_contact_default( @@ -814,7 +813,10 @@ class Domain(TimeStampedModel, DomainHelper): logger.error(error) return None else: - # Grab from cache after its been created + # Grab from cache + if(self._cache and desired_property in self._cache): + return self.grab_contact_in_keys(self._cache[desired_property], contact_type_choice) + cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) if cached_contact is None: raise ValueError("No contact was found in cache or the registry") @@ -1188,7 +1190,8 @@ class Domain(TimeStampedModel, DomainHelper): # if not, that's a problem # TODO- discuss-should we check if contact is in public contacts - # and add it if not- this is really to keep in mind for the transition + # and add it if not- + # this is really to keep in mind for the transition req = commands.InfoContact(id=domainContact.contact) data = registry.send(req, cleaned=True).res_data[0] @@ -1256,9 +1259,6 @@ class Domain(TimeStampedModel, DomainHelper): def _get_property(self, property): """Get some piece of info about a domain.""" - logger.info( - f"_get_property() -> prop is... {property} prop in cache... {property not in self._cache} cache is {self._cache}" - ) if property not in self._cache: self._fetch_cache( fetch_hosts=(property == "hosts"), diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 272b5936e..f066f75c0 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -275,9 +275,6 @@ class TestDomainStatuses(MockEppLib): _ = domain.statuses status_list = [status.state for status in self.mockDataInfoDomain.statuses] self.assertEquals(domain._cache["statuses"], status_list) - expectedCreateContact = self._convertPublicContactToEpp( - domain.security_contact, True, createContact=True - ) # Called in _fetch_cache self.mockedSendFunction.assert_has_calls( [ diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 3da4de3fa..6b3706b92 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -250,6 +250,9 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): """The initial value for the form.""" domain = self.get_object() initial = super().get_initial() + if(domain.security_contact.email == "dotgov@cisa.dhs.gov"): + initial["security_email"] = None + return initial initial["security_email"] = domain.security_contact.email return initial From 2536992fbb157905210cbf12d9b2c3e2d8319875 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 25 Sep 2023 10:58:55 -0600 Subject: [PATCH 28/58] Additional test cases / Code cleanup --- src/registrar/models/domain.py | 128 +++++++++++--------- src/registrar/tests/common.py | 108 +++++++++-------- src/registrar/tests/test_models_domain.py | 141 +++++++++++++++------- src/registrar/tests/test_views.py | 15 ++- src/registrar/views/domain.py | 5 +- 5 files changed, 242 insertions(+), 155 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 54679c209..d0f19d5a7 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -717,34 +717,23 @@ class Domain(TimeStampedModel, DomainHelper): fillvalue=None, ) ) + desired_contact = PublicContact( domain=self, contact_type=contact_type, registry_id=contact_id, - email=contact.email, - voice=contact.voice, + email=contact.email or "", + voice=contact.voice or "", fax=contact.fax, - pw=auth_info.pw, - name=postal_info.name, + pw=auth_info.pw or "", + name=postal_info.name or "", org=postal_info.org, - city=addr.city, - pc=addr.pc, - cc=addr.cc, - sp=addr.sp, + city=addr.city or "", + pc=addr.pc or "", + cc=addr.cc or "", + sp=addr.sp or "", **streets, ) - db_contact = PublicContact.objects.filter( - registry_id=contact_id, contact_type=contact_type, domain=self - ) - # Saves to DB - if create_object and db_contact.count() == 0: - # Doesn't run custom save logic, just saves to DB - desired_contact.save(skip_epp_save=True) - logger.debug(f"Created a new PublicContact: {desired_contact}") - return desired_contact - - if db_contact.count() == 1: - return db_contact.get() return desired_contact @@ -754,40 +743,14 @@ class Domain(TimeStampedModel, DomainHelper): return registry.send(req, cleaned=True).res_data[0] except RegistryError as error: logger.error( - "Registry threw error for contact id %s contact type is %s, error code is\n %s full error is %s", # noqa + "Registry threw error for contact id %s contact type is %s, error code is\n %s full error is %s", # noqa contact.registry_id, contact.contact_type, error.code, error, - ) + ) raise error - def get_contact_default( - self, contact_type_choice: PublicContact.ContactTypeChoices - ) -> PublicContact: - """Returns a default contact based off the contact_type_choice. - Used - - contact_type_choice is a literal in PublicContact.ContactTypeChoices, - for instance: PublicContact.ContactTypeChoices.SECURITY. - - If you wanted to get the default contact for Security, you would call: - get_contact_default(PublicContact.ContactTypeChoices.SECURITY), - or get_contact_default("security") - """ - choices = PublicContact.ContactTypeChoices - contact: PublicContact - match (contact_type_choice): - case choices.ADMINISTRATIVE: - contact = self.get_default_administrative_contact() - case choices.SECURITY: - contact = self.get_default_security_contact() - case choices.TECHNICAL: - contact = self.get_default_technical_contact() - case choices.REGISTRANT: - contact = self.get_default_registrant_contact() - return contact - def generic_contact_getter( self, contact_type_choice: PublicContact.ContactTypeChoices ) -> PublicContact: @@ -798,7 +761,10 @@ class Domain(TimeStampedModel, DomainHelper): If you wanted to setup getter logic for Security, you would call: cache_contact_helper(PublicContact.ContactTypeChoices.SECURITY), - or cache_contact_helper("security") + or cache_contact_helper("security"). + + Note: Registrant is handled slightly differently internally, + but the output will be the same. """ # registrant_contact(s) are an edge case. They exist on # the "registrant" property as opposed to contacts. @@ -810,13 +776,10 @@ class Domain(TimeStampedModel, DomainHelper): contacts = self._get_property(desired_property) except KeyError as error: # Q: Should we be raising an error instead? - logger.error(error) + logger.error(f"Could not find {contact_type_choice}: {error}") return None else: # Grab from cache - if(self._cache and desired_property in self._cache): - return self.grab_contact_in_keys(self._cache[desired_property], contact_type_choice) - cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) if cached_contact is None: raise ValueError("No contact was found in cache or the registry") @@ -880,6 +843,10 @@ class Domain(TimeStampedModel, DomainHelper): # If the for loop didn't do a return, # then we know that it doesn't exist within cache + logger.info( + f"Requested contact {contact.registry_id} " "Does not exist in cache." + ) + return None # ForeignKey on UserDomainRole creates a "permissions" member for # all of the user-roles that are in place for this domain @@ -1195,10 +1162,14 @@ class Domain(TimeStampedModel, DomainHelper): req = commands.InfoContact(id=domainContact.contact) data = registry.send(req, cleaned=True).res_data[0] + # Map the object we recieved from EPP to a PublicContact + mapped_object = self.map_epp_contact_to_public_contact( + data, domainContact.contact, domainContact.type + ) + + # Find/create it in the DB, then add it to the list cleaned["contacts"].append( - self.map_epp_contact_to_public_contact( - data, domainContact.contact, domainContact.type - ) + self._get_or_create_public_contact(mapped_object) ) # get nameserver info, if there are any @@ -1236,6 +1207,48 @@ class Domain(TimeStampedModel, DomainHelper): except RegistryError as e: logger.error(e) + def _get_or_create_public_contact(self, public_contact: PublicContact): + """Tries to find a PublicContact object in our DB. + If it can't, it'll create it.""" + db_contact = PublicContact.objects.filter( + registry_id=public_contact.registry_id, + contact_type=public_contact.contact_type, + domain=self, + ) + + # Raise an error if we find duplicates. + # This should not occur... + if db_contact.count() > 1: + raise Exception( + f"Multiple contacts found for {public_contact.contact_type}" + ) + + if db_contact.count() == 1: + existing_contact = db_contact.get() + # Does the item we're grabbing match + # what we have in our DB? + # If not, we likely have a duplicate. + if ( + existing_contact.email != public_contact.email + or existing_contact.registry_id != public_contact.registry_id + ): + raise ValueError( + "Requested PublicContact is out of sync " + "with DB. Potential duplicate?" + ) + + # If it already exists, we can + # assume that the DB instance was updated + # during set, so we should just use that. + return existing_contact + + # Saves to DB if it doesn't exist already. + # Doesn't run custom save logic, just saves to DB + public_contact.save(skip_epp_save=True) + logger.debug(f"Created a new PublicContact: {public_contact}") + # Append the item we just created + return public_contact + def _registrant_to_public_contact(self, registry_id: str): """EPPLib returns the registrant as a string, which is the registrants associated registry_id. This function is used to @@ -1248,9 +1261,10 @@ class Domain(TimeStampedModel, DomainHelper): # Grabs the expanded contact full_object = self._request_contact_info(contact) # Maps it to type PublicContact - return self.map_epp_contact_to_public_contact( + mapped_object = self.map_epp_contact_to_public_contact( full_object, contact.registry_id, contact.contact_type ) + return self._get_or_create_public_contact(mapped_object) def _invalidate_cache(self): """Remove cache data when updates are made.""" diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index a8d919c9b..3abce8355 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -564,57 +564,46 @@ class MockEppLib(TestCase): self.statuses = statuses self.registrant = registrant - def dummyInfoContactResultData( - id, - email, - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), - pw="thisisnotapassword", - ): - fake = info.InfoContactResultData( - id=id, - postal_info=common.PostalInfo( - name="Registry Customer Service", - addr=common.ContactAddr( - street=["4200 Wilson Blvd."], - city="Arlington", - pc="22201", - cc="US", - sp="VA", + def dummyInfoContactResultData( + self, + id, + email, + cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + pw="thisisnotapassword", + ): + fake = info.InfoContactResultData( + id=id, + postal_info=common.PostalInfo( + name="Registry Customer Service", + addr=common.ContactAddr( + street=["4200 Wilson Blvd."], + city="Arlington", + pc="22201", + cc="US", + sp="VA", + ), + org="Cybersecurity and Infrastructure Security Agency", + type="type", ), - org="Cybersecurity and Infrastructure Security Agency", - type="type", - ), - voice="+1.8882820870", - fax="+1-212-9876543", - email=email, - auth_info=common.ContactAuthInfo(pw=pw), - roid=..., - statuses=[], - cl_id=..., - cr_id=..., - cr_date=cr_date, - up_id=..., - up_date=..., - tr_date=..., - disclose=..., - vat=..., - ident=..., - notify_email=..., - ) - return fake + voice="+1.8882820870", + fax="+1-212-9876543", + email=email, + auth_info=common.ContactAuthInfo(pw=pw), + roid=..., + statuses=[], + cl_id=..., + cr_id=..., + cr_date=cr_date, + up_id=..., + up_date=..., + tr_date=..., + disclose=..., + vat=..., + ident=..., + notify_email=..., + ) + return fake - mockSecurityContact = dummyInfoContactResultData( - "securityContact", "security@mail.gov" - ) - mockTechnicalContact = dummyInfoContactResultData( - "technicalContact", "tech@mail.gov" - ) - mockAdministrativeContact = dummyInfoContactResultData( - "adminContact", "admin@mail.gov" - ) - mockRegistrantContact = dummyInfoContactResultData( - "regContact", "registrant@mail.gov" - ) mockDataInfoDomain = fakedEppObject( "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), @@ -629,6 +618,9 @@ class MockEppLib(TestCase): common.Status(state="inactive", description="", lang="en"), ], ) + mockDataInfoContact = mockDataInfoDomain.dummyInfoContactResultData( + "123", "123@mail.gov", datetime.datetime(2023, 5, 25, 19, 45, 35), "lastPw" + ) InfoDomainWithContacts = fakedEppObject( "fakepw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), @@ -653,15 +645,27 @@ class MockEppLib(TestCase): ], registrant="regContact", ) + + mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData( + "securityContact", "security@mail.gov" + ) + mockTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData( + "technicalContact", "tech@mail.gov" + ) + mockAdministrativeContact = InfoDomainWithContacts.dummyInfoContactResultData( + "adminContact", "admin@mail.gov" + ) + mockRegistrantContact = InfoDomainWithContacts.dummyInfoContactResultData( + "regContact", "registrant@mail.gov" + ) + infoDomainNoContact = fakedEppObject( "security", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), contacts=[], hosts=["fake.host.com"], ) - mockDataInfoContact = dummyInfoContactResultData( - "123", "123@mail.gov", datetime.datetime(2023, 5, 25, 19, 45, 35), "lastPw" - ) + mockDataInfoHosts = fakedEppObject( "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35) ) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index f066f75c0..fe95b7a69 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -129,7 +129,6 @@ class TestDomainCache(MockEppLib): ) expected_contact = PublicContact( - id=4, domain=domain, contact_type=PublicContact.ContactTypeChoices.SECURITY, registry_id="123", @@ -145,34 +144,24 @@ class TestDomainCache(MockEppLib): sp="VA", street1="4200 Wilson Blvd.", ) - # Match when these both were updated/created - expected_contact.updated_at = mapped.updated_at - expected_contact.created_at = mapped.created_at - # Mapped object is what we expect - self.assertEqual(mapped, expected_contact) + # Test purposes only, since we're comparing + # two duplicate objects. We would expect + # these not to have the same state. + expected_contact._state = mapped._state + + # Mapped object is what we expect + self.assertEqual(mapped.__dict__, expected_contact.__dict__) + + # The mapped object should correctly translate to a DB + # object. If not, something else went wrong. + db_object = domain._get_or_create_public_contact(mapped) in_db = PublicContact.objects.filter( registry_id=domain.security_contact.registry_id, contact_type=PublicContact.ContactTypeChoices.SECURITY, ).get() # DB Object is the same as the mapped object - self.assertEqual(mapped, in_db) - - mapped_second = domain.map_epp_contact_to_public_contact( - self.mockDataInfoContact, - self.mockDataInfoContact.id, - PublicContact.ContactTypeChoices.SECURITY, - ) - - in_db_once = PublicContact.objects.filter( - registry_id=domain.security_contact.registry_id, - contact_type=PublicContact.ContactTypeChoices.SECURITY, - ) - self.assertEqual(mapped_second, in_db) - # If mapper is called a second time, - # it just grabs existing data rather than - # a new object - self.assertTrue(in_db_once.count() == 1) + self.assertEqual(db_object, in_db) class TestDomainCreation(MockEppLib): @@ -344,6 +333,8 @@ class TestRegistrantContacts(MockEppLib): super().tearDown() self.domain._invalidate_cache() self.domain_contact._invalidate_cache() + PublicContact.objects.all().delete() + Domain.objects.all().delete() # self.contactMailingAddressPatch.stop() # self.createContactPatch.stop() @@ -548,17 +539,35 @@ class TestRegistrantContacts(MockEppLib): security contact email Then Domain sends `commands.UpdateContact` to the registry """ + # Trigger the getter for default values + _ = self.domain.security_contact security_contact = self.domain.get_default_security_contact() security_contact.email = "originalUserEmail@gmail.com" security_contact.registry_id = "fail" self.domain.security_contact = security_contact + # All contacts should be in the DB + self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) + # Check if security_contact is what we expect... + self.assertEqual(security_contact.email, "originalUserEmail@gmail.com") + # If the item in PublicContact is as expected... + current_item = PublicContact.objects.filter( + domain=self.domain, contact_type=PublicContact.ContactTypeChoices.SECURITY + ).get() + self.assertEqual(current_item.email, "originalUserEmail@gmail.com") + self.assertEqual(security_contact, current_item) + + # This contact should be associated with a domain + self.assertEqual(self.domain.security_contact, security_contact) + self.assertEqual( + self.domain.security_contact.email, "originalUserEmail@gmail.com" + ) expectedCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) expectedUpdateDomain = commands.UpdateDomain( - name=self.domain.name, + name=self.domain_contact.name, add=[ common.DomainContact( contact=security_contact.registry_id, type="security" @@ -567,6 +576,7 @@ class TestRegistrantContacts(MockEppLib): ) security_contact.email = "changedEmail@email.com" self.domain.security_contact = security_contact + expectedSecondCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) @@ -582,11 +592,15 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) # Check if security_contact is what we expect... - self.assertEqual(self.domain.security_contact.email, "changedEmail@email.com") - self.assertEqual(self.domain.security_contact, security_contact) + self.assertEqual(security_contact.email, "changedEmail@email.com") # If the item in PublicContact is as expected... current_item = PublicContact.objects.filter(domain=self.domain).get() self.assertEqual(current_item.email, "changedEmail@email.com") + self.assertEqual(security_contact, current_item) + + # Check the item associated with the domain... + self.assertEqual(self.domain.security_contact, security_contact) + self.assertEqual(self.domain.security_contact.email, "changedEmail@email.com") @skip("not implemented yet") def test_update_is_unsuccessful(self): @@ -610,16 +624,26 @@ class TestRegistrantContacts(MockEppLib): pass def test_contact_getter_security(self): + self.maxDiff = None # Create prexisting object... - expected_security_contact = PublicContact.objects.filter( + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockSecurityContact, + contact_id="securityContact", + contact_type=PublicContact.ContactTypeChoices.SECURITY, + ) + + # Checks if we grabbed the correct PublicContact... + self.assertEqual( + self.domain_contact.security_contact.email, expected_contact.email + ) + + expected_contact_db = PublicContact.objects.filter( registry_id=self.domain_contact.security_contact.registry_id, contact_type=PublicContact.ContactTypeChoices.SECURITY, ).get() - # Checks if we grab the correct PublicContact... - self.assertEqual( - self.domain_contact.security_contact, expected_security_contact - ) + self.assertEqual(self.domain_contact.security_contact, expected_contact_db) + self.mockedSendFunction.assert_has_calls( [ call( @@ -629,18 +653,27 @@ class TestRegistrantContacts(MockEppLib): ] ) # Checks if we are recieving the cache we expect... - self.assertEqual( - self.domain_contact._cache["contacts"][0], expected_security_contact - ) + self.assertEqual(self.domain_contact._cache["contacts"][0], expected_contact_db) def test_contact_getter_technical(self): - expected_contact = PublicContact.objects.filter( + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockTechnicalContact, + contact_id="technicalContact", + contact_type=PublicContact.ContactTypeChoices.TECHNICAL, + ) + + self.assertEqual( + self.domain_contact.technical_contact.email, expected_contact.email + ) + + # Checks if we grab the correct PublicContact... + expected_contact_db = PublicContact.objects.filter( registry_id=self.domain_contact.technical_contact.registry_id, contact_type=PublicContact.ContactTypeChoices.TECHNICAL, ).get() # Checks if we grab the correct PublicContact... - self.assertEqual(self.domain_contact.technical_contact, expected_contact) + self.assertEqual(self.domain_contact.technical_contact, expected_contact_db) self.mockedSendFunction.assert_has_calls( [ call( @@ -650,16 +683,28 @@ class TestRegistrantContacts(MockEppLib): ] ) # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["contacts"][1], expected_contact) + self.assertEqual(self.domain_contact._cache["contacts"][1], expected_contact_db) def test_contact_getter_administrative(self): - expected_contact = PublicContact.objects.filter( + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockAdministrativeContact, + contact_id="adminContact", + contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE, + ) + + self.assertEqual( + self.domain_contact.administrative_contact.email, expected_contact.email + ) + + expected_contact_db = PublicContact.objects.filter( registry_id=self.domain_contact.administrative_contact.registry_id, contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE, ).get() # Checks if we grab the correct PublicContact... - self.assertEqual(self.domain_contact.administrative_contact, expected_contact) + self.assertEqual( + self.domain_contact.administrative_contact, expected_contact_db + ) self.mockedSendFunction.assert_has_calls( [ call( @@ -669,16 +714,26 @@ class TestRegistrantContacts(MockEppLib): ] ) # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["contacts"][2], expected_contact) + self.assertEqual(self.domain_contact._cache["contacts"][2], expected_contact_db) def test_contact_getter_registrant(self): - expected_contact = PublicContact.objects.filter( + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockRegistrantContact, + contact_id="regContact", + contact_type=PublicContact.ContactTypeChoices.REGISTRANT, + ) + + self.assertEqual( + self.domain_contact.registrant_contact.email, expected_contact.email + ) + + expected_contact_db = PublicContact.objects.filter( registry_id=self.domain_contact.registrant_contact.registry_id, contact_type=PublicContact.ContactTypeChoices.REGISTRANT, ).get() # Checks if we grab the correct PublicContact... - self.assertEqual(self.domain_contact.registrant_contact, expected_contact) + self.assertEqual(self.domain_contact.registrant_contact, expected_contact_db) self.mockedSendFunction.assert_has_calls( [ call( @@ -688,7 +743,7 @@ class TestRegistrantContacts(MockEppLib): ] ) # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["registrant"], expected_contact) + self.assertEqual(self.domain_contact._cache["registrant"], expected_contact_db) class TestRegistrantNameservers(TestCase): diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 318cc261d..b8a922983 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -5,7 +5,7 @@ from django.conf import settings from django.test import Client, TestCase from django.urls import reverse from django.contrib.auth import get_user_model -from .common import completed_application +from .common import MockEppLib, completed_application from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore @@ -1128,7 +1128,7 @@ class TestDomainPermissions(TestWithDomainPermissions): self.assertEqual(response.status_code, 403) -class TestDomainDetail(TestWithDomainPermissions, WebTest): +class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): def setUp(self): super().setUp() self.app.set_user(self.user.username) @@ -1406,6 +1406,17 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest): ) self.assertContains(page, "Testy") + def test_domain_security_email_no_security_contact(self): + """Loads a domain with no defined security email. + We should not show the default.""" + page = self.client.get( + reverse("domain-security-email", kwargs={"pk": self.domain.id}) + ) + + # Loads correctly + self.assertContains(page, "Domain security email") + self.assertNotContains(page, "dotgov@cisa.dhs.gov") + def test_domain_security_email(self): """Can load domain's security email page.""" page = self.client.get( diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 6b3706b92..25f53e8be 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -250,7 +250,10 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): """The initial value for the form.""" domain = self.get_object() initial = super().get_initial() - if(domain.security_contact.email == "dotgov@cisa.dhs.gov"): + if ( + domain.security_contact is None or + domain.security_contact.email == "dotgov@cisa.dhs.gov" + ): initial["security_email"] = None return initial initial["security_email"] = domain.security_contact.email From 82603f57ff99c5969468a1212b42f6d16ea76b12 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:52:08 -0600 Subject: [PATCH 29/58] Fix test cases --- src/registrar/tests/test_models_domain.py | 44 +++-------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index fe95b7a69..9a0f52e6a 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -532,42 +532,16 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) def test_updates_security_email(self): - """ - Scenario: Registrant replaces one valid security contact email with another - Given a domain exists in the registry with a user-added security email - When `domain.security_contact` is set equal to a PublicContact with a new - security contact email - Then Domain sends `commands.UpdateContact` to the registry - """ - # Trigger the getter for default values - _ = self.domain.security_contact security_contact = self.domain.get_default_security_contact() security_contact.email = "originalUserEmail@gmail.com" security_contact.registry_id = "fail" - self.domain.security_contact = security_contact - # All contacts should be in the DB - self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) - # Check if security_contact is what we expect... - self.assertEqual(security_contact.email, "originalUserEmail@gmail.com") - # If the item in PublicContact is as expected... - current_item = PublicContact.objects.filter( - domain=self.domain, contact_type=PublicContact.ContactTypeChoices.SECURITY - ).get() - self.assertEqual(current_item.email, "originalUserEmail@gmail.com") - self.assertEqual(security_contact, current_item) - - # This contact should be associated with a domain - self.assertEqual(self.domain.security_contact, security_contact) - self.assertEqual( - self.domain.security_contact.email, "originalUserEmail@gmail.com" - ) - + security_contact.save() expectedCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) expectedUpdateDomain = commands.UpdateDomain( - name=self.domain_contact.name, + name=self.domain.name, add=[ common.DomainContact( contact=security_contact.registry_id, type="security" @@ -575,14 +549,14 @@ class TestRegistrantContacts(MockEppLib): ], ) security_contact.email = "changedEmail@email.com" - self.domain.security_contact = security_contact - + security_contact.save() expectedSecondCreateCommand = self._convertPublicContactToEpp( security_contact, disclose_email=True ) updateContact = self._convertPublicContactToEpp( security_contact, disclose_email=True, createContact=False ) + expected_calls = [ call(expectedCreateCommand, cleaned=True), call(expectedUpdateDomain, cleaned=True), @@ -591,16 +565,6 @@ class TestRegistrantContacts(MockEppLib): ] self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) - # Check if security_contact is what we expect... - self.assertEqual(security_contact.email, "changedEmail@email.com") - # If the item in PublicContact is as expected... - current_item = PublicContact.objects.filter(domain=self.domain).get() - self.assertEqual(current_item.email, "changedEmail@email.com") - self.assertEqual(security_contact, current_item) - - # Check the item associated with the domain... - self.assertEqual(self.domain.security_contact, security_contact) - self.assertEqual(self.domain.security_contact.email, "changedEmail@email.com") @skip("not implemented yet") def test_update_is_unsuccessful(self): From e12598bf207d357c10ee4beec62dc18a849772ac Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:44:41 -0600 Subject: [PATCH 30/58] Fix linter --- ops/scripts/manifest-sandbox-template.yaml | 1 - src/registrar/models/domain.py | 36 ++++++++++------------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/ops/scripts/manifest-sandbox-template.yaml b/ops/scripts/manifest-sandbox-template.yaml index a521aab09..1bf979c9f 100644 --- a/ops/scripts/manifest-sandbox-template.yaml +++ b/ops/scripts/manifest-sandbox-template.yaml @@ -11,7 +11,6 @@ applications: command: ./run.sh health-check-type: http health-check-http-endpoint: /health - health-check-invocation-timeout: 30 env: # Send stdout and stderr straight to the terminal without buffering PYTHONUNBUFFERED: yup diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index d0f19d5a7..34fe9b880 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -704,20 +704,17 @@ class Domain(TimeStampedModel, DomainHelper): auth_info = contact.auth_info postal_info = contact.postal_info addr = postal_info.addr - streets = {} - if addr is not None and addr.street is not None: - # 'zips' two lists together. - # For instance, (('street1', 'some_value_here'), - # ('street2', 'some_value_here')) - # Dict then converts this to a useable kwarg which we can pass in - streets = dict( - zip_longest( - ["street1", "street2", "street3"], - addr.street, - fillvalue=None, - ) + # 'zips' two lists together. + # For instance, (('street1', 'some_value_here'), + # ('street2', 'some_value_here')) + # Dict then converts this to a useable kwarg which we can pass in + streets = dict( + zip_longest( + ["street1", "street2", "street3"], + addr.street if addr is not None else [], + fillvalue=None, ) - + ) desired_contact = PublicContact( domain=self, contact_type=contact_type, @@ -725,13 +722,14 @@ class Domain(TimeStampedModel, DomainHelper): email=contact.email or "", voice=contact.voice or "", fax=contact.fax, - pw=auth_info.pw or "", name=postal_info.name or "", org=postal_info.org, - city=addr.city or "", - pc=addr.pc or "", - cc=addr.cc or "", - sp=addr.sp or "", + # For linter - default to "" instead of None + pw=getattr(auth_info, 'pw', ""), + city=getattr(addr, 'city', ""), + pc=getattr(addr, 'pc', ""), + cc=getattr(addr, 'cc', ""), + sp=getattr(addr, 'sp', ""), **streets, ) @@ -753,7 +751,7 @@ class Domain(TimeStampedModel, DomainHelper): def generic_contact_getter( self, contact_type_choice: PublicContact.ContactTypeChoices - ) -> PublicContact: + ) -> PublicContact | None: """Abstracts the cache logic on EppLib contact items contact_type_choice is a literal in PublicContact.ContactTypeChoices, From 75913adf29ecc52b6ed88f66d8f267d48f7b5084 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:54:41 -0600 Subject: [PATCH 31/58] Fixing linter, once more --- src/registrar/models/domain.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 34fe9b880..293e72cd8 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -725,11 +725,11 @@ class Domain(TimeStampedModel, DomainHelper): name=postal_info.name or "", org=postal_info.org, # For linter - default to "" instead of None - pw=getattr(auth_info, 'pw', ""), - city=getattr(addr, 'city', ""), - pc=getattr(addr, 'pc', ""), - cc=getattr(addr, 'cc', ""), - sp=getattr(addr, 'sp', ""), + pw=getattr(auth_info, "pw", ""), + city=getattr(addr, "city", ""), + pc=getattr(addr, "pc", ""), + cc=getattr(addr, "cc", ""), + sp=getattr(addr, "sp", ""), **streets, ) @@ -751,7 +751,7 @@ class Domain(TimeStampedModel, DomainHelper): def generic_contact_getter( self, contact_type_choice: PublicContact.ContactTypeChoices - ) -> PublicContact | None: + ) -> PublicContact: """Abstracts the cache logic on EppLib contact items contact_type_choice is a literal in PublicContact.ContactTypeChoices, @@ -773,9 +773,8 @@ class Domain(TimeStampedModel, DomainHelper): try: contacts = self._get_property(desired_property) except KeyError as error: - # Q: Should we be raising an error instead? logger.error(f"Could not find {contact_type_choice}: {error}") - return None + raise error else: # Grab from cache cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) From 152cd437fc7176aa2e422ba85e83ec4dbd49d0c8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:23:34 -0600 Subject: [PATCH 32/58] Code cleanup --- src/registrar/models/domain.py | 14 +++++--------- src/registrar/tests/test_models_domain.py | 16 ++-------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 293e72cd8..c76cc717f 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -21,7 +21,6 @@ from .utility.time_stamped_model import TimeStampedModel from .public_contact import PublicContact - logger = logging.getLogger(__name__) @@ -539,7 +538,6 @@ class Domain(TimeStampedModel, DomainHelper): current_contact = PublicContact.objects.filter( registry_id=contact.registry_id ).get() - logger.debug(f"current contact was accessed {current_contact}") if current_contact.email != contact.email: self._update_epp_contact(contact=contact) @@ -751,7 +749,7 @@ class Domain(TimeStampedModel, DomainHelper): def generic_contact_getter( self, contact_type_choice: PublicContact.ContactTypeChoices - ) -> PublicContact: + ) -> PublicContact | None: """Abstracts the cache logic on EppLib contact items contact_type_choice is a literal in PublicContact.ContactTypeChoices, @@ -773,8 +771,9 @@ class Domain(TimeStampedModel, DomainHelper): try: contacts = self._get_property(desired_property) except KeyError as error: + # Q: Should we be raising an error instead? logger.error(f"Could not find {contact_type_choice}: {error}") - raise error + return None else: # Grab from cache cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) @@ -841,7 +840,7 @@ class Domain(TimeStampedModel, DomainHelper): # If the for loop didn't do a return, # then we know that it doesn't exist within cache logger.info( - f"Requested contact {contact.registry_id} " "Does not exist in cache." + f"Requested contact {contact.registry_id} does not exist in cache." ) return None @@ -890,7 +889,6 @@ class Domain(TimeStampedModel, DomainHelper): while not exitEarly and count < 3: try: logger.info("Getting domain info from epp") - logger.debug(f"domain info name is... {self.__dict__}") req = commands.InfoDomain(name=self.name) domainInfo = registry.send(req, cleaned=True).res_data[0] exitEarly = True @@ -1242,7 +1240,7 @@ class Domain(TimeStampedModel, DomainHelper): # Saves to DB if it doesn't exist already. # Doesn't run custom save logic, just saves to DB public_contact.save(skip_epp_save=True) - logger.debug(f"Created a new PublicContact: {public_contact}") + logger.info(f"Created a new PublicContact: {public_contact}") # Append the item we just created return public_contact @@ -1265,7 +1263,6 @@ class Domain(TimeStampedModel, DomainHelper): def _invalidate_cache(self): """Remove cache data when updates are made.""" - logger.debug(f"cache was cleared! {self.__dict__}") self._cache = {} def _get_property(self, property): @@ -1277,7 +1274,6 @@ class Domain(TimeStampedModel, DomainHelper): ) if property in self._cache: - logger.debug(self._cache[property]) return self._cache[property] else: raise KeyError( diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 9a0f52e6a..f82441231 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -48,6 +48,7 @@ class TestDomainCache(MockEppLib): # using a setter should clear the cache domain.expiration_date = datetime.date.today() + self.assertEquals(domain._cache, {}) # send should have been called only once self.mockedSendFunction.assert_has_calls( @@ -97,7 +98,7 @@ class TestDomainCache(MockEppLib): expectedContactsList = [domain.security_contact] expectedHostsDict = { "name": self.mockDataInfoDomain.hosts[0], - "cr_date": self.mockDataInfoDomain.cr_date, + "cr_date": self.mockDataInfoHosts.cr_date, } # this can be changed when the getter for contacts is implemented @@ -112,7 +113,6 @@ class TestDomainCache(MockEppLib): # The contact list should not contain what is sent by the registry by default, # as _fetch_cache will transform the type to PublicContact self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) - self.assertEqual(domain._cache["contacts"], expectedContactsList) # get and check hosts is set correctly @@ -575,18 +575,6 @@ class TestRegistrantContacts(MockEppLib): """ raise - @skip("not implemented yet") - def test_contact_getters_cache(self): - """ - Scenario: A user is grabbing a domain that has multiple contact objects - When each contact is retrieved from cache - Then the user retrieves the correct contact objects - """ - - @skip("not implemented yet") - def test_epp_public_contact_mapper(self): - pass - def test_contact_getter_security(self): self.maxDiff = None # Create prexisting object... From c390bf99d98a8574506df769592bd50dec8af4cc Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:26:14 -0600 Subject: [PATCH 33/58] Removed missed logger --- src/registrar/models/domain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index c76cc717f..843fae6a6 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -695,7 +695,6 @@ class Domain(TimeStampedModel, DomainHelper): f"got {contact_id} with a length of {len(contact_id)}" ) - logger.debug(f"map_epp_contact_to_public_contact contact -> {contact}") if not isinstance(contact, eppInfo.InfoContactResultData): raise ValueError("Contact must be of type InfoContactResultData") From 60726f8c9bc3611def9550778ebcc42a6b400e50 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:02:03 -0600 Subject: [PATCH 34/58] Test + Resolved angry linter, suggestions, --- src/registrar/models/domain.py | 45 +++++++------------ .../templates/domain_security_email.html | 2 +- src/registrar/tests/test_views.py | 29 +++++++++++- src/registrar/views/domain.py | 7 +-- 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 843fae6a6..0e3cff325 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -353,7 +353,7 @@ class Domain(TimeStampedModel, DomainHelper): raise NotImplementedError() @Cache - def registrant_contact(self) -> PublicContact: + def registrant_contact(self) -> PublicContact | None: registrant = PublicContact.ContactTypeChoices.REGISTRANT return self.generic_contact_getter(registrant) @@ -368,7 +368,7 @@ class Domain(TimeStampedModel, DomainHelper): ) @Cache - def administrative_contact(self) -> PublicContact: + def administrative_contact(self) -> PublicContact | None: """Get or set the admin contact for this domain.""" admin = PublicContact.ContactTypeChoices.ADMINISTRATIVE return self.generic_contact_getter(admin) @@ -436,7 +436,7 @@ class Domain(TimeStampedModel, DomainHelper): ) @Cache - def security_contact(self) -> PublicContact: + def security_contact(self) -> PublicContact | None: """Get or set the security contact for this domain.""" security = PublicContact.ContactTypeChoices.SECURITY return self.generic_contact_getter(security) @@ -570,7 +570,7 @@ class Domain(TimeStampedModel, DomainHelper): ) @Cache - def technical_contact(self) -> PublicContact: + def technical_contact(self) -> PublicContact | None: """Get or set the tech contact for this domain.""" tech = PublicContact.ContactTypeChoices.TECHNICAL return self.generic_contact_getter(tech) @@ -652,21 +652,12 @@ class Domain(TimeStampedModel, DomainHelper): def isActive(self): return self.state == Domain.State.CREATED - # Q: I don't like this function name much, - # what would be better here? - # Q2: - # This can likely be done without passing in - # contact_id and contact_type and instead embedding it inside of - # contact, but the tradeoff for that is that it unnecessarily complicates using this - # (as you'd have to create a custom dictionary), and type checking becomes weaker. - # I'm sure though that there is an easier alternative... - # TLDR: This doesn't look as pretty, but it makes using this function easier + def map_epp_contact_to_public_contact( self, contact: eppInfo.InfoContactResultData, contact_id, - contact_type, - create_object=True, + contact_type ): """Maps the Epp contact representation to a PublicContact object. @@ -675,8 +666,6 @@ class Domain(TimeStampedModel, DomainHelper): contact_id -> str: The given registry_id of the object (i.e "cheese@cia.gov") contact_type -> str: The given contact type, (i.e. "tech" or "registrant") - - create_object -> bool: Flag for if this object is saved or not """ if contact is None: @@ -708,10 +697,11 @@ class Domain(TimeStampedModel, DomainHelper): streets = dict( zip_longest( ["street1", "street2", "street3"], - addr.street if addr is not None else [], + addr.street if addr is not None else [""], fillvalue=None, ) ) + desired_contact = PublicContact( domain=self, contact_type=contact_type, @@ -770,7 +760,7 @@ class Domain(TimeStampedModel, DomainHelper): try: contacts = self._get_property(desired_property) except KeyError as error: - # Q: Should we be raising an error instead? + # Q: Should we be raising an error instead? logger.error(f"Could not find {contact_type_choice}: {error}") return None else: @@ -805,19 +795,19 @@ class Domain(TimeStampedModel, DomainHelper): contact.domain = self return contact - def grab_contact_in_keys(self, contacts, check_type): + def grab_contact_in_keys(self, contacts, contact_type): """Grabs a contact object. Returns None if nothing is found. - check_type compares contact["type"] == check_type. + contact_type compares contact.contact_type == contact_type. - For example, check_type = 'security' + For example, contact_type = 'security' """ # Registrant doesn't exist as an array - if check_type == PublicContact.ContactTypeChoices.REGISTRANT: + if contact_type == PublicContact.ContactTypeChoices.REGISTRANT: if ( isinstance(contacts, PublicContact) and contacts.contact_type is not None - and contacts.contact_type == check_type + and contacts.contact_type == contact_type ): if contacts.registry_id is None: raise ValueError("registry_id cannot be None") @@ -826,11 +816,10 @@ class Domain(TimeStampedModel, DomainHelper): raise ValueError("Invalid contact object for registrant_contact") for contact in contacts: - print(f"grab_contact_in_keys -> contact item {contact.__dict__}") if ( isinstance(contact, PublicContact) and contact.contact_type is not None - and contact.contact_type == check_type + and contact.contact_type == contact_type ): if contact.registry_id is None: raise ValueError("registry_id cannot be None") @@ -1072,10 +1061,8 @@ class Domain(TimeStampedModel, DomainHelper): def _get_or_create_contact(self, contact: PublicContact): """Try to fetch info about a contact. Create it if it does not exist.""" - try: return self._request_contact_info(contact) - except RegistryError as e: if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST: logger.info( @@ -1132,8 +1119,6 @@ class Domain(TimeStampedModel, DomainHelper): # Registrant should be of type PublicContact if "registrant" in cleaned.keys(): - # Registrant, if it exists, should always exist in EppLib. - # If it doesn't, that is bad. We expect this to exist cleaned["registrant"] = self._registrant_to_public_contact( cleaned["registrant"] ) diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index deb54764e..8175fa394 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -21,7 +21,7 @@ + >{% if form.security_email.value is None or form.security_email.value == "dotgov@cisa.dhs.gov"%}Add security email{% else %}Save{% endif %} {% endblock %} {# domain_content #} diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index b8a922983..c71669dfd 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1,5 +1,5 @@ from unittest import skip -from unittest.mock import MagicMock, ANY +from unittest.mock import MagicMock, ANY, patch from django.conf import settings from django.test import Client, TestCase @@ -1406,9 +1406,35 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): ) self.assertContains(page, "Testy") + def test_domain_security_email_existing_security_contact(self): + """Can load domain's security email page.""" + self.mockSendPatch = patch("registrar.models.domain.registry.send") + self.mockedSendFunction = self.mockSendPatch.start() + self.mockedSendFunction.side_effect = self.mockSend + + domain_contact, _ = Domain.objects.get_or_create(name="freeman.gov") + # Add current user to this domain + _ = UserDomainRole( + user=self.user, + domain = domain_contact, + role = "admin" + ).save() + page = self.client.get( + reverse("domain-security-email", kwargs={"pk": domain_contact.id}) + ) + + # Loads correctly + self.assertContains(page, "Domain security email") + self.assertContains(page, "security@mail.gov") + self.mockSendPatch.stop() + def test_domain_security_email_no_security_contact(self): """Loads a domain with no defined security email. We should not show the default.""" + self.mockSendPatch = patch("registrar.models.domain.registry.send") + self.mockedSendFunction = self.mockSendPatch.start() + self.mockedSendFunction.side_effect = self.mockSend + page = self.client.get( reverse("domain-security-email", kwargs={"pk": self.domain.id}) ) @@ -1416,6 +1442,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): # Loads correctly self.assertContains(page, "Domain security email") self.assertNotContains(page, "dotgov@cisa.dhs.gov") + self.mockSendPatch.stop() def test_domain_security_email(self): """Can load domain's security email page.""" diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 25f53e8be..5df608ab9 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -250,13 +250,14 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): """The initial value for the form.""" domain = self.get_object() initial = super().get_initial() + security_contact = domain.security_contact if ( - domain.security_contact is None or - domain.security_contact.email == "dotgov@cisa.dhs.gov" + security_contact is None or + security_contact.email == "dotgov@cisa.dhs.gov" ): initial["security_email"] = None return initial - initial["security_email"] = domain.security_contact.email + initial["security_email"] = security_contact.email return initial def get_success_url(self): From d286fb6709a8013d7411b2a8457b958fd18f1f10 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 28 Sep 2023 08:01:37 -0600 Subject: [PATCH 35/58] Lint --- src/registrar/models/domain.py | 10 ++-------- src/registrar/tests/test_views.py | 9 ++------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 0e3cff325..03544e5c9 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -652,12 +652,8 @@ class Domain(TimeStampedModel, DomainHelper): def isActive(self): return self.state == Domain.State.CREATED - def map_epp_contact_to_public_contact( - self, - contact: eppInfo.InfoContactResultData, - contact_id, - contact_type + self, contact: eppInfo.InfoContactResultData, contact_id, contact_type ): """Maps the Epp contact representation to a PublicContact object. @@ -827,9 +823,7 @@ class Domain(TimeStampedModel, DomainHelper): # If the for loop didn't do a return, # then we know that it doesn't exist within cache - logger.info( - f"Requested contact {contact.registry_id} does not exist in cache." - ) + logger.info(f"Requested contact {contact.registry_id} does not exist in cache.") return None # ForeignKey on UserDomainRole creates a "permissions" member for diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index c71669dfd..996d49792 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1414,11 +1414,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): domain_contact, _ = Domain.objects.get_or_create(name="freeman.gov") # Add current user to this domain - _ = UserDomainRole( - user=self.user, - domain = domain_contact, - role = "admin" - ).save() + _ = UserDomainRole(user=self.user, domain=domain_contact, role="admin").save() page = self.client.get( reverse("domain-security-email", kwargs={"pk": domain_contact.id}) ) @@ -1434,7 +1430,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): self.mockSendPatch = patch("registrar.models.domain.registry.send") self.mockedSendFunction = self.mockSendPatch.start() self.mockedSendFunction.side_effect = self.mockSend - + page = self.client.get( reverse("domain-security-email", kwargs={"pk": self.domain.id}) ) @@ -1454,7 +1450,6 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): @skip("Ticket 912 needs to fix this one") def test_domain_security_email_form(self): """Adding a security email works. - Uses self.app WebTest because we need to interact with forms. """ security_email_page = self.app.get( From d3483b270070a86bde5423382701a7ec093b1999 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 29 Sep 2023 09:19:48 -0600 Subject: [PATCH 36/58] Added type: ignore --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 03544e5c9..c59007380 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -714,7 +714,7 @@ class Domain(TimeStampedModel, DomainHelper): cc=getattr(addr, "cc", ""), sp=getattr(addr, "sp", ""), **streets, - ) + ) # type: ignore return desired_contact From eaffde9d3bcbbe0c6ed51aecd97a45527936da74 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 29 Sep 2023 09:28:27 -0600 Subject: [PATCH 37/58] Lint --- src/registrar/models/domain.py | 2 +- src/registrar/tests/test_views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index c59007380..014da1ae2 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -714,7 +714,7 @@ class Domain(TimeStampedModel, DomainHelper): cc=getattr(addr, "cc", ""), sp=getattr(addr, "sp", ""), **streets, - ) # type: ignore + ) # type: ignore return desired_contact diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 996d49792..6096cd3b4 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -5,7 +5,7 @@ from django.conf import settings from django.test import Client, TestCase from django.urls import reverse from django.contrib.auth import get_user_model -from .common import MockEppLib, completed_application +from .common import MockEppLib, completed_application # type: ignore from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore From a84bbb5d3a6ae669346e01a1c18d9b1cb9a0078e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:39:26 -0600 Subject: [PATCH 38/58] Lint issue --- src/registrar/views/domain.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 5df608ab9..1cbf5c8e4 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -251,10 +251,7 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): domain = self.get_object() initial = super().get_initial() security_contact = domain.security_contact - if ( - security_contact is None or - security_contact.email == "dotgov@cisa.dhs.gov" - ): + if security_contact is None or security_contact.email == "dotgov@cisa.dhs.gov": initial["security_email"] = None return initial initial["security_email"] = security_contact.email From 1990f91e6a2a452a2f8915c4e4a723703ea2b7a9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:38:41 -0600 Subject: [PATCH 39/58] PR Changes --- src/registrar/models/domain.py | 227 ++++++++++-------- src/registrar/models/public_contact.py | 4 + src/registrar/models/utility/contact_error.py | 2 + src/registrar/tests/common.py | 9 +- src/registrar/tests/test_models_domain.py | 74 +++--- 5 files changed, 185 insertions(+), 131 deletions(-) create mode 100644 src/registrar/models/utility/contact_error.py diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 014da1ae2..bf255aca5 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -14,6 +14,7 @@ from epplibwrapper import ( RegistryError, ErrorCode, ) +from registrar.models.utility.contact_error import ContactError from .utility.domain_field import DomainField from .utility.domain_helper import DomainHelper @@ -668,36 +669,31 @@ class Domain(TimeStampedModel, DomainHelper): return None if contact_type is None: - raise ValueError("contact_type is None") + raise ContactError("contact_type is None") if contact_id is None: - raise ValueError("contact_id is None") + raise ContactError("contact_id is None") - if len(contact_id) > 16 or len(contact_id) < 1: - raise ValueError( + # Since contact_id is registry_id, + # check that its the right length + contact_id_length = len(contact_id) + if ( + contact_id_length > PublicContact.get_max_id_length() + or contact_id_length < 1 + ): + raise ContactError( "contact_id is of invalid length. " "Cannot exceed 16 characters, " - f"got {contact_id} with a length of {len(contact_id)}" + f"got {contact_id} with a length of {contact_id_length}" ) if not isinstance(contact, eppInfo.InfoContactResultData): - raise ValueError("Contact must be of type InfoContactResultData") + raise ContactError("Contact must be of type InfoContactResultData") auth_info = contact.auth_info postal_info = contact.postal_info addr = postal_info.addr - # 'zips' two lists together. - # For instance, (('street1', 'some_value_here'), - # ('street2', 'some_value_here')) - # Dict then converts this to a useable kwarg which we can pass in - streets = dict( - zip_longest( - ["street1", "street2", "street3"], - addr.street if addr is not None else [""], - fillvalue=None, - ) - ) - + streets = self._convert_streets_to_dict(addr.street) desired_contact = PublicContact( domain=self, contact_type=contact_type, @@ -718,6 +714,30 @@ class Domain(TimeStampedModel, DomainHelper): return desired_contact + def _convert_streets_to_dict(self, streets): + """ + Converts EPPLibs street representation + to PublicContacts + + EPPLib returns 'street' as an sequence of strings. + Meanwhile, PublicContact has this split into three + seperate properties: street1, street2, street3. + + Handles this disparity + """ + # 'zips' two lists together. + # For instance, (('street1', 'some_value_here'), + # ('street2', 'some_value_here')) + # Dict then converts this to a useable kwarg which we can pass in + street_dict = dict( + zip_longest( + ["street1", "street2", "street3"], + streets if streets is not None else [""], + fillvalue=None, + ) + ) + return street_dict + def _request_contact_info(self, contact: PublicContact): try: req = commands.InfoContact(id=contact.registry_id) @@ -735,7 +755,9 @@ class Domain(TimeStampedModel, DomainHelper): def generic_contact_getter( self, contact_type_choice: PublicContact.ContactTypeChoices ) -> PublicContact | None: - """Abstracts the cache logic on EppLib contact items + """Retrieves the desired PublicContact from the registry. + This abstracts the caching and EPP retrieval for + all contact items and thus may result in EPP calls being sent. contact_type_choice is a literal in PublicContact.ContactTypeChoices, for instance: PublicContact.ContactTypeChoices.SECURITY. @@ -744,8 +766,6 @@ class Domain(TimeStampedModel, DomainHelper): cache_contact_helper(PublicContact.ContactTypeChoices.SECURITY), or cache_contact_helper("security"). - Note: Registrant is handled slightly differently internally, - but the output will be the same. """ # registrant_contact(s) are an edge case. They exist on # the "registrant" property as opposed to contacts. @@ -754,16 +774,16 @@ class Domain(TimeStampedModel, DomainHelper): desired_property = "registrant" try: + # Grab from cache contacts = self._get_property(desired_property) except KeyError as error: - # Q: Should we be raising an error instead? logger.error(f"Could not find {contact_type_choice}: {error}") return None else: - # Grab from cache - cached_contact = self.grab_contact_in_keys(contacts, contact_type_choice) + cached_contact = self.get_contact_in_keys(contacts, contact_type_choice) if cached_contact is None: - raise ValueError("No contact was found in cache or the registry") + # TODO - #1103 + raise ContactError("No contact was found in cache or the registry") return cached_contact @@ -780,52 +800,66 @@ class Domain(TimeStampedModel, DomainHelper): return contact def get_default_technical_contact(self): - """Gets the default administrative contact.""" + """Gets the default technical contact.""" contact = PublicContact.get_default_technical() contact.domain = self return contact def get_default_registrant_contact(self): - """Gets the default administrative contact.""" + """Gets the default registrant contact.""" contact = PublicContact.get_default_registrant() contact.domain = self return contact - def grab_contact_in_keys(self, contacts, contact_type): - """Grabs a contact object. - Returns None if nothing is found. - contact_type compares contact.contact_type == contact_type. + def get_contact_in_keys(self, contacts, contact_type): + """Gets a contact object. - For example, contact_type = 'security' + Args: + contacts ([PublicContact]): List of PublicContacts + contact_type (literal): Which PublicContact to get + Returns: + PublicContact | None """ - # Registrant doesn't exist as an array + # Registrant doesn't exist as an array, and is of + # a special data type, so we need to handle that. if contact_type == PublicContact.ContactTypeChoices.REGISTRANT: - if ( - isinstance(contacts, PublicContact) - and contacts.contact_type is not None - and contacts.contact_type == contact_type - ): - if contacts.registry_id is None: - raise ValueError("registry_id cannot be None") - return contacts - else: - raise ValueError("Invalid contact object for registrant_contact") + desired_contact = None + if isinstance(contacts, str): + desired_contact = self._registrant_to_public_contact( + self._cache["registrant"] + ) + # Set the cache with the updated object + # for performance reasons. + if "registrant" in self._cache: + self._cache["registrant"] = desired_contact + elif isinstance(contacts, PublicContact): + desired_contact = contacts - for contact in contacts: - if ( - isinstance(contact, PublicContact) - and contact.contact_type is not None - and contact.contact_type == contact_type - ): - if contact.registry_id is None: - raise ValueError("registry_id cannot be None") - return contact + return self._handle_registrant_contact(desired_contact) - # If the for loop didn't do a return, - # then we know that it doesn't exist within cache - logger.info(f"Requested contact {contact.registry_id} does not exist in cache.") + _registry_id: str + if contact_type in contacts: + _registry_id = contacts.get(contact_type) + + desired = PublicContact.objects.filter( + registry_id=_registry_id, domain=self, contact_type=contact_type + ) + + if desired.count() == 1: + return desired.get() + + logger.info(f"Requested contact {_registry_id} does not exist in cache.") return None + def _handle_registrant_contact(self, contact): + if ( + contact.contact_type is not None + and contact.contact_type == PublicContact.ContactTypeChoices.REGISTRANT + ): + return contact + else: + raise ValueError("Invalid contact object for registrant_contact") + # ForeignKey on UserDomainRole creates a "permissions" member for # all of the user-roles that are in place for this domain @@ -1108,30 +1142,24 @@ class Domain(TimeStampedModel, DomainHelper): cleaned = {k: v for k, v in cache.items() if v is not ...} # statuses can just be a list no need to keep the epp object - if "statuses" in cleaned.keys(): + if "statuses" in cleaned: cleaned["statuses"] = [status.state for status in cleaned["statuses"]] - # Registrant should be of type PublicContact - if "registrant" in cleaned.keys(): - cleaned["registrant"] = self._registrant_to_public_contact( - cleaned["registrant"] - ) - if ( # fetch_contacts and - "_contacts" in cleaned.keys() + "_contacts" in cleaned and isinstance(cleaned["_contacts"], list) and len(cleaned["_contacts"]) > 0 ): - cleaned["contacts"] = [] + choices = PublicContact.ContactTypeChoices + # We expect that all these fields get populated, + # so we can create these early, rather than waiting. + cleaned["contacts"] = { + choices.ADMINISTRATIVE: None, + choices.SECURITY: None, + choices.TECHNICAL: None, + } for domainContact in cleaned["_contacts"]: - # we do not use _get_or_create_* because we expect the object we - # just asked the registry for still exists -- - # if not, that's a problem - - # TODO- discuss-should we check if contact is in public contacts - # and add it if not- - # this is really to keep in mind for the transition req = commands.InfoContact(id=domainContact.contact) data = registry.send(req, cleaned=True).res_data[0] @@ -1140,10 +1168,10 @@ class Domain(TimeStampedModel, DomainHelper): data, domainContact.contact, domainContact.type ) - # Find/create it in the DB, then add it to the list - cleaned["contacts"].append( - self._get_or_create_public_contact(mapped_object) - ) + # Find/create it in the DB + in_db = self._get_or_create_public_contact(mapped_object) + + cleaned["contacts"][in_db.contact_type] = in_db.registry_id # get nameserver info, if there are any if ( @@ -1182,7 +1210,7 @@ class Domain(TimeStampedModel, DomainHelper): def _get_or_create_public_contact(self, public_contact: PublicContact): """Tries to find a PublicContact object in our DB. - If it can't, it'll create it.""" + If it can't, it'll create it. Returns PublicContact""" db_contact = PublicContact.objects.filter( registry_id=public_contact.registry_id, contact_type=public_contact.contact_type, @@ -1190,37 +1218,36 @@ class Domain(TimeStampedModel, DomainHelper): ) # Raise an error if we find duplicates. - # This should not occur... + # This should not occur if db_contact.count() > 1: raise Exception( f"Multiple contacts found for {public_contact.contact_type}" ) - if db_contact.count() == 1: - existing_contact = db_contact.get() - # Does the item we're grabbing match - # what we have in our DB? - # If not, we likely have a duplicate. - if ( - existing_contact.email != public_contact.email - or existing_contact.registry_id != public_contact.registry_id - ): - raise ValueError( - "Requested PublicContact is out of sync " - "with DB. Potential duplicate?" - ) + # Save to DB if it doesn't exist already. + if db_contact.count() == 0: + # Doesn't run custom save logic, just saves to DB + public_contact.save(skip_epp_save=True) + logger.info(f"Created a new PublicContact: {public_contact}") + # Append the item we just created + return public_contact - # If it already exists, we can - # assume that the DB instance was updated - # during set, so we should just use that. - return existing_contact + existing_contact = db_contact.get() - # Saves to DB if it doesn't exist already. - # Doesn't run custom save logic, just saves to DB - public_contact.save(skip_epp_save=True) - logger.info(f"Created a new PublicContact: {public_contact}") - # Append the item we just created - return public_contact + # Does the item we're grabbing match + # what we have in our DB? + if ( + existing_contact.email != public_contact.email + or existing_contact.registry_id != public_contact.registry_id + ): + existing_contact.delete() + public_contact.save() + logger.warning("Requested PublicContact is out of sync " "with DB.") + return public_contact + # If it already exists, we can + # assume that the DB instance was updated + # during set, so we should just use that. + return existing_contact def _registrant_to_public_contact(self, registry_id: str): """EPPLib returns the registrant as a string, diff --git a/src/registrar/models/public_contact.py b/src/registrar/models/public_contact.py index b99bd1098..4afe3c467 100644 --- a/src/registrar/models/public_contact.py +++ b/src/registrar/models/public_contact.py @@ -149,6 +149,10 @@ class PublicContact(TimeStampedModel): pw="thisisnotapassword", ) + @classmethod + def get_max_id_length(cls): + return cls._meta.get_field("registry_id").max_length + def __str__(self): return ( f"{self.name} <{self.email}>" diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py new file mode 100644 index 000000000..93084eca2 --- /dev/null +++ b/src/registrar/models/utility/contact_error.py @@ -0,0 +1,2 @@ +class ContactError(Exception): + ... diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 3abce8355..94fd2bc66 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -683,9 +683,9 @@ class MockEppLib(TestCase): else: return MagicMock(res_data=[self.mockDataInfoDomain]) elif isinstance(_request, commands.InfoContact): - # Default contact return - mocked_result = self.mockDataInfoContact - # For testing contact types... + mocked_result: info.InfoContactResultData + + # For testing contact types match getattr(_request, "id", None): case "securityContact": mocked_result = self.mockSecurityContact @@ -695,7 +695,8 @@ class MockEppLib(TestCase): mocked_result = self.mockAdministrativeContact case "regContact": mocked_result = self.mockRegistrantContact - case "123": + case _: + # Default contact return mocked_result = self.mockDataInfoContact return MagicMock(res_data=[mocked_result]) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index f82441231..dbd7d6a79 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -89,13 +89,17 @@ class TestDomainCache(MockEppLib): def test_cache_nested_elements(self): """Cache works correctly with the nested objects cache and hosts""" domain, _ = Domain.objects.get_or_create(name="igorville.gov") - # The contact list will initally contain objects of type 'DomainContact' + # The contact list will initially contain objects of type 'DomainContact' # this is then transformed into PublicContact, and cache should NOT # hold onto the DomainContact object expectedUnfurledContactsList = [ common.DomainContact(contact="123", type="security"), ] - expectedContactsList = [domain.security_contact] + expectedContactsList = { + PublicContact.ContactTypeChoices.ADMINISTRATIVE: None, + PublicContact.ContactTypeChoices.SECURITY: "123", + PublicContact.ContactTypeChoices.TECHNICAL: None, + } expectedHostsDict = { "name": self.mockDataInfoDomain.hosts[0], "cr_date": self.mockDataInfoHosts.cr_date, @@ -122,15 +126,16 @@ class TestDomainCache(MockEppLib): def test_map_epp_contact_to_public_contact(self): # Tests that the mapper is working how we expect domain, _ = Domain.objects.get_or_create(name="registry.gov") + security = PublicContact.ContactTypeChoices.SECURITY mapped = domain.map_epp_contact_to_public_contact( self.mockDataInfoContact, self.mockDataInfoContact.id, - PublicContact.ContactTypeChoices.SECURITY, + security, ) expected_contact = PublicContact( domain=domain, - contact_type=PublicContact.ContactTypeChoices.SECURITY, + contact_type=security, registry_id="123", email="123@mail.gov", voice="+1.8882820870", @@ -158,11 +163,23 @@ class TestDomainCache(MockEppLib): db_object = domain._get_or_create_public_contact(mapped) in_db = PublicContact.objects.filter( registry_id=domain.security_contact.registry_id, - contact_type=PublicContact.ContactTypeChoices.SECURITY, + contact_type=security, ).get() # DB Object is the same as the mapped object self.assertEqual(db_object, in_db) + domain.security_contact = in_db + # Trigger the getter + _ = domain.security_contact + # Check to see that changes made + # to DB objects persist in cache correctly + in_db.email = "123test@mail.gov" + in_db.save() + + cached_contact = domain._cache["contacts"].get(security) + self.assertEqual(cached_contact, in_db.registry_id) + self.assertEqual(domain.security_contact.email, "123test@mail.gov") + class TestDomainCreation(MockEppLib): """Rule: An approved domain application must result in a domain""" @@ -335,8 +352,6 @@ class TestRegistrantContacts(MockEppLib): self.domain_contact._invalidate_cache() PublicContact.objects.all().delete() Domain.objects.all().delete() - # self.contactMailingAddressPatch.stop() - # self.createContactPatch.stop() def test_no_security_email(self): """ @@ -576,22 +591,22 @@ class TestRegistrantContacts(MockEppLib): raise def test_contact_getter_security(self): - self.maxDiff = None - # Create prexisting object... + security = PublicContact.ContactTypeChoices.SECURITY + # Create prexisting object expected_contact = self.domain.map_epp_contact_to_public_contact( self.mockSecurityContact, contact_id="securityContact", - contact_type=PublicContact.ContactTypeChoices.SECURITY, + contact_type=security, ) - # Checks if we grabbed the correct PublicContact... + # Checks if we grabbed the correct PublicContact self.assertEqual( self.domain_contact.security_contact.email, expected_contact.email ) expected_contact_db = PublicContact.objects.filter( registry_id=self.domain_contact.security_contact.registry_id, - contact_type=PublicContact.ContactTypeChoices.SECURITY, + contact_type=security, ).get() self.assertEqual(self.domain_contact.security_contact, expected_contact_db) @@ -604,27 +619,29 @@ class TestRegistrantContacts(MockEppLib): ), ] ) - # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["contacts"][0], expected_contact_db) + # Checks if we are receiving the cache we expect + cache = self.domain_contact._cache["contacts"] + self.assertEqual(cache.get(security), "securityContact") def test_contact_getter_technical(self): + technical = PublicContact.ContactTypeChoices.TECHNICAL expected_contact = self.domain.map_epp_contact_to_public_contact( self.mockTechnicalContact, contact_id="technicalContact", - contact_type=PublicContact.ContactTypeChoices.TECHNICAL, + contact_type=technical, ) self.assertEqual( self.domain_contact.technical_contact.email, expected_contact.email ) - # Checks if we grab the correct PublicContact... + # Checks if we grab the correct PublicContact expected_contact_db = PublicContact.objects.filter( registry_id=self.domain_contact.technical_contact.registry_id, - contact_type=PublicContact.ContactTypeChoices.TECHNICAL, + contact_type=technical, ).get() - # Checks if we grab the correct PublicContact... + # Checks if we grab the correct PublicContact self.assertEqual(self.domain_contact.technical_contact, expected_contact_db) self.mockedSendFunction.assert_has_calls( [ @@ -634,14 +651,16 @@ class TestRegistrantContacts(MockEppLib): ), ] ) - # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["contacts"][1], expected_contact_db) + # Checks if we are receiving the cache we expect + cache = self.domain_contact._cache["contacts"] + self.assertEqual(cache.get(technical), "technicalContact") def test_contact_getter_administrative(self): + administrative = PublicContact.ContactTypeChoices.ADMINISTRATIVE expected_contact = self.domain.map_epp_contact_to_public_contact( self.mockAdministrativeContact, contact_id="adminContact", - contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE, + contact_type=administrative, ) self.assertEqual( @@ -650,10 +669,10 @@ class TestRegistrantContacts(MockEppLib): expected_contact_db = PublicContact.objects.filter( registry_id=self.domain_contact.administrative_contact.registry_id, - contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE, + contact_type=administrative, ).get() - # Checks if we grab the correct PublicContact... + # Checks if we grab the correct PublicContact self.assertEqual( self.domain_contact.administrative_contact, expected_contact_db ) @@ -665,8 +684,9 @@ class TestRegistrantContacts(MockEppLib): ), ] ) - # Checks if we are recieving the cache we expect... - self.assertEqual(self.domain_contact._cache["contacts"][2], expected_contact_db) + # Checks if we are receiving the cache we expect + cache = self.domain_contact._cache["contacts"] + self.assertEqual(cache.get(administrative), "adminContact") def test_contact_getter_registrant(self): expected_contact = self.domain.map_epp_contact_to_public_contact( @@ -684,7 +704,7 @@ class TestRegistrantContacts(MockEppLib): contact_type=PublicContact.ContactTypeChoices.REGISTRANT, ).get() - # Checks if we grab the correct PublicContact... + # Checks if we grab the correct PublicContact self.assertEqual(self.domain_contact.registrant_contact, expected_contact_db) self.mockedSendFunction.assert_has_calls( [ @@ -694,7 +714,7 @@ class TestRegistrantContacts(MockEppLib): ), ] ) - # Checks if we are recieving the cache we expect... + # Checks if we are receiving the cache we expect. self.assertEqual(self.domain_contact._cache["registrant"], expected_contact_db) From ca189ff3d7418cc98dd460d04ecb046a36ce75bf Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:14:06 -0600 Subject: [PATCH 40/58] Fix merge issues --- src/registrar/models/domain.py | 50 ++++++++++++++++++++++- src/registrar/tests/test_models_domain.py | 7 ++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 0295b794f..9629a9938 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1005,7 +1005,7 @@ class Domain(TimeStampedModel, DomainHelper): logger.info("pendingCreate()-> inside pending create") self._delete_domain() # TODO - delete ticket any additional error handling here - + @transition( field="state", source=[State.DNS_NEEDED], @@ -1026,6 +1026,46 @@ class Domain(TimeStampedModel, DomainHelper): raise ValueError("Not ready to become created, cannot transition yet") logger.info("able to transition to ready state") + def _fetch_contacts(self, contact_data): + """Fetch contact info.""" + contacts = [] + for domainContact in contact_data: + req = commands.InfoContact(id=domainContact.contact) + data = registry.send(req, cleaned=True).res_data[0] + contact = { + "id": domainContact.contact, + "type": domainContact.type, + "auth_info": getattr(data, "auth_info", ...), + "cr_date": getattr(data, "cr_date", ...), + "disclose": getattr(data, "disclose", ...), + "email": getattr(data, "email", ...), + "fax": getattr(data, "fax", ...), + "postal_info": getattr(data, "postal_info", ...), + "statuses": getattr(data, "statuses", ...), + "tr_date": getattr(data, "tr_date", ...), + "up_date": getattr(data, "up_date", ...), + "voice": getattr(data, "voice", ...), + } + contacts.append({k: v for k, v in contact.items() if v is not ...}) + return contacts + + def _fetch_hosts(self, host_data): + """Fetch host info.""" + hosts = [] + for name in host_data: + req = commands.InfoHost(name=name) + data = registry.send(req, cleaned=True).res_data[0] + host = { + "name": name, + "addrs": getattr(data, "addrs", ...), + "cr_date": getattr(data, "cr_date", ...), + "statuses": getattr(data, "statuses", ...), + "tr_date": getattr(data, "tr_date", ...), + "up_date": getattr(data, "up_date", ...), + } + hosts.append({k: v for k, v in host.items() if v is not ...}) + return hosts + def _disclose_fields(self, contact: PublicContact): """creates a disclose object that can be added to a contact Create using .disclose= on the command before sending. @@ -1170,6 +1210,8 @@ class Domain(TimeStampedModel, DomainHelper): and isinstance(cleaned["_contacts"], list) and len(cleaned["_contacts"]) > 0 ): + #cleaned["contacts"] = self._fetch_contacts(cleaned["_contacts"]) + choices = PublicContact.ContactTypeChoices # We expect that all these fields get populated, # so we can create these early, rather than waiting. @@ -1192,6 +1234,12 @@ class Domain(TimeStampedModel, DomainHelper): cleaned["contacts"][in_db.contact_type] = in_db.registry_id + # We're only getting contacts, so retain the old + # hosts that existed in cache (if they existed) + # and pass them along. + if old_cache_hosts is not None: + cleaned["hosts"] = old_cache_hosts + # get nameserver info, if there are any if ( # fetch_hosts and diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 2d9ad3144..91668be8e 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -94,7 +94,7 @@ class TestDomainCache(MockEppLib): expectedUnfurledContactsList = [ common.DomainContact(contact="123", type="security"), ] - expectedContactsList = { + expectedContactsDict = { PublicContact.ContactTypeChoices.ADMINISTRATIVE: None, PublicContact.ContactTypeChoices.SECURITY: "123", PublicContact.ContactTypeChoices.TECHNICAL: None, @@ -116,12 +116,11 @@ class TestDomainCache(MockEppLib): # The contact list should not contain what is sent by the registry by default, # as _fetch_cache will transform the type to PublicContact self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) - self.assertEqual(domain._cache["contacts"], expectedContactsList) + self.assertEqual(domain._cache["contacts"], expectedContactsDict) # get and check hosts is set correctly domain._get_property("hosts") self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) - self.assertEqual(domain._cache["contacts"], [expectedContactsDict]) # invalidate cache domain._cache = {} @@ -133,7 +132,7 @@ class TestDomainCache(MockEppLib): # get contacts domain._get_property("contacts") self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) - self.assertEqual(domain._cache["contacts"], [expectedContactsDict]) + self.assertEqual(domain._cache["contacts"], expectedContactsDict) def test_map_epp_contact_to_public_contact(self): # Tests that the mapper is working how we expect From 60cf20fc3d7c96c47b8677900b60e28a7b6f65c1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:21:40 -0600 Subject: [PATCH 41/58] Merge madness --- src/registrar/models/domain.py | 112 ++++++++++++++------------------- 1 file changed, 48 insertions(+), 64 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 9629a9938..76622624d 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1025,46 +1025,6 @@ class Domain(TimeStampedModel, DomainHelper): if len(nameserverList) < 2 or len(nameserverList) > 13: raise ValueError("Not ready to become created, cannot transition yet") logger.info("able to transition to ready state") - - def _fetch_contacts(self, contact_data): - """Fetch contact info.""" - contacts = [] - for domainContact in contact_data: - req = commands.InfoContact(id=domainContact.contact) - data = registry.send(req, cleaned=True).res_data[0] - contact = { - "id": domainContact.contact, - "type": domainContact.type, - "auth_info": getattr(data, "auth_info", ...), - "cr_date": getattr(data, "cr_date", ...), - "disclose": getattr(data, "disclose", ...), - "email": getattr(data, "email", ...), - "fax": getattr(data, "fax", ...), - "postal_info": getattr(data, "postal_info", ...), - "statuses": getattr(data, "statuses", ...), - "tr_date": getattr(data, "tr_date", ...), - "up_date": getattr(data, "up_date", ...), - "voice": getattr(data, "voice", ...), - } - contacts.append({k: v for k, v in contact.items() if v is not ...}) - return contacts - - def _fetch_hosts(self, host_data): - """Fetch host info.""" - hosts = [] - for name in host_data: - req = commands.InfoHost(name=name) - data = registry.send(req, cleaned=True).res_data[0] - host = { - "name": name, - "addrs": getattr(data, "addrs", ...), - "cr_date": getattr(data, "cr_date", ...), - "statuses": getattr(data, "statuses", ...), - "tr_date": getattr(data, "tr_date", ...), - "up_date": getattr(data, "up_date", ...), - } - hosts.append({k: v for k, v in host.items() if v is not ...}) - return hosts def _disclose_fields(self, contact: PublicContact): """creates a disclose object that can be added to a contact Create using @@ -1141,6 +1101,29 @@ class Domain(TimeStampedModel, DomainHelper): ) return err.code + def _fetch_contacts(self, contact_data): + """Fetch contact info.""" + contacts = [] + for domainContact in contact_data: + req = commands.InfoContact(id=domainContact.contact) + data = registry.send(req, cleaned=True).res_data[0] + contact = { + "id": domainContact.contact, + "type": domainContact.type, + "auth_info": getattr(data, "auth_info", ...), + "cr_date": getattr(data, "cr_date", ...), + "disclose": getattr(data, "disclose", ...), + "email": getattr(data, "email", ...), + "fax": getattr(data, "fax", ...), + "postal_info": getattr(data, "postal_info", ...), + "statuses": getattr(data, "statuses", ...), + "tr_date": getattr(data, "tr_date", ...), + "up_date": getattr(data, "up_date", ...), + "voice": getattr(data, "voice", ...), + } + contacts.append({k: v for k, v in contact.items() if v is not ...}) + return contacts + def _get_or_create_contact(self, contact: PublicContact): """Try to fetch info about a contact. Create it if it does not exist.""" try: @@ -1166,6 +1149,23 @@ class Domain(TimeStampedModel, DomainHelper): ) raise e + + def _fetch_hosts(self, host_data): + """Fetch host info.""" + hosts = [] + for name in host_data: + req = commands.InfoHost(name=name) + data = registry.send(req, cleaned=True).res_data[0] + host = { + "name": name, + "addrs": getattr(data, "addrs", ...), + "cr_date": getattr(data, "cr_date", ...), + "statuses": getattr(data, "statuses", ...), + "tr_date": getattr(data, "tr_date", ...), + "up_date": getattr(data, "up_date", ...), + } + hosts.append({k: v for k, v in host.items() if v is not ...}) + return hosts def _update_or_create_host(self, host): raise NotImplementedError() @@ -1242,33 +1242,17 @@ class Domain(TimeStampedModel, DomainHelper): # get nameserver info, if there are any if ( - # fetch_hosts and - "_hosts" in cleaned + fetch_hosts + and "_hosts" in cleaned and isinstance(cleaned["_hosts"], list) and len(cleaned["_hosts"]) ): - # TODO- add elif in cache set it to be the old cache value - # no point in removing - cleaned["hosts"] = [] - for name in cleaned["_hosts"]: - # we do not use _get_or_create_* because we expect the object we - # just asked the registry for still exists -- - # if not, that's a problem - req = commands.InfoHost(name=name) - data = registry.send(req, cleaned=True).res_data[0] - # extract properties from response - # (Ellipsis is used to mean "null") - host = { - "name": name, - "addrs": getattr(data, "addrs", ...), - "cr_date": getattr(data, "cr_date", ...), - "statuses": getattr(data, "statuses", ...), - "tr_date": getattr(data, "tr_date", ...), - "up_date": getattr(data, "up_date", ...), - } - cleaned["hosts"].append( - {k: v for k, v in host.items() if v is not ...} - ) + cleaned["hosts"] = self._fetch_hosts(cleaned["_hosts"]) + # We're only getting hosts, so retain the old + # contacts that existed in cache (if they existed) + # and pass them along. + if old_cache_contacts is not None: + cleaned["contacts"] = old_cache_contacts # replace the prior cache with new data self._cache = cleaned From 1cf5bc3eaa25f8dc81011e4a3eb445f24118a5de Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:42:27 -0600 Subject: [PATCH 42/58] Fine details --- src/registrar/tests/common.py | 2 +- src/registrar/tests/test_models_domain.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 4ef11e261..723210164 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -668,7 +668,7 @@ class MockEppLib(TestCase): ) mockDataInfoHosts = fakedEppObject( - "lastPw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35) + "lastPw", cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35) ) def mockSend(self, _request, cleaned): diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 91668be8e..da23066cc 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -121,7 +121,7 @@ class TestDomainCache(MockEppLib): # get and check hosts is set correctly domain._get_property("hosts") self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) - + self.assertEqual(domain._cache["contacts"], expectedContactsDict) # invalidate cache domain._cache = {} @@ -662,6 +662,13 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) def test_updates_security_email(self): + """ + Scenario: Registrant replaces one valid security contact email with another + Given a domain exists in the registry with a user-added security email + When `domain.security_contact` is set equal to a PublicContact with a new + security contact email + Then Domain sends `commands.UpdateContact` to the registry + """ security_contact = self.domain.get_default_security_contact() security_contact.email = "originalUserEmail@gmail.com" security_contact.registry_id = "fail" From 4b493112ed39b7033c744781b0a7f1076104b10a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:50:04 -0600 Subject: [PATCH 43/58] Linter fixes --- src/registrar/models/domain.py | 3 --- src/registrar/tests/test_views.py | 1 - 2 files changed, 4 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 76622624d..d187048a7 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1210,8 +1210,6 @@ class Domain(TimeStampedModel, DomainHelper): and isinstance(cleaned["_contacts"], list) and len(cleaned["_contacts"]) > 0 ): - #cleaned["contacts"] = self._fetch_contacts(cleaned["_contacts"]) - choices = PublicContact.ContactTypeChoices # We expect that all these fields get populated, # so we can create these early, rather than waiting. @@ -1231,7 +1229,6 @@ class Domain(TimeStampedModel, DomainHelper): # Find/create it in the DB in_db = self._get_or_create_public_contact(mapped_object) - cleaned["contacts"][in_db.contact_type] = in_db.registry_id # We're only getting contacts, so retain the old diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 98319d141..a909be1f1 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -25,7 +25,6 @@ from registrar.models import ( from registrar.views.application import ApplicationWizard, Step from .common import less_console_noise -from .common import MockEppLib class TestViews(TestCase): From 4ca753f9a9392a3bb7ce88937c6f86b663f06895 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:58:50 -0600 Subject: [PATCH 44/58] The final lint --- src/registrar/models/domain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index d187048a7..96ea6a98c 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1005,7 +1005,7 @@ class Domain(TimeStampedModel, DomainHelper): logger.info("pendingCreate()-> inside pending create") self._delete_domain() # TODO - delete ticket any additional error handling here - + @transition( field="state", source=[State.DNS_NEEDED], @@ -1025,7 +1025,7 @@ class Domain(TimeStampedModel, DomainHelper): if len(nameserverList) < 2 or len(nameserverList) > 13: raise ValueError("Not ready to become created, cannot transition yet") logger.info("able to transition to ready state") - + def _disclose_fields(self, contact: PublicContact): """creates a disclose object that can be added to a contact Create using .disclose= on the command before sending. @@ -1149,7 +1149,7 @@ class Domain(TimeStampedModel, DomainHelper): ) raise e - + def _fetch_hosts(self, host_data): """Fetch host info.""" hosts = [] From 49960afb8f4be26a3dec7a8f3c376012a0d0b487 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:06:34 -0600 Subject: [PATCH 45/58] Final, real --- src/registrar/models/domain.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 96ea6a98c..11d78a346 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -705,7 +705,10 @@ class Domain(TimeStampedModel, DomainHelper): auth_info = contact.auth_info postal_info = contact.postal_info addr = postal_info.addr - streets = self._convert_streets_to_dict(addr.street) + streets = None + if addr is not None: + streets = addr.street + streets_kwargs = self._convert_streets_to_dict(streets) desired_contact = PublicContact( domain=self, contact_type=contact_type, @@ -721,7 +724,7 @@ class Domain(TimeStampedModel, DomainHelper): pc=getattr(addr, "pc", ""), cc=getattr(addr, "cc", ""), sp=getattr(addr, "sp", ""), - **streets, + **streets_kwargs, ) # type: ignore return desired_contact From f89578d82125e388e702bc93562c1766335ef545 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:32:29 -0600 Subject: [PATCH 46/58] Update domain.py --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 71ce5c43e..1eec0b8cf 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -370,7 +370,7 @@ class Domain(TimeStampedModel, DomainHelper): @Cache def administrative_contact(self) -> PublicContact | None: - """Get or set the admin contact for this domain.""" + """Get the admin contact for this domain.""" admin = PublicContact.ContactTypeChoices.ADMINISTRATIVE return self.generic_contact_getter(admin) From e456cec33b00f50a3fea1275c4f23a617ba62037 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 09:16:56 -0600 Subject: [PATCH 47/58] Requested PR changes Removed skip on test_domain_security_email_form, fixed common.py, extended contact_error.py, fixed domain_detail not showing the right data, small cleanup of domain.py --- src/registrar/models/domain.py | 79 +++++++++---------- src/registrar/models/utility/contact_error.py | 47 ++++++++++- src/registrar/templates/domain_detail.html | 2 +- src/registrar/tests/common.py | 2 +- src/registrar/tests/test_views.py | 1 - 5 files changed, 84 insertions(+), 47 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 1eec0b8cf..2d117b3d2 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -14,7 +14,7 @@ from epplibwrapper import ( RegistryError, ErrorCode, ) -from registrar.models.utility.contact_error import ContactError +from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from .utility.domain_field import DomainField from .utility.domain_helper import DomainHelper @@ -676,10 +676,10 @@ class Domain(TimeStampedModel, DomainHelper): return None if contact_type is None: - raise ContactError("contact_type is None") + raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE) if contact_id is None: - raise ContactError("contact_id is None") + raise ContactError(code=ContactErrorCodes.CONTACT_ID_NONE) # Since contact_id is registry_id, # check that its the right length @@ -727,13 +727,25 @@ class Domain(TimeStampedModel, DomainHelper): def _convert_streets_to_dict(self, streets): """ Converts EPPLibs street representation - to PublicContacts + to PublicContacts. + + Args: + streets (Sequence[str]): Streets from EPPLib. + + Returns: + dict: { + "street1": str or "", + + "street2": str or None, + + "street3": str or None, + } EPPLib returns 'street' as an sequence of strings. Meanwhile, PublicContact has this split into three seperate properties: street1, street2, street3. - Handles this disparity + Handles this disparity. """ # 'zips' two lists together. # For instance, (('street1', 'some_value_here'), @@ -1117,26 +1129,27 @@ class Domain(TimeStampedModel, DomainHelper): def _fetch_contacts(self, contact_data): """Fetch contact info.""" - contacts = [] + choices = PublicContact.ContactTypeChoices + # We expect that all these fields get populated, + # so we can create these early, rather than waiting. + contacts_dict = { + choices.ADMINISTRATIVE: None, + choices.SECURITY: None, + choices.TECHNICAL: None, + } for domainContact in contact_data: req = commands.InfoContact(id=domainContact.contact) data = registry.send(req, cleaned=True).res_data[0] - contact = { - "id": domainContact.contact, - "type": domainContact.type, - "auth_info": getattr(data, "auth_info", ...), - "cr_date": getattr(data, "cr_date", ...), - "disclose": getattr(data, "disclose", ...), - "email": getattr(data, "email", ...), - "fax": getattr(data, "fax", ...), - "postal_info": getattr(data, "postal_info", ...), - "statuses": getattr(data, "statuses", ...), - "tr_date": getattr(data, "tr_date", ...), - "up_date": getattr(data, "up_date", ...), - "voice": getattr(data, "voice", ...), - } - contacts.append({k: v for k, v in contact.items() if v is not ...}) - return contacts + + # Map the object we recieved from EPP to a PublicContact + mapped_object = self.map_epp_contact_to_public_contact( + data, domainContact.contact, domainContact.type + ) + + # Find/create it in the DB + in_db = self._get_or_create_public_contact(mapped_object) + contacts_dict[in_db.contact_type] = in_db.registry_id + return contacts_dict def _get_or_create_contact(self, contact: PublicContact): """Try to fetch info about a contact. Create it if it does not exist.""" @@ -1224,27 +1237,7 @@ class Domain(TimeStampedModel, DomainHelper): and isinstance(cleaned["_contacts"], list) and len(cleaned["_contacts"]) > 0 ): - choices = PublicContact.ContactTypeChoices - # We expect that all these fields get populated, - # so we can create these early, rather than waiting. - cleaned["contacts"] = { - choices.ADMINISTRATIVE: None, - choices.SECURITY: None, - choices.TECHNICAL: None, - } - for domainContact in cleaned["_contacts"]: - req = commands.InfoContact(id=domainContact.contact) - data = registry.send(req, cleaned=True).res_data[0] - - # Map the object we recieved from EPP to a PublicContact - mapped_object = self.map_epp_contact_to_public_contact( - data, domainContact.contact, domainContact.type - ) - - # Find/create it in the DB - in_db = self._get_or_create_public_contact(mapped_object) - cleaned["contacts"][in_db.contact_type] = in_db.registry_id - + cleaned["contacts"] = self._fetch_contacts(cleaned["_contacts"]) # We're only getting contacts, so retain the old # hosts that existed in cache (if they existed) # and pass them along. diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py index 93084eca2..5c99a0004 100644 --- a/src/registrar/models/utility/contact_error.py +++ b/src/registrar/models/utility/contact_error.py @@ -1,2 +1,47 @@ +from enum import IntEnum + + +class ContactErrorCodes(IntEnum): + """ + Used in the ContactError class for + error mapping. + + Overview of contact error codes: + - 2000 CONTACT_TYPE_NONE + - 2001 CONTACT_ID_NONE + - 2002 CONTACT_ID_INVALID_LENGTH + - 2003 CONTACT_INVALID_TYPE + """ + + CONTACT_TYPE_NONE = 2000 + CONTACT_ID_NONE = 2001 + CONTACT_ID_INVALID_LENGTH = 2002 + CONTACT_INVALID_TYPE = 2003 + + class ContactError(Exception): - ... + """ + Overview of contact error codes: + - 2000 CONTACT_TYPE_NONE + - 2001 CONTACT_ID_NONE + - 2002 CONTACT_ID_INVALID_LENGTH + - 2003 CONTACT_INVALID_TYPE + """ + # For linter + _contact_id_error = "contact_id has an invalid length. Cannot exceed 16 characters." + _contact_invalid_error = "Contact must be of type InfoContactResultData" + _error_mapping = { + ContactErrorCodes.CONTACT_TYPE_NONE: "contact_type is None", + ContactErrorCodes.CONTACT_ID_NONE: "contact_id is None", + ContactErrorCodes.CONTACT_ID_INVALID_LENGTH: _contact_id_error, + ContactErrorCodes.CONTACT_INVALID_TYPE: _contact_invalid_error, + } + + def __init__(self, *args, code=None, **kwargs): + super().__init__(*args, **kwargs) + self.code = code + if self.code in self._error_mapping: + self.message = self._error_mapping.get(self.code) + + def __str__(self): + return f"{self.message}" diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 6a700b393..b9aba5e63 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,7 +46,7 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% include "includes/summary_item.html" with title='Security email' value=domain.security_email edit_link=url %} + {% include "includes/summary_item.html" with title='Security email' value=domain.get_security_email() edit_link=url %} {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index a3522bf87..0dd1ee231 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -606,7 +606,7 @@ class MockEppLib(TestCase): return fake mockDataInfoDomain = fakedEppObject( - "lastPw", + "fakePw", cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), contacts=[ common.DomainContact( diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index a909be1f1..6e379a5e2 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1466,7 +1466,6 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): ) self.assertContains(page, "Domain security email") - @skip("Ticket 912 needs to fix this one") def test_domain_security_email_form(self): """Adding a security email works. Uses self.app WebTest because we need to interact with forms. From 3ef9ccb1eec78b4053e36a9feac51ebc09d77b5f Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 09:54:42 -0600 Subject: [PATCH 48/58] Fix linter + test --- src/registrar/models/utility/contact_error.py | 1 + src/registrar/templates/domain_detail.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py index 5c99a0004..e888d238e 100644 --- a/src/registrar/models/utility/contact_error.py +++ b/src/registrar/models/utility/contact_error.py @@ -27,6 +27,7 @@ class ContactError(Exception): - 2002 CONTACT_ID_INVALID_LENGTH - 2003 CONTACT_INVALID_TYPE """ + # For linter _contact_id_error = "contact_id has an invalid length. Cannot exceed 16 characters." _contact_invalid_error = "Contact must be of type InfoContactResultData" diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index b9aba5e63..76b59075c 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,7 +46,7 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% include "includes/summary_item.html" with title='Security email' value=domain.get_security_email() edit_link=url %} + {% include "includes/summary_item.html" with title='Security email' value=domain.security_contact.email edit_link=url %} {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} From d645c2fbaa9182c86980900069e9113f05e52eb7 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:30:39 -0600 Subject: [PATCH 49/58] Don't display default sec email --- src/registrar/models/domain.py | 5 ++++- src/registrar/templates/domain_detail.html | 7 ++++++- src/registrar/views/utility/permission_views.py | 13 ++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 2d117b3d2..0c39054a3 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -603,7 +603,10 @@ class Domain(TimeStampedModel, DomainHelper): def get_security_email(self): logger.info("get_security_email-> getting the contact ") secContact = self.security_contact - return secContact.email + if secContact is not None: + return secContact.email + else: + return None def clientHoldStatus(self): return epp.Status(state=self.Status.CLIENT_HOLD, description="", lang="en") diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 76b59075c..ff66100be 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,7 +46,12 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% include "includes/summary_item.html" with title='Security email' value=domain.security_contact.email edit_link=url %} + {% if security_email is not None and security_email != "dotgov@cisa.dhs.gov"%} + {% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %} + {% else %} + {% include "includes/summary_item.html" with title='Security email' value=None edit_link=url %} + {% endif %} + {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index 417ee8417..ac4ace1fb 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -29,9 +29,20 @@ class DomainPermissionView(DomainPermission, DetailView, abc.ABC): # variable name in template context for the model object context_object_name = "domain" - # Adds context information for user permissions + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.security_email = None + + def get_object(self, queryset=None): + obj = super().get_object(queryset) + self.security_email = obj.get_security_email() + return obj + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + # Store the security email + context["security_email"] = self.security_email + # Adds context information for user permissions user = self.request.user context["is_analyst_or_superuser"] = user.is_staff or user.is_superuser # Stored in a variable for the linter From 0413dbcbb4aac2072d7d99f41b6c5a0dd75a0cb9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:40:58 -0600 Subject: [PATCH 50/58] Store in a form instead --- src/registrar/templates/domain_detail.html | 7 +------ src/registrar/views/utility/permission_views.py | 13 +------------ 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index ff66100be..2ff47148c 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,12 +46,7 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% if security_email is not None and security_email != "dotgov@cisa.dhs.gov"%} - {% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %} - {% else %} - {% include "includes/summary_item.html" with title='Security email' value=None edit_link=url %} - {% endif %} - + {% include "includes/summary_item.html" with title='Security email' value=form.security_email.value edit_link=url %} {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index ac4ace1fb..417ee8417 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -29,20 +29,9 @@ class DomainPermissionView(DomainPermission, DetailView, abc.ABC): # variable name in template context for the model object context_object_name = "domain" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.security_email = None - - def get_object(self, queryset=None): - obj = super().get_object(queryset) - self.security_email = obj.get_security_email() - return obj - + # Adds context information for user permissions def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - # Store the security email - context["security_email"] = self.security_email - # Adds context information for user permissions user = self.request.user context["is_analyst_or_superuser"] = user.is_staff or user.is_superuser # Stored in a variable for the linter From 6891ea76f24b3fc4b686375718598c8eb5ebafde Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:44:17 -0600 Subject: [PATCH 51/58] Get security email context --- src/registrar/templates/domain_detail.html | 7 +++++-- src/registrar/views/domain.py | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 2ff47148c..c8e3864de 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,8 +46,11 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% include "includes/summary_item.html" with title='Security email' value=form.security_email.value edit_link=url %} - + {% if security_email is not None and security_email != "dotgov@cisa.dhs.gov"%} + {% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %} + {% else %} + {% include "includes/summary_item.html" with title='Security email' value=None edit_link=url %} + {% endif %} {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 4eabacadd..79ccea7c5 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -41,7 +41,9 @@ class DomainView(DomainPermissionView): """Domain detail overview page.""" template_name = "domain_detail.html" - + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["security_email"] = self.get_object().get_security_email() class DomainOrgNameAddressView(DomainPermissionView, FormMixin): """Organization name and mailing address view""" From 238a33b95eb4dff55fc9e3643a212bc0129a29b8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:53:47 -0600 Subject: [PATCH 52/58] Cleanup --- src/registrar/models/domain.py | 5 +---- src/registrar/views/domain.py | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 0c39054a3..2d117b3d2 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -603,10 +603,7 @@ class Domain(TimeStampedModel, DomainHelper): def get_security_email(self): logger.info("get_security_email-> getting the contact ") secContact = self.security_contact - if secContact is not None: - return secContact.email - else: - return None + return secContact.email def clientHoldStatus(self): return epp.Status(state=self.Status.CLIENT_HOLD, description="", lang="en") diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 79ccea7c5..715ea7fb3 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -41,9 +41,6 @@ class DomainView(DomainPermissionView): """Domain detail overview page.""" template_name = "domain_detail.html" - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["security_email"] = self.get_object().get_security_email() class DomainOrgNameAddressView(DomainPermissionView, FormMixin): """Organization name and mailing address view""" From 9546aedd59b5848642fa589678636a0357d2dc89 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:57:34 -0600 Subject: [PATCH 53/58] Lint --- src/registrar/views/domain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 715ea7fb3..4eabacadd 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -42,6 +42,7 @@ class DomainView(DomainPermissionView): template_name = "domain_detail.html" + class DomainOrgNameAddressView(DomainPermissionView, FormMixin): """Organization name and mailing address view""" From 540a604055c0f22082e37797f45f6f4e9e459138 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:14:25 -0600 Subject: [PATCH 54/58] Add default text --- src/registrar/models/domain.py | 10 +++------- src/registrar/models/utility/contact_error.py | 4 ++++ src/registrar/templates/domain_detail.html | 2 +- src/registrar/views/domain.py | 8 ++++++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 2d117b3d2..ee63362fb 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -688,14 +688,10 @@ class Domain(TimeStampedModel, DomainHelper): contact_id_length > PublicContact.get_max_id_length() or contact_id_length < 1 ): - raise ContactError( - "contact_id is of invalid length. " - "Cannot exceed 16 characters, " - f"got {contact_id} with a length of {contact_id_length}" - ) + raise ContactError(code=ContactErrorCodes.CONTACT_ID_INVALID_LENGTH) if not isinstance(contact, eppInfo.InfoContactResultData): - raise ContactError("Contact must be of type InfoContactResultData") + raise ContactError(code=ContactErrorCodes.CONTACT_INVALID_TYPE) auth_info = contact.auth_info postal_info = contact.postal_info @@ -805,7 +801,7 @@ class Domain(TimeStampedModel, DomainHelper): cached_contact = self.get_contact_in_keys(contacts, contact_type_choice) if cached_contact is None: # TODO - #1103 - raise ContactError("No contact was found in cache or the registry") + raise ContactError(code=ContactErrorCodes.CONTACT_NOT_FOUND) return cached_contact diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py index e888d238e..fad928afe 100644 --- a/src/registrar/models/utility/contact_error.py +++ b/src/registrar/models/utility/contact_error.py @@ -17,6 +17,7 @@ class ContactErrorCodes(IntEnum): CONTACT_ID_NONE = 2001 CONTACT_ID_INVALID_LENGTH = 2002 CONTACT_INVALID_TYPE = 2003 + CONTACT_NOT_FOUND = 2004 class ContactError(Exception): @@ -26,16 +27,19 @@ class ContactError(Exception): - 2001 CONTACT_ID_NONE - 2002 CONTACT_ID_INVALID_LENGTH - 2003 CONTACT_INVALID_TYPE + - 2004 CONTACT_NOT_FOUND """ # For linter _contact_id_error = "contact_id has an invalid length. Cannot exceed 16 characters." _contact_invalid_error = "Contact must be of type InfoContactResultData" + _contact_not_found_error = "No contact was found in cache or the registry" _error_mapping = { ContactErrorCodes.CONTACT_TYPE_NONE: "contact_type is None", ContactErrorCodes.CONTACT_ID_NONE: "contact_id is None", ContactErrorCodes.CONTACT_ID_INVALID_LENGTH: _contact_id_error, ContactErrorCodes.CONTACT_INVALID_TYPE: _contact_invalid_error, + ContactErrorCodes.CONTACT_NOT_FOUND: _contact_not_found_error } def __init__(self, *args, code=None, **kwargs): diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index c8e3864de..ea3efd68c 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -49,7 +49,7 @@ {% if security_email is not None and security_email != "dotgov@cisa.dhs.gov"%} {% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %} {% else %} - {% include "includes/summary_item.html" with title='Security email' value=None edit_link=url %} + {% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %} {% endif %} {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 4eabacadd..a3903367e 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -42,6 +42,14 @@ class DomainView(DomainPermissionView): template_name = "domain_detail.html" + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + security_email = self.get_object().get_security_email() + if security_email is None or security_email == "dotgov@cisa.dhs.gov": + context["security_email"] = None + return context + context["security_email"] = security_email + return context class DomainOrgNameAddressView(DomainPermissionView, FormMixin): """Organization name and mailing address view""" From c447eb407f307ee1d7f6cf1fcb2eff19b8708908 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:25:10 -0600 Subject: [PATCH 55/58] Undo additional changes - revert to PR accepted --- src/registrar/models/domain.py | 14 +++-- src/registrar/models/utility/contact_error.py | 52 +------------------ src/registrar/templates/domain_detail.html | 7 +-- src/registrar/views/domain.py | 8 --- 4 files changed, 12 insertions(+), 69 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index ee63362fb..55e714416 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -14,7 +14,7 @@ from epplibwrapper import ( RegistryError, ErrorCode, ) -from registrar.models.utility.contact_error import ContactError, ContactErrorCodes +from registrar.models.utility.contact_error import ContactError from .utility.domain_field import DomainField from .utility.domain_helper import DomainHelper @@ -676,10 +676,10 @@ class Domain(TimeStampedModel, DomainHelper): return None if contact_type is None: - raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE) + raise ContactError("contact_type is None") if contact_id is None: - raise ContactError(code=ContactErrorCodes.CONTACT_ID_NONE) + raise ContactError("contact_id is None") # Since contact_id is registry_id, # check that its the right length @@ -688,10 +688,14 @@ class Domain(TimeStampedModel, DomainHelper): contact_id_length > PublicContact.get_max_id_length() or contact_id_length < 1 ): - raise ContactError(code=ContactErrorCodes.CONTACT_ID_INVALID_LENGTH) + raise ContactError( + "contact_id is of invalid length. " + "Cannot exceed 16 characters, " + f"got {contact_id} with a length of {contact_id_length}" + ) if not isinstance(contact, eppInfo.InfoContactResultData): - raise ContactError(code=ContactErrorCodes.CONTACT_INVALID_TYPE) + raise ContactError("Contact must be of type InfoContactResultData") auth_info = contact.auth_info postal_info = contact.postal_info diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py index fad928afe..93084eca2 100644 --- a/src/registrar/models/utility/contact_error.py +++ b/src/registrar/models/utility/contact_error.py @@ -1,52 +1,2 @@ -from enum import IntEnum - - -class ContactErrorCodes(IntEnum): - """ - Used in the ContactError class for - error mapping. - - Overview of contact error codes: - - 2000 CONTACT_TYPE_NONE - - 2001 CONTACT_ID_NONE - - 2002 CONTACT_ID_INVALID_LENGTH - - 2003 CONTACT_INVALID_TYPE - """ - - CONTACT_TYPE_NONE = 2000 - CONTACT_ID_NONE = 2001 - CONTACT_ID_INVALID_LENGTH = 2002 - CONTACT_INVALID_TYPE = 2003 - CONTACT_NOT_FOUND = 2004 - - class ContactError(Exception): - """ - Overview of contact error codes: - - 2000 CONTACT_TYPE_NONE - - 2001 CONTACT_ID_NONE - - 2002 CONTACT_ID_INVALID_LENGTH - - 2003 CONTACT_INVALID_TYPE - - 2004 CONTACT_NOT_FOUND - """ - - # For linter - _contact_id_error = "contact_id has an invalid length. Cannot exceed 16 characters." - _contact_invalid_error = "Contact must be of type InfoContactResultData" - _contact_not_found_error = "No contact was found in cache or the registry" - _error_mapping = { - ContactErrorCodes.CONTACT_TYPE_NONE: "contact_type is None", - ContactErrorCodes.CONTACT_ID_NONE: "contact_id is None", - ContactErrorCodes.CONTACT_ID_INVALID_LENGTH: _contact_id_error, - ContactErrorCodes.CONTACT_INVALID_TYPE: _contact_invalid_error, - ContactErrorCodes.CONTACT_NOT_FOUND: _contact_not_found_error - } - - def __init__(self, *args, code=None, **kwargs): - super().__init__(*args, **kwargs) - self.code = code - if self.code in self._error_mapping: - self.message = self._error_mapping.get(self.code) - - def __str__(self): - return f"{self.message}" + ... diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index ea3efd68c..6a700b393 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,11 +46,8 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% if security_email is not None and security_email != "dotgov@cisa.dhs.gov"%} - {% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %} - {% else %} - {% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %} - {% endif %} + {% include "includes/summary_item.html" with title='Security email' value=domain.security_email edit_link=url %} + {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index a3903367e..4eabacadd 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -42,14 +42,6 @@ class DomainView(DomainPermissionView): template_name = "domain_detail.html" - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - security_email = self.get_object().get_security_email() - if security_email is None or security_email == "dotgov@cisa.dhs.gov": - context["security_email"] = None - return context - context["security_email"] = security_email - return context class DomainOrgNameAddressView(DomainPermissionView, FormMixin): """Organization name and mailing address view""" From b2f8753a98dd2c8bb3d11c0b648e4d553d0fd3a3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:28:27 -0600 Subject: [PATCH 56/58] Update domain.py --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 55e714416..492921e6f 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -805,7 +805,7 @@ class Domain(TimeStampedModel, DomainHelper): cached_contact = self.get_contact_in_keys(contacts, contact_type_choice) if cached_contact is None: # TODO - #1103 - raise ContactError(code=ContactErrorCodes.CONTACT_NOT_FOUND) + raise ContactError("No contact was found in cache or the registry") return cached_contact From e6948f462bdace7dfa30e12702976a98aa589453 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:27:09 -0600 Subject: [PATCH 57/58] Correct a typo on the success message Per Gabys request --- src/registrar/views/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 4eabacadd..d8c3c80fa 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -292,7 +292,7 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): contact.save() messages.success( - self.request, "The security email for this domain have been updated." + self.request, "The security email for this domain has been updated." ) # superclass has the redirect From 41eab0d3a70d327a67baba51dd5b8fe99fb53bec Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:31:59 -0600 Subject: [PATCH 58/58] Fix verbiage --- src/registrar/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 6e379a5e2..68aaf0ed8 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1487,7 +1487,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib): self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) success_page = result.follow() self.assertContains( - success_page, "The security email for this domain have been updated" + success_page, "The security email for this domain has been updated" ) def test_domain_overview_blocked_for_ineligible_user(self):