diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 78e4e57..c59d399 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 912ac55..573167d 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/core/door_visits.html b/core/templates/core/door_visits.html index cb75bc2..28c4ea2 100644 --- a/core/templates/core/door_visits.html +++ b/core/templates/core/door_visits.html @@ -12,6 +12,9 @@ + + Neighborhoods + Visit History @@ -25,14 +28,18 @@
-
+
-
+
+
+ + +
@@ -130,12 +137,12 @@
    {% if households.has_previous %}
  • - +
  • - +
  • @@ -145,12 +152,12 @@ {% if households.has_next %}
  • - +
  • - +
  • @@ -450,4 +457,4 @@ } } -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/neighborhood_counts.html b/core/templates/core/neighborhood_counts.html new file mode 100644 index 0000000..79a3edd --- /dev/null +++ b/core/templates/core/neighborhood_counts.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +
    +
    +
    +

    Neighborhood Summary

    +

    Household counts by neighborhood based on active filters.

    +
    + +
    + + + {% if district_filter or neighborhood_filter or city_filter or address_filter %} +
    + + Active Filters: + {% if district_filter %}District: {{ district_filter }}{% endif %} + {% if neighborhood_filter %}{% if district_filter %}, {% endif %}Neighborhood: {{ neighborhood_filter }}{% endif %} + {% if city_filter %}{% if district_filter or neighborhood_filter %}, {% endif %}City: {{ city_filter }}{% endif %} + {% if address_filter %}{% if district_filter or neighborhood_filter or city_filter %}, {% endif %}Address: {{ address_filter }}{% endif %} +
    + {% endif %} + + +
    +
    +
    Neighborhoods
    + + {{ neighborhoods|length }} Neighborhoods + +
    +
    + + + + + + + + + + {% for item in neighborhoods %} + + + + + + {% empty %} + + + + {% endfor %} + +
    NeighborhoodUnvisited HouseholdsAction
    + {{ item.display_name }} + + + {{ item.count }} + + + + View Households + +
    + No data found for the current filters. +
    +
    +
    +
    +{% endblock %} diff --git a/core/urls.py b/core/urls.py index 0c592a0..a1ec9ee 100644 --- a/core/urls.py +++ b/core/urls.py @@ -63,6 +63,7 @@ urlpatterns = [ path('door-visits/', views.door_visits, name='door_visits'), path('door-visits/log/', views.log_door_visit, name='log_door_visit'), path('door-visits/history/', views.door_visit_history, name='door_visit_history'), + path('door-visits/neighborhoods/', views.neighborhood_counts, name='neighborhood_counts'), # Call Queue path('call-queue/', views.call_queue, name='call_queue'), diff --git a/core/views.py b/core/views.py index 69c271f..89560a6 100644 --- a/core/views.py +++ b/core/views.py @@ -1588,6 +1588,7 @@ def door_visits(request): tenant = get_object_or_404(Tenant, id=selected_tenant_id) + city_filter = request.GET.get("city", "").strip() # Filters from GET parameters district_filter = request.GET.get('district', '').strip() neighborhood_filter = request.GET.get('neighborhood', '').strip() @@ -1596,6 +1597,8 @@ def door_visits(request): # Initial queryset: unvisited targeted voters for this tenant voters = Voter.objects.filter(tenant=tenant, door_visit=False, is_targeted=True) + if city_filter: + voters = voters.filter(city__icontains=city_filter) # Apply filters if provided if district_filter: voters = voters.filter(district=district_filter) @@ -1671,6 +1674,7 @@ def door_visits(request): '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', ''), 'visit_form': DoorVisitLogForm(), @@ -2076,3 +2080,56 @@ def profile(request): if request.method == 'POST': u_form = UserUpdateForm(request.POST, instance=request.user) + + +@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'], permission='core.view_voter') +def neighborhood_counts(request): + """ + Shows household counts by neighborhood after applying filters from door visits. + """ + 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() + + voters = Voter.objects.filter(tenant=tenant, door_visit=False, is_targeted=True) + + 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)) + + household_qs = voters.values('neighborhood', 'address_street', 'city', 'state', 'zip_code').distinct() + + neighborhood_counts_dict = {} + for h in household_qs: + nb = h['neighborhood'] + neighborhood_counts_dict[nb] = neighborhood_counts_dict.get(nb, 0) + 1 + + neighborhood_list = [ + {'neighborhood': nb, 'display_name': nb or "Unknown", 'count': count} + for nb, count in neighborhood_counts_dict.items() + ] + + neighborhood_list.sort(key=lambda x: x['count'], reverse=True) + + context = { + 'selected_tenant': tenant, + 'neighborhoods': neighborhood_list, + 'city_filter': city_filter, + 'district_filter': district_filter, + 'neighborhood_filter': neighborhood_filter, + 'address_filter': address_filter, + } + return render(request, 'core/neighborhood_counts.html', context)