def post(self, request: Request, pk: str): """ Select your preferred rates to buy a shipment label. """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( f"The shipment is '{shipment.status}' and therefore already {ShipmentStatus.purchased.value}", code="state_error", status_code=status.HTTP_409_CONFLICT, ) # Submit shipment to carriers response: Shipment = (SerializerDecorator[ShipmentPurchaseSerializer]( context=request, data={ **Shipment(shipment).data, **SerializerDecorator[ShipmentPurchaseData](data=request.data).data, }, ).save().instance) # Update shipment state SerializerDecorator[ShipmentSerializer](shipment, data=DP.to_dict(response), context=request).save() create_shipment_tracker(shipment, context=request) return Response(Shipment(shipment).data)
class ShippingDetails(APIView): @swagger_auto_schema( tags=['Proxy'], operation_id=f"{ENDPOINT_ID}buy_label", operation_summary="Buy a shipment label", request_body=ShippingRequest(), responses={ 200: Shipment(), 400: ErrorResponse() }, ) def post(self, request: Request): """ Once the shipping rates are retrieved, provide the required info to submit the shipment by specifying your preferred rate. """ payload = SerializerDecorator[ShippingRequestValidation]( data=request.data).data response = Shipments.create( payload, resolve_tracking_url=( lambda tracking_number, carrier_name: reverse( "purplship.server.proxy:shipment-tracking", kwargs=dict(tracking_number=tracking_number, carrier_name=carrier_name)))) return Response(Shipment(response).data, status=status.HTTP_201_CREATED)
class ShipmentParcels(APIView): @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}add_parcel", operation_summary="Add a shipment parcel", responses={ 200: Shipment(), 400: ErrorResponse() }, request_body=ParcelData(), ) def post(self, request: Request, pk: str): """ Add a parcel to an existing shipment for a multi-parcel shipment. """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "Shipment already 'purchased'", code="state_error", status_code=status.HTTP_409_CONFLICT, ) parcel = (SerializerDecorator[ParcelSerializer]( data=request.data, context=request).save().instance) shipment.shipment_parcels.add(parcel) reset_related_shipment_rates(shipment) return Response(Shipment(shipment).data)
def get(self, request: Request, pk: str): """ Retrieve a shipment. """ shipment = models.Shipment.access_by(request).get(pk=pk) return Response(Shipment(shipment).data)
def post(self, request: Request, pk: str): """ Add the customs declaration for the shipment if non existent. """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "Shipment already 'purchased'", code="state_error", status_code=status.HTTP_409_CONFLICT, ) if shipment.customs is not None: raise PurplShipApiException( "Shipment customs declaration already defined", code="state_error", status_code=status.HTTP_409_CONFLICT, ) payload: dict = dict(customs=DP.to_dict(request.data), shipment_rates=[], messages=[]) SerializerDecorator[ShipmentSerializer](shipment, data=payload, context=request).save() return Response(Shipment(shipment).data)
def post(self, request: Request, pk: str): """ Add one or many options to your shipment.<br/> **eg:**<br/> - add shipment **insurance** - specify the preferred transaction **currency** - setup a **cash collected on delivery** option ```json { "insurance": 120, "currency": "USD" } ``` And many more, check additional options available in the [reference](#operation/all_references). """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "Shipment already 'purchased'", code="state_error", status_code=status.HTTP_409_CONFLICT, ) payload: dict = dict(options=DP.to_dict(request.data), shipment_rates=[], messages=[]) SerializerDecorator[ShipmentSerializer](shipment, data=payload).save() return Response(Shipment(shipment).data)
def get(self, request: Request): """ Retrieve all shipments. """ shipments = self.filter_queryset(self.get_queryset()) response = self.paginate_queryset(Shipment(shipments, many=True).data) return self.get_paginated_response(response)
class ShipmentDetail(APIView): @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}retrieve", operation_summary="Retrieve a shipment", responses={ 200: Shipment(), 400: ErrorResponse() }, ) def get(self, request: Request, pk: str): """ Retrieve a shipment. """ shipment = models.Shipment.access_by(request).get(pk=pk) return Response(Shipment(shipment).data) @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}cancel", operation_summary="Cancel a shipment", responses={ 200: OperationResponse(), 400: ErrorResponse() }, ) def delete(self, request: Request, pk: str): """ Void a shipment with the associated label. """ shipment = models.Shipment.access_by(request).get(pk=pk) if shipment.status not in [ ShipmentStatus.purchased.value, ShipmentStatus.created.value, ]: raise PurplShipApiException( f"The shipment is '{shipment.status}' and can therefore not be cancelled anymore...", code="state_error", status_code=status.HTTP_409_CONFLICT, ) if shipment.pickup_shipments.exists(): raise PurplShipApiException( (f"This shipment is scheduled for pickup '{shipment.pickup_shipments.first().pk}' " "Please cancel this shipment from the pickup before."), code="state_error", status_code=status.HTTP_409_CONFLICT, ) confirmation = SerializerDecorator[ShipmentCancelSerializer]( shipment, data={}, context=request).save() return Response(OperationResponse(confirmation.instance).data)
def post(self, request: Request): """ Create a new shipment instance. """ carrier_filter = { **SerializerDecorator[ShipmentMode](data=request.query_params).data } shipment = (SerializerDecorator[ShipmentSerializer]( data=request.data, context=request).save(carrier_filter=carrier_filter).instance) return Response(Shipment(shipment).data, status=status.HTTP_201_CREATED)
class ShipmentRates(APIView): logging_methods = ["GET"] @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}rates", operation_summary="Fetch new shipment rates", responses={ 200: Shipment(), 400: ErrorResponse() }, request_body=ShipmentRateData(), ) def post(self, request: Request, pk: str): """ Refresh the list of the shipment rates """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) rate_payload = SerializerDecorator[ShipmentRateData]( data=request.data).data carrier_ids = (rate_payload["carrier_ids"] if "carrier_ids" in rate_payload else shipment.carrier_ids) carriers = Carriers.list( active=True, capability="shipping", context=request, test=shipment.test_mode, carrier_ids=carrier_ids, ) rate_response: RateResponse = (SerializerDecorator[RateSerializer]( context=request, data={ **ShipmentData(shipment).data, **rate_payload }).save(carriers=carriers).instance) SerializerDecorator[ShipmentSerializer]( shipment, context=request, data={ "rates": Rate(rate_response.rates, many=True).data, "messages": DP.to_dict(rate_response.messages), **rate_payload, }, ).save(carriers=carriers) return Response(Shipment(shipment).data)
class ShipmentOptions(APIView): @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}set_options", operation_summary="Add shipment options", responses={ 200: Shipment(), 400: ErrorResponse() }, request_body=openapi.Schema( title="options", type=openapi.TYPE_OBJECT, additional_properties=True, ), ) def post(self, request: Request, pk: str): """ Add one or many options to your shipment.<br/> **eg:**<br/> - add shipment **insurance** - specify the preferred transaction **currency** - setup a **cash collected on delivery** option ```json { "insurance": 120, "currency": "USD" } ``` And many more, check additional options available in the [reference](#operation/all_references). """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "Shipment already 'purchased'", code="state_error", status_code=status.HTTP_409_CONFLICT, ) payload: dict = dict(options=DP.to_dict(request.data), shipment_rates=[], messages=[]) SerializerDecorator[ShipmentSerializer](shipment, data=payload).save() return Response(Shipment(shipment).data)
def post(self, request: Request): """ Once the shipping rates are retrieved, provide the required info to submit the shipment by specifying your preferred rate. """ payload = SerializerDecorator[ShippingRequestValidation]( data=request.data).data response = Shipments.create( payload, resolve_tracking_url=( lambda tracking_number, carrier_name: reverse( "purplship.server.proxy:shipment-tracking", kwargs=dict(tracking_number=tracking_number, carrier_name=carrier_name)))) return Response(Shipment(response).data, status=status.HTTP_201_CREATED)
def post(self, request: Request, pk: str): """ Add a parcel to an existing shipment for a multi-parcel shipment. """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "Shipment already 'purchased'", code="state_error", status_code=status.HTTP_409_CONFLICT, ) parcel = (SerializerDecorator[ParcelSerializer]( data=request.data, context=request).save().instance) shipment.shipment_parcels.add(parcel) reset_related_shipment_rates(shipment) return Response(Shipment(shipment).data)
class ShipmentPurchase(APIView): @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}purchase", operation_summary="Buy a shipment label", responses={ 200: Shipment(), 400: ErrorResponse() }, request_body=ShipmentPurchaseData(), ) def post(self, request: Request, pk: str): """ Select your preferred rates to buy a shipment label. """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( f"The shipment is '{shipment.status}' and therefore already {ShipmentStatus.purchased.value}", code="state_error", status_code=status.HTTP_409_CONFLICT, ) # Submit shipment to carriers response: Shipment = (SerializerDecorator[ShipmentPurchaseSerializer]( context=request, data={ **Shipment(shipment).data, **SerializerDecorator[ShipmentPurchaseData](data=request.data).data, }, ).save().instance) # Update shipment state SerializerDecorator[ShipmentSerializer](shipment, data=DP.to_dict(response), context=request).save() create_shipment_tracker(shipment, context=request) return Response(Shipment(shipment).data)
class ShipmentCustoms(APIView): @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}add_customs", operation_summary="Add a customs declaration", responses={ 200: Shipment(), 400: ErrorResponse() }, request_body=CustomsData(), ) def post(self, request: Request, pk: str): """ Add the customs declaration for the shipment if non existent. """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) if shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "Shipment already 'purchased'", code="state_error", status_code=status.HTTP_409_CONFLICT, ) if shipment.customs is not None: raise PurplShipApiException( "Shipment customs declaration already defined", code="state_error", status_code=status.HTTP_409_CONFLICT, ) payload: dict = dict(customs=DP.to_dict(request.data), shipment_rates=[], messages=[]) SerializerDecorator[ShipmentSerializer](shipment, data=payload, context=request).save() return Response(Shipment(shipment).data)
def post(self, request: Request, pk: str): """ Refresh the list of the shipment rates """ shipment = (models.Shipment.access_by(request).exclude( status=ShipmentStatus.cancelled.value).get(pk=pk)) rate_payload = SerializerDecorator[ShipmentRateData]( data=request.data).data carrier_ids = (rate_payload["carrier_ids"] if "carrier_ids" in rate_payload else shipment.carrier_ids) carriers = Carriers.list( active=True, capability="shipping", context=request, test=shipment.test_mode, carrier_ids=carrier_ids, ) rate_response: RateResponse = (SerializerDecorator[RateSerializer]( context=request, data={ **ShipmentData(shipment).data, **rate_payload }).save(carriers=carriers).instance) SerializerDecorator[ShipmentSerializer]( shipment, context=request, data={ "rates": Rate(rate_response.rates, many=True).data, "messages": DP.to_dict(rate_response.messages), **rate_payload, }, ).save(carriers=carriers) return Response(Shipment(shipment).data)
class ShipmentList(GenericAPIView): pagination_class = type("CustomPagination", (LimitOffsetPagination, ), dict(default_limit=20)) filter_backends = (filters.DjangoFilterBackend, ) filterset_class = ShipmentFilters model = models.Shipment def get_queryset(self): queryset = super().get_queryset() _filters = tuple() query_params = getattr(self.request, "query_params", {}) carrier_name = query_params.get("carrier_name") if carrier_name is not None: _filters += ( Q(meta__rate_provider=carrier_name) | Q( **{ f"selected_rate_carrier__{carrier_name}settings__isnull": False }), ) return queryset.filter(*_filters) @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}list", operation_summary="List all shipments", responses={ 200: Shipments(), 400: ErrorResponse() }, manual_parameters=ShipmentFilters.parameters, ) def get(self, request: Request): """ Retrieve all shipments. """ shipments = self.filter_queryset(self.get_queryset()) response = self.paginate_queryset(Shipment(shipments, many=True).data) return self.get_paginated_response(response) @swagger_auto_schema( tags=["Shipments"], operation_id=f"{ENDPOINT_ID}create", operation_summary="Create a shipment", responses={ 200: Shipment(), 400: ErrorResponse() }, request_body=ShipmentData(), query_serializer=ShipmentMode(), ) def post(self, request: Request): """ Create a new shipment instance. """ carrier_filter = { **SerializerDecorator[ShipmentMode](data=request.query_params).data } shipment = (SerializerDecorator[ShipmentSerializer]( data=request.data, context=request).save(carrier_filter=carrier_filter).instance) return Response(Shipment(shipment).data, status=status.HTTP_201_CREATED)