Writing Clean and Reusable Django APIs: Mastering Mixins


Today, I'd like to share a neat little trick I discovered to simplify and clean up my Django REST Framework (DRF) views by leveraging mixins. If you're like me, and you hate seeing repetitive code everywhere, you're in the right place!
The Problem
When building APIs, especially CRUD operations (Create, Read, Update, Delete), it's easy to repeat yourself. You often end up writing very similar views for each model, creating a mess that's hard to maintain. My code was starting to look repetitive, bloated, and just not very clean.
The Solution: Mixins
Mixins in DRF let you separate your code into small, reusable pieces. By combining DRF's built-in mixins (ListModelMixin
, CreateModelMixin
, RetrieveModelMixin
, etc.) and generic views (GenericAPIView
), you can write super clean and concise code.
But I wanted to push it further—so I built customized, reusable view functions.
My Approach: Customized CRUD Functions
I created a single file called mixins.py
inside my Django app to centralize all CRUD actions. Here's the simplified concept:
Customized Create View:
def create_customized(model, serializer):
"""
This function get a model and a serializer class and return the objet created
"""
class CustomCreateView(CreateModelMixin, GenericAPIView):
"""
Create something
"""
queryset = model.objects.all()
serializer_class = serializer
def post(self, request, *args,**kwargs):
"""
post something
"""
response = self.create(request, *args, **kwargs)
if response.status_code == status.HTTP_201_CREATED:
return Response({
"message": f"{CREATED}"
}, status=status.HTTP_201_CREATED)
return response
return CustomCreateView
Customized List View:
def list_customized(model, serializer):
"""
This function get a model and a serializer class and return list of objet
"""
class ListCustomView(ListModelMixin, GenericAPIView):
"""
List something
"""
queryset = model.objects.all()
serializer_class = serializer
def get(self, request, *args, **kwargs):
"""
get all things
"""
return self.list(request, *args, **kwargs)
return ListCustomView
Customized Detail, Update, and Delete View:
For detail, update, and delete, I combined multiple mixins into one:
def detail_update_delete_customized(model, serializer):
"""
This function get a model and a serializer class and return detail, update and delete object
"""
class DetailCustomView(RetrieveModelMixin, GenericAPIView):
"""
Detail of somthing
"""
queryset = model.objects.all()
serializer_class = serializer
def get(self, request, *args, **kwargs):
"""
Get one thing
"""
if kwargs.get('pk'):
return self.retrieve(request, *args, **kwargs)
class UpdateCustomView(UpdateModelMixin, GenericAPIView):
"""
Update something
"""
queryset = model.objects.all()
serializer_class = serializer
def put(self, request, *args, **kwargs):
"""
update all files of something's database
"""
response = self.update(request, *args, **kwargs)
if response.status_code == status.HTTP_200_OK:
return Response({
"message":f"{MODIFIED}"
}, status=status.HTTP_200_OK)
return response
def patch(self, request, *args, **kwargs):
"""
Update just part of something's database
"""
response = self.partial_update(request, *args, **kwargs)
if response.status_code == status.HTTP_200_OK:
return Response({
"message": f"{MODIFIED}"
}, status=status.HTTP_200_OK)
return response
class DeleteCustomView(DestroyModelMixin, GenericAPIView):
"""
Delete one thing
"""
queryset = model.objects.all()
serializer_class = serializer
def delete(self, request, *args,**kwargs):
"""
Delete a store in database
"""
response = self.destroy(request, *args, **kwargs)
if response.status_code == status.HTTP_204_NO_CONTENT:
return Response({
"message": f"{DELETED}"
}, status=status.HTTP_204_NO_CONTENT)
return response
class CombineActionView(UpdateCustomView,
DetailCustomView,
DeleteCustomView):
"""
all action
"""
return CombineActionView
The Result
Now, every time I need CRUD operations for a new model, it's just a matter of calling these functions and passing the model and serializer as parameters. No more repetitive code!
urlpatterns = [
#endpoints for store
path("boutique/new/",
create_customized(Boutique, BoutiqueSerializer).as_view(),
name="creation-boutique"),
...
]
Benefits
Cleaner code: Easier to read and maintain.
Reusable logic: Write once, use everywhere.
Faster development: Spend less time copying and pasting.
Conclusion
Mixins are powerful allies when you want to keep your DRF code clean, readable, and reusable. Try them out, and let me know how it goes!
What's your favorite tip to write cleaner Django code? Share in the comments!
Subscribe to my newsletter
Read articles from Meda Mondonga directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
