예제 #1
0
class ShipmentNoteViewSet(mixins.ConfigurableCreateModelMixin,
                          mixins.ConfigurableRetrieveModelMixin,
                          mixins.ConfigurableListModelMixin,
                          ConfigurableGenericViewSet):

    queryset = ShipmentNote.objects.all()

    serializer_class = ShipmentNoteSerializer

    permission_classes = READ_PERMISSIONS

    filter_backends = (
        filters.SearchFilter,
        filters.OrderingFilter,
        DjangoFilterBackend,
    )
    filterset_fields = ('user_id', )
    ordering_fields = ('created_at', )
    search_fields = ('message', )

    configuration = {
        'create':
        ActionConfiguration(request_serializer=ShipmentNoteCreateSerializer,
                            response_serializer=ShipmentNoteSerializer,
                            permission_classes=WRITE_PERMISSIONS),
        'list':
        ActionConfiguration(response_serializer=ShipmentNoteSerializer)
    }

    def get_queryset(self):
        return self.queryset.filter(shipment_id=self.kwargs['shipment_pk'])
예제 #2
0
    def test_get_resource_from_configurable_serializer(self, viewset):
        viewset.configuration = {
            'list':
            ActionConfiguration(response_serializer=ListResponseSerializer, ),
            'create':
            ActionConfiguration(
                request_serializer=CreateRequestSerializer,
                response_serializer=CreateResponseSerializer,
            ),
            'custom_action':
            ActionConfiguration(
                request_serializer=CustomActionSerializer,
                response_serializer={
                    'default': CustomActionSerializer,
                    'csv': CustomActionCSVSerializer,
                },
            ),
        }

        renderer = CGVSJsonRenderer()
        response_data = DefaultSerializer(BasicModel(my_field='1')).data
        renderer_context = {
            'view': viewset,
            'request': viewset.request,
            'response': viewset.response
        }

        viewset.action = 'update'
        response = renderer.render(response_data,
                                   renderer_context=renderer_context)
        response = json.loads(response)
        assert response['data']['type'] == 'DefaultSerializer'

        viewset.action = 'list'
        response = renderer.render(response_data,
                                   renderer_context=renderer_context)
        response = json.loads(response)
        assert response['data']['type'] == 'ListResponseSerializer'

        viewset.action = 'create'
        response = renderer.render(response_data,
                                   renderer_context=renderer_context)
        response = json.loads(response)
        assert response['data']['type'] == 'CreateResponseSerializer'

        viewset.action = 'custom_action'
        response = renderer.render(response_data,
                                   renderer_context=renderer_context)
        response = json.loads(response)
        assert response['data']['type'] == 'CustomActionSerializer'

        viewset.action = 'custom_action'
        viewset.kwargs['format'] = 'csv'
        response = renderer.render(response_data,
                                   renderer_context=renderer_context)
        response = json.loads(response)
        assert response['data']['type'] == 'CustomActionCSVSerializer'
        viewset.kwargs['format'] = None
예제 #3
0
    def test_standardize_response_class_no_default(self):
        """
        Providing response_serializer as a dict without a default should raise an AttributeError
        """

        with pytest.raises(AttributeError) as attr_err:
            ActionConfiguration(
                response_serializer=ActionConfiguration.ResponseSerializers(
                    other=CustomActionSerializer, ))
        assert 'response_serializer needs a default provided' in str(
            attr_err.value)
예제 #4
0
    def test_standardize_response_dict_no_default(self):
        """
        Providing response_serializer as a dict without a default should raise an AttributeError
        """
        config = ActionConfiguration(response_serializer={
            'other': CustomActionSerializer,
        })

        assert isinstance(config.response_serializer, dict)
        assert 'other' in config.response_serializer
        assert config.response_serializer['other'] == CustomActionSerializer

        with pytest.raises(AttributeError) as attr_err:
            config.standardize_serializer_properties()
        assert 'response_serializer needs a default provided' in str(
            attr_err.value)
예제 #5
0
    def test_standardize_response_class(self):
        """
        Providing response_serializer as a dict should populate the ResponseSerializers instance correctly
        """
        config = ActionConfiguration(
            response_serializer=ActionConfiguration.ResponseSerializers(
                default=CustomActionSerializer, ))

        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == CustomActionSerializer

        config.standardize_serializer_properties()

        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == CustomActionSerializer
예제 #6
0
class DocumentViewSet(mixins.ConfigurableCreateModelMixin,
                      mixins.ConfigurableRetrieveModelMixin,
                      mixins.ConfigurableUpdateModelMixin,
                      mixins.ConfigurableListModelMixin,
                      ConfigurableGenericViewSet):

    queryset = Document.objects.all()

    serializer_class = DocumentSerializer

    permission_classes = READ_PERMISSIONS

    filter_backends = (
        filters.SearchFilter,
        filters.OrderingFilter,
        DjangoFilterBackend,
    )
    filter_class = DocumentFilterSet

    search_fields = ('name', 'description')

    ordering_fields = (
        'updated_at',
        'created_at',
    )

    configuration = {
        'create':
        ActionConfiguration(request_serializer=DocumentCreateSerializer,
                            response_serializer=DocumentSerializer,
                            permission_classes=WRITE_PERMISSIONS),
        'update':
        ActionConfiguration(permission_classes=WRITE_PERMISSIONS),
    }

    def get_queryset(self):
        return self.queryset.filter(shipment__id=self.kwargs['shipment_pk'])

    def perform_create(self, serializer):
        if settings.PROFILES_ENABLED:
            created = serializer.save(owner_id=get_owner_id(self.request),
                                      shipment_id=self.kwargs['shipment_pk'])
        else:
            created = serializer.save(shipment_id=self.kwargs['shipment_pk'])
        return created
예제 #7
0
class ShipmentTagViewSet(mixins.ConfigurableCreateModelMixin,
                         mixins.ConfigurableDestroyModelMixin,
                         mixins.ConfigurableUpdateModelMixin,
                         ConfigurableGenericViewSet):

    queryset = ShipmentTag.objects.all()

    serializer_class = ShipmentTagSerializer

    permission_classes = READ_PERMISSIONS

    configuration = {
        'create':
        ActionConfiguration(request_serializer=ShipmentTagCreateSerializer,
                            permission_classes=WRITE_PERMISSIONS),
        'update':
        ActionConfiguration(request_serializer=ShipmentTagUpdateSerializer,
                            permission_classes=WRITE_PERMISSIONS)
    }
예제 #8
0
class RouteViewSet(ConfigurableModelViewSet):
    queryset = Route.objects.all()
    serializer_class = RouteSerializer
    permission_classes = ((permissions.IsAuthenticated, )
                          if settings.PROFILES_ENABLED else
                          (permissions.AllowAny, ))

    configuration = {
        'create':
        ActionConfiguration(request_serializer=RouteCreateSerializer, ),
        'update':
        ActionConfiguration(request_serializer=RouteUpdateSerializer, ),
        'reorder':
        ActionConfiguration(request_serializer=RouteOrderSerializer, ),
    }

    def get_queryset(self):
        queryset = self.queryset

        if settings.PROFILES_ENABLED:
            queryset_filter = owner_access_filter(self.request)
            queryset = queryset.filter(queryset_filter)

        return queryset

    def perform_create(self, serializer):
        return serializer.save(owner_id=get_owner_id(self.request))

    @action(detail=True, methods=['post'], url_name='reorder')
    def reorder(self, request, version, pk):
        route = self.get_object()

        serializer = self.get_serializer(instance=route, data=request.data)
        serializer.is_valid(raise_exception=True)

        route.set_routeleg_order(serializer.validated_data['legs'])

        response = self.get_serializer(
            route, serialization_type=SerializationType.RESPONSE)
        return Response(response.data, status=status.HTTP_200_OK)
