Add Django site, Docker packaging, and beta/prod Gitea deploys.
Unignore site/ (was blocked by mkdocs /site rule), add compose/Docker/uv tooling, and split deploys so push to main goes to beta while prod stays manual.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
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})
|
||||
Reference in New Issue
Block a user