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 = [
|
fields = [
|
||||||
'first_name', 'last_name', 'nickname', 'birthdate', 'address_street', 'city', 'state', 'prior_state',
|
'first_name', 'last_name', 'nickname', 'birthdate', 'address_street', 'city', 'state', 'prior_state',
|
||||||
'zip_code', 'county', 'latitude', 'longitude',
|
'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'
|
'registration_date', 'is_targeted', 'candidate_support', 'yard_sign', 'window_sticker', 'notes'
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
@ -32,6 +32,7 @@ class VoterForm(forms.ModelForm):
|
|||||||
self.fields['yard_sign'].widget.attrs.update({'class': 'form-select'})
|
self.fields['yard_sign'].widget.attrs.update({'class': 'form-select'})
|
||||||
self.fields['window_sticker'].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['phone_type'].widget.attrs.update({'class': 'form-select'})
|
||||||
|
self.fields['secondary_phone_type'].widget.attrs.update({'class': 'form-select'})
|
||||||
|
|
||||||
class AdvancedVoterSearchForm(forms.Form):
|
class AdvancedVoterSearchForm(forms.Form):
|
||||||
MONTH_CHOICES = [
|
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)
|
longitude = models.DecimalField(max_digits=12, decimal_places=9, null=True, blank=True)
|
||||||
phone = models.CharField(max_length=20, blank=True)
|
phone = models.CharField(max_length=20, blank=True)
|
||||||
phone_type = models.CharField(max_length=10, choices=PHONE_TYPE_CHOICES, default='cell')
|
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)
|
email = models.EmailField(blank=True)
|
||||||
district = models.CharField(max_length=100, blank=True, db_index=True)
|
district = models.CharField(max_length=100, blank=True, db_index=True)
|
||||||
precinct = 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):
|
def save(self, *args, **kwargs):
|
||||||
# Auto-format phone number
|
# Auto-format phone number
|
||||||
self.phone = format_phone_number(self.phone)
|
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
|
# Ensure longitude is truncated to 12 characters before saving
|
||||||
if self.longitude:
|
if self.longitude:
|
||||||
|
|||||||
@ -134,6 +134,12 @@
|
|||||||
{% if voter.phone %}
|
{% if voter.phone %}
|
||||||
<div class="small text-muted">{{ voter.get_phone_type_display }}</div>
|
<div class="small text-muted">{{ voter.get_phone_type_display }}</div>
|
||||||
{% endif %}
|
{% 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>
|
||||||
<td>
|
<td>
|
||||||
{% if voter.is_targeted %}
|
{% if voter.is_targeted %}
|
||||||
|
|||||||
@ -92,6 +92,13 @@
|
|||||||
<span class="badge bg-light text-dark border ms-1">{{ voter.get_phone_type_display }}</span>
|
<span class="badge bg-light text-dark border ms-1">{{ voter.get_phone_type_display }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</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">
|
<li class="mb-3">
|
||||||
<label class="small text-muted d-block">Birthdate</label>
|
<label class="small text-muted d-block">Birthdate</label>
|
||||||
<span class="fw-semibold">{{ voter.birthdate|date:"M d, Y"|default:"N/A" }}</span>
|
<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>
|
<label class="form-label fw-medium">{{ voter_form.phone_type.label }}</label>
|
||||||
{{ voter_form.phone_type }}
|
{{ voter_form.phone_type }}
|
||||||
</div>
|
</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">
|
<div class="col-md-12 mb-3">
|
||||||
<label class="form-label fw-medium">{{ voter_form.email.label }}</label>
|
<label class="form-label fw-medium">{{ voter_form.email.label }}</label>
|
||||||
{{ voter_form.email }}
|
{{ voter_form.email }}
|
||||||
|
|||||||
@ -45,7 +45,12 @@
|
|||||||
<div class="small text-muted">{{ voter.address|default:"No address provided" }}</div>
|
<div class="small text-muted">{{ voter.address|default:"No address provided" }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="badge bg-light text-dark border">{{ voter.district|default:"-" }}</span></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>
|
<td>
|
||||||
{% if voter.is_targeted %}
|
{% if voter.is_targeted %}
|
||||||
<span class="badge bg-primary">Yes</span>
|
<span class="badge bg-primary">Yes</span>
|
||||||
|
|||||||
@ -531,14 +531,14 @@ def export_voters_csv(request):
|
|||||||
writer = csv.writer(response)
|
writer = csv.writer(response)
|
||||||
writer.writerow([
|
writer.writerow([
|
||||||
'Voter ID', 'First Name', 'Last Name', 'Nickname', 'Birthdate',
|
'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'
|
'District', 'Precinct', 'Is Targeted', 'Support', 'Yard Sign', 'Window Sticker', 'Notes'
|
||||||
])
|
])
|
||||||
|
|
||||||
for voter in voters:
|
for voter in voters:
|
||||||
writer.writerow([
|
writer.writerow([
|
||||||
voter.voter_id, voter.first_name, voter.last_name, voter.nickname, voter.birthdate,
|
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.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
|
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