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, [])
|
failed_rows = request.session.get(session_key, [])
|
||||||
if not failed_rows:
|
if not failed_rows:
|
||||||
self.message_user(request, "No error log found in session.", level=messages.WARNING)
|
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 = HttpResponse(content_type="text/csv")
|
||||||
response["Content-Disposition"] = f"attachment; filename={self.model._meta.model_name}_import_errors.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)
|
preview_rows.append(row)
|
||||||
v_id = row.get(mapping.get("voter_id"))
|
v_id = row.get(mapping.get("voter_id"))
|
||||||
if v_id:
|
if v_id:
|
||||||
voter_ids_for_preview.append(v_id)
|
voter_ids_for_preview.append(v_id.strip())
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -305,7 +305,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
for row in preview_rows:
|
for row in preview_rows:
|
||||||
voter_id_val = row.get(mapping.get("voter_id"))
|
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
|
update_count += 1
|
||||||
else:
|
else:
|
||||||
create_count += 1
|
create_count += 1
|
||||||
@ -326,7 +326,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
return render(request, "admin/import_preview.html", context)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get("file_path")
|
file_path = request.POST.get("file_path")
|
||||||
tenant_id = request.POST.get("tenant")
|
tenant_id = request.POST.get("tenant")
|
||||||
@ -355,9 +355,14 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for i, row in enumerate(reader):
|
for i, row in enumerate(reader):
|
||||||
total_processed += 1
|
total_processed += 1
|
||||||
try:
|
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:
|
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)
|
failed_rows.append(row)
|
||||||
skipped_no_id += 1
|
skipped_no_id += 1
|
||||||
errors += 1
|
errors += 1
|
||||||
@ -478,10 +483,10 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:voter-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = VoterImportForm(request.POST, request.FILES)
|
form = VoterImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -490,14 +495,14 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
@ -554,7 +559,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for field_name, _ in EVENT_MAPPABLE_FIELDS:
|
for field_name, _ in EVENT_MAPPABLE_FIELDS:
|
||||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
total_count = 0
|
total_count = 0
|
||||||
create_count = 0
|
create_count = 0
|
||||||
@ -613,7 +618,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
return render(request, "admin/import_preview.html", context)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
|
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get('file_path')
|
file_path = request.POST.get('file_path')
|
||||||
@ -628,7 +633,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
count = 0
|
count = 0
|
||||||
errors = 0
|
errors = 0
|
||||||
failed_rows = []
|
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)
|
reader = csv.DictReader(f)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
try:
|
try:
|
||||||
@ -708,10 +713,10 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:event-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = EventImportForm(request.POST, request.FILES)
|
form = EventImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -720,14 +725,14 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
@ -784,7 +789,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
|
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
total_count = 0
|
total_count = 0
|
||||||
create_count = 0
|
create_count = 0
|
||||||
@ -827,7 +832,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
return render(request, "admin/import_preview.html", context)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
|
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get('file_path')
|
file_path = request.POST.get('file_path')
|
||||||
@ -842,7 +847,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
count = 0
|
count = 0
|
||||||
errors = 0
|
errors = 0
|
||||||
failed_rows = []
|
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)
|
reader = csv.DictReader(f)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
try:
|
try:
|
||||||
@ -880,10 +885,10 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:volunteer-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = VolunteerImportForm(request.POST, request.FILES)
|
form = VolunteerImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -892,14 +897,14 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
@ -935,7 +940,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
extra_context = extra_context or {}
|
extra_context = extra_context or {}
|
||||||
from core.models import Tenant
|
from core.models import Tenant
|
||||||
extra_context['tenants'] = Tenant.objects.all()
|
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):
|
def get_urls(self):
|
||||||
urls = super().get_urls()
|
urls = super().get_urls()
|
||||||
@ -955,7 +960,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for field_name, _ in EVENT_PARTICIPATION_MAPPABLE_FIELDS:
|
for field_name, _ in EVENT_PARTICIPATION_MAPPABLE_FIELDS:
|
||||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
total_count = 0
|
total_count = 0
|
||||||
create_count = 0
|
create_count = 0
|
||||||
@ -964,7 +969,6 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for row in reader:
|
for row in reader:
|
||||||
total_count += 1
|
total_count += 1
|
||||||
voter_id = row.get(mapping.get('voter_id'))
|
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
|
# Extract first_name and last_name from CSV based on mapping
|
||||||
csv_first_name = row.get(mapping.get('first_name'), '')
|
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)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
|
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get('file_path')
|
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}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
|
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
count = 0
|
count = 0
|
||||||
errors = 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
|
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
|
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:
|
if not voter_id:
|
||||||
row["Import Error"] = "Missing voter ID"
|
row["Import Error"] = "Missing voter ID"
|
||||||
failed_rows.append(row)
|
failed_rows.append(row)
|
||||||
@ -1096,10 +1103,10 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:eventparticipation-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = EventParticipationImportForm(request.POST, request.FILES)
|
form = EventParticipationImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -1108,14 +1115,14 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
@ -1170,7 +1177,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for field_name, _ in DONATION_MAPPABLE_FIELDS:
|
for field_name, _ in DONATION_MAPPABLE_FIELDS:
|
||||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
total_count = 0
|
total_count = 0
|
||||||
create_count = 0
|
create_count = 0
|
||||||
@ -1213,7 +1220,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
return render(request, "admin/import_preview.html", context)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
|
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get('file_path')
|
file_path = request.POST.get('file_path')
|
||||||
@ -1228,7 +1235,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
count = 0
|
count = 0
|
||||||
errors = 0
|
errors = 0
|
||||||
failed_rows = []
|
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)
|
reader = csv.DictReader(f)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
try:
|
try:
|
||||||
@ -1237,6 +1244,9 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
amount_str = row.get(mapping.get('amount'))
|
amount_str = row.get(mapping.get('amount'))
|
||||||
method_name = row.get(mapping.get('method'))
|
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:
|
if not voter_id:
|
||||||
row["Import Error"] = "Missing voter ID"
|
row["Import Error"] = "Missing voter ID"
|
||||||
failed_rows.append(row)
|
failed_rows.append(row)
|
||||||
@ -1304,10 +1314,10 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:donation-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = DonationImportForm(request.POST, request.FILES)
|
form = DonationImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -1316,14 +1326,14 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
@ -1379,7 +1389,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for field_name, _ in INTERACTION_MAPPABLE_FIELDS:
|
for field_name, _ in INTERACTION_MAPPABLE_FIELDS:
|
||||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
total_count = 0
|
total_count = 0
|
||||||
create_count = 0
|
create_count = 0
|
||||||
@ -1423,7 +1433,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
return render(request, "admin/import_preview.html", context)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
|
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get('file_path')
|
file_path = request.POST.get('file_path')
|
||||||
@ -1438,7 +1448,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
count = 0
|
count = 0
|
||||||
errors = 0
|
errors = 0
|
||||||
failed_rows = []
|
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)
|
reader = csv.DictReader(f)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
try:
|
try:
|
||||||
@ -1447,6 +1457,9 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
date_str = row.get(mapping.get('date'))
|
date_str = row.get(mapping.get('date'))
|
||||||
type_name = row.get(mapping.get('type'))
|
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:
|
if not voter_id:
|
||||||
row["Import Error"] = "Missing voter ID"
|
row["Import Error"] = "Missing voter ID"
|
||||||
failed_rows.append(row)
|
failed_rows.append(row)
|
||||||
@ -1515,10 +1528,10 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:interaction-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = InteractionImportForm(request.POST, request.FILES)
|
form = InteractionImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -1527,14 +1540,14 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
@ -1589,7 +1602,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for field_name, _ in VOTER_LIKELIHOOD_MAPPABLE_FIELDS:
|
for field_name, _ in VOTER_LIKELIHOOD_MAPPABLE_FIELDS:
|
||||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
total_count = 0
|
total_count = 0
|
||||||
create_count = 0
|
create_count = 0
|
||||||
@ -1632,7 +1645,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
return render(request, "admin/import_preview.html", context)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
|
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get('file_path')
|
file_path = request.POST.get('file_path')
|
||||||
@ -1647,7 +1660,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
count = 0
|
count = 0
|
||||||
errors = 0
|
errors = 0
|
||||||
failed_rows = []
|
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)
|
reader = csv.DictReader(f)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
try:
|
try:
|
||||||
@ -1655,6 +1668,9 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
election_type_name = row.get(mapping.get('election_type'))
|
election_type_name = row.get(mapping.get('election_type'))
|
||||||
likelihood_val = row.get(mapping.get('likelihood'))
|
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:
|
if not voter_id:
|
||||||
row["Import Error"] = "Missing voter ID"
|
row["Import Error"] = "Missing voter ID"
|
||||||
failed_rows.append(row)
|
failed_rows.append(row)
|
||||||
@ -1697,10 +1713,10 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:voterlikelihood-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = VoterLikelihoodImportForm(request.POST, request.FILES)
|
form = VoterLikelihoodImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -1709,14 +1725,14 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
@ -1772,7 +1788,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
for field_name, _ in VOTING_RECORD_MAPPABLE_FIELDS:
|
for field_name, _ in VOTING_RECORD_MAPPABLE_FIELDS:
|
||||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||||
try:
|
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)
|
reader = csv.DictReader(f)
|
||||||
total_count = 0
|
total_count = 0
|
||||||
create_count = 0
|
create_count = 0
|
||||||
@ -1831,7 +1847,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
return render(request, "admin/import_preview.html", context)
|
return render(request, "admin/import_preview.html", context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing preview: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
|
|
||||||
elif "_import" in request.POST:
|
elif "_import" in request.POST:
|
||||||
file_path = request.POST.get('file_path')
|
file_path = request.POST.get('file_path')
|
||||||
@ -1846,7 +1862,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
count = 0
|
count = 0
|
||||||
errors = 0
|
errors = 0
|
||||||
failed_rows = []
|
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)
|
reader = csv.DictReader(f)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
try:
|
try:
|
||||||
@ -1855,6 +1871,9 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
election_description = row.get(mapping.get('election_description'))
|
election_description = row.get(mapping.get('election_description'))
|
||||||
primary_party = row.get(mapping.get('primary_party'))
|
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:
|
if not voter_id:
|
||||||
row["Import Error"] = "Missing voter ID"
|
row["Import Error"] = "Missing voter ID"
|
||||||
failed_rows.append(row)
|
failed_rows.append(row)
|
||||||
@ -1914,10 +1933,10 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
if errors > 0:
|
if errors > 0:
|
||||||
error_url = reverse("admin:votingrecord-download-errors")
|
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)
|
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:
|
except Exception as e:
|
||||||
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
self.message_user(request, f"Error processing file: {e}", level=messages.ERROR)
|
||||||
return redirect("..\\n")
|
return redirect("../")
|
||||||
else:
|
else:
|
||||||
form = VotingRecordImportForm(request.POST, request.FILES)
|
form = VotingRecordImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -1926,14 +1945,14 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
if not csv_file.name.endswith('.csv'):
|
if not csv_file.name.endswith('.csv'):
|
||||||
self.message_user(request, "Please upload a CSV file.", level=messages.ERROR)
|
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:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as tmp:
|
||||||
for chunk in csv_file.chunks():
|
for chunk in csv_file.chunks():
|
||||||
tmp.write(chunk)
|
tmp.write(chunk)
|
||||||
file_path = tmp.name
|
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)
|
reader = csv.reader(f)
|
||||||
headers = next(reader)
|
headers = next(reader)
|
||||||
|
|
||||||
|
|||||||
@ -118,6 +118,7 @@ class AdvancedVoterSearchForm(forms.Form):
|
|||||||
neighborhood = forms.CharField(required=False)
|
neighborhood = forms.CharField(required=False)
|
||||||
district = forms.CharField(required=False)
|
district = forms.CharField(required=False)
|
||||||
precinct = forms.CharField(required=False)
|
precinct = forms.CharField(required=False)
|
||||||
|
email = forms.EmailField(required=False) # Added email field
|
||||||
phone_type = forms.ChoiceField(
|
phone_type = forms.ChoiceField(
|
||||||
choices=[('', 'Any')] + Voter.PHONE_TYPE_CHOICES,
|
choices=[('', 'Any')] + Voter.PHONE_TYPE_CHOICES,
|
||||||
required=False
|
required=False
|
||||||
@ -125,7 +126,7 @@ class AdvancedVoterSearchForm(forms.Form):
|
|||||||
is_targeted = forms.BooleanField(required=False, label="Targeted Only")
|
is_targeted = forms.BooleanField(required=False, label="Targeted Only")
|
||||||
door_visit = forms.BooleanField(required=False, label="Visited Only")
|
door_visit = forms.BooleanField(required=False, label="Visited Only")
|
||||||
candidate_support = forms.ChoiceField(
|
candidate_support = forms.ChoiceField(
|
||||||
choices=[('', 'Any')] + Voter.SUPPORT_CHOICES,
|
choices=[('', 'Any')] + Voter.CANDIDATE_SUPPORT_CHOICES,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
yard_sign = forms.ChoiceField(
|
yard_sign = forms.ChoiceField(
|
||||||
@ -443,7 +444,7 @@ class DoorVisitLogForm(forms.Form):
|
|||||||
label="Wants a Yard Sign"
|
label="Wants a Yard Sign"
|
||||||
)
|
)
|
||||||
candidate_support = forms.ChoiceField(
|
candidate_support = forms.ChoiceField(
|
||||||
choices=Voter.SUPPORT_CHOICES,
|
choices=Voter.CANDIDATE_SUPPORT_CHOICES,
|
||||||
initial="unknown",
|
initial="unknown",
|
||||||
widget=forms.Select(attrs={"class": "form-select"}),
|
widget=forms.Select(attrs={"class": "form-select"}),
|
||||||
label="Candidate Support"
|
label="Candidate Support"
|
||||||
@ -484,16 +485,6 @@ class UserUpdateForm(forms.ModelForm):
|
|||||||
model = User
|
model = User
|
||||||
fields = ['first_name', 'last_name', 'email']
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
for field in self.fields.values():
|
for field in self.fields.values():
|
||||||
|
|||||||
@ -125,7 +125,7 @@ class Interest(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Voter(models.Model):
|
class Voter(models.Model):
|
||||||
SUPPORT_CHOICES = [
|
CANDIDATE_SUPPORT_CHOICES = [
|
||||||
('unknown', 'Unknown'),
|
('unknown', 'Unknown'),
|
||||||
('supporting', 'Supporting'),
|
('supporting', 'Supporting'),
|
||||||
('not_supporting', 'Not Supporting'),
|
('not_supporting', 'Not Supporting'),
|
||||||
@ -170,7 +170,7 @@ class Voter(models.Model):
|
|||||||
precinct = models.CharField(max_length=100, blank=True, db_index=True)
|
precinct = models.CharField(max_length=100, blank=True, db_index=True)
|
||||||
registration_date = models.DateField(null=True, blank=True)
|
registration_date = models.DateField(null=True, blank=True)
|
||||||
is_targeted = models.BooleanField(default=False, db_index=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)
|
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)
|
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)
|
notes = models.TextField(blank=True)
|
||||||
|
|||||||
@ -49,6 +49,10 @@
|
|||||||
<label class="form-label small fw-bold text-muted">Precinct</label>
|
<label class="form-label small fw-bold text-muted">Precinct</label>
|
||||||
{{ form.precinct }}
|
{{ form.precinct }}
|
||||||
</div>
|
</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">
|
<div class="col-md-4">
|
||||||
<label class="form-label small fw-bold text-muted">Phone Type</label>
|
<label class="form-label small fw-bold text-muted">Phone Type</label>
|
||||||
{{ form.phone_type }}
|
{{ form.phone_type }}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ from django.contrib import messages
|
|||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.conf import settings
|
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 .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 logging
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -157,7 +157,7 @@ def voter_list(request):
|
|||||||
|
|
||||||
if query:
|
if query:
|
||||||
query = query.strip()
|
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:
|
if "," in query:
|
||||||
parts = [p.strip() for p in query.split(",")]
|
parts = [p.strip() for p in query.split(",")]
|
||||||
@ -358,7 +358,6 @@ def edit_likelihood(request, likelihood_id):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = VoterLikelihoodForm(request.POST, instance=likelihood, tenant=tenant)
|
form = VoterLikelihoodForm(request.POST, instance=likelihood, tenant=tenant)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
# Check for conflict with another record of same election_type
|
|
||||||
election_type = form.cleaned_data['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():
|
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()
|
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'):
|
if data.get('address'):
|
||||||
voters = voters.filter(Q(address__icontains=data['address']) | Q(address_street__icontains=data['address']))
|
voters = voters.filter(Q(address__icontains=data['address']) | Q(address_street__icontains=data['address']))
|
||||||
if data.get('voter_id'):
|
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'):
|
if data.get('birth_month'):
|
||||||
voters = voters.filter(birthdate__month=data['birth_month'])
|
voters = voters.filter(birthdate__month=data['birth_month'])
|
||||||
if data.get('city'):
|
if data.get('city'):
|
||||||
@ -498,6 +497,8 @@ def voter_advanced_search(request):
|
|||||||
voters = voters.filter(district=data['district'])
|
voters = voters.filter(district=data['district'])
|
||||||
if data.get('precinct'):
|
if data.get('precinct'):
|
||||||
voters = voters.filter(precinct=data['precinct'])
|
voters = voters.filter(precinct=data['precinct'])
|
||||||
|
if data.get('email'):
|
||||||
|
voters = voters.filter(email__icontains=data['email'])
|
||||||
if data.get('phone_type'):
|
if data.get('phone_type'):
|
||||||
voters = voters.filter(phone_type=data['phone_type'])
|
voters = voters.filter(phone_type=data['phone_type'])
|
||||||
if data.get('is_targeted'):
|
if data.get('is_targeted'):
|
||||||
@ -563,7 +564,7 @@ def export_voters_csv(request):
|
|||||||
if data.get('address'):
|
if data.get('address'):
|
||||||
voters = voters.filter(Q(address__icontains=data['address']) | Q(address_street__icontains=data['address']))
|
voters = voters.filter(Q(address__icontains=data['address']) | Q(address_street__icontains=data['address']))
|
||||||
if data.get('voter_id'):
|
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'):
|
if data.get('birth_month'):
|
||||||
voters = voters.filter(birthdate__month=data['birth_month'])
|
voters = voters.filter(birthdate__month=data['birth_month'])
|
||||||
if data.get('city'):
|
if data.get('city'):
|
||||||
@ -574,6 +575,8 @@ def export_voters_csv(request):
|
|||||||
voters = voters.filter(district=data['district'])
|
voters = voters.filter(district=data['district'])
|
||||||
if data.get('precinct'):
|
if data.get('precinct'):
|
||||||
voters = voters.filter(precinct=data['precinct'])
|
voters = voters.filter(precinct=data['precinct'])
|
||||||
|
if data.get('email'):
|
||||||
|
voters = voters.filter(email__icontains=data['email'])
|
||||||
if data.get('phone_type'):
|
if data.get('phone_type'):
|
||||||
voters = voters.filter(phone_type=data['phone_type'])
|
voters = voters.filter(phone_type=data['phone_type'])
|
||||||
if data.get('is_targeted'):
|
if data.get('is_targeted'):
|
||||||
@ -842,7 +845,7 @@ def voter_search_json(request):
|
|||||||
tenant = get_object_or_404(Tenant, id=selected_tenant_id)
|
tenant = get_object_or_404(Tenant, id=selected_tenant_id)
|
||||||
voters = Voter.objects.filter(tenant=tenant)
|
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:
|
if "," in query:
|
||||||
parts = [p.strip() for p in query.split(",") ]
|
parts = [p.strip() for p in query.split(",") ]
|
||||||
@ -925,6 +928,7 @@ def volunteer_add(request):
|
|||||||
'form': form,
|
'form': form,
|
||||||
'tenant': tenant,
|
'tenant': tenant,
|
||||||
'selected_tenant': tenant,
|
'selected_tenant': tenant,
|
||||||
|
'is_create': True,
|
||||||
}
|
}
|
||||||
return render(request, 'core/volunteer_detail.html', context)
|
return render(request, 'core/volunteer_detail.html', context)
|
||||||
|
|
||||||
@ -1993,21 +1997,5 @@ def profile(request):
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
u_form = UserUpdateForm(request.POST, instance=request.user)
|
u_form = UserUpdateForm(request.POST, instance=request.user)
|
||||||
v_form = VolunteerProfileForm(request.POST, instance=volunteer) if volunteer else None
|
# v_form = VolunteerProfileForm(request.POST, instance=volunteer) if volunteer else None # Removed VolunteerProfileForm
|
||||||
|
v_form = None # Set v_form to None after removal
|
||||||
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)
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user