# Topic covered
* Serialization
* Serializer
* ModelSerializer
* Regular Django views
  * List View, Detail View
* Customize the serialization behavior

Serialization

Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types.

Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

# Why Use Serializers?
Convert Django model objects → JSON (for APIs)
Convert JSON data → Django model objects (for saving to DB)
Validate incoming API data
Customize data representation
  • Files
# Create
snippets/serializers.py

# Update
snippets/views.py
snippets/urls.py

3.1 Serializer

Gives you a powerful, generic way to control the output of your responses

View CODE here

# Create: serializer.py

class SnippetsSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=10)
    # Add all the fieds that you want to Display/Serialize

    ...
    def create(self, validated_data):
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        ...
        instance.title = validated_data.get('title', instance.title)
        instance.save()
        return instance

For GET request - adding only serializer fields is sufficient

For POST, PUT request - instance should be saved, So need to implement both .create() and .update() methods.

Serializer fields

Serializer fields handle converting between primitive values and internal datatypes. They also deal with validating input values, as well as retrieving and setting the values from their parent objects.

Field-level validation: https://www.django-rest-framework.org/api-guide/serializers/#field-level-validation

Object-level validation: https://www.django-rest-framework.org/api-guide/serializers/#object-level-validation

Serializing objects

https://github.com/amrit94/drf-guide/blob/01-Serializers/snippets/views.py

# 1. Translated the model instance into Python native datatypes
serializer = SnippetSerializer(Snippet.objects.all())
serializer.data
# Output: {'language': 'python', 'style': 'friendly'}

# 2. Then render the data into json
content = JSONRenderer().render(serializer.data)
content
# b'{"language":"python","style":"friendly"}'

Deserializing objects

https://github.com/amrit94/drf-guide/blob/01-Serializers/snippets/views.py

data = {"title": "Lang10", "language": "python", "style": "friendly"} """
serializer = SnippetSerializer(snippet, data=data) 

serializer.is_valid()
serializer.validated_data
serializer.save()

3.2 ModelSerializer

It provides shortcut for creating serializers.

The ModelSerializer class is the same as a regular Serializer class, except that:

  • It will automatically generate a set of fields for you, based on the model.
  • It will automatically generate validators for the serializer, such as unique_together validators.
  • It includes simple default implementations of .create() and .update().

SnippetSerializer-class is replicating a lot of information that’s also contained in the Snippet-model. It would be nice if we could keep our code a bit more concise

View CODE here

ModelSerializer class

# SnippetSerializer-class  --> ModelSerializer class
class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        # Add only those fields which you want to display
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

To add all fields fields = '__all__'

Additional keyword arguments

class CreateUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['email', 'username', 'password']
        read_only_fields = ['username']
        extra_kwargs = {'password': {'write_only': True}}

3.3 Writing regular Django views using our Serializer

@csrf_exempt

  • This decorator marks a view as being exempt from the protection ensured by the middleware
  • Normally should not do this –> for POST, PUT and DELETE operations

3.3.1 List View

This view will list all the existing snippets, and also create a new snippet

# views.py

@csrf_exempt
def snippet_list(request):    
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)
	
    # many=True - is used to serialize a queryset or list of objects 
    # instead of a single object instance 
    
    # By defaults `safe=True` --> to serialized Dict obj
    # safe=False --> to serialized Non-Dict obj
    
    # JsonResponse --> serialized to JSON

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)  
        return JsonResponse(serializer.errors, status=400)

	# JSONParser().parse(request) - First  parse a stream into Python native datatypes
	
	# Then we restore those native datatypes into a fully populated object instance.

3.3.2 Detail View

This view handles individual snippet, and can be used to retrieve, update or delete the snippet.

@csrf_exempt
def snippet_detail(request, pk):    
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return JsonResponse({"error": "question not found "}, status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    # many=True not required - Coz of single instance of snippet

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=200)
        return JsonResponse(serializer.errors, status=400)
        
    # SnippetSerializer(snippet, data=data) - snippet = old fields value
    # data = updated fields value

    elif request.method == 'DELETE':
        snippet.delete()
        return JsonResponse({"Success": "question deleted "}, status=204)

urls.py

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>/', views.snippet_detail),
]

customize the serialization behavior

https://testdriven.io/blog/drf-serializers/

Two of the most useful functions inside the BaseSerializer class that we can override are to_representation() and to_internal_value().

By overriding them, we can change the serialization and deserialization behavior, respectively, to append additional data, extract data, and handle relationships.

  • to_representation() allows us to change the serialization output
  • to_internal_value() allows us to change the deserialization output

NOTE

  • ‘rest_framework’, not added in installed app
    • Works with POSTMAN
    • Error in browser: rest_framework/api.html
      • For this need to add