This commit is contained in:
zandercymatics 2024-04-01 10:12:25 -06:00
parent 94132bb9a2
commit 58b8e4649d
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
6 changed files with 224 additions and 84 deletions

View file

@ -883,8 +883,7 @@ class DomainInformationAdmin(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

@ -0,0 +1,83 @@
# Generated by Django 4.2.10 on 2024-04-01 15:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0080_create_groups_v09"),
]
operations = [
migrations.AddField(
model_name="domaininformation",
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,
),
),
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,
),
),
migrations.AlterField(
model_name="domaininformation",
name="generic_org_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"),
],
help_text="Type of organization",
max_length=255,
null=True,
),
),
]

View file

@ -1,38 +0,0 @@
# 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

@ -54,7 +54,23 @@ class DomainInformation(TimeStampedModel):
choices=OrganizationChoices.choices, choices=OrganizationChoices.choices,
null=True, null=True,
blank=True, blank=True,
help_text="Type of Organization", help_text="Type of organization",
)
# TODO - Ticket #1911: stub this data from DomainRequest
is_election_board = models.BooleanField(
null=True,
blank=True,
help_text="Is your organization an election office?",
)
# TODO - Ticket #1911: stub this data from DomainRequest
organization_type = models.CharField(
max_length=255,
choices=DomainRequest.OrgChoicesElectionOffice.choices,
null=True,
blank=True,
help_text="Type of organization - Election office",
) )
federally_recognized_tribe = models.BooleanField( federally_recognized_tribe = models.BooleanField(

View file

@ -101,7 +101,7 @@ class DomainRequest(TimeStampedModel):
""" """
Primary organization choices: Primary organization choices:
For use in the request experience For use in the request experience
Keys need to match OrganizationChoicesElectionOffice and OrganizationChoicesVerbose Keys need to match OrgChoicesElectionOffice and OrganizationChoicesVerbose
""" """
FEDERAL = "federal", "Federal" FEDERAL = "federal", "Federal"
@ -113,7 +113,7 @@ 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): class OrgChoicesElectionOffice(models.TextChoices):
""" """
Primary organization choices for Django admin: Primary organization choices for Django admin:
Keys need to match OrganizationChoices and OrganizationChoicesVerbose. Keys need to match OrganizationChoices and OrganizationChoicesVerbose.
@ -142,6 +142,44 @@ class DomainRequest(TimeStampedModel):
CITY_ELECTION = "city_election", "City - Election" CITY_ELECTION = "city_election", "City - Election"
SPECIAL_DISTRICT_ELECTION = "special_district_election", "Special district - Election" SPECIAL_DISTRICT_ELECTION = "special_district_election", "Special district - Election"
@classmethod
def get_org_election_to_org_generic(cls):
"""
Creates and returns a dictionary mapping from election-specific organization
choice enums to their corresponding general organization choice enums.
If no such mapping exists, it is simple excluded from the map.
"""
# This can be mapped automatically but its harder to read.
# For clarity reasons, we manually define this.
org_election_map = {
cls.STATE_OR_TERRITORY_ELECTION: cls.STATE_OR_TERRITORY,
cls.TRIBAL_ELECTION: cls.TRIBAL,
cls.COUNTY_ELECTION: cls.COUNTY,
cls.CITY_ELECTION: cls.CITY,
cls.SPECIAL_DISTRICT_ELECTION: cls.SPECIAL_DISTRICT,
}
return org_election_map
@classmethod
def get_org_generic_to_org_election(cls):
"""
Creates and returns a dictionary mapping from general organization
choice enums to their corresponding election-specific organization enums.
If no such mapping exists, it is simple excluded from the map.
"""
# This can be mapped automatically but its harder to read.
# For clarity reasons, we manually define this.
org_election_map = {
cls.STATE_OR_TERRITORY: cls.STATE_OR_TERRITORY_ELECTION,
cls.TRIBAL: cls.TRIBAL_ELECTION,
cls.COUNTY: cls.COUNTY_ELECTION,
cls.CITY: cls.CITY_ELECTION,
cls.SPECIAL_DISTRICT: cls.SPECIAL_DISTRICT_ELECTION,
}
return org_election_map
class OrganizationChoicesVerbose(models.TextChoices): class OrganizationChoicesVerbose(models.TextChoices):
""" """
Tertiary organization choices Tertiary organization choices
@ -435,9 +473,16 @@ class DomainRequest(TimeStampedModel):
help_text="Type of organization", help_text="Type of organization",
) )
is_election_board = models.BooleanField(
null=True,
blank=True,
help_text="Is your organization an election office?",
)
# TODO - Ticket #1911: stub this data from DomainRequest
organization_type = models.CharField( organization_type = models.CharField(
max_length=255, max_length=255,
choices=OrganizationChoicesElectionOffice.choices, choices=OrgChoicesElectionOffice.choices,
null=True, null=True,
blank=True, blank=True,
help_text="Type of organization - Election office", help_text="Type of organization - Election office",
@ -474,12 +519,6 @@ class DomainRequest(TimeStampedModel):
help_text="Federal government branch", help_text="Federal government branch",
) )
is_election_board = models.BooleanField(
null=True,
blank=True,
help_text="Is your organization an election office?",
)
organization_name = models.CharField( organization_name = models.CharField(
null=True, null=True,
blank=True, blank=True,

View file

@ -3,15 +3,16 @@ import logging
from django.db.models.signals import pre_save, 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, DomainRequest from .models import User, Contact, DomainRequest, DomainInformation
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@receiver(pre_save, sender=DomainRequest) @receiver(pre_save, sender=DomainRequest)
@receiver(pre_save, sender=DomainInformation)
def create_or_update_organization_type(sender, instance, **kwargs): def create_or_update_organization_type(sender, instance, **kwargs):
"""The organization_type field on DomainRequest is consituted from the """The organization_type field on DomainRequest and DomainInformation is consituted from the
generic_org_type and is_election_board fields. To keep the organization_type 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 field up to date, we need to update it before save based off of those field
values. values.
@ -21,50 +22,67 @@ def create_or_update_organization_type(sender, instance, **kwargs):
organization_type is set to a corresponding election variant. Otherwise, it directly organization_type is set to a corresponding election variant. Otherwise, it directly
mirrors the generic_org_type value. mirrors the generic_org_type value.
""" """
if not isinstance(instance, DomainRequest): if not isinstance(instance, DomainRequest) and not isinstance(instance, DomainInformation):
# I don't see how this could possibly happen - but its still a good check to have. # 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. # Lets force a fail condition rather than wait for one to happen, if this occurs.
raise ValueError("Type mismatch. The instance was not DomainRequest.") raise ValueError("Type mismatch. The instance was not DomainRequest or DomainInformation.")
# == Init variables == # # == 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 is_new_instance = instance.id is None
election_org_choices = DomainRequest.OrgChoicesElectionOffice
# For any given organization type, return the "_election" variant.
# For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION
generic_org_to_org_map = election_org_choices.get_org_generic_to_org_election()
# For any given "_election" variant, return the base org type.
# For example: STATE_OR_TERRITORY_ELECTION => STATE_OR_TERRITORY
election_org_to_generic_org_map = election_org_choices.get_org_election_to_org_generic()
# A new record is added with organization_type not defined. # A new record is added with organization_type not defined.
# This happens from the regular domain request flow. # This happens from the regular domain request flow.
if is_new_instance: if is_new_instance:
# == Check for invalid conditions before proceeding == # # == 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, # Since organization type is linked with generic_org_type and election board,
# we have to update one or the other, not both. # we have to update one or the other, not both.
raise ValueError("Cannot update organization_type and generic_org_type simultaneously.") if instance.organization_type and instance.generic_org_type:
organization_type = str(instance.organization_type)
generic_org_type = str(instance.generic_org_type)
# We can only proceed if all values match (fixtures, load_from_da).
# Otherwise, we're overwriting data so lets forbid this.
if (
"_election" in organization_type != instance.is_election_board or
organization_type != generic_org_type
):
message = (
"Cannot add organization_type and generic_org_type simultaneously "
"when generic_org_type, is_election_board, and organization_type values do not match."
)
raise ValueError(message)
elif not instance.organization_type and not instance.generic_org_type: elif not instance.organization_type and not instance.generic_org_type:
# Do values to update - do nothing # No values to update - do nothing
return None return None
# == Program flow will halt here if there is no reason to update == # # == Program flow will halt here if there is no reason to update == #
# == Update the linked values == # # == Update the linked values == #
# Find out which field needs updating
organization_type_needs_update = instance.organization_type is None organization_type_needs_update = instance.organization_type is None
generic_org_type_needs_update = instance.generic_org_type is None generic_org_type_needs_update = instance.generic_org_type is None
# Update that field # If a field is none, it indicates (per prior checks) that the
# related field (generic org type <-> org type) has data and we should update according to that.
if organization_type_needs_update: if organization_type_needs_update:
_update_org_type_from_generic_org_and_election(instance, invalid_types) _update_org_type_from_generic_org_and_election(instance)
elif generic_org_type_needs_update: elif generic_org_type_needs_update:
_update_generic_org_and_election_from_org_type(instance) _update_generic_org_and_election_from_org_type(instance)
else: else:
# This indicates that all data already matches,
# so we should just do nothing because there is nothing to update.
pass
else:
# == Init variables == #
# Instance is already in the database, fetch its current state # Instance is already in the database, fetch its current state
current_instance = DomainRequest.objects.get(id=instance.id) current_instance = DomainRequest.objects.get(id=instance.id)
@ -77,6 +95,7 @@ def create_or_update_organization_type(sender, instance, **kwargs):
if organization_type_changed and (generic_org_type_changed or is_election_board_changed): 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, # Since organization type is linked with generic_org_type and election board,
# we have to update one or the other, not both. # we have to update one or the other, not both.
# This will not happen in normal flow as it is not possible otherwise.
raise ValueError("Cannot update organization_type and generic_org_type simultaneously.") raise ValueError("Cannot update organization_type and generic_org_type simultaneously.")
elif not organization_type_changed and (not generic_org_type_changed and not is_election_board_changed): elif not organization_type_changed and (not generic_org_type_changed and not is_election_board_changed):
# Do values to update - do nothing # Do values to update - do nothing
@ -90,28 +109,50 @@ def create_or_update_organization_type(sender, instance, **kwargs):
# Update that field # Update that field
if organization_type_needs_update: if organization_type_needs_update:
_update_org_type_from_generic_org_and_election(instance, invalid_types) _update_org_type_from_generic_org_and_election(instance)
elif generic_org_type_needs_update: elif generic_org_type_needs_update:
_update_generic_org_and_election_from_org_type(instance) _update_generic_org_and_election_from_org_type(instance)
def _update_org_type_from_generic_org_and_election(instance, invalid_types): def _update_org_type_from_generic_org_and_election(instance):
# TODO handle if generic_org_type is None """Given a field values for generic_org_type and is_election_board, update the
if instance.generic_org_type not in invalid_types and instance.is_election_board: organization_type field."""
instance.organization_type = f"{instance.generic_org_type}_election"
# We convert to a string because the enum types are different.
generic_org_type = str(instance.generic_org_type)
# For any given organization type, return the "_election" variant.
# For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION
election_org_choices = DomainRequest.OrgChoicesElectionOffice
org_map = election_org_choices.get_org_generic_to_org_election()
# This essentially means: instance.generic_org_type not in invalid_types
if generic_org_type in org_map and instance.is_election_board:
instance.organization_type = org_map[generic_org_type]
else: else:
instance.organization_type = str(instance.generic_org_type) instance.organization_type = generic_org_type
def _update_generic_org_and_election_from_org_type(instance): def _update_generic_org_and_election_from_org_type(instance):
"""Given a value for organization_type, update the """Given the field value for organization_type, update the
generic_org_type and is_election_board values.""" generic_org_type and is_election_board field."""
# TODO find a better solution than this
# We convert to a string because the enum types are different
# between OrgChoicesElectionOffice and OrganizationChoices.
# But their names are the same (for the most part).
current_org_type = str(instance.organization_type) current_org_type = str(instance.organization_type)
if "_election" in current_org_type:
instance.generic_org_type = current_org_type.split("_election")[0] # For any given organization type, return the generic variant.
# For example: STATE_OR_TERRITORY_ELECTION => STATE_OR_TERRITORY
election_org_choices = DomainRequest.OrgChoicesElectionOffice
org_map = election_org_choices.get_org_election_to_org_generic()
# This essentially means: "_election" in current_org_type
if current_org_type in org_map:
new_org = org_map[current_org_type]
instance.generic_org_type = new_org
instance.is_election_board = True instance.is_election_board = True
else: else:
instance.organization_type = str(instance.generic_org_type) instance.generic_org_type = current_org_type
instance.is_election_board = False instance.is_election_board = False
@receiver(post_save, sender=User) @receiver(post_save, sender=User)