Back to Blog
March 14, 20261,000 views

Django REST Framework — 150 Deep Questions & Answers

Who is this for? Developers who want to understand *why* DRF works the way it does — not just memorize APIs, but build a mental model that makes every decision obvious.

Django REST Framework — 150 Deep Questions & Answers

**Who is this for?** Developers who want to understand *why* DRF works the way it does — not just memorize APIs, but build a mental model that makes every decision obvious.

🔰 Basics & Setup

1. What is Django REST Framework (DRF) and how does it differ from plain Django?

Django handles HTML responses — it was built for server-rendered web pages. DRF sits on top of Django and adds everything you need to build APIs: serialization, authentication, permissions, throttling, and content negotiation.

Plain Django can return JSON, but you'd write everything manually:

# Plain Django — manual, verbose
import json
from django.http import HttpResponse

def user_list(request):
    users = list(User.objects.values('id', 'username', 'email'))
    return HttpResponse(json.dumps(users), content_type='application/json')

With DRF, the same thing becomes:

# DRF — structured, powerful
from rest_framework import generics
from .serializers import UserSerializer

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

DRF gives you: validation, serialization, authentication, browsable API, pagination, filtering — all wired together.

2. How do you install and configure DRF in a Django project?

pip install djangorestframework
# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}
# urls.py — add browsable API login
urlpatterns = [
    ...
    path('api-auth/', include('rest_framework.urls')),
]

3. What is the role of `DEFAULT_RENDERER_CLASSES` in DRF settings?

Renderers decide how your response data gets formatted before it's sent to the client. By default DRF includes JSONRenderer (returns JSON) and BrowsableAPIRenderer (returns the interactive HTML browser).

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',          # API clients get JSON
        'rest_framework.renderers.BrowsableAPIRenderer',  # Browsers get HTML UI
    ]
}

In production you often remove BrowsableAPIRenderer to reduce overhead and hide your API structure:

# Production settings
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ]
}

4. What does the `REST_FRAMEWORK` settings dict control?

It's the global configuration for all DRF behavior. Think of it as the "default behavior" for every view unless you override at the view level.

REST_FRAMEWORK = {
    # Who can access by default
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],

    # How they prove identity
    'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.TokenAuthentication'],

    # How many requests per second
    'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.UserRateThrottle'],
    'DEFAULT_THROTTLE_RATES': {'user': '1000/day'},

    # How results are split into pages
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,

    # How filtering works
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],

    # What formats are accepted/returned
    'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'],
    'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser'],
}

5. What is an `APIView` and how does it differ from Django's `View`?

Django's View dispatches HTTP methods but knows nothing about APIs. APIView wraps it and adds:

- request.data instead of request.POST - Content negotiation (JSON in, JSON out) - Authentication and permission checking on every request - Throttling - Better exception handling (returns JSON errors, not HTML 500 pages)

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class ProductView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, pk):
        product = get_object_or_404(Product, pk=pk)
        serializer = ProductSerializer(product)
        return Response(serializer.data)

    def put(self, request, pk):
        product = get_object_or_404(Product, pk=pk)
        serializer = ProductSerializer(product, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

6. How does DRF handle content negotiation?

DRF reads the Accept header from the request and picks the best renderer. It reads Content-Type to pick the right parser for incoming data.

# Client sends: Accept: application/json
# DRF uses: JSONRenderer

# Client sends: Accept: text/html
# DRF uses: BrowsableAPIRenderer

# Per-view override
class MyView(APIView):
    renderer_classes = [JSONRenderer]  # Force JSON only for this view

7. What is the browsable API and how do you disable it?

The browsable API is DRF's built-in HTML interface — it lets you explore, make requests, and see responses in a browser. Great for development, but exposes your API structure in production.

# Disable globally in production
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        # BrowsableAPIRenderer removed
    ]
}

# Or conditionally based on environment
import os
renderers = ['rest_framework.renderers.JSONRenderer']
if os.environ.get('DJANGO_ENV') == 'development':
    renderers.append('rest_framework.renderers.BrowsableAPIRenderer')

REST_FRAMEWORK = {'DEFAULT_RENDERER_CLASSES': renderers}

8. What is the difference between `api_view` decorator and `APIView` class?

api_view is for function-based views. APIView is for class-based views. Same power, different style.

# Function-based with @api_view
from rest_framework.decorators import api_view

@api_view(['GET', 'POST'])
def product_list(request):
    if request.method == 'GET':
        products = Product.objects.all()
        return Response(ProductSerializer(products, many=True).data)
    elif request.method == 'POST':
        serializer = ProductSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)

Use @api_view for simple, one-off endpoints. Use APIView or generics when you need inheritance and reuse.

9. How do you set global default settings vs per-view settings in DRF?

Global = REST_FRAMEWORK dict in settings.py. Per-view = class attributes on the view.

# Global (settings.py)
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
}

# Per-view override
class PublicProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    permission_classes = [AllowAny]          # Override global auth
    authentication_classes = []              # No auth needed
    throttle_classes = [AnonRateThrottle]    # Different throttle

The per-view setting always wins.

10. What is `DEFAULT_PARSER_CLASSES` and which parsers are available?

Parsers decode incoming request bodies. DRF picks the right one based on the request's Content-Type header.

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',           # Content-Type: application/json
        'rest_framework.parsers.FormParser',           # Content-Type: application/x-www-form-urlencoded
        'rest_framework.parsers.MultiPartParser',      # Content-Type: multipart/form-data (file uploads)
    ]
}

# Per-view (e.g. file upload endpoint only)
class FileUploadView(APIView):
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request):
        file = request.FILES['file']
        # handle file
        return Response({'filename': file.name})

11. How do you return a JSON response from a DRF view?

Use Response() — never HttpResponse with json.dumps(). DRF's Response handles content negotiation automatically.

from rest_framework.response import Response
from rest_framework import status

class OrderView(APIView):
    def get(self, request):
        data = {'order_id': 123, 'status': 'pending'}
        return Response(data)  # 200 by default

    def post(self, request):
        # Created
        return Response({'id': 1}, status=status.HTTP_201_CREATED)

    def delete(self, request, pk):
        # No content
        return Response(status=status.HTTP_204_NO_CONTENT)

12. What HTTP methods does `APIView` support out of the box?

APIView dispatches to methods named after HTTP verbs: get, post, put, patch, delete, head, options, trace. You only define the ones you want.

class ArticleView(APIView):
    def get(self, request, pk):    # Handles GET
        ...
    def patch(self, request, pk):  # Handles PATCH
        ...
    # POST not defined → 405 Method Not Allowed

13. How does DRF's request object differ from Django's `HttpRequest`?

DRF wraps Django's request and enhances it:

# Django's request
request.POST        # Only form data
request.FILES       # File uploads
request.META        # Headers (raw)

# DRF's request (wraps the above)
request.data        # ANY parsed data: JSON, form, multipart — one unified interface
request.query_params  # Same as request.GET but clearer name
request.user        # Authenticated user (after auth classes run)
request.auth        # Auth token/credential object
request.accepted_renderer  # Which renderer was selected
class MyView(APIView):
    def post(self, request):
        # Works for JSON body, form data, multipart — same code
        name = request.data.get('name')
        page = request.query_params.get('page', 1)

14. What is `request.data` and how does it differ from `request.POST`?

request.POST only works for form-encoded bodies. request.data works for JSON, form, XML — whatever parser is configured.

# Client sends JSON: {"username": "alok", "password": "secret"}
# request.POST → empty {}
# request.data → {"username": "alok", "password": "secret"} ✓

class LoginView(APIView):
    def post(self, request):
        username = request.data.get('username')  # Works for all content types
        password = request.data.get('password')

15. How do you enable CORS in a DRF project?

pip install django-cors-headers
# settings.py
INSTALLED_APPS = [..., 'corsheaders']

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # Must be FIRST
    'django.middleware.common.CommonMiddleware',
    ...
]

# Allow specific origins (production)
CORS_ALLOWED_ORIGINS = [
    'https://app.myapp.de',
    'https://app.myapp.at',
]

# Allow all origins (development only)
CORS_ALLOW_ALL_ORIGINS = True  # Never use in production

# Allow credentials (cookies/auth headers)
CORS_ALLOW_CREDENTIALS = True

# Custom headers
CORS_ALLOW_HEADERS = [
    'authorization',
    'content-type',
    'x-requested-with',
]

📦 Serializers

16. What is a serializer in DRF and what problem does it solve?

A serializer does two jobs: 1. Outgoing: Converts Python objects (Django models) → JSON 2. Incoming: Validates and converts JSON → Python objects (ready to save)

Without serializers you'd write validation and conversion by hand for every endpoint. Serializers give you a schema-based, reusable, testable layer.

from rest_framework import serializers

class PropertySerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=200)
    price = serializers.DecimalField(max_digits=10, decimal_places=2)
    is_active = serializers.BooleanField(default=True)

17. What is the difference between `Serializer` and `ModelSerializer`?

Serializer is fully manual — you define every field. ModelSerializer introspects your Django model and generates fields automatically.

# Manual Serializer — you define everything
class PropertySerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=200)
    price = serializers.DecimalField(max_digits=10, decimal_places=2)

    def create(self, validated_data):
        return Property.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title', instance.title)
        instance.save()
        return instance


# ModelSerializer — auto-generates fields from model
class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['id', 'title', 'price']
    # create() and update() are auto-implemented!

Use ModelSerializer by default. Use Serializer when you need full control or there's no model involved.

18. How does `ModelSerializer` automatically generate fields?

DRF maps Django model field types to serializer field types:

| Model Field | Serializer Field | |-------------|-----------------| | CharField | CharField | | IntegerField | IntegerField | | DecimalField | DecimalField | | BooleanField | BooleanField | | ForeignKey | PrimaryKeyRelatedField | | DateTimeField | DateTimeField |

class Property(models.Model):
    title = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = '__all__'
    # Auto-generates: title (CharField), price (DecimalField),
    # owner (PrimaryKeyRelatedField), created_at (DateTimeField, read_only)

19. What is the `Meta` class inside a serializer?

