Django Interview Questions 31-35 (Async, Testing, DRF Basics)
Hello! We've covered a lot of ground in Django, from the basics of MVT and models to more advanced features like security and caching. Now, we're going to bridge the gap between a traditional Django app and a modern, high-performance API.
This lesson covers some vital modern topics. We'll start with Django's new asynchronous capabilities. Then, we'll cover the essentials of testing, a non-negotiable skill for professional developers. Finally, we'll begin our deep dive into the most popular Django add-on: Django REST Framework (DRF), the tool you'll use to build powerful web APIs.
31. Explain Django’s asynchronous support. (Views, ORM limitations)
This is a modern Django feature. Traditionally, Django was purely synchronous. When a request came in, it was handled by one worker thread from start to finish. If that thread had to wait (e.g., for a slow database query or an external API call), it was blocked and couldn't do any other work.
Analogy: The Barista
- Synchronous Django (Old way): You have one barista. When a customer orders a complex latte, the barista makes the espresso, steams the milk, and hands it over. While the espresso is pulling (an I/O wait), the barista just stands there, blocked. The entire line of customers has to wait.
- Asynchronous Django (New way): You have an async barista. When a customer orders, the barista starts the espresso machine (`await pull_espresso()`). Instead of waiting, they immediately move to the next customer and take their order. When the espresso is ready, the machine sends a signal, and the barista picks it up when they have a free moment. They can handle many customers concurrently.
Django now supports async views by running on an ASGI (Asynchronous Server Gateway Interface) server like Uvicorn. This is ideal for tasks that are I/O-bound (like making many external API calls at once).
The `async` View:
import asyncio
from django.http import HttpResponse
# By defining the view with 'async def',
# Django knows to run it in an async context.
async def my_async_view(request):
# This allows other tasks to run
# while we are 'sleeping' (waiting for I/O).
await asyncio.sleep(5)
return HttpResponse("Done after 5 seconds!")
The ORM Limitation (Very Important!)
As of now, the Django ORM is still synchronous. You cannot do `await Product.objects.all()`.
If you need to access the database from inside an `async` view, you must wrap your ORM call in a special function called `sync_to_async`. This tells Django to safely run the synchronous DB query in a separate thread, which unblocks the main async loop.
from asgiref.sync import sync_to_async
from .models import Product
async def get_all_products_async():
# This is how you run a sync ORM call
# from within an async function.
products = await sync_to_async(list)(Product.objects.all())
return products
32. How do you write unit tests in Django?
Django has a powerful built-in testing framework that is an extension of Python's standard `unittest` module.
A unit test is a function that tests one small, isolated "unit" of your code (e.g., "Does my `Product` model correctly calculate its sale price?").
The key features of Django's testing are:
- Test Database: When you run tests, Django automatically creates a brand new, blank test database. This ensures your tests are isolated and don't destroy your real data.
- `TestCase`: You write tests by subclassing `django.test.TestCase`. This class provides helpful tools, like an`self.client` object.
- `self.client`: This is a dummy web browser that lets you "pretend" to be a user. You can use it to `self.client.get('/my-url/')` or `self.client.post(...)` and then check the response.
# my_app/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Product
class ProductTests(TestCase):
# This 'setUp' method runs *before* every test
def setUp(self):
# Create a sample product to test against
Product.objects.create(name="Test Product", price=100)
def test_product_model_str(self):
"""Test the model's __str__ method."""
product = Product.objects.get(name="Test Product")
self.assertEqual(str(product), "Test Product")
def test_product_list_view(self):
"""Test that the product list page loads."""
# 'reverse' finds the URL from its name
url = reverse('product-list')
# Use the test client to make a GET request
response = self.client.get(url)
# Check that the page loaded successfully
self.assertEqual(response.status_code, 200)
# Check that our product's name is in the HTML
self.assertContains(response, "Test Product")
# To run the tests for 'my_app':
# $ python manage.py test my_app
Sample Output:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.042s
OK
Destroying test database for alias 'default'...
33. What is Django REST Framework (DRF)?
Django REST Framework (DRF) is a powerful and flexible toolkit for building Web APIs in Django.
While standard Django is built to return HTML (web pages), DRF is built to return data (like `JSON` or `XML`).
Analogy: The Restaurant, Revisited
- Standard Django: Is a sit-down restaurant. It takes your order (request) and returns a fully-plated meal (an HTML web page) for you to consume.
- DRF: Is a food delivery service. It takes your order (request) and returns the raw, un-plated food in a box (`JSON` data). This allows other applications (like a mobile app, a JavaScript frontend, or another backend) to consume your data and present it however they want.
DRF provides all the "batteries-included" features Django is famous for, but for APIs:
- Serializers (for converting data)
- Authentication (e.g., token, OAuth)
- Permissions (e.g., "Is this user the owner of this post?")
- Browsable API (a user-friendly website for testing your API)
34. What are serializers in DRF? Types of serializers.
A Serializer is the translator in DRF. Its job is to convert complex data types, like Django model instances, into native Python datatypes (like dictionaries) that can then be easily rendered into `JSON`.
It also does the reverse: deserialization. It takes incoming `JSON` data, validates it, and converts it back into a Python object or model instance.
Types of Serializers
The two main types are just like Django's forms:
- `Serializer`: A generic class where you must manually define every field. Good for data that doesn't map to a model.
- `ModelSerializer`: This is the magic. It automatically generates fields, validators, and `.save()` methods for you just by looking at your linked `Model`. You use this 95% of the time.
# my_app/serializers.py
from rest_framework import serializers
from .models import Product
# This ModelSerializer will:
# 1. Automatically create 'name' and 'price' fields
# 2. Automatically validate them (e.g., is 'price' a decimal?)
# 3. Automatically provide .create() and .update() methods
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'name', 'price', 'in_stock']
# --- How it's used in practice ---
# SERIALIZATION (Object -> JSON)
product = Product.objects.get(pk=1)
serializer = ProductSerializer(product)
print(serializer.data)
# Output:
# {'id': 1, 'name': 'Test Product', 'price': '100.00', 'in_stock': True}
# DESERIALIZATION (JSON -> Object)
json_data = {'name': 'New Product', 'price': '50.00'}
serializer = ProductSerializer(data=json_data)
if serializer.is_valid():
new_product = serializer.save()
print(f"Created: {new_product.name}")
# Output:
# Created: New Product
35. Explain DRF ViewSets and Routers.
ViewSets and Routers are a set of conventions in DRF that let you write a full, working CRUD (Create, Read, Update, Delete) API with almost no code.
`ViewSet`
A `ViewSet` is a single class that combines the logic for a set of related views. Instead of writing a `ProductListView`, a `ProductDetailView`, and a `ProductCreateView`, you just write one `ProductViewSet`.
It doesn't have `.get()` or `.post()` methods. Instead, it has methods that map to database actions:`.list()`, `.retrieve()`, `.create()`, `.update()`, `.destroy()`.
`Router`
A `Router` is what connects a `ViewSet` to your `urls.py` file. You tell the router about your ViewSet, and it automatically generates all the URL patterns for you.
# my_app/views.py
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer
# This ONE class provides all 5 CRUD operations
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
# ...add permissions, authentication, etc.
# my_project/urls.py
from rest_framework.routers import DefaultRouter
from my_app.views import ProductViewSet
# 1. Create a router
router = DefaultRouter()
# 2. Register our ViewSet with the router
router.register(r'products', ProductViewSet, basename='product')
# 3. 'router.urls' now contains a full set of URLs
urlpatterns = [
# ... your other urls ...
path('api/', include(router.urls)),
]
# The router has now automatically generated:
# /api/products/ -> .list() (GET) and .create() (POST)
# /api/products/{pk}/ -> .retrieve() (GET), .update() (PUT),
# .partial_update() (PATCH),
# and .destroy() (DELETE)
Tip: The combination of `ModelSerializer`, `ModelViewSet`, and `Router` is the core of DRF. It lets you write a complete, production-ready JSON API for a model in as little as 3-5 lines of code.