0
votes

When using a DRF ViewSet and an APIView, I get two different results for serialization of a DurationField.

At the first endpoint host/app/items, which corresponds to the viewset Items and lists all of the created items, I get this response:

[
    {
        "id": 2,
        "duration": "604800.0",
        ...
    }
    ...
]

The response from host/app/user_data includes the items corresponding to the item instances which have a relation to the profile:

{
    ...
    "items": [
        {
            "item": {
                "id": 2,
                "duration": "P7DT00H00M00S",
                ...
            }
            ...
        }
    ]
}

But the duration is in the ISO 8601 duration format. This was very perplexing, because the endpoints use the same serializer. I confirmed this by forcing the serializing with duration = serializers.DurationField() in ItemSerializer. I want the same format, in seconds. What can I do?

These are issues I found while researching this issue: https://github.com/encode/django-rest-framework/issues/4430 https://github.com/encode/django-rest-framework/issues/4665

Urls:

router = routers.DefaultRouter()
router.register(r'items', views.Items)

urlpatterns = [
    path('', include(router.urls)),
    path('user_data', views.UserData.as_view(), name='user_data'),
    ...
]

Views:

class Items(viewsets.ModelViewSet):
    queryset = Item.objects.all()
    serializer_class = ItemSerializer

    def get_permissions(self):
        if self.action in ('list', 'retrieve'):
            permission_classes = [AllowAny]
        else:
            permission_classes = [IsAdminUser]
        return [permission() for permission in permission_classes]


class UserData(APIView):
    """Get authenticated user's information: data related to models User and Profile"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        user_serialized = UserSerializer(user)
        profile_serialized = ProfileSerializer(user.profile)
        user_info = {}
        user_info.update(user_serialized.data)
        user_info.update(profile_serialized.data)
        return JsonResponse(user_info)

Serializers:

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = '__all__'
        depth = 1


class ItemInstanceSerializer(serializers.ModelSerializer):
    item = ItemSerializer()

    class Meta:
        model = ItemInstance
        fields = ['item']
        depth = 2


class ProfileSerializer(serializers.ModelSerializer):
    items = ItemInstanceSerializer(source='iteminstance_set', many=True)

    class Meta:
        model = Profile
        fields = ['address', 'birth_date', 'items']

Models:

class Item(models.Model):
    ...
    SINGLE_DAY = timedelta(days=1)
    WEEK = timedelta(weeks=1)
    MONTH = timedelta(weeks=4)
    YEAR = timedelta(weeks=52)
    DURATION_CHOICES = [
        (SINGLE_DAY, 'Single-day'),
        (WEEK, 'Week'),
        (MONTH, 'Month'),
        (YEAR, 'Year'),
    ]
    duration = models.DurationField(
        choices=DURATION_CHOICES,
    )
    ...


class ItemInstance(models.Model):
    ...
    item = models.ForeignKey(
        Item,
        on_delete=models.CASCADE,
    )
    ...
1

1 Answers

0
votes

The problem is with the JSONResponse used in UserData. It will use Django's serialization instead of DRF's. Use rest_framework.response.Response instead. You can literally change it out and it should work. You can also remove the line item = ItemSerializer() from ItemInstanceSerializer since ItemSerializer doesn't do anything special. DRF's default serializer fields are nice. They are created here implicitly by ModelSerializer.

The reason why the serialization is different is that serializer.data doesn't always return digested values (fully rendered). When you give that structure to JSONResponse, it will do its own thing with the values that are not JSON-friendly primitives, while Response does it differently. According to documentation serializer.data should only return primitive types, but this appears to not be the case with timedelta. Peculiar.