It's the configuration for ModelSerializer. Tells DRF which model to use and which fields to include.

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property          # Which model
        fields = ['id', 'title', 'price']  # Explicit field list (recommended)
        # OR
        fields = '__all__'        # All fields (be careful — exposes everything)
        # OR
        exclude = ['internal_notes']  # All except these

        read_only_fields = ['id', 'created_at']
        extra_kwargs = {
            'price': {'required': True, 'min_value': 0},
        }

20. How do you make a field read-only in a serializer?

Three ways:

class PropertySerializer(serializers.ModelSerializer):
    # Method 1: field-level
    slug = serializers.SlugField(read_only=True)

    # Method 2: Meta.read_only_fields
    class Meta:
        model = Property
        fields = '__all__'
        read_only_fields = ['id', 'created_at', 'updated_at']

    # Method 3: extra_kwargs
    class Meta:
        model = Property
        fields = '__all__'
        extra_kwargs = {
            'created_at': {'read_only': True},
        }

Read-only fields are included in output (serialization) but ignored on input (deserialization).

21. How do you make a field write-only in a serializer?

Write-only fields are accepted on input but never included in output. Perfect for passwords.

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, min_length=8)
    password_confirm = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['username', 'email', 'password', 'password_confirm']

    def validate(self, data):
        if data['password'] != data['password_confirm']:
            raise serializers.ValidationError("Passwords don't match")
        return data

    def create(self, validated_data):
        validated_data.pop('password_confirm')
        user = User.objects.create_user(**validated_data)
        return user

22. What is the `source` argument in a serializer field?

source maps a serializer field name to a different model attribute or related field.

class PropertySerializer(serializers.ModelSerializer):
    # Rename field in API output
    property_title = serializers.CharField(source='title')

    # Access related model attribute
    owner_name = serializers.CharField(source='owner.get_full_name')

    # Access nested relationship
    city_name = serializers.CharField(source='location.city.name')

    class Meta:
        model = Property
        fields = ['property_title', 'owner_name', 'city_name']

23. How do you validate a single field using `validate_<field>` method?

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['title', 'price', 'zip_code']

    def validate_price(self, value):
        if value <= 0:
            raise serializers.ValidationError("Price must be positive.")
        if value > 100_000_000:
            raise serializers.ValidationError("Price seems unrealistic.")
        return value

    def validate_zip_code(self, value):
        if not value.isdigit() or len(value) != 5:
            raise serializers.ValidationError("German ZIP must be 5 digits.")
        return value

24. How do you perform object-level validation in a serializer?

Use the validate() method — it receives all fields together, letting you compare them.

class BookingSerializer(serializers.ModelSerializer):
    class Meta:
        model = Booking
        fields = ['property', 'check_in', 'check_out', 'guests']

    def validate(self, data):
        if data['check_in'] >= data['check_out']:
            raise serializers.ValidationError(
                "Check-out must be after check-in."
            )
        if data['guests'] > data['property'].max_guests:
            raise serializers.ValidationError(
                f"This property allows max {data['property'].max_guests} guests."
            )
        return data

25. What is the difference between `create()` and `update()` in a serializer?

create() is called when you call serializer.save() on a new instance (no instance passed). update() is called when you pass an existing instance.

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['title', 'price', 'owner']

    def create(self, validated_data):
        # Called by: serializer.save()
        # Can inject extra data here
        return Property.objects.create(**validated_data)

    def update(self, instance, validated_data):
        # Called by: serializer.save() when instance is passed
        instance.title = validated_data.get('title', instance.title)
        instance.price = validated_data.get('price', instance.price)
        instance.save()
        return instance
# In the view:
# CREATE
serializer = PropertySerializer(data=request.data)
serializer.save()  # calls create()

# UPDATE
serializer = PropertySerializer(property, data=request.data, partial=True)
serializer.save()  # calls update()

26. How do you handle nested serializers?

class OwnerSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email']

class PropertySerializer(serializers.ModelSerializer):
    owner = OwnerSerializer(read_only=True)  # Nested output
    owner_id = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all(),
        source='owner',
        write_only=True
    )

    class Meta:
        model = Property
        fields = ['id', 'title', 'owner', 'owner_id']

This gives: read as nested object, write with just an ID — the most practical pattern.

27. What is `many=True` used for in serializers?

It tells the serializer to handle a list of objects instead of a single one. Internally creates a ListSerializer.

# Serializing multiple objects
properties = Property.objects.all()
serializer = PropertySerializer(properties, many=True)
# serializer.data → [{"id": 1, ...}, {"id": 2, ...}]

# In a view
class PropertyListView(APIView):
    def get(self, request):
        properties = Property.objects.filter(is_active=True)
        serializer = PropertySerializer(properties, many=True)
        return Response(serializer.data)

28. How do you serialize a queryset vs a single object?

# Single object — no many=True
property = Property.objects.get(pk=1)
serializer = PropertySerializer(property)
# data: {"id": 1, "title": "..."}

# Queryset — many=True required
properties = Property.objects.all()
serializer = PropertySerializer(properties, many=True)
# data: [{"id": 1, ...}, {"id": 2, ...}]

29. What does `validated_data` contain and when is it available?

validated_data is the cleaned, Python-native data after is_valid() runs. It's only available after validation passes.

class PropertyView(APIView):
    def post(self, request):
        serializer = PropertySerializer(data=request.data)

        # Before is_valid() — validated_data doesn't exist yet
        if serializer.is_valid():
            # Now validated_data is available
            print(serializer.validated_data)
            # {'title': 'Modern Flat', 'price': Decimal('250000.00')}
            serializer.save()
            return Response(serializer.data, status=201)

        return Response(serializer.errors, status=400)

30. How do you add custom fields not in the model to a serializer?

class PropertySerializer(serializers.ModelSerializer):
    # Computed field
    price_per_sqm = serializers.SerializerMethodField()
    # Field not on model but in validated_data for write
    promo_code = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'area_sqm', 'price_per_sqm', 'promo_code']

    def get_price_per_sqm(self, obj):
        if obj.area_sqm:
            return round(obj.price / obj.area_sqm, 2)
        return None

    def create(self, validated_data):
        promo_code = validated_data.pop('promo_code', None)
        instance = super().create(validated_data)
        if promo_code:
            apply_discount(instance, promo_code)
        return instance

31. What is `SerializerMethodField` and when do you use it?

SerializerMethodField lets you add a read-only computed field backed by a method. Perfect for any value that's derived rather than stored.

class PropertySerializer(serializers.ModelSerializer):
    is_premium = serializers.SerializerMethodField()
    full_address = serializers.SerializerMethodField()
    days_listed = serializers.SerializerMethodField()

    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'is_premium', 'full_address', 'days_listed']

    def get_is_premium(self, obj):
        return obj.price > 500_000

    def get_full_address(self, obj):
        return f"{obj.street}, {obj.zip_code} {obj.city}"

    def get_days_listed(self, obj):
        from django.utils import timezone
        return (timezone.now() - obj.created_at).days

32. How do you control which fields are included using `fields` in Meta?

# Explicit list — most recommended
class PropertyListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'city']  # Only these 4

# All fields — use carefully
class PropertyAdminSerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = '__all__'  # Every model field

Explicit field lists are safer — they don't accidentally expose new model fields when you add them.

33. How do you exclude specific fields using `exclude` in Meta?

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        exclude = ['internal_notes', 'admin_score', 'raw_data']
        # Everything except these fields

You can't use both fields and exclude together.

34. What is `extra_kwargs` in `ModelSerializer.Meta`?

extra_kwargs lets you add or override field options without redefining the field.

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'slug', 'owner', 'created_at']
        extra_kwargs = {
            'slug': {'read_only': True},
            'created_at': {'read_only': True},
            'price': {
                'min_value': 0,
                'max_value': 100_000_000,
                'error_messages': {'min_value': 'Price cannot be negative.'}
            },
            'owner': {'write_only': True},
        }

35. How do you handle file uploads in a serializer?

class PropertyImageSerializer(serializers.ModelSerializer):
    image = serializers.ImageField()

    class Meta:
        model = PropertyImage
        fields = ['id', 'property', 'image', 'caption']

    def validate_image(self, value):
        max_size = 5 * 1024 * 1024  # 5MB
        if value.size > max_size:
            raise serializers.ValidationError("Image must be under 5MB.")
        return value
# View must use MultiPartParser
class PropertyImageUploadView(APIView):
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request):
        serializer = PropertyImageSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)

36. What is a `HyperlinkedModelSerializer`?

Instead of primary keys for relationships, it uses URLs. Better for REST purists; makes APIs self-documenting.

class PropertySerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Property
        fields = ['url', 'id', 'title', 'owner']
        # owner → "http://api.example.com/users/5/" instead of 5

In practice, most teams use ModelSerializer with PKs — simpler and more predictable.

37. How does DRF handle `UniqueValidator` and `UniqueTogetherValidator`?

DRF adds these automatically from your model's unique and unique_together constraints.

# Automatic from model
class Property(models.Model):
    slug = models.SlugField(unique=True)

    class Meta:
        unique_together = [['owner', 'title']]

# DRF auto-adds validators — or manually:
class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['slug', 'owner', 'title']
        validators = [
            UniqueTogetherValidator(
                queryset=Property.objects.all(),
                fields=['owner', 'title'],
                message="You already have a property with this title."
            )
        ]

38. What is `to_representation()` and when would you override it?

to_representation() controls the final output format. Override it to transform data after normal serialization.

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'currency']

    def to_representation(self, instance):
        data = super().to_representation(instance)
        # Format price with currency
        data['price'] = f"{data['currency']} {data['price']:,.2f}"
        data.pop('currency')  # Remove raw currency field
        # Add computed field not worth a SerializerMethodField
        data['url'] = f"/properties/{instance.pk}/"
        return data

39. What is `to_internal_value()` and when would you override it?

to_internal_value() handles input transformation before validation. Override to normalize or transform incoming data.

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['title', 'price']

    def to_internal_value(self, data):
        # Normalize: convert German number format "250.000,99" → "250000.99"
        if 'price' in data and isinstance(data['price'], str):
            data = data.copy()
            data['price'] = data['price'].replace('.', '').replace(',', '.')
        return super().to_internal_value(data)

40. How do you pass extra context to a serializer?

Use the context parameter. Common uses: passing request, user, or view-specific data.

