Add signals

This commit is contained in:
zandercymatics 2024-03-29 13:06:53 -06:00
parent 9e4b48a84b
commit ed8535b695
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
6 changed files with 193 additions and 11 deletions

View file

@ -1095,8 +1095,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
"Type of organization", "Type of organization",
{ {
"fields": [ "fields": [
"generic_org_type", "organization_type",
"is_election_board",
"federal_type", "federal_type",
"federal_agency", "federal_agency",
"tribe_name", "tribe_name",

View file

@ -98,6 +98,8 @@ class DomainRequestFixture:
def _set_non_foreign_key_fields(cls, da: DomainRequest, app: dict): def _set_non_foreign_key_fields(cls, da: DomainRequest, app: dict):
"""Helper method used by `load`.""" """Helper method used by `load`."""
da.status = app["status"] if "status" in app else "started" da.status = app["status"] if "status" in app else "started"
# TODO for a future ticket: Allow for more than just "federal" here
da.generic_org_type = app["generic_org_type"] if "generic_org_type" in app else "federal" da.generic_org_type = app["generic_org_type"] if "generic_org_type" in app else "federal"
da.federal_agency = ( da.federal_agency = (
app["federal_agency"] app["federal_agency"]
@ -235,9 +237,6 @@ class DomainFixture(DomainRequestFixture):
).last() ).last()
logger.debug(f"Approving {domain_request} for {user}") logger.debug(f"Approving {domain_request} for {user}")
# We don't want fixtures sending out real emails to
# fake email addresses, so we just skip that and log it instead
# All approvals require an investigator, so if there is none, # All approvals require an investigator, so if there is none,
# assign one. # assign one.
if domain_request.investigator is None: if domain_request.investigator is None:

View file

@ -0,0 +1,38 @@
# Generated by Django 4.2.10 on 2024-03-29 15:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0080_create_groups_v09"),
]
operations = [
migrations.AddField(
model_name="domainrequest",
name="organization_type",
field=models.CharField(
blank=True,
choices=[
("federal", "Federal"),
("interstate", "Interstate"),
("state_or_territory", "State or territory"),
("tribal", "Tribal"),
("county", "County"),
("city", "City"),
("special_district", "Special district"),
("school_district", "School district"),
("state_or_territory_election", "State or territory - Election"),
("tribal_election", "Tribal - Election"),
("county_election", "County - Election"),
("city_election", "City - Election"),
("special_district_election", "Special district - Election"),
],
help_text="Type of organization - Election office",
max_length=255,
null=True,
),
),
]

View file

@ -198,7 +198,7 @@ class Domain(TimeStampedModel, DomainHelper):
is called in the validate function on the request/domain page is called in the validate function on the request/domain page
throws- RegistryError or InvalidDomainError""" throws- RegistryError or InvalidDomainError"""
return True
if not cls.string_could_be_domain(domain): if not cls.string_could_be_domain(domain):
logger.warning("Not a valid domain: %s" % str(domain)) logger.warning("Not a valid domain: %s" % str(domain))
# throw invalid domain error so that it can be caught in # throw invalid domain error so that it can be caught in

View file

@ -100,8 +100,8 @@ class DomainRequest(TimeStampedModel):
class OrganizationChoices(models.TextChoices): class OrganizationChoices(models.TextChoices):
""" """
Primary organization choices: Primary organization choices:
For use in django admin For use in the request experience
Keys need to match OrganizationChoicesVerbose Keys need to match OrganizationChoicesElectionOffice and OrganizationChoicesVerbose
""" """
FEDERAL = "federal", "Federal" FEDERAL = "federal", "Federal"
@ -113,9 +113,38 @@ class DomainRequest(TimeStampedModel):
SPECIAL_DISTRICT = "special_district", "Special district" SPECIAL_DISTRICT = "special_district", "Special district"
SCHOOL_DISTRICT = "school_district", "School district" SCHOOL_DISTRICT = "school_district", "School district"
class OrganizationChoicesElectionOffice(models.TextChoices):
"""
Primary organization choices for Django admin:
Keys need to match OrganizationChoices and OrganizationChoicesVerbose.
The enums here come in two variants:
Regular (matches the choices from OrganizationChoices)
Election (Appends " - Election" to the string)
When adding the election variant, you must append "_election" to the end of the string.
"""
# We can't inherit OrganizationChoices due to models.TextChoices being an enum.
# We can redefine these values instead.
FEDERAL = "federal", "Federal"
INTERSTATE = "interstate", "Interstate"
STATE_OR_TERRITORY = "state_or_territory", "State or territory"
TRIBAL = "tribal", "Tribal"
COUNTY = "county", "County"
CITY = "city", "City"
SPECIAL_DISTRICT = "special_district", "Special district"
SCHOOL_DISTRICT = "school_district", "School district"
# Election variants
STATE_OR_TERRITORY_ELECTION = "state_or_territory_election", "State or territory - Election"
TRIBAL_ELECTION = "tribal_election", "Tribal - Election"
COUNTY_ELECTION = "county_election", "County - Election"
CITY_ELECTION = "city_election", "City - Election"
SPECIAL_DISTRICT_ELECTION = "special_district_election", "Special district - Election"
class OrganizationChoicesVerbose(models.TextChoices): class OrganizationChoicesVerbose(models.TextChoices):
""" """
Secondary organization choices Tertiary organization choices
For use in the domain request form and on the templates For use in the domain request form and on the templates
Keys need to match OrganizationChoices Keys need to match OrganizationChoices
""" """
@ -406,6 +435,14 @@ class DomainRequest(TimeStampedModel):
help_text="Type of organization", help_text="Type of organization",
) )
organization_type = models.CharField(
max_length=255,
choices=OrganizationChoicesElectionOffice.choices,
null=True,
blank=True,
help_text="Type of organization - Election office",
)
federally_recognized_tribe = models.BooleanField( federally_recognized_tribe = models.BooleanField(
null=True, null=True,
help_text="Is the tribe federally recognized", help_text="Is the tribe federally recognized",
@ -449,6 +486,7 @@ class DomainRequest(TimeStampedModel):
help_text="Organization name", help_text="Organization name",
db_index=True, db_index=True,
) )
address_line1 = models.CharField( address_line1 = models.CharField(
null=True, null=True,
blank=True, blank=True,
@ -525,6 +563,7 @@ class DomainRequest(TimeStampedModel):
related_name="domain_request", related_name="domain_request",
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
alternative_domains = models.ManyToManyField( alternative_domains = models.ManyToManyField(
"registrar.Website", "registrar.Website",
blank=True, blank=True,

View file

@ -1,14 +1,121 @@
import logging import logging
from django.db.models.signals import post_save from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver from django.dispatch import receiver
from .models import User, Contact from .models import User, Contact, DomainRequest
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@receiver(pre_save, sender=DomainRequest)
def create_or_update_organization_type(sender, instance, **kwargs):
"""The organization_type field on DomainRequest is consituted from the
generic_org_type and is_election_board fields. To keep the organization_type
field up to date, we need to update it before save based off of those field
values.
If the instance is marked as an election board and the generic_org_type is not
one of the excluded types (FEDERAL, INTERSTATE, SCHOOL_DISTRICT), the
organization_type is set to a corresponding election variant. Otherwise, it directly
mirrors the generic_org_type value.
"""
if not isinstance(instance, DomainRequest):
# I don't see how this could possibly happen - but its still a good check to have.
# Lets force a fail condition rather than wait for one to happen, if this occurs.
raise ValueError("Type mismatch. The instance was not DomainRequest.")
# == Init variables == #
# We can't grab the election variant if it is in federal, interstate, or school_district.
# The "election variant" is just the org name, with " - Election" appended to the end.
# For example, "School district - Election".
invalid_types = [
DomainRequest.OrganizationChoices.FEDERAL,
DomainRequest.OrganizationChoices.INTERSTATE,
DomainRequest.OrganizationChoices.SCHOOL_DISTRICT,
]
# TODO - maybe we need a check here for .filter then .get
is_new_instance = instance.id is None
# A new record is added with organization_type not defined.
# This happens from the regular domain request flow.
if is_new_instance:
# == Check for invalid conditions before proceeding == #
if instance.organization_type and instance.generic_org_type:
# Since organization type is linked with generic_org_type and election board,
# we have to update one or the other, not both.
raise ValueError("Cannot update organization_type and generic_org_type simultaneously.")
# If no changes occurred, do nothing
if not instance.organization_type and not instance.generic_org_type:
return None
# == Program flow will halt here if there is no reason to update == #
# == Update the linked values == #
# Find out which field needs updating
organization_type_needs_update = instance.organization_type is None
generic_org_type_needs_update = instance.generic_org_type is None
# Update that field
if organization_type_needs_update:
_update_org_type_from_generic_org_and_election(instance, invalid_types)
elif generic_org_type_needs_update:
_update_generic_org_and_election_from_org_type(instance)
else:
# Instance is already in the database, fetch its current state
current_instance = DomainRequest.objects.get(id=instance.id)
# Check the new and old values
generic_org_type_changed = instance.generic_org_type != current_instance.generic_org_type
is_election_board_changed = instance.is_election_board != current_instance.is_election_board
organization_type_changed = instance.organization_type != current_instance.organization_type
# == Check for invalid conditions before proceeding == #
if organization_type_changed and (generic_org_type_changed or is_election_board_changed):
# Since organization type is linked with generic_org_type and election board,
# we have to update one or the other, not both.
raise ValueError("Cannot update organization_type and generic_org_type simultaneously.")
# If no changes occured, do nothing
if not organization_type_changed and (not generic_org_type_changed and not is_election_board_changed):
return None
# == Program flow will halt here if there is no reason to update == #
# == Update the linked values == #
# Find out which field needs updating
organization_type_needs_update = generic_org_type_changed or is_election_board_changed
generic_org_type_needs_update = organization_type_changed
# Update that field
if organization_type_needs_update:
_update_org_type_from_generic_org_and_election(instance, invalid_types)
elif generic_org_type_needs_update:
_update_generic_org_and_election_from_org_type(instance)
def _update_org_type_from_generic_org_and_election(instance, invalid_types):
# TODO handle if generic_org_type is None
if instance.generic_org_type not in invalid_types and instance.is_election_board:
instance.organization_type = f"{instance.generic_org_type}_election"
else:
instance.organization_type = str(instance.generic_org_type)
def _update_generic_org_and_election_from_org_type(instance):
"""Given a value for organization_type, update the
generic_org_type and is_election_board values."""
# TODO find a better solution than this
current_org_type = str(instance.organization_type)
if "_election" in current_org_type:
instance.generic_org_type = current_org_type.split("_election")[0]
instance.is_election_board = True
else:
instance.organization_type = str(instance.generic_org_type)
instance.is_election_board = False
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def handle_profile(sender, instance, **kwargs): def handle_profile(sender, instance, **kwargs):
"""Method for when a User is saved. """Method for when a User is saved.