CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-djangorestframework

Web APIs for Django, made easy.

Pending
Overview
Eval results
Files

content-negotiation.mddocs/

Content Negotiation

Parser and renderer classes for handling multiple content types with automatic format detection and conversion in Django REST Framework.

Capabilities

Parser Classes

Parser classes handle incoming request data in different formats.

class BaseParser:
    """
    Base class for all parser implementations.
    """
    media_type = None  # Media type this parser handles
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parse incoming data stream.
        
        Args:
            stream: Input data stream
            media_type (str): Request content type
            parser_context (dict): Additional context
            
        Returns:
            Parsed data
            
        Raises:
            ParseError: If parsing fails
        """
        raise NotImplementedError

class JSONParser(BaseParser):
    """
    Parser for JSON data.
    """
    media_type = 'application/json'
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parse JSON data from stream.
        
        Returns:
            dict or list: Parsed JSON data
            
        Raises:
            ParseError: If JSON is malformed
        """

class FormParser(BaseParser):
    """
    Parser for HTML form data (application/x-www-form-urlencoded).
    """
    media_type = 'application/x-www-form-urlencoded'
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parse form-encoded data.
        
        Returns:
            QueryDict: Parsed form data
        """

class MultiPartParser(BaseParser):
    """
    Parser for multipart form data (multipart/form-data).
    """
    media_type = 'multipart/form-data'
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parse multipart form data including file uploads.
        
        Returns:
            DataAndFiles: Container with .data and .files attributes
        """

class FileUploadParser(BaseParser):
    """
    Parser for raw file uploads.
    """
    media_type = '*/*'  # Accepts any media type
    errors = {
        'unhandled': 'FileUploadParser can only handle a single file upload.',
        'no_filename': 'Missing filename. Request should include Content-Disposition header with filename parameter.',
    }
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parse raw file upload.
        
        Returns:
            dict: Dictionary with single file entry
            
        Raises:
            ParseError: If multiple files or no filename
        """

Renderer Classes

Renderer classes serialize data into different output formats.

class BaseRenderer:
    """
    Base class for all renderer implementations.
    """
    media_type = None     # Media type this renderer produces
    format = None         # Format name for URL suffixes
    charset = 'utf-8'     # Character encoding
    render_style = 'text' # Rendering style hint
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render data into output format.
        
        Args:
            data: Data to render
            accepted_media_type (str): Accepted media type from negotiation
            renderer_context (dict): Additional context
            
        Returns:
            bytes: Rendered output
        """
        raise NotImplementedError

class JSONRenderer(BaseRenderer):
    """
    Renderer for JSON output.
    """
    media_type = 'application/json'
    format = 'json'
    encoder_class = encoders.JSONEncoder
    ensure_ascii = True
    compact = True
    strict = True
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render data as JSON.
        
        Returns:
            bytes: JSON-encoded data
        """

class TemplateHTMLRenderer(BaseRenderer):
    """
    Renderer for HTML templates.
    """
    media_type = 'text/html'
    format = 'html'
    template_name = None
    exception_template_names = []
    charset = 'utf-8'
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render data using Django template.
        
        Returns:
            bytes: Rendered HTML
        """

class StaticHTMLRenderer(TemplateHTMLRenderer):
    """
    Renderer for static HTML content.
    """
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render static HTML string.
        
        Returns:
            bytes: HTML content as bytes
        """

class BrowsableAPIRenderer(BaseRenderer):
    """
    Renderer for browsable API interface.
    """
    media_type = 'text/html'
    format = 'api'
    template = 'rest_framework/api.html'
    filter_template = 'rest_framework/filters/base.html'
    code_style = 'emacs'
    charset = 'utf-8'
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render browsable API interface.
        
        Returns:
            bytes: HTML for browsable API
        """

class HTMLFormRenderer(BaseRenderer):
    """
    Renderer for HTML forms.
    """
    media_type = 'application/x-www-form-urlencoded'
    format = 'form'
    template_pack = 'rest_framework/vertical/'
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render HTML form for data input.
        
        Returns:
            str: HTML form markup
        """

class MultiPartRenderer(BaseRenderer):
    """
    Renderer for multipart form data.
    """
    media_type = 'multipart/form-data'
    format = 'multipart'
    BOUNDARY = 'BoUnDaRyStRiNg'
    charset = 'utf-8'
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render multipart form data.
        
        Returns:
            bytes: Multipart-encoded data
        """

