Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 44

Building a `RetrieveUpdateDestroyAPIView`

Recommended resources

Hello! Welcome to the next lesson on building APIs with Django REST Framework (DRF).

In our last session, you successfully implemented a ListCreateAPIView, which provided a single endpoint (/api/resource/) to handle listing a collection of objects and creating new ones. This is a fundamental pattern for managing resource collections.

Today, we'll complete the full set of CRUD (Create, Retrieve, Update, Delete) operations. We will focus on the other side of the coin: interacting with a single, specific instance of a resource (e.g., /api/resource/1/). Your learning goal for this lesson is to implement a detail/update/delete endpoint using RetrieveUpdateDestroyAPIView. This powerful generic view will allow us to retrieve, update, and delete an object from a single, concise class.

The Path to Conciseness: From APIView to Generic Views

Just as with ListCreateAPIView, we could build this functionality manually using the base APIView. Doing so helps to appreciate the abstraction that generic views provide, which aligns with your interest in understanding the framework's internal mechanisms.

Let's look at what it takes to build a detail view the "long way."

3 - Class based views

The official DRF tutorial provides an excellent example of building a detail view using the base APIView. This will serve as our 'before' picture.

Please read the section under the heading 'Rewriting our API using class-based views'. Focus on the SnippetDetail(APIView) class. Notice that it requires separate methods for get(), put(), and delete(), as well as a helper method get_object() to fetch the specific instance from the database. This is the boilerplate we are about to eliminate.

As you can see, each HTTP method (GET, PUT, DELETE) requires its own explicit implementation, even though the core logic (find object, serialize, act) is very repetitive.


Understanding RetrieveUpdateDestroyAPIView

RetrieveUpdateDestroyAPIView is a concrete generic view designed specifically for this purpose. It combines the functionality for all read-write-delete operations on a single model instance.

It's constructed by inheriting from GenericAPIView and three distinct "mixin" classes:

  • RetrieveModelMixin: Provides the .retrieve() method, which handles fetching a single object (GET).
  • UpdateModelMixin: Provides the .update() and .partial_update() methods (PUT and PATCH).
  • DestroyModelMixin: Provides the .destroy() method (DELETE).
Django REST Framework Generic Views and Mixins Hierarchy
This flowchart illustrates how `RetrieveUpdateDestroyAPIView` is composed from the base `GenericAPIView` and the `Retrieve`, `Update`, and `Destroy` mixins, each providing a piece of the final functionality.

The DRF documentation provides a great reference for these components.

Generic views

The 'Generic views' documentation page provides a concise summary of all the concrete views and the mixins they are built from. This will confirm the structure we just discussed.

Skim the 'Concrete View Classes' section until you find RetrieveUpdateDestroyAPIView. Note its stated purpose ('read-write-delete endpoints') and the list of mixins it extends. This reinforces how DRF composes complex functionality from smaller, reusable parts—a core concept you're familiar with from your programming background.


Implementing the Detail View

Now that you understand the theory, let's put it into practice. The implementation is strikingly similar to ListCreateAPIView, but with two key differences: the class we inherit from and the URL pattern we map it to.

First, let's see the "after" picture from the DRF tutorial.

3 - Class based views

The DRF tutorial refactors the verbose APIView into the final, concise generic view. The difference is significant.

Read the code under the 'Using generic class-based views' heading. Look at the SnippetDetail class. All the get, put, and delete methods have been replaced by just two attributes: queryset and serializer_class.

This is incredibly concise. But how does the view know which object to retrieve, update, or delete? The answer lies in the URL.

Let's watch a video that walks through the complete process of creating the view and wiring up its URL.

How To Make a Django REST API - Full Tutorial & Deployment

This tutorial from Tech With Tim provides a clear, practical demonstration of creating and using a RetrieveUpdateDestroyAPIView.

