Пример #1
0
class PickupDetails(APIView):
    @swagger_auto_schema(tags=['Pickups'],
                         operation_id=f"{ENDPOINT_ID}retrieve",
                         operation_summary="Retrieve a pickup",
                         responses={
                             200: Pickup(),
                             400: ErrorResponse()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request GET \\
                  --url /v1/pickups/<PICKUP_ID> \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def get(self, request: Request, pk: str):
        """Retrieve a scheduled pickup."""
        pickup = models.Pickup.access_by(request).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(),
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request PATCH \\
                  --url /v1/pickups/<PICKUP_ID> \\
                  --header 'Authorization: Token <API_KEY>' \\
                  --data '{
                    "address": {
                      "phone_number": "514-000-0000",
                      "residential": false,
                      "email": "*****@*****.**"
                    },
                    "ready_time": "13:00",
                    "closing_time": "20:00",
                }'
                '''
                         }])
    def patch(self, request: Request, pk: str):
        """
        Modify a pickup for one or many shipments with labels already purchased.
        """
        pickup = models.Pickup.access_by(request).get(pk=pk)
        instance = SerializerDecorator[PickupUpdateData](
            pickup, data=request.data, context=request).save().instance

        return Response(Pickup(instance).data, status=status.HTTP_200_OK)
Пример #2
0
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)
Пример #3
0
class WebhookDetail(APIView):
    @swagger_auto_schema(tags=['Webhooks'],
                         operation_id=f"{ENDPOINT_ID}retrieve",
                         operation_summary="Retrieve a webhook",
                         responses={
                             200: Webhook(),
                             400: ErrorResponse()
                         })
    def get(self, request: Request, pk: str):
        """
        Retrieve a webhook.
        """
        webhook = models.Webhook.access_by(request).get(pk=pk)
        return Response(Webhook(webhook).data)

    @swagger_auto_schema(tags=['Webhooks'],
                         operation_id=f"{ENDPOINT_ID}update",
                         operation_summary="Update a webhook",
                         request_body=WebhookData(),
                         responses={
                             200: Webhook(),
                             400: ErrorResponse()
                         })
    def patch(self, request: Request, pk: str):
        """
        update a webhook.
        """
        webhook = models.Webhook.access_by(request).get(pk=pk)

        SerializerDecorator[WebhookSerializer](webhook,
                                               data=request.data).save()
        return Response(Webhook(webhook).data)

    @swagger_auto_schema(tags=['Webhooks'],
                         operation_id=f"{ENDPOINT_ID}remove",
                         operation_summary="Remove a webhook",
                         responses={
                             200: Operation(),
                             400: ErrorResponse()
                         })
    def delete(self, request: Request, pk: str):
        """
        Remove a webhook.
        """
        webhook = models.Webhook.access_by(request).get(pk=pk)

        webhook.delete(keep_parents=True)
        serializer = Operation(dict(operation="Remove webhook", success=True))
        return Response(serializer.data)
Пример #4
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()
        },
        manual_parameters=[
            openapi.Parameter('carrier_name',
                              in_=openapi.IN_PATH,
                              type=openapi.TYPE_STRING,
                              enum=CARRIER_NAMES),
        ],
    )
    def post(self, request: Request, carrier_name: str):
        """
        Cancel a shipment and the label previously created
        """
        test_filter = SerializerDecorator[TestFilters](
            data=request.query_params).data
        payload = SerializerDecorator[ShipmentCancelRequest](
            data=request.data).data

        response = Shipments.cancel(payload,
                                    context=request,
                                    carrier_name=carrier_name,
                                    **test_filter)

        return Response(OperationResponse(response).data,
                        status=status.HTTP_202_ACCEPTED)
Пример #5
0
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)
Пример #6
0
class PickupList(GenericAPIView):
    pagination_class = type('CustomPagination', (LimitOffsetPagination, ),
                            dict(default_limit=20))
    filter_backends = (filters.DjangoFilterBackend, )
    filterset_class = PickupFilters
    model = models.Pickup

    @swagger_auto_schema(tags=['Pickups'],
                         operation_id=f"{ENDPOINT_ID}list",
                         operation_summary="List shipment pickups",
                         responses={
                             200: Pickups(),
                             400: ErrorResponse()
                         },
                         manual_parameters=PickupFilters.parameters,
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request GET \\
                  --url '/v1/pickups' \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def get(self, request: Request):
        """
        Retrieve all scheduled pickups.
        """
        pickups = self.filter_queryset(self.get_queryset())
        response = self.paginate_queryset(Pickup(pickups, many=True).data)
        return self.get_paginated_response(response)
Пример #7
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(),
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request POST \\
                  --url /v1/pickups/<PICKUP_ID> \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def post(self, request: Request, pk: str):
        """
        Cancel a pickup of one or more shipments.
        """
        pickup = models.Pickup.access_by(request).get(pk=pk)
        confirmation = SerializerDecorator[PickupCancelData](
            pickup, data=request.data, context=request).save().instance

        return Response(OperationConfirmation(confirmation).data,
                        status=status.HTTP_200_OK)
Пример #8
0
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)
Пример #9
0
class DiscardCommodities(APIView):
    @swagger_auto_schema(tags=['Customs'],
                         operation_id=f"{ENDPOINT_ID}discard_commodity",
                         operation_summary="Discard a commodity",
                         responses={
                             200: Operation(),
                             400: ErrorResponse()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request DELETE \\
                  --url /v1/customs_info/<CUSTOMS_INFO_ID>/commodities/<COMMODITY_ID> \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def delete(self, request: Request, pk: str, ck: str):
        """
        Discard a customs commodity.
        """
        customs = models.Customs.access_by(request).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 modified",
                status_code=status.HTTP_409_CONFLICT,
                code='state_error')

        commodity = customs.commodities.get(pk=ck)
        commodity.delete(keep_parents=True)
        serializer = Operation(
            dict(operation="Discard customs commodity", success=True))
        return Response(serializer.data)
Пример #10
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()
        },
        manual_parameters=[
            openapi.Parameter('carrier_name',
                              in_=openapi.IN_PATH,
                              type=openapi.TYPE_STRING,
                              enum=CARRIER_NAMES),
        ],
    )
    def post(self, request: Request, carrier_name: str):
        """
        Cancel a pickup previously scheduled
        """
        test_filter = SerializerDecorator[TestFilters](
            data=request.query_params).data
        payload = SerializerDecorator[PickupCancelRequest](
            data=request.data).data

        response = Pickups.cancel(payload,
                                  context=request,
                                  carrier_name=carrier_name,
                                  **test_filter)

        return Response(OperationResponse(response).data,
                        status=status.HTTP_200_OK)
Пример #11
0
class TrackersDetails(APIView):
    permission_classes = [IsAuthenticatedOrReadOnly]

    @swagger_auto_schema(
        tags=["Trackers"],
        operation_id=f"{ENDPOINT_ID}retrieves",
        operation_summary="Retrieves a shipment tracker",
        responses={
            200: TrackingStatus(),
            404: ErrorResponse()
        },
    )
    def get(self, request: Request, id_or_tracking_number: str):
        """
        Retrieve a shipment tracker
        """
        __filter = Q(pk=id_or_tracking_number) | Q(
            tracking_number=id_or_tracking_number)
        trackers = models.Tracking.objects.filter(__filter)

        if len(trackers) == 0:
            models.Tracking.objects.get(__filter)

        return Response(TrackingStatus(trackers.first()).data)

    @swagger_auto_schema(
        tags=["Trackers"],
        operation_id=f"{ENDPOINT_ID}remove",
        operation_summary="Discard a shipment tracker",
        responses={
            200: Operation(),
            400: ErrorResponse()
        },
    )
    def delete(self, request: Request, id_or_tracking_number: str):
        """
        Discard a shipment tracker.
        """
        tracker = models.Tracking.access_by(request).get(
            Q(pk=id_or_tracking_number)
            | Q(tracking_number=id_or_tracking_number))

        tracker.delete(keep_parents=True)
        serializer = Operation(
            dict(operation="Discard a tracker", success=True))
        return Response(serializer.data)
Пример #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(),
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request POST \\
                  --url /v1/pickups/<PICKUP_ID> \\
                  --header 'Authorization: Token <API_KEY>' \\
                  --data '{
                    "pickup_date": "2020-10-25",
                    "address": {
                      "address_line1": "125 Church St",
                      "person_name": "John Doe",
                      "city": "Moncton",
                      "country_code": "CA",
                      "postal_code": "E1C4Z8",
                      "state_code": "NB",
                    },
                    "ready_time": "13:00",
                    "closing_time": "17:00",
                    "instruction": "Should not be folded",
                    "package_location": "At the main entrance hall",
                    "tracking_numbers": [
                        "8545763607864201002"
                    ]
                }'
                '''
                         }])
    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,
        }

        pickup = SerializerDecorator[PickupData](
            data=request.data,
            context=request).save(carrier_filter=carrier_filter).instance

        return Response(Pickup(pickup).data, status=status.HTTP_201_CREATED)