예제 #9
0
    def test_standardize_serializer(self):
        """
        Providing just a serializer should populate both the Request and Response.default references
        Re-running standardize_serializer_properties should not change the standardized properties
        """
        config = ActionConfiguration(serializer=DefaultSerializer)

        assert config.serializer == DefaultSerializer
        assert config.request_serializer is None
        assert config.response_serializer is None
        assert config.re_serialize_response is False

        config.standardize_serializer_properties()

        assert config.serializer is None
        assert config.request_serializer == DefaultSerializer
        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == DefaultSerializer
        assert config.re_serialize_response is False

        config.standardize_serializer_properties()

        assert config.serializer is None
        assert config.request_serializer == DefaultSerializer
        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == DefaultSerializer
        assert config.re_serialize_response is False
예제 #10
0
    def test_standardize_both_serializer(self):
        """
        Providing both should populate both the Request and the Response.default reference.
        Since we are setting both request and response, so we will set `re_serializer_response` to True.
        Re-running standardize_serializer_properties should not change the standardized properties
        """
        config = ActionConfiguration(
            request_serializer=CreateRequestSerializer,
            response_serializer=CreateResponseSerializer,
        )

        assert config.serializer is None
        assert config.request_serializer == CreateRequestSerializer
        assert config.response_serializer == CreateResponseSerializer
        assert config.re_serialize_response is False

        config.standardize_serializer_properties()

        assert config.serializer is None
        assert config.request_serializer == CreateRequestSerializer
        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == CreateResponseSerializer
        assert config.re_serialize_response is True

        config.standardize_serializer_properties()

        assert config.serializer is None
        assert config.request_serializer == CreateRequestSerializer
        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == CreateResponseSerializer
        assert config.re_serialize_response is True
예제 #11
0
    def test_standardize_response_serializer(self):
        """
        Providing just a response_serializer should populate only the Response.default reference.
        Since we don't have a request_serializer set, the get_serializer in the ConfigurableGenericViewSet
        will return the class level `serializer_class` and we don't know if it's different than our
        response_serializer, so we will set `re_serializer_response` to True.
        Re-running standardize_serializer_properties should not change the standardized properties
        """
        config = ActionConfiguration(response_serializer=DefaultSerializer)

        assert config.serializer is None
        assert config.request_serializer is None
        assert config.response_serializer == DefaultSerializer
        assert config.re_serialize_response is False

        config.standardize_serializer_properties()

        assert config.serializer is None
        assert config.request_serializer is None
        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == DefaultSerializer
        assert config.re_serialize_response is True

        config.standardize_serializer_properties()

        assert config.serializer is None
        assert config.request_serializer is None
        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == DefaultSerializer
        assert config.re_serialize_response is True
예제 #12
0
    def test_standardize_response_dict_many(self):
        """
        Providing response_serializer as a dict with multiple properties
        should populate the ResponseSerializers instance correctly
        """
        config = ActionConfiguration(response_serializer={
            'default': CustomActionSerializer,
            'csv': CustomActionCSVSerializer,
        })

        assert isinstance(config.response_serializer, dict)
        assert 'default' in config.response_serializer
        assert 'csv' in config.response_serializer
        assert config.response_serializer['default'] == CustomActionSerializer
        assert config.response_serializer['csv'] == CustomActionCSVSerializer

        config.standardize_serializer_properties()

        assert isinstance(config.response_serializer,
                          ActionConfiguration.ResponseSerializers)
        assert config.response_serializer.default == CustomActionSerializer
        assert config.response_serializer.csv == CustomActionCSVSerializer
