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")