import logging from django.conf import settings from rest_framework import authentication, mixins, serializers from rest_framework.authentication import get_authorization_header from rest_framework.exceptions import AuthenticationFailed from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from basic_info import models as basic_models from flower.viewsets import LimitedLimitOffsetPagination from shipment.models import Shipment logger = logging.getLogger(__name__) class AgentAccessPrincipal: id = None username = "agent" is_staff = False is_superuser = False is_anonymous = False is_authenticated = True class AgentAccessKeyAuthentication(authentication.BaseAuthentication): """ 使用固定 AGENT_ACCESS_KEY 的轻量认证。 约定: - 通过 Authorization 头直接传入密钥 - 不使用 Bearer 前缀 """ def authenticate(self, request): configured_key = str(getattr(settings, "AGENT_ACCESS_KEY", "") or "").strip() if not configured_key: logger.warning("Agent API authentication failed: AGENT_ACCESS_KEY is not configured") raise AuthenticationFailed("AGENT_ACCESS_KEY 未配置") provided_key = get_authorization_header(request).decode("utf-8").strip() if not provided_key: raise AuthenticationFailed("缺少 Authorization 请求头") if provided_key != configured_key: raise AuthenticationFailed("AGENT_ACCESS_KEY 无效") return AgentAccessPrincipal(), None def authenticate_header(self, request): return "AgentAccessKey" class AgentUnshippedShipmentQuerySerializer(serializers.Serializer): merchant_id = serializers.IntegerField(min_value=1) area = serializers.CharField(allow_blank=False, max_length=30) limit = serializers.IntegerField(required=False, min_value=1) offset = serializers.IntegerField(required=False, min_value=0) def validate(self, attrs): area = attrs["area"].strip() if not area: raise serializers.ValidationError({"area": "地区不能为空"}) merchant = basic_models.Merchant.objects.filter(id=attrs["merchant_id"]).first() if merchant is None: raise serializers.ValidationError({"merchant_id": "商户不存在"}) attrs["area"] = area attrs["merchant"] = merchant return attrs class AgentShipmentListSerializer(serializers.ModelSerializer): customer_name = serializers.CharField(source="customer.name", read_only=True) status_display = serializers.CharField(source="get_status_display", read_only=True) class Meta: model = Shipment fields = [ "id", "merchant_id", "customer", "customer_name", "shipment_date", "address", "contact_name", "contact_phone", "area", "remark", "status", "status_display", "external_id", "delivery", "created_at", "updated_at", ] class AgentUnshippedShipmentListView(mixins.ListModelMixin, GenericAPIView): """ Agent 专用:查询尚未进入送货单的出货单列表。 GET /api/v2/ai/shipments/unshipped/?merchant_id=&area=<地区>&limit=&offset= """ authentication_classes = [AgentAccessKeyAuthentication] permission_classes = [IsAuthenticated] serializer_class = AgentShipmentListSerializer pagination_class = LimitedLimitOffsetPagination def get(self, request): query_serializer = AgentUnshippedShipmentQuerySerializer(data=request.query_params) query_serializer.is_valid(raise_exception=True) self.validated_query = query_serializer.validated_data return self.list(request) def get_queryset(self): validated_query = getattr(self, "validated_query", None) if validated_query is None: query_serializer = AgentUnshippedShipmentQuerySerializer(data=self.request.query_params) query_serializer.is_valid(raise_exception=True) validated_query = query_serializer.validated_data merchant = validated_query["merchant"] area = validated_query["area"] return ( Shipment.objects.filter( merchant=merchant, area=area, delivery__isnull=True, ) .select_related("customer", "merchant") .order_by("-created_at", "-id") )