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)
Beispiel #3
0
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)
Beispiel #10
0
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)
Beispiel #11
0
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)
Beispiel #12
0
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)
Beispiel #13
0
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)
Beispiel #15
0
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)
Beispiel #18
0
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)
Beispiel #19
0
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)
Beispiel #20
0
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)
Beispiel #22
0
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)
Beispiel #23
0
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)
Beispiel #24
0
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)
Beispiel #25
0
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)
Beispiel #26
0
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)
Beispiel #27
0
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)