Importance notes for using Django Rest Framework

1. Serializers


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        ordering = ('created',)

- serializers.Serializer

class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

- serializers.ModelSerializer

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')


+ An automatically determined set of fields.
+ Simple default implementations for the create() and update() methods.


2. View (Request objects, Response objects, Status codes, Wrapping API views)


2.1 Normal views       


@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

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

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


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

urlpatterns = format_suffix_patterns(urlpatterns)


2.2 Class-based views     


class SnippetList(APIView):
    """
    List all snippets, or create a new snippet.
    """
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class SnippetDetail(APIView):
    """
    Retrieve, update or delete a snippet instance.
    """
    def get_object(self, pk):
        try:
            return Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        snippet = self.get_object(pk)
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


urlpatterns = [
    path('snippets/', views.SnippetList.as_view()),
    path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)


2.3 Generic Class-based views     


####################################################################
class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

####################################################################
class SnippetList(generics.ListAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

class SnippetList(generics.CreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

class SnippetUpdate(generics.RetrieveUpdateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

class SnippetDelete(generics.DestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


3. Permissions


3.1 Default class (AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly)


permission_classes = (permissions.IsAuthenticatedOrReadOnly)

3.1 Custom class



class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user


permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

4. Authenticate

5. Pagination



    pagination_class = PostPageNumberPagination #PageNumberPagination


6. Browsing the API with swagger



7. Blog Api review


7.1 Serializer



- Only available when write(POST,PUT) not response for showing(GET)
    extra_kwargs = {"password":{"write_only": True}}

- Validate all
    def validate(self, data):

- Validate a field
    def validate_email(self, value):


- Should create 3 separated Serializer for CRUD

    PostCreateUpdateSerializer


    PostDetailSerializer


    PostListSerializer


- Should add hyperlink detail to list and to detail
    post_detail_url = HyperlinkedIdentityField(
        view_name='posts-api:detail',
        lookup_field='slug'
        )


class PostListSerializer(ModelSerializer):
    url = post_detail_url

class PostDetailSerializer(ModelSerializer):
    url = post_detail_url



- Use SerializerMethodField to define a custom field return a single data or another serializer

    def get_image(self, obj):
        try:
            image = obj.image.url
        except:
            image = None
        return image


    def get_comments(self, obj):
        c_qs = Comment.objects.filter_by_instance(obj)
        comments = CommentSerializer(c_qs, many=True).data
        return comments

- Use a Serializer in a Serializer for showing relationship (nested serializer)

user = UserDetailSerializer(read_only=True)

- Custom create and update function

def create(self, validated_data):

def update(self, instance, validated_data):


7.2 Views


- Should use these generic based views below for CRUD

class PostCreateAPIView(CreateAPIView):

class PostDetailAPIView(RetrieveAPIView):

class PostUpdateAPIView(RetrieveUpdateAPIView):

class PostDeleteAPIView(DestroyAPIView):

class PostListAPIView(ListAPIView):

- Should use DjangoFilterBackend for filter list view

    filter_backends = (DjangoFilterBackend,)
    filterset_fields = ('category', 'in_stock')

- Should use SearchFilter for searching on list view

filter_backends = (filters.SearchFilter,)
    search_fields = ('username', 'email')

- Should use OrderingFilter for ordering on list view

filter_backends = (filters.OrderingFilter,)
    ordering_fields = ('username', 'email')


- Use perform_create on CreateAPIView or perform_update on RetrieveUpdateAPIView to save relation

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

    def perform_update(self, serializer):
        serializer.save(user=self.request.user)   

- Should use (Because supported: validate method, authenticate, permission, panination ... ) 
 + APIView for complex cases
 + CreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, DestroyAPIView, ListAPIView for normal cases

- Rules for REST

+ List(GET): status code 200 and body response
+ Detail(GET): status code 200 and body response, 404
+ Create(POST): status code 201 and response created data, 400 and errors
+ Edit(PUT): status code 200 and response updated data, 400 and errors
+ Delete(DELETE) status code 204 and no content











Comments