Please watch the following segments: 00:18:25 - 00:19:28: The presenter defines the view. Pay attention to the three class attributes: queryset, serializer_class, and lookup_field. lookup_field = 'pk' tells DRF that the value captured from the URL corresponds to the primary key of the model. 00:19:28 - 00:20:21: The presenter creates the URL pattern in urls.py. The crucial part is path('blog-post/<int:pk>/', ...) which captures an integer from the URL and names it pk. 00:20:21 - 00:21:12: The presenter demonstrates interacting with the endpoint in the browsable API, showing how a GET request retrieves the object, a PUT request updates it, and a DELETE request removes it.

To summarize the process:

  1. A request is made to a URL like /api/posts/5/.
  2. Django's URL resolver matches the pattern and captures the value 5 as the pk keyword argument.
  3. The request is routed to your RetrieveUpdateDestroyAPIView.
  4. The view uses the pk value (5) and the lookup_field ('pk') to filter the queryset. It effectively performs Post.objects.get(pk=5).
  5. Once the object is retrieved, the appropriate action (retrieve, update, or destroy) is performed.

Customizing Object Lookup with get_object()

In the previous lesson, you learned to customize the queryset for list views by overriding the get_queryset() method. The equivalent for detail views is get_object().

While the default behavior of looking up by pk is very common, you might need more complex logic. For example, you might want to look up an object by a slug field, or a combination of URL parameters. Overriding get_object() gives you full control.

Generic views

The DRF documentation explains the get_object() method and provides a clear example of how to override it.

Navigate to the 'API Reference' section, then find GenericAPIView. Under the 'Methods' subsection, read about get_object(self). The documentation explains its default behavior and provides an example for looking up an object based on multiple URL keyword arguments. This is the primary hook for customizing instance retrieval.

For example, if your model has a non-integer uuid field that you use as a public identifier, you would set lookup_field = 'uuid' in your view and use <uuid:uuid> in your URL pattern. If your logic is more complex than a simple field lookup, get_object() is the tool for the job.

Test your understanding!

You're continuing with the Task API from the last lesson. Your Task model has the standard integer id (primary key) and also a uuid field (e.g., uuid = models.UUIDField(default=uuid.uuid4, editable=False)).

For security and usability reasons, you want the public-facing API to use the UUID for object lookups, not the sequential integer ID. Your detail endpoint URL should look like /api/tasks/<uuid>/.

How would you implement the TaskRetrieveUpdateDestroyView and its corresponding URL pattern in urls.py to achieve this?

Show answer

You would need to set the lookup_field in your view and use the correct path converter in your URL pattern.

1. In views.py:

You specify that the lookup should be performed on the uuid field.

# views.py
from rest_framework import generics
from .models import Task
from .serializers import TaskSerializer

class TaskRetrieveUpdateDestroyView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    lookup_field = 'uuid' # Tell DRF to use the 'uuid' field for the lookup

2. In urls.py:

You use the uuid: path converter to ensure that only valid UUIDs are matched and captured from the URL.

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    # ... other paths
    path('tasks/<uuid:uuid>/', views.TaskRetrieveUpdateDestroyView.as_view(), name='task-detail'),
]

Note how the name of the captured URL parameter (uuid) matches the lookup_field in the view. DRF uses this convention by default.


Conclusion

Congratulations! You have now implemented the full suite of CRUD operations for a resource. By combining ListCreateAPIView for collection management and RetrieveUpdateDestroyAPIView for instance management, you have a complete and conventional RESTful API for your model.

Key Takeaways:

  • RetrieveUpdateDestroyAPIView is the generic view for handling read-write-delete operations on a single model instance.
  • It handles GET (retrieve), PUT/PATCH (update), and DELETE (destroy) requests.
  • It relies on a URL pattern that captures a unique identifier (like a primary key or UUID).
  • The lookup_field attribute tells DRF which model field to use for the lookup based on the value from the URL.
  • For advanced lookup logic, you can override the get_object() method.

Preview of the Next Lesson:
So far, you've created two separate view classes for one Task model: TaskListCreateView and TaskRetrieveUpdateDestroyView. While this is a great improvement, you might notice that the queryset and serializer_class are duplicated. In the next lesson, we'll explore ViewSets and Routers. You will learn how to refactor this logic into a single ViewSet class and have DRF automatically generate all the necessary URL patterns for you, further reducing code and enforcing convention.