예제 #13
0
class AccessRequestViewSet(mixins.ConfigurableCreateModelMixin,
                           mixins.ConfigurableRetrieveModelMixin,
                           mixins.ConfigurableUpdateModelMixin,
                           mixins.ConfigurableListModelMixin,
                           ConfigurableGenericViewSet):
    queryset = AccessRequest.objects.all()

    serializer_class = AccessRequestSerializer

    permission_classes = ((
        permissions.IsAuthenticated,
        ShipmentExists,
        HasViewSetActionPermissions,
        IsShipmentOwner | IsRequester,
    ) if settings.PROFILES_ENABLED else (
        permissions.AllowAny,
        ShipmentExists,
    ))

    filter_backends = (
        filters.SearchFilter,
        filters.OrderingFilter,
        DjangoFilterBackend,
    )
    filterset_fields = ('requester_id', 'approved', 'approved_by')
    ordering_fields = ('created_at', 'approved_at')

    configuration = {
        'create':
        ActionConfiguration(request_serializer=AccessRequestCreateSerializer,
                            response_serializer=AccessRequestSerializer,
                            permission_classes=(IsNotShipmentOwner, )
                            if settings.PROFILES_ENABLED else (
                                permissions.AllowAny,
                                ShipmentExists,
                            )),
    }

    def get_queryset(self):
        queryset = self.queryset.filter(shipment_id=self.kwargs['shipment_pk'])

        if settings.PROFILES_ENABLED:
            queryset = queryset.filter(
                Q(requester_id=self.request.user.id)
                | shipment_owner_access_filter(self.request))
        return queryset
예제 #14
0
class RouteLegViewSet(cgvs_mixins.CreateModelMixin,
                      cgvs_mixins.DestroyModelMixin,
                      ConfigurableGenericViewSet):
    queryset = RouteLeg.objects.all()
    serializer_class = RouteLegInfoSerializer
    permission_classes = ((permissions.IsAuthenticated, NestedRoutePermission)
                          if settings.PROFILES_ENABLED else
                          (permissions.AllowAny, NestedRoutePermission))

    configuration = {
        'create':
        ActionConfiguration(
            request_serializer=RouteLegCreateSerializer,
            response_serializer=RouteLegInfoSerializer,
        )
    }

    def get_queryset(self):
        queryset = self.queryset.filter(route__id=self.kwargs['route_pk'])

        if settings.PROFILES_ENABLED:
            user_id, organization_id = get_user(self.request)

            queryset_filter = Q(route__owner_id=user_id)
            if organization_id:
                queryset_filter |= Q(route__owner_id=organization_id)

            queryset = queryset.filter(queryset_filter)

        return queryset

    def perform_destroy(self, instance):
        if instance.route.routeleg_set.filter(shipment__state__gt=TransitState.
                                              AWAITING_PICKUP.value).exists():
            raise ValidationError(
                'Cannot remove shipment from route after transit has begun')

        order = list(instance.route.get_routeleg_order())
        order.remove(instance.pk)

        super().perform_destroy(instance)
        instance.route.set_routeleg_order(order)
예제 #15
0
    def test_configurable_generic_view_set_default_user_perms(self, viewset):
        viewset.default_required_user_permissions = [
            'feature.default_permission'
        ]
        viewset.configuration = {
            'list':
            ActionConfiguration(
                response_serializer=ListResponseSerializer,
                permission_classes=[FakePermission, FakePermission2],
                required_user_permissions=[
                    'feature.permission', 'feature.permission2'
                ],
            ),
            'custom_action':
            ActionConfiguration(
                permission_classes=[FakePermission, FakePermission3], ),
        }

        viewset.action = 'list'
        assert viewset.get_serializer().__class__ == DefaultSerializer
        assert viewset.get_serializer(
            serialization_type=SerializationType.RESPONSE
        ).__class__ == ListResponseSerializer
        assert viewset.get_serializer_class() == DefaultSerializer
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == ListResponseSerializer
        assert viewset.get_permissions() == [FakePermission, FakePermission2]
        assert viewset.configuration['list'].serializer is None
        assert viewset.action_user_permissions['list'] == [
            'feature.permission', 'feature.permission2'
        ]

        viewset.action = 'custom_action'
        assert viewset.get_serializer().__class__ == DefaultSerializer
        assert viewset.get_serializer(serialization_type=SerializationType.
                                      RESPONSE).__class__ == DefaultSerializer
        assert viewset.get_serializer_class() == DefaultSerializer
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == DefaultSerializer
        assert viewset.get_permissions() == [FakePermission, FakePermission3]
        assert viewset.configuration['custom_action'].serializer is None
        assert 'custom_action' in viewset.action_user_permissions
        assert viewset.action_user_permissions['custom_action'] == [
            'feature.default_permission'
        ]

        viewset.action = 'update'
        assert viewset.get_serializer().__class__ == DefaultSerializer
        assert viewset.get_serializer(serialization_type=SerializationType.
                                      RESPONSE).__class__ == DefaultSerializer
        assert viewset.get_serializer_class() == DefaultSerializer
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == DefaultSerializer
        assert viewset.get_permissions() == [FakePermission2]
        assert 'update' not in viewset.configuration
        assert 'update' in viewset.action_user_permissions
        assert viewset.action_user_permissions['update'] == [
            'feature.default_permission'
        ]

        viewset.action = 'list'
        assert viewset.action_user_permissions['list'] == [
            'feature.permission', 'feature.permission2'
        ]
