mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-19 02:49:21 +02:00
Refactor
This commit is contained in:
parent
94132bb9a2
commit
58b8e4649d
6 changed files with 224 additions and 84 deletions
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 == #
|
||||||
|
# Since organization type is linked with generic_org_type and election board,
|
||||||
|
# we have to update one or the other, not both.
|
||||||
if instance.organization_type and instance.generic_org_type:
|
if instance.organization_type and instance.generic_org_type:
|
||||||
# Since organization type is linked with generic_org_type and election board,
|
organization_type = str(instance.organization_type)
|
||||||
# we have to update one or the other, not both.
|
generic_org_type = str(instance.generic_org_type)
|
||||||
raise ValueError("Cannot update organization_type and generic_org_type simultaneously.")
|
|
||||||
|
# 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:
|
||||||
|
# This indicates that all data already matches,
|
||||||
|
# so we should just do nothing because there is nothing to update.
|
||||||
|
pass
|
||||||
else:
|
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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue