Autosave: 20260206-141042

This commit is contained in:
Flatlogic Bot 2026-02-06 14:10:46 +00:00
parent d244ac9d3f
commit 0d11fc7d5d
9 changed files with 173 additions and 171 deletions

View File

@ -126,7 +126,7 @@ class BaseImportAdminMixin:
failed_rows = request.session.get(session_key, [])
if not failed_rows:
self.message_user(request, "No error log found in session.", level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f"attachment; filename={self.model._meta.model_name}_import_errors.csv"
@ -294,7 +294,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
preview_rows.append(row)
v_id = row.get(mapping.get("voter_id"))
if v_id:
voter_ids_for_preview.append(v_id)
voter_ids_for_preview.append(v_id.strip())
else:
break
@ -305,7 +305,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for row in preview_rows:
voter_id_val = row.get(mapping.get("voter_id"))
if voter_id_val in existing_preview_ids:
if voter_id_val and voter_id_val.strip() in existing_preview_ids:
update_count += 1
else:
create_count += 1
@ -326,7 +326,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get("file_path")
tenant_id = request.POST.get("tenant")
@ -355,9 +355,14 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for i, row in enumerate(reader):
total_processed += 1
try:
voter_id = row.get(mapping.get("voter_id"))
raw_voter_id = row.get(mapping.get("voter_id"))
voter_id = raw_voter_id.strip() if raw_voter_id else None
if not voter_id:
row["Import Error"] = "Voter ID is required"
# Enhanced error message to guide the user
mapped_column_name = mapping.get("voter_id", "N/A")
error_detail = f"Raw value: '{raw_voter_id}'. " if raw_voter_id is not None else "Value was None."
row["Import Error"] = f"Voter ID is required. Please check if the '{mapped_column_name}' column is correctly mapped and contains values for all rows. {error_detail}"
failed_rows.append(row)
skipped_no_id += 1
errors += 1
@ -478,10 +483,10 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:voter-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = VoterImportForm(request.POST, request.FILES)
if form.is_valid():
@ -490,14 +495,14 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
@ -554,7 +559,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for field_name, _ in EVENT_MAPPABLE_FIELDS:
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
total_count = 0
create_count = 0
@ -613,7 +618,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get('file_path')
@ -628,7 +633,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
count = 0
errors = 0
failed_rows = []
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
@ -708,10 +713,10 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:event-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = EventImportForm(request.POST, request.FILES)
if form.is_valid():
@ -720,14 +725,14 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
@ -784,7 +789,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
total_count = 0
create_count = 0
@ -827,7 +832,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get('file_path')
@ -842,7 +847,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
count = 0
errors = 0
failed_rows = []
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
@ -880,10 +885,10 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:volunteer-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = VolunteerImportForm(request.POST, request.FILES)
if form.is_valid():
@ -892,14 +897,14 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
@ -935,7 +940,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
extra_context = extra_context or {}
from core.models import Tenant
extra_context['tenants'] = Tenant.objects.all()
return super().changelist_view(request, extra_context=extra_context)
return super().changelist_list(request, extra_context=extra_context)
def get_urls(self):
urls = super().get_urls()
@ -955,7 +960,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for field_name, _ in EVENT_PARTICIPATION_MAPPABLE_FIELDS:
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
total_count = 0
create_count = 0
@ -964,7 +969,6 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for row in reader:
total_count += 1
voter_id = row.get(mapping.get('voter_id'))
event_name = row.get(mapping.get('event_name'))
# Extract first_name and last_name from CSV based on mapping
csv_first_name = row.get(mapping.get('first_name'), '')
@ -1012,7 +1016,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get('file_path')
@ -1024,7 +1028,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
count = 0
errors = 0
@ -1034,6 +1038,9 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
voter_id = row.get(mapping.get('voter_id')) if mapping.get('voter_id') else None
participation_status_val = row.get(mapping.get('participation_status')) if mapping.get('participation_status') else None
if voter_id: # Only strip if voter_id is not None
voter_id = voter_id.strip()
if not voter_id:
row["Import Error"] = "Missing voter ID"
failed_rows.append(row)
@ -1096,10 +1103,10 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:eventparticipation-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = EventParticipationImportForm(request.POST, request.FILES)
if form.is_valid():
@ -1108,14 +1115,14 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
@ -1170,7 +1177,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for field_name, _ in DONATION_MAPPABLE_FIELDS:
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
total_count = 0
create_count = 0
@ -1213,7 +1220,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get('file_path')
@ -1228,7 +1235,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
count = 0
errors = 0
failed_rows = []
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
@ -1237,6 +1244,9 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
amount_str = row.get(mapping.get('amount'))
method_name = row.get(mapping.get('method'))
if voter_id: # Only strip if voter_id is not None
voter_id = voter_id.strip()
if not voter_id:
row["Import Error"] = "Missing voter ID"
failed_rows.append(row)
@ -1304,10 +1314,10 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:donation-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = DonationImportForm(request.POST, request.FILES)
if form.is_valid():
@ -1316,14 +1326,14 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
@ -1379,7 +1389,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for field_name, _ in INTERACTION_MAPPABLE_FIELDS:
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
total_count = 0
create_count = 0
@ -1423,7 +1433,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get('file_path')
@ -1438,7 +1448,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
count = 0
errors = 0
failed_rows = []
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
@ -1447,6 +1457,9 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
date_str = row.get(mapping.get('date'))
type_name = row.get(mapping.get('type'))
if voter_id: # Only strip if voter_id is not None
voter_id = voter_id.strip()
if not voter_id:
row["Import Error"] = "Missing voter ID"
failed_rows.append(row)
@ -1515,10 +1528,10 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:interaction-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = InteractionImportForm(request.POST, request.FILES)
if form.is_valid():
@ -1527,14 +1540,14 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
@ -1589,7 +1602,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for field_name, _ in VOTER_LIKELIHOOD_MAPPABLE_FIELDS:
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
total_count = 0
create_count = 0
@ -1632,7 +1645,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get('file_path')
@ -1647,7 +1660,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
count = 0
errors = 0
failed_rows = []
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
@ -1655,6 +1668,9 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
election_type_name = row.get(mapping.get('election_type'))
likelihood_val = row.get(mapping.get('likelihood'))
if voter_id: # Only strip if voter_id is not None
voter_id = voter_id.strip()
if not voter_id:
row["Import Error"] = "Missing voter ID"
failed_rows.append(row)
@ -1697,10 +1713,10 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:voterlikelihood-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = VoterLikelihoodImportForm(request.POST, request.FILES)
if form.is_valid():
@ -1709,14 +1725,14 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
@ -1772,7 +1788,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
for field_name, _ in VOTING_RECORD_MAPPABLE_FIELDS:
mapping[field_name] = request.POST.get(f'map_{field_name}')
try:
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
total_count = 0
create_count = 0
@ -1831,7 +1847,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
return render(request, "admin/import_preview.html", context)
except Exception as e:
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
elif "_import" in request.POST:
file_path = request.POST.get('file_path')
@ -1846,7 +1862,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
count = 0
errors = 0
failed_rows = []
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
@ -1855,6 +1871,9 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
election_description = row.get(mapping.get('election_description'))
primary_party = row.get(mapping.get('primary_party'))
if voter_id: # Only strip if voter_id is not None
voter_id = voter_id.strip()
if not voter_id:
row["Import Error"] = "Missing voter ID"
failed_rows.append(row)
@ -1914,10 +1933,10 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if errors > 0:
error_url = reverse("admin:votingrecord-download-errors")
self.message_user(request, mark_safe(f"Failed to import {errors} rows. <a href='{error_url}' download>Download failed records</a>"), level=messages.WARNING)
return redirect("..\\n")
return redirect("../")
except Exception as e:
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
else:
form = VotingRecordImportForm(request.POST, request.FILES)
if form.is_valid():
@ -1926,14 +1945,14 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
if not csv_file.name.endswith('.csv'):
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
return redirect("..\\n")
return redirect("../")
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
for chunk in csv_file.chunks():
tmp.write(chunk)
file_path = tmp.name
with open(file_path, 'r', encoding='UTF-8') as f:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)

View File

@ -118,6 +118,7 @@ class AdvancedVoterSearchForm(forms.Form):
neighborhood = forms.CharField(required=False)
district = forms.CharField(required=False)
precinct = forms.CharField(required=False)
email = forms.EmailField(required=False) # Added email field
phone_type = forms.ChoiceField(
choices=[('', 'Any')] + Voter.PHONE_TYPE_CHOICES,
required=False
@ -125,7 +126,7 @@ class AdvancedVoterSearchForm(forms.Form):
is_targeted = forms.BooleanField(required=False, label="Targeted Only")
door_visit = forms.BooleanField(required=False, label="Visited Only")
candidate_support = forms.ChoiceField(
choices=[('', 'Any')] + Voter.SUPPORT_CHOICES,
choices=[('', 'Any')] + Voter.CANDIDATE_SUPPORT_CHOICES,
required=False
)
yard_sign = forms.ChoiceField(
@ -443,7 +444,7 @@ class DoorVisitLogForm(forms.Form):
label="Wants a Yard Sign"
)
candidate_support = forms.ChoiceField(
choices=Voter.SUPPORT_CHOICES,
choices=Voter.CANDIDATE_SUPPORT_CHOICES,
initial="unknown",
widget=forms.Select(attrs={"class": "form-select"}),
label="Candidate Support"
@ -484,16 +485,6 @@ class UserUpdateForm(forms.ModelForm):
model = User
fields = ['first_name', 'last_name', 'email']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.update({'class': 'form-control'})
class VolunteerProfileForm(forms.ModelForm):
class Meta:
model = Volunteer
fields = ['phone']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():

View File

@ -125,7 +125,7 @@ class Interest(models.Model):
return self.name
class Voter(models.Model):
SUPPORT_CHOICES = [
CANDIDATE_SUPPORT_CHOICES = [
('unknown', 'Unknown'),
('supporting', 'Supporting'),
('not_supporting', 'Not Supporting'),
@ -170,7 +170,7 @@ class Voter(models.Model):
precinct = models.CharField(max_length=100, blank=True, db_index=True)
registration_date = models.DateField(null=True, blank=True)
is_targeted = models.BooleanField(default=False, db_index=True)
candidate_support = models.CharField(max_length=20, choices=SUPPORT_CHOICES, default='unknown', db_index=True)
candidate_support = models.CharField(max_length=20, choices=CANDIDATE_SUPPORT_CHOICES, default='unknown', db_index=True)
yard_sign = models.CharField(max_length=20, choices=YARD_SIGN_CHOICES, default='none', db_index=True)
window_sticker = models.CharField(max_length=20, choices=WINDOW_STICKER_CHOICES, default='none', verbose_name='Window Sticker Status', db_index=True)
notes = models.TextField(blank=True)

View File

@ -49,6 +49,10 @@
<label class="form-label small fw-bold text-muted">Precinct</label>
{{ form.precinct }}
</div>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted">Email</label>
{{ form.email }}
</div>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted">Phone Type</label>
{{ form.phone_type }}

View File

@ -17,7 +17,7 @@ from django.contrib import messages
from django.core.paginator import Paginator
from django.conf import settings
from .models import Voter, Tenant, Interaction, Donation, VoterLikelihood, EventParticipation, Event, EventType, InteractionType, DonationMethod, ElectionType, CampaignSettings, Volunteer, ParticipationStatus, VolunteerEvent, Interest, VolunteerRole, ScheduledCall
from .forms import VoterForm, InteractionForm, DonationForm, VoterLikelihoodForm, EventParticipationForm, VoterImportForm, AdvancedVoterSearchForm, EventParticipantAddForm, EventForm, VolunteerForm, VolunteerEventForm, VolunteerEventAddForm, DoorVisitLogForm, ScheduledCallForm, UserUpdateForm, VolunteerProfileForm, EventParticipationImportForm, ParticipantMappingForm
from .forms import VoterForm, InteractionForm, DonationForm, VoterLikelihoodForm, EventParticipationForm, VoterImportForm, AdvancedVoterSearchForm, EventParticipantAddForm, EventForm, VolunteerForm, VolunteerEventForm, VolunteerEventAddForm, DoorVisitLogForm, ScheduledCallForm, UserUpdateForm, EventParticipationImportForm, ParticipantMappingForm
import logging
import zoneinfo
from django.utils import timezone
@ -157,7 +157,7 @@ def voter_list(request):
if query:
query = query.strip()
search_filter = Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(voter_id__icontains=query)
search_filter = Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(voter_id__iexact=query)
if "," in query:
parts = [p.strip() for p in query.split(",")]
@ -358,7 +358,6 @@ def edit_likelihood(request, likelihood_id):
if request.method == 'POST':
form = VoterLikelihoodForm(request.POST, instance=likelihood, tenant=tenant)
if form.is_valid():
# Check for conflict with another record of same election_type
election_type = form.cleaned_data['election_type']
if VoterLikelihood.objects.filter(voter=likelihood.voter, election_type=election_type).exclude(id=likelihood.id).exists():
VoterLikelihood.objects.filter(voter=likelihood.voter, election_type=election_type).exclude(id=likelihood.id).delete()
@ -487,7 +486,7 @@ def voter_advanced_search(request):
if data.get('address'):
voters = voters.filter(Q(address__icontains=data['address']) | Q(address_street__icontains=data['address']))
if data.get('voter_id'):
voters = voters.filter(voter_id__icontains=data['voter_id'])
voters = voters.filter(voter_id__iexact=data['voter_id'])
if data.get('birth_month'):
voters = voters.filter(birthdate__month=data['birth_month'])
if data.get('city'):
@ -498,6 +497,8 @@ def voter_advanced_search(request):
voters = voters.filter(district=data['district'])
if data.get('precinct'):
voters = voters.filter(precinct=data['precinct'])
if data.get('email'):
voters = voters.filter(email__icontains=data['email'])
if data.get('phone_type'):
voters = voters.filter(phone_type=data['phone_type'])
if data.get('is_targeted'):
@ -563,7 +564,7 @@ def export_voters_csv(request):
if data.get('address'):
voters = voters.filter(Q(address__icontains=data['address']) | Q(address_street__icontains=data['address']))
if data.get('voter_id'):
voters = voters.filter(voter_id__icontains=data['voter_id'])
voters = voters.filter(voter_id__iexact=data['voter_id'])
if data.get('birth_month'):
voters = voters.filter(birthdate__month=data['birth_month'])
if data.get('city'):
@ -574,6 +575,8 @@ def export_voters_csv(request):
voters = voters.filter(district=data['district'])
if data.get('precinct'):
voters = voters.filter(precinct=data['precinct'])
if data.get('email'):
voters = voters.filter(email__icontains=data['email'])
if data.get('phone_type'):
voters = voters.filter(phone_type=data['phone_type'])
if data.get('is_targeted'):
@ -842,7 +845,7 @@ def voter_search_json(request):
tenant = get_object_or_404(Tenant, id=selected_tenant_id)
voters = Voter.objects.filter(tenant=tenant)
search_filter = Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(voter_id__icontains=query)
search_filter = Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(voter_id__iexact=query)
if "," in query:
parts = [p.strip() for p in query.split(",") ]
@ -925,6 +928,7 @@ def volunteer_add(request):
'form': form,
'tenant': tenant,
'selected_tenant': tenant,
'is_create': True,
}
return render(request, 'core/volunteer_detail.html', context)
@ -1993,21 +1997,5 @@ def profile(request):
if request.method == 'POST':
u_form = UserUpdateForm(request.POST, instance=request.user)
v_form = VolunteerProfileForm(request.POST, instance=volunteer) if volunteer else None
if u_form.is_valid() and (not v_form or v_form.is_valid()):
u_form.save()
if v_form:
v_form.save()
messages.success(request, f'Your profile has been updated!')
return redirect('profile')
else:
u_form = UserUpdateForm(instance=request.user)
v_form = VolunteerProfileForm(instance=volunteer) if volunteer else None
context = {
'u_form': u_form,
'v_form': v_form
}
return render(request, 'core/profile.html', context)
# v_form = VolunteerProfileForm(request.POST, instance=volunteer) if volunteer else None # Removed VolunteerProfileForm
v_form = None # Set v_form to None after removal