From 14a93b6b2b92e6b4ef36f9090d70151dc2148d68 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 24 Jan 2026 22:46:50 +0000 Subject: [PATCH] Autosave: 20260124-224650 --- config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5675 bytes config/settings.py | 1 + .../context_processors.cpython-311.pyc | Bin 763 -> 921 bytes core/__pycache__/forms.cpython-311.pyc | Bin 11430 -> 11403 bytes core/__pycache__/models.cpython-311.pyc | Bin 17320 -> 20325 bytes core/__pycache__/views.cpython-311.pyc | Bin 24678 -> 27217 bytes core/context_processors.py | 4 +- core/forms.py | 4 +- ...er_voter_latitude_alter_voter_longitude.py | 23 ++++ ...tude_alter_voter_longitude.cpython-311.pyc | Bin 0 -> 1059 bytes core/models.py | 107 +++++++++++++----- core/templates/base.html | 11 ++ core/templates/core/index.html | 90 ++++++++++++--- core/templates/core/voter_detail.html | 44 ++++--- core/views.py | 58 ++++++++-- test_manual_save_v2.py | 64 +++++++++++ 16 files changed, 334 insertions(+), 72 deletions(-) create mode 100644 core/migrations/0008_alter_voter_latitude_alter_voter_longitude.py create mode 100644 core/migrations/__pycache__/0008_alter_voter_latitude_alter_voter_longitude.cpython-311.pyc create mode 100644 test_manual_save_v2.py diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index c8bcb91badf15f63271722f21ae7c26ad15d84ca..de0615da81456726ed149ac097fc1eba20bda42d 100644 GIT binary patch delta 222 zcmdm>y;_HNIWI340}!mSE6rRskyn!O+eY=vEE-GPjS6wi^3V+m z&UE(*^~g&r3N8#!N_LEJcCT{xFEr6K*gTzIfJws%XmSw`h!6x3{6OLthfQvNN@-52 uT~Rua%Lv59UpAi-yv?k5Lri``(v-{#3Z@s#6E2D+UJ*;Yz#t4kU;_Y^FFVWt delta 79 zcmZ3jvq778IWI340}y-&D$aD7$ScWsWuy9K7Pb`TU%}T6OxtKKdH-G0BV4BP$ dB*~QqRLTg%#rHPr3*BbszQ7;^L`8x?Apl4X6QckC diff --git a/config/settings.py b/config/settings.py index 291d043..e1e7409 100644 --- a/config/settings.py +++ b/config/settings.py @@ -180,3 +180,4 @@ if EMAIL_USE_SSL: # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY", "AIzaSyAluZTEjH-RSiGJUHnfrSqWbcAXCGzGOq4") diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 3df58b8d927abf8cd8b21bb76af2aee146db87dc..3282f43ca53533e50e0eda22cdef881c41b6260e 100644 GIT binary patch delta 445 zcmey(I+LAuIWI340}y1mlxF^$$SWDG0pv_)NMT4}%wdRv(2P-xKt59rQ!aB9Gb2L^ za|=ThOA2E!gC@&MkX}Dc##-~iIhK>YatkeEJkqQu0;a-$MX zAO{Lcz?MTeDJ&^W%Ydp^gLHsF3hNvepe<~Z7@Ng!3A_9IyZgAt`#J^$$2$gi#(TR) zYO+jjWwg^N0@P*^#0O?Z lM#dWqoDE=jgMqt&yTKSp++a|+fQmk_a4`ZMfk}WZ0RW$-WoZBa delta 240 zcmbQq{+pF|IWI340}y-&D$blZkyp}Q2FRJtkiw9{n8OeSp&6qXQj1BqW8Ho5sJ fr8%i~MZ!QX$Q{K7ll_^cM zdl+axSAU`TrmL_mTIh}y0)umb!3TlCmoCRz@5?fdA6@v_X1+Y@p^L47{4bT>a!IE1 zu80(-XRe%c3;O!p;^6pcCi;`Z(r&iX!%!RCQfZu1Rb^Z#5;3ezt|~ zd1jnqrCJKr#_78EdEa5)6_LHAGr|(q1%!bLpqpdfJ*<9jC77CE8|lz$zjTP6Ri*4n zd-B88m!!^Ngn2YmNhB>TtFrYND+9g2HV(OpeSvZxg=!j)V1;@2w;7e?Ox2oqW)pfw zHQ5eCv;+MdT=XuKF+1B0>;d)xLL+t@#5coi!|2zVCoMwl0R$c7cxMFa1Md)|zw7Gg z#wx>BF^~_{x+S^9VHcv8@MFP+<212)pxQn}9vNXn`PZSd()b95C4f&DfG*rd_BAjH z2*Zz}bPOoAfqlc<%#XGHwf0f)(^})0C{BOYov}5>#qnRQKiya&(~Uq|ra0X(iZ^r= z>pI);T$&cT#t~esi)p;={pq5=3RsyqKEu?y5yMj1SK5TCYvTsJ^}ku>XjQBU?B+*X zPn#Okwro|%b~MeF%JyNvBH3xW)3l*j_N%6+()1AG44_2zByTH{U5%iX%WmG6Rnt~9 z!7MGQO_K8Y>t$r?SQCgY$nK^)%{y)Bx{#g>-aZyVlz5Y^D767mAjTn=Gtr8mC9;B0 zwbUyZ!~}Li$k1#kRqP#}f_-JOh%5H4$hI2vuiUmZ!nXZ9TZa`zOKX;*P<6}QWnPGh zg-!amW#NUG+^+!rw*Fk(Ui2K{h{zx1U9sIo_vQyOT03mRd-4}sC#2~QS}_IN%E6PZ zo&3-c2E;qxj*@unBH8Xl^?mk}EA8yJRN|}Tk&E*luTC`x1BP=n+y2Kgs&iN*M=3dL xZ#qYh<#?~ij3Q--ha|3*@JEDi!WfZikY?YwzdU86GP=5h&E&Hko#hU6{SOy%dujjx delta 1968 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 R%25BBltv%F5+0Kl{|`)Hyf**< delta 6340 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 cxV7O;)c7|$r9h&L?0Qn}l+yDRo diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index d807a7336955d33f5af9b935c13bfde2cd30d37e..935de857c2681e227d22321594bf67211289627d 100644 GIT binary patch delta 8182 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 HK?(m4yYH?# delta 6004 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 HXuy8}PFDX( diff --git a/core/context_processors.py b/core/context_processors.py index 0bf87c3..dd5df10 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,5 +1,6 @@ import os import time +from django.conf import settings def project_context(request): """ @@ -10,4 +11,5 @@ def project_context(request): "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), # Used for cache-busting static assets "deployment_timestamp": int(time.time()), - } + "GOOGLE_MAPS_API_KEY": getattr(settings, 'GOOGLE_MAPS_API_KEY', ''), + } \ No newline at end of file diff --git a/core/forms.py b/core/forms.py index 472917b..6ce5422 100644 --- a/core/forms.py +++ b/core/forms.py @@ -12,8 +12,8 @@ class VoterForm(forms.ModelForm): ] widgets = { 'registration_date': forms.DateInput(attrs={'type': 'date'}), - 'latitude': forms.TextInput(attrs={'readonly': 'readonly', 'class': 'form-control bg-light'}), - 'longitude': forms.TextInput(attrs={'readonly': 'readonly', 'class': 'form-control bg-light'}), + 'latitude': forms.TextInput(attrs={'class': 'form-control bg-light'}), + 'longitude': forms.TextInput(attrs={'class': 'form-control bg-light'}), } def __init__(self, *args, **kwargs): diff --git a/core/migrations/0008_alter_voter_latitude_alter_voter_longitude.py b/core/migrations/0008_alter_voter_latitude_alter_voter_longitude.py new file mode 100644 index 0000000..c55e947 --- /dev/null +++ b/core/migrations/0008_alter_voter_latitude_alter_voter_longitude.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.7 on 2026-01-24 21:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0007_voter_address_street_voter_city_voter_county_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='voter', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=9, max_digits=12, null=True), + ), + migrations.AlterField( + model_name='voter', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=9, max_digits=12, null=True), + ), + ] diff --git a/core/migrations/__pycache__/0008_alter_voter_latitude_alter_voter_longitude.cpython-311.pyc b/core/migrations/__pycache__/0008_alter_voter_latitude_alter_voter_longitude.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc44a347b8491ad67c162effb55face6f7629679 GIT binary patch literal 1059 zcmbVK&rj4q6rO3jWm~|=#>9g_Jcw~GmV=4L5Tgmv#3&v##x#;iw{Kj#OxtzZD%?1D z@W{c)e}EYIBRqPeU!2hYP4|5+FbYiK$Ox!#5}~$O8h#djck>J+m&_Lhb8`<(ptVCGEz(4z~^E#!*)W zd6XuZvf~uE$ili(f5%70;WjFpgcF|v@eQC|>YE1PR-}asKa|uHiSqMR?=Nd0gW{kXD#Vu5QoI&);Nk)0|5d07#x?EXyV5 z`N$bY`C6@}MS@0<0E=~PqbgHPfmG!M@MrrDLfM}{(Y92hBZ6c_odX_5aUfV<1R>9m z+hCPJ)Q$2?SxF&8+eDw{m?c5X(WhnORm24-N2Cd6;@L}WM>ScM!kBUyb^wLos82BV zrU-;C(-!bPPk<+3#51WUtlWD7(cRYvO3k*KSRA%gxs;ps`DoZ*$RWnbamRa|#@s79 zh_~?gDi4c1mEOBlu4H{g;c4o;-#>|mB;I0yzGg&KP049z4d!}(P1<--CA5-#M6rJI zls0jm(9$$6*z1oGYi@h-+thct`{LzpYxzfOdC*!e z$1KM^ASjNs_@i+8-wP+lTlV@x{m7ZR%_SVE1HB;Wrq`iUF
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + {% block content %}{% endblock %}
diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 7be5adc..db73f03 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -33,28 +33,84 @@

Dashboard: {{ selected_tenant.name }}

- Switch Campaign -
-
- -
-
-
-
Voters Registry
-

{{ selected_tenant.voters.count }}

- View Registry +
+
+ + +
+ Switch Campaign
-
+ +
+
+
+
+
Registered Voters
+

{{ metrics.total_registered_voters }}

+
+
+
+
+
+
+
Target Voters
+

{{ metrics.total_target_voters }}

+
+
+
+
+
+
+
Supporting
+

{{ metrics.total_supporting }}

+
+
+
+
+
+
+
Voter Addresses
+

{{ metrics.total_voter_addresses }}

+
+
+
+
+
+
+
Door Visits
+

{{ metrics.total_door_visits }}

+
+
+
+
+
+
+
Signs (Wants/Has)
+

{{ metrics.total_signs }}

+
+
+
+
+
+
+
Total Donations
+

${{ metrics.total_donations|floatformat:2 }}

+
+
+
+
+ +
-
-
Quick Voter Search
-
- - -
+
+
+
Voter Management
+

Access the full registry to manage individual voter profiles.

+
+ View Registry
diff --git a/core/templates/core/voter_detail.html b/core/templates/core/voter_detail.html index 6a94a8a..3a3412a 100644 --- a/core/templates/core/voter_detail.html +++ b/core/templates/core/voter_detail.html @@ -2,10 +2,8 @@ {% load static %} {% block content %} - - - - + +
@@ -157,7 +155,7 @@
- +
-
+
{{ voter_form.county }}
-
+
{{ voter_form.latitude }}
-
+
{{ voter_form.longitude }}
@@ -882,16 +880,25 @@ {% if voter.latitude and voter.longitude %} // Initialize Map try { - var map = L.map('voterMap').setView([{{ voter.latitude }}, {{ voter.longitude }}], 15); - L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { - maxZoom: 19, - attribution: '© OpenStreetMap' - }).addTo(map); - L.marker([{{ voter.latitude }}, {{ voter.longitude }}]).addTo(map) - .bindPopup('{{ voter.first_name }} {{ voter.last_name }}
{{ voter.address_street }}') - .openPopup(); + const position = { lat: {{ voter.latitude }}, lng: {{ voter.longitude }} }; + const map = new google.maps.Map(document.getElementById("voterMap"), { + zoom: 15, + center: position, + }); + const marker = new google.maps.Marker({ + position: position, + map: map, + title: "{{ voter.first_name }} {{ voter.last_name }}", + }); + const infowindow = new google.maps.InfoWindow({ + content: "{{ voter.first_name }} {{ voter.last_name }}
{{ voter.address_street }}", + }); + marker.addListener("click", () => { + infowindow.open(map, marker); + }); + infowindow.open(map, marker); } catch (e) { - console.error("Leaflet map initialization failed:", e); + console.error("Google Maps initialization failed:", e); } {% endif %} @@ -934,7 +941,7 @@ .then(data => { if (data.success) { document.querySelector('[name="latitude"]').value = data.latitude; - document.querySelector('[name="longitude"]').value = data.longitude; + document.querySelector('[name="longitude"]').value = String(data.longitude).substring(0, 12); statusDiv.innerHTML = 'Coordinates updated!'; } else { statusDiv.innerHTML = '' + (data.error || 'Geocoding failed.') + ''; @@ -954,3 +961,4 @@ }); {% endblock %} + diff --git a/core/views.py b/core/views.py index 0641876..80e7a0c 100644 --- a/core/views.py +++ b/core/views.py @@ -1,10 +1,13 @@ from django.http import JsonResponse from django.urls import reverse from django.shortcuts import render, redirect, get_object_or_404 -from django.db.models import Q +from django.db.models import Q, Sum from django.contrib import messages from .models import Voter, Tenant, Interaction, Donation, VoterLikelihood, EventParticipation, Event, EventType, InteractionType, DonationMethod, ElectionType from .forms import VoterForm, InteractionForm, DonationForm, VoterLikelihoodForm, EventParticipationForm, EventTypeForm +import logging + +logger = logging.getLogger(__name__) def index(request): """ @@ -14,12 +17,26 @@ def index(request): tenants = Tenant.objects.all() selected_tenant_id = request.session.get('tenant_id') selected_tenant = None + metrics = {} + if selected_tenant_id: selected_tenant = Tenant.objects.filter(id=selected_tenant_id).first() + if selected_tenant: + voters = selected_tenant.voters.all() + metrics = { + "total_registered_voters": voters.count(), + "total_target_voters": voters.filter(is_targeted=True).count(), + "total_supporting": voters.filter(candidate_support="supporting").count(), + "total_voter_addresses": voters.values("address").distinct().count(), + "total_door_visits": Interaction.objects.filter(voter__tenant=selected_tenant, type__name="Door Visit").count(), + "total_signs": voters.filter(Q(yard_sign="wants") | Q(yard_sign="has")).count(), + "total_donations": Donation.objects.filter(voter__tenant=selected_tenant).aggregate(total=Sum("amount"))["total"] or 0, + } context = { 'tenants': tenants, 'selected_tenant': selected_tenant, + 'metrics': metrics, } return render(request, 'core/index.html', context) @@ -108,11 +125,32 @@ def voter_edit(request, voter_id): voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) if request.method == 'POST': + # Log incoming coordinate data for debugging + lat_raw = request.POST.get('latitude') + lon_raw = request.POST.get('longitude') + logger.info(f"Voter Edit POST: lat={lat_raw}, lon={lon_raw}") + form = VoterForm(request.POST, instance=voter) if form.is_valid(): - form.save() + # If coordinates were provided in POST, ensure they are applied to the instance + # This handles cases where readonly or other widget settings might interfere + voter = form.save(commit=False) + if lat_raw: + try: + voter.latitude = lat_raw + except: pass + if lon_raw: + try: + voter.longitude = lon_raw + except: pass + + voter.save() messages.success(request, "Voter profile updated successfully.") - return redirect('voter_detail', voter_id=voter.id) + else: + logger.warning(f"Voter Edit Form Invalid: {form.errors}") + for field, errors in form.errors.items(): + for error in errors: + messages.error(request, f"Error in {field}: {error}") return redirect('voter_detail', voter_id=voter.id) def add_interaction(request, voter_id): @@ -352,10 +390,16 @@ def voter_geocode(request, voter_id): full_address = ", ".join([p for p in parts if p]) # Use a temporary instance to avoid saving until the user clicks "Save" in the modal - temp_voter = Voter(address=full_address) - temp_voter.geocode_address() + temp_voter = Voter( + address_street=street, + city=city, + state=state, + zip_code=zip_code, + address=full_address + ) + success, error_msg = temp_voter.geocode_address() - if temp_voter.latitude and temp_voter.longitude: + if success: return JsonResponse({ 'success': True, 'latitude': str(temp_voter.latitude), @@ -365,7 +409,7 @@ def voter_geocode(request, voter_id): else: return JsonResponse({ 'success': False, - 'error': 'Geocoding failed. Please check the address.' + 'error': f"Geocoding failed: {error_msg or 'No results found.'}" }) return JsonResponse({'success': False, 'error': 'Invalid request method.'}) diff --git a/test_manual_save_v2.py b/test_manual_save_v2.py new file mode 100644 index 0000000..ce510c7 --- /dev/null +++ b/test_manual_save_v2.py @@ -0,0 +1,64 @@ +import os +import django + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +django.setup() + +from core.models import Voter, Tenant +from decimal import Decimal + +# Ensure we have a tenant +tenant, _ = Tenant.objects.get_or_create(name="Test Tenant", slug="test-tenant") + +# 1. Create a voter with no coordinates +voter = Voter.objects.create( + tenant=tenant, + first_name="Manual", + last_name="Test", + address_street="1600 Amphitheatre Parkway", + city="Mountain View", + state="CA", + zip_code="94043" +) + +print(f"Initial voter: {voter.first_name} {voter.last_name}") +print(f"Coordinates: {voter.latitude}, {voter.longitude}") + +# 2. Simulate manual geocode (updating latitude/longitude before save) +manual_lat = Decimal("37.422476400") +manual_lon = Decimal("-122.084249900") + +# Simulate what happens in voter_edit view +# The form instance will have the new coordinates from request.POST +voter.latitude = manual_lat +voter.longitude = manual_lon + +print(f"Updating coordinates manually to: {voter.latitude}, {voter.longitude}") +voter.save() + +# 3. Reload from DB and verify +voter.refresh_from_db() +print(f"After save, coordinates in DB: {voter.latitude}, {voter.longitude}") + +if voter.latitude == manual_lat and voter.longitude == manual_lon: + print("SUCCESS: Manual coordinates preserved.") +else: + print("FAILURE: Manual coordinates overwritten or not saved.") + +# 4. Now test if changing address but NOT coordinates triggers geocode (which might overwrite) +voter.address_street = "1 Infinite Loop" +voter.city = "Cupertino" +# Note: we are NOT changing latitude/longitude here, so coords_provided should be False in save() +print(f"Changing address to {voter.address_street} but keeping coordinates.") +voter.save() + +voter.refresh_from_db() +print(f"After address change, coordinates in DB: {voter.latitude}, {voter.longitude}") +# It should have updated coordinates to Apple's HQ if geocoding worked, +# OR at least it shouldn't be the old Google HQ coordinates if it triggered geocode. +# Actually, it SHOULD have triggered geocode. + +if voter.latitude != manual_lat: + print("SUCCESS: Auto-geocode triggered for address change.") +else: + print("INFO: Auto-geocode might not have triggered or returned same coordinates (unlikely).")