from django.contrib import messages from django.contrib.auth.decorators import login_required from django.db.models import Prefetch, Q from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.views.decorators.http import require_GET, require_http_methods from contacts.models import Channel, ConsentRecord, Contact from contacts.nominatim import NominatimError, suggest_addresses from messaging.services import channel_preferences, set_channel_preferences def _consent_flags(contact: Contact) -> dict[str, bool]: return channel_preferences(contact) def _postal_from_post(post) -> dict: return Contact.make_postal_address( line1=post.get("address_line1", ""), line2=post.get("address_line2", ""), city=post.get("address_city", ""), state=post.get("address_state", ""), zip_code=post.get("address_zip", ""), country=post.get("address_country", "US"), ) @login_required def contact_list(request): contacts = Contact.objects.prefetch_related( Prefetch("consents", queryset=ConsentRecord.objects.all()) ).all() q = (request.GET.get("q") or "").strip() if q: contacts = contacts.filter( Q(first_name__icontains=q) | Q(last_name__icontains=q) | Q(email__icontains=q) | Q(phone__icontains=q) ) rows = list(contacts[:200]) for contact in rows: contact.consent_flags = _consent_flags(contact) return render( request, "contacts/list.html", {"contacts": rows, "q": q}, ) @login_required @require_http_methods(["GET", "POST"]) def contact_detail(request, pk): contact = get_object_or_404( Contact.objects.prefetch_related("consents"), pk=pk ) if request.method == "POST": contact.postal_address = _postal_from_post(request.POST) contact.notes = (request.POST.get("notes") or "").strip() contact.save(update_fields=["postal_address", "notes", "updated_at"]) set_channel_preferences( contact, { Channel.EMAIL: "consent_email" in request.POST, Channel.SMS: "consent_sms" in request.POST, Channel.POSTCARD: "consent_postcard" in request.POST, }, reason="portal_manual", ) messages.success(request, "Contact updated.") return redirect("contacts:detail", pk=contact.pk) prefs = _consent_flags(contact) return render( request, "contacts/detail.html", {"contact": contact, "prefs": prefs}, ) @login_required def contact_import(request): return render(request, "contacts/import.html") @require_GET def address_suggest(request): """ Backend proxy for Nominatim search. Browser JS must call this URL only — never Nominatim directly. """ q = (request.GET.get("q") or "").strip() if len(q) < 3: return JsonResponse({"results": []}) try: limit = int(request.GET.get("limit") or 5) except (TypeError, ValueError): limit = 5 try: results = suggest_addresses(q, limit=limit) except NominatimError as exc: return JsonResponse({"error": str(exc), "results": []}, status=502) return JsonResponse({"results": results})