Template
Add shopper accounts, reviews, tracking, and seed_demo (#10)
## Summary - Closes #9. Port shop-gated buyer accounts (register/login/profile, password reset, order history), purchase-gated reviews, and Stripe customer ids. Cards stay on Stripe. - Slim public contact form to email + interest + message. Shipping tracking status, scan events, paste-in numbers, and EasyPost webhook when FEATURE_SHIPPING is on. - Add template-neutral `seed_demo` (generic SKUs, demo shoppers, guest orders, tracking stubs). Prod refused. `--reset` deletes tagged demo rows after invoices. ## Test plan - [ ] `cd site && uv run python manage.py test` (173 tests passed locally) - [ ] Shop on: `/account/register`, login, profile, password reset, claim guest orders by email - [ ] Shop off: `/account/` URLs absent; Sign in / Account links hidden - [ ] Checkout as logged-in buyer stores `Order.user` and Stripe customer id; product review only after paid purchase - [ ] Shipping on: portal paste-in tracking + EasyPost webhook updates status; shipping off: orders still work without tracking UI - [ ] `seed_demo` refused in prod; `--reset` rebuilds tagged catalog/shoppers without deleting staff invoices Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
1 parent
9cdce7a897
commit
f3e34f5099
66 files changed
+3051
-285
No files matched your search
+3
-26
@@ -49,6 +49,7 @@ def robots_txt(request):
|
||||
"Allow: /",
|
||||
"Disallow: /portal/",
|
||||
"Disallow: /accounts/",
|
||||
"Disallow: /account/",
|
||||
"Disallow: /admin/",
|
||||
"Disallow: /api/",
|
||||
f"Sitemap: {site}/sitemap.xml",
|
||||
@@ -99,48 +100,24 @@ def contact(request):
|
||||
form = ContactForm(request.POST)
|
||||
if form.is_valid():
|
||||
data = form.cleaned_data
|
||||
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 "",
|
||||
)
|
||||
submitted_email = data["email"].lower()
|
||||
contact_obj, _created, match_reason = upsert_contact(
|
||||
email=submitted_email,
|
||||
first_name=data["first_name"],
|
||||
last_name=data.get("last_name") or "",
|
||||
phone=data.get("phone") or "",
|
||||
postal_address=postal
|
||||
if Contact.postal_address_has_content(postal)
|
||||
else None,
|
||||
source=Contact.Source.CONTACT_FORM,
|
||||
merge_phone_address=False,
|
||||
)
|
||||
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"},
|
||||
)
|
||||
if Contact.postal_address_has_content(postal):
|
||||
ConsentRecord.objects.get_or_create(
|
||||
contact=contact_obj,
|
||||
channel=Channel.POSTCARD,
|
||||
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()
|
||||
if (
|
||||
match_reason in {"phone", "address"}
|
||||
match_reason
|
||||
and (contact_obj.email or "").lower() != submitted_email
|
||||
):
|
||||
body = (
|
||||
|
||||
Reference in new issue
Block a user