Autosave: 20260129-192127
This commit is contained in:
parent
d8bf0cd82c
commit
3ac4dc73fb
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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 = [
|
||||
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -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:
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user