Autosave: 20260129-192127

This commit is contained in:
Flatlogic Bot 2026-01-29 19:21:27 +00:00
parent d8bf0cd82c
commit 3ac4dc73fb
11 changed files with 57 additions and 4 deletions

View File

@ -7,7 +7,7 @@ class VoterForm(forms.ModelForm):
fields = [
'first_name', 'last_name', 'nickname', 'birthdate', 'address_street', 'city', 'state', 'prior_state',
'zip_code', 'county', 'latitude', 'longitude',
'phone', 'phone_type', 'email', 'voter_id', 'district', 'precinct',
'phone', 'phone_type', 'secondary_phone', 'secondary_phone_type', 'email', 'voter_id', 'district', 'precinct',
'registration_date', 'is_targeted', 'candidate_support', 'yard_sign', 'window_sticker', 'notes'
]
widgets = {
@ -32,6 +32,7 @@ class VoterForm(forms.ModelForm):
self.fields['yard_sign'].widget.attrs.update({'class': 'form-select'})
self.fields['window_sticker'].widget.attrs.update({'class': 'form-select'})
self.fields['phone_type'].widget.attrs.update({'class': 'form-select'})
self.fields['secondary_phone_type'].widget.attrs.update({'class': 'form-select'})
class AdvancedVoterSearchForm(forms.Form):
MONTH_CHOICES = [

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.7 on 2026-01-29 18:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0026_alter_interaction_date'),
]
operations = [
migrations.AddField(
model_name='voter',
name='secondary_phone',
field=models.CharField(blank=True, max_length=20),
),
migrations.AddField(
model_name='voter',
name='secondary_phone_type',
field=models.CharField(choices=[('home', 'Home Phone'), ('cell', 'Cell Phone'), ('work', 'Work Phone')], default='cell', max_length=10),
),
]

View File

@ -149,6 +149,8 @@ class Voter(models.Model):
longitude = models.DecimalField(max_digits=12, decimal_places=9, null=True, blank=True)
phone = models.CharField(max_length=20, blank=True)
phone_type = models.CharField(max_length=10, choices=PHONE_TYPE_CHOICES, default='cell')
secondary_phone = models.CharField(max_length=20, blank=True)
secondary_phone_type = models.CharField(max_length=10, choices=PHONE_TYPE_CHOICES, default="cell")
email = models.EmailField(blank=True)
district = models.CharField(max_length=100, blank=True, db_index=True)
precinct = models.CharField(max_length=100, blank=True, db_index=True)
@ -222,6 +224,7 @@ class Voter(models.Model):
def save(self, *args, **kwargs):
# Auto-format phone number
self.phone = format_phone_number(self.phone)
self.secondary_phone = format_phone_number(self.secondary_phone)
# Ensure longitude is truncated to 12 characters before saving
if self.longitude:

View File

@ -134,6 +134,12 @@
{% if voter.phone %}
<div class="small text-muted">{{ voter.get_phone_type_display }}</div>
{% endif %}
{% if voter.secondary_phone %}
<div class="mt-2">
{{ voter.secondary_phone }}
<div class="small text-muted">{{ voter.get_secondary_phone_type_display }}</div>
</div>
{% endif %}
</td>
<td>
{% if voter.is_targeted %}

View File

@ -92,6 +92,13 @@
<span class="badge bg-light text-dark border ms-1">{{ voter.get_phone_type_display }}</span>
{% endif %}
</li>
{% if voter.secondary_phone %}
<li class="mb-3">
<label class="small text-muted d-block">Secondary Phone</label>
<span class="fw-semibold">{{ voter.secondary_phone }}</span>
<span class="badge bg-light text-dark border ms-1">{{ voter.get_secondary_phone_type_display }}</span>
</li>
{% endif %}
<li class="mb-3">
<label class="small text-muted d-block">Birthdate</label>
<span class="fw-semibold">{{ voter.birthdate|date:"M d, Y"|default:"N/A" }}</span>
@ -453,6 +460,14 @@
<label class="form-label fw-medium">{{ voter_form.phone_type.label }}</label>
{{ voter_form.phone_type }}
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-medium">{{ voter_form.secondary_phone.label }}</label>
{{ voter_form.secondary_phone }}
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-medium">{{ voter_form.secondary_phone_type.label }}</label>
{{ voter_form.secondary_phone_type }}
</div>
<div class="col-md-12 mb-3">
<label class="form-label fw-medium">{{ voter_form.email.label }}</label>
{{ voter_form.email }}

View File

@ -45,7 +45,12 @@
<div class="small text-muted">{{ voter.address|default:"No address provided" }}</div>
</td>
<td><span class="badge bg-light text-dark border">{{ voter.district|default:"-" }}</span></td>
<td>{{ voter.phone|default:"-" }}</td>
<td>
{{ voter.phone|default:"-" }}
{% if voter.secondary_phone %}
<br><small class="text-muted">{{ voter.secondary_phone }}</small>
{% endif %}
</td>
<td>
{% if voter.is_targeted %}
<span class="badge bg-primary">Yes</span>

View File

@ -531,14 +531,14 @@ def export_voters_csv(request):
writer = csv.writer(response)
writer.writerow([
'Voter ID', 'First Name', 'Last Name', 'Nickname', 'Birthdate',
'Address', 'City', 'State', 'Zip Code', 'Phone', 'Phone Type', 'Email',
'Address', 'City', 'State', 'Zip Code', 'Phone', 'Phone Type', 'Secondary Phone', 'Secondary Phone Type', 'Email',
'District', 'Precinct', 'Is Targeted', 'Support', 'Yard Sign', 'Window Sticker', 'Notes'
])
for voter in voters:
writer.writerow([
voter.voter_id, voter.first_name, voter.last_name, voter.nickname, voter.birthdate,
voter.address, voter.city, voter.state, voter.zip_code, voter.phone, voter.get_phone_type_display(), voter.email,
voter.address, voter.city, voter.state, voter.zip_code, voter.phone, voter.get_phone_type_display(), voter.secondary_phone, voter.get_secondary_phone_type_display(), voter.email,
voter.district, voter.precinct, 'Yes' if voter.is_targeted else 'No',
voter.get_candidate_support_display(), voter.get_yard_sign_display(), voter.get_window_sticker_display(), voter.notes
])