Пример #13
0
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)
Пример #14
0
class WebhookList(GenericAPIView):
    pagination_class = LimitOffsetPagination
    default_limit = 20

    @swagger_auto_schema(tags=['Webhooks'],
                         operation_id=f"{ENDPOINT_ID}list",
                         operation_summary="List all webhooks",
                         responses={
                             200: Webhooks(),
                             400: ErrorResponse()
                         },
                         query_serializer=WebhookFilters)
    def get(self, request: Request):
        """
        Retrieve all webhooks.
        """
        query = (SerializerDecorator[WebhookFilters](
            data=request.query_params).data
                 if any(request.query_params) else {})
        webhooks = models.Webhook.access_by(request).filter(**query)
        response = self.paginate_queryset(Webhook(webhooks, many=True).data)
        return self.get_paginated_response(response)

    @swagger_auto_schema(tags=['Webhooks'],
                         operation_id=f"{ENDPOINT_ID}create",
                         operation_summary="Create a webhook",
                         request_body=WebhookData(),
                         responses={
                             200: Webhook(),
                             400: ErrorResponse()
                         })
    def post(self, request: Request):
        """Create a new webhook."""
        webhook = SerializerDecorator[WebhookSerializer](
            data=request.data, context=request).save().instance

        return Response(Webhook(webhook).data, status=status.HTTP_201_CREATED)