class OpenAPIRenderer(BaseRenderer):
    """
    Renderer for OpenAPI schema format.
    """
    media_type = 'application/vnd.oai.openapi'
    format = 'openapi'
    charset = 'utf-8'
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render OpenAPI schema.
        
        Returns:
            bytes: YAML-formatted OpenAPI schema
        """

class JSONOpenAPIRenderer(BaseRenderer):
    """
    Renderer for OpenAPI schema in JSON format.
    """
    media_type = 'application/vnd.oai.openapi+json'
    format = 'openapi-json'
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render OpenAPI schema as JSON.
        
        Returns:
            bytes: JSON-formatted OpenAPI schema
        """

Content Negotiation

Classes that determine appropriate parser/renderer based on request.

class BaseContentNegotiation:
    """
    Base class for content negotiation.
    """
    def select_parser(self, request, parsers):
        """
        Select appropriate parser for request.
        
        Args:
            request: HTTP request
            parsers (list): Available parser instances
            
        Returns:
            Parser: Selected parser instance
            
        Raises:
            UnsupportedMediaType: If no suitable parser found
        """
        raise NotImplementedError
    
    def select_renderer(self, request, renderers, format_suffix=None):
        """
        Select appropriate renderer for response.
        
        Args:
            request: HTTP request
            renderers (list): Available renderer instances
            format_suffix (str): URL format suffix
            
        Returns:
            tuple: (renderer, media_type)
            
        Raises:
            NotAcceptable: If no acceptable renderer found
        """
        raise NotImplementedError

class DefaultContentNegotiation(BaseContentNegotiation):
    """
    Default content negotiation implementation.
    """
    settings = api_settings
    
    def select_parser(self, request, parsers):
        """
        Select parser based on Content-Type header.
        """
    
    def select_renderer(self, request, renderers, format_suffix=None):
        """
        Select renderer based on Accept header and format suffix.
        """
    
    def get_accept_list(self, request):
        """
        Parse Accept header into list of media types with priorities.
        
        Args:
            request: HTTP request
            
        Returns:
            list: Sorted list of (media_type, priority) tuples
        """
    
    def filter_renderers(self, renderers, format):
        """
        Filter renderers by format suffix.
        
        Args:
            renderers (list): Available renderers
            format (str): Format suffix
            
        Returns:
            list: Filtered renderers
        """

Utility Classes

Helper classes for content negotiation.

class DataAndFiles:
    """
    Container for parsed multipart data.
    """
    def __init__(self, data, files):
        """
        Args:
            data (dict): Form field data
            files (dict): Uploaded file data
        """
        self.data = data
        self.files = files

Usage Examples

Custom Parser

from rest_framework.parsers import BaseParser
from rest_framework.exceptions import ParseError
import yaml

