Skip to content

Instantly share code, notes, and snippets.

@dohsimpson
Created October 29, 2018 15:13
Show Gist options
  • Save dohsimpson/e8a185496bccca262d7e955e6bbfb8b5 to your computer and use it in GitHub Desktop.
Save dohsimpson/e8a185496bccca262d7e955e6bbfb8b5 to your computer and use it in GitHub Desktop.
Django-REST-Framework
Serializers
* Notice that we're using hyperlinked relations in this case, with HyperlinkedModelSerializer. You can also use primary key and various other relationships, but hyperlinking is good RESTful design
* HyperlinkedModelSerializer:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
* ModelSerializer:
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
* Reverse:
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
Views:
* ModelViewSet:
* class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
* Plain View: (Not Recommended with csrf_exempt)
* @csrf_exempt
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 JsonResponse(serializer.data, safe=False)
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)
* Funciton-based APIView:
@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)
* Class-based APIView:
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 Mixins:
class SnippetList(mixins.ListModelMixin, -> provide list() implementation
mixins.CreateModelMixin, -> provide create() implementation
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
* 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
* Methods:
get_queryset(self): Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the queryset attribute.
* Format Suffix
View: def snippet_list(request, format=None):
Url: urlpatterns = format_suffix_patterns(urlpatterns)
* Request
request.POST # Only handles form data. Only works for 'POST' method.
request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods.
* Response
return Response(data) # Renders to content type as requested by the client.
* Status Codes
status.HTTP_400_BAD_REQUEST
*
Settings:
* Pagination:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
* Installation:
INSTALLED_APPS = (
...
'rest_framework',
)
URL:
* router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Client:
* BASH:
* GET:
curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
http -a admin:password123 http://127.0.0.1:8000/users/
* Request Header:
* Request Accept format:
Accept: text/html # sent by web browser
Accept: application/json
* Request/Response Data Format:
Content-Type: multipart/form-data
Content-Disposition: form-data; name="description"
Content-Type: application/json OR application/javascript
* suffix:
http http://127.0.0.1:8000/snippets.json # JSON suffix
http http://127.0.0.1:8000/snippets.api # Browsable API suffix
* Browsable API:
* http://127.0.0.1:8000/users/
Authentication:
* Browsable API:
LogIn and LogOut View:
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
# The 'api-auth/' part of pattern can actually be whatever URL you want to use.
* View:
* set USER_FIELD to self.request.user:
def perform_create(self, serializer):
serializer.save(USER_FIELD=self.request.user)
* Authenticated View:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
* IsAuthenticatedOrReadOnly: ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access
*
Ceveats:
* Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
* These wrappers (APIView, @api_view) provide a few bits of functionality such as making sure you receive Request instances in your view, and adding context to Response objects so that content negotiation can be performed. The wrappers also provide behaviour such as returning 405 Method Not Allowed responses when appropriate, and handling any ParseError exception that occurs when accessing request.data with malformed input.
* Because the API chooses the content type of the response based on the client request, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a web browser. This allows for the API to return a fully web-browsable HTML representation.
* We can also write our API views using class-based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code DRY. One of the big wins of using class-based views is that it allows us to easily compose reusable bits of behaviour (with Mixins).
* auto-incremented fields and fields with default or fields seted with custom save() do not need to be supplied with POST.
* Because 'snippets' is a reverse relationship on the User model, it will not be included by default when using the ModelSerializer class, so we needed to add an explicit field for it.
* We'll also add a couple of views to views.py. We'd like to just use read-only views for the user representations, so we'll use the ListAPIView and RetrieveAPIView generic class-based views
* Now that snippets are associated with the user that created them, let's update our SnippetSerializer to reflect that. Add the following field to the serializer definition in serializers.py: owner = serializers.ReadOnlyField(source='owner.username'). Note: Make sure you also add 'owner', to the list of fields in the inner Meta class.
* The field we've added is the untyped ReadOnlyField class, in contrast to the other typed fields, such as CharField, BooleanField etc... The untyped ReadOnlyField is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. We could have also used CharField(read_only=True) here
* This class (GenericAPIView) extends REST framework's APIView class, adding commonly required behavior for standard list and detail views.
* Each of the concrete generic views provided is built by combining GenericAPIView, with one or more mixin classes.
* The generic views provided by REST framework allow you to quickly build API views that map closely to your database models. If the generic views don't suit the needs of your API, you can drop down to using the regular APIView class, or reuse the mixins and base classes used by the generic views to compose your own set of reusable generic views.
* This method (get_queryset) should always be used rather than accessing self.queryset directly, as self.queryset gets evaluated only once, and those results are cached for all subsequent requests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment