Skip to content

Commit

Permalink
Adjust models and import scripts to allow candidates to be merged (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
fgregg authored May 4, 2024
1 parent 82cde5f commit 7312c52
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 74 deletions.
2 changes: 2 additions & 0 deletions camp_fin/api_parts.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class TransactionSerializer(serializers.ModelSerializer):
transaction_type = serializers.StringRelatedField(read_only=True)
full_name = serializers.StringRelatedField(read_only=True)
transaction_subject = EntityField(read_only=True)
donor_name = serializers.StringRelatedField(read_only=True)

class Meta:
model = Transaction
Expand All @@ -80,6 +81,7 @@ class Meta:
"description",
"transaction_type",
"name_prefix",
"donor_name",
"first_name",
"middle_name",
"last_name",
Expand Down
15 changes: 14 additions & 1 deletion camp_fin/management/commands/import_candidate_filings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,20 @@ def _create_filing(self, record):
filing_period_type=filing_type,
)

entity = models.Entity.objects.get(user_id=record["StateID"])
# Lots of view code depends on filings being attached to the
# candidate, while it would be more logical to have the filings be
# attached to the candidate's *committee*.
#
# But, we bend our head to practicality and associate the
# filing with the entity representing the candidate.

entity = (
models.Entity.objects.filter(
candidate__campaign__committee__entity__user_id=record["StateID"]
)
.distinct()
.get()
)

url = f"https://login.cfis.sos.state.nm.us//ReportsOutput//{record['ReportFileName']}"
final = record["Amended"] == "0" or None
Expand Down
91 changes: 66 additions & 25 deletions camp_fin/management/commands/import_candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re

from django.core.management.base import BaseCommand
from django.db.models import Q
from django.utils.crypto import get_random_string
from django.utils.text import slugify
from tqdm import tqdm
Expand Down Expand Up @@ -37,27 +38,24 @@ def handle(self, *args, **options):
for record in tqdm(reader):