class YAMLParser(BaseParser):
    """
    Parser for YAML data.
    """
    media_type = 'application/yaml'
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parse YAML data from stream.
        """
        try:
            data = stream.read()
            return yaml.safe_load(data)
        except yaml.YAMLError as exc:
            raise ParseError(f'YAML parse error: {exc}')

# Usage in view
from rest_framework.views import APIView

class YAMLView(APIView):
    parser_classes = [YAMLParser]
    
    def post(self, request):
        # request.data contains parsed YAML
        return Response({'received': request.data})

Custom Renderer

from rest_framework.renderers import BaseRenderer
import yaml

class YAMLRenderer(BaseRenderer):
    """
    Renderer for YAML output.
    """
    media_type = 'application/yaml'
    format = 'yaml'
    
    def render(self, data, accepted_media_type=None, renderer_context=None):
        """
        Render data as YAML.
        """
        return yaml.dump(data, default_flow_style=False).encode('utf-8')

# Usage in view
class YAMLView(APIView):
    renderer_classes = [YAMLRenderer]
    
    def get(self, request):
        data = {'message': 'Hello World'}
        return Response(data)

View-Level Parser/Renderer Configuration

from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer

class BookView(APIView):
    """
    View supporting multiple input and output formats.
    """
    parser_classes = [JSONParser, FormParser, MultiPartParser]
    renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
    
    def get(self, request):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        
        # Response format determined by Accept header or format suffix
        return Response(serializer.data)
    
    def post(self, request):
        # Input format determined by Content-Type header
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Global Parser/Renderer Configuration

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
        'myapp.parsers.YAMLParser',  # Custom parser
    ],
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
        'myapp.renderers.YAMLRenderer',  # Custom renderer
    ],
}

File Upload Handling

from rest_framework.parsers import FileUploadParser
from rest_framework.response import Response
from rest_framework import status

class FileUploadView(APIView):
    parser_classes = [FileUploadParser]
    
    def put(self, request, filename):
        """
        Handle raw file upload.
        """
        file_obj = request.data['file']
        
        # Process the uploaded file
        with open(f'/uploads/{filename}', 'wb') as destination:
            for chunk in file_obj.chunks():
                destination.write(chunk)
        
        return Response(
            {'message': f'File {filename} uploaded successfully'},
            status=status.HTTP_201_CREATED
        )

# Client usage:
# PUT /api/upload/document.pdf
# Content-Type: application/pdf
# Content-Disposition: attachment; filename="document.pdf"
# <binary file data>

HTML Template Rendering

class BookListView(APIView):
    renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
    
    def get(self, request):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        
        # For HTML requests, provide template context
        return Response({
            'books': serializer.data,
            'title': 'Book List'
        }, template_name='books/list.html')

# books/list.html template:
# <h1>{{ title }}</h1>
# {% for book in books %}
#   <div>{{ book.title }} by {{ book.author }}</div>
# {% endfor %}

Content Negotiation Based on Accept Header

class BookView(APIView):
    def get(self, request):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        
        # Check what format was requested
        if request.accepted_renderer.format == 'json':
            # JSON response
            return Response(serializer.data)
        elif request.accepted_renderer.format == 'html':
            # HTML response
            return Response(
                {'books': serializer.data},
                template_name='books/list.html'
            )
        else:
            # Default response
            return Response(serializer.data)

# Client requests:
# Accept: application/json -> JSON response
# Accept: text/html -> HTML response  
# Accept: */* -> Default format

Custom Content Negotiation

from rest_framework.content_negotiation import BaseContentNegotiation

class IgnoreClientContentNegotiation(BaseContentNegotiation):
    """
    Content negotiation that ignores client preferences.
    """
    def select_parser(self, request, parsers):
        """
        Always select the first parser.
        """
        return parsers[0]
    
    def select_renderer(self, request, renderers, format_suffix=None):
        """
        Always select JSON renderer if available.
        """
        for renderer in renderers:
            if renderer.format == 'json':
                return (renderer, renderer.media_type)
        
        # Fallback to first renderer
        return (renderers[0], renderers[0].media_type)

# Usage in view
class CustomNegotiationView(APIView):
    content_negotiation_class = IgnoreClientContentNegotiation
    
    def get(self, request):
        return Response({'message': 'Always JSON'})

Handling Parse Errors

from rest_framework.views import APIView
from rest_framework.exceptions import ParseError

class StrictJSONView(APIView):
    parser_classes = [JSONParser]
    
    def post(self, request):
        try:
            # request.data automatically parsed by JSONParser
            data = request.data
            
            if not isinstance(data, dict):
                raise ParseError("Expected JSON object")
            
            return Response({'received': data})
            
        except ParseError as e:
            return Response(
                {'error': str(e)},
                status=status.HTTP_400_BAD_REQUEST
            )

Utility Functions

def zero_as_none(value):
    """
    Convert zero values to None for rendering.
    
    Args:
        value: Value to check
        
    Returns:
        None if value is zero, otherwise original value
    """

Install with Tessl CLI

npx tessl i tessl/pypi-djangorestframework

docs

auth-permissions.md

content-negotiation.md

decorators.md

fields-validation.md

generic-views.md

index.md

pagination-filtering.md

request-response.md

routers-urls.md

serializers.md

status-exceptions.md

testing.md

views-viewsets.md

tile.json