From 0d11fc7d5da5c969e7acfce35dac7ddd398fc4c6 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 6 Feb 2026 14:10:46 +0000 Subject: [PATCH] Autosave: 20260206-141042 --- core/__pycache__/admin.cpython-311.pyc | Bin 105424 -> 106172 bytes core/__pycache__/forms.cpython-311.pyc | Bin 39292 -> 38438 bytes core/__pycache__/models.cpython-311.pyc | Bin 32646 -> 32658 bytes core/__pycache__/views.cpython-311.pyc | Bin 113546 -> 113069 bytes core/admin.py | 285 ++++++++++-------- core/forms.py | 15 +- core/models.py | 4 +- .../templates/core/voter_advanced_search.html | 4 + core/views.py | 36 +-- 9 files changed, 173 insertions(+), 171 deletions(-) diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index d4f449480fc4cb31daa81eeb2d2bbdbb7e2f5868..950bfbe1a5cb3d649ab6783af473d6b27a947a45 100644 GIT binary patch delta 16308 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 delta 16202 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 Ag#Z8m diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 33120a9c379623fb883e0b3a6bc1c96088aff790..8bace115a512435d6a5dd3f54be68cc8b1a7d513 100644 GIT binary patch delta 2880 zcma)83s96*6yEd0uDZM6@>rH-ciE*ya0OJ55b=fa2(sDPKzvZ~$Xyi_7XBZ_@kyeI znI@f@Nv%m6$HG_Y@1x8fR5nhi$!KLdWmFTDlR=bYLUD>YEFi2h9AnH@+;371i=YMQaSLws9_3C!q1es7zNgqAC_I6c5qQ*OLA5y? z!q94dkj*#Id@%c0v(TF%x=Aqy+}hQHi>*zH#ZYWxJ_KDf;Kmq(yu++k!Up-Rm@0r6 zwAgEdFRF8@$}1M+R(KY;#Y|jp&(IjNrZ}_nv!_hUDaB9iq7W)zMr?Pu*4&hHUTfaw zIjc=<*Cw9VCN|{Yh+&n1liPKZ<(G%85R`8+cVCfB!wUjb``A!y!S=K;*o|uw-dF8l z0;vd-CpzT1L=UKUk^#Y1?0Qs>*OMgJiQVZX_}R!S%HvFa0FR9-fPL7JXO!bcF9p?p zn(-cQ;isu5}|sVNu_tObgTmlQntd|m^mq2eT+;A z_H)`B&nBUBOfDSb)IDPkD?etsR@|GK?5Dn-dd^S%3GF8C1k*b-Vl=H|CD0Q5pDz=} zTHpj0kNw@}f~J)w5PYb>Da#^oWqK{N%H8QEP=3awpJPZyil6++j1(b+hq8@nrinZR zr*PYZ*s#8gOpjGr##;fqx_Y!bVPA;^Qi}GW+NdUWW<-;>H+_TGMy$d@Yo~itX z4ZCqt-s`YW?#Ocr8XlwX$e@D-%Bmkq!+Sibo@xOql|Pjt?*zf5 zz1FL}j%N$E4!OeKoemJ&=-l9jX34ME^_=p4s964PIq!A|9N67C^zZw8`+*N!z6qqLx-Bvc6XeaYP-1B5QIPm=A zAKXuwjNd+S{60#}I9f2Bt<<1EU5Oq8OFXb2D@zu@A^fVOT-QS+Aq2j}VqWQ1m5nC- z%@X;5tSQq7>YJn|u%hj$T)62^!rlQTV2sX5;q4pza1s(cLWA4E566!Az5)?Fh5->6 z@Z&jF-HiT73?w8dyqm<(OVU2qG(eKL3cIeXKawaC5brt;) zSV&0VxtqWucg%kXgjm227_~#397NSwh3%(OC>l{oDFSwT+CJ`Fij)wtv6z{6zS)5 zNsIlQZLGt@>h7Vtqq(FmG{ahaY~d$jWw z=2U|+g}rQI^t~OXK3i~dvoFc^_EB}SSI5vhTFnL0&m-V>Y$%QTI!dyySu0G!qf4Bs z7a5lnM3*h5EcG`a^=Kv(37+Tv7A=!tbZ^#|E{_~Q*6D^iy~3v3K}+>LBxxih*hYif zha^oNvC=CH=F)>R}gsJoc>n!k_d!@`d0m58{ny zCCI~$jWhA&`t8aBCjSwupSv0C2youcr~{X5m<~Tx-M>-CXzQofh+C?MnfH UrXe77Dei?$MK6|O$>!+)0QuH8$p8QV delta 3283 zcmb7G4OCRs7QW}s+yMu{!5NsjFgU}XLWY1k4?V3F-_wC=A>g$(4~A zJk$Jv-2GxdR4bE|Wq230?3Z4yT9K93D}Am^>$S8@o=owo$BNcF``m#^8Q0=nnD3su z_dfgEXPIh%;)5O#SJhW(bfC~Wp197ZTZkm1>dCh{s+XJ?bwpmg?! z6a!g?s3WLEIHWMy=lIAG`Q15sBK)07`{18*bAvQ#w$Didqj?WIT{W1a5StgLyC$m$ zYx9yKj?#|4Z#d{lPN9QU!{NO5f^qbrON4hYj(9kb-$?q{h&Ro9OZbo~K7pcw*`!-; zC|JnHvYhr)=~9VB^bs5@OdH3(rznXHS%kbItL8Im?h}HE{xQh=0ToIJA1l1wx6XuP zHx`o*75z_rwZN2sF3w<&j}g)kmQip>%0%Sd^apSJoC>;l=}!7O&3jy9 z0WP}bx`bZrp+OjzsPN;O_-Zt=MsggZlADXO2058l9tQvcyfn=C}Yl7LF$C#6rDhnA-TXqpK41 zi!KJkI*pwXE-MQr*X|>y;pRvY-nPF=WO;+*2qBS>UD{20;7qAV2H-;0H0^ophM)$^ zlI`Stutx_hFVpERpz?c!9}q6epEgI5s7u)XH^OCTE6>rz651TZZgI z?SIC!zrye3dna9_-;PWo^+V@U**t*S(6U9>nSx|RX+L-?>_mjOD!iH@6ev++79@mt za8~-ru>477gqozlm(H_OIZEimvSPJLsc5Hy6U*~S#-8QxTuYY&+PmJomNFk=SG1Bt z@Yss=Lm{2~A04}r6 z9ww&=dK4nuj*U=G^#rWNCOXeg!ZU8;1X(WESag-)SmIHLPvM zs&FhK3K9N#C5sqhan;66kVRgXq+1(~#f(A(*Oe@0WVs90t^basLTQ~{NT&LFc)qSW zxIfMHy+p%`St^|L&WKCL{^Pc$zM+Vu!;*#@NE7U82q-&IYe8FtI;d`nqh~}GoNX-T zEedC=w3_t8_tFC4I%@I&!YD>QF78l=oXN`x(x zavPtaP?g;6iDu{9Osa86*AqxCFbcW!qm9yv@YCI<(IaHz0$#&RzkvFCESV>96|Y5L zm2oPzt{y$gPSKc*(7xRW-`!)^0QKyP4%Q|$tk`rp7+yA2NC>-Wct&Wsw~^0Q<~gRh zXM%J5-}|h(7tqhumBxlbglDl4+7&jraq-Il;&FI?YiTf*IS>9q^lar|K9^=HCytZ?^R}m%!{qR^P~(w&eyKN@%2g33sYl<> zp~Kkn&=0{Zq7+uoRfE}oN5DbIBsz3t=0Z$cHkl4LwzY0%*HLy;?4um)y2NfK>>j{Qb#?@^ zlad`u?DSy8oz-4e8d<4g#e;28ww2hjXDf{@1c?m@=R}DFkJaJGLMUvHjKb}#ibtvfOgHpr3j+*1 XYScbqduo1wn?(2_2t2A6Sfu|0yb_=d diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 9a15037c6fb6f5653a1783d591777a10383527bc..54e855180c568931fb60e6ad3d03b6f80c0b1589 100644 GIT binary patch delta 104 zcmZqs&p7EnBkyuvUM>b8c*EA3d441BOJ{CLXGcF5PZ!4!*Z9rfof(-F_b@Xs{Agf+ v!4FIy9_P&f%zPtz delta 92 zcmbRApRw&fBkyuvUM>b8IGxv+xqBn;OJ_#@&5SOLObWZ085n*vFu>pkCKhqF4-7D( j!GE)|TOX6)0sae-Nf&YpFGv(!lqk9)QMCE6$9XdVEm0o0 diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index c8cfc4029b41b44b094b5a60557d15b4175e67e0..156fa51ecaf544d1592e3fd99c07928d747a00dd 100644 GIT binary patch delta 17746 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 Jb9u}5e*p3^3)TPt delta 18251 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 diff --git a/core/admin.py b/core/admin.py index e1d3803..51dd659 100644 --- a/core/admin.py +++ b/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. Download failed records"), 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. Download failed records"), 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. Download failed records"), 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. Download failed records"), 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. Download failed records"), 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. Download failed records"), 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. Download failed records"), 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. Download failed records"), 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" diff --git a/core/forms.py b/core/forms.py index e3764db..670ed7f 100644 --- a/core/forms.py +++ b/core/forms.py @@ -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(): diff --git a/core/models.py b/core/models.py index 4723cea..032d9ec 100644 --- a/core/models.py +++ b/core/models.py @@ -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) diff --git a/core/templates/core/voter_advanced_search.html b/core/templates/core/voter_advanced_search.html index 6305f05..769d26f 100644 --- a/core/templates/core/voter_advanced_search.html +++ b/core/templates/core/voter_advanced_search.html @@ -49,6 +49,10 @@ {{ form.precinct }} +
+ + {{ form.email }} +
{{ form.phone_type }} diff --git a/core/views.py b/core/views.py index b15e6fa..a9ae83e 100644 --- a/core/views.py +++ b/core/views.py @@ -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