JSONField serializes as json for POST, but string for GET

Viewed 2263

There is likely a very simple problem with my code, but I've been slamming my head against this problem for a couple days and can't make any headway.

Important Packages:

Django==1.11.3
django-cors-headers==2.1.0
djangorestframework==3.7.0
drf-nested-routers==0.90.0
psycopg2==2.7.3
pycparser==2.18

Here is what is happening:

I create a model via an AJAX call

enter image description here

My server correctly serializes the brainstorm_data field as a json object.

Now I navigate my user to the next page and fetch the current model

enter image description here

For some reason, brainstorm_data is now be returned as a string. Anytime I call a GET request on this resource I always get a string representation of the JSON object.

Here is the code associated:

models.py

from django.contrib.postgres.fields import JSONField

class Adventure(TimeStampedModel,
                    models.Model):
        name = models.CharField(max_length=200)
        user = models.ForeignKey(User)
        world = models.ForeignKey(World)
        theme = models.ForeignKey(Theme, default=1)
        brainstorm_data = JSONField()
        image_src = models.CharField(max_length=400, null=True, blank=True)
        sentence_summary = models.TextField(null=True, blank=True)
        paragraph_summary = models.TextField(null=True, blank=True)
        page_summary = models.TextField(null=True, blank=True)
        outline_complete = models.BooleanField(default=False)
        brainstorm_complete = models.BooleanField(default=False)
        private = models.BooleanField(default=False)

        def __str__(self):
            return self.name

views.py

class MyAdventuresViewSet(viewsets.ModelViewSet):
    queryset = Adventure.objects.all()
    serializer_class = AdventureSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        return Adventure.objects.filter(user=self.request.user)

    def create(self, request, *args, **kwargs):
        user = self.request.user
        world = World.objects.filter(user=user).first()

        if not world:
            world = World.objects.create(name='My World', user=user,
                                         description="This is a default world we created for your adventures",
                                         image_src=static('worlds/images/world_placeholder.png'))

        data = request.data.copy()
        data['user'] = user.pk
        data['world'] = world.pk
        data['theme'] = 1  # default theme
        data['brainstorm_data'] = default_brainstorm
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        adventure = serializer.save()

        Storyboard.objects.create(adventure=adventure, raw=default_storyboard['raw'], html=default_storyboard['html'])

        return JsonResponse(serializer.data)

    @detail_route(methods=['post'])
    def complete_outline(self, request, pk):
        adventure = Adventure.objects.get(pk=self.kwargs['pk'])
        complete_adventure_outline(adventure)

        serializer = self.get_serializer(data=adventure)
        serializer.is_valid(raise_exception=True)
        return JsonResponse(serializer.data, status=200)

    @detail_route(methods=['post'])
    def genres(self, request, pk):
        genre_names = request.data
        genre_models = Genre.objects.filter(name__in=genre_names)
        adventure = self.get_object()
        adventure.genre_set.set(genre_models)
        adventure.save()
        serializer = AdventureSerializer(adventure)
        return JsonResponse(serializer.data)

serializers.py

class AdventureSerializer(serializers.ModelSerializer):
    genre_set = serializers.StringRelatedField(many=True, read_only=True)
    character_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    location_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    storyboard = serializers.PrimaryKeyRelatedField(read_only=True)
    theme = serializers.PrimaryKeyRelatedField(queryset=Theme.objects.all())

    class Meta:
        model = Adventure
        fields = '__all__'

mixins

# this is a dictionary used to default brainstorm data each time an adventure is created
default_brainstorm = {
    "nodes": [...],
    "edges": [...]
}
2 Answers

You can override the to_internal_value and to_representation in a new serializer field to handle the return data for JSON field.

class JSONSerializerField(serializers.Field):
    """Serializer for JSONField -- required to make field writable"""

    def to_internal_value(self, data):
        return data

    def to_representation(self, value):
        return value

And in turn, you would use this Field in a serializer:

class SomeSerializer(serializers.ModelSerializer):
    json_field = JSONSerializerField()

    class Meta:
        model = SomeModelClass
        fields = ('json_field', )

This should solve your problem :)

When I originally created the columns I did it with a different json field package. The base DB columns was actually text instead of json or jsonb. Creating new columns (django json fields), migrating the data, and then shifting the data back got my database back in a consistent order.

Related