try:
candidate = models.Candidate.objects.get(
entity__user_id=record["StateID"]
pac = models.PAC.objects.get(entity__user_id=record["StateID"])
except models.PAC.DoesNotExist:
entity_type, _ = models.EntityType.objects.get_or_create(
description="Candidate Committee",
)
candidates_linked += 1

except models.Candidate.DoesNotExist:
candidate_type, _ = models.EntityType.objects.get_or_create(
description="Candidate"
)
person = models.Entity.objects.create(
entity_type=candidate_type,
user_id=record["StateID"],
entity = models.Entity.objects.create(
entity_type=entity_type, user_id=record["StateID"]
)

candidate = models.Candidate.objects.create(
full_name=record["CandidateName"],
slug=f'{slugify(record["CandidateName"])}-{get_random_string(5)}',
entity=person,
)
committee_name = re.split(
"<br>", record["PoliticalPartyCommitteeName"]
)[0].strip(", ")

candidates_created += 1
pac = models.PAC.objects.create(
name=committee_name,
slug=f"{slugify(committee_name)}-{get_random_string(5)}",
entity=entity,
)

election_year = re.match(r"\d{4}", record["ElectionName"]).group(0)

Expand Down Expand Up @@ -92,14 +90,57 @@ def handle(self, *args, **options):
else:
county = None

campaign, created = models.Campaign.objects.get_or_create(
election_season=election_season,
candidate=candidate,
office=office,
district=district,
county=county,
political_party=political_party,
)
try:
campaign = models.Campaign.objects.get(
election_season=election_season,
committee=pac,
office=office,
district=district,
county=county,
political_party=political_party,
)
except models.Campaign.DoesNotExist:
try:
candidate = (
models.Candidate.objects.filter(
Q(campaign__in=pac.campaigns.all())
| Q(
email__iexact=record["CandidateEmail"],
business_phone=record["PublicPhoneNumber"],
)
)
.distinct()
.get()
)
candidates_linked += 1
except models.Candidate.DoesNotExist:

candidate_type, _ = models.EntityType.objects.get_or_create(
description="Candidate"
)
person = models.Entity.objects.create(
entity_type=candidate_type,
)

candidate = models.Candidate.objects.create(
full_name=record["CandidateName"],
slug=f'{slugify(record["CandidateName"])}-{get_random_string(5)}',
entity=person,
email=record["CandidateEmail"],
business_phone=record["PublicPhoneNumber"],
)

candidates_created += 1

campaign = models.Campaign.objects.create(
election_season=election_season,
candidate=candidate,
committee=pac,
office=office,
district=district,
county=county,
political_party=political_party,
)

campaign.sos_link = "https://login.cfis.sos.state.nm.us/#/exploreDetails/{id}/{office_id}/{district_id}/{election_id}/{election_year}".format( # noqa
id=record["IDNumber"],
Expand Down
24 changes: 20 additions & 4 deletions camp_fin/management/commands/import_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,22 @@ def _get_filing(self, record):
state_id = record["OrgID"]

try:
entity = models.Entity.objects.get(user_id=state_id)
pac = models.PAC.objects.get(entity__user_id=state_id)
except models.PAC.DoesNotExist:
pac = models.PAC.objects.get(name=record["Committee Name"])
pac.entity.user_id = state_id
pac.entity.save()

try:
# candidate entity
entity = (
models.Entity.objects.filter(candidate__campaign__committee=pac)
.distinct()
.get()
)
except models.Entity.DoesNotExist:
entity = models.PAC.objects.get(name=record["Committee Name"]).entity
entity.user_id = state_id
entity.save()
# committee entity
entity = pac.entity

# We want to associate the transactions with the final filing
# for a reporting period
Expand All @@ -234,6 +245,11 @@ def _get_filing(self, record):
entity=entity,
)

# the same person can have multiple canidate committees, so we
# need to disambiguate which one this filing is for
if entity.entity_type.description == "Candidate":
filings = filings.filter(campaign__committee=pac)

try:
filing = filings.get()
except models.Filing.DoesNotExist:
Expand Down
49 changes: 49 additions & 0 deletions camp_fin/migrations/0089_auto_20240502_0741.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 3.2.25 on 2024-05-02 13:41

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("camp_fin", "0088_auto_20240425_1018"),
]

operations = [
migrations.RemoveField(
model_name="campaign",
name="committee_address",
),
migrations.RemoveField(
model_name="campaign",
name="committee_email",
),
migrations.RemoveField(
model_name="campaign",
name="committee_fax_number",
),
migrations.RemoveField(
model_name="campaign",
name="committee_name",
),
migrations.RemoveField(
model_name="campaign",
name="committee_phone_1",
),
migrations.RemoveField(
model_name="campaign",
name="committee_phone_2",
),
migrations.AddField(
model_name="campaign",
name="committee",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
related_name="campaigns",
to="camp_fin.pac",
),
preserve_default=False,
),
]
17 changes: 5 additions & 12 deletions camp_fin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,10 @@ class Campaign(models.Model):
null=True,
on_delete=models.CASCADE,
)
committee_name = models.CharField(max_length=100, null=True)
committee_phone_1 = models.CharField(max_length=25, null=True)
committee_phone_2 = models.CharField(max_length=25, null=True)
committee_fax_number = models.CharField(max_length=25, null=True)
committee_email = models.CharField(max_length=255, null=True)
committee_address = models.ForeignKey(
"Address",
related_name="committee_address",
null=True,
on_delete=models.CASCADE,
committee = models.ForeignKey(
"PAC", related_name="campaigns", on_delete=models.CASCADE
)

initial_balance = models.FloatField(null=True)
initial_balance_from_self = models.BooleanField(null=True)
initial_debt = models.FloatField(null=True)
Expand All @@ -142,8 +135,8 @@ def __str__(self):
self.candidate.first_name, self.candidate.last_name
)
return "{0} ({1})".format(candidate_name, office)
elif self.committee_name:
return "{0} ({1})".format(self.committee_name, office)
elif self.committee.name:
return "{0} ({1})".format(self.committee.name, office)
else:
party = self.political_party.name
return "{0} ({1})".format(party, office)
Expand Down
6 changes: 3 additions & 3 deletions camp_fin/templates/camp_fin/candidate-detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ <h3><i class="fa fa-fw fa-history"></i> Past campaigns</h3>
{% endif %}
</td>
<td>
{% if campaign.committee_name %}
<a href="{% url 'search' %}?term={{ campaign.committee_name|urlencode }}">
{{ campaign.committee_name }}
{% if campaign.committee.name %}
<a href="{% url 'search' %}?term={{ campaign.committee.name|urlencode }}">
{{ campaign.committee.name }}
</a>
{% endif %}
</td>
Expand Down
8 changes: 8 additions & 0 deletions camp_fin/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.test import TestCase, TransactionTestCase

from camp_fin.models import (
PAC,
Campaign,
Candidate,
County,
Expand Down Expand Up @@ -39,6 +40,9 @@ def races(cls):
cls.second_entity = Entity.objects.create(user_id=2)
cls.third_entity = Entity.objects.create(user_id=3)
cls.fourth_entity = Entity.objects.create(user_id=4)
cls.fifth_entity = Entity.objects.create(user_id=4)

cls.some_pac = PAC.objects.create(entity=cls.fifth_entity)

first_party = PoliticalParty.objects.create(name="Democrat")
second_party = PoliticalParty.objects.create(name="Republican")
Expand Down Expand Up @@ -108,6 +112,7 @@ def races(cls):
office=cls.office,
date_added=datetime.datetime.now(pytz.utc),
political_party=first_party,
committee=cls.some_pac,
)

cls.second_campaign = Campaign.objects.create(
Expand All @@ -117,6 +122,7 @@ def races(cls):
office=cls.office,
date_added=datetime.datetime.now(pytz.utc),
political_party=second_party,
committee=cls.some_pac,
)

cls.third_campaign = Campaign.objects.create(
Expand All @@ -126,6 +132,7 @@ def races(cls):
office=cls.office,
date_added=datetime.datetime.now(pytz.utc),
political_party=second_party,
committee=cls.some_pac,
)

cls.non_race_campaign = Campaign.objects.create(
Expand All @@ -134,6 +141,7 @@ def races(cls):
office=cls.office,
date_added=datetime.datetime.now(pytz.utc),
political_party=third_party,
committee=cls.some_pac,
)

cls.campaigns = [cls.first_campaign, cls.second_campaign, cls.third_campaign]
Expand Down
Loading

0 comments on commit 7312c52

Please sign in to comment.