generated from westfarn/web_django_template
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.
160 lines
5.3 KiB
Python
160 lines
5.3 KiB
Python
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, 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
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@login_required
|
|
def shipment_list(request):
|
|
shipments = Shipment.objects.select_related("order")[:200]
|
|
labeled = Shipment.objects.filter(status=Shipment.Status.LABELED).values_list(
|
|
"order_id", flat=True
|
|
)
|
|
unshipped = Order.objects.filter(status=Order.Status.PAID).exclude(pk__in=labeled)
|
|
return render(
|
|
request,
|
|
"shipping/list.html",
|
|
{"shipments": shipments, "unshipped": unshipped},
|
|
)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def shipment_create(request):
|
|
order = get_object_or_404(Order, pk=request.POST.get("order"))
|
|
try:
|
|
weight = int(request.POST.get("weight_oz") or "16")
|
|
except ValueError:
|
|
weight = 16
|
|
try:
|
|
shipment = create_shipment_for_order(order, weight_oz=weight)
|
|
quote_rates(shipment)
|
|
except ShippingError as exc:
|
|
messages.error(request, str(exc))
|
|
return redirect("shipping:shipment_list")
|
|
except Exception as exc: # noqa: BLE001
|
|
logger.exception("rate quote failed")
|
|
messages.error(request, f"Could not quote rates: {exc}")
|
|
return redirect("shipping:shipment_list")
|
|
return redirect("shipping:shipment_detail", pk=shipment.pk)
|
|
|
|
|
|
@login_required
|
|
def shipment_detail(request, pk):
|
|
shipment = get_object_or_404(Shipment.objects.select_related("order"), pk=pk)
|
|
return render(request, "shipping/detail.html", {"shipment": shipment})
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def shipment_buy(request, pk):
|
|
shipment = get_object_or_404(Shipment, pk=pk)
|
|
rate_id = (request.POST.get("rate_id") or "").strip()
|
|
try:
|
|
buy_label(shipment, rate_id=rate_id)
|
|
except ShippingError as exc:
|
|
messages.error(request, str(exc))
|
|
except Exception as exc: # noqa: BLE001
|
|
logger.exception("buy label failed")
|
|
messages.error(request, f"Could not buy label: {exc}")
|
|
else:
|
|
messages.success(request, f"Label created for {shipment.order.number}.")
|
|
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):
|
|
body = pirate_ship_csv()
|
|
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")
|