5

I'm following this link (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers) to write nested serializer. But when I pop the 'vars' from validated_data in the create method of HostSerializer, I found it's empty.

I'm using django 1.9.2 and django restframework 3.3.2.

My model:

class Host(models.Model):

  name = CharField(max_length=20, primary_key=True)
  vm_cpu = IntegerField(default=2)
  vm_mem = IntegerField(default=2048)
  create_vm = BooleanField(default=True)

  def __unicode__(self):
    return('%s' % (self.name))

class Variable(models.Model):

  name = CharField(max_length=10)
  value = CharField(max_length=20)
  host = models.ForeignKey(Host, related_name='vars')

  def __unicode__(self):
    return('%s=%s' % (self.name, self.value))

Serializer

class VariableSerializer(ModelSerializer):

  class Meta:
    model = Variable

class HostSerializer(ModelSerializer):        

  vars = VariableSerializer(many=True)

  class Meta:
    model = Host

  def create(self, validated_data):

    # i set a break point here and found vars_data is empty
    vars_data = validated_data.pop('vars')
    host = Host.objects.create(**validated_data)
    for v in vars_data:
        Variable.objects.create(host = host, **v)

    return host

This is the problem I found vars_data is an empty list:

  def create(self, validated_data):

    # i set a break point here and found vars_data is empty
    vars_data = validated_data.pop('vars')

Here's the rest of the code

admin.py

class VariableAdmin(admin.ModelAdmin):
  list_display = ['name', 'value']

class HostAdmin(admin.ModelAdmin):    
  list_display = ['name']


admin.site.register(Variable, VariableAdmin)
admin.site.register(Host, HostAdmin)

urls.py

router = DefaultRouter()
router.register(r'variables', VariableViewSet, base_name='variables')
router.register(r'hosts', HostViewSet, base_name='hosts')

urlpatterns = [
          url(r'^', include(router.urls)),
          ]

views.py

class VariableViewSet(ModelViewSet):
  queryset = Variable.objects.all()
  serializer_class = VariableSerializer

class HostViewSet(ModelViewSet):
  queryset = Host.objects.all()
  serializer_class = HostSerializer

My test program

post.py

import json
import requests

file = 'host.json'
url = 'http://localhost:8001/test_nest/hosts/'

with open(file, 'r') as f:
  j = f.read()

data = json.loads(j)

r = requests.post(url, data = data)
print r.text

And here's the test data

host.json

{
"name": "host4",
"vars": [
    {
        "name": "var2-a",
        "value": "a1"
    },
    {
        "name": "var2-b",
        "value": "a2"
    }
],
"vm_cpu": 2,
"vm_mem": 2048,
"create_vm": true
}

I'm new to django. So I'm wondering if it's something simple and obvious. Did I use the wrong viewset? Did I post to the wrong URL? Or I setup the URL structure wrong?

youhong
  • 211
  • 1
  • 2
  • 9
  • Figured out the problem.. – youhong Feb 29 '16 at 00:58
  • Figured out the problem. In my post.py, I changed this line `r = requests.post(url, data = data)` to `r = requests.post(url, json = data)`. I think it has to do w/ telling requests to take the content type as json. Note the json parameter to request is only available for requests version 2.4.6 and above. – youhong Feb 29 '16 at 01:04

2 Answers2

0

I your serializers try using...

def update(self, instance,validated_data):

    instance.vars_data = validated_data.get('vars',instance.vars)
    instance.host = Host.objects.create(**validated_data)
    for v in vars_data:
        v,created=Variable.objects.create(host = host, **v)
        instance.v.add(v)

    return host
Amrit
  • 2,115
  • 1
  • 21
  • 41
0

The following code works for me. Maybe you can try it:

models.py

class UserProfile(AbstractUser):
    pass


class TobaccoCert(models.Model):
    license = models.CharField(primary_key=True, max_length=15)
    license_image = models.ImageField(upload_to="certs", blank=True, null=True, verbose_name='cert image')

views.py

class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
   serializer_class = UserRegSerializer
   ...

   def create(self, request, *args, **kwargs):
        data = request.data
        # here i make my nested data `certs` and update it to data
        image_data = request.FILES.get('images')
        _license = data.get('username', None)
        certs = {'license': _license, 'license_image': image_data}
        data['certs'] = certs
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)  # save it
        ret_dict = serializer.data
        payload = jwt_payload_handler(user)
        ret_dict["token"] = jwt_encode_handler(payload)  # 获取token
        ret_dict["name"] = user.name if user.name else user.username
        headers = self.get_success_headers(serializer.data)
        return Response(ret_dict, status=status.HTTP_201_CREATED, headers=headers)

serializer.py

class CertSerializer(serializers.ModelSerializer):
    pass


class UserRegSerializer(serializers.ModelSerializer):
    # [Serializer relations - Django REST framework](https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers)
    # this line is vars of your code
    certs = CertSerializer(many=True,write_only=True)
    ...
    password = serializers.CharField(
        style={'input_type': 'password'}, help_text="passowrd", label="passowrd", write_only=True,
    )

    class Meta:
        model = User
        fields = ("username", "mobile", "password", 'province', 'city', 'dist', 'code', 'certs')

    def create(self, validated_data):
        """
        """
        # here we get certs
        certs_data = self.initial_data.get('certs')
        # just pop certs because it is useless for user create
        certs_empty = validated_data.pop('certs')
        user = super().create(validated_data=validated_data)
        user.set_password(validated_data["password"])
        user.save()
        # use certs data to create cert
        image_url = self.save_image(certs_data)
        _license = validated_data.get('username', None)
        TobaccoCert.objects.create(license=_license, license_image=image_url)
        return user

In short, you need to add your nested parameters directly in the request parameters. In my example code, it is certs which is vars in your example and then use certs_data = self.initial_data.get('certs') get the parameters you passed in create method.

Another possible way is post your data before your requests: In postman: enter image description here in request: python - Django Rest Framework writable nested serializer with multiple nested objects - Stack Overflow

In addition, you can try to modify queryDict directly. You can refer to this link and here

Some useful links

django - Need help understanding many and source fields in a serializer - Stack Overflow

python - Django rest framework writeable nested serializer data missing from validated_data - Stack Overflow

imoyao
  • 11
  • 1