# In the view
serializer = PropertySerializer(
    property,
    context={'request': request, 'show_private': request.user.is_staff}
)

# In the serializer
class PropertySerializer(serializers.ModelSerializer):
    private_notes = serializers.SerializerMethodField()

    def get_private_notes(self, obj):
        if self.context.get('show_private'):
            return obj.private_notes
        return None

41. How do you use `partial=True` in a serializer for PATCH requests?

partial=True makes all fields optional, so PATCH can update just one field without sending everything.

class PropertyView(APIView):
    def patch(self, request, pk):
        property = get_object_or_404(Property, pk=pk)
        # partial=True → only validate/update provided fields
        serializer = PropertySerializer(
            property,
            data=request.data,
            partial=True
        )
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=400)

PUT without partial=True requires all fields. PATCH with partial=True allows any subset.

42. What is the difference between `is_valid()` with `raise_exception=True` vs without?

# Without raise_exception — manual handling
if serializer.is_valid():
    serializer.save()
    return Response(serializer.data)
else:
    return Response(serializer.errors, status=400)

# With raise_exception=True — auto 400 on invalid
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
# If invalid → DRF raises ValidationError → returns 400 automatically

raise_exception=True is cleaner for most cases.

43. How do you handle forward relationships (FK, M2M) in serializers?

class PropertySerializer(serializers.ModelSerializer):
    # By default: FK → returns PK integer
    owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())

    # As nested object (read)
    owner_detail = OwnerSerializer(source='owner', read_only=True)

    # As string
    owner_name = serializers.StringRelatedField(source='owner')

    # M2M as list of PKs
    amenities = serializers.PrimaryKeyRelatedField(
        many=True, queryset=Amenity.objects.all()
    )

    class Meta:
        model = Property
        fields = ['id', 'title', 'owner', 'owner_detail', 'owner_name', 'amenities']

44. How do you serialize a `GenericRelatedField`?

When you have a GenericForeignKey and need to serialize it as different types:

class GenericObjectRelatedField(serializers.RelatedField):
    def to_representation(self, value):
        if isinstance(value, Property):
            return PropertySerializer(value).data
        if isinstance(value, User):
            return UserSerializer(value).data
        raise Exception(f'Unexpected type: {type(value)}')

class ActivitySerializer(serializers.ModelSerializer):
    target = GenericObjectRelatedField(read_only=True)

    class Meta:
        model = Activity
        fields = ['id', 'verb', 'target', 'created_at']

45. What are `ListSerializer` and `BaseSerializer` used for?

ListSerializer is what gets created when you use many=True. Override it for bulk create/update logic.

class PropertyListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        properties = [Property(**item) for item in validated_data]
        return Property.objects.bulk_create(properties)

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['title', 'price']
        list_serializer_class = PropertyListSerializer

# Usage: bulk create
serializer = PropertySerializer(data=request.data, many=True)
if serializer.is_valid():
    serializer.save()  # Calls bulk_create → single DB query

BaseSerializer is the base class when you need full control over both in and out, with no model.

🔭 Views & ViewSets

46. What is a `ViewSet` and how does it differ from `APIView`?

APIView maps HTTP methods to functions (get, post, etc.). ViewSet maps actions (list, create, retrieve, update, destroy) and connects to routers automatically.

from rest_framework import viewsets

class PropertyViewSet(viewsets.ViewSet):
    def list(self, request):         # GET /properties/
        ...
    def create(self, request):       # POST /properties/
        ...
    def retrieve(self, request, pk): # GET /properties/{pk}/
        ...
    def update(self, request, pk):   # PUT /properties/{pk}/
        ...
    def destroy(self, request, pk):  # DELETE /properties/{pk}/
        ...

47. What is a `ModelViewSet` and what actions does it provide by default?

ModelViewSet gives you all 5 CRUD actions with minimal code:

class PropertyViewSet(viewsets.ModelViewSet):
    queryset = Property.objects.all()
    serializer_class = PropertySerializer
    permission_classes = [IsAuthenticated]

# This single class handles:
# GET    /properties/       → list()
# POST   /properties/       → create()
# GET    /properties/{pk}/  → retrieve()
# PUT    /properties/{pk}/  → update()
# PATCH  /properties/{pk}/  → partial_update()
# DELETE /properties/{pk}/  → destroy()

48. What are the individual mixins used for?

Mixins let you compose only the actions you need:

from rest_framework import mixins, generics

# Read-only list + detail (no create/update/delete)
class PropertyReadOnlyViewSet(
    mixins.ListModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet
):
    queryset = Property.objects.all()
    serializer_class = PropertySerializer

# Create + List only (no update/delete)
class CommentViewSet(
    mixins.CreateModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

49. What is `GenericAPIView` and what does it provide?

GenericAPIView extends APIView with queryset, serializer, pagination, filtering, and lookup wiring. All generic views and mixins are built on it.

from rest_framework.generics import GenericAPIView

class PropertyView(GenericAPIView):
    queryset = Property.objects.all()
    serializer_class = PropertySerializer

    def get(self, request):
        qs = self.get_queryset()          # filtered queryset
        serializer = self.get_serializer(qs, many=True)
        return Response(serializer.data)

50. What is `get_queryset()` and why override it vs defining `queryset` directly?

queryset is static — evaluated once at class definition. get_queryset() is dynamic — called per request.

class PropertyViewSet(viewsets.ModelViewSet):
    serializer_class = PropertySerializer

    def get_queryset(self):
        # Filter by current user → needs request context
        return Property.objects.filter(
            owner=self.request.user,
            is_active=True
        ).select_related('owner', 'location')
        # Cannot do this with a class-level queryset attribute

Always override get_queryset() when you need self.request or dynamic filtering.

51. What is `get_serializer_class()` and when is it useful?

Returns different serializers based on the action, user role, or request context.

class PropertyViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        if self.action == 'list':
            return PropertyListSerializer    # Lightweight for lists
        if self.action == 'create':
            return PropertyCreateSerializer  # Extra validation for creation
        if self.request.user.is_staff:
            return PropertyAdminSerializer   # More fields for admins
        return PropertyDetailSerializer      # Default

52. How do you restrict a ViewSet to only certain actions?

from rest_framework.routers import DefaultRouter

router = DefaultRouter()

# Only list and retrieve — no create/update/delete
router.register('properties', PropertyViewSet, basename='property')

# In the ViewSet, use http_method_names or mixins:
class PropertyViewSet(
    mixins.ListModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet
):
    queryset = Property.objects.all()
    serializer_class = PropertySerializer

53. What is `get_object()` and how does it work?

get_object() fetches a single model instance using lookup_field (default: pk), applies object-level permissions, and raises 404 if not found.

class PropertyDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Property.objects.all()
    serializer_class = PropertySerializer

    def get(self, request, pk):
        # get_object() handles: lookup by pk, 404, permission check
        property = self.get_object()
        serializer = self.get_serializer(property)
        return Response(serializer.data)

54. How do you override `perform_create()` in a view?

perform_create() is the hook for injecting extra data at save time without changing the serializer.

class PropertyViewSet(viewsets.ModelViewSet):
    serializer_class = PropertySerializer

    def perform_create(self, serializer):
        # Inject owner from the authenticated user
        serializer.save(
            owner=self.request.user,
            created_ip=self.request.META.get('REMOTE_ADDR')
        )

55. How do you override `perform_update()` and `perform_destroy()`?

class PropertyViewSet(viewsets.ModelViewSet):
    def perform_update(self, serializer):
        serializer.save(last_modified_by=self.request.user)

    def perform_destroy(self, instance):
        # Soft delete instead of hard delete
        instance.is_deleted = True
        instance.deleted_by = self.request.user
        instance.save()
        # Don't call instance.delete()

56. What is `ReadOnlyModelViewSet`?

A ModelViewSet with only list and retrieve — read-only API endpoints.

class CityViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = City.objects.all()
    serializer_class = CitySerializer
    permission_classes = [AllowAny]

# Gives:
# GET /cities/       → list
# GET /cities/{pk}/  → retrieve
# No POST, PUT, PATCH, DELETE

57. How do you add custom actions to a ViewSet using `@action`?

from rest_framework.decorators import action

class PropertyViewSet(viewsets.ModelViewSet):
    queryset = Property.objects.all()
    serializer_class = PropertySerializer

    @action(detail=True, methods=['post'], url_path='publish')
    def publish(self, request, pk=None):
        property = self.get_object()
        property.is_published = True
        property.save()
        return Response({'status': 'published'})

    @action(detail=False, methods=['get'], url_path='featured')
    def featured(self, request):
        featured = Property.objects.filter(is_featured=True)
        serializer = self.get_serializer(featured, many=True)
        return Response(serializer.data)

# URLs generated:
# POST /properties/{pk}/publish/
# GET  /properties/featured/

58. What is the `detail` parameter in `@action`?

- detail=True → operates on a single object, URL includes {pk}: /properties/{pk}/publish/ - detail=False → operates on the collection: /properties/featured/

59. How do you return different serializers for list vs detail views?

class PropertyViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        if self.action == 'list':
            return PropertyListSerializer    # Fewer fields, faster
        return PropertyDetailSerializer      # All fields
# PropertyListSerializer — lean
class PropertyListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'city', 'thumbnail']

# PropertyDetailSerializer — full
class PropertyDetailSerializer(serializers.ModelSerializer):
    owner = OwnerSerializer(read_only=True)
    images = PropertyImageSerializer(many=True, read_only=True)

    class Meta:
        model = Property
        fields = '__all__'

60. How do you return a custom response from a ViewSet action?

@action(detail=True, methods=['get'])
def price_history(self, request, pk=None):
    property = self.get_object()
    history = PriceHistory.objects.filter(property=property).order_by('-date')
    data = {
        'property_id': pk,
        'current_price': property.price,
        'history': [
            {'date': h.date, 'price': str(h.price)} for h in history
        ]
    }
    return Response(data)

🔐 Authentication & Permissions

71. What authentication classes does DRF provide out of the box?

| Class | Mechanism | Use Case | |-------|-----------|----------| | SessionAuthentication | Django session cookie | Browser-based clients | | TokenAuthentication | Authorization: Token header | Mobile/SPA clients | | BasicAuthentication | Base64 username:password | Testing only | | RemoteUserAuthentication | Server-provided REMOTE_USER | SSO/enterprise |

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ]
}

72. How does `SessionAuthentication` work in DRF?

It uses Django's built-in session framework. After login, Django creates a session cookie. DRF reads that cookie to identify the user.

Requires CSRF enforcement — DRF adds CSRF checks automatically for SessionAuthentication.

# Login
from django.contrib.auth import login

class LoginView(APIView):
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        user = authenticate(
            username=request.data['username'],
            password=request.data['password']
        )
        if user:
            login(request, user)  # Creates session
            return Response({'status': 'logged in'})
        return Response({'error': 'Invalid credentials'}, status=401)

73. How does `TokenAuthentication` work and how do you set it up?

# 1. Add to INSTALLED_APPS
INSTALLED_APPS = [..., 'rest_framework.authtoken']

# 2. Run migration
# python manage.py migrate

# 3. Generate token on login
from rest_framework.authtoken.models import Token

class LoginView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        user = authenticate(**request.data)
        if user:
            token, created = Token.objects.get_or_create(user=user)
            return Response({'token': token.key})
        return Response({'error': 'Invalid'}, status=401)

# 4. Client sends: Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4f

76. What is the difference between authentication and permissions in DRF?

- Authentication = Who are you? — identifies the user from the request - Permissions = What are you allowed to do? — decides if the identified user can perform this action

class PropertyView(APIView):
    authentication_classes = [TokenAuthentication]  # WHO: identify via token
    permission_classes = [IsAuthenticated]          # WHAT: must be logged in

    def get(self, request, pk):
        # At this point:
        # request.user is set (authentication passed)
        # User is confirmed authenticated (permission passed)
        property = get_object_or_404(Property, pk=pk)
        return Response(PropertySerializer(property).data)

80. How do you write a custom permission class?

from rest_framework.permissions import BasePermission

class IsPropertyOwner(BasePermission):
    message = "You must be the owner of this property."

    def has_permission(self, request, view):
        # View-level: must be authenticated
        return request.user and request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        # Object-level: must own the property
        if request.method in ['GET', 'HEAD', 'OPTIONS']:
            return True  # Safe methods allowed
        return obj.owner == request.user


class PropertyViewSet(viewsets.ModelViewSet):
    permission_classes = [IsPropertyOwner]

81. What is `has_permission()` vs `has_object_permission()`?

- has_permission() → checked on every request before the view runs - has_object_permission() → checked only when get_object() is called (detail views)

class IsOwnerOrReadOnly(BasePermission):
    def has_permission(self, request, view):
        # Everyone can read, authenticated users can write
        if request.method in SAFE_METHODS:
            return True
        return request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        # Reading: anyone
        if request.method in SAFE_METHODS:
            return True
        # Writing: only owner
        return obj.owner == request.user

⚡ Throttling, Filtering & Pagination

91. What is throttling in DRF and why is it important?

Throttling limits how many requests a client can make in a given time window. Without it, your API is vulnerable to abuse, scraping, and denial-of-service.

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day',
    }
}

93. How do you create a custom throttle class?

from rest_framework.throttling import UserRateThrottle

class AIAnalysisThrottle(UserRateThrottle):
    scope = 'ai_analysis'

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'ai_analysis': '10/day',  # Only 10 AI calls per user per day
    }
}

# View
class ImmoCheckView(APIView):
    throttle_classes = [AIAnalysisThrottle]

101. What is `PageNumberPagination`?

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}

# Custom class for more control
class PropertyPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = 'page_size'  # ?page_size=50
    max_page_size = 100
    page_query_param = 'page'            # ?page=2

# Response:
# {
#   "count": 150,
#   "next": "http://api.example.com/properties/?page=3",
#   "previous": "http://api.example.com/properties/?page=1",
#   "results": [...]
# }

102. What is `LimitOffsetPagination`?

class PropertyPagination(LimitOffsetPagination):
    default_limit = 20
    max_limit = 100

# Client uses: ?limit=20&offset=40
# Response:
# {
#   "count": 150,
#   "next": "...?limit=20&offset=60",
#   "previous": "...?limit=20&offset=20",
#   "results": [...]
# }

🗺️ Routers & URLs

111. What is a DRF `Router` and what problem does it solve?

Routers auto-generate URL patterns for ViewSets. Without them, you'd manually write 6 URL patterns per ViewSet.

# Without router — tedious
urlpatterns = [
    path('properties/', PropertyViewSet.as_view({'get': 'list', 'post': 'create'})),
    path('properties/<pk>/', PropertyViewSet.as_view({
        'get': 'retrieve', 'put': 'update',
        'patch': 'partial_update', 'delete': 'destroy'
    })),
]

# With router — clean
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('properties', PropertyViewSet)
router.register('users', UserViewSet)

urlpatterns = [path('api/', include(router.urls))]

112. What is the difference between `DefaultRouter` and `SimpleRouter`?

DefaultRouter adds an API root endpoint (/api/) that lists all registered routes. SimpleRouter doesn't.

In production, SimpleRouter is preferable — you don't want to expose your route map.

🧪 Testing

121. What is `APITestCase` and how does it differ from Django's `TestCase`?

APITestCase provides APIClient — which understands DRF authentication and formats requests properly for JSON APIs.

from rest_framework.test import APITestCase, APIClient
from rest_framework import status

class PropertyAPITest(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user('testuser', password='pass123')
        self.property = Property.objects.create(
            title='Test Property',
            price=250000,
            owner=self.user
        )

    def test_list_properties(self):
        self.client.force_authenticate(user=self.user)
        response = self.client.get('/api/properties/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)

    def test_unauthenticated_access(self):
        response = self.client.get('/api/properties/')
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

🔄 Advanced & Real-World

134. What is `exception_handler` setting and how does it work?

# settings.py
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'myapp.utils.custom_exception_handler'
}

# utils.py
from rest_framework.views import exception_handler
from rest_framework.response import Response

def custom_exception_handler(exc, context):
    # Call DRF's default handler first
    response = exception_handler(exc, context)

    if response is not None:
        # Wrap in consistent format
        response.data = {
            'success': False,
            'error': {
                'code': response.status_code,
                'message': response.data,
            }
        }
    return response

150. How do you optimize DRF serializer performance for large datasets using `select_related` and `prefetch_related`?

The N+1 problem is the most common DRF performance killer.

# BAD — N+1 queries: 1 for properties + N for each owner
class PropertyViewSet(viewsets.ModelViewSet):
    queryset = Property.objects.all()
    serializer_class = PropertySerializer

# GOOD — 2 queries total
class PropertyViewSet(viewsets.ModelViewSet):
    serializer_class = PropertySerializer

    def get_queryset(self):
        return Property.objects.select_related(
            'owner',        # FK — single JOIN
            'location',     # FK
        ).prefetch_related(
            'images',       # Reverse FK — separate optimized query
            'amenities',    # M2M
        ).filter(is_active=True)
# For SerializerMethodField that hits the DB — cache results
class PropertySerializer(serializers.ModelSerializer):
    similar_count = serializers.SerializerMethodField()

    def get_similar_count(self, obj):
        # annotate this in the queryset instead:
        return getattr(obj, 'similar_count', 0)

# In the viewset:
def get_queryset(self):
    return Property.objects.annotate(
        similar_count=Count('similar_properties')
    )

61. How do you get the current user inside a serializer?

Access it via self.context['request'].user:

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'is_owner']

    is_owner = serializers.SerializerMethodField()

    def get_is_owner(self, obj):
        request = self.context.get('request')
        if request and request.user.is_authenticated:
            return obj.owner == request.user
        return False

Always pass context={'request': request} when instantiating the serializer in your view — or use get_serializer() which does it automatically.

62. How do you handle writable nested serializers?

By default, nested serializers are read-only. To make them writable, override create() and update():

class AddressSerializer(serializers.ModelSerializer):
    class Meta:
        model = Address
        fields = ['street', 'city', 'zip_code', 'country']

class PropertySerializer(serializers.ModelSerializer):
    address = AddressSerializer()

    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'address']

    def create(self, validated_data):
        address_data = validated_data.pop('address')
        address = Address.objects.create(**address_data)
        property = Property.objects.create(address=address, **validated_data)
        return property

    def update(self, instance, validated_data):
        address_data = validated_data.pop('address', None)
        if address_data:
            for attr, value in address_data.items():
                setattr(instance.address, attr, value)
            instance.address.save()
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

63. What is the difference between `PUT` and `PATCH` in DRF views?

- PUT = full update — all required fields must be sent, missing fields get reset to default - PATCH = partial update — only send the fields you want to change

class PropertyView(APIView):
    def put(self, request, pk):
        property = get_object_or_404(Property, pk=pk)
        # partial=False (default) — requires ALL fields
        serializer = PropertySerializer(property, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=400)

    def patch(self, request, pk):
        property = get_object_or_404(Property, pk=pk)
        # partial=True — only validate/update provided fields
        serializer = PropertySerializer(property, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=400)

In ModelViewSet, update() handles PUT and partial_update() handles PATCH automatically.

64. What is `get_serializer_context()` and when do you override it?

It builds the context dict passed to every serializer. Override to inject extra data:

class PropertyViewSet(viewsets.ModelViewSet):
    def get_serializer_context(self):
        context = super().get_serializer_context()
        # Add extra context for the serializer to use
        context['show_private_fields'] = self.request.user.is_staff
        context['currency'] = self.request.query_params.get('currency', 'EUR')
        return context

The base method already adds request, format, and view — you're extending, not replacing.

65. How do you implement dynamic fields in a serializer (return different fields per request)?

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        fields = kwargs.pop('fields', None)
        super().__init__(*args, **kwargs)
        if fields is not None:
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

class PropertySerializer(DynamicFieldsModelSerializer):
    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'owner', 'address', 'images']

# Usage: only return id + title + price
serializer = PropertySerializer(property, fields=['id', 'title', 'price'])

Or via query param in the view:

def get_serializer(self, *args, **kwargs):
    fields = self.request.query_params.get('fields')
    if fields:
        kwargs['fields'] = fields.split(',')
    return super().get_serializer(*args, **kwargs)
# GET /properties/?fields=id,title,price

