class PickupDetails(APIView): @swagger_auto_schema( tags=['Pickups'], operation_id=f"{ENDPOINT_ID}retrieve", operation_summary="Retrieve a pickup", responses={ 200: Pickup(), 400: ErrorResponse() }, ) def get(self, request: Request, pk: str): """Retrieve a scheduled pickup.""" pickup = request.user.pickup_set.get(pk=pk) return Response(Pickup(pickup).data) @swagger_auto_schema(tags=['Pickups'], operation_id=f"{ENDPOINT_ID}update", operation_summary="Update a pickup", responses={ 200: OperationConfirmation(), 400: ErrorResponse() }, request_body=PickupUpdateData()) def patch(self, request: Request, pk: str): """ Modify a pickup for one or many shipments with labels already purchased. """ pickup = request.user.pickup_set.get(pk=pk) instance = SerializerDecorator[PickupUpdateData]( pickup, data=request.data).save(user=request.user).instance return Response(Pickup(instance).data, status=status.HTTP_200_OK)
class AddressList(GenericAPIView): pagination_class = LimitOffsetPagination default_limit = 20 @swagger_auto_schema( tags=['Addresses'], operation_id=f"{ENDPOINT_ID}list", operation_summary="List all addresses", responses={200: Addresses(), 400: ErrorResponse()} ) def get(self, request: Request): """ Retrieve all addresses. """ addresses = request.user.address_set.all() response = self.paginate_queryset(Address(addresses, many=True).data) return self.get_paginated_response(response) @swagger_auto_schema( tags=['Addresses'], operation_id=f"{ENDPOINT_ID}create", operation_summary="Create an address", request_body=AddressData(), responses={200: Address(), 400: ErrorResponse()} ) def post(self, request: Request): """ Create a new address. """ address = SerializerDecorator[AddressSerializer](data=request.data).save(user=request.user).instance return Response(Address(address).data, status=status.HTTP_201_CREATED)
class CustomsList(GenericAPIView): pagination_class = LimitOffsetPagination default_limit = 20 @swagger_auto_schema(tags=['Customs'], operation_id=f"{ENDPOINT_ID}list", operation_summary="List all customs info", responses={ 200: CustomsInfoList(), 400: ErrorResponse() }) def get(self, request: Request): """ Retrieve all stored customs declarations. """ customs_info = request.user.customs_set.all() serializer = Customs(customs_info, many=True) response = self.paginate_queryset(serializer.data) return self.get_paginated_response(response) @swagger_auto_schema(tags=['Customs'], operation_id=f"{ENDPOINT_ID}create", operation_summary="Create a customs info", request_body=CustomsData(), responses={ 200: Customs(), 400: ErrorResponse() }) def post(self, request: Request): """ Create a new customs declaration. """ customs = SerializerDecorator[CustomsSerializer]( data=request.data).save(user=request.user).instance return Response(Customs(customs).data, status=status.HTTP_201_CREATED)
class ParcelList(GenericAPIView): pagination_class = LimitOffsetPagination default_limit = 20 @swagger_auto_schema(tags=['Parcels'], operation_id=f"{ENDPOINT_ID}list", operation_summary="List all parcels", responses={ 200: Parcels(), 400: ErrorResponse() }) def get(self, request: Request): """ Retrieve all stored parcels. """ parcels = request.user.parcel_set.all() serializer = Parcel(parcels, many=True) response = self.paginate_queryset(serializer.data) return self.get_paginated_response(response) @swagger_auto_schema(tags=['Parcels'], operation_id=f"{ENDPOINT_ID}create", operation_summary="Create a parcel", request_body=ParcelData(), responses={ 200: Parcel(), 400: ErrorResponse() }) def post(self, request: Request): """ Create a new parcel. """ parcel = SerializerDecorator[ParcelSerializer](data=request.data).save( user=request.user).instance return Response(Parcel(parcel).data, status=status.HTTP_201_CREATED)
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 = request.user.shipment_set.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) payload = { **Shipment(shipment).data, **SerializerDecorator[ShipmentPurchaseData](data=request.data).data } # Submit shipment to carriers response: Shipment = SerializerDecorator[ShipmentValidationData]( data=payload).save(user=request.user).instance # Update shipment state SerializerDecorator[ShipmentSerializer]( shipment, data=DP.to_dict(response)).save() return Response(Shipment(shipment).data)
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 = request.user.shipment_set.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).save( user=request.user).instance shipment.shipment_parcels.add(parcel) reset_related_shipment_rates(shipment) 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 = request.user.shipment_set.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) SerializerDecorator[ShipmentSerializer]( shipment, data=dict(customs=request.data)).save() reset_related_shipment_rates(shipment) return Response(Shipment(shipment).data)
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() }) def get(self, request: Request, pk: str): """ Refresh the list of the shipment rates """ shipment = request.user.shipment_set.get(pk=pk) rate_response: RateResponse = SerializerDecorator[RateSerializer]( data=ShipmentData(shipment).data).save(user=shipment.user).instance payload: dict = DP.to_dict( dict(rates=Rate(rate_response.rates, many=True).data, messages=Message(rate_response.messages, many=True).data, selected_rate=None)) SerializerDecorator[ShipmentSerializer](shipment, data=payload).save() return Response(Shipment(shipment).data)
class TrackingAPIView(APIView): logging_methods = ['GET'] @swagger_auto_schema(tags=['Proxy'], operation_id=f"{ENDPOINT_ID}track_shipment", operation_summary="Track a shipment", query_serializer=TestFilters(), responses={ 200: TrackingResponse(), 400: ErrorResponse() }) def get(self, request: Request, carrier_name: str, tracking_number: str): """ You can track a shipment by specifying the carrier and the shipment tracking number. """ params = TestFilters(data=request.query_params) params.is_valid(raise_exception=True) tracking_request = TrackingRequest(data=dict( tracking_numbers=[tracking_number])) tracking_request.is_valid(raise_exception=True) response = Shipments.track(tracking_request.data, carrier_filter={ **params.validated_data, 'carrier_name': carrier_name, 'user': request.user }) return Response(TrackingResponse(response).data, status=status.HTTP_200_OK if response.tracking is not None else status.HTTP_404_NOT_FOUND)
class CarrierList(GenericAPIView): pagination_class = LimitOffsetPagination default_limit = 100 @swagger_auto_schema(tags=['Carriers'], operation_id=f"{ENDPOINT_ID}list", operation_summary="List all carriers", responses={ 200: CarriersSettingsList(), 400: ErrorResponse() }, query_serializer=CarrierFilters) def get(self, request: Request): """ Returns the list of configured carriers """ query = SerializerDecorator[CarrierFilters]( data=request.query_params).data carriers = [ carrier.data for carrier in Carriers.list(**{ **query, 'user': request.user }) ] response = self.paginate_queryset( CarrierSettings(carriers, many=True).data) return self.get_paginated_response(response)
class ShippingList(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 shipment: reverse( "purpleserver.proxy:shipment-tracking", kwargs=dict(tracking_number=shipment.tracking_number, carrier_name=shipment.carrier_name) ) ) ) return Response(Shipment(response).data, status=status.HTTP_201_CREATED)
class PickupRequest(APIView): @swagger_auto_schema(tags=['Pickups'], operation_id=f"{ENDPOINT_ID}schedule", operation_summary="Schedule a pickup", responses={ 200: Pickup(), 400: ErrorResponse() }, query_serializer=TestFilters(), request_body=PickupData()) def post(self, request: Request, carrier_name: str): """ Schedule a pickup for one or many shipments with labels already purchased. """ carrier_filter = { **SerializerDecorator[TestFilters](data=request.query_params).data, "carrier_name": carrier_name, "user": request.user } pickup = SerializerDecorator[PickupData](data=request.data).save( user=request.user, carrier_filter=carrier_filter).instance return Response(Pickup(pickup).data, status=status.HTTP_201_CREATED)
class TrackingDetails(APIView): logging_methods = ['GET'] @swagger_auto_schema( tags=['Trackers'], operation_id=f"{ENDPOINT_ID}retrieve", operation_summary="Retrieves or creates a shipment trackers", query_serializer=TestFilters(), responses={200: TrackingStatus(), 404: ErrorResponse()} ) def get(self, request: Request, carrier_name: str, tracking_number: str): """ This API retrieves or creates (if non existent) a tracking status object containing the details and events of a shipping in progress. """ data = dict(tracking_number=tracking_number) carrier_filter = { **SerializerDecorator[TestFilters](data=request.query_params).data, "carrier_name": carrier_name, "user": request.user } tracking = request.user.tracking_set.filter(tracking_number=tracking_number).first() tracking = SerializerDecorator[TrackingSerializer]( tracking, data=data).save(user=request.user, carrier_filter=carrier_filter).instance return Response(TrackingStatus(tracking).data)
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 = request.user.shipment_set.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 = request.user.shipment_set.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={}).save() return Response(OperationResponse(confirmation.instance).data)
class PickupDetails(APIView): @swagger_auto_schema( tags=['Proxy'], operation_id=f"{ENDPOINT_ID}schedule_pickup", operation_summary="Schedule a pickup", query_serializer=TestFilters(), request_body=PickupRequest(), responses={200: PickupResponse(), 400: ErrorResponse()}, ) def post(self, request: Request, carrier_name: str): """ Schedule one or many parcels pickup """ filters = SerializerDecorator[TestFilters](data=request.query_params).data payload = SerializerDecorator[PickupRequest](data=request.data).data response = Pickups.schedule(payload, carrier_filter={ **filters, 'carrier_name': carrier_name, 'user': request.user }) return Response(PickupResponse(response).data, status=status.HTTP_201_CREATED) @swagger_auto_schema( tags=['Proxy'], operation_id=f"{ENDPOINT_ID}update_pickup", operation_summary="Update a pickup", query_serializer=TestFilters(), request_body=PickupUpdateRequest(), responses={200: PickupResponse(), 400: ErrorResponse()}, ) def put(self, request: Request, carrier_name: str): """ Modify a scheduled pickup """ filters = SerializerDecorator[TestFilters](data=request.query_params).data payload = SerializerDecorator[PickupUpdateRequest](data=request.data).data response = Pickups.update(payload, carrier_filter={**filters, 'carrier_name': carrier_name, 'user': request.user}) return Response(PickupResponse(response).data, status=status.HTTP_200_OK)
class AddressDetail(APIView): @swagger_auto_schema( tags=['Addresses'], operation_id=f"{ENDPOINT_ID}retrieve", operation_summary="Retrieve an address", responses={200: Address(), 400: ErrorResponse()} ) def get(self, request: Request, pk: str): """ Retrieve an address. """ address = request.user.address_set.get(pk=pk) return Response(Address(address).data) @swagger_auto_schema( tags=['Addresses'], operation_id=f"{ENDPOINT_ID}update", operation_summary="Update an address", request_body=AddressData(), responses={200: Address(), 400: ErrorResponse()} ) def patch(self, request: Request, pk: str): """ update an address. """ address = request.user.address_set.get(pk=pk) shipment = address.shipper.first() or address.recipient.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this address has been 'purchased' and can no longer be modified", status_code=status.HTTP_409_CONFLICT, code='state_error' ) SerializerDecorator[AddressSerializer](address, data=request.data).save() reset_related_shipment_rates(shipment) return Response(Address(address).data)
class ShipmentList(GenericAPIView): pagination_class = LimitOffsetPagination default_limit = 20 @swagger_auto_schema(tags=['Shipments'], operation_id=f"{ENDPOINT_ID}list", operation_summary="List all shipments", responses={ 200: Shipments(), 400: ErrorResponse() }) def get(self, request: Request): """ Retrieve all shipments. """ shipments = request.user.shipment_set.all() 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()) def post(self, request: Request): """ Create a new shipment instance. """ shipment = SerializerDecorator[ShipmentSerializer]( data=request.data).save(user=request.user).instance return Response(Shipment(shipment).data, status=status.HTTP_201_CREATED)
class TrackerList(GenericAPIView): pagination_class = LimitOffsetPagination default_limit = 20 @swagger_auto_schema( tags=['Trackers'], operation_id=f"{ENDPOINT_ID}list", operation_summary="List all shipment trackers", responses={200: Trackers(), 400: ErrorResponse()} ) def get(self, request: Request): """ Retrieve all shipment trackers. """ trackers = request.user.tracking_set.all() response = self.paginate_queryset(TrackingStatus(trackers, many=True).data) return self.get_paginated_response(response)
class PickupList(GenericAPIView): pagination_class = LimitOffsetPagination default_limit = 20 @swagger_auto_schema(tags=['Pickups'], operation_id=f"{ENDPOINT_ID}list", operation_summary="List shipment pickups", responses={ 200: Pickups(), 400: ErrorResponse() }) def get(self, request: Request): """ Retrieve all scheduled pickups. """ pickups = request.user.pickup_set.all() response = self.paginate_queryset(Pickup(pickups, many=True).data) return self.get_paginated_response(response)
class PickupCancel(APIView): @swagger_auto_schema(tags=['Pickups'], operation_id=f"{ENDPOINT_ID}cancel", operation_summary="Cancel a pickup", responses={ 200: OperationConfirmation(), 400: ErrorResponse() }, request_body=PickupCancelData()) def post(self, request: Request, pk: str): """ Cancel a pickup of one or more shipments. """ pickup = request.user.pickup_set.get(pk=pk) confirmation = SerializerDecorator[PickupCancelData]( pickup, data=request.data).save(user=request.user).instance return Response(OperationConfirmation(confirmation).data, status=status.HTTP_200_OK)
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 = request.user.shipment_set.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 = DP.to_dict(dict(options=request.data)) SerializerDecorator[ShipmentSerializer](shipment, data=payload).save() reset_related_shipment_rates(shipment) return Response(Shipment(shipment).data)
class PickupCancel(APIView): @swagger_auto_schema( tags=['Proxy'], operation_id=f"{ENDPOINT_ID}cancel_pickup", operation_summary="Cancel a pickup", query_serializer=TestFilters(), request_body=PickupCancelRequest(), responses={200: OperationResponse(), 400: ErrorResponse()}, ) def post(self, request: Request, carrier_name: str): """ Cancel a pickup previously scheduled """ filters = SerializerDecorator[TestFilters](data=request.query_params).data payload = SerializerDecorator[PickupCancelRequest](data=request.data).data response = Pickups.cancel(payload, carrier_filter={**filters, 'carrier_name': carrier_name, 'user': request.user}) return Response(OperationResponse(response).data, status=status.HTTP_200_OK)
class ShippingCancel(APIView): @swagger_auto_schema( tags=['Proxy'], operation_id=f"{ENDPOINT_ID}void_label", operation_summary="Void a shipment label", query_serializer=TestFilters(), request_body=ShipmentCancelRequest(), responses={200: OperationResponse(), 400: ErrorResponse()}, ) def post(self, request: Request, carrier_name: str): """ Cancel a shipment and the label previously created """ filters = SerializerDecorator[TestFilters](data=request.query_params).data payload = SerializerDecorator[ShipmentCancelRequest](data=request.data).data response = Shipments.cancel(payload, carrier_filter={**filters, 'carrier_name': carrier_name, 'user': request.user}) return Response(OperationResponse(response).data, status=status.HTTP_202_ACCEPTED)
class Utils(APIView): logging_methods = [] renderer_classes = [BinaryFileRenderer, JSONOpenAPIRenderer] @swagger_auto_schema( tags=['Utils'], operation_id=f"{ENDPOINT_ID}print_label", operation_summary="Print a Label", operation_description="Returns a label PDF file.", request_body=LabelPrintingRequest(), responses={ 201: None, 400: ErrorResponse() }, ) def post(self, request: Request): try: try: print_request = LabelPrintingRequest(data=request.data) print_request.is_valid(raise_exception=True) content = base64.b64decode(request.data["label"]) buffer = io.BytesIO() buffer.write(content) return Response( buffer.getvalue(), headers={ 'Content-Disposition': f'attachment; filename="{request.data["name"]}.pdf"' }, content_type='application/pdf') except ValidationError as ve: logger.exception(ve) return Response(ve.args, status=status.HTTP_400_BAD_REQUEST) except Exception as e: logger.exception(e) return Response(e.args, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class RateViewAPI(APIView): @swagger_auto_schema( tags=['Proxy'], operation_id=f"{ENDPOINT_ID}fetch_rates", operation_summary="Fetch shipment rates", operation_description=DESCRIPTIONS, responses={ 200: RateResponse(), 400: ErrorResponse() }, request_body=RateRequest(), ) def post(self, request: Request): rate_request = RateRequest(data=request.data) rate_request.is_valid(raise_exception=True) response = Rates.fetch(rate_request.validated_data, request.user) return Response( RateResponse(response).data, status=status.HTTP_207_MULTI_STATUS if len(response.messages) > 0 else status.HTTP_201_CREATED)
class ParcelDetail(APIView): @swagger_auto_schema(tags=['Parcels'], operation_id=f"{ENDPOINT_ID}retrieve", operation_summary="Retrieve a parcel", responses={ 200: Parcel(), 400: ErrorResponse() }) def get(self, request: Request, pk: str): """ Retrieve a parcel. """ address = request.user.parcel_set.get(pk=pk) return Response(Parcel(address).data) @swagger_auto_schema(tags=['Parcels'], operation_id=f"{ENDPOINT_ID}update", operation_summary="Update a parcel", request_body=ParcelData(), responses={ 200: Parcel(), 400: ErrorResponse() }) def patch(self, request: Request, pk: str): """ modify an existing parcel's details. """ parcel = request.user.parcel_set.get(pk=pk) shipment = parcel.shipment_parcels.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this parcel has been 'purchased' and can no longer be modified", status_code=status.HTTP_409_CONFLICT, code='state_error') SerializerDecorator[ParcelSerializer](parcel, data=request.data).save() reset_related_shipment_rates(shipment) return Response(Parcel(parcel).data) @swagger_auto_schema(tags=['Parcels'], operation_id=f"{ENDPOINT_ID}discard", operation_summary="Remove a parcel", responses={ 200: Operation(), 400: ErrorResponse() }) def delete(self, request: Request, pk: str): """ Remove a parcel. """ parcel = request.user.parcel_set.get(pk=pk) shipment = parcel.shipment_parcels.first() if shipment is not None and ( shipment.status == ShipmentStatus.purchased.value or len(shipment.shipment_parcels.all()) == 1): raise PurplShipApiException( "A shipment attached to this parcel is purchased or has only one parcel. The parcel cannot be removed!", status_code=status.HTTP_409_CONFLICT, code='state_error') parcel.delete(keep_parents=True) shipment.shipment_parcels.set( shipment.shipment_parcels.exclude(id=parcel.id)) serializer = Operation(dict(operation="Remove parcel", success=True)) reset_related_shipment_rates(shipment) return Response(serializer.data)
class CustomsDetail(APIView): @swagger_auto_schema(tags=['Customs'], operation_id=f"{ENDPOINT_ID}retrieve", operation_summary="Retrieve a customs info", responses={ 200: Customs(), 400: ErrorResponse() }) def get(self, request: Request, pk: str): """ Retrieve customs declaration. """ address = request.user.customs_set.get(pk=pk) return Response(Customs(address).data) @swagger_auto_schema(tags=['Customs'], operation_id=f"{ENDPOINT_ID}update", operation_summary="Update a customs info", request_body=CustomsData(), responses={ 200: Customs(), 400: ErrorResponse() }) def patch(self, request: Request, pk: str): """ modify an existing customs declaration. """ customs = request.user.customs_set.get(pk=pk) shipment = customs.shipment_set.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this customs info has been 'purchased' and can no longer be modified", status_code=status.HTTP_409_CONFLICT, code='state_error') SerializerDecorator[CustomsSerializer]( customs, data=request.data).save(user=request.user) reset_related_shipment_rates(shipment) return Response(Customs(customs).data) @swagger_auto_schema(tags=['Customs'], operation_id=f"{ENDPOINT_ID}discard", operation_summary="Discard a customs info", responses={ 200: Operation(), 400: ErrorResponse() }) def delete(self, request: Request, pk: str): """ Discard a customs declaration. """ customs = request.user.customs_set.get(pk=pk) shipment = customs.shipment_set.first() if shipment is not None and shipment.status == ShipmentStatus.purchased.value: raise PurplShipApiException( "The shipment related to this customs info has been 'purchased' and cannot be discarded", status_code=status.HTTP_409_CONFLICT, code='state_error') customs.delete(keep_parents=True) shipment.customs = None serializer = Operation( dict(operation="Discard customs info", success=True)) reset_related_shipment_rates(shipment) return Response(serializer.data)