Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type error object is not JSON serializable when using ChoiceField

I am creating a REST API in Django using the Django Rest framework and cannot use any other libraries or plugins. I have been having an issue the past few days which I am having trouble solving.

In my seralizer.py I have the following

class BookSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source = 'owner.username')
    genres = serializers.ChoiceField(choices=Genre.objects.values_list())
    # genres = GenreSerializer()

class Meta:
    model = VideoGame
    fields = ('title', 'description', 'brief', 'genres', 'owner') 

I would like it so when users access the GUI and attempt to make a new book, they can select a genre from a choicefield, this is then translated into its ID. If users access via the rest API, they must also enter the ID.

When the database is blank, I am able to successfully post a new book to the server and select the dropdown using the choicefield. But when the database has an entry, I get the error below. This is solved when i replace the choicefield with the serializers. However then I am left with an input field and not a choice field. Does anyone know how I can solve this?

TypeError at /book/
<Genre: Genre object> is not JSON serializable
like image 399
Oonah Avatar asked Oct 30 '22 07:10

Oonah


1 Answers

You need to look at a) what does ChoiceField expect to receive as choices and b) what does values_list() return.

a) from the docs the ChoiceField expects " A list of valid values, or a list of (key, display_name) tuples." In this case valid values would be the primary key ids of your Genres.

b) According to the docs values_list() with no arguments returns "all the fields in the model, in the order they were declared."

I don't think you could have got the error message you show in the question from using choices=Genre.objects.values_list() ...maybe you tried choices=Genre.objects.all() instead?

Anyway, you could do this:

class BookSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source = 'owner.username')
    genres = serializers.ChoiceField(choices=Genre.objects.values_list('pk', flat=True))

If there is a field on your Genre model that would make a good 'display name' then you could do:

class BookSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source = 'owner.username')
    genres = serializers.ChoiceField(choices=Genre.objects.values_list('pk', 'name'))

However there is one problem with the both options above, which is that the queryset will be evaluated as soon as you import the file containing the resource. (because it is immediately converted to a list by the ChoiceField).

It is bad practice to have such side-effects at import time. Also since it is only ever evaluated once each time you start your server, the choices will be out of date when you change the genres.

Fortunately there is a better option:
http://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield

This is analogous to a ModelChoiceField in Django. It has a queryset argument which is used for validating choices... but unlike the ChoiceField it knows not to immediately evaluate the queryset. It will be evaluated each time choices need to be validated.

So you should do:

class BookSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source = 'owner.username')
    genres = serializers.PrimaryKeyRelatedField(queryset=Genre.objects.all())

You can see the docs here if you need to customise the 'display name' for your choices on the relation field.

like image 152
Anentropic Avatar answered Nov 15 '22 03:11

Anentropic