-
Advanced Search
{% if can_edit_voter %}
+ Add New Voter
diff --git a/core/views.py b/core/views.py
index bb19060..ef40c1a 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1487,7 +1487,7 @@ def door_visit_history(request):
"latest_interaction": interaction
}
- visited_households[key]["voters_at_address"].add(f"{v.first_name} {v.last_name}")
+ visited_households[key]["voters_at_address"].add((v.id, f"{v.first_name} {v.last_name}"))
# Sort volunteer counts by total (descending)
sorted_volunteer_counts = sorted(volunteer_counts.items(), key=lambda x: x[1], reverse=True)
diff --git a/door_views_update.py b/door_views_update.py
index 765c0e7..616727e 100644
--- a/door_views_update.py
+++ b/door_views_update.py
@@ -1,211 +1,16 @@
-def door_visits(request):
- """
- Manage door knocking visits. Groups unvisited targeted voters by household.
- """
- selected_tenant_id = request.session.get("tenant_id")
- if not selected_tenant_id:
- messages.warning(request, "Please select a campaign first.")
- return redirect("index")
+import sys
- tenant = get_object_or_404(Tenant, id=selected_tenant_id)
-
- # Filters from GET parameters
- district_filter = request.GET.get('district', '').strip()
- neighborhood_filter = request.GET.get('neighborhood', '').strip()
- address_filter = request.GET.get('address', '').strip()
+file_path = 'core/views.py'
+with open(file_path, 'r') as f:
+ content = f.read()
- # Initial queryset: unvisited targeted voters for this tenant
- voters = Voter.objects.filter(tenant=tenant, door_visit=False, is_targeted=True)
+old_code = 'visited_households[key]["voters_at_address"].add(f"{v.first_name} {v.last_name}")'
+new_code = 'visited_households[key]["voters_at_address"].add((v.id, f"{v.first_name} {v.last_name}"))'
- # Apply filters if provided
- if district_filter:
- voters = voters.filter(district=district_filter)
- if neighborhood_filter:
- voters = voters.filter(neighborhood__icontains=neighborhood_filter)
- if address_filter:
- voters = voters.filter(Q(address_street__icontains=address_filter) | Q(address__icontains=address_filter))
-
- # Grouping by household (unique address)
- households_dict = {}
- for voter in voters:
- # Key for grouping is the unique address components
- key = (voter.address_street, voter.city, voter.state, voter.zip_code)
- if key not in households_dict:
- # Parse street name and number for sorting
- street_number = ""
- street_name = voter.address_street
- match = re.match(r'^(\d+)\s+(.*)$', voter.address_street)
- if match:
- street_number = match.group(1)
- street_name = match.group(2)
-
- try:
- street_number_sort = int(street_number)
- except ValueError:
- street_number_sort = 0
-
- households_dict[key] = {
- 'address_street': voter.address_street,
- 'city': voter.city,
- 'state': voter.state,
- 'zip_code': voter.zip_code,
- 'neighborhood': voter.neighborhood,
- 'district': voter.district,
- 'latitude': float(voter.latitude) if voter.latitude else None,
- 'longitude': float(voter.longitude) if voter.longitude else None,
- 'street_name_sort': street_name.lower(),
- 'street_number_sort': street_number_sort,
- 'target_voters': [],
- 'voters_json': []
- }
- households_dict[key]['target_voters'].append(voter)
- households_dict[key]['voters_json'].append({'id': voter.id, 'name': f"{voter.first_name} {voter.last_name}"})
-
- households_list = list(households_dict.values())
- for h in households_list:
- h['voters_json_str'] = json.dumps(h['voters_json'])
-
- households_list.sort(key=lambda x: (
- (x['neighborhood'] or '').lower(),
- x['street_name_sort'],
- x['street_number_sort']
- ))
-
- # Prepare data for Google Map (all filtered households with coordinates)
- map_data = [
- {
- 'lat': h['latitude'],
- 'lng': h['longitude'],
- 'address': f"{h['address_street']}, {h['city']}, {h['state']}",
- 'voters': ", ".join([f"{v.first_name} {v.last_name}" for v in h['target_voters']])
- }
- for h in households_list if h['latitude'] and h['longitude']
- ]
-
- paginator = Paginator(households_list, 50)
- page_number = request.GET.get('page')
- households_page = paginator.get_page(page_number)
-
- context = {
- 'selected_tenant': tenant,
- 'households': households_page,
- 'district_filter': district_filter,
- 'neighborhood_filter': neighborhood_filter,
- 'address_filter': address_filter,
- 'map_data_json': json.dumps(map_data),
- 'google_maps_api_key': getattr(settings, 'GOOGLE_MAPS_API_KEY', ''),
- 'visit_form': DoorVisitLogForm(),
- }
- return render(request, 'core/door_visits.html', context)
-
-@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'], permission='core.view_voter')
-def log_door_visit(request):
- """
- Mark all targeted voters at a specific address as visited, update their flags,
- and create interaction records.
- """
- selected_tenant_id = request.session.get("tenant_id")
- if not selected_tenant_id:
- return redirect("index")
-
- tenant = get_object_or_404(Tenant, id=selected_tenant_id)
- campaign_settings, _ = CampaignSettings.objects.get_or_create(tenant=tenant)
-
- # Capture query string for redirecting back with filters
- next_qs = request.POST.get("next_query_string", "")
- redirect_url = reverse("door_visits")
- if next_qs:
- redirect_url += f"?{next_qs}"
-
- # Get the volunteer linked to the current user
- volunteer = Volunteer.objects.filter(user=request.user, tenant=tenant).first()
-
- if request.method == "POST":
- form = DoorVisitLogForm(request.POST)
- if form.is_valid():
- address_street = request.POST.get("address_street")
- city = request.POST.get("city")
- state = request.POST.get("state")
- zip_code = request.POST.get("zip_code")
-
- outcome = form.cleaned_data["outcome"]
- notes = form.cleaned_data["notes"]
- wants_yard_sign = form.cleaned_data["wants_yard_sign"]
- candidate_support = form.cleaned_data["candidate_support"]
- follow_up = form.cleaned_data["follow_up"]
- follow_up_voter_id = form.cleaned_data.get("follow_up_voter")
- call_notes = form.cleaned_data["call_notes"]
-
- # Determine date/time in campaign timezone
- campaign_tz_name = campaign_settings.timezone or "America/Chicago"
- try:
- tz = zoneinfo.ZoneInfo(campaign_tz_name)
- except:
- tz = zoneinfo.ZoneInfo("America/Chicago")
-
- interaction_date = timezone.now().astimezone(tz)
-
- # Get or create InteractionType
- interaction_type, _ = InteractionType.objects.get_or_create(tenant=tenant, name="Door Visit")
-
- # Find targeted voters at this exact address
- voters = Voter.objects.filter(
- tenant=tenant,
- address_street=address_street,
- city=city,
- state=state,
- zip_code=zip_code,
- is_targeted=True
- )
-
- if not voters.exists():
- messages.warning(request, f"No targeted voters found at {address_street}.")
- return redirect(redirect_url)
-
- # Get default caller for follow-ups
- default_caller = None
- if follow_up:
- default_caller = Volunteer.objects.filter(tenant=tenant, is_default_caller=True).first()
-
- for voter in voters:
- # 1) Update voter flags
- voter.door_visit = True
-
- # 2) If "Wants a Yard Sign" checkbox is selected
- if wants_yard_sign:
- voter.yard_sign = "wants"
-
- # 3) Update support status if Supporting or Not Supporting
- if candidate_support in ["supporting", "not_supporting"]:
- voter.candidate_support = candidate_support
-
- voter.save()
-
- # 4) Create interaction
- Interaction.objects.create(
- voter=voter,
- volunteer=volunteer,
- type=interaction_type,
- date=interaction_date,
- description=outcome,
- notes=notes
- )
-
- # 5) Create ScheduledCall if follow_up is checked and this is the selected voter
- if follow_up and follow_up_voter_id and str(voter.id) == follow_up_voter_id:
- ScheduledCall.objects.create(
- tenant=tenant,
- voter=voter,
- volunteer=default_caller,
- comments=call_notes,
- status="pending"
- )
-
- if follow_up:
- messages.success(request, f"Door visit logged and follow-up call scheduled for {address_street}.")
- else:
- messages.success(request, f"Door visit logged for {address_street}.")
- else:
- messages.error(request, "There was an error in the visit log form.")
-
- return redirect(redirect_url)
\ No newline at end of file
+if old_code in content:
+ new_content = content.replace(old_code, new_code)
+ with open(file_path, 'w') as f:
+ f.write(new_content)
+ print("Successfully patched core/views.py")
+else:
+ print("Could not find the target line in core/views.py")