예제 #16
0
    def test_configurable_generic_view_set(self, viewset):
        viewset.configuration = {
            'list':
            ActionConfiguration(
                response_serializer=ListResponseSerializer,
                permission_classes=[FakePermission, FakePermission2],
                required_user_permissions=[
                    'feature.permission', 'feature.permission2'
                ],
            ),
            'create':
            ActionConfiguration(
                request_serializer=CreateRequestSerializer,
                request_validation=False,
                response_serializer=CreateResponseSerializer,
                permission_classes=[FakePermission],
                required_user_permissions=['feature.permission'],
                success_status=status.HTTP_202_ACCEPTED,
            ),
            'custom_action':
            ActionConfiguration(
                request_serializer=CustomActionSerializer,
                response_serializer={
                    'default': CustomActionSerializer,
                    'csv': CustomActionCSVSerializer,
                },
                permission_classes=[FakePermission, FakePermission3],
                required_user_permissions='feature.custom_permission',
            ),
        }

        viewset.action = 'list'
        assert viewset.get_serializer().__class__ == DefaultSerializer
        assert viewset.get_serializer(
            serialization_type=SerializationType.RESPONSE
        ).__class__ == ListResponseSerializer
        assert viewset.get_serializer_class() == DefaultSerializer
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == ListResponseSerializer
        assert viewset.get_permissions() == [FakePermission, FakePermission2]
        assert viewset.configuration['list'].serializer is None
        assert viewset.action_user_permissions['list'] == [
            'feature.permission', 'feature.permission2'
        ]

        viewset.action = 'create'
        assert viewset.get_serializer().__class__ == CreateRequestSerializer
        assert viewset.get_serializer(
            serialization_type=SerializationType.RESPONSE
        ).__class__ == CreateResponseSerializer
        assert viewset.get_serializer_class() == CreateRequestSerializer
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == CreateResponseSerializer
        assert viewset.get_permissions() == [FakePermission]
        assert viewset.configuration['create'].raise_validation is False
        assert viewset.configuration['create'].serializer is None
        assert viewset.action_user_permissions['create'] == [
            'feature.permission'
        ]

        viewset.action = 'custom_action'
        assert viewset.get_serializer().__class__ == CustomActionSerializer
        assert viewset.get_serializer(
            serialization_type=SerializationType.RESPONSE
        ).__class__ == CustomActionSerializer
        assert viewset.get_serializer_class() == CustomActionSerializer
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == CustomActionSerializer
        assert viewset.get_permissions() == [FakePermission, FakePermission3]
        assert viewset.configuration['custom_action'].serializer is None
        assert viewset.action_user_permissions['custom_action'] == [
            'feature.custom_permission'
        ]
        viewset.kwargs['format'] = 'csv'
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == CustomActionCSVSerializer
        viewset.kwargs['format'] = 'invalid'
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == CustomActionSerializer
        viewset.kwargs['format'] = None

        viewset.action = 'update'
        assert viewset.get_serializer().__class__ == DefaultSerializer
        assert viewset.get_serializer(serialization_type=SerializationType.
                                      RESPONSE).__class__ == DefaultSerializer
        assert viewset.get_serializer_class() == DefaultSerializer
        assert viewset.get_serializer_class(
            SerializationType.RESPONSE) == DefaultSerializer
        assert viewset.get_permissions() == [FakePermission2]
        assert 'update' not in viewset.configuration
        assert 'update' not in viewset.action_user_permissions
