Hello! In our last lesson, we saw how DefaultRouter can inspect a ModelViewSet and automatically generate a full set of URL patterns for standard CRUD operations. This is a massive time-saver, but real-world applications often need more than just Create, Retrieve, Update, and Delete.
What if you need an endpoint to "publish" a draft article, "complete" a to-do item, or retrieve a list of "archived" orders? These actions don't map cleanly to the standard HTTP verbs and RESTful nouns. This is where custom actions come in.
Today's lesson is focused on a powerful feature that gives you this flexibility: adding custom actions to ViewSets using the @action decorator. You'll learn how to define these custom endpoints and see how DRF's routing system automatically wires them up, just like it does for the standard actions.
Extending ViewSets with @action
The @action decorator is your tool for marking any method inside a ViewSet as a routable endpoint. Since you're proficient with Python decorators, you can think of @action as a way to "register" a regular class method with the DRF router, giving it special properties that turn it into an API endpoint.
To get a foundational understanding, let's start with the official Django REST Framework documentation.
The DRF documentation provides the definitive explanation for how to mark extra actions for routing. It clearly lays out the main arguments and provides a complete code example.
Please read the section titled Marking extra actions for routing. Focus on understanding the role of the @action decorator and, most importantly, the difference between setting the detail argument to True versus False.
From that reading, there are two critical concepts to grasp:
-
List vs. Detail Actions: The
detailargument in the@actiondecorator determines the scope of your custom action.@action(detail=False): This creates a list route. The action operates on the entire collection of resources. Its URL will be at the base of the resource, like/books/favorite_books/. The method signature will bedef my_action(self, request):.@action(detail=True): This creates a detail route. The action operates on a single specific instance of a resource. Its URL will contain a primary key (pk), like/books/42/mark_as_favorite/. The method signature must accept the primary key:def my_action(self, request, pk=None):.
-
HTTP Methods: By default, an action only responds to
GETrequests. You can specify which HTTP methods it should respond to using themethodsargument, for example,methods=['post']. This is crucial for actions that change data.
Implementing a Custom Action: A Practical Walkthrough
Let's see this in action. The following video provides a clear, step-by-step implementation of a custom list action (detail=False) to fetch all orders belonging to the currently logged-in user.
Viewset Actions, Filtering and Permissions in Django REST Framework
Watch this segment to see how a custom action is built from scratch within a ModelViewSet. Pay close attention to how the queryset is filtered and the data is serialized.
Please watch from 08:43 to 12:03. This part introduces the concept and then implements a user_orders action. Notice how methods like self.get_queryset() and self.get_serializer() are used inside the custom action.
Let's break down the key parts of the user_orders action shown in the video:
from rest_framework.decorators import action
from rest_framework.response import Response
class OrderViewSet(viewsets.ModelViewSet):
# ... queryset and serializer_class are defined here ...
@action(detail=False, methods=['get'])
def user_orders(self, request):
# 1. Use the ViewSet's default queryset as a starting point.
orders = self.get_queryset().filter(user=request.user)
# 2. Use the ViewSet's default serializer to prepare the data.
serializer = self.get_serializer(orders, many=True)
# 3. Return the response.
return Response(serializer.data)
This pattern is very powerful. You're leveraging the existing configuration of your ViewSet (queryset, serializer_class) to build new functionality with minimal code.
Now let's consider a detail action (detail=True). A common use case is performing an action on a specific object, like marking a book as a favorite.
How to Use @action Decorator in Django Rest Framework
This article provides another excellent, practical example that demonstrates both a detail action (mark_as_favorite) and a list action (favorite_books). It's a great supplement to the video.
Read the section titled Practical Example of @action. Focus on the mark_as_favorite function. Notice how it uses self.get_object() to retrieve the specific instance and how the decorator is configured for a POST request on a detail URL.
The mark_as_favorite action highlights the pattern for detail routes:
@action(detail=True, methods=['post'])
def mark_as_favorite(self, request, pk=None):
# 1. Get the specific object instance using the pk from the URL.
book = self.get_object()
# 2. Perform the business logic.
book.favorite = True
book.save()
# 3. Return a confirmation response.
return Response({"message": f"Book '{book.title}' marked as favorite."})
The key method here is self.get_object(), which DRF provides to fetch the instance corresponding to the pk in the URL.
When implemented, these actions appear directly in the browsable API, making them easy to discover and test.

Test your understanding!
Imagine you have a BlogPostViewSet for managing blog posts. A post can have a status field, which can be 'draft' or 'published'.
You need to add two custom actions:
- An action to publish a single, specific draft post. This should be triggered by a
POSTrequest. - An action to retrieve a list of all posts that are currently drafts. This should be triggered by a
GETrequest.
For each action, what would the @action(...) decorator look like, and what would the method signature be?
Show answer
-
Publishing a single post:
This is an action on a specific instance, sodetail=True. It changes data, so it should be aPOSTrequest. The URL will need a primary key.- Decorator:
@action(detail=True, methods=['post']) - Signature:
def publish(self, request, pk=None):
- Decorator:
-
Retrieving all drafts:
This is an action on the entire collection, sodetail=False. It retrieves data, so it should be aGETrequest. The URL will be on the collection's base endpoint.- Decorator:
@action(detail=False, methods=['get']) - Signature:
def drafts(self, request):
- Decorator:
Customizing URL Paths and Permissions
The @action decorator is more versatile than just detail and methods. Two other particularly useful arguments are url_path and permission_classes.
-
url_path: By default, the URL for your action is derived from its method name (e.g.,user_ordersbecomes/user-orders/). You can override this withurl_path. For example,@action(detail=False, url_path='my-orders')would create the endpoint/orders/my-orders/. -
permission_classes: You can specify permissions that apply only to this action, overriding the default permissions set on theViewSet. This is extremely useful for fine-grained access control.
The video you watched earlier briefly touches on both of these.
Viewset Actions, Filtering and Permissions in Django REST Framework
Let's revisit the video to see how these advanced customizations are applied.
Watch the segment from 11:55 to 12:34 to see url_path in use. Then, watch from 13:27 to 14:43 to see how permission_classes=[IsAuthenticated] can be used to protect a single action.
This ability to attach specific permissions to an action is a powerful concept that provides a glimpse into our next topic.
Conclusion
You now have the tools to extend your ViewSets beyond standard CRUD, enabling you to build APIs that more closely match your application's specific business logic.
Key Takeaways:
- The
@actiondecorator turns aViewSetmethod into a custom API endpoint. @action(detail=True)creates an endpoint for a single object instance (e.g.,/posts/1/publish/). You useself.get_object()to get the instance.@action(detail=False)creates an endpoint for the entire collection (e.g.,/posts/drafts/). You typically filterself.get_queryset().- The
methods,url_path, andpermission_classesarguments give you fine-grained control over the action's behavior. - DRF's routers automatically discover and generate URLs for methods decorated with
@action.
Preview of the Next Module:
In this lesson, we briefly used permission_classes to lock down a custom action. In the next module, DRF: Authentication and Permissions, we will fully explore this topic. We'll start by installing Django REST Framework itself and then dive into how to manage API access by identifying users (Authentication) and controlling what they are allowed to do (Permissions). This is a critical step in building secure and professional-grade APIs.