4views
4views
In the Terminal
Clone the repo. Your command should look something like this:
We’ll work out way towards them, but we’re going to start at a lower level.
DRF also provides a decorator to mark view functions as API methods. It
also gives us HTTP response classes that have more options for rendering
our serialized data.
@api_view(["GET", "POST"])
def post_list(request):
...
The last change we can make to utilize the api_view decorator is to start
using rest_framework.response.Response objects instead of JsonResponse.
For example
return Response(PostSerializer(post).data)
or
return Response(status=HTTPStatus.NO_CONTENT)
We can use this to replace all the different types of Django response classes
we’ve been using. However this means we should also replace the use of
the get_object_or_404 shortcut and return a Response with status code 404
instead:
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return Response(status=HTTPStatus.NOT_FOUND)
Try it out
We can make all these changes to our Blango API methods. Since this is
going to be temporary, we won’t go through the changes in much detail.
Instead, you can simply replace the entire content of api_views.py with
this:
from http import HTTPStatus
@api_view(["GET", "POST"])
def post_list(request):
if request.method == "GET":
posts = Post.objects.all()
return Response({"data": PostSerializer(posts,
many=True).data})
elif request.method == "POST":
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
post = serializer.save()
return Response(
status=HTTPStatus.CREATED,
headers={"Location": reverse("api_post_detail",
args=(post.pk,))},
)
return Response(serializer.errors,
status=HTTPStatus.BAD_REQUEST)
if request.method == "GET":
return Response(PostSerializer(post).data)
elif request.method == "PUT":
serializer = PostSerializer(post, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(status=HTTPStatus.NO_CONTENT)
return Response(serializer.errors,
status=HTTPStatus.BAD_REQUEST)
elif request.method == "DELETE":
post.delete()
return Response(status=HTTPStatus.NO_CONTENT)
Note that we’ve changed serializer.is_valid() to not raise an exception
on failure. Instead, if it returns false, we’ll return a Response containing an
error dictionary.
Status Module
Now let’s try out our API. Most of it hasn’t changed. If you try to POST or PUT
a Post object with errors, you’ll get a detailed error response back. For
example, if we remove the summary field:
View Blog
{
"summary": [
"This field is required."
]
}
{
"detail": "Method \"PUT\" not allowed."
}
The biggest change though, is if you load up the API in a web browser.
Django Rest Framework provides its own browser-based GUI to let you
work with the API. Here’s what the Post detail view looks like, for example.
post detail
json put
The DRF web GUI is able to understand the api_view decorator and only
show the form because the view allows PUT requests. If you load the Post
list URL, you’ll notice there’s no DELETE button at the top because it’s not
supported.
We’ll now look at a small shortcut that DRF provides to make it easier to
request different content.
Optional Format Suffixes
urlpatterns = [
path("posts/", post_list, name="api_post_list"),
path("posts/<int:pk>", post_detail, name="api_post_detail"),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Notice that the trailing / on the detail URL has been removed – otherwise
we’d end up having URLs like /api/v1/posts/5/.json.
Then we need to add an extra argument to each of our views: format. This
can default to None. We don’t have to actually do anything for it, the
api_view decorator takes care of it, but it must be present.
Open api_views.py
and
def post_detail(request, pk, format=None):
Try it out
Make the above change to your own api_urls.py (add the import, wrap
urlpatterns and remove the trailing / on the second URL pattern).
urlpatterns = [
path("posts/", post_list, name="api_post_list"),
path("posts/<int:pk>", post_detail, name="api_post_detail"),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Now you can try it out in a browser. If you visit, say, /api/v1/posts/2 you’ll
get the DRF web GUI. But, if you visit /api/v1/posts/2.json, you’ll get a
JSON response. Note, Codio’s browser does not format JSON like the
screenshot. However, the structure should be the same.
View Blog
json in browser
Next up we’ll look at how we can further simplify the code with class-based
views.
APIView
APIView
The rest_framework.views.APIView class is a base class you can inherit
from to define API views as class-based views. Using it doesn’t offer many
advantages over the function views we’ve already seen, but you might like
to use it if you prefer class-based views. We’ll see where class-based views
have genuine advantages in the next sub-section.
For now, we won’t dive deeply into the APIView, instead we’ll just show how
to use it to replace our Blango Post detail view. You don’t have to try this
out, as we’ll be moving on to generic views soon.
class PostDetail(APIView):
@staticmethod
def get_post(pk):
return get_object_or_404(Post, pk=pk)
We still need to define get() and post() methods to respond the GET and
POST HTTP requests, respectively. However, we’ve reduced the amount of
code in the method bodies to a single line.
class PostDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
queryset = Post.objects.all()
serializer_class = PostSerializer
You can see how this reduces the amount of code we need to write
significantly, thanks to the fact that REST follows a pattern that DRF
provides.
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
That’s it. The get() method that calls list() is defined on the
ListCreateAPIView itself, so we don’t even need to define that.
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
More information
More information on Generic Views is available at the official
documentation page.
Try it out
We’ll also consolidate all our API related code into the api directory inside
the blog app. Create a new file called views.py inside this directory, and
implement the class-based views inside. When you’re done the content of
this file should be like this:
Open api/views.py
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
Next, create urls.py also inside the api directory. You can put this content
inside:
Open api/urls.py
urlpatterns = [
path("posts/", PostList.as_view(), name="api_post_list"),
path("posts/<int:pk>", PostDetail.as_view(),
name="api_post_detail"),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Open the main urls.py file (inside the blango directory) and change the
api/v1/ rule to include your new urls.py file instead:
Open urls.py
path("api/v1/", include("blog.api.urls"))
Finally, we can delete the faithful files we’ve been using before:
blog/api_views.py and blog/api_urls.py.
You can now test out the new implementation of the API. You should notice
that it mostly behaves as our previous implementation. One difference is
that when we POST a new Post, a full Post object is returned, instead of a
201 response with a Location header. As we mentioned in the REST
introduction, you could choose to implement object creation in either way,
and DRF chooses to return the full object. We can then look at the id of the
object to determine its URL. Both responses are valid.
View Blog
If you try hitting the API endpoints in a browser, you should notice you’ll
also be able to POST data using a form that DRF generates, instead of having
to write out JSON. Because we’re using these generic views, DRF is able to
more closely relate the view and model (via the serializer) and generate a
form for you.
That’s all we’re going to cover for Django Rest Framework views. In the
next module, we’ll look at improving our API with authentication.
Pushing to GitHub
Pushing to GitHub
Before continuing, you must push your work to GitHub. In the terminal:
git add .
git commit -m "Finish DRF views"
Push to GitHub:
git push