Autosave: 20260206-141042
This commit is contained in:
parent
d244ac9d3f
commit
0d11fc7d5d
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
141
core/admin.py
141
core/admin.py
@ -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)
|
||||
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user