Пример #15
0
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)
Пример #16
0
class CustomsCommodities(APIView):
    @swagger_auto_schema(tags=['Customs'],
                         operation_id=f"{ENDPOINT_ID}add_commodity",
                         operation_summary="Add a commodity",
                         responses={
                             200: Customs(),
                             400: ErrorResponse()
                         },
                         request_body=CommodityData(),
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request POST \\
                  --url /v1/customs_info/<CUSTOMS_INFO_ID>/commodities \\
                  --header 'Authorization: Token <API_KEY>' \\
                  --header 'Content-Type: application/json' \\
                  --data '{
                    "weight": 1,
                    "weight_unit": "KG",
                    "quantity": 1,
                    "sku": "XXXXX0000123",
                    "value_amount": 25,
                    "value_currency": "USD",
                    "origin_country": "CA"
                  }'
                '''
                         }])
    def post(self, request: Request, pk: str):
        """
        Add a customs commodity.
        """
        customs = models.Customs.access_by(request).get(pk=pk)
        shipment = customs.shipment_set.first()

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                "The associated shipment is already 'purchased'",
                status_code=status.HTTP_409_CONFLICT,
                code='state_error')

        commodity = SerializerDecorator[CommoditySerializer](
            data=request.data, context=request).save().instance
        customs.commodities.add(commodity)
        return Response(Customs(commodity.customs_set.first()).data)
Пример #17
0
class WebhookTest(APIView):
    @swagger_auto_schema(tags=['Webhooks'],
                         operation_id=f"{ENDPOINT_ID}test",
                         operation_summary="Test a webhook",
                         request_body=WebhookTestRequest(),
                         responses={
                             200: Operation(),
                             400: ErrorResponse()
                         })
    def post(self, request: Request, pk: str):
        """
        test a webhook.
        """
        webhook = models.Webhook.access_by(request).get(pk=pk)

        notification, *_ = notify_subscribers([webhook], request.data)
        _, response = notification
        serializer = Operation(
            dict(operation="Test Webhook", success=response.ok))
        return Response(serializer.data)
Пример #18
0
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)
Пример #19
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(),
        query_serializer=TestFilters(),
    )
    def post(self, request: Request):
        payload = SerializerDecorator[RateRequest](data=request.data).data
        test_filter = SerializerDecorator[TestFilters](data=request.query_params).data

        response = Rates.fetch(payload, context=request, **test_filter)

        return Response(
            RateResponse(response).data,
            status=status.HTTP_207_MULTI_STATUS if len(response.messages) > 0 else status.HTTP_201_CREATED
        )
Пример #20
0
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)
Пример #21
0
class TrackersCreate(APIView):
    logging_methods = ["GET"]

    @swagger_auto_schema(
        tags=["Trackers"],
        operation_id=f"{ENDPOINT_ID}create",
        operation_summary="Create a shipment tracker",
        query_serializer=TestFilters(),
        responses={
            200: TrackingStatus(),
            404: ErrorResponse()
        },
        manual_parameters=[
            openapi.Parameter(
                "carrier_name",
                in_=openapi.IN_PATH,
                type=openapi.TYPE_STRING,
                enum=CARRIER_NAMES,
            ),
        ],
    )
    def get(self, request: Request, carrier_name: str, tracking_number: str):
        """
        This API creates or retrieves (if existent) a tracking status object containing the
        details and events of a shipping in progress.
        """
        carrier_filter = {
            **SerializerDecorator[TestFilters](data=request.query_params).data,
            "carrier_name":
            carrier_name,
        }
        tracking = (models.Tracking.access_by(request).filter(
            tracking_number=tracking_number).first())

        instance = (SerializerDecorator[TrackingSerializer](
            tracking,
            data=dict(tracking_number=tracking_number),
            context=request).save(carrier_filter=carrier_filter).instance)

        return Response(TrackingStatus(instance).data)
Пример #22
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,
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request GET \\
                  --url '/v1/carriers' \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    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, 'context': request
            })
        ]
        response = self.paginate_queryset(
            CarrierSettings(carriers, many=True).data)
        return self.get_paginated_response(response)
Пример #23
0
class TrackerList(GenericAPIView):
    pagination_class = type("CustomPagination", (LimitOffsetPagination, ),
                            dict(default_limit=20))
    filter_backends = (filters.DjangoFilterBackend, )
    filterset_class = TrackerFilters
    model = models.Tracking

    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(
                **{f"tracking_carrier__{carrier_name}settings__isnull": False
                   }), )

        return queryset.filter(*_filters)

    @swagger_auto_schema(
        tags=["Trackers"],
        operation_id=f"{ENDPOINT_ID}list",
        operation_summary="List all shipment trackers",
        responses={
            200: Trackers(),
            400: ErrorResponse()
        },
        manual_parameters=TrackerFilters.parameters,
    )
    def get(self, request: Request):
        """
        Retrieve all shipment trackers.
        """
        trackers = self.filter_queryset(self.get_queryset())
        response = self.paginate_queryset(
            TrackingStatus(trackers, many=True).data)
        return self.get_paginated_response(response)
Пример #24
0
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()
        },
        manual_parameters=[
            openapi.Parameter('carrier_name',
                              in_=openapi.IN_PATH,
                              type=openapi.TYPE_STRING,
                              enum=CARRIER_NAMES),
        ],
    )
    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.
        """
        test_filter = SerializerDecorator[TestFilters](
            data=request.query_params).data
        payload = SerializerDecorator[TrackingRequest](data=dict(
            tracking_numbers=[tracking_number])).data

        response = Shipments.track(payload,
                                   context=request,
                                   carrier_name=carrier_name,
                                   **test_filter)

        return Response(TrackingResponse(response).data,
                        status=status.HTTP_200_OK if response.tracking
                        is not None else status.HTTP_404_NOT_FOUND)
Пример #25
0
class ParcelList(GenericAPIView):
    queryset = models.Parcel.objects
    pagination_class = type('CustomPagination', (LimitOffsetPagination, ),
                            dict(default_limit=20))

    @swagger_auto_schema(tags=['Parcels'],
                         operation_id=f"{ENDPOINT_ID}list",
                         operation_summary="List all parcels",
                         responses={
                             200: Parcels(),
                             400: ErrorResponse()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request GET \\
                  --url '/v1/parcels' \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def get(self, request: Request):
        """
        Retrieve all stored parcels.
        """
        parcels = models.Parcel.access_by(request).filter(
            shipment_parcels=None)
        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()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request POST \\
                    --url /v1/parcels \\
                    --header 'Authorization: Token <API_KEY>' \\
                    --header 'Content-Type: application/json' \\
                    --data '{
                      "weight": 1,
                      "weight_unit": "KG",
                      "package_preset": "canadapost_corrugated_small_box"
                    }'
                '''
                         }])
    def post(self, request: Request):
        """
        Create a new parcel.
        """
        parcel = SerializerDecorator[ParcelSerializer](
            data=request.data, context=request).save().instance
        return Response(Parcel(parcel).data, status=status.HTTP_201_CREATED)
