Django Rest Framework 1/3

sjp5554·2020년 7월 6일
0

Core functionalities of Django Rest Framework

Serializers

Serializers take the data that exist on the server and serialize it and output into a format that can be read by other technologies. If data is sent to the server, it packages the data up so that the data can be read by the data models on the server and be saved in the database.

Serializing objects : Server -> Client

  1. The model instance is translated into Python native datatypes.
  2. The data is rendered into json format.
serializer = CommentSerializer(comment)
serializer.data
// {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
json = JSONRenderer().render(serializer.data)
json
// b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'

De-serializing objects : Client -> Server

  1. Parse a stream into Python native datatypes.
  2. Restore the native datatypes to into a dictionary of validated data.
import io
from rest_framework.parsers import JSONParser

stream = io.BytesIO(json)
data = JSONParser().parse(stream)

serializer = CommentSerializer(data=data)
serializer.is_valid()
// True
serializer.validated_data
// {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

Two main types of serializers

General Serializer

Long way, more customizable

ModelSerializer

Shorter way, less customizable

Validation

When deserializing data, you always need to call is_valid() before attempting to access the validated data or save an object instance. If any validation errors do occur, the .errors property will contain a dictionary representing the resulting error messages.

How to specify custom validation checks for our models and serializers

Of course, DRF provides built-in validators such as UniqueValidator, UniqueTogetherValidator, UniqueForDateValidator, etc. They can be used by specifying the aforementioned validators in class Meta of serializer classes:

class Meta:
	validators = [UniqueTogetherValidator(queryset=ToDoItem.objects.all(), fields=('list', 'position'))]

However, there will be times when they are not enough, and you have to customize your own validators.

Raising an exception on invalid data

is_valid() method takes an optional raise_exception flag which will cause it to raise a serializers.ValidationError exception if there are validation errors.
Validation erors are automatically dealt with by the default exception handler that DRF provides, which will return HTTP 400 Bad Request responses by default.

serializer.is_valid(raise_exception=True)

Field-level validation

Validation check on a single field. For example, check if specific words are not used in an Article instance's description field. Add .validate_<field_name> methods to your Serializer subclass to specify custom field-level validation. These methods take a signle argument, which is the field value that requires validation. They should return the validated value or raise a ValidationError.

def validate_title(self, value):
        // Check that the blog post is about Django.
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

Object-level validation

Validation check on object level, using multiple fields. For example, checking if an Article instance's title and descriptions of an instance are the same. Add .validate() method to your Serializer subclass to perform a validation that requires access to multiple fields. The argument must be a dictionary of field values.

def validate(self, data):
        // Check that start is before finish.
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

ModelSerializer

ModelSerializer allows us to speed up the creation of Serializers based on models we defined, providing us all the code that is needed for the most common development use cases.
ModelSerializer class is different from a regular Serializer class in 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().

Handling Nested Relationships

By default, ForeignKey relationship is expressed using the primary key of the related object.
To display this relationship in a more explicit way, use serializer relational fields.

class Article(models.Model):
    author = models.ForeignKey(Journalist, on_delete=models.CASCADE, related_name='articles')

class ArticleSerializer(serializers.ModelSerializer):
	// Now the author will be displayed using its __str__() function
    author = serializers.StringRelatedField()

Another way to express nested relationship within serializers is using the hyperlink related fields. This is a field that allows us to get a direct link to a proper endpoint for a specific resource.

class JournalistSerializer(serializers.ModelSerializer):
    articles = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='article-detail')

Serializer relations

Relational fields are used to represent model relatinoships. The relational fields are declared in relations.py, but by convention you should import them from the serializers module, using from rest_framework import serializers and refer to fields as serializers.<FieldName>

  • StringRelatedField
  • PrimaryKeyRelatedField
  • HyperlinekedRelatedField
  • SlugRelatedField

For more information, check out the official documentation on serializer relations.

Where to make nested relationships explicit in serializers

Take, for example, an Article class which is in Foreign Key relationship with a Journalist class (Many To One). In order to handle nested relationship between these two classes, from whose serializer should the other model's serializer be called?

In this case, the call should be made from JournalistSerializer. A rule of thumb is that the caller is likely to be the model serializer of the "One" in a Many-To-One relationship. This is because the "Many" model tends to be dependent on the "One" model, and to handle a POST request within a same class based view, the dependent side should be called from the independent side, not the other way around.

EX)

class JournalistSerializer(serializers.ModelSerializer):
    articles = ArticleSerializer(many=True, read_only=True)

    class Meta:
        model = Journalist
        fields = "__all__"

// Not
class ArticleSerializer(serializers.ModelSerializer):
    author = JournalistSerializer()

Request objects

DRF's Request object extends the regular HttpRequest, providing more flexible request parsing.
request.data attribute can handle arbitrary data, and works for 'POST', 'PUT', and 'PATCH' methods.

It is important to note that DRF request object is different from pure django's request object

Response objects

DRF's Response object is a time of TemplateResponse that takes unrendered content and uses content negotiation to determine the correct content type to return to the client.

From the source code:

class Response(SimpleTemplateResponse):
	// An HttpResponse that allows its data to be rendered into arbitrary media types.

Status codes

DRF provides more explicit identifiers for each status code other than just numeric HTTP status codes.
Ex) HTTP_400_BAD_REQUEST in the status module. Use these rather than using numeric identifiers.

Wrapping API views

  1. The @api_view decorator for working with function based views.
  2. The APIView class for working with class-based views.

These wrappers ensure that you receive Request instances in your view and help add context to Response objects so that content negotiation can be performed.
The wrappers also support returning 405 Method Not Allowed responses when appropriate, and handling any ParseError exception if request.data is malformed.

Basic DRF View

Following is a rudimentary DRF view introduced in django-rest-framework.org.

@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)

Adding optional format suffixes to our URLs

Because with DRF, our responses are no longer hardwired to a single content type, we can add format suffixes to our API endpoints, which will be able to perceive URLs that explicitly refer to a given format.

// views.py
def snippet_detail(request, pk, format=None):
	pass

// urls.py
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

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

urlpatterns = format_suffix_patterns(urlpatterns)

Control the format of the response either by using (1) the Accept header:

http http://127.0.0.1:8000/snippets/ Accept:application/json  # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html         # Request HTML

Or (2) by appending a format suffix:

http http://127.0.0.1:8000/snippets.json  # JSON suffix
http http://127.0.0.1:8000/snippets.api   # Browsable API suffix

You can also control the format of the request that we send, using the Content-Type header.

# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"

Class-based views

class ArticleDetailAPIView(APIView):
    def get_object(self, pk):
        article = get_object_or_404(Article, pk=pk)
        return article
    
    def get(self, request, pk):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)
    
    def put(self, request, pk):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article, 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):
        article = self.get_object(pk)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

0개의 댓글