66. What is `depth` in `ModelSerializer.Meta` and when should you avoid it?

depth auto-expands related objects to N levels deep:

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = '__all__'
        depth = 1  # Expand FK relationships one level

# depth=1: owner becomes {"id": 1, "username": "alok", ...} instead of just 1
# depth=2: owner.profile also expanded

Avoid it because: - No control over which fields of related objects are exposed - Can expose sensitive data accidentally - Can trigger N+1 queries - Read-only (can't write nested data)

Use explicit nested serializers instead — more control, safer.

67. How do you filter a queryset based on URL kwargs?

# URL: /api/owners/<owner_id>/properties/
class PropertyViewSet(viewsets.ModelViewSet):
    serializer_class = PropertySerializer

    def get_queryset(self):
        owner_id = self.kwargs.get('owner_id')
        if owner_id:
            return Property.objects.filter(owner_id=owner_id)
        return Property.objects.all()
# urls.py
urlpatterns = [
    path('owners/<int:owner_id>/properties/',
         PropertyViewSet.as_view({'get': 'list'})),
]

68. What is `action` attribute on the view and how do you use it?

self.action tells you which ViewSet action is currently running (list, create, retrieve, update, partial_update, destroy, or your custom action name):

class PropertyViewSet(viewsets.ModelViewSet):
    def get_permissions(self):
        # Different permissions for different actions
        if self.action in ['list', 'retrieve']:
            return [AllowAny()]
        if self.action == 'destroy':
            return [IsAdminUser()]
        return [IsAuthenticated()]

    def get_throttle_classes(self):
        if self.action == 'create':
            return [UserRateThrottle]
        return []

69. How do you add pagination to a specific ViewSet action?

class PropertyViewSet(viewsets.ModelViewSet):
    @action(detail=False, methods=['get'])
    def search(self, request):
        queryset = Property.objects.filter(
            title__icontains=request.query_params.get('q', '')
        )
        # Manually apply pagination
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

70. What does `finalize_response()` do in DRF?

It's called just before the response is returned. Useful for adding headers or post-processing:

class PropertyViewSet(viewsets.ModelViewSet):
    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        # Add custom header to every response from this viewset
        response['X-App-Version'] = '2.1.0'
        response['X-Response-Time'] = getattr(request, '_response_time', 'unknown')
        return response

74. What is `BasicAuthentication` and when should you avoid it?

BasicAuthentication sends base64-encoded username:password with every request in the Authorization header.

# Client sends: Authorization: Basic YWxvazpzZWNyZXQ=
# (base64 of "alok:secret")

Avoid it because: - Credentials sent with EVERY request (not just login) - base64 is not encryption — trivially decodable - No token expiry mechanism - Only safe over HTTPS (still sends credentials constantly)

When it's OK: Internal tools, testing, machine-to-machine in controlled environments over HTTPS.

75. How do you implement JWT authentication in DRF?

pip install djangorestframework-simplejwt
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

from datetime import timedelta
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
}
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view()),       # POST: get tokens
    path('api/token/refresh/', TokenRefreshView.as_view()),  # POST: refresh
]
# Get tokens
POST /api/token/ {"username": "alok", "password": "secret"}
# Response: {"access": "eyJ...", "refresh": "eyJ..."}

# Use access token
Authorization: Bearer eyJ...

# Refresh when expired
POST /api/token/refresh/ {"refresh": "eyJ..."}

77. What is `IsAuthenticated` permission class?

Returns True only if request.user is authenticated (not anonymous):

from rest_framework.permissions import IsAuthenticated

class MyView(APIView):
    permission_classes = [IsAuthenticated]
    # 401 for unauthenticated, 403 if authenticated but no permission

78. What is `IsAdminUser` permission class?

Returns True only if request.user.is_staff is True:

from rest_framework.permissions import IsAdminUser

class AdminOnlyView(APIView):
    permission_classes = [IsAdminUser]
    # Only Django staff users can access

79. What is `IsAuthenticatedOrReadOnly` permission class?

Safe methods (GET, HEAD, OPTIONS) are allowed for anyone. Write methods require authentication:

from rest_framework.permissions import IsAuthenticatedOrReadOnly

class PropertyViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticatedOrReadOnly]
    # GET /properties/ → anyone
    # POST /properties/ → must be authenticated

82. How do you set permissions per-view vs globally?

# Global (settings.py) — applies to ALL views
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated']
}

# Per-view — overrides global
class PublicListingsView(generics.ListAPIView):
    permission_classes = [AllowAny]  # Public endpoint

class AdminView(generics.ListAPIView):
    permission_classes = [IsAdminUser]  # Admin only

83. What is `AllowAny` and when would you use it?

AllowAny explicitly grants access to all users — authenticated or not. Use when you want to be explicit about a public endpoint, even though the global default requires auth:

class RegisterView(APIView):
    permission_classes = [AllowAny]  # Explicit — no auth needed to register

class HealthCheckView(APIView):
    permission_classes = [AllowAny]  # Public health check endpoint

84. How do you combine multiple permissions (AND logic) in DRF?

Listing multiple permission classes applies AND logic — all must pass:

class PropertyView(APIView):
    permission_classes = [IsAuthenticated, IsVerifiedUser, IsActiveSubscriber]
    # User must be: authenticated AND verified AND subscribed

For OR logic, you need a custom class:

from rest_framework.permissions import BasePermission, IsAuthenticated, IsAdminUser

class IsAuthenticatedOrAdmin(BasePermission):
    def has_permission(self, request, view):
        return (IsAuthenticated().has_permission(request, view) or
                IsAdminUser().has_permission(request, view))

85. How do you implement row-level or object-level permissions?

Override has_object_permission(). It's only called for detail endpoints (retrieve, update, destroy):

class IsOwnerOrAdmin(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in ['GET', 'HEAD', 'OPTIONS']:
            return True
        return obj.owner == request.user or request.user.is_staff

class PropertyViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated, IsOwnerOrAdmin]
    # get_object() automatically calls has_object_permission()

Important: has_object_permission is NOT called on list views — only when get_object() is called. Filter querysets manually for list views.

86. What is `request.user` and `request.auth` in DRF?

- request.user → The authenticated Django User instance (or AnonymousUser) - request.auth → The raw auth credential (Token instance, JWT payload, None)

class MyView(APIView):
    def get(self, request):
        print(request.user)       # <User: alok>
        print(request.auth)       # Token instance (for TokenAuthentication)
                                  # or JWT dict (for JWTAuthentication)
        print(request.user.is_authenticated)  # True/False

87. How do you test authenticated endpoints in DRF tests?

from rest_framework.test import APITestCase
from rest_framework.authtoken.models import Token

class PropertyTests(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user('alok', password='pass')

    # Method 1: force_authenticate (most common)
    def test_create_property(self):
        self.client.force_authenticate(user=self.user)
        response = self.client.post('/api/properties/', {'title': 'Test', 'price': 100000})
        self.assertEqual(response.status_code, 201)

    # Method 2: Token credentials
    def test_with_token(self):
        token = Token.objects.create(user=self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Token {token.key}')
        response = self.client.get('/api/properties/')
        self.assertEqual(response.status_code, 200)

    # Method 3: JWT
    def test_with_jwt(self):
        from rest_framework_simplejwt.tokens import RefreshToken
        refresh = RefreshToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {refresh.access_token}')
        response = self.client.get('/api/properties/')
        self.assertEqual(response.status_code, 200)

88. What is `DjangoModelPermissions`?

Maps standard Django model permissions (add_, change_, delete_, view_) to HTTP methods:

class PropertyViewSet(viewsets.ModelViewSet):
    permission_classes = [DjangoModelPermissions]
    # POST   → requires app.add_property
    # PUT    → requires app.change_property
    # DELETE → requires app.delete_property
    # GET    → requires app.view_property

Great for admin-style APIs where permissions are managed through Django's admin interface.

89. How do you throttle anonymous vs authenticated users differently?

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '20/day',     # Anonymous: 20 requests per day
        'user': '1000/day',   # Authenticated: 1000 per day
    }
}

90. How does DRF handle CSRF protection with `SessionAuthentication`?

DRF enforces CSRF for SessionAuthentication only — not for Token/JWT auth. For AJAX requests from browsers using session auth:

# Django sets CSRF cookie automatically
# Include it in your AJAX headers:
# X-CSRFToken: <value from csrftoken cookie>

# In React/Vue with axios:
import Cookies from 'js-cookie'
axios.defaults.headers.common['X-CSRFToken'] = Cookies.get('csrftoken')

For SPA + session auth, either set CSRF_COOKIE_HTTPONLY = False so JS can read the cookie, or use Token/JWT authentication instead.

92. What is `AnonRateThrottle` vs `UserRateThrottle`?

- AnonRateThrottle — identifies anonymous users by IP address, applies rate from 'anon' key - UserRateThrottle — identifies authenticated users by user ID, applies rate from 'user' key

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '5000/day',
    }
}

94. What is `ScopedRateThrottle`?

Lets you set different rates for different views using a throttle_scope attribute:

class LoginView(APIView):
    throttle_scope = 'login'  # Uses 'login' rate

class AIAnalysisView(APIView):
    throttle_scope = 'ai'     # Uses 'ai' rate

class StandardView(APIView):
    throttle_scope = 'standard'
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.ScopedRateThrottle'],
    'DEFAULT_THROTTLE_RATES': {
        'login': '5/hour',
        'ai': '10/day',
        'standard': '1000/day',
    }
}

95. How do you set throttle rates globally vs per-view?

# Global (settings.py)
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.UserRateThrottle'],
    'DEFAULT_THROTTLE_RATES': {'user': '1000/day'}
}

# Per-view override
class ExpensiveView(APIView):
    throttle_classes = [UserRateThrottle]
    throttle_scope = 'expensive'  # if using ScopedRateThrottle

class NoThrottleView(APIView):
    throttle_classes = []  # No throttling for this view

96. What is `django-filter` and how does it integrate with DRF?

django-filter provides declarative filtering for querysets. DRF integrates it via DjangoFilterBackend:

pip install django-filter
# settings.py
INSTALLED_APPS = [..., 'django_filters']
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
# filters.py
import django_filters