Пример #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()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request GET \\
                  --url /v1/parcels/<PARCEL_ID> \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def get(self, request: Request, pk: str):
        """
        Retrieve a parcel.
        """
        address = models.Parcel.access_by(request).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()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request PATCH \\
                    --url /v1/parcels/<PARCEL_ID> \\
                    --header 'Authorization: Token <API_KEY>' \\
                    --header 'Content-Type: application/json' \\
                    --data '{
                      "weight": 1.2,
                    }'
                '''
                         }])
    def patch(self, request: Request, pk: str):
        """
        modify an existing parcel's details.
        """
        parcel = models.Parcel.access_by(request).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()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request DELETE \\
                    --url /v1/parcels/<PARCEL_ID> \\
                    --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def delete(self, request: Request, pk: str):
        """
        Remove a parcel.
        """
        parcel = models.Parcel.access_by(request).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)
Пример #27
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()
        },
        manual_parameters=[
            openapi.Parameter('carrier_name',
                              in_=openapi.IN_PATH,
                              type=openapi.TYPE_STRING,
                              enum=CARRIER_NAMES),
        ],
    )
    def post(self, request: Request, carrier_name: str):
        """
        Schedule one or many parcels pickup
        """
        test_filter = SerializerDecorator[TestFilters](
            data=request.query_params).data
        payload = SerializerDecorator[PickupRequest](data=request.data).data

        response = Pickups.schedule(payload,
                                    context=request,
                                    carrier_name=carrier_name,
                                    **test_filter)

        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()
        },
        manual_parameters=[
            openapi.Parameter('carrier_name',
                              in_=openapi.IN_PATH,
                              type=openapi.TYPE_STRING,
                              enum=CARRIER_NAMES),
        ],
    )
    def put(self, request: Request, carrier_name: str):
        """
        Modify a scheduled pickup
        """
        test_filter = SerializerDecorator[TestFilters](
            data=request.query_params).data
        payload = SerializerDecorator[PickupUpdateRequest](
            data=request.data).data

        response = Pickups.update(payload,
                                  context=request,
                                  carrier_name=carrier_name,
                                  **test_filter)

        return Response(PickupResponse(response).data,
                        status=status.HTTP_200_OK)
Пример #28
0
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)
Пример #29
0
class AddressDetail(APIView):
    @swagger_auto_schema(tags=['Addresses'],
                         operation_id=f"{ENDPOINT_ID}retrieve",
                         operation_summary="Retrieve an address",
                         responses={
                             200: Address(),
                             400: ErrorResponse()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request GET \\
                  --url /v1/addresses/<ADDRESS_ID> \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def get(self, request: Request, pk: str):
        """
        Retrieve an address.
        """
        address = models.Address.access_by(request).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()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request PATCH \\
                  --url /v1/addresses/<ADDRESS_ID> \\
                  --header 'Authorization: Token <API_KEY>' \\
                  --header 'Content-Type: application/json' \\
                  --data '{
                    "city": "Pierrefonds"
                }'
                '''
                         }])
    def patch(self, request: Request, pk: str):
        """
        update an address.
        """
        address = models.Address.access_by(request).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)

    @swagger_auto_schema(tags=['Addresses'],
                         operation_id=f"{ENDPOINT_ID}discard",
                         operation_summary="Discard an address",
                         responses={
                             200: Operation(),
                             400: ErrorResponse()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request DELETE \\
                  --url /v1/addresses/<ADDRESS_ID> \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def delete(self, request: Request, pk: str):
        """
        Discard an address.
        """
        address = models.Address.access_by(request).get(pk=pk)
        shipment = address.shipper.first() or address.recipient.first()
        if shipment is not None:
            raise PurplShipApiException(
                "This address is linked to a shipment and cannot be removed",
                status_code=status.HTTP_409_CONFLICT,
                code='state_error')

        address.delete(keep_parents=True)
        serializer = Operation(dict(operation="Discard address", success=True))
        return Response(serializer.data)
Пример #30
0
class AddressList(GenericAPIView):
    queryset = models.Address.objects
    pagination_class = type('CustomPagination', (LimitOffsetPagination, ),
                            dict(default_limit=20))

    @swagger_auto_schema(tags=['Addresses'],
                         operation_id=f"{ENDPOINT_ID}list",
                         operation_summary="List all addresses",
                         responses={
                             200: Addresses(),
                             400: ErrorResponse()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request GET \\
                  --url '/v1/addresses' \\
                  --header 'Authorization: Token <API_KEY>'
                '''
                         }])
    def get(self, request: Request):
        """
        Retrieve all addresses.
        """
        addresses = models.Address.access_by(request).filter(shipper=None,
                                                             recipient=None)
        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()
                         },
                         code_examples=[{
                             'lang':
                             'bash',
                             'source':
                             '''
                curl --request POST \\
                  --url /v1/addresses \\
                  --header 'Authorization: Token <API_KEY>' \\
                  --header 'Content-Type: application/json' \\
                  --data '{
                    "address_line1": "125 Church St",
                    "person_name": "John Doe",
                    "company_name": "A corp.",
                    "phone_number": "+1 514 000 0000",
                    "city": "Moncton",
                    "country_code": "CA",
                    "postal_code": "E1C4Z8",
                    "residential": false,
                    "state_code": "NB"
                }'
                '''
                         }])
    def post(self, request: Request):
        """
        Create a new address.
        """
        address = SerializerDecorator[AddressSerializer](
            data=request.data, context=request).save().instance
        return Response(Address(address).data, status=status.HTTP_201_CREATED)