django-rest-framework/community/3.0-announcement/index.html

4791 lines
132 KiB
HTML

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Django REST framework - Web APIs for Django">
<link rel="canonical" href="https://www.django-rest-framework.org/community/3.0-announcement/">
<link rel="prev" href="../3.1-announcement/">
<link rel="next" href="../kickstarter-announcement/">
<link rel="icon" href="../../theme/img/favicon.ico">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.3">
<title>3.0 Announcement - Django REST framework</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../../theme/stylesheets/extra.css">
<link rel="stylesheet" href="../../theme/stylesheets/prettify.css">
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="custom" data-md-color-accent="custom">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#django-rest-framework-30" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="Django REST framework" class="md-header__button md-logo" aria-label="Django REST framework" data-md-component="logo">
<img src="../../theme/img/logo.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Django REST framework
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
3.0 Announcement
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="(prefers-color-scheme)" data-md-color-scheme="default" data-md-color-primary="custom" data-md-color-accent="custom" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m14.3 16-.7-2h-3.2l-.7 2H7.8L11 7h2l3.2 9zM20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12zm-9.15 3.96h2.3L12 9z"/></svg>
</label>
<input class="md-option" data-md-color-media="(prefers-color-scheme: light)" data-md-color-scheme="default" data-md-color-primary="custom" data-md-color-accent="custom" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_2" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
<input class="md-option" data-md-color-media="(prefers-color-scheme: dark)" data-md-color-scheme="slate" data-md-color-primary="custom" data-md-color-accent="custom" aria-label="Switch to system preference" type="radio" name="__palette" id="__palette_2">
<label class="md-header__button md-icon" title="Switch to system preference" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/encode/django-rest-framework" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
</nav>
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../../tutorial/quickstart/" class="md-tabs__link">
Tutorial
</a>
</li>
<li class="md-tabs__item">
<a href="../../api-guide/requests/" class="md-tabs__link">
API Guide
</a>
</li>
<li class="md-tabs__item">
<a href="../../topics/documenting-your-api/" class="md-tabs__link">
Topics
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../tutorials-and-resources/" class="md-tabs__link">
Community
</a>
</li>
</ul>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="Django REST framework" class="md-nav__button md-logo" aria-label="Django REST framework" data-md-component="logo">
<img src="../../theme/img/logo.png" alt="logo">
</a>
Django REST framework
</label>
<div class="md-nav__source">
<a href="https://github.com/encode/django-rest-framework" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" >
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<span class="md-ellipsis">
Tutorial
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Tutorial
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../tutorial/quickstart/" class="md-nav__link">
<span class="md-ellipsis">
Quickstart
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../tutorial/1-serialization/" class="md-nav__link">
<span class="md-ellipsis">
1 - Serialization
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../tutorial/2-requests-and-responses/" class="md-nav__link">
<span class="md-ellipsis">
2 - Requests and responses
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../tutorial/3-class-based-views/" class="md-nav__link">
<span class="md-ellipsis">
3 - Class based views
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../tutorial/4-authentication-and-permissions/" class="md-nav__link">
<span class="md-ellipsis">
4 - Authentication and permissions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../tutorial/5-relationships-and-hyperlinked-apis/" class="md-nav__link">
<span class="md-ellipsis">
5 - Relationships and hyperlinked APIs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../tutorial/6-viewsets-and-routers/" class="md-nav__link">
<span class="md-ellipsis">
6 - Viewsets and routers
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" >
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
<span class="md-ellipsis">
API Guide
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
API Guide
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../api-guide/requests/" class="md-nav__link">
<span class="md-ellipsis">
Requests
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/responses/" class="md-nav__link">
<span class="md-ellipsis">
Responses
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/views/" class="md-nav__link">
<span class="md-ellipsis">
Views
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/generic-views/" class="md-nav__link">
<span class="md-ellipsis">
Generic views
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/viewsets/" class="md-nav__link">
<span class="md-ellipsis">
Viewsets
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/routers/" class="md-nav__link">
<span class="md-ellipsis">
Routers
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/parsers/" class="md-nav__link">
<span class="md-ellipsis">
Parsers
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/renderers/" class="md-nav__link">
<span class="md-ellipsis">
Renderers
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/serializers/" class="md-nav__link">
<span class="md-ellipsis">
Serializers
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/fields/" class="md-nav__link">
<span class="md-ellipsis">
Serializer fields
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/relations/" class="md-nav__link">
<span class="md-ellipsis">
Serializer relations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/validators/" class="md-nav__link">
<span class="md-ellipsis">
Validators
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/permissions/" class="md-nav__link">
<span class="md-ellipsis">
Permissions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/caching/" class="md-nav__link">
<span class="md-ellipsis">
Caching
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/throttling/" class="md-nav__link">
<span class="md-ellipsis">
Throttling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/filtering/" class="md-nav__link">
<span class="md-ellipsis">
Filtering
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/pagination/" class="md-nav__link">
<span class="md-ellipsis">
Pagination
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/versioning/" class="md-nav__link">
<span class="md-ellipsis">
Versioning
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/content-negotiation/" class="md-nav__link">
<span class="md-ellipsis">
Content negotiation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/metadata/" class="md-nav__link">
<span class="md-ellipsis">
Metadata
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/schemas/" class="md-nav__link">
<span class="md-ellipsis">
Schemas
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/format-suffixes/" class="md-nav__link">
<span class="md-ellipsis">
Format suffixes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/reverse/" class="md-nav__link">
<span class="md-ellipsis">
Returning URLs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/exceptions/" class="md-nav__link">
<span class="md-ellipsis">
Exceptions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/status-codes/" class="md-nav__link">
<span class="md-ellipsis">
Status codes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api-guide/settings/" class="md-nav__link">
<span class="md-ellipsis">
Settings
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_4" >
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="0">
<span class="md-ellipsis">
Topics
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_4">
<span class="md-nav__icon md-icon"></span>
Topics
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../topics/documenting-your-api/" class="md-nav__link">
<span class="md-ellipsis">
Documenting your API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../topics/internationalization/" class="md-nav__link">
<span class="md-ellipsis">
Internationalization
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../topics/ajax-csrf-cors/" class="md-nav__link">
<span class="md-ellipsis">
AJAX, CSRF & CORS
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../topics/html-and-forms/" class="md-nav__link">
<span class="md-ellipsis">
HTML & Forms
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../topics/browser-enhancements/" class="md-nav__link">
<span class="md-ellipsis">
Browser Enhancements
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../topics/browsable-api/" class="md-nav__link">
<span class="md-ellipsis">
The Browsable API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../topics/rest-hypermedia-hateoas/" class="md-nav__link">
<span class="md-ellipsis">
REST, Hypermedia & HATEOAS
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" checked>
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
<span class="md-ellipsis">
Community
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Community
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../tutorials-and-resources/" class="md-nav__link">
<span class="md-ellipsis">
Tutorials and Resources
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../third-party-packages/" class="md-nav__link">
<span class="md-ellipsis">
Third Party Packages
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../contributing/" class="md-nav__link">
<span class="md-ellipsis">
Contributing to REST framework
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../project-management/" class="md-nav__link">
<span class="md-ellipsis">
Project management
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../release-notes/" class="md-nav__link">
<span class="md-ellipsis">
Release Notes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.16-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.16 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.15-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.15 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.14-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.14 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.13-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.13 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.12-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.12 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.11-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.11 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.10-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.10 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.9-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.9 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.8-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.8 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.7-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.7 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.6-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.6 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.5-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.5 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.4-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.4 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.3-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.3 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.2-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.2 Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3.1-announcement/" class="md-nav__link">
<span class="md-ellipsis">
3.1 Announcement
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
3.0 Announcement
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
3.0 Announcement
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#new-features" class="md-nav__link">
<span class="md-ellipsis">
New features
</span>
</a>
<nav class="md-nav" aria-label="New features">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#rest-framework-under-the-hood" class="md-nav__link">
<span class="md-ellipsis">
REST framework: Under the hood.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#request-objects" class="md-nav__link">
<span class="md-ellipsis">
Request objects
</span>
</a>
<nav class="md-nav" aria-label="Request objects">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-data-and-query_params-properties" class="md-nav__link">
<span class="md-ellipsis">
The .data and .query_params properties.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serializers" class="md-nav__link">
<span class="md-ellipsis">
Serializers
</span>
</a>
<nav class="md-nav" aria-label="Serializers">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#single-step-object-creation" class="md-nav__link">
<span class="md-ellipsis">
Single-step object creation.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-create-and-update-methods" class="md-nav__link">
<span class="md-ellipsis">
The .create() and .update() methods.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use-validated_data-instead-of-object" class="md-nav__link">
<span class="md-ellipsis">
Use .validated_data instead of .object.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#using-is_validraise_exceptiontrue" class="md-nav__link">
<span class="md-ellipsis">
Using .is_valid(raise_exception=True)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#using-serializersvalidationerror" class="md-nav__link">
<span class="md-ellipsis">
Using serializers.ValidationError.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#change-to-validate_field_name" class="md-nav__link">
<span class="md-ellipsis">
Change to validate_&lt;field_name&gt;.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#removal-of-transform_field_name" class="md-nav__link">
<span class="md-ellipsis">
Removal of transform_&lt;field_name&gt;.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#differences-between-modelserializer-validation-and-modelform" class="md-nav__link">
<span class="md-ellipsis">
Differences between ModelSerializer validation and ModelForm.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#writable-nested-serialization" class="md-nav__link">
<span class="md-ellipsis">
Writable nested serialization.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#printable-serializer-representations" class="md-nav__link">
<span class="md-ellipsis">
Printable serializer representations.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-extra_kwargs-option" class="md-nav__link">
<span class="md-ellipsis">
The extra_kwargs option.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#changes-to-hyperlinkedmodelserializer" class="md-nav__link">
<span class="md-ellipsis">
Changes to HyperlinkedModelSerializer.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#fields-for-model-methods-and-properties" class="md-nav__link">
<span class="md-ellipsis">
Fields for model methods and properties.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-listserializer-class" class="md-nav__link">
<span class="md-ellipsis">
The ListSerializer class.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-baseserializer-class" class="md-nav__link">
<span class="md-ellipsis">
The BaseSerializer class.
</span>
</a>
<nav class="md-nav" aria-label="The BaseSerializer class.">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#read-only-baseserializer-classes" class="md-nav__link">
<span class="md-ellipsis">
Read-only BaseSerializer classes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#read-write-baseserializer-classes" class="md-nav__link">
<span class="md-ellipsis">
Read-write BaseSerializer classes.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#creating-new-generic-serializers-with-baseserializer" class="md-nav__link">
<span class="md-ellipsis">
Creating new generic serializers with BaseSerializer.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serializer-fields" class="md-nav__link">
<span class="md-ellipsis">
Serializer fields
</span>
</a>
<nav class="md-nav" aria-label="Serializer fields">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-field-and-readonly-field-classes" class="md-nav__link">
<span class="md-ellipsis">
The Field and ReadOnly field classes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-required-allow_null-allow_blank-and-default-arguments" class="md-nav__link">
<span class="md-ellipsis">
The required, allow_null, allow_blank and default arguments.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#coercing-output-types" class="md-nav__link">
<span class="md-ellipsis">
Coercing output types.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#removal-of-validate" class="md-nav__link">
<span class="md-ellipsis">
Removal of .validate().
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-listfield-class" class="md-nav__link">
<span class="md-ellipsis">
The ListField class.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-choicefield-class-may-now-accept-a-flat-list" class="md-nav__link">
<span class="md-ellipsis">
The ChoiceField class may now accept a flat list.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-multiplechoicefield-class" class="md-nav__link">
<span class="md-ellipsis">
The MultipleChoiceField class.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#changes-to-the-custom-field-api" class="md-nav__link">
<span class="md-ellipsis">
Changes to the custom field API.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#explicit-queryset-required-on-relational-fields" class="md-nav__link">
<span class="md-ellipsis">
Explicit queryset required on relational fields.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#optional-argument-to-serializermethodfield" class="md-nav__link">
<span class="md-ellipsis">
Optional argument to SerializerMethodField.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#enforcing-consistent-source-usage" class="md-nav__link">
<span class="md-ellipsis">
Enforcing consistent source usage.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-uniquevalidator-and-uniquetogethervalidator-classes" class="md-nav__link">
<span class="md-ellipsis">
The UniqueValidator and UniqueTogetherValidator classes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-uniquefordatevalidator-classes" class="md-nav__link">
<span class="md-ellipsis">
The UniqueForDateValidator classes.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#generic-views" class="md-nav__link">
<span class="md-ellipsis">
Generic views
</span>
</a>
<nav class="md-nav" aria-label="Generic views">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#simplification-of-view-logic" class="md-nav__link">
<span class="md-ellipsis">
Simplification of view logic.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#changes-to-prepost-save-hooks" class="md-nav__link">
<span class="md-ellipsis">
Changes to pre/post save hooks.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#removal-of-view-attributes" class="md-nav__link">
<span class="md-ellipsis">
Removal of view attributes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#put-as-create" class="md-nav__link">
<span class="md-ellipsis">
PUT as create.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#customizing-error-responses" class="md-nav__link">
<span class="md-ellipsis">
Customizing error responses.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#the-metadata-api" class="md-nav__link">
<span class="md-ellipsis">
The metadata API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#serializers-as-html-forms" class="md-nav__link">
<span class="md-ellipsis">
Serializers as HTML forms
</span>
</a>
<nav class="md-nav" aria-label="Serializers as HTML forms">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-style-keyword-argument-for-serializer-fields" class="md-nav__link">
<span class="md-ellipsis">
The style keyword argument for serializer fields.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-style" class="md-nav__link">
<span class="md-ellipsis">
API style
</span>
</a>
<nav class="md-nav" aria-label="API style">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#unicode-json-by-default" class="md-nav__link">
<span class="md-ellipsis">
Unicode JSON by default.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#compact-json-by-default" class="md-nav__link">
<span class="md-ellipsis">
Compact JSON by default.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#file-fields-as-urls" class="md-nav__link">
<span class="md-ellipsis">
File fields as URLs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#throttle-headers-using-retry-after" class="md-nav__link">
<span class="md-ellipsis">
Throttle headers using Retry-After.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#date-and-time-objects-as-iso-8601-strings-in-serializer-data" class="md-nav__link">
<span class="md-ellipsis">
Date and time objects as ISO-8601 strings in serializer data.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#decimals-as-strings-in-serializer-data" class="md-nav__link">
<span class="md-ellipsis">
Decimals as strings in serializer data.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#miscellaneous-notes" class="md-nav__link">
<span class="md-ellipsis">
Miscellaneous notes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#whats-coming-next" class="md-nav__link">
<span class="md-ellipsis">
What's coming next
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../kickstarter-announcement/" class="md-nav__link">
<span class="md-ellipsis">
Kickstarter Announcement
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mozilla-grant/" class="md-nav__link">
<span class="md-ellipsis">
Mozilla Grant
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../jobs/" class="md-nav__link">
<span class="md-ellipsis">
Jobs
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#new-features" class="md-nav__link">
<span class="md-ellipsis">
New features
</span>
</a>
<nav class="md-nav" aria-label="New features">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#rest-framework-under-the-hood" class="md-nav__link">
<span class="md-ellipsis">
REST framework: Under the hood.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#request-objects" class="md-nav__link">
<span class="md-ellipsis">
Request objects
</span>
</a>
<nav class="md-nav" aria-label="Request objects">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-data-and-query_params-properties" class="md-nav__link">
<span class="md-ellipsis">
The .data and .query_params properties.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serializers" class="md-nav__link">
<span class="md-ellipsis">
Serializers
</span>
</a>
<nav class="md-nav" aria-label="Serializers">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#single-step-object-creation" class="md-nav__link">
<span class="md-ellipsis">
Single-step object creation.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-create-and-update-methods" class="md-nav__link">
<span class="md-ellipsis">
The .create() and .update() methods.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use-validated_data-instead-of-object" class="md-nav__link">
<span class="md-ellipsis">
Use .validated_data instead of .object.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#using-is_validraise_exceptiontrue" class="md-nav__link">
<span class="md-ellipsis">
Using .is_valid(raise_exception=True)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#using-serializersvalidationerror" class="md-nav__link">
<span class="md-ellipsis">
Using serializers.ValidationError.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#change-to-validate_field_name" class="md-nav__link">
<span class="md-ellipsis">
Change to validate_&lt;field_name&gt;.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#removal-of-transform_field_name" class="md-nav__link">
<span class="md-ellipsis">
Removal of transform_&lt;field_name&gt;.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#differences-between-modelserializer-validation-and-modelform" class="md-nav__link">
<span class="md-ellipsis">
Differences between ModelSerializer validation and ModelForm.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#writable-nested-serialization" class="md-nav__link">
<span class="md-ellipsis">
Writable nested serialization.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#printable-serializer-representations" class="md-nav__link">
<span class="md-ellipsis">
Printable serializer representations.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-extra_kwargs-option" class="md-nav__link">
<span class="md-ellipsis">
The extra_kwargs option.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#changes-to-hyperlinkedmodelserializer" class="md-nav__link">
<span class="md-ellipsis">
Changes to HyperlinkedModelSerializer.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#fields-for-model-methods-and-properties" class="md-nav__link">
<span class="md-ellipsis">
Fields for model methods and properties.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-listserializer-class" class="md-nav__link">
<span class="md-ellipsis">
The ListSerializer class.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-baseserializer-class" class="md-nav__link">
<span class="md-ellipsis">
The BaseSerializer class.
</span>
</a>
<nav class="md-nav" aria-label="The BaseSerializer class.">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#read-only-baseserializer-classes" class="md-nav__link">
<span class="md-ellipsis">
Read-only BaseSerializer classes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#read-write-baseserializer-classes" class="md-nav__link">
<span class="md-ellipsis">
Read-write BaseSerializer classes.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#creating-new-generic-serializers-with-baseserializer" class="md-nav__link">
<span class="md-ellipsis">
Creating new generic serializers with BaseSerializer.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serializer-fields" class="md-nav__link">
<span class="md-ellipsis">
Serializer fields
</span>
</a>
<nav class="md-nav" aria-label="Serializer fields">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-field-and-readonly-field-classes" class="md-nav__link">
<span class="md-ellipsis">
The Field and ReadOnly field classes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-required-allow_null-allow_blank-and-default-arguments" class="md-nav__link">
<span class="md-ellipsis">
The required, allow_null, allow_blank and default arguments.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#coercing-output-types" class="md-nav__link">
<span class="md-ellipsis">
Coercing output types.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#removal-of-validate" class="md-nav__link">
<span class="md-ellipsis">
Removal of .validate().
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-listfield-class" class="md-nav__link">
<span class="md-ellipsis">
The ListField class.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-choicefield-class-may-now-accept-a-flat-list" class="md-nav__link">
<span class="md-ellipsis">
The ChoiceField class may now accept a flat list.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-multiplechoicefield-class" class="md-nav__link">
<span class="md-ellipsis">
The MultipleChoiceField class.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#changes-to-the-custom-field-api" class="md-nav__link">
<span class="md-ellipsis">
Changes to the custom field API.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#explicit-queryset-required-on-relational-fields" class="md-nav__link">
<span class="md-ellipsis">
Explicit queryset required on relational fields.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#optional-argument-to-serializermethodfield" class="md-nav__link">
<span class="md-ellipsis">
Optional argument to SerializerMethodField.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#enforcing-consistent-source-usage" class="md-nav__link">
<span class="md-ellipsis">
Enforcing consistent source usage.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-uniquevalidator-and-uniquetogethervalidator-classes" class="md-nav__link">
<span class="md-ellipsis">
The UniqueValidator and UniqueTogetherValidator classes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-uniquefordatevalidator-classes" class="md-nav__link">
<span class="md-ellipsis">
The UniqueForDateValidator classes.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#generic-views" class="md-nav__link">
<span class="md-ellipsis">
Generic views
</span>
</a>
<nav class="md-nav" aria-label="Generic views">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#simplification-of-view-logic" class="md-nav__link">
<span class="md-ellipsis">
Simplification of view logic.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#changes-to-prepost-save-hooks" class="md-nav__link">
<span class="md-ellipsis">
Changes to pre/post save hooks.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#removal-of-view-attributes" class="md-nav__link">
<span class="md-ellipsis">
Removal of view attributes.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#put-as-create" class="md-nav__link">
<span class="md-ellipsis">
PUT as create.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#customizing-error-responses" class="md-nav__link">
<span class="md-ellipsis">
Customizing error responses.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#the-metadata-api" class="md-nav__link">
<span class="md-ellipsis">
The metadata API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#serializers-as-html-forms" class="md-nav__link">
<span class="md-ellipsis">
Serializers as HTML forms
</span>
</a>
<nav class="md-nav" aria-label="Serializers as HTML forms">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-style-keyword-argument-for-serializer-fields" class="md-nav__link">
<span class="md-ellipsis">
The style keyword argument for serializer fields.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-style" class="md-nav__link">
<span class="md-ellipsis">
API style
</span>
</a>
<nav class="md-nav" aria-label="API style">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#unicode-json-by-default" class="md-nav__link">
<span class="md-ellipsis">
Unicode JSON by default.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#compact-json-by-default" class="md-nav__link">
<span class="md-ellipsis">
Compact JSON by default.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#file-fields-as-urls" class="md-nav__link">
<span class="md-ellipsis">
File fields as URLs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#throttle-headers-using-retry-after" class="md-nav__link">
<span class="md-ellipsis">
Throttle headers using Retry-After.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#date-and-time-objects-as-iso-8601-strings-in-serializer-data" class="md-nav__link">
<span class="md-ellipsis">
Date and time objects as ISO-8601 strings in serializer data.
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#decimals-as-strings-in-serializer-data" class="md-nav__link">
<span class="md-ellipsis">
Decimals as strings in serializer data.
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#miscellaneous-notes" class="md-nav__link">
<span class="md-ellipsis">
Miscellaneous notes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#whats-coming-next" class="md-nav__link">
<span class="md-ellipsis">
What's coming next
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<nav class="md-path" aria-label="Navigation" >
<ol class="md-path__list">
<li class="md-path__item">
<a href="../.." class="md-path__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-path__item">
<a href="../tutorials-and-resources/" class="md-path__link">
<span class="md-ellipsis">
Community
</span>
</a>
</li>
</ol>
</nav>
<article class="md-content__inner md-typeset">
<h1 id="django-rest-framework-30">Django REST framework 3.0<a class="headerlink" href="#django-rest-framework-30" title="Permanent link">&para;</a></h1>
<p>The 3.0 release of Django REST framework is the result of almost four years of iteration and refinement. It comprehensively addresses some of the previous remaining design issues in serializers, fields and the generic views.</p>
<p><strong>This release is incremental in nature. There <em>are</em> some breaking API changes, and upgrading <em>will</em> require you to read the release notes carefully, but the migration path should otherwise be relatively straightforward.</strong></p>
<p>The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier.</p>
<p>3.0 is the first of three releases that have been funded by our recent <a href="https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3">Kickstarter campaign</a>.</p>
<p>As ever, a huge thank you to our many <a href="https://www.django-rest-framework.org/community/kickstarter-announcement/#sponsors">wonderful sponsors</a>. If you're looking for a Django gig, and want to work with smart community-minded folks, you should probably check out that list and see who's hiring.</p>
<hr />
<h2 id="new-features">New features<a class="headerlink" href="#new-features" title="Permanent link">&para;</a></h2>
<p>Notable features of this new release include:</p>
<ul>
<li>Printable representations on serializers that allow you to inspect exactly what fields are present on the instance.</li>
<li>Simple model serializers that are vastly easier to understand and debug, and that make it easy to switch between the implicit <code>ModelSerializer</code> class and the explicit <code>Serializer</code> class.</li>
<li>A new <code>BaseSerializer</code> class, making it easier to write serializers for alternative storage backends, or to completely customize your serialization and validation logic.</li>
<li>A cleaner fields API including new classes such as <code>ListField</code> and <code>MultipleChoiceField</code>.</li>
<li><a href="https://github.com/encode/django-rest-framework/blob/main/rest_framework/mixins.py">Super simple default implementations</a> for the generic views.</li>
<li>Support for overriding how validation errors are handled by your API.</li>
<li>A metadata API that allows you to customize how <code>OPTIONS</code> requests are handled by your API.</li>
<li>A more compact JSON output with unicode style encoding turned on by default.</li>
<li>Templated based HTML form rendering for serializers. This will be finalized as public API in the upcoming 3.1 release.</li>
</ul>
<p>Significant new functionality continues to be planned for the 3.1 and 3.2 releases. These releases will correspond to the two <a href="https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3">Kickstarter stretch goals</a> - "Feature improvements" and "Admin interface". Further 3.x releases will present simple upgrades, without the same level of fundamental API changes necessary for the 3.0 release.</p>
<hr />
<h4 id="rest-framework-under-the-hood">REST framework: Under the hood.<a class="headerlink" href="#rest-framework-under-the-hood" title="Permanent link">&para;</a></h4>
<p>This talk from the <a href="https://www.djangounderthehood.com/">Django: Under the Hood</a> event in Amsterdam, Nov 2014, gives some good background context on the design decisions behind 3.0.</p>
<iframe style="display: block; margin: 0 auto 0 auto" width="560" height="315" src="//www.youtube.com/embed/3cSsbe-tA0E" frameborder="0" allowfullscreen></iframe>
<hr />
<p><em>Below is an in-depth guide to the API changes and migration notes for 3.0.</em></p>
<h2 id="request-objects">Request objects<a class="headerlink" href="#request-objects" title="Permanent link">&para;</a></h2>
<h4 id="the-data-and-query_params-properties">The <code>.data</code> and <code>.query_params</code> properties.<a class="headerlink" href="#the-data-and-query_params-properties" title="Permanent link">&para;</a></h4>
<p>The usage of <code>request.DATA</code> and <code>request.FILES</code> is now pending deprecation in favor of a single <code>request.data</code> attribute that contains <em>all</em> the parsed data.</p>
<p>Having separate attributes is reasonable for web applications that only ever parse url-encoded or multipart requests, but makes less sense for the general-purpose request parsing that REST framework supports.</p>
<p>You may now pass all the request data to a serializer class in a single argument:</p>
<div class="language-text highlight"><pre><span></span><code># Do this...
ExampleSerializer(data=request.data)
</code></pre></div>
<p>Instead of passing the files argument separately:</p>
<div class="language-text highlight"><pre><span></span><code># Don&#39;t do this...
ExampleSerializer(data=request.DATA, files=request.FILES)
</code></pre></div>
<p>The usage of <code>request.QUERY_PARAMS</code> is now pending deprecation in favor of the lowercased <code>request.query_params</code>.</p>
<hr />
<h2 id="serializers">Serializers<a class="headerlink" href="#serializers" title="Permanent link">&para;</a></h2>
<h4 id="single-step-object-creation">Single-step object creation.<a class="headerlink" href="#single-step-object-creation" title="Permanent link">&para;</a></h4>
<p>Previously the serializers used a two-step object creation, as follows:</p>
<ol>
<li>Validating the data would create an object instance. This instance would be available as <code>serializer.object</code>.</li>
<li>Calling <code>serializer.save()</code> would then save the object instance to the database.</li>
</ol>
<p>This style is in-line with how the <code>ModelForm</code> class works in Django, but is problematic for a number of reasons:</p>
<ul>
<li>Some data, such as many-to-many relationships, cannot be added to the object instance until after it has been saved. This type of data needed to be hidden in some undocumented state on the object instance, or kept as state on the serializer instance so that it could be used when <code>.save()</code> is called.</li>
<li>Instantiating model instances directly means that you cannot use model manager classes for instance creation, e.g. <code>ExampleModel.objects.create(...)</code>. Manager classes are an excellent layer at which to enforce business logic and application-level data constraints.</li>
<li>The two step process makes it unclear where to put deserialization logic. For example, should extra attributes such as the current user get added to the instance during object creation or during object save?</li>
</ul>
<p>We now use single-step object creation, like so:</p>
<ol>
<li>Validating the data makes the cleaned data available as <code>serializer.validated_data</code>.</li>
<li>Calling <code>serializer.save()</code> then saves and returns the new object instance.</li>
</ol>
<p>The resulting API changes are further detailed below.</p>
<h4 id="the-create-and-update-methods">The <code>.create()</code> and <code>.update()</code> methods.<a class="headerlink" href="#the-create-and-update-methods" title="Permanent link">&para;</a></h4>
<p>The <code>.restore_object()</code> method is now removed, and we instead have two separate methods, <code>.create()</code> and <code>.update()</code>. These methods work slightly different to the previous <code>.restore_object()</code>.</p>
<p>When using the <code>.create()</code> and <code>.update()</code> methods you should both create <em>and save</em> the object instance. This is in contrast to the previous <code>.restore_object()</code> behavior that would instantiate the object but not save it.</p>
<p>These methods also replace the optional <code>.save_object()</code> method, which no longer exists.</p>
<p>The following example from the tutorial previously used <code>restore_object()</code> to handle both creating and updating object instances.</p>
<div class="language-text highlight"><pre><span></span><code>def restore_object(self, attrs, instance=None):
if instance:
# Update existing instance
instance.title = attrs.get(&#39;title&#39;, instance.title)
instance.code = attrs.get(&#39;code&#39;, instance.code)
instance.linenos = attrs.get(&#39;linenos&#39;, instance.linenos)
instance.language = attrs.get(&#39;language&#39;, instance.language)
instance.style = attrs.get(&#39;style&#39;, instance.style)
return instance
# Create new instance
return Snippet(**attrs)
</code></pre></div>
<p>This would now be split out into two separate methods.</p>
<div class="language-text highlight"><pre><span></span><code>def update(self, instance, validated_data):
instance.title = validated_data.get(&#39;title&#39;, instance.title)
instance.code = validated_data.get(&#39;code&#39;, instance.code)
instance.linenos = validated_data.get(&#39;linenos&#39;, instance.linenos)
instance.language = validated_data.get(&#39;language&#39;, instance.language)
instance.style = validated_data.get(&#39;style&#39;, instance.style)
instance.save()
return instance
def create(self, validated_data):
return Snippet.objects.create(**validated_data)
</code></pre></div>
<p>Note that these methods should return the newly created object instance.</p>
<h4 id="use-validated_data-instead-of-object">Use <code>.validated_data</code> instead of <code>.object</code>.<a class="headerlink" href="#use-validated_data-instead-of-object" title="Permanent link">&para;</a></h4>
<p>You must now use the <code>.validated_data</code> attribute if you need to inspect the data before saving, rather than using the <code>.object</code> attribute, which no longer exists.</p>
<p>For example the following code <em>is no longer valid</em>:</p>
<div class="language-text highlight"><pre><span></span><code>if serializer.is_valid():
name = serializer.object.name # Inspect validated field data.
logging.info(&#39;Creating ticket &quot;%s&quot;&#39; % name)
serializer.object.user = request.user # Include the user when saving.
serializer.save()
</code></pre></div>
<p>Instead of using <code>.object</code> to inspect a partially constructed instance, you would now use <code>.validated_data</code> to inspect the cleaned incoming values. Also you can't set extra attributes on the instance directly, but instead pass them to the <code>.save()</code> method as keyword arguments.</p>
<p>The corresponding code would now look like this:</p>
<div class="language-text highlight"><pre><span></span><code>if serializer.is_valid():
name = serializer.validated_data[&#39;name&#39;] # Inspect validated field data.
logging.info(&#39;Creating ticket &quot;%s&quot;&#39; % name)
serializer.save(user=request.user) # Include the user when saving.
</code></pre></div>
<h4 id="using-is_validraise_exceptiontrue">Using <code>.is_valid(raise_exception=True)</code><a class="headerlink" href="#using-is_validraise_exceptiontrue" title="Permanent link">&para;</a></h4>
<p>The <code>.is_valid()</code> method now takes an optional boolean flag, <code>raise_exception</code>.</p>
<p>Calling <code>.is_valid(raise_exception=True)</code> will cause a <code>ValidationError</code> to be raised if the serializer data contains validation errors. This error will be handled by REST framework's default exception handler, allowing you to remove error response handling from your view code.</p>
<p>The handling and formatting of error responses may be altered globally by using the <code>EXCEPTION_HANDLER</code> settings key.</p>
<p>This change also means it's now possible to alter the style of error responses used by the built-in generic views, without having to include mixin classes or other overrides.</p>
<h4 id="using-serializersvalidationerror">Using <code>serializers.ValidationError</code>.<a class="headerlink" href="#using-serializersvalidationerror" title="Permanent link">&para;</a></h4>
<p>Previously <code>serializers.ValidationError</code> error was simply a synonym for <code>django.core.exceptions.ValidationError</code>. This has now been altered so that it inherits from the standard <code>APIException</code> base class.</p>
<p>The reason behind this is that Django's <code>ValidationError</code> class is intended for use with HTML forms and its API makes using it slightly awkward with nested validation errors that can occur in serializers.</p>
<p>For most users this change shouldn't require any updates to your codebase, but it is worth ensuring that whenever raising validation errors you should prefer using the <code>serializers.ValidationError</code> exception class, and not Django's built-in exception.</p>
<p>We strongly recommend that you use the namespaced import style of <code>import serializers</code> and not <code>from serializers import ValidationError</code> in order to avoid any potential confusion.</p>
<h4 id="change-to-validate_field_name">Change to <code>validate_&lt;field_name&gt;</code>.<a class="headerlink" href="#change-to-validate_field_name" title="Permanent link">&para;</a></h4>
<p>The <code>validate_&lt;field_name&gt;</code> method hooks that can be attached to serializer classes change their signature slightly and return type. Previously these would take a dictionary of all incoming data, and a key representing the field name, and would return a dictionary including the validated data for that field:</p>
<div class="language-text highlight"><pre><span></span><code>def validate_score(self, attrs, source):
if attrs[&#39;score&#39;] % 10 != 0:
raise serializers.ValidationError(&#39;This field should be a multiple of ten.&#39;)
return attrs
</code></pre></div>
<p>This is now simplified slightly, and the method hooks simply take the value to be validated, and return the validated value.</p>
<div class="language-text highlight"><pre><span></span><code>def validate_score(self, value):
if value % 10 != 0:
raise serializers.ValidationError(&#39;This field should be a multiple of ten.&#39;)
return value
</code></pre></div>
<p>Any ad-hoc validation that applies to more than one field should go in the <code>.validate(self, attrs)</code> method as usual.</p>
<p>Because <code>.validate_&lt;field_name&gt;</code> would previously accept the complete dictionary of attributes, it could be used to validate a field depending on the input in another field. Now if you need to do this you should use <code>.validate()</code> instead.</p>
<p>You can either return <code>non_field_errors</code> from the validate method by raising a simple <code>ValidationError</code></p>
<div class="language-text highlight"><pre><span></span><code>def validate(self, attrs):
# serializer.errors == {&#39;non_field_errors&#39;: [&#39;A non field error&#39;]}
raise serializers.ValidationError(&#39;A non field error&#39;)
</code></pre></div>
<p>Alternatively if you want the errors to be against a specific field, use a dictionary of when instantiating the <code>ValidationError</code>, like so:</p>
<div class="language-text highlight"><pre><span></span><code>def validate(self, attrs):
# serializer.errors == {&#39;my_field&#39;: [&#39;A field error&#39;]}
raise serializers.ValidationError({&#39;my_field&#39;: &#39;A field error&#39;})
</code></pre></div>
<p>This ensures you can still write validation that compares all the input fields, but that marks the error against a particular field.</p>
<h4 id="removal-of-transform_field_name">Removal of <code>transform_&lt;field_name&gt;</code>.<a class="headerlink" href="#removal-of-transform_field_name" title="Permanent link">&para;</a></h4>
<p>The under-used <code>transform_&lt;field_name&gt;</code> on serializer classes is no longer provided. Instead you should just override <code>to_representation()</code> if you need to apply any modifications to the representation style.</p>
<p>For example:</p>
<div class="language-text highlight"><pre><span></span><code>def to_representation(self, instance):
ret = super(UserSerializer, self).to_representation(instance)
ret[&#39;username&#39;] = ret[&#39;username&#39;].lower()
return ret
</code></pre></div>
<p>Dropping the extra point of API means there's now only one right way to do things. This helps with repetition and reinforcement of the core API, rather than having multiple differing approaches.</p>
<p>If you absolutely need to preserve <code>transform_&lt;field_name&gt;</code> behavior, for example, in order to provide a simpler 2.x to 3.0 upgrade, you can use a mixin, or serializer base class that add the behavior back in. For example:</p>
<div class="language-text highlight"><pre><span></span><code>class BaseModelSerializer(ModelSerializer):
&quot;&quot;&quot;
A custom ModelSerializer class that preserves 2.x style `transform_&lt;field_name&gt;` behavior.
&quot;&quot;&quot;
def to_representation(self, instance):
ret = super(BaseModelSerializer, self).to_representation(instance)
for key, value in ret.items():
method = getattr(self, &#39;transform_&#39; + key, None)
if method is not None:
ret[key] = method(value)
return ret
</code></pre></div>
<h4 id="differences-between-modelserializer-validation-and-modelform">Differences between ModelSerializer validation and ModelForm.<a class="headerlink" href="#differences-between-modelserializer-validation-and-modelform" title="Permanent link">&para;</a></h4>
<p>This change also means that we no longer use the <code>.full_clean()</code> method on model instances, but instead perform all validation explicitly on the serializer. This gives a cleaner separation, and ensures that there's no automatic validation behavior on <code>ModelSerializer</code> classes that can't also be easily replicated on regular <code>Serializer</code> classes.</p>
<p>For the most part this change should be transparent. Field validation and uniqueness checks will still be run as normal, but the implementation is a little different.</p>
<p>The one difference that you do need to note is that the <code>.clean()</code> method will not be called as part of serializer validation, as it would be if using a <code>ModelForm</code>. Use the serializer <code>.validate()</code> method to perform a final validation step on incoming data where required.</p>
<p>There may be some cases where you really do need to keep validation logic in the model <code>.clean()</code> method, and cannot instead separate it into the serializer <code>.validate()</code>. You can do so by explicitly instantiating a model instance in the <code>.validate()</code> method.</p>
<div class="language-text highlight"><pre><span></span><code>def validate(self, attrs):
instance = ExampleModel(**attrs)
instance.clean()
return attrs
</code></pre></div>
<p>Again, you really should look at properly separating the validation logic out of the model method if possible, but the above might be useful in some backwards compatibility cases, or for an easy migration path.</p>
<h4 id="writable-nested-serialization">Writable nested serialization.<a class="headerlink" href="#writable-nested-serialization" title="Permanent link">&para;</a></h4>
<p>REST framework 2.x attempted to automatically support writable nested serialization, but the behavior was complex and non-obvious. Attempting to automatically handle these case is problematic:</p>
<ul>
<li>There can be complex dependencies involved in order of saving multiple related model instances.</li>
<li>It's unclear what behavior the user should expect when related models are passed <code>None</code> data.</li>
<li>It's unclear how the user should expect to-many relationships to handle updates, creations and deletions of multiple records.</li>
</ul>
<p>Using the <code>depth</code> option on <code>ModelSerializer</code> will now create <strong>read-only nested serializers</strong> by default.</p>
<p>If you try to use a writable nested serializer without writing a custom <code>create()</code> and/or <code>update()</code> method you'll see an assertion error when you attempt to save the serializer. For example:</p>
<div class="language-text highlight"><pre><span></span><code>&gt;&gt;&gt; class ProfileSerializer(serializers.ModelSerializer):
&gt;&gt;&gt; class Meta:
&gt;&gt;&gt; model = Profile
&gt;&gt;&gt; fields = [&#39;address&#39;, &#39;phone&#39;]
&gt;&gt;&gt;
&gt;&gt;&gt; class UserSerializer(serializers.ModelSerializer):
&gt;&gt;&gt; profile = ProfileSerializer()
&gt;&gt;&gt; class Meta:
&gt;&gt;&gt; model = User
&gt;&gt;&gt; fields = [&#39;username&#39;, &#39;email&#39;, &#39;profile&#39;]
&gt;&gt;&gt;
&gt;&gt;&gt; data = {
&gt;&gt;&gt; &#39;username&#39;: &#39;lizzy&#39;,
&gt;&gt;&gt; &#39;email&#39;: &#39;lizzy@example.com&#39;,
&gt;&gt;&gt; &#39;profile&#39;: {&#39;address&#39;: &#39;123 Acacia Avenue&#39;, &#39;phone&#39;: &#39;01273 100200&#39;}
&gt;&gt;&gt; }
&gt;&gt;&gt;
&gt;&gt;&gt; serializer = UserSerializer(data=data)
&gt;&gt;&gt; serializer.save()
AssertionError: The `.create()` method does not support nested writable fields by default. Write an explicit `.create()` method for serializer `UserSerializer`, or set `read_only=True` on nested serializer fields.
</code></pre></div>
<p>To use writable nested serialization you'll want to declare a nested field on the serializer class, and write the <code>create()</code> and/or <code>update()</code> methods explicitly.</p>
<div class="language-text highlight"><pre><span></span><code>class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = [&#39;username&#39;, &#39;email&#39;, &#39;profile&#39;]
def create(self, validated_data):
profile_data = validated_data.pop(&#39;profile&#39;)
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
</code></pre></div>
<p>The single-step object creation makes this far simpler and more obvious than the previous <code>.restore_object()</code> behavior.</p>
<h4 id="printable-serializer-representations">Printable serializer representations.<a class="headerlink" href="#printable-serializer-representations" title="Permanent link">&para;</a></h4>
<p>Serializer instances now support a printable representation that allows you to inspect the fields present on the instance.</p>
<p>For instance, given the following example model:</p>
<div class="language-text highlight"><pre><span></span><code>class LocationRating(models.Model):
location = models.CharField(max_length=100)
rating = models.IntegerField()
created_by = models.ForeignKey(User)
</code></pre></div>
<p>Let's create a simple <code>ModelSerializer</code> class corresponding to the <code>LocationRating</code> model.</p>
<div class="language-text highlight"><pre><span></span><code>class LocationRatingSerializer(serializer.ModelSerializer):
class Meta:
model = LocationRating
</code></pre></div>
<p>We can now inspect the serializer representation in the Django shell, using <code>python manage.py shell</code>...</p>
<div class="language-text highlight"><pre><span></span><code>&gt;&gt;&gt; serializer = LocationRatingSerializer()
&gt;&gt;&gt; print(serializer) # Or use `print serializer` in Python 2.x
LocationRatingSerializer():
id = IntegerField(label=&#39;ID&#39;, read_only=True)
location = CharField(max_length=100)
rating = IntegerField()
created_by = PrimaryKeyRelatedField(queryset=User.objects.all())
</code></pre></div>
<h4 id="the-extra_kwargs-option">The <code>extra_kwargs</code> option.<a class="headerlink" href="#the-extra_kwargs-option" title="Permanent link">&para;</a></h4>
<p>The <code>write_only_fields</code> option on <code>ModelSerializer</code> has been moved to <code>PendingDeprecation</code> and replaced with a more generic <code>extra_kwargs</code>.</p>
<div class="language-text highlight"><pre><span></span><code>class MySerializer(serializer.ModelSerializer):
class Meta:
model = MyModel
fields = [&#39;id&#39;, &#39;email&#39;, &#39;notes&#39;, &#39;is_admin&#39;]
extra_kwargs = {
&#39;is_admin&#39;: {&#39;write_only&#39;: True}
}
</code></pre></div>
<p>Alternatively, specify the field explicitly on the serializer class:</p>
<div class="language-text highlight"><pre><span></span><code>class MySerializer(serializer.ModelSerializer):
is_admin = serializers.BooleanField(write_only=True)
class Meta:
model = MyModel
fields = [&#39;id&#39;, &#39;email&#39;, &#39;notes&#39;, &#39;is_admin&#39;]
</code></pre></div>
<p>The <code>read_only_fields</code> option remains as a convenient shortcut for the more common case.</p>
<h4 id="changes-to-hyperlinkedmodelserializer">Changes to <code>HyperlinkedModelSerializer</code>.<a class="headerlink" href="#changes-to-hyperlinkedmodelserializer" title="Permanent link">&para;</a></h4>
<p>The <code>view_name</code> and <code>lookup_field</code> options have been moved to <code>PendingDeprecation</code>. They are no longer required, as you can use the <code>extra_kwargs</code> argument instead:</p>
<div class="language-text highlight"><pre><span></span><code>class MySerializer(serializer.HyperlinkedModelSerializer):
class Meta:
model = MyModel
fields = [&#39;url&#39;, &#39;email&#39;, &#39;notes&#39;, &#39;is_admin&#39;]
extra_kwargs = {
&#39;url&#39;: {&#39;lookup_field&#39;: &#39;uuid&#39;}
}
</code></pre></div>
<p>Alternatively, specify the field explicitly on the serializer class:</p>
<div class="language-text highlight"><pre><span></span><code>class MySerializer(serializer.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name=&#39;mymodel-detail&#39;,
lookup_field=&#39;uuid&#39;
)
class Meta:
model = MyModel
fields = [&#39;url&#39;, &#39;email&#39;, &#39;notes&#39;, &#39;is_admin&#39;]
</code></pre></div>
<h4 id="fields-for-model-methods-and-properties">Fields for model methods and properties.<a class="headerlink" href="#fields-for-model-methods-and-properties" title="Permanent link">&para;</a></h4>
<p>With <code>ModelSerializer</code> you can now specify field names in the <code>fields</code> option that refer to model methods or properties. For example, suppose you have the following model:</p>
<div class="language-text highlight"><pre><span></span><code>class Invitation(models.Model):
created = models.DateTimeField()
to_email = models.EmailField()
message = models.CharField(max_length=1000)
def expiry_date(self):
return self.created + datetime.timedelta(days=30)
</code></pre></div>
<p>You can include <code>expiry_date</code> as a field option on a <code>ModelSerializer</code> class.</p>
<div class="language-text highlight"><pre><span></span><code>class InvitationSerializer(serializers.ModelSerializer):
class Meta:
model = Invitation
fields = [&#39;to_email&#39;, &#39;message&#39;, &#39;expiry_date&#39;]
</code></pre></div>
<p>These fields will be mapped to <code>serializers.ReadOnlyField()</code> instances.</p>
<div class="language-text highlight"><pre><span></span><code>&gt;&gt;&gt; serializer = InvitationSerializer()
&gt;&gt;&gt; print(repr(serializer))
InvitationSerializer():
to_email = EmailField(max_length=75)
message = CharField(max_length=1000)
expiry_date = ReadOnlyField()
</code></pre></div>
<h4 id="the-listserializer-class">The <code>ListSerializer</code> class.<a class="headerlink" href="#the-listserializer-class" title="Permanent link">&para;</a></h4>
<p>The <code>ListSerializer</code> class has now been added, and allows you to create base serializer classes for only accepting multiple inputs.</p>
<div class="language-text highlight"><pre><span></span><code>class MultipleUserSerializer(ListSerializer):
child = UserSerializer()
</code></pre></div>
<p>You can also still use the <code>many=True</code> argument to serializer classes. It's worth noting that <code>many=True</code> argument transparently creates a <code>ListSerializer</code> instance, allowing the validation logic for list and non-list data to be cleanly separated in the REST framework codebase.</p>
<p>You will typically want to <em>continue to use the existing <code>many=True</code> flag</em> rather than declaring <code>ListSerializer</code> classes explicitly, but declaring the classes explicitly can be useful if you need to write custom <code>create</code> or <code>update</code> methods for bulk updates, or provide for other custom behavior.</p>
<p>See also the new <code>ListField</code> class, which validates input in the same way, but does not include the serializer interfaces of <code>.is_valid()</code>, <code>.data</code>, <code>.save()</code> and so on.</p>
<h4 id="the-baseserializer-class">The <code>BaseSerializer</code> class.<a class="headerlink" href="#the-baseserializer-class" title="Permanent link">&para;</a></h4>
<p>REST framework now includes a simple <code>BaseSerializer</code> class that can be used to easily support alternative serialization and deserialization styles.</p>
<p>This class implements the same basic API as the <code>Serializer</code> class:</p>
<ul>
<li><code>.data</code> - Returns the outgoing primitive representation.</li>
<li><code>.is_valid()</code> - Deserializes and validates incoming data.</li>
<li><code>.validated_data</code> - Returns the validated incoming data.</li>
<li><code>.errors</code> - Returns an errors during validation.</li>
<li><code>.save()</code> - Persists the validated data into an object instance.</li>
</ul>
<p>There are four methods that can be overridden, depending on what functionality you want the serializer class to support:</p>
<ul>
<li><code>.to_representation()</code> - Override this to support serialization, for read operations.</li>
<li><code>.to_internal_value()</code> - Override this to support deserialization, for write operations.</li>
<li><code>.create()</code> and <code>.update()</code> - Override either or both of these to support saving instances.</li>
</ul>
<p>Because this class provides the same interface as the <code>Serializer</code> class, you can use it with the existing generic class-based views exactly as you would for a regular <code>Serializer</code> or <code>ModelSerializer</code>.</p>
<p>The only difference you'll notice when doing so is the <code>BaseSerializer</code> classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input.</p>
<h5 id="read-only-baseserializer-classes">Read-only <code>BaseSerializer</code> classes.<a class="headerlink" href="#read-only-baseserializer-classes" title="Permanent link">&para;</a></h5>
<p>To implement a read-only serializer using the <code>BaseSerializer</code> class, we just need to override the <code>.to_representation()</code> method. Let's take a look at an example using a simple Django model:</p>
<div class="language-text highlight"><pre><span></span><code>class HighScore(models.Model):
created = models.DateTimeField(auto_now_add=True)
player_name = models.CharField(max_length=10)
score = models.IntegerField()
</code></pre></div>
<p>It's simple to create a read-only serializer for converting <code>HighScore</code> instances into primitive data types.</p>
<div class="language-text highlight"><pre><span></span><code>class HighScoreSerializer(serializers.BaseSerializer):
def to_representation(self, obj):
return {
&#39;score&#39;: obj.score,
&#39;player_name&#39;: obj.player_name
}
</code></pre></div>
<p>We can now use this class to serialize single <code>HighScore</code> instances:</p>
<div class="language-text highlight"><pre><span></span><code>@api_view([&#39;GET&#39;])
def high_score(request, pk):
instance = HighScore.objects.get(pk=pk)
serializer = HighScoreSerializer(instance)
return Response(serializer.data)
</code></pre></div>
<p>Or use it to serialize multiple instances:</p>
<div class="language-text highlight"><pre><span></span><code>@api_view([&#39;GET&#39;])
def all_high_scores(request):
queryset = HighScore.objects.order_by(&#39;-score&#39;)
serializer = HighScoreSerializer(queryset, many=True)
return Response(serializer.data)
</code></pre></div>
<h5 id="read-write-baseserializer-classes">Read-write <code>BaseSerializer</code> classes.<a class="headerlink" href="#read-write-baseserializer-classes" title="Permanent link">&para;</a></h5>
<p>To create a read-write serializer we first need to implement a <code>.to_internal_value()</code> method. This method returns the validated values that will be used to construct the object instance, and may raise a <code>ValidationError</code> if the supplied data is in an incorrect format.</p>
<p>Once you've implemented <code>.to_internal_value()</code>, the basic validation API will be available on the serializer, and you will be able to use <code>.is_valid()</code>, <code>.validated_data</code> and <code>.errors</code>.</p>
<p>If you want to also support <code>.save()</code> you'll need to also implement either or both of the <code>.create()</code> and <code>.update()</code> methods.</p>
<p>Here's a complete example of our previous <code>HighScoreSerializer</code>, that's been updated to support both read and write operations.</p>
<div class="language-text highlight"><pre><span></span><code>class HighScoreSerializer(serializers.BaseSerializer):
def to_internal_value(self, data):
score = data.get(&#39;score&#39;)
player_name = data.get(&#39;player_name&#39;)
# Perform the data validation.
if not score:
raise ValidationError({
&#39;score&#39;: &#39;This field is required.&#39;
})
if not player_name:
raise ValidationError({
&#39;player_name&#39;: &#39;This field is required.&#39;
})
if len(player_name) &gt; 10:
raise ValidationError({
&#39;player_name&#39;: &#39;May not be more than 10 characters.&#39;
})
# Return the validated values. This will be available as
# the `.validated_data` property.
return {
&#39;score&#39;: int(score),
&#39;player_name&#39;: player_name
}
def to_representation(self, obj):
return {
&#39;score&#39;: obj.score,
&#39;player_name&#39;: obj.player_name
}
def create(self, validated_data):
return HighScore.objects.create(**validated_data)
</code></pre></div>
<h4 id="creating-new-generic-serializers-with-baseserializer">Creating new generic serializers with <code>BaseSerializer</code>.<a class="headerlink" href="#creating-new-generic-serializers-with-baseserializer" title="Permanent link">&para;</a></h4>
<p>The <code>BaseSerializer</code> class is also useful if you want to implement new generic serializer classes for dealing with particular serialization styles, or for integrating with alternative storage backends.</p>
<p>The following class is an example of a generic serializer that can handle coercing arbitrary objects into primitive representations.</p>
<div class="language-text highlight"><pre><span></span><code>class ObjectSerializer(serializers.BaseSerializer):
&quot;&quot;&quot;
A read-only serializer that coerces arbitrary complex objects
into primitive representations.
&quot;&quot;&quot;
def to_representation(self, obj):
for attribute_name in dir(obj):
attribute = getattr(obj, attribute_name)
if attribute_name.startswith(&#39;_&#39;):
# Ignore private attributes.
pass
elif hasattr(attribute, &#39;__call__&#39;):
# Ignore methods and other callables.
pass
elif isinstance(attribute, (str, int, bool, float, type(None))):
# Primitive types can be passed through unmodified.
output[attribute_name] = attribute
elif isinstance(attribute, list):
# Recursively deal with items in lists.
output[attribute_name] = [
self.to_representation(item) for item in attribute
]
elif isinstance(attribute, dict):
# Recursively deal with items in dictionaries.
output[attribute_name] = {
str(key): self.to_representation(value)
for key, value in attribute.items()
}
else:
# Force anything else to its string representation.
output[attribute_name] = str(attribute)
</code></pre></div>
<hr />
<h2 id="serializer-fields">Serializer fields<a class="headerlink" href="#serializer-fields" title="Permanent link">&para;</a></h2>
<h4 id="the-field-and-readonly-field-classes">The <code>Field</code> and <code>ReadOnly</code> field classes.<a class="headerlink" href="#the-field-and-readonly-field-classes" title="Permanent link">&para;</a></h4>
<p>There are some minor tweaks to the field base classes.</p>
<p>Previously we had these two base classes:</p>
<ul>
<li><code>Field</code> as the base class for read-only fields. A default implementation was included for serializing data.</li>
<li><code>WritableField</code> as the base class for read-write fields.</li>
</ul>
<p>We now use the following:</p>
<ul>
<li><code>Field</code> is the base class for all fields. It does not include any default implementation for either serializing or deserializing data.</li>
<li><code>ReadOnlyField</code> is a concrete implementation for read-only fields that simply returns the attribute value without modification.</li>
</ul>
<h4 id="the-required-allow_null-allow_blank-and-default-arguments">The <code>required</code>, <code>allow_null</code>, <code>allow_blank</code> and <code>default</code> arguments.<a class="headerlink" href="#the-required-allow_null-allow_blank-and-default-arguments" title="Permanent link">&para;</a></h4>
<p>REST framework now has more explicit and clear control over validating empty values for fields.</p>
<p>Previously the meaning of the <code>required=False</code> keyword argument was underspecified. In practice its use meant that a field could either be not included in the input, or it could be included, but be <code>None</code> or the empty string.</p>
<p>We now have a better separation, with separate <code>required</code>, <code>allow_null</code> and <code>allow_blank</code> arguments.</p>
<p>The following set of arguments are used to control validation of empty values:</p>
<ul>
<li><code>required=False</code>: The value does not need to be present in the input, and will not be passed to <code>.create()</code> or <code>.update()</code> if it is not seen.</li>
<li><code>default=&lt;value&gt;</code>: The value does not need to be present in the input, and a default value will be passed to <code>.create()</code> or <code>.update()</code> if it is not seen.</li>
<li><code>allow_null=True</code>: <code>None</code> is a valid input.</li>
<li><code>allow_blank=True</code>: <code>''</code> is valid input. For <code>CharField</code> and subclasses only.</li>
</ul>
<p>Typically you'll want to use <code>required=False</code> if the corresponding model field has a default value, and additionally set either <code>allow_null=True</code> or <code>allow_blank=True</code> if required.</p>
<p>The <code>default</code> argument is also available and always implies that the field is not required to be in the input. It is unnecessary to use the <code>required</code> argument when a default is specified, and doing so will result in an error.</p>
<h4 id="coercing-output-types">Coercing output types.<a class="headerlink" href="#coercing-output-types" title="Permanent link">&para;</a></h4>
<p>The previous field implementations did not forcibly coerce returned values into the correct type in many cases. For example, an <code>IntegerField</code> would return a string output if the attribute value was a string. We now more strictly coerce to the correct return type, leading to more constrained and expected behavior.</p>
<h4 id="removal-of-validate">Removal of <code>.validate()</code>.<a class="headerlink" href="#removal-of-validate" title="Permanent link">&para;</a></h4>
<p>The <code>.validate()</code> method is now removed from field classes. This method was in any case undocumented and not public API. You should instead simply override <code>to_internal_value()</code>.</p>
<div class="language-text highlight"><pre><span></span><code>class UppercaseCharField(serializers.CharField):
def to_internal_value(self, data):
value = super(UppercaseCharField, self).to_internal_value(data)
if value != value.upper():
raise serializers.ValidationError(&#39;The input should be uppercase only.&#39;)
return value
</code></pre></div>
<p>Previously validation errors could be raised in either <code>.to_native()</code> or <code>.validate()</code>, making it non-obvious which should be used. Providing only a single point of API ensures more repetition and reinforcement of the core API.</p>
<h4 id="the-listfield-class">The <code>ListField</code> class.<a class="headerlink" href="#the-listfield-class" title="Permanent link">&para;</a></h4>
<p>The <code>ListField</code> class has now been added. This field validates list input. It takes a <code>child</code> keyword argument which is used to specify the field used to validate each item in the list. For example:</p>
<div class="language-text highlight"><pre><span></span><code>scores = ListField(child=IntegerField(min_value=0, max_value=100))
</code></pre></div>
<p>You can also use a declarative style to create new subclasses of <code>ListField</code>, like this:</p>
<div class="language-text highlight"><pre><span></span><code>class ScoresField(ListField):
child = IntegerField(min_value=0, max_value=100)
</code></pre></div>
<p>We can now use the <code>ScoresField</code> class inside another serializer:</p>
<div class="language-text highlight"><pre><span></span><code>scores = ScoresField()
</code></pre></div>
<p>See also the new <code>ListSerializer</code> class, which validates input in the same way, but also includes the serializer interfaces of <code>.is_valid()</code>, <code>.data</code>, <code>.save()</code> and so on.</p>
<h4 id="the-choicefield-class-may-now-accept-a-flat-list">The <code>ChoiceField</code> class may now accept a flat list.<a class="headerlink" href="#the-choicefield-class-may-now-accept-a-flat-list" title="Permanent link">&para;</a></h4>
<p>The <code>ChoiceField</code> class may now accept a list of choices in addition to the existing style of using a list of pairs of <code>(name, display_value)</code>. The following is now valid:</p>
<div class="language-text highlight"><pre><span></span><code>color = ChoiceField(choices=[&#39;red&#39;, &#39;green&#39;, &#39;blue&#39;])
</code></pre></div>
<h4 id="the-multiplechoicefield-class">The <code>MultipleChoiceField</code> class.<a class="headerlink" href="#the-multiplechoicefield-class" title="Permanent link">&para;</a></h4>
<p>The <code>MultipleChoiceField</code> class has been added. This field acts like <code>ChoiceField</code>, but returns a set, which may include none, one or many of the valid choices.</p>
<h4 id="changes-to-the-custom-field-api">Changes to the custom field API.<a class="headerlink" href="#changes-to-the-custom-field-api" title="Permanent link">&para;</a></h4>
<p>The <code>from_native(self, value)</code> and <code>to_native(self, data)</code> method names have been replaced with the more obviously named <code>to_internal_value(self, data)</code> and <code>to_representation(self, value)</code>.</p>
<p>The <code>field_from_native()</code> and <code>field_to_native()</code> methods are removed. Previously you could use these methods if you wanted to customize the behavior in a way that did not simply lookup the field value from the object. For example...</p>
<div class="language-text highlight"><pre><span></span><code>def field_to_native(self, obj, field_name):
&quot;&quot;&quot;A custom read-only field that returns the class name.&quot;&quot;&quot;
return obj.__class__.__name__
</code></pre></div>
<p>Now if you need to access the entire object you'll instead need to override one or both of the following:</p>
<ul>
<li>Use <code>get_attribute</code> to modify the attribute value passed to <code>to_representation()</code>.</li>
<li>Use <code>get_value</code> to modify the data value passed <code>to_internal_value()</code>.</li>
</ul>
<p>For example:</p>
<div class="language-text highlight"><pre><span></span><code>def get_attribute(self, obj):
# Pass the entire object through to `to_representation()`,
# instead of the standard attribute lookup.
return obj
def to_representation(self, value):
return value.__class__.__name__
</code></pre></div>
<h4 id="explicit-queryset-required-on-relational-fields">Explicit <code>queryset</code> required on relational fields.<a class="headerlink" href="#explicit-queryset-required-on-relational-fields" title="Permanent link">&para;</a></h4>
<p>Previously relational fields that were explicitly declared on a serializer class could omit the queryset argument if (and only if) they were declared on a <code>ModelSerializer</code>.</p>
<p>This code <em>would be valid</em> in <code>2.4.3</code>:</p>
<div class="language-text highlight"><pre><span></span><code>class AccountSerializer(serializers.ModelSerializer):
organizations = serializers.SlugRelatedField(slug_field=&#39;name&#39;)
class Meta:
model = Account
</code></pre></div>
<p>However this code <em>would not be valid</em> in <code>3.0</code>:</p>
<div class="language-text highlight"><pre><span></span><code># Missing `queryset`
class AccountSerializer(serializers.Serializer):
organizations = serializers.SlugRelatedField(slug_field=&#39;name&#39;)
def restore_object(self, attrs, instance=None):
# ...
</code></pre></div>
<p>The queryset argument is now always required for writable relational fields.
This removes some magic and makes it easier and more obvious to move between implicit <code>ModelSerializer</code> classes and explicit <code>Serializer</code> classes.</p>
<div class="language-text highlight"><pre><span></span><code>class AccountSerializer(serializers.ModelSerializer):
organizations = serializers.SlugRelatedField(
slug_field=&#39;name&#39;,
queryset=Organization.objects.all()
)
class Meta:
model = Account
</code></pre></div>
<p>The <code>queryset</code> argument is only ever required for writable fields, and is not required or valid for fields with <code>read_only=True</code>.</p>
<h4 id="optional-argument-to-serializermethodfield">Optional argument to <code>SerializerMethodField</code>.<a class="headerlink" href="#optional-argument-to-serializermethodfield" title="Permanent link">&para;</a></h4>
<p>The argument to <code>SerializerMethodField</code> is now optional, and defaults to <code>get_&lt;field_name&gt;</code>. For example the following is valid:</p>
<div class="language-text highlight"><pre><span></span><code>class AccountSerializer(serializers.Serializer):
# `method_name=&#39;get_billing_details&#39;` by default.
billing_details = serializers.SerializerMethodField()
def get_billing_details(self, account):
return calculate_billing(account)
</code></pre></div>
<p>In order to ensure a consistent code style an assertion error will be raised if you include a redundant method name argument that matches the default method name. For example, the following code <em>will raise an error</em>:</p>
<div class="language-text highlight"><pre><span></span><code>billing_details = serializers.SerializerMethodField(&#39;get_billing_details&#39;)
</code></pre></div>
<h4 id="enforcing-consistent-source-usage">Enforcing consistent <code>source</code> usage.<a class="headerlink" href="#enforcing-consistent-source-usage" title="Permanent link">&para;</a></h4>
<p>I've see several codebases that unnecessarily include the <code>source</code> argument, setting it to the same value as the field name. This usage is redundant and confusing, making it less obvious that <code>source</code> is usually not required.</p>
<p>The following usage will <em>now raise an error</em>:</p>
<div class="language-text highlight"><pre><span></span><code>email = serializers.EmailField(source=&#39;email&#39;)
</code></pre></div>
<h4 id="the-uniquevalidator-and-uniquetogethervalidator-classes">The <code>UniqueValidator</code> and <code>UniqueTogetherValidator</code> classes.<a class="headerlink" href="#the-uniquevalidator-and-uniquetogethervalidator-classes" title="Permanent link">&para;</a></h4>
<p>REST framework now provides new validators that allow you to ensure field uniqueness, while still using a completely explicit <code>Serializer</code> class instead of using <code>ModelSerializer</code>.</p>
<p>The <code>UniqueValidator</code> should be applied to a serializer field, and takes a single <code>queryset</code> argument.</p>
<div class="language-text highlight"><pre><span></span><code>from rest_framework import serializers
from rest_framework.validators import UniqueValidator
class OrganizationSerializer(serializers.Serializer):
url = serializers.HyperlinkedIdentityField(view_name=&#39;organization_detail&#39;)
created = serializers.DateTimeField(read_only=True)
name = serializers.CharField(
max_length=100,
validators=UniqueValidator(queryset=Organization.objects.all())
)
</code></pre></div>
<p>The <code>UniqueTogetherValidator</code> should be applied to a serializer, and takes a <code>queryset</code> argument and a <code>fields</code> argument which should be a list or tuple of field names.</p>
<div class="language-text highlight"><pre><span></span><code>class RaceResultSerializer(serializers.Serializer):
category = serializers.ChoiceField([&#39;5k&#39;, &#39;10k&#39;])
position = serializers.IntegerField()
name = serializers.CharField(max_length=100)
class Meta:
validators = [UniqueTogetherValidator(
queryset=RaceResult.objects.all(),
fields=[&#39;category&#39;, &#39;position&#39;]
)]
</code></pre></div>
<h4 id="the-uniquefordatevalidator-classes">The <code>UniqueForDateValidator</code> classes.<a class="headerlink" href="#the-uniquefordatevalidator-classes" title="Permanent link">&para;</a></h4>
<p>REST framework also now includes explicit validator classes for validating the <code>unique_for_date</code>, <code>unique_for_month</code>, and <code>unique_for_year</code> model field constraints. These are used internally instead of calling into <code>Model.full_clean()</code>.</p>
<p>These classes are documented in the <a href="../../api-guide/validators/">Validators</a> section of the documentation.</p>
<hr />
<h2 id="generic-views">Generic views<a class="headerlink" href="#generic-views" title="Permanent link">&para;</a></h2>
<h4 id="simplification-of-view-logic">Simplification of view logic.<a class="headerlink" href="#simplification-of-view-logic" title="Permanent link">&para;</a></h4>
<p>The view logic for the default method handlers has been significantly simplified, due to the new serializers API.</p>
<h4 id="changes-to-prepost-save-hooks">Changes to pre/post save hooks.<a class="headerlink" href="#changes-to-prepost-save-hooks" title="Permanent link">&para;</a></h4>
<p>The <code>pre_save</code> and <code>post_save</code> hooks no longer exist, but are replaced with <code>perform_create(self, serializer)</code> and <code>perform_update(self, serializer)</code>.</p>
<p>These methods should save the object instance by calling <code>serializer.save()</code>, adding in any additional arguments as required. They may also perform any custom pre-save or post-save behavior.</p>
<p>For example:</p>
<div class="language-text highlight"><pre><span></span><code>def perform_create(self, serializer):
# Include the owner attribute directly, rather than from request data.
instance = serializer.save(owner=self.request.user)
# Perform a custom post-save action.
send_email(instance.to_email, instance.message)
</code></pre></div>
<p>The <code>pre_delete</code> and <code>post_delete</code> hooks no longer exist, and are replaced with <code>.perform_destroy(self, instance)</code>, which should delete the instance and perform any custom actions.</p>
<div class="language-text highlight"><pre><span></span><code>def perform_destroy(self, instance):
# Perform a custom pre-delete action.
send_deletion_alert(user=instance.created_by, deleted=instance)
# Delete the object instance.
instance.delete()
</code></pre></div>
<h4 id="removal-of-view-attributes">Removal of view attributes.<a class="headerlink" href="#removal-of-view-attributes" title="Permanent link">&para;</a></h4>
<p>The <code>.object</code> and <code>.object_list</code> attributes are no longer set on the view instance. Treating views as mutable object instances that store state during the processing of the view tends to be poor design, and can lead to obscure flow logic.</p>
<p>I would personally recommend that developers treat view instances as immutable objects in their application code.</p>
<h4 id="put-as-create">PUT as create.<a class="headerlink" href="#put-as-create" title="Permanent link">&para;</a></h4>
<p>Allowing <code>PUT</code> as create operations is problematic, as it necessarily exposes information about the existence or non-existence of objects. It's also not obvious that transparently allowing re-creating of previously deleted instances is necessarily a better default behavior than simply returning <code>404</code> responses.</p>
<p>Both styles "<code>PUT</code> as 404" and "<code>PUT</code> as create" can be valid in different circumstances, but we've now opted for the 404 behavior as the default, due to it being simpler and more obvious.</p>
<p>If you need to restore the previous behavior you may want to include <a href="https://gist.github.com/tomchristie/a2ace4577eff2c603b1b">this <code>AllowPUTAsCreateMixin</code> class</a> as a mixin to your views.</p>
<h4 id="customizing-error-responses">Customizing error responses.<a class="headerlink" href="#customizing-error-responses" title="Permanent link">&para;</a></h4>
<p>The generic views now raise <code>ValidationFailed</code> exception for invalid data. This exception is then dealt with by the exception handler, rather than the view returning a <code>400 Bad Request</code> response directly.</p>
<p>This change means that you can now easily customize the style of error responses across your entire API, without having to modify any of the generic views.</p>
<hr />
<h2 id="the-metadata-api">The metadata API<a class="headerlink" href="#the-metadata-api" title="Permanent link">&para;</a></h2>
<p>Behavior for dealing with <code>OPTIONS</code> requests was previously built directly into the class-based views. This has now been properly separated out into a Metadata API that allows the same pluggable style as other API policies in REST framework.</p>
<p>This makes it far easier to use a different style for <code>OPTIONS</code> responses throughout your API, and makes it possible to create third-party metadata policies.</p>
<hr />
<h2 id="serializers-as-html-forms">Serializers as HTML forms<a class="headerlink" href="#serializers-as-html-forms" title="Permanent link">&para;</a></h2>
<p>REST framework 3.0 includes templated HTML form rendering for serializers.</p>
<p>This API should not yet be considered finalized, and will only be promoted to public API for the 3.1 release.</p>
<p>Significant changes that you do need to be aware of include:</p>
<ul>
<li>Nested HTML forms are now supported, for example, a <code>UserSerializer</code> with a nested <code>ProfileSerializer</code> will now render a nested <code>fieldset</code> when used in the browsable API.</li>
<li>Nested lists of HTML forms are not yet supported, but are planned for 3.1.</li>
<li>Because we now use templated HTML form generation, <strong>the <code>widget</code> option is no longer available for serializer fields</strong>. You can instead control the template that is used for a given field, by using the <code>style</code> dictionary.</li>
</ul>
<h4 id="the-style-keyword-argument-for-serializer-fields">The <code>style</code> keyword argument for serializer fields.<a class="headerlink" href="#the-style-keyword-argument-for-serializer-fields" title="Permanent link">&para;</a></h4>
<p>The <code>style</code> keyword argument can be used to pass through additional information from a serializer field, to the renderer class. In particular, the <code>HTMLFormRenderer</code> uses the <code>base_template</code> key to determine which template to render the field with.</p>
<p>For example, to use a <code>textarea</code> control instead of the default <code>input</code> control, you would use the following…</p>
<div class="language-text highlight"><pre><span></span><code>additional_notes = serializers.CharField(
style={&#39;base_template&#39;: &#39;textarea.html&#39;}
)
</code></pre></div>
<p>Similarly, to use a radio button control instead of the default <code>select</code> control, you would use the following…</p>
<div class="language-text highlight"><pre><span></span><code>color_channel = serializers.ChoiceField(
choices=[&#39;red&#39;, &#39;blue&#39;, &#39;green&#39;],
style={&#39;base_template&#39;: &#39;radio.html&#39;}
)
</code></pre></div>
<p>This API should be considered provisional, and there may be minor alterations with the incoming 3.1 release.</p>
<hr />
<h2 id="api-style">API style<a class="headerlink" href="#api-style" title="Permanent link">&para;</a></h2>
<p>There are some improvements in the default style we use in our API responses.</p>
<h4 id="unicode-json-by-default">Unicode JSON by default.<a class="headerlink" href="#unicode-json-by-default" title="Permanent link">&para;</a></h4>
<p>Unicode JSON is now the default. The <code>UnicodeJSONRenderer</code> class no longer exists, and the <code>UNICODE_JSON</code> setting has been added. To revert this behavior use the new setting:</p>
<div class="language-text highlight"><pre><span></span><code>REST_FRAMEWORK = {
&#39;UNICODE_JSON&#39;: False
}
</code></pre></div>
<h4 id="compact-json-by-default">Compact JSON by default.<a class="headerlink" href="#compact-json-by-default" title="Permanent link">&para;</a></h4>
<p>We now output compact JSON in responses by default. For example, we return:</p>
<div class="language-text highlight"><pre><span></span><code>{&quot;email&quot;:&quot;amy@example.com&quot;,&quot;is_admin&quot;:true}
</code></pre></div>
<p>Instead of the following:</p>
<div class="language-text highlight"><pre><span></span><code>{&quot;email&quot;: &quot;amy@example.com&quot;, &quot;is_admin&quot;: true}
</code></pre></div>
<p>The <code>COMPACT_JSON</code> setting has been added, and can be used to revert this behavior if needed:</p>
<div class="language-text highlight"><pre><span></span><code>REST_FRAMEWORK = {
&#39;COMPACT_JSON&#39;: False
}
</code></pre></div>
<h4 id="file-fields-as-urls">File fields as URLs<a class="headerlink" href="#file-fields-as-urls" title="Permanent link">&para;</a></h4>
<p>The <code>FileField</code> and <code>ImageField</code> classes are now represented as URLs by default. You should ensure you set Django's <a href="https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-MEDIA_URL">standard <code>MEDIA_URL</code> setting</a> appropriately, and ensure your application <a href="https://docs.djangoproject.com/en/stable/howto/static-files/#serving-uploaded-files-in-development">serves the uploaded files</a>.</p>
<p>You can revert this behavior, and display filenames in the representation by using the <code>UPLOADED_FILES_USE_URL</code> settings key:</p>
<div class="language-text highlight"><pre><span></span><code>REST_FRAMEWORK = {
&#39;UPLOADED_FILES_USE_URL&#39;: False
}
</code></pre></div>
<p>You can also modify serializer fields individually, using the <code>use_url</code> argument:</p>
<div class="language-text highlight"><pre><span></span><code>uploaded_file = serializers.FileField(use_url=False)
</code></pre></div>
<p>Also note that you should pass the <code>request</code> object to the serializer as context when instantiating it, so that a fully qualified URL can be returned. Returned URLs will then be of the form <code>https://example.com/url_path/filename.txt</code>. For example:</p>
<div class="language-text highlight"><pre><span></span><code>context = {&#39;request&#39;: request}
serializer = ExampleSerializer(instance, context=context)
return Response(serializer.data)
</code></pre></div>
<p>If the request is omitted from the context, the returned URLs will be of the form <code>/url_path/filename.txt</code>.</p>
<h4 id="throttle-headers-using-retry-after">Throttle headers using <code>Retry-After</code>.<a class="headerlink" href="#throttle-headers-using-retry-after" title="Permanent link">&para;</a></h4>
<p>The custom <code>X-Throttle-Wait-Second</code> header has now been dropped in favor of the standard <code>Retry-After</code> header. You can revert this behavior if needed by writing a custom exception handler for your application.</p>
<h4 id="date-and-time-objects-as-iso-8601-strings-in-serializer-data">Date and time objects as ISO-8601 strings in serializer data.<a class="headerlink" href="#date-and-time-objects-as-iso-8601-strings-in-serializer-data" title="Permanent link">&para;</a></h4>
<p>Date and Time objects are now coerced to strings by default in the serializer output. Previously they were returned as <code>Date</code>, <code>Time</code> and <code>DateTime</code> objects, and later coerced to strings by the renderer.</p>
<p>You can modify this behavior globally by settings the existing <code>DATE_FORMAT</code>, <code>DATETIME_FORMAT</code> and <code>TIME_FORMAT</code> settings keys. Setting these values to <code>None</code> instead of their default value of <code>'iso-8601'</code> will result in native objects being returned in serializer data.</p>
<div class="language-text highlight"><pre><span></span><code>REST_FRAMEWORK = {
# Return native `Date` and `Time` objects in `serializer.data`
&#39;DATETIME_FORMAT&#39;: None
&#39;DATE_FORMAT&#39;: None
&#39;TIME_FORMAT&#39;: None
}
</code></pre></div>
<p>You can also modify serializer fields individually, using the <code>date_format</code>, <code>time_format</code> and <code>datetime_format</code> arguments:</p>
<div class="language-text highlight"><pre><span></span><code># Return `DateTime` instances in `serializer.data`, not strings.
created = serializers.DateTimeField(format=None)
</code></pre></div>
<h4 id="decimals-as-strings-in-serializer-data">Decimals as strings in serializer data.<a class="headerlink" href="#decimals-as-strings-in-serializer-data" title="Permanent link">&para;</a></h4>
<p>Decimals are now coerced to strings by default in the serializer output. Previously they were returned as <code>Decimal</code> objects, and later coerced to strings by the renderer.</p>
<p>You can modify this behavior globally by using the <code>COERCE_DECIMAL_TO_STRING</code> settings key.</p>
<div class="language-text highlight"><pre><span></span><code>REST_FRAMEWORK = {
&#39;COERCE_DECIMAL_TO_STRING&#39;: False
}
</code></pre></div>
<p>Or modify it on an individual serializer field, using the <code>coerce_to_string</code> keyword argument.</p>
<div class="language-text highlight"><pre><span></span><code># Return `Decimal` instances in `serializer.data`, not strings.
amount = serializers.DecimalField(
max_digits=10,
decimal_places=2,
coerce_to_string=False
)
</code></pre></div>
<p>The default JSON renderer will return float objects for un-coerced <code>Decimal</code> instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs.</p>
<hr />
<h2 id="miscellaneous-notes">Miscellaneous notes<a class="headerlink" href="#miscellaneous-notes" title="Permanent link">&para;</a></h2>
<ul>
<li>The serializer <code>ChoiceField</code> does not currently display nested choices, as was the case in 2.4. This will be address as part of 3.1.</li>
<li>Due to the new templated form rendering, the 'widget' option is no longer valid. This means there's no easy way of using third party "autocomplete" widgets for rendering select inputs that contain a large number of choices. You'll either need to use a regular select or a plain text input. We may consider addressing this in 3.1 or 3.2 if there's sufficient demand.</li>
<li>Some of the default validation error messages were rewritten and might no longer be pre-translated. You can still <a href="https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files">create language files with Django</a> if you wish to localize them.</li>
<li><code>APIException</code> subclasses could previously take any arbitrary type in the <code>detail</code> argument. These exceptions now use translatable text strings, and as a result call <code>force_text</code> on the <code>detail</code> argument, which <em>must be a string</em>. If you need complex arguments to an <code>APIException</code> class, you should subclass it and override the <code>__init__()</code> method. Typically you'll instead want to use a custom exception handler to provide for non-standard error responses.</li>
</ul>
<hr />
<h2 id="whats-coming-next">What's coming next<a class="headerlink" href="#whats-coming-next" title="Permanent link">&para;</a></h2>
<p>3.0 is an incremental release, and there are several upcoming features that will build on the baseline improvements that it makes.</p>
<p>The 3.1 release is planned to address improvements in the following components:</p>
<ul>
<li>Public API for using serializers as HTML forms.</li>
<li>Request parsing, mediatypes &amp; the implementation of the browsable API.</li>
<li>Introduction of a new pagination API.</li>
<li>Better support for API versioning.</li>
</ul>
<p>The 3.2 release is planned to introduce an alternative admin-style interface to the browsable API.</p>
<p>You can follow development on the GitHub site, where we use <a href="https://github.com/encode/django-rest-framework/milestones">milestones to indicate planning timescales</a>.</p>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<div class="md-progress" data-md-component="progress" role="progressbar"></div>
<script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["content.tabs.link", "content.code.annotate", "content.code.copy", "navigation.tabs", "navigation.tabs.sticky", "navigation.instant", "navigation.instant.prefetch", "navigation.instant.progress", "navigation.path", "navigation.sections", "navigation.top", "navigation.tracking", "search.suggest", "toc.follow"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../assets/javascripts/bundle.79ae519e.min.js"></script>
<script src="../../theme/js/prettify-1.0.js"></script>
<script>
document$.subscribe(function() {
document.querySelectorAll('pre code').forEach(code => {
code.parentElement.classList.add('prettyprint', 'well');
});
prettyPrint();
});
</script>
</body>
</html>