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."
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 (PUTandPATCH).DestroyModelMixin: Provides the.destroy()method (DELETE).

The DRF documentation provides a great reference for these components.
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.
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:
- A request is made to a URL like
/api/posts/5/. - Django's URL resolver matches the pattern and captures the value
5as thepkkeyword argument. - The request is routed to your
RetrieveUpdateDestroyAPIView. - The view uses the
pkvalue (5) and thelookup_field('pk') to filter thequeryset. It effectively performsPost.objects.get(pk=5). - Once the object is retrieved, the appropriate action (
retrieve,update, ordestroy) 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.
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:
RetrieveUpdateDestroyAPIViewis the generic view for handling read-write-delete operations on a single model instance.- It handles
GET(retrieve),PUT/PATCH(update), andDELETE(destroy) requests. - It relies on a URL pattern that captures a unique identifier (like a primary key or UUID).
- The
lookup_fieldattribute 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.