Instrument webhooks, fix compose layout, and ship social connect.
Deploy Beta / unit-tests (push) Successful in 10s
Deploy Beta / docker (push) Successful in 15s
Deploy Beta / deploy-beta (push) Successful in 1m40s

Add Grafana-friendly webhook request logging, Quill overflow fix, New Contact flow, in-place postcard campaign compose, and functional social account connect + composer.
This commit is contained in:
2026-08-10 05:15:49 -05:00
parent b3a6ee0cd0
commit 58258f2875
10 changed files with 778 additions and 311 deletions
+74
View File
@@ -1,5 +1,7 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.db.models import Prefetch, Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
@@ -7,6 +9,7 @@ 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 contacts.services import upsert_contact
from messaging.services import channel_preferences, set_channel_preferences
@@ -48,6 +51,76 @@ def contact_list(request):
)
@login_required
@require_http_methods(["GET", "POST"])
def contact_create(request):
form = {
"first_name": "",
"last_name": "",
"email": "",
"phone": "",
"address_line1": "",
"address_line2": "",
"address_city": "",
"address_state": "",
"address_zip": "",
"address_country": "US",
"notes": "",
"consent_email": True,
"consent_sms": False,
"consent_postcard": True,
}
if request.method == "POST":
for key in list(form.keys()):
if key.startswith("consent_"):
form[key] = key in request.POST
else:
form[key] = (request.POST.get(key) or "").strip()
email = form["email"].lower()
errors: list[str] = []
if not form["first_name"]:
errors.append("First name is required.")
if not email:
errors.append("Email is required.")
else:
try:
validate_email(email)
except ValidationError:
errors.append("Enter a valid email address.")
postal = _postal_from_post(request.POST)
has_postal = Contact.postal_address_has_content(postal)
if form["consent_sms"] and not form["phone"]:
errors.append("Phone is required for SMS consent.")
if form["consent_postcard"] and not has_postal:
# Soft: allow save but clear postcard consent if no address
form["consent_postcard"] = False
if not errors:
contact, created, reason = upsert_contact(
email=email,
first_name=form["first_name"],
last_name=form["last_name"],
phone=form["phone"],
postal_address=postal if has_postal else None,
source=Contact.Source.MANUAL,
notes_append=form["notes"],
)
set_channel_preferences(
contact,
{
Channel.EMAIL: form["consent_email"],
Channel.SMS: form["consent_sms"],
Channel.POSTCARD: form["consent_postcard"],
},
reason="portal_manual",
)
verb = "Added" if created else f"Updated (matched by {reason or 'email'})"
messages.success(request, f"{verb} {contact}.")
return redirect("contacts:detail", pk=contact.pk)
for err in errors:
messages.error(request, err)
return render(request, "contacts/create.html", {"form": form})
@login_required
@require_http_methods(["GET", "POST"])
def contact_detail(request, pk):
@@ -76,6 +149,7 @@ def contact_detail(request, pk):
{"contact": contact, "prefs": prefs},
)
@login_required
def contact_import(request):
return render(request, "contacts/import.html")