예제 #17
0
    def test_validation_retrieve(self):
        action = 'retrieve'
        config = ActionConfiguration(request_serializer=ListResponseSerializer)

        with pytest.raises(AttributeError) as attr_err:
            config.validate_action(action)
        assert 'request_serializer not valid for action retrieve' in str(
            attr_err.value)

        config = ActionConfiguration(request_validation=True)

        with pytest.raises(AttributeError) as attr_err:
            config.validate_action(action)
        assert 'request_validation not valid for action retrieve' in str(
            attr_err.value)

        config = ActionConfiguration(success_status=200)

        with pytest.raises(AttributeError) as attr_err:
            config.validate_action(action)
        assert 'success_status not valid for action retrieve' in str(
            attr_err.value)
예제 #18
0
class ShipmentViewSet(ConfigurableModelViewSet):
    # We need to revisit the ConfigurableGenericViewSet to ensure
    # that it properly allow the inheritance of this attribute
    resource_name = 'Shipment'

    queryset = Shipment.objects.all()

    serializer_class = ShipmentSerializer

    permission_classes = ((
        HasViewSetActionPermissions,
        IsOwnerOrShared,
    ) if settings.PROFILES_ENABLED else (permissions.AllowAny, ))

    filter_backends = (
        filters.SearchFilter,
        filters.OrderingFilter,
        DjangoFilterBackend,
    )

    filterset_class = ShipmentFilter

    search_fields = SHIPMENT_SEARCH_FIELDS

    ordering_fields = SHIPMENT_ORDERING_FIELDS

    http_method_names = ['get', 'post', 'delete', 'patch']

    configuration = {
        'create':
        ActionConfiguration(
            request_serializer=ShipmentCreateSerializer,
            response_serializer=ShipmentTxSerializer,
        ),
        'update':
        ActionConfiguration(request_serializer=ShipmentUpdateSerializer,
                            response_serializer=ShipmentTxSerializer,
                            permission_classes=WRITE_PERMISSIONS),
        'retrieve':
        ActionConfiguration(permission_classes=READ_PERMISSIONS),
        'destroy':
        ActionConfiguration(permission_classes=DELETE_PERMISSIONS),
    }

    def get_queryset(self):
        queryset = self.queryset
        if settings.PROFILES_ENABLED:
            queryset_filter = owner_access_filter(self.request)

            permission_link = self.request.query_params.get('permission_link')
            if permission_link:
                # The  validity of the permission link object has already been done by the permission class
                queryset_filter = queryset_filter | Q(
                    pk=PermissionLink.objects.get(
                        pk=permission_link).shipment.pk)

            elif self.detail:
                return queryset

            else:
                queryset_filter = queryset_filter | shipment_list_wallets_filter(
                    self.request)
                queryset_filter = queryset_filter | Q(
                    pk__in=self.request.user.access_request_shipments)

            queryset = queryset.filter(queryset_filter)

        queryset = self._parse_customer_fields_queries(queryset)

        return queryset

    def _parse_customer_fields_queries(self, queryset):
        for key, value in self.request.query_params.items():
            if key.startswith('customer_fields__'):
                queryset = queryset.filter(**{key: parse_value(value)})

        return queryset

    def perform_create(self, serializer):
        if settings.PROFILES_ENABLED:
            background_data_hash_interval = self.request.user.token.get(
                'background_data_hash_interval',
                settings.DEFAULT_BACKGROUND_DATA_HASH_INTERVAL)
            manual_update_hash_interval = self.request.user.token.get(
                'manual_update_hash_interval',
                settings.DEFAULT_MANUAL_UPDATE_HASH_INTERVAL)
            created = serializer.save(
                owner_id=get_owner_id(self.request),
                updated_by=self.request.user.id,
                background_data_hash_interval=background_data_hash_interval,
                manual_update_hash_interval=manual_update_hash_interval)
        else:
            created = serializer.save(background_data_hash_interval=settings.
                                      DEFAULT_BACKGROUND_DATA_HASH_INTERVAL,
                                      manual_update_hash_interval=settings.
                                      DEFAULT_MANUAL_UPDATE_HASH_INTERVAL)
        return created

    def perform_update(self, serializer):
        return serializer.save(updated_by=self.request.user.id)

    def create(self, request, *args, **kwargs):
        """
        Create a Shipment object and make Async Request to Engine
        """
        LOG.debug('Creating a shipment object.')
        log_metric('transmission.info',
                   tags={
                       'method': 'shipments.create',
                       'module': __name__
                   })
        # Create Shipment
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        shipment = self.perform_create(serializer)
        async_job = shipment.asyncjob_set.all()[:1]

        response = self.get_serializer(
            shipment, serialization_type=SerializationType.RESPONSE)
        if async_job:
            LOG.debug(f'AsyncJob created with id {async_job[0].id}.')
            response.instance.async_job_id = async_job[0].id

        return Response(response.data, status=status.HTTP_202_ACCEPTED)

    @method_decorator(cache_page(60 * 60, remember_all_urls=True)
                      )  # Cache responses for 1 hour
    @action(
        detail=True,
        methods=['get'],
        permission_classes=(IsOwnerOrShared | AccessRequest.permission(
            Endpoints.tracking, PermissionLevel.READ_ONLY), ),
    )
    def tracking(self, request, version, pk):
        """
        Retrieve tracking data from db
        """
        LOG.debug(f'Retrieve tracking data for a shipment {pk}.')
        log_metric('transmission.info',
                   tags={
                       'method': 'shipments.tracking',
                       'module': __name__
                   })
        shipment = self.get_object()

        if hasattr(shipment, 'routeleg'):
            if shipment.state == TransitState.AWAITING_PICKUP:
                # RouteTrackingData may contain data for other shipments already picked up.
                # This shipment should not include those data as it has not yet begun transit.
                tracking_data = RouteTrackingData.objects.none()
            else:
                tracking_data = RouteTrackingData.objects.filter(
                    route__id=shipment.routeleg.route.id)
        else:
            tracking_data = TrackingData.objects.filter(
                shipment__id=shipment.id)

        response = Template('{"data": $geojson}')
        response = response.substitute(
            geojson=render_filtered_point_features(shipment, tracking_data))
        return HttpResponse(content=response,
                            content_type='application/vnd.api+json')

    def update(self, request, *args, **kwargs):
        """
        Update the shipment with new details, overwriting the built-in method
        """
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        LOG.debug(f'Updating shipment {instance.id} with new details.')
        log_metric('transmission.info',
                   tags={
                       'method': 'shipments.update',
                       'module': __name__
                   })

        serializer = self.get_serializer(instance,
                                         data=request.data,
                                         partial=partial)
        serializer.is_valid(raise_exception=True)

        shipment = self.perform_update(serializer)
        async_jobs = shipment.asyncjob_set.filter(
            state__in=[JobState.PENDING, JobState.RUNNING])
        response = self.get_serializer(
            shipment, serialization_type=SerializationType.RESPONSE)

        response.instance.async_job_id = async_jobs.latest(
            'created_at').id if async_jobs else None

        return Response(response.data, status=status.HTTP_202_ACCEPTED)