Add customer accounts, shipment tracking, and purchase reviews.
CI / test (pull_request) Successful in 35s

Shoppers can register, save shipping details, and view order history while cards stay on Stripe. EasyPost tracker updates (including numbers from Pirate Ship) and 1–5 star reviews are limited to buyers. The contact form now only asks for email and a message.
This commit is contained in:
2026-09-07 06:37:52 -05:00
parent 23a6035ba8
commit c9b81ceed7
59 changed files with 1905 additions and 275 deletions
+74 -1
View File
@@ -1,18 +1,24 @@
import json
import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods, require_POST
from shipping.models import Shipment
from shipping.services import (
ShippingError,
apply_tracker_payload,
attach_tracking,
buy_label,
create_shipment_for_order,
pirate_ship_csv,
quote_rates,
refresh_tracking,
)
from shop.models import Order
@@ -77,6 +83,40 @@ def shipment_buy(request, pk):
return redirect("shipping:shipment_detail", pk=shipment.pk)
@login_required
@require_POST
def shipment_attach_tracking(request, pk):
shipment = get_object_or_404(Shipment, pk=pk)
try:
attach_tracking(
shipment,
tracking_number=request.POST.get("tracking_number") or "",
carrier=request.POST.get("carrier") or "",
)
except ShippingError as exc:
messages.error(request, str(exc))
except Exception as exc: # noqa: BLE001
logger.exception("attach tracking failed")
messages.error(request, f"Could not save tracking: {exc}")
else:
messages.success(request, "Tracking saved.")
return redirect("shipping:shipment_detail", pk=shipment.pk)
@login_required
@require_POST
def shipment_refresh_tracking(request, pk):
shipment = get_object_or_404(Shipment, pk=pk)
try:
refresh_tracking(shipment)
except Exception as exc: # noqa: BLE001
logger.exception("refresh tracking failed")
messages.error(request, f"Could not refresh tracking: {exc}")
else:
messages.success(request, "Tracking updated.")
return redirect("shipping:shipment_detail", pk=shipment.pk)
@login_required
@require_http_methods(["GET"])
def pirate_ship_export(request):
@@ -84,3 +124,36 @@ def pirate_ship_export(request):
response = HttpResponse(body, content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="pirate-ship-orders.csv"'
return response
@csrf_exempt
@require_http_methods(["POST"])
def easypost_webhook(request):
secret = (getattr(settings, "EASYPOST_WEBHOOK_SECRET", "") or "").strip()
if secret:
got = (
request.headers.get("X-Webhook-Secret")
or request.GET.get("token")
or ""
).strip()
if got != secret:
return HttpResponseBadRequest("invalid secret")
try:
payload = json.loads(request.body.decode("utf-8") or "{}")
except json.JSONDecodeError:
return HttpResponseBadRequest("invalid json")
result = payload.get("result") or payload
if not isinstance(result, dict):
return HttpResponse("ok")
tracker_id = (result.get("id") or "").strip()
tracking = (result.get("tracking_code") or result.get("tracking_number") or "").strip()
shipment = None
if tracker_id:
shipment = Shipment.objects.filter(tracker_id=tracker_id).first()
if shipment is None and tracking:
shipment = Shipment.objects.filter(tracking_number=tracking).first()
if shipment is None:
return HttpResponse("ok")
apply_tracker_payload(shipment, result)
logger.info("easypost tracker updated shipment %s", shipment.pk)
return HttpResponse("ok")