Django Rest Framework, HyperlinkedModelSerializers, ModelViewSets and Writable GenericForeignKeys: How?

I have a model FinancialTransaction

which is typical of the field content_type

, object_id

and content_object

to set up global relationships to any of my other models.

I figured out how to serialize this readable relationship:

class FinancialTransactionSerializer(serializers.HyperlinkedModelSerializer):
    content_object = serializers.SerializerMethodField('get_content_obj_url')

    def get_content_obj_url(self, obj):
        obj = obj.content_object

        view_name = obj._meta.object_name.lower() + "-detail"
        s = serializers.HyperlinkedIdentityField(source=obj, view_name=view_name)
        s.initialize(self, None)
        return s.field_to_native(obj, None)

    class Meta:
        model = FinancialTransaction
        fields = ('id', 'value', 'date', 'memo', 'banking_account', 'content_object')

      

ViewSet:

class FinancialTransactionViewSet(viewsets.ModelViewSet):
    model = FinancialTransaction
    serializer_class = FinancialTransactionSerializer

      

This creates a hyperlink to the associated object for the serialized view when I do a GET on the view.

However, I'm kind of stuck on how to make it so that I can POST a new financial transagent with an already existing linked entity.

Ideally, this would work just like a normal ForeignKey, where I can POST something like:

{"value": "200.00",
 "date": "2014-10-10",
 "memo": "repairs",
 "banking_account": "http://domain.com/api/banking_account/134/",
 "content_object": "http://domain.com/api/property/432/"
}

      

+3


source to share


2 answers


Okay, to answer my own question ...

I tried it restore_fields

in my own serializer like this:

class FinancialTransactionSerializer(serializers.HyperlinkedModelSerializer):
    content_object = serializers.SerializerMethodField('get_content_obj_url')

    def get_content_obj_url(self, obj):
        obj = obj.content_object

        view_name = get_view_name(obj)
        s = serializers.HyperlinkedIdentityField(source=obj, view_name=view_name)
        s.initialize(self, None)
        return s.field_to_native(obj, None)

    def restore_fields(self, data, files):
        content_object = None

        if 'content_object' in data:
            request = self.context.get('request')  
            content_object = get_object_from_url(request.DATA['content_object'])

        attrs = super(FinancialTransactionSerializer, self).restore_fields(data, files)
        if content_object:
            attrs['content_object'] = content_object
        return attrs

    class Meta:
        model = FinancialTransaction
        fields = ('id', 'value', 'date', 'memo', 'banking_account', 'content_object')

def get_model_from_url(url: str):
    return resolve(urlparse(url).path).func.cls.model

def get_object_from_url(url: str):
    model = get_model_from_url(url)
    pk = resolve(urlparse(url).path).kwargs.get('pk')
    if not pk:
        return None
    return model.objects.get(pk=pk)

      



This setup serializes the objects so that the field content_object

contains a hyperlink to the associated object, and when POST'ing to a view using this serializer and the data includes a key content_object

, we get the associated object and pass it in.

The restore_fields

attrs returned from the value are used in the method restore_object

, and since we looked up the content object and put it in attrs, it restore_object

sets the content_object

FinancialTransaction object attribute to the retrieved object and then Django takes care of the rest.

So far, the only drawback I see is that it doesn't add the field content_object

to the view API ... but I'm not sure how that would work anyway, since linked objects are usually provided in select and I don't I think we would like the selection to be populated with every single object in our database.

+1


source


You can read more in this document: Mark additional steps for routing , if you need specified routing for your request POST

, see their sample code:

from django.contrib.auth.models import User
from rest_framework import status
from rest_framework import viewsets
# See these imports
from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from myapp.serializers import UserSerializer, PasswordSerializer

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    # just place your logical def with the existing decorator, like so:
    @detail_route(methods=['post'])
    def set_password(self, request, pk=None):
        user = self.get_object()
        serializer = PasswordSerializer(data=request.DATA)
        if serializer.is_valid():
            user.set_password(serializer.data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

      



Hope this helps.

0


source







All Articles