Django Rest Framework - OPTIONS request - Get foreign key choices

Viewed 3937

Lets say I have two models in Django, and I'm using standard Viewsets and Serializers to expose endpoints.

ModelA has a ForeignKey field to ModelB. When I do an OPTIONS request to the ModelA endpoint it just lists it as a field, like below:

"actions": {
    "POST": {
        "pk": {
            "type": "integer",
            "required": false,
            "read_only": true,
            "label": "ID"
        },
        "created": {
            "type": "datetime",
            "required": false,
            "read_only": true,
            "label": "Created"
        },

        "foreign_key_field": {
            "type": "field",
            "required": true,
            "read_only": false,
            "label": "Wcp"
        },
    }
}

I would like it to have a 'choices' property which pulls down every single instance of Model B. This way in my Angular frontend I can create a dropdown of all the choice.

I think I need to make a new MetaData class or somthing. But I can't find any solid way of doing this. I also think this functionality has changed in 3.X of DRF so old answers around the internet no longer help.

Cheers, Dean

3 Answers

Made some improvements to @ralfzen his answer to improve serialization. Also needed to use RelatedField instead of just ManyRelatedField

from django.utils.encoding import force_text
from rest_framework.views import APIView
from rest_framework.metadata import SimpleMetadata
from rest_framework.relations import ManyRelatedField, RelatedField


class MyMetaData(SimpleMetadata):

    def get_field_info(self, field):
        field_info = super(MyMetaData, self).get_field_info(field)
        if isinstance(field, (RelatedField, ManyRelatedField)):
            field_info['choices'] = [
                {
                    'value': choice_value,
                    'display_name': force_text(choice_name, strings_only=True)
                }
                for choice_value, choice_name in field.get_choices().items()
            ]
        return field_info


class MyView(APIView):
    metadata_class = MyMetaData

I don't have a perfect answer as well, but my way of achieving this was mapping json items to their ids and storing such a collection like this (in angular):

$scope.nodes = response.data; //original data retrieved from django
$scope.nodesIds = response.data.map(function (v) { return v.id; }); //particular ids

doing this, you're able to use that map in ng-options in such a way:

<select ng-model="node_start" ng-options="node for node in nodesIds" />

Any better ideas?

//After thorough consideration (it turns out that this is a 'manual' approach)

from rest_framework.metadata import SimpleMetadata
class MyMetadata(SimpleMetadata):
    def get_serializer_info(self, serializer):
        orderedDict = super().get_serializer_info(serializer)
        orderedDict['foreign_key_field']['choices'] = [x.id for x in Node.objects.all()]
        return orderedDict

class ViewSet(ModelViewSet):
    metadata_class = MyMetadata

Please let me know if it works for you :)

//Finally (smart approach with changes in default DRF code)

If you want it fully automatically, all you have to do is adding three lines of code to metadata.py:

on top:

from rest_framework.relations import PrimaryKeyRelatedField

in get_field_info method:

if type(field) is PrimaryKeyRelatedField:
    field_info['choices'] = [x.id for x in field.queryset.all()]
Related