1

Complete DRF beginner here... I'm confused about the following concepts:

  1. Let's say I POST some data, including a complex JSON blob for one of the fields, in order to create an object. Where should I actually create this object? Looking at the 3.1 docs, it seems like two places are equally valid for this: Serializer.create() and ViewSet.create(). How do I decide where to create my object and which way is considered "canonical"?

  2. I understand that I need to run Serializer.is_valid() in order to validate the POSTed data. However, what is the difference between .data and .validated_data? They appear to be the same.

  3. Finally, what is the "canonical" way to use a JSONField (e.g. django-jsonfield, but I'm not married to this package/implementation)? I have a model with several JSONFields and would like to use it "correctly" in DRF. I am aware of https://stackoverflow.com/a/28200902/585783, but it doesn't seem enough.

EDIT: My use case is an API POST that includes a complex JSON blob in one of the fields. I need to parse the JSON field, validate it, get/create several objects based on it, link new and existing objects, and finally store the JSON field in one of the new objects. So, I need to do custom validation for this JSON field by parsing it to python:

from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser

class MySerializer(serializers.ModelSerializer):
    my_json_field = JSONSerializerField()

    def validate_my_json_field(self, value):
        stream = BytesIO(value)
        list_of_dicts = JSONParser().parse(stream)
        # do lots of validation to list_of_dicts
        # render list_of_dicts back to a JSON string
        return validated_list_of_dicts_as_json

Now, depending on which way I choose in Concept 1, I have to parse the validated JSON again to create my objects in create(), which doesn't feel right.

Thanks in advance!

Community
  • 1
  • 1
Cloud Artisans
  • 4,016
  • 3
  • 30
  • 37

1 Answers1

3

The contents of HTTP requests (POST, GET, PUT, DELETE) will always be processed by the views (View, APIView, generic views, viewsets). The serializers are just part of how these views process the requests. Serializers are the "means" to connect the View layer with the Model layer. For what serializers do specifically, please read the first paragraph of the this page of the official docs.

To answer #1: you almost always do not need to touch either unless you have a very specific use case. In those extraordinary cases:

  • You override Serializer.create() if you have to customize how model instances are converted into native Python objects and vice versa. (e.g. create multiple objects)
  • You override ViewSet.create() if you need to customize how the actual request itself will be processed. (e.g. if there is an additional query parameter in the request, add some response headers)

To answer #2, you almost never need to use is_valid() when using generic views or ViewSets. They already do it under the hood for you. The serializer's .data and .validated_data are a bit tricky to explain. The former contains the Python datatype representation of the queryset/model instances you want to serialize, while the latter is the result of the validation process involved in checking if a Python object conforms to that particular Python datatype representation mentioned earlier, which in turn can be converted into a model instance. If that did not make sense, refer to Serializing objects and Deserializing objects.

As for #3, what do you mean by JSON field? As far as I know, Django does not have a model field called JSONField. Is this from a third party package or your own custom written model field? If so, then you will probably have to find or write a package that will let you integrate it with DRF smoothly and "correctly" whatever that means.

EDIT
Your use case is too complicated. I can only give you rough code for this one.

class MyModelSerializer(serializers.ModelSerializer):
    my_json_field = JSONSerializerField()

    class Meta:
        model = MyModel

    def validate(self, data):
        # Get JSON blob
        json_blob = data['my_json_field']
        # Implement your own JSON blob cleanup method
        # Return None if invalid
        json_blob = clean_json_blob(json_blob)
        # Raise HTTP 400 with your custom error message if not valid
        if not json_blob:
            raise serializers.ValidationError({'error': 'Your error message'})
        # Reassign if you made changes and return
        data['my_json_field'] = json_blob
        return data

    def create(self, validated_data):
        json_blob = validated_data['my_json_field']
        # Implement your object creation here
        create_all_other_objects_from_json(json_blob)
        # ...
        # Then return a MyModel instance
        return my_model
Wannabe Coder
  • 1,457
  • 12
  • 21
  • Thanks a lot for your thoughtful answer. I have added some code to explain #3 in more detail, as well as providing more context for #1 and #2. As you can see, I need to "touch" quite a lot because of my complex use case, so please lmk if you have any other thoughts. – Cloud Artisans Aug 04 '15 at 15:15
  • I added a very rough sample code. I hope this will help! – Wannabe Coder Aug 04 '15 at 16:09