class PropertyFilter(django_filters.FilterSet):
    min_price = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
    max_price = django_filters.NumberFilter(field_name='price', lookup_expr='lte')
    city = django_filters.CharFilter(field_name='location__city', lookup_expr='iexact')
    year_built = django_filters.RangeFilter()

    class Meta:
        model = Property
        fields = ['property_type', 'is_available', 'min_price', 'max_price']

class PropertyViewSet(viewsets.ModelViewSet):
    filterset_class = PropertyFilter
    # GET /properties/?min_price=200000&max_price=500000&city=Munich

97. How do you use `filterset_fields` in a view?

Quick way to add exact-match filtering without writing a FilterSet:

class PropertyViewSet(viewsets.ModelViewSet):
    filterset_fields = ['city', 'property_type', 'is_available', 'owner']
    # GET /properties/?city=Munich&property_type=apartment&is_available=true

For more complex lookups (ranges, contains), use a full FilterSet class.

98. What is `SearchFilter` and how do you configure it?

SearchFilter adds ?search=term functionality across multiple fields:

from rest_framework.filters import SearchFilter

class PropertyViewSet(viewsets.ModelViewSet):
    filter_backends = [SearchFilter]
    search_fields = [
        'title',           # Exact contains match
        '^city',           # Starts-with match
        '=zip_code',       # Exact match
        '@description',    # Full-text search (PostgreSQL only)
        'owner__username', # Related field
    ]
    # GET /properties/?search=munich apartment
    # Searches title, city, zip_code, description, owner.username

99. What is `OrderingFilter` and how do you use it?

Lets clients sort results via ?ordering=field:

from rest_framework.filters import OrderingFilter

class PropertyViewSet(viewsets.ModelViewSet):
    filter_backends = [OrderingFilter]
    ordering_fields = ['price', 'created_at', 'title', 'area_sqm']
    ordering = ['-created_at']  # Default ordering

    # GET /properties/?ordering=price         → cheapest first
    # GET /properties/?ordering=-price        → most expensive first
    # GET /properties/?ordering=city,price    → by city, then price

100. How do you write a custom `FilterBackend`?

from rest_framework.filters import BaseFilterBackend

class IsOwnerFilterBackend(BaseFilterBackend):
    """Only return objects owned by the current user."""

    def filter_queryset(self, request, queryset, view):
        return queryset.filter(owner=request.user)

class ActiveOnlyFilterBackend(BaseFilterBackend):
    """Only return active objects unless admin."""

    def filter_queryset(self, request, queryset, view):
        if not request.user.is_staff:
            return queryset.filter(is_active=True)
        return queryset

class PropertyViewSet(viewsets.ModelViewSet):
    filter_backends = [IsOwnerFilterBackend, DjangoFilterBackend, OrderingFilter]

103. What is `CursorPagination` and when is it preferred?

Cursor pagination uses an opaque cursor (encoded position) instead of page numbers. Ideal for real-time feeds where data changes frequently:

from rest_framework.pagination import CursorPagination

class PropertyFeedPagination(CursorPagination):
    page_size = 20
    cursor_query_param = 'cursor'
    ordering = '-created_at'  # Must be stable, unique ordering

class PropertyFeedViewSet(viewsets.ReadOnlyModelViewSet):
    pagination_class = PropertyFeedPagination
    # Response: {"next": "?cursor=cD0yMDIz...", "previous": null, "results": [...]}

Use CursorPagination when: - Feed updates frequently (new items inserted between pages) - PageNumber pagination would skip/repeat items as data changes - You don't need "jump to page N" functionality

104. How do you set a custom page size per request?

class FlexiblePagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = 'page_size'  # Enable client to set size
    max_page_size = 100                  # Cap at 100

# Client: GET /properties/?page=2&page_size=50

105. How do you override `get_paginated_response()` to customize output?

class CustomPagination(PageNumberPagination):
    page_size = 20

    def get_paginated_response(self, data):
        return Response({
            'meta': {
                'total_count': self.page.paginator.count,
                'total_pages': self.page.paginator.num_pages,
                'current_page': self.page.number,
                'page_size': self.page_size,
                'has_next': self.page.has_next(),
                'has_previous': self.page.has_previous(),
            },
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link(),
            },
            'results': data
        })

106. How do you add pagination to a non-ModelViewSet view?

class PropertySearchView(APIView):
    pagination_class = PageNumberPagination

    def get(self, request):
        queryset = Property.objects.filter(
            title__icontains=request.query_params.get('q', '')
        )
        paginator = self.pagination_class()
        paginator.page_size = 20

        page = paginator.paginate_queryset(queryset, request)
        serializer = PropertySerializer(page, many=True)
        return paginator.get_paginated_response(serializer.data)

107. What is the `filter_queryset()` method in `GenericAPIView`?

It applies all configured filter backends to the queryset. Called automatically in list() but must be called manually in custom actions:

class PropertyViewSet(viewsets.ModelViewSet):
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]

    @action(detail=False, methods=['get'])
    def export(self, request):
        # Must manually call filter_queryset to apply filters
        queryset = self.filter_queryset(self.get_queryset())
        # Now export the filtered data
        return generate_csv_response(queryset)

108. How do you implement search across related fields?

class PropertyViewSet(viewsets.ModelViewSet):
    filter_backends = [SearchFilter]
    search_fields = [
        'title',
        'description',
        'location__city',          # Related model field
        'location__district',
        'owner__username',         # Related model field
        'owner__company_name',
    ]

For complex search (full-text, fuzzy), use PostgreSQL's search:

from django.contrib.postgres.search import SearchVector, SearchQuery

class PropertyViewSet(viewsets.ModelViewSet):
    def get_queryset(self):
        qs = Property.objects.all()
        q = self.request.query_params.get('q')
        if q:
            qs = qs.annotate(
                search=SearchVector('title', 'description', 'location__city')
            ).filter(search=SearchQuery(q))
        return qs

109. How do you combine multiple filter backends on the same view?

from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend

class PropertyViewSet(viewsets.ModelViewSet):
    filter_backends = [
        DjangoFilterBackend,  # ?city=Munich&property_type=apartment
        SearchFilter,         # ?search=modern kitchen
        OrderingFilter,       # ?ordering=-price
    ]
    filterset_fields = ['city', 'property_type', 'is_available']
    search_fields = ['title', 'description']
    ordering_fields = ['price', 'area_sqm', 'created_at']
    ordering = ['-created_at']

# All work together:
# GET /properties/?city=Munich&search=balcony&ordering=-price&page=2

110. What are `lookup_field` and `lookup_url_kwarg`?

- lookup_field — model field used to look up a single object (default: pk) - lookup_url_kwarg — URL parameter name (default: same as lookup_field)

# Use slug instead of pk for URLs
class PropertyViewSet(viewsets.ModelViewSet):
    lookup_field = 'slug'       # Looks up by Property.slug
    lookup_url_kwarg = 'slug'   # URL: /properties/<slug>/

# urls.py with router — generates: /properties/<slug>/
router.register('properties', PropertyViewSet)

# Custom URL kwarg name different from field name
class PropertyViewSet(viewsets.ModelViewSet):
    lookup_field = 'slug'
    lookup_url_kwarg = 'property_slug'  # URL: /properties/<property_slug>/

113. How do you name router-generated URLs for use in `reverse()`?

router = DefaultRouter()
router.register('properties', PropertyViewSet, basename='property')

# DRF generates these URL names:
# property-list      → /properties/
# property-detail    → /properties/{pk}/
# property-{action}  → /properties/{pk}/custom-action/

# Using reverse:
from rest_framework.reverse import reverse

url = reverse('property-list', request=request)
url = reverse('property-detail', args=[property.pk], request=request)

114. When would you NOT use a router and write URLs manually?

- Non-standard URL patterns (nested resources without a nested router) - Function-based views with @api_view - When you need full control over URL names - Complex permission-based URL structures

# Manual — full control
urlpatterns = [
    path('my-properties/', PropertyListView.as_view(), name='my-property-list'),
    path('properties/<int:pk>/publish/', PropertyPublishView.as_view()),
    path('search/', PropertySearchView.as_view(), name='property-search'),
]

115. How do custom `@action` decorators affect router-generated URLs?

class PropertyViewSet(viewsets.ModelViewSet):
    @action(detail=True, methods=['post'], url_path='publish', url_name='publish')
    def publish(self, request, pk=None): ...

    @action(detail=False, methods=['get'], url_path='featured', url_name='featured')
    def featured(self, request): ...

# Generated URLs:
# POST /properties/{pk}/publish/  → name: property-publish
# GET  /properties/featured/      → name: property-featured

# reverse:
reverse('property-publish', args=[pk])
reverse('property-featured')

116. How do you version your API using URL-based versioning?

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION': 'v1',
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    'VERSION_PARAM': 'version',
}

# urls.py
urlpatterns = [
    path('api/<str:version>/', include('api.urls')),
]

# In view — access version
class PropertyViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        if self.request.version == 'v2':
            return PropertySerializerV2
        return PropertySerializer

117. How do you use `APIClient` to make requests in tests?

from rest_framework.test import APIClient

client = APIClient()

# GET
response = client.get('/api/properties/')

# POST with JSON
response = client.post('/api/properties/', {'title': 'Test', 'price': 200000}, format='json')

# POST with multipart (file upload)
with open('image.jpg', 'rb') as f:
    response = client.post('/api/upload/', {'image': f}, format='multipart')

# With auth
client.force_authenticate(user=user)
# or
client.credentials(HTTP_AUTHORIZATION='Bearer <token>')

118. How do you authenticate a user in a DRF test?

Three ways — use force_authenticate for most tests:

