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,178 @@
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from analytics.services import attribute_lead_from_request
|
||||
from contacts.models import Channel, ConsentRecord, Contact
|
||||
from leads.models import Lead
|
||||
from messaging.services import (
|
||||
channel_preferences,
|
||||
parse_unsubscribe_token,
|
||||
process_unsubscribe_token,
|
||||
set_channel_preferences,
|
||||
unsubscribe_all,
|
||||
)
|
||||
from public.forms import ContactForm, NotifyForm
|
||||
|
||||
|
||||
def home(request):
|
||||
return render(request, "public/home.html")
|
||||
|
||||
|
||||
def about(request):
|
||||
return render(request, "public/about.html")
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def contact(request):
|
||||
if request.method == "POST":
|
||||
form = ContactForm(request.POST)
|
||||
if form.is_valid():
|
||||
data = form.cleaned_data
|
||||
defaults = {
|
||||
"first_name": data["first_name"],
|
||||
"last_name": data.get("last_name") or "",
|
||||
"phone": data.get("phone") or "",
|
||||
"source": Contact.Source.CONTACT_FORM,
|
||||
}
|
||||
postal = Contact.make_postal_address(
|
||||
line1=data.get("address_line1") or "",
|
||||
line2=data.get("address_line2") or "",
|
||||
city=data.get("address_city") or "",
|
||||
state=data.get("address_state") or "",
|
||||
zip_code=data.get("address_zip") or "",
|
||||
)
|
||||
if Contact.postal_address_has_content(postal):
|
||||
defaults["postal_address"] = postal
|
||||
contact_obj, _ = Contact.objects.update_or_create(
|
||||
email=data["email"].lower(),
|
||||
defaults=defaults,
|
||||
)
|
||||
ConsentRecord.objects.update_or_create(
|
||||
contact=contact_obj,
|
||||
channel=Channel.EMAIL,
|
||||
defaults={"opted_in": True, "reason": "contact_form"},
|
||||
)
|
||||
if (data.get("phone") or "").strip():
|
||||
ConsentRecord.objects.update_or_create(
|
||||
contact=contact_obj,
|
||||
channel=Channel.SMS,
|
||||
defaults={"opted_in": True, "reason": "contact_form"},
|
||||
)
|
||||
interest = data.get("interest") or ""
|
||||
interest_label = dict(ContactForm.INTEREST_CHOICES).get(interest, interest)
|
||||
body = data.get("message") or ""
|
||||
if interest_label:
|
||||
body = f"Interest: {interest_label}\n\n{body}".strip()
|
||||
lead = Lead.objects.create(
|
||||
contact=contact_obj,
|
||||
message=body,
|
||||
status=Lead.Status.NEW,
|
||||
)
|
||||
attribute_lead_from_request(request, lead)
|
||||
messages.success(request, "Thanks — Monica will be in touch soon.")
|
||||
return redirect(f"{reverse('public:contact')}?sent=1")
|
||||
else:
|
||||
form = ContactForm()
|
||||
return render(request, "public/contact.html", {"form": form})
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def under_construction(request):
|
||||
"""Direct route (also used by middleware). Accepts notify-me emails."""
|
||||
if request.method == "POST":
|
||||
form = NotifyForm(request.POST)
|
||||
if form.is_valid():
|
||||
email = form.cleaned_data["email"].lower()
|
||||
Contact.objects.get_or_create(
|
||||
email=email,
|
||||
defaults={
|
||||
"first_name": "",
|
||||
"source": Contact.Source.NOTIFY_ME,
|
||||
},
|
||||
)
|
||||
messages.success(request, "You're on the list — we'll email when we launch.")
|
||||
return redirect("public:under_construction")
|
||||
else:
|
||||
form = NotifyForm()
|
||||
return render(request, "public/under_construction.html", {"form": form})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def unsubscribe_one_click(request, token: str):
|
||||
"""
|
||||
One-click opt-out for the channel encoded in the token.
|
||||
|
||||
CSRF-exempt so mail clients can POST List-Unsubscribe=One-Click (RFC 8058).
|
||||
"""
|
||||
ok = process_unsubscribe_token(token)
|
||||
if not ok:
|
||||
return render(request, "public/unsubscribe.html", {"valid": False})
|
||||
return redirect("public:unsubscribe", token=token)
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def unsubscribe(request, token: str):
|
||||
"""
|
||||
Signed-token preference center for email / SMS / postcard.
|
||||
|
||||
GET ?one_click=1 opts out the token channel then redirects here.
|
||||
POST saves checkboxes or unsubscribes from all channels.
|
||||
"""
|
||||
contact, token_channel = parse_unsubscribe_token(token)
|
||||
if not contact:
|
||||
return render(
|
||||
request,
|
||||
"public/unsubscribe.html",
|
||||
{"valid": False},
|
||||
)
|
||||
|
||||
if request.method == "GET" and request.GET.get("one_click") in {
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
}:
|
||||
process_unsubscribe_token(token)
|
||||
return redirect("public:unsubscribe", token=token)
|
||||
|
||||
if request.method == "POST":
|
||||
action = (request.POST.get("action") or "save").strip()
|
||||
if action == "unsubscribe_all":
|
||||
unsubscribe_all(contact, reason="preferences_unsubscribe_all")
|
||||
messages.success(
|
||||
request, "You are unsubscribed from all marketing channels."
|
||||
)
|
||||
else:
|
||||
prefs = {
|
||||
Channel.EMAIL: "consent_email" in request.POST,
|
||||
Channel.SMS: "consent_sms" in request.POST,
|
||||
Channel.POSTCARD: "consent_postcard" in request.POST,
|
||||
}
|
||||
set_channel_preferences(
|
||||
contact, prefs, reason="preferences_save"
|
||||
)
|
||||
messages.success(request, "Your communication preferences were saved.")
|
||||
return redirect("public:unsubscribe", token=token)
|
||||
|
||||
contact = Contact.objects.prefetch_related("consents").get(pk=contact.pk)
|
||||
prefs = channel_preferences(contact)
|
||||
identity = contact.email or contact.phone or contact.full_name or "your profile"
|
||||
return render(
|
||||
request,
|
||||
"public/unsubscribe.html",
|
||||
{
|
||||
"valid": True,
|
||||
"contact": contact,
|
||||
"identity": identity,
|
||||
"prefs": prefs,
|
||||
"token_channel": token_channel,
|
||||
"token": token,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def page_not_found(request, exception):
|
||||
return render(request, "public/404.html", status=404)
|
||||
Reference in New Issue
Block a user