126 lines
5.1 KiB
Python
126 lines
5.1 KiB
Python
import re
|
|
import json
|
|
import logging
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.db.models import Q
|
|
from django.contrib import messages
|
|
from django.core.paginator import Paginator
|
|
from django.conf import settings
|
|
from .models import Voter, Tenant, CampaignSettings
|
|
from .permissions import role_required
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'])
|
|
def yard_sign_voters(request):
|
|
"""
|
|
Manage yard sign requests. Groups voters who want a yard sign by household.
|
|
Enhanced to ensure neighborhoods are correctly identified even if some voters in a household have empty fields.
|
|
"""
|
|
selected_tenant_id = request.session.get("tenant_id")
|
|
if not selected_tenant_id:
|
|
messages.warning(request, "Please select a campaign first.")
|
|
return redirect("index")
|
|
|
|
tenant = get_object_or_404(Tenant, id=selected_tenant_id)
|
|
|
|
city_filter = request.GET.get("city", "").strip()
|
|
district_filter = request.GET.get('district', '').strip()
|
|
neighborhood_filter = request.GET.get('neighborhood', '').strip()
|
|
address_filter = request.GET.get('address', '').strip()
|
|
|
|
# Initial queryset: voters who want a yard sign for this tenant
|
|
voters = Voter.objects.filter(tenant=tenant, is_inactive=False, yard_sign='wants')
|
|
|
|
if city_filter:
|
|
voters = voters.filter(city__icontains=city_filter)
|
|
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__icontains=address_filter) | Q(address_street__icontains=address_filter))
|
|
|
|
# Grouping by household (unique address)
|
|
households_dict = {}
|
|
for voter in voters:
|
|
# Normalize address components for robust grouping
|
|
street = (voter.address_street or "").strip()
|
|
city = (voter.city or "").strip()
|
|
state = (voter.state or "").strip()
|
|
zip_code = (voter.zip_code or "").strip()
|
|
|
|
key = (street.lower(), city.lower(), state.lower(), zip_code.lower())
|
|
|
|
if key not in households_dict:
|
|
street_number = ""
|
|
street_name = street
|
|
match = re.match(r'^(\d+)\s+(.*)$', 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': street,
|
|
'city': city,
|
|
'state': state,
|
|
'zip_code': zip_code,
|
|
'neighborhood': (voter.neighborhood or "").strip(),
|
|
'district': (voter.district or "").strip(),
|
|
'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,
|
|
'voters_who_want_sign': [],
|
|
}
|
|
else:
|
|
# Pick a non-empty neighborhood/district if the current one is empty
|
|
if not households_dict[key]['neighborhood'] and voter.neighborhood:
|
|
households_dict[key]['neighborhood'] = voter.neighborhood.strip()
|
|
if not households_dict[key]['district'] and voter.district:
|
|
households_dict[key]['district'] = voter.district.strip()
|
|
|
|
households_dict[key]['voters_who_want_sign'].append(voter)
|
|
|
|
households_list = list(households_dict.values())
|
|
|
|
# Sort by neighborhood, then street name, then street number
|
|
households_list.sort(key=lambda x: (
|
|
(x['neighborhood'] or '').lower(),
|
|
x['street_name_sort'],
|
|
x['street_number_sort']
|
|
))
|
|
|
|
# Prepare data for Google Map
|
|
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['voters_who_want_sign']]),
|
|
'notes': ", ".join([f"{v.first_name}: {v.notes}" for v in h['voters_who_want_sign'] if v.notes]),
|
|
'voter_ids': [v.id for v in h['voters_who_want_sign']]
|
|
}
|
|
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,
|
|
"city_filter": city_filter,
|
|
'map_data_json': json.dumps(map_data),
|
|
'GOOGLE_MAPS_API_KEY': getattr(settings, 'GOOGLE_MAPS_API_KEY', ''),
|
|
}
|
|
return render(request, 'core/yard_sign_voters.html', context) |