class PropertyTests(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user('alok', password='pass123')

    def test_with_force_authenticate(self):
        self.client.force_authenticate(user=self.user)
        # Now all requests act as self.user

    def test_with_token(self):
        token = Token.objects.create(user=self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Token {token.key}')

    def test_unauthenticated(self):
        self.client.force_authenticate(user=None)  # Reset to anon

119. How do you test file uploads in DRF?

from django.test import override_settings
from django.core.files.uploadedfile import SimpleUploadedFile

class UploadTests(APITestCase):
    @override_settings(MEDIA_ROOT='/tmp/test-media/')
    def test_upload_image(self):
        self.client.force_authenticate(user=self.user)

        image = SimpleUploadedFile(
            'property.jpg',
            b'fake-image-content',
            content_type='image/jpeg'
        )

        response = self.client.post(
            '/api/images/',
            {'property': 1, 'image': image},
            format='multipart'
        )
        self.assertEqual(response.status_code, 201)

120. How do you assert specific response status codes and data?

from rest_framework import status

class PropertyTests(APITestCase):
    def test_create_property(self):
        self.client.force_authenticate(user=self.user)
        data = {'title': 'Luxury Flat', 'price': 450000, 'city': 'Munich'}
        response = self.client.post('/api/properties/', data, format='json')

        # Status code
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        # Response data
        self.assertEqual(response.data['title'], 'Luxury Flat')
        self.assertEqual(response.data['price'], '450000.00')
        self.assertIn('id', response.data)

        # Database
        self.assertEqual(Property.objects.count(), 1)
        self.assertEqual(Property.objects.first().owner, self.user)

122. How do you test custom permissions?

class IsOwnerPermissionTest(APITestCase):
    def setUp(self):
        self.owner = User.objects.create_user('owner', password='pass')
        self.other = User.objects.create_user('other', password='pass')
        self.property = Property.objects.create(
            title='My Property', owner=self.owner
        )

    def test_owner_can_update(self):
        self.client.force_authenticate(user=self.owner)
        response = self.client.patch(
            f'/api/properties/{self.property.pk}/',
            {'title': 'Updated Title'}
        )
        self.assertEqual(response.status_code, 200)

    def test_non_owner_cannot_update(self):
        self.client.force_authenticate(user=self.other)
        response = self.client.patch(
            f'/api/properties/{self.property.pk}/',
            {'title': 'Hacked Title'}
        )
        self.assertEqual(response.status_code, 403)

123. How do you test throttling behavior?

from unittest.mock import patch
from django.core.cache import cache

class ThrottleTests(APITestCase):
    def setUp(self):
        cache.clear()  # Clear throttle cache before each test

    @override_settings(
        REST_FRAMEWORK={
            'DEFAULT_THROTTLE_CLASSES': ['rest_framework.throttling.AnonRateThrottle'],
            'DEFAULT_THROTTLE_RATES': {'anon': '3/minute'}
        }
    )
    def test_anon_throttle(self):
        for i in range(3):
            response = self.client.get('/api/properties/')
            self.assertEqual(response.status_code, 200)

        # 4th request should be throttled
        response = self.client.get('/api/properties/')
        self.assertEqual(response.status_code, 429)  # Too Many Requests
        self.assertIn('Retry-After', response.headers)

124. How do you use `force_authenticate()` vs `credentials()`?

# force_authenticate — bypasses authentication completely, sets user directly
# Best for: most unit tests (fast, no token setup needed)
self.client.force_authenticate(user=self.user)

# credentials — sends actual auth headers, goes through auth classes
# Best for: testing authentication itself
token = Token.objects.create(user=self.user)
self.client.credentials(HTTP_AUTHORIZATION=f'Token {token.key}')

# Reset credentials
self.client.credentials()                    # Clear all credentials
self.client.force_authenticate(user=None)   # Force anonymous

125. How do you mock external API calls in DRF tests?

from unittest.mock import patch, MagicMock

class ImmoCheckTests(APITestCase):
    @patch('properties.services.openai_client.chat.completions.create')
    def test_ai_analysis(self, mock_openai):
        # Mock the OpenAI response
        mock_openai.return_value = MagicMock(
            choices=[MagicMock(message=MagicMock(content='{"score": 8.5}'))]
        )

        self.client.force_authenticate(user=self.user)
        response = self.client.post('/api/immocheck/', {'property_id': 1})

        self.assertEqual(response.status_code, 200)
        mock_openai.assert_called_once()

    @patch('requests.get')
    def test_external_data_fetch(self, mock_get):
        mock_get.return_value = MagicMock(
            status_code=200,
            json=lambda: {'population': 1500000, 'unemployment': 3.2}
        )
        # Test continues...

126. How do you test a ViewSet's custom `@action` endpoint?

class PropertyActionTests(APITestCase):
    def test_publish_action(self):
        property = Property.objects.create(
            title='Draft Property',
            owner=self.user,
            is_published=False
        )
        self.client.force_authenticate(user=self.user)

        response = self.client.post(f'/api/properties/{property.pk}/publish/')

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['status'], 'published')
        property.refresh_from_db()
        self.assertTrue(property.is_published)

127. How do you implement API versioning (URL, header, query param)?

# URL versioning (most common)
REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
}
# URLs: /api/v1/properties/, /api/v2/properties/

# Header versioning
REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
}
# Header: Accept: application/json; version=1.0

# Query param versioning
REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning',
    'VERSION_PARAM': 'version',
}
# URL: /api/properties/?version=v2

128. What are renderers in DRF and how do you create a custom one?

from rest_framework.renderers import BaseRenderer
import csv
import io

class CSVRenderer(BaseRenderer):
    media_type = 'text/csv'
    format = 'csv'

    def render(self, data, accepted_media_type=None, renderer_context=None):
        if not data:
            return ''
        output = io.StringIO()
        writer = csv.DictWriter(output, fieldnames=data[0].keys())
        writer.writeheader()
        writer.writerows(data)
        return output.getvalue()

class PropertyExportView(APIView):
    renderer_classes = [JSONRenderer, CSVRenderer]

    def get(self, request):
        properties = Property.objects.all()
        serializer = PropertySerializer(properties, many=True)
        return Response(serializer.data)
        # GET /export/ → JSON
        # GET /export/?format=csv → CSV (or Accept: text/csv header)

129. What is `JSONRenderer` vs `BrowsableAPIRenderer`?

- JSONRenderer — renders response as application/json. Used by all API clients. - BrowsableAPIRenderer — renders an interactive HTML page. Only for browsers in development.

# Show only in development
import os
if os.environ.get('ENVIRONMENT') == 'production':
    DEFAULT_RENDERER_CLASSES = ['rest_framework.renderers.JSONRenderer']
else:
    DEFAULT_RENDERER_CLASSES = [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ]

130. How do you implement a custom exception handler in DRF?

# utils/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.exceptions import ValidationError, NotFound, PermissionDenied
import logging

logger = logging.getLogger(__name__)

def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if response is not None:
        error_data = {
            'success': False,
            'error': {
                'status_code': response.status_code,
                'detail': response.data,
            }
        }
        # Log server errors
        if response.status_code >= 500:
            logger.error(f'Server error: {exc}', exc_info=True)

        response.data = error_data

    return response

# settings.py
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'utils.exceptions.custom_exception_handler'
}

131. How do you return standardized error responses across your API?

# Use DRF's built-in exception classes
from rest_framework.exceptions import (
    ValidationError, NotFound, PermissionDenied, NotAuthenticated
)

class PropertyView(APIView):
    def get(self, request, pk):
        try:
            property = Property.objects.get(pk=pk)
        except Property.DoesNotExist:
            raise NotFound({'error': 'Property not found', 'code': 'not_found'})

        if not property.is_active:
            raise PermissionDenied({'error': 'Property is inactive', 'code': 'inactive'})

        return Response(PropertySerializer(property).data)

Combined with a custom exception handler, this gives consistent error shapes across all endpoints.

132. How do you handle soft deletes in DRF?

# Model
class Property(models.Model):
    is_deleted = models.BooleanField(default=False)
    deleted_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        default_manager_name = 'objects'

# ViewSet
class PropertyViewSet(viewsets.ModelViewSet):
    def get_queryset(self):
        # Exclude soft-deleted by default
        return Property.objects.filter(is_deleted=False)

    def perform_destroy(self, instance):
        # Override destroy to soft-delete instead
        from django.utils import timezone
        instance.is_deleted = True
        instance.deleted_at = timezone.now()
        instance.save()
        # Note: returns 204 No Content as expected

133. How do you implement a bulk create endpoint?

class BulkPropertySerializer(serializers.ListSerializer):
    def create(self, validated_data):
        properties = [Property(**item) for item in validated_data]
        return Property.objects.bulk_create(properties)

class PropertySerializer(serializers.ModelSerializer):
    class Meta:
        model = Property
        fields = ['title', 'price', 'city']
        list_serializer_class = BulkPropertySerializer

class BulkCreateView(APIView):
    def post(self, request):
        # Accept list of objects
        serializer = PropertySerializer(data=request.data, many=True)
        if serializer.is_valid():
            serializer.save(owner=request.user)
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)

# POST /api/properties/bulk/
# [{"title": "Flat A", "price": 200000}, {"title": "Flat B", "price": 300000}]

135. How do you stream large query results in DRF?

import csv
from django.http import StreamingHttpResponse

class Echo:
    def write(self, value):
        return value

class PropertyCSVExportView(APIView):
    def get(self, request):
        def generate_rows():
            pseudo_buffer = Echo()
            writer = csv.writer(pseudo_buffer)
            yield writer.writerow(['ID', 'Title', 'Price', 'City'])

            # Stream in chunks — never loads all into memory
            for property in Property.objects.filter(is_active=True).iterator(chunk_size=500):
                yield writer.writerow([property.id, property.title, property.price, property.city])

        response = StreamingHttpResponse(
            generate_rows(),
            content_type='text/csv'
        )
        response['Content-Disposition'] = 'attachment; filename="properties.csv"'
        return response

136. How do you implement an endpoint that returns a file download?

import io
from django.http import FileResponse
from reportlab.pdfgen import canvas

class PropertyPDFView(APIView):
    def get(self, request, pk):
        property = get_object_or_404(Property, pk=pk)

        # Generate PDF in memory
        buffer = io.BytesIO()
        p = canvas.Canvas(buffer)
        p.drawString(100, 750, f"Property: {property.title}")
        p.drawString(100, 730, f"Price: €{property.price:,.2f}")
        p.showPage()
        p.save()
        buffer.seek(0)

        return FileResponse(
            buffer,
            as_attachment=True,
            filename=f'property_{pk}.pdf',
            content_type='application/pdf'
        )

137. How do you use DRF with Celery for async task triggering?

# tasks.py
from celery import shared_task

