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.
285
core/admin.py
285
core/admin.py
@ -19,7 +19,7 @@ from .models import (
|
||||
Interest, Volunteer, VolunteerEvent, ParticipationStatus, VolunteerRole
|
||||
)
|
||||
from .forms import (
|
||||
VoterImportForm, EventImportForm, EventParticipationImportForm,
|
||||
VoterImportForm, EventImportForm, EventParticipationImportForm,
|
||||
DonationImportForm, InteractionImportForm, VoterLikelihoodImportForm,
|
||||
VolunteerImportForm, VotingRecordImportForm
|
||||
)
|
||||
@ -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"
|
||||
@ -252,7 +252,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
inlines = [VotingRecordInline, DonationInline, InteractionInline, VoterLikelihoodInline]
|
||||
readonly_fields = ('address',)
|
||||
change_list_template = "admin/voter_change_list.html"
|
||||
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
from core.models import Tenant
|
||||
@ -274,7 +274,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
file_path = request.POST.get("file_path")
|
||||
tenant_id = request.POST.get("tenant")
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in VOTER_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f"map_{field_name}")
|
||||
@ -294,18 +294,18 @@ 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
|
||||
|
||||
|
||||
existing_preview_ids = set(Voter.objects.filter(tenant=tenant, voter_id__in=voter_ids_for_preview).values_list("voter_id", flat=True))
|
||||
|
||||
|
||||
create_count = 0
|
||||
update_count = 0
|
||||
|
||||
|
||||
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,12 +326,12 @@ 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")
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in VOTER_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f"map_{field_name}")
|
||||
@ -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
|
||||
@ -446,7 +451,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
created_count += 1
|
||||
else:
|
||||
updated_count += 1
|
||||
|
||||
|
||||
# Special handling for interests - assuming a comma-separated list in CSV
|
||||
if 'interests' in mapping and row.get(mapping['interests']):
|
||||
interest_names = [name.strip() for name in row[mapping['interests']].split(',') if name.strip()]
|
||||
@ -478,26 +483,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -514,7 +519,7 @@ class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = VoterImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Voters"
|
||||
@ -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
|
||||
@ -564,7 +569,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
total_count += 1
|
||||
event_name = row.get(mapping.get('name'))
|
||||
event_date = row.get(mapping.get('date'))
|
||||
|
||||
|
||||
exists = False
|
||||
if event_name and event_date:
|
||||
try:
|
||||
@ -579,18 +584,18 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
|
||||
if dt:
|
||||
exists = Event.objects.filter(tenant=tenant, name=event_name, date=dt).exists()
|
||||
|
||||
|
||||
except ValueError:
|
||||
# Handle cases where date parsing fails
|
||||
pass
|
||||
|
||||
|
||||
if exists:
|
||||
update_count += 1
|
||||
action = 'update'
|
||||
else:
|
||||
create_count += 1
|
||||
action = 'create'
|
||||
|
||||
|
||||
if len(preview_data) < 10:
|
||||
preview_data.append({
|
||||
'action': action,
|
||||
@ -613,13 +618,13 @@ 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')
|
||||
tenant_id = request.POST.get('tenant')
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in EVENT_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||
@ -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:
|
||||
@ -658,7 +663,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
event_type_obj, _ = EventType.objects.get_or_create(tenant=tenant, name=event_type_name)
|
||||
|
||||
defaults = {
|
||||
@ -699,7 +704,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
row["Import Error"] = str(e)
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
self.message_user(request, f"Successfully imported {count} events.")
|
||||
@ -708,26 +713,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -744,7 +749,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = EventImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Events"
|
||||
@ -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
|
||||
@ -797,14 +802,14 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
exists = False
|
||||
if email:
|
||||
exists = Volunteer.objects.filter(tenant=tenant, email=email).exists()
|
||||
|
||||
|
||||
if exists:
|
||||
update_count += 1
|
||||
action = 'update'
|
||||
else:
|
||||
create_count += 1
|
||||
action = 'create'
|
||||
|
||||
|
||||
if len(preview_data) < 10:
|
||||
preview_data.append({
|
||||
'action': action,
|
||||
@ -827,13 +832,13 @@ 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')
|
||||
tenant_id = request.POST.get('tenant')
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in VOLUNTEER_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||
@ -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:
|
||||
@ -852,7 +857,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
defaults = {
|
||||
'first_name': row.get(mapping.get('first_name')) or '',
|
||||
'last_name': row.get(mapping.get('last_name')) or '',
|
||||
@ -871,7 +876,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
row["Import Error"] = str(e)
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
self.message_user(request, f"Successfully imported {count} volunteers.")
|
||||
@ -880,26 +885,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -916,7 +921,7 @@ class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = VolunteerImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Volunteers"
|
||||
@ -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,8 +969,7 @@ 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'), '')
|
||||
csv_last_name = row.get(mapping.get('last_name'), '')
|
||||
@ -981,14 +985,14 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
exists = EventParticipation.objects.filter(voter=voter, event__name=event_name).exists()
|
||||
except Voter.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
if exists:
|
||||
update_count += 1
|
||||
action = 'update'
|
||||
else:
|
||||
create_count += 1
|
||||
action = 'create'
|
||||
|
||||
|
||||
if len(preview_data) < 10:
|
||||
preview_data.append({
|
||||
'action': action,
|
||||
@ -1012,19 +1016,19 @@ 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')
|
||||
tenant_id = request.POST.get('tenant')
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
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)
|
||||
count = 0
|
||||
errors = 0
|
||||
@ -1033,13 +1037,16 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
try:
|
||||
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)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
voter = Voter.objects.get(tenant=tenant, voter_id=voter_id)
|
||||
except Voter.DoesNotExist:
|
||||
@ -1085,7 +1092,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
row["Import Error"] = str(e)
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
self.message_user(request, f"Successfully imported {count} participations.")
|
||||
@ -1096,26 +1103,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -1132,7 +1139,7 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = EventParticipationImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Participations"
|
||||
@ -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
|
||||
@ -1179,18 +1186,18 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
for row in reader:
|
||||
total_count += 1
|
||||
voter_id = row.get(mapping.get('voter_id'))
|
||||
|
||||
|
||||
exists = False
|
||||
if voter_id:
|
||||
exists = Voter.objects.filter(tenant=tenant, voter_id=voter_id).exists()
|
||||
|
||||
|
||||
if exists:
|
||||
update_count += 1
|
||||
action = 'update'
|
||||
else:
|
||||
create_count += 1
|
||||
action = 'create'
|
||||
|
||||
|
||||
if len(preview_data) < 10:
|
||||
preview_data.append({
|
||||
'action': action,
|
||||
@ -1213,13 +1220,13 @@ 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')
|
||||
tenant_id = request.POST.get('tenant')
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in DONATION_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||
@ -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)
|
||||
@ -1248,7 +1258,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
voter = Voter.objects.get(tenant=tenant, voter_id=voter_id)
|
||||
except Voter.DoesNotExist:
|
||||
@ -1256,7 +1266,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
if '/' in date_str:
|
||||
parsed_date = datetime.strptime(date_str, '%m/%d/%Y').date()
|
||||
@ -1280,7 +1290,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
donation_method, _ = DonationMethod.objects.get_or_create(tenant=tenant, name=method_name)
|
||||
|
||||
Donation.objects.create(
|
||||
@ -1295,7 +1305,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
row["Import Error"] = str(e)
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
self.message_user(request, f"Successfully imported {count} donations.")
|
||||
@ -1304,26 +1314,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -1340,7 +1350,7 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = DonationImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Donations"
|
||||
@ -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
|
||||
@ -1389,18 +1399,18 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
total_count += 1
|
||||
voter_id = row.get(mapping.get('voter_id'))
|
||||
volunteer_email = row.get(mapping.get('volunteer_email'))
|
||||
|
||||
|
||||
exists = False
|
||||
if voter_id:
|
||||
exists = Voter.objects.filter(tenant=tenant, voter_id=voter_id).exists()
|
||||
|
||||
|
||||
if exists:
|
||||
update_count += 1
|
||||
action = 'update'
|
||||
else:
|
||||
create_count += 1
|
||||
action = 'create'
|
||||
|
||||
|
||||
if len(preview_data) < 10:
|
||||
preview_data.append({
|
||||
'action': action,
|
||||
@ -1423,13 +1433,13 @@ 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')
|
||||
tenant_id = request.POST.get('tenant')
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in INTERACTION_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||
@ -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)
|
||||
@ -1458,7 +1471,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
voter = Voter.objects.get(tenant=tenant, voter_id=voter_id)
|
||||
except Voter.DoesNotExist:
|
||||
@ -1466,7 +1479,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
volunteer = None
|
||||
if volunteer_email:
|
||||
try:
|
||||
@ -1506,7 +1519,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
row["Import Error"] = str(e)
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
self.message_user(request, f"Successfully imported {count} interactions.")
|
||||
@ -1515,26 +1528,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -1551,7 +1564,7 @@ class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = InteractionImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Interactions"
|
||||
@ -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
|
||||
@ -1598,18 +1611,18 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
for row in reader:
|
||||
total_count += 1
|
||||
voter_id = row.get(mapping.get('voter_id'))
|
||||
|
||||
|
||||
exists = False
|
||||
if voter_id:
|
||||
exists = Voter.objects.filter(tenant=tenant, voter_id=voter_id).exists()
|
||||
|
||||
|
||||
if exists:
|
||||
update_count += 1
|
||||
action = 'update'
|
||||
else:
|
||||
create_count += 1
|
||||
action = 'create'
|
||||
|
||||
|
||||
if len(preview_data) < 10:
|
||||
preview_data.append({
|
||||
'action': action,
|
||||
@ -1632,13 +1645,13 @@ 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')
|
||||
tenant_id = request.POST.get('tenant')
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in VOTER_LIKELIHOOD_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||
@ -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,12 +1668,15 @@ 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)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
if not election_type_name or not likelihood_val:
|
||||
row["Import Error"] = "Missing election type or likelihood"
|
||||
failed_rows.append(row)
|
||||
@ -1674,7 +1690,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
election_type, _ = ElectionType.objects.get_or_create(tenant=tenant, name=election_type_name)
|
||||
|
||||
VoterLikelihood.objects.update_or_create(
|
||||
@ -1688,7 +1704,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
row["Import Error"] = str(e)
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
self.message_user(request, f"Import complete: {count} likelihoods created/updated.")
|
||||
@ -1697,26 +1713,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -1733,7 +1749,7 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = VoterLikelihoodImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Likelihoods"
|
||||
@ -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
|
||||
@ -1782,7 +1798,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
total_count += 1
|
||||
voter_id = row.get(mapping.get('voter_id'))
|
||||
election_date = row.get(mapping.get('election_date'))
|
||||
|
||||
|
||||
exists = False
|
||||
if voter_id and election_date:
|
||||
try:
|
||||
@ -1797,18 +1813,18 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
|
||||
if dt:
|
||||
exists = VotingRecord.objects.filter(voter__tenant=tenant, voter__voter_id=voter_id, election_date=dt).exists()
|
||||
|
||||
|
||||
except ValueError:
|
||||
# Handle cases where date parsing fails
|
||||
pass
|
||||
|
||||
|
||||
if exists:
|
||||
update_count += 1
|
||||
action = 'update'
|
||||
else:
|
||||
create_count += 1
|
||||
action = 'create'
|
||||
|
||||
|
||||
if len(preview_data) < 10:
|
||||
preview_data.append({
|
||||
'action': action,
|
||||
@ -1831,13 +1847,13 @@ 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')
|
||||
tenant_id = request.POST.get('tenant')
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
|
||||
|
||||
mapping = {}
|
||||
for field_name, _ in VOTING_RECORD_MAPPABLE_FIELDS:
|
||||
mapping[field_name] = request.POST.get(f'map_{field_name}')
|
||||
@ -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)
|
||||
@ -1866,7 +1885,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
voter = Voter.objects.get(tenant=tenant, voter_id=voter_id)
|
||||
except Voter.DoesNotExist:
|
||||
@ -1905,7 +1924,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
row["Import Error"] = str(e)
|
||||
failed_rows.append(row)
|
||||
errors += 1
|
||||
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
self.message_user(request, f"Successfully imported {count} voting records.")
|
||||
@ -1914,26 +1933,26 @@ 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():
|
||||
csv_file = request.FILES['file']
|
||||
tenant = form.cleaned_data['tenant']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -1950,7 +1969,7 @@ class VotingRecordAdmin(BaseImportAdminMixin, admin.ModelAdmin):
|
||||
return render(request, "admin/import_mapping.html", context)
|
||||
else:
|
||||
form = VotingRecordImportForm()
|
||||
|
||||
|
||||
context = self.admin_site.each_context(request)
|
||||
context['form'] = form
|
||||
context['title'] = "Import Voting Records"
|
||||
|
||||
@ -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