Route Cookie Preferences to /terms/, link analytics banner there, teal mobile nav, and give the contact form more side padding on small screens.
240 lines
8.0 KiB
Python
240 lines
8.0 KiB
Python
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.http import HttpResponse
|
|
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_GET, 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
|
|
from public.notifications import notify_admins_of_contact_form
|
|
|
|
|
|
def _public_site_base(request) -> str:
|
|
base = (settings.PUBLIC_SITE_URL or "").rstrip("/")
|
|
if base:
|
|
return base
|
|
return request.build_absolute_uri("/").rstrip("/")
|
|
|
|
|
|
def home(request):
|
|
return render(request, "public/home.html")
|
|
|
|
|
|
def about(request):
|
|
return render(request, "public/about.html")
|
|
|
|
|
|
def terms(request):
|
|
return render(request, "public/terms.html")
|
|
|
|
|
|
@require_GET
|
|
def robots_txt(request):
|
|
site = _public_site_base(request)
|
|
body = "\n".join(
|
|
[
|
|
"User-agent: *",
|
|
"Allow: /",
|
|
"Disallow: /portal/",
|
|
"Disallow: /accounts/",
|
|
"Disallow: /admin/",
|
|
"Disallow: /api/",
|
|
f"Sitemap: {site}/sitemap.xml",
|
|
"",
|
|
]
|
|
)
|
|
return HttpResponse(body, content_type="text/plain; charset=utf-8")
|
|
|
|
|
|
@require_GET
|
|
def sitemap_xml(request):
|
|
site = _public_site_base(request)
|
|
paths = [
|
|
("public:home", "1.0", "weekly"),
|
|
("public:about", "0.8", "monthly"),
|
|
("public:contact", "0.9", "monthly"),
|
|
("public:terms", "0.5", "yearly"),
|
|
]
|
|
urls = []
|
|
for name, priority, changefreq in paths:
|
|
path = reverse(name)
|
|
urls.append(
|
|
" <url>\n"
|
|
f" <loc>{site}{path}</loc>\n"
|
|
f" <changefreq>{changefreq}</changefreq>\n"
|
|
f" <priority>{priority}</priority>\n"
|
|
" </url>"
|
|
)
|
|
body = (
|
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
|
+ "\n".join(urls)
|
|
+ "\n</urlset>\n"
|
|
)
|
|
return HttpResponse(body, content_type="application/xml; charset=utf-8")
|
|
|
|
|
|
@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)
|
|
notify_admins_of_contact_form(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)
|