@shared_task
def generate_property_analysis(property_id, user_id):
    property = Property.objects.get(pk=property_id)
    analysis = call_openai_api(property)
    PropertyAnalysis.objects.create(property=property, data=analysis)

# views.py
class StartAnalysisView(APIView):
    def post(self, request, pk):
        property = get_object_or_404(Property, pk=pk, owner=request.user)

        # Queue async task — returns immediately
        task = generate_property_analysis.delay(pk, request.user.id)

        return Response({
            'task_id': task.id,
            'status': 'queued',
            'status_url': f'/api/tasks/{task.id}/'
        }, status=202)  # 202 Accepted

class TaskStatusView(APIView):
    def get(self, request, task_id):
        from celery.result import AsyncResult
        result = AsyncResult(task_id)
        return Response({
            'task_id': task_id,
            'status': result.status,
            'result': result.result if result.ready() else None,
        })

138. How do you document your DRF API with `drf-spectacular`?

pip install drf-spectacular
# settings.py
INSTALLED_APPS = [..., 'drf_spectacular']
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
    'TITLE': 'MyApp API',
    'DESCRIPTION': 'Property management SaaS API',
    'VERSION': '1.0.0',
}

# urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = [
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema')),
]
# Annotate views for better docs
from drf_spectacular.utils import extend_schema, OpenApiParameter

class PropertyViewSet(viewsets.ModelViewSet):
    @extend_schema(
        summary="List all properties",
        parameters=[
            OpenApiParameter('city', str, description='Filter by city'),
            OpenApiParameter('min_price', int, description='Minimum price'),
        ],
        responses={200: PropertySerializer(many=True)}
    )
    def list(self, request):
        return super().list(request)

139. How do you implement cursor-based pagination for a real-time feed?

from rest_framework.pagination import CursorPagination

class ActivityFeedPagination(CursorPagination):
    page_size = 20
    ordering = '-created_at'  # Must be a stable, unique field

class PropertyActivityViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = ActivitySerializer
    pagination_class = ActivityFeedPagination

    def get_queryset(self):
        return Activity.objects.filter(
            property__owner=self.request.user
        ).select_related('property', 'user')

# Response:
# {
#   "next": "http://api/activities/?cursor=cD0yMDIz...",
#   "previous": null,
#   "results": [...]
# }
# Cursor is an encoded position — no skip/repeat as new items are added

140. How does DRF work with Django channels for WebSocket APIs?

# consumers.py (Django Channels)
from channels.generic.websocket import AsyncWebsocketConsumer
from rest_framework.authtoken.models import Token
import json

class PropertyUpdatesConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        # Authenticate via token in query string
        token_key = self.scope['query_string'].decode().split('=')[1]
        try:
            token = await Token.objects.aget(key=token_key)
            self.user = await token.auser()
        except Token.DoesNotExist:
            await self.close()
            return

        self.group_name = f'user_{self.user.id}_properties'
        await self.channel_layer.group_add(self.group_name, self.channel_name)
        await self.accept()

    async def property_update(self, event):
        await self.send(text_data=json.dumps(event['data']))

# Sending updates from DRF views:
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync

class PropertyViewSet(viewsets.ModelViewSet):
    def perform_update(self, serializer):
        instance = serializer.save()
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            f'user_{instance.owner.id}_properties',
            {'type': 'property.update', 'data': PropertySerializer(instance).data}
        )

141. How do you implement API rate limiting per plan tier?

from rest_framework.throttling import UserRateThrottle

class FreeTierThrottle(UserRateThrottle):
    scope = 'free'

class ProTierThrottle(UserRateThrottle):
    scope = 'pro'

class EnterpriseTierThrottle(UserRateThrottle):
    scope = 'enterprise'

class TierBasedThrottle(UserRateThrottle):
    def get_cache_key(self, request, view):
        if not request.user.is_authenticated:
            return None  # Let AnonRateThrottle handle this
        return f'throttle_user_{request.user.pk}'

    def allow_request(self, request, view):
        if not request.user.is_authenticated:
            return True
        plan = getattr(request.user, 'subscription_plan', 'free')
        rates = {'free': '10/day', 'pro': '1000/day', 'enterprise': '100000/day'}
        self.rate = rates.get(plan, '10/day')
        self.num_requests, self.duration = self.parse_rate(self.rate)
        return super().allow_request(request, view)

142. What are the common DRF anti-patterns to avoid?

# ❌ Anti-pattern 1: Logic in views instead of serializers
class PropertyView(APIView):
    def post(self, request):
        title = request.data.get('title', '').strip()
        if len(title) < 3:
            return Response({'error': 'Title too short'}, status=400)
        # ... more manual validation

# ✓ Put validation in serializers where it belongs

# ❌ Anti-pattern 2: N+1 queries
class PropertyViewSet(viewsets.ModelViewSet):
    queryset = Property.objects.all()  # No select_related → N+1

# ✓ Use select_related/prefetch_related

# ❌ Anti-pattern 3: Inconsistent error responses
return Response({'msg': 'Error'}, status=400)   # Some views
return Response({'detail': 'Error'}, status=400) # Other views

# ✓ Use a custom exception handler for consistency

# ❌ Anti-pattern 4: Ignoring object-level permissions
class PropertyView(APIView):
    permission_classes = [IsAuthenticated]
    def put(self, request, pk):
        property = Property.objects.get(pk=pk)
        # Missing: is request.user the owner?

# ✓ Always check object-level permissions

143. How do you implement a health check endpoint?

from django.db import connection
from django.core.cache import cache
import redis

class HealthCheckView(APIView):
    permission_classes = [AllowAny]
    authentication_classes = []

    def get(self, request):
        health = {'status': 'ok', 'checks': {}}

        # Database
        try:
            connection.ensure_connection()
            health['checks']['database'] = 'ok'
        except Exception as e:
            health['checks']['database'] = f'error: {str(e)}'
            health['status'] = 'degraded'

        # Cache (Redis)
        try:
            cache.set('health_check', '1', timeout=5)
            cache.get('health_check')
            health['checks']['cache'] = 'ok'
        except Exception as e:
            health['checks']['cache'] = f'error: {str(e)}'
            health['status'] = 'degraded'

        status_code = 200 if health['status'] == 'ok' else 503
        return Response(health, status=status_code)

144. How do you implement multi-language support in DRF error messages?

# DRF uses Django's i18n system for built-in error messages
# settings.py
USE_I18N = True
LANGUAGE_CODE = 'de'  # German default

# For custom messages:
from django.utils.translation import gettext_lazy as _

class PropertySerializer(serializers.ModelSerializer):
    def validate_price(self, value):
        if value <= 0:
            raise serializers.ValidationError(_('Der Preis muss positiv sein.'))
        return value

# In request: Accept-Language: de
# DRF automatically uses the right language for built-in errors

145. What is the difference between `Response` and `HttpResponse` in DRF?

from django.http import HttpResponse     # Django's basic response
from rest_framework.response import Response  # DRF's response

# HttpResponse — static, no content negotiation
def my_view(request):
    return HttpResponse(json.dumps({'key': 'value'}), content_type='application/json')
    # Always returns JSON, ignores Accept header

# Response — smart, content-negotiated
class MyView(APIView):
    def get(self, request):
        return Response({'key': 'value'})
        # Returns JSON, HTML, XML etc. based on Accept header and renderer classes
        # Data is NOT serialized yet at this point — happens later

146. How do you handle conditional requests (ETags, Last-Modified)?

from django.utils.http import http_date
import hashlib

class PropertyDetailView(APIView):
    def get(self, request, pk):
        property = get_object_or_404(Property, pk=pk)

        # Generate ETag from content hash
        data = PropertySerializer(property).data
        etag = hashlib.md5(str(data).encode()).hexdigest()
        last_modified = property.updated_at

        # Check If-None-Match
        if request.META.get('HTTP_IF_NONE_MATCH') == f'"{etag}"':
            return Response(status=304)

        response = Response(data)
        response['ETag'] = f'"{etag}"'
        response['Last-Modified'] = http_date(last_modified.timestamp())
        return response

147. How do you cache API responses in DRF?

from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_headers

class PropertyListView(generics.ListAPIView):
    @method_decorator(cache_page(60 * 15))           # Cache 15 minutes
    @method_decorator(vary_on_headers('Authorization'))  # Different cache per user
    def get(self, request, *args, **kwargs):
        return super().get(request, *args, **kwargs)

Or manually with Redis:

from django.core.cache import cache

class CityStatsView(APIView):
    def get(self, request, city):
        cache_key = f'city_stats_{city}'
        data = cache.get(cache_key)

        if data is None:
            data = compute_expensive_city_stats(city)
            cache.set(cache_key, data, timeout=3600)  # 1 hour

        return Response(data)

148. How do you implement request logging in DRF?

# middleware/logging.py
import time
import logging

logger = logging.getLogger('api')

class APILoggingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start = time.time()
        response = self.get_response(request)
        duration = time.time() - start

        if request.path.startswith('/api/'):
            logger.info(
                f'{request.method} {request.path} '
                f'status={response.status_code} '
                f'duration={duration:.3f}s '
                f'user={getattr(request.user, "id", "anon")}'
            )
        return response

# settings.py
MIDDLEWARE = [
    'middleware.logging.APILoggingMiddleware',
    ...
]

149. How do you implement HATEOAS (hypermedia links) in DRF?

HATEOAS means responses include links to related actions/resources:

class PropertySerializer(serializers.ModelSerializer):
    links = serializers.SerializerMethodField()

    class Meta:
        model = Property
        fields = ['id', 'title', 'price', 'links']

    def get_links(self, obj):
        request = self.context.get('request')
        return {
            'self': request.build_absolute_uri(f'/api/properties/{obj.pk}/'),
            'owner': request.build_absolute_uri(f'/api/users/{obj.owner_id}/'),
            'images': request.build_absolute_uri(f'/api/properties/{obj.pk}/images/'),
            'publish': request.build_absolute_uri(f'/api/properties/{obj.pk}/publish/'),
        }
# Response: {"id": 1, "title": "...", "links": {"self": "http://...", ...}}

This guide was designed for developers who want to build a deep, lasting understanding of Django REST Framework — not just pass an interview. Every question here is something that comes up in real production systems.