Splitting model instance for serializer into 3 dif

2019-06-28 04:28发布

问题:

Tom Christie has helped me a bunch in gear me to the right direction of using REST framework, but I have another problem, now:

NOTE: This is using a viewsets.ModelViewSet

In my original code, I can return coordinate JSON data by using zip() with split() on the model instance xyz (which holds coordinate data like "20x40x50"). I called my own toJSON() function to make JSON-ready ouput of everything i need. It comes out something like:

[
  {
   "id" : "4"
   "x" : "500",
   "Y" : "80",
   "z" : "150"
   "color" : "yellow"
  },
  ...
]

The problem with using REST Framework serializers is that I only know how to do the serializers.Field(source"xyz") thing. I do not know how to return "x" "y" "z" as separate fields, instead of return "xyz" as one big field.

Here's my code:

serializers.py:
---------------
class NoteSerializer(serializers.ModelSerializer):
    owner = serializers.Field(source='owner.username')
    firstname = serializers.Field(source='owner.first_name')
    lastname = serializers.Field(source='owner.last_name')

    x = ???
    y = ???
    z = ???

class Meta:
    model = Note
    fields = ('id','owner','firstname','lastname','text','color', 'x', 'y, 'z', 'time')

And here's the view:

    views.py:
    ---------
    def list(self, request, format=None):
        if request.method == 'GET':
            queryset = Note.objects.filter(owner=request.user)
            serializer = NoteSerializer(queryset, many=True)
            if 'text' in request.GET:
                if self.is_numeric(request.GET['id']) and self.is_numeric(request.GET['x']) and self.is_numeric(request.GET['y']) and self.is_numeric(request.GET['z']):

                    serializer = NoteSerializer(data=request.QUERY_PARAMS)
                    intx = int(float(request.GET['x']))
                    inty = int(float(request.GET['y']))
                    intz = int(float(request.GET['z']))
                    serializer.object.xyz = str(intx) +'x'+ str(inty) +'x'+ str(intz)
                    serializer.save()

            return Response(serializer.data, status=status.HTTP_201_CREATED)

    def create(self, request, format=None):

        serializer = NoteSerializer(data=request.DATA)
        if serializer.is_valid():
            serializer.object.owner = request.user
            serializer.save()

            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Here's my model:

from django.db import models
import datetime
import json
from django.utils import timezone
from django.core.urlresolvers import reverse
from django.core import serializers
from django.contrib.auth.models import User

class Note(models.Model):
owner = models.ForeignKey('auth.User', null=True)
text = models.CharField(max_length=500)
color = models.CharField(max_length=20)
xyz = models.CharField(max_length=20)
time = models.DateTimeField((u"Note Creation Date and Time"), auto_now_add=True,  blank=True)

def __unicode__(self):
    return unicode(self.owner)

Thanks a lot for the help! I'm new to Python/Django/REST. This seems very interesting, but just has been frustrating me for days.

UPDATE:

It seems I cannot access xyz through the views.py with serializer.object.xyz. It says the same error "Nonetype has no attribute xyz"

serializer = NoteSerializer(data=request.QUERY_PARAMS)
intx = int(float(request.GET['x']))
inty = int(float(request.GET['y']))
intz = int(float(request.GET['z']))
serializer.object.xyz = str(intx) +'x'+ str(inty) +'x'+ str(intz)
serializer.save()

回答1:

You can try sending your x,y,z to the serializer using the get_serializer_context so something like...

from core import models

from rest_framework import generics
from rest_framework import serializers

class BloopModelSerializer(serializers.ModelSerializer):
    x_coord = serializers.SerializerMethodField('get_x')

    def get_x(self, instance):
        return self.context['x']

    class Meta:
        model = models.Bloop

class BloopAPIView(generics.ListCreateAPIView):
    serializer_class = BloopModelSerializer
    queryset = models.Bloop.objects.all()

    def get_serializer_context(self):
        context = super(BloopAPIView, self).get_serializer_context()
        # you have access to self.request here
        context.update({
            'x': 1111,
            'y': 2222
        })

        return context

This way you don't have to override the list and create functions anymore.

On a side note, you can also try putting your coords into its own serializer and group them within the serializer. rest_framework can nest serializers, so you can add a CoordinatesSerializer class and your Model Serializer would look something like this

class CoordinateSerializer(serializers.Serializer):
    x = models.Field()
    y = models.Field()
    # ...

class BloopModelSerializer(serializers.ModelSerializer):
    coordinates = CoordinateSerializer('get_coords')

    def get_coords(self, instance):
        return self.context['coords']

    class Meta:
        model = models.Bloop


回答2:

My approach on this:

COORD = dict(x=0, y=1, z=2)

class CoordinateField(serializers.Field):
    def field_to_native(self, obj, field_name):
        # retrieve and split coordinates
        coor = obj.content.split('x')

        # get coordinate value depending on coordinate key (x,y,z)
        return int(coor[COORD[field_name]])

    def field_from_native(self, data, files, field_name, into):
        into['xyz'] = u'{x}x{y}x{z}'.format(**data)
        super(CoordinateField, self).field_from_native(data, files, field_name, into)

class BloopModelSerializer(serializers.ModelSerializer):
    x = CoordinateField()
    y = CoordinateField()
    z = CoordinateField()

    class Meta:
        model = Bloop

and this is what I get as result:

{
    "x": 10,
    "y": 20,
    "z": 30,
    "content": "10x20x30"
},

EDIT:

views.py

class BloopList(generics.ListCreateAPIView):
    queryset = Bloop.objects.all()
    serializer_class = BloopModelSerializer

bloop_list = Bloop.as_view()

urls.py

url(r'^api/bloops/$', 'myapp.views.bloop_list', name='bloop-list'),

SUGGESTION

You should not use list GET method to change/add object, DRF has built-in classes to make it very easy and makes you follow correct REST standards.

For example, your list method takes request data using GET params, which is a bad idea, whenever you update or add new object you should provide data inside request body using POST or PUT. DRF assumes, that is the way data is provided and takes care of everything.