mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-27 12:04:02 +03:00
556 lines
36 KiB
HTML
556 lines
36 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<meta charset="utf-8">
|
|
<title>Filtering - Django REST framework</title>
|
|
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//img/favicon.ico" rel="icon" type="image/x-icon">
|
|
<link rel="canonical" href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/filtering.html"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="Django, API, REST, Filtering, Generic Filtering, API Guide, Custom generic filtering, Third party packages">
|
|
<meta name="author" content="Tom Christie">
|
|
|
|
<!-- Le styles -->
|
|
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/prettify.css" rel="stylesheet">
|
|
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap.css" rel="stylesheet">
|
|
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap-responsive.css" rel="stylesheet">
|
|
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/default.css" rel="stylesheet">
|
|
|
|
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
|
|
<!--[if lt IE 9]>
|
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
|
<![endif]-->
|
|
|
|
<script type="text/javascript">
|
|
|
|
var _gaq = _gaq || [];
|
|
_gaq.push(['_setAccount', 'UA-18852272-2']);
|
|
_gaq.push(['_trackPageview']);
|
|
|
|
(function() {
|
|
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
|
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
|
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
|
})();
|
|
|
|
</script>
|
|
<style>
|
|
span.fusion-wrap a {
|
|
display: block;
|
|
margin-top: 10px;
|
|
color: black;
|
|
}
|
|
|
|
a.fusion-poweredby {
|
|
display: block;
|
|
margin-top: 10px;
|
|
}
|
|
@media (max-width: 767px) {
|
|
div.promo {display: none;}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body onload="prettyPrint()" class="filtering-page">
|
|
|
|
<div class="wrapper">
|
|
|
|
<div class="navbar navbar-inverse navbar-fixed-top">
|
|
<div class="navbar-inner">
|
|
<div class="container-fluid">
|
|
<a class="repo-link btn btn-primary btn-small" href="https://github.com/tomchristie/django-rest-framework/tree/master">GitHub</a>
|
|
<a class="repo-link btn btn-inverse btn-small " href="../api-guide/pagination.html">Next <i class="icon-arrow-right icon-white"></i></a>
|
|
<a class="repo-link btn btn-inverse btn-small " href="../api-guide/throttling.html"><i class="icon-arrow-left icon-white"></i> Previous</a>
|
|
<a class="repo-link btn btn-inverse btn-small" href="#searchModal" data-toggle="modal"><i class="icon-search icon-white"></i> Search</a>
|
|
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
</a>
|
|
<a class="brand" href="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">Django REST framework</a>
|
|
<div class="nav-collapse collapse">
|
|
<ul class="nav">
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">Home</a></li>
|
|
<li class="dropdown">
|
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Tutorial <b class="caret"></b></a>
|
|
<ul class="dropdown-menu">
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/quickstart.html">Quickstart</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/1-serialization.html">1 - Serialization</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/2-requests-and-responses.html">2 - Requests and responses</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/3-class-based-views.html">3 - Class based views</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/4-authentication-and-permissions.html">4 - Authentication and permissions</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/5-relationships-and-hyperlinked-apis.html">5 - Relationships and hyperlinked APIs</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/6-viewsets-and-routers.html">6 - Viewsets and routers</a></li>
|
|
</ul>
|
|
</li>
|
|
<li class="dropdown">
|
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">API Guide <b class="caret"></b></a>
|
|
<ul class="dropdown-menu">
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/requests.html">Requests</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/responses.html">Responses</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/views.html">Views</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/generic-views.html">Generic views</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/viewsets.html">Viewsets</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/routers.html">Routers</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/parsers.html">Parsers</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/renderers.html">Renderers</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/serializers.html">Serializers</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/fields.html">Serializer fields</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/relations.html">Serializer relations</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/authentication.html">Authentication</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/permissions.html">Permissions</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/throttling.html">Throttling</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/filtering.html">Filtering</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/pagination.html">Pagination</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/content-negotiation.html">Content negotiation</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/format-suffixes.html">Format suffixes</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/reverse.html">Returning URLs</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/exceptions.html">Exceptions</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/status-codes.html">Status codes</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/testing.html">Testing</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/settings.html">Settings</a></li>
|
|
</ul>
|
|
</li>
|
|
<li class="dropdown">
|
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a>
|
|
<ul class="dropdown-menu">
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/documenting-your-api.html">Documenting your API</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/ajax-csrf-cors.html">AJAX, CSRF & CORS</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browser-enhancements.html">Browser enhancements</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browsable-api.html">The Browsable API</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-hypermedia-hateoas.html">REST, Hypermedia & HATEOAS</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/contributing.html">Contributing to REST framework</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-framework-2-announcement.html">2.0 Announcement</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.2-announcement.html">2.2 Announcement</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.3-announcement.html">2.3 Announcement</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/kickstarter-announcement.html">Kickstarter Announcement</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/release-notes.html">Release Notes</a></li>
|
|
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/credits.html">Credits</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<ul class="nav pull-right">
|
|
<!-- TODO
|
|
<li class="dropdown">
|
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Version: 2.0.0 <b class="caret"></b></a>
|
|
<ul class="dropdown-menu">
|
|
<li><a href="#">Trunk</a></li>
|
|
<li><a href="#">2.0.0</a></li>
|
|
</ul>
|
|
</li>
|
|
-->
|
|
</ul>
|
|
</div><!--/.nav-collapse -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="body-content">
|
|
<div class="container-fluid">
|
|
|
|
<!-- Search Modal -->
|
|
<div id="searchModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
|
<div class="modal-header">
|
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
|
<h3 id="myModalLabel">Documentation search</h3>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Custom google search -->
|
|
<script>
|
|
(function() {
|
|
var cx = '015016005043623903336:rxraeohqk6w';
|
|
var gcse = document.createElement('script');
|
|
gcse.type = 'text/javascript';
|
|
gcse.async = true;
|
|
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
|
|
'//www.google.com/cse/cse.js?cx=' + cx;
|
|
var s = document.getElementsByTagName('script')[0];
|
|
s.parentNode.insertBefore(gcse, s);
|
|
})();
|
|
</script>
|
|
<gcse:search></gcse:search>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row-fluid">
|
|
|
|
<div class="span3">
|
|
<!-- TODO
|
|
<p style="margin-top: -12px">
|
|
<a class="btn btn-mini btn-primary" style="width: 60px">« previous</a>
|
|
<a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next »</a>
|
|
</p>
|
|
-->
|
|
<div id="table-of-contents">
|
|
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
|
|
<li class="main"><a href="#filtering">Filtering</a></li>
|
|
<li><a href="#filtering-against-the-current-user">Filtering against the current user</a></li>
|
|
<li><a href="#filtering-against-the-url">Filtering against the URL</a></li>
|
|
<li><a href="#filtering-against-query-parameters">Filtering against query parameters</a></li>
|
|
<li class="main"><a href="#generic-filtering">Generic Filtering</a></li>
|
|
<li><a href="#setting-filter-backends">Setting filter backends</a></li>
|
|
<li><a href="#filtering-and-object-lookups">Filtering and object lookups</a></li>
|
|
<li><a href="#overriding-the-initial-queryset">Overriding the initial queryset</a></li>
|
|
<li class="main"><a href="#api-guide">API Guide</a></li>
|
|
<li><a href="#djangofilterbackend">DjangoFilterBackend</a></li>
|
|
<li><a href="#searchfilter">SearchFilter</a></li>
|
|
<li><a href="#orderingfilter">OrderingFilter</a></li>
|
|
<li><a href="#djangoobjectpermissionsfilter">DjangoObjectPermissionsFilter</a></li>
|
|
<li class="main"><a href="#custom-generic-filtering">Custom generic filtering</a></li>
|
|
<li><a href="#example">Example</a></li>
|
|
<li class="main"><a href="#third-party-packages">Third party packages</a></li>
|
|
<li><a href="#django-rest-framework-chain">Django REST framework chain</a></li>
|
|
|
|
<div class="promo">
|
|
|
|
</div>
|
|
</ul>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="main-content" class="span9">
|
|
<p><a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/filters.py"><span class="label label-info">filters.py</span></a></p>
|
|
<h1 id="filtering">Filtering</h1>
|
|
<blockquote>
|
|
<p>The root QuerySet provided by the Manager describes all objects in the database table. Usually, though, you'll need to select only a subset of the complete set of objects.</p>
|
|
<p>— <a href="https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters">Django documentation</a></p>
|
|
</blockquote>
|
|
<p>The default behavior of REST framework's generic list views is to return the entire queryset for a model manager. Often you will want your API to restrict the items that are returned by the queryset.</p>
|
|
<p>The simplest way to filter the queryset of any view that subclasses <code>GenericAPIView</code> is to override the <code>.get_queryset()</code> method.</p>
|
|
<p>Overriding this method allows you to customize the queryset returned by the view in a number of different ways.</p>
|
|
<h2 id="filtering-against-the-current-user">Filtering against the current user</h2>
|
|
<p>You might want to filter the queryset to ensure that only results relevant to the currently authenticated user making the request are returned.</p>
|
|
<p>You can do so by filtering based on the value of <code>request.user</code>.</p>
|
|
<p>For example:</p>
|
|
<pre class="prettyprint lang-py"><code>from myapp.models import Purchase
|
|
from myapp.serializers import PurchaseSerializer
|
|
from rest_framework import generics
|
|
|
|
class PurchaseList(generics.ListAPIView):
|
|
serializer_class = PurchaseSerializer
|
|
|
|
def get_queryset(self):
|
|
"""
|
|
This view should return a list of all the purchases
|
|
for the currently authenticated user.
|
|
"""
|
|
user = self.request.user
|
|
return Purchase.objects.filter(purchaser=user)
|
|
</code></pre>
|
|
<h2 id="filtering-against-the-url">Filtering against the URL</h2>
|
|
<p>Another style of filtering might involve restricting the queryset based on some part of the URL. </p>
|
|
<p>For example if your URL config contained an entry like this:</p>
|
|
<pre class="prettyprint lang-py"><code>url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
|
|
</code></pre>
|
|
<p>You could then write a view that returned a purchase queryset filtered by the username portion of the URL:</p>
|
|
<pre class="prettyprint lang-py"><code>class PurchaseList(generics.ListAPIView):
|
|
serializer_class = PurchaseSerializer
|
|
|
|
def get_queryset(self):
|
|
"""
|
|
This view should return a list of all the purchases for
|
|
the user as determined by the username portion of the URL.
|
|
"""
|
|
username = self.kwargs['username']
|
|
return Purchase.objects.filter(purchaser__username=username)
|
|
</code></pre>
|
|
<h2 id="filtering-against-query-parameters">Filtering against query parameters</h2>
|
|
<p>A final example of filtering the initial queryset would be to determine the initial queryset based on query parameters in the url.</p>
|
|
<p>We can override <code>.get_queryset()</code> to deal with URLs such as <code>http://example.com/api/purchases?username=denvercoder9</code>, and filter the queryset only if the <code>username</code> parameter is included in the URL:</p>
|
|
<pre class="prettyprint lang-py"><code>class PurchaseList(generics.ListAPIView):
|
|
serializer_class = PurchaseSerializer
|
|
|
|
def get_queryset(self):
|
|
"""
|
|
Optionally restricts the returned purchases to a given user,
|
|
by filtering against a `username` query parameter in the URL.
|
|
"""
|
|
queryset = Purchase.objects.all()
|
|
username = self.request.QUERY_PARAMS.get('username', None)
|
|
if username is not None:
|
|
queryset = queryset.filter(purchaser__username=username)
|
|
return queryset
|
|
</code></pre>
|
|
<hr />
|
|
<h1 id="generic-filtering">Generic Filtering</h1>
|
|
<p>As well as being able to override the default queryset, REST framework also includes support for generic filtering backends that allow you to easily construct complex searches and filters.</p>
|
|
<h2 id="setting-filter-backends">Setting filter backends</h2>
|
|
<p>The default filter backends may be set globally, using the <code>DEFAULT_FILTER_BACKENDS</code> setting. For example.</p>
|
|
<pre class="prettyprint lang-py"><code>REST_FRAMEWORK = {
|
|
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
|
|
}
|
|
</code></pre>
|
|
<p>You can also set the filter backends on a per-view, or per-viewset basis,
|
|
using the <code>GenericAPIView</code> class based views.</p>
|
|
<pre class="prettyprint lang-py"><code>from django.contrib.auth.models import User
|
|
from myapp.serializers import UserSerializer
|
|
from rest_framework import filters
|
|
from rest_framework import generics
|
|
|
|
class UserListView(generics.ListAPIView):
|
|
queryset = User.objects.all()
|
|
serializer = UserSerializer
|
|
filter_backends = (filters.DjangoFilterBackend,)
|
|
</code></pre>
|
|
<h2 id="filtering-and-object-lookups">Filtering and object lookups</h2>
|
|
<p>Note that if a filter backend is configured for a view, then as well as being used to filter list views, it will also be used to filter the querysets used for returning a single object.</p>
|
|
<p>For instance, given the previous example, and a product with an id of <code>4675</code>, the following URL would either return the corresponding object, or return a 404 response, depending on if the filtering conditions were met by the given product instance:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/products/4675/?category=clothing&max_price=10.00
|
|
</code></pre>
|
|
<h2 id="overriding-the-initial-queryset">Overriding the initial queryset</h2>
|
|
<p>Note that you can use both an overridden <code>.get_queryset()</code> and generic filtering together, and everything will work as expected. For example, if <code>Product</code> had a many-to-many relationship with <code>User</code>, named <code>purchase</code>, you might want to write a view like this:</p>
|
|
<pre class="prettyprint lang-py"><code>class PurchasedProductsList(generics.ListAPIView):
|
|
"""
|
|
Return a list of all the products that the authenticated
|
|
user has ever purchased, with optional filtering.
|
|
"""
|
|
model = Product
|
|
serializer_class = ProductSerializer
|
|
filter_class = ProductFilter
|
|
|
|
def get_queryset(self):
|
|
user = self.request.user
|
|
return user.purchase_set.all()
|
|
</code></pre>
|
|
<hr />
|
|
<h1 id="api-guide">API Guide</h1>
|
|
<h2 id="djangofilterbackend">DjangoFilterBackend</h2>
|
|
<p>The <code>DjangoFilterBackend</code> class supports highly customizable field filtering, using the <a href="https://github.com/alex/django-filter">django-filter package</a>. </p>
|
|
<p>To use REST framework's <code>DjangoFilterBackend</code>, first install <code>django-filter</code>.</p>
|
|
<pre class="prettyprint lang-py"><code>pip install django-filter
|
|
</code></pre>
|
|
<h4 id="specifying-filter-fields">Specifying filter fields</h4>
|
|
<p>If all you need is simple equality-based filtering, you can set a <code>filter_fields</code> attribute on the view, or viewset, listing the set of fields you wish to filter against.</p>
|
|
<pre class="prettyprint lang-py"><code>class ProductList(generics.ListAPIView):
|
|
queryset = Product.objects.all()
|
|
serializer_class = ProductSerializer
|
|
filter_fields = ('category', 'in_stock')
|
|
</code></pre>
|
|
<p>This will automatically create a <code>FilterSet</code> class for the given fields, and will allow you to make requests such as:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/products?category=clothing&in_stock=True
|
|
</code></pre>
|
|
<h4 id="specifying-a-filterset">Specifying a FilterSet</h4>
|
|
<p>For more advanced filtering requirements you can specify a <code>FilterSet</code> class that should be used by the view. For example:</p>
|
|
<pre class="prettyprint lang-py"><code>import django_filters
|
|
from myapp.models import Product
|
|
from myapp.serializers import ProductSerializer
|
|
from rest_framework import generics
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
min_price = django_filters.NumberFilter(name="price", lookup_type='gte')
|
|
max_price = django_filters.NumberFilter(name="price", lookup_type='lte')
|
|
class Meta:
|
|
model = Product
|
|
fields = ['category', 'in_stock', 'min_price', 'max_price']
|
|
|
|
class ProductList(generics.ListAPIView):
|
|
queryset = Product.objects.all()
|
|
serializer_class = ProductSerializer
|
|
filter_class = ProductFilter
|
|
</code></pre>
|
|
<p>Which will allow you to make requests such as:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/products?category=clothing&max_price=10.00
|
|
</code></pre>
|
|
<p>You can also span relationships using <code>django-filter</code>, let's assume that each
|
|
product has foreign key to <code>Manufacturer</code> model, so we create filter that
|
|
filters using <code>Manufacturer</code> name. For example:</p>
|
|
<pre class="prettyprint lang-py"><code>import django_filters
|
|
from myapp.models import Product
|
|
from myapp.serializers import ProductSerializer
|
|
from rest_framework import generics
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
class Meta:
|
|
model = Product
|
|
fields = ['category', 'in_stock', 'manufacturer__name`]
|
|
</code></pre>
|
|
<p>This enables us to make queries like:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/products?manufacturer__name=foo
|
|
</code></pre>
|
|
<p>This is nice, but it exposes the Django's double underscore convention as part of the API. If you instead want to explicitly name the filter argument you can instead explicitly include it on the <code>FilterSet</code> class:</p>
|
|
<pre class="prettyprint lang-py"><code>import django_filters
|
|
from myapp.models import Product
|
|
from myapp.serializers import ProductSerializer
|
|
from rest_framework import generics
|
|
|
|
class ProductFilter(django_filters.FilterSet):
|
|
manufacturer = django_filters.CharFilter(name="manufacturer__name")
|
|
|
|
class Meta:
|
|
model = Product
|
|
fields = ['category', 'in_stock', 'manufacturer`]
|
|
</code></pre>
|
|
<p>And now you can execute:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/products?manufacturer=foo
|
|
</code></pre>
|
|
<p>For more details on using filter sets see the <a href="https://django-filter.readthedocs.org/en/latest/index.html">django-filter documentation</a>.</p>
|
|
<hr />
|
|
<p><strong>Hints & Tips</strong></p>
|
|
<ul>
|
|
<li>By default filtering is not enabled. If you want to use <code>DjangoFilterBackend</code> remember to make sure it is installed by using the <code>'DEFAULT_FILTER_BACKENDS'</code> setting.</li>
|
|
<li>When using boolean fields, you should use the values <code>True</code> and <code>False</code> in the URL query parameters, rather than <code>0</code>, <code>1</code>, <code>true</code> or <code>false</code>. (The allowed boolean values are currently hardwired in Django's <a href="https://github.com/django/django/blob/master/django/forms/widgets.py">NullBooleanSelect implementation</a>.) </li>
|
|
<li><code>django-filter</code> supports filtering across relationships, using Django's double-underscore syntax.</li>
|
|
<li>For Django 1.3 support, make sure to install <code>django-filter</code> version 0.5.4, as later versions drop support for 1.3.</li>
|
|
</ul>
|
|
<hr />
|
|
<h2 id="searchfilter">SearchFilter</h2>
|
|
<p>The <code>SearchFilter</code> class supports simple single query parameter based searching, and is based on the <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields">Django admin's search functionality</a>.</p>
|
|
<p>The <code>SearchFilter</code> class will only be applied if the view has a <code>search_fields</code> attribute set. The <code>search_fields</code> attribute should be a list of names of text type fields on the model, such as <code>CharField</code> or <code>TextField</code>.</p>
|
|
<pre class="prettyprint lang-py"><code>class UserListView(generics.ListAPIView):
|
|
queryset = User.objects.all()
|
|
serializer = UserSerializer
|
|
filter_backends = (filters.SearchFilter,)
|
|
search_fields = ('username', 'email')
|
|
</code></pre>
|
|
<p>This will allow the client to filter the items in the list by making queries such as:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/users?search=russell
|
|
</code></pre>
|
|
<p>You can also perform a related lookup on a ForeignKey or ManyToManyField with the lookup API double-underscore notation:</p>
|
|
<pre class="prettyprint lang-py"><code>search_fields = ('username', 'email', 'profile__profession')
|
|
</code></pre>
|
|
<p>By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched.</p>
|
|
<p>The search behavior may be restricted by prepending various characters to the <code>search_fields</code>.</p>
|
|
<ul>
|
|
<li>'^' Starts-with search.</li>
|
|
<li>'=' Exact matches.</li>
|
|
<li>'@' Full-text search. (Currently only supported Django's MySQL backend.)</li>
|
|
</ul>
|
|
<p>For example:</p>
|
|
<pre class="prettyprint lang-py"><code>search_fields = ('=username', '=email')
|
|
</code></pre>
|
|
<p>By default, the search parameter is named <code>'search</code>', but this may be overridden with the <code>SEARCH_PARAM</code> setting.</p>
|
|
<p>For more details, see the <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields">Django documentation</a>.</p>
|
|
<hr />
|
|
<h2 id="orderingfilter">OrderingFilter</h2>
|
|
<p>The <code>OrderingFilter</code> class supports simple query parameter controlled ordering of results. By default, the query parameter is named <code>'ordering'</code>, but this may by overridden with the <code>ORDERING_PARAM</code> setting.</p>
|
|
<p>For example, to order users by username:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/users?ordering=username
|
|
</code></pre>
|
|
<p>The client may also specify reverse orderings by prefixing the field name with '-', like so:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/users?ordering=-username
|
|
</code></pre>
|
|
<p>Multiple orderings may also be specified:</p>
|
|
<pre class="prettyprint lang-py"><code>http://example.com/api/users?ordering=account,username
|
|
</code></pre>
|
|
<h3 id="specifying-which-fields-may-be-ordered-against">Specifying which fields may be ordered against</h3>
|
|
<p>It's recommended that you explicitly specify which fields the API should allowing in the ordering filter. You can do this by setting an <code>ordering_fields</code> attribute on the view, like so:</p>
|
|
<pre class="prettyprint lang-py"><code>class UserListView(generics.ListAPIView):
|
|
queryset = User.objects.all()
|
|
serializer_class = UserSerializer
|
|
filter_backends = (filters.OrderingFilter,)
|
|
ordering_fields = ('username', 'email')
|
|
</code></pre>
|
|
<p>This helps prevent unexpected data leakage, such as allowing users to order against a password hash field or other sensitive data.</p>
|
|
<p>If you <em>don't</em> specify an <code>ordering_fields</code> attribute on the view, the filter class will default to allowing the user to filter on any readable fields on the serializer specified by the <code>serializer_class</code> attribute.</p>
|
|
<p>If you are confident that the queryset being used by the view doesn't contain any sensitive data, you can also explicitly specify that a view should allow ordering on <em>any</em> model field or queryset aggregate, by using the special value <code>'__all__'</code>.</p>
|
|
<pre class="prettyprint lang-py"><code>class BookingsListView(generics.ListAPIView):
|
|
queryset = Booking.objects.all()
|
|
serializer_class = BookingSerializer
|
|
filter_backends = (filters.OrderingFilter,)
|
|
ordering_fields = '__all__'
|
|
</code></pre>
|
|
<h3 id="specifying-a-default-ordering">Specifying a default ordering</h3>
|
|
<p>If an <code>ordering</code> attribute is set on the view, this will be used as the default ordering.</p>
|
|
<p>Typically you'd instead control this by setting <code>order_by</code> on the initial queryset, but using the <code>ordering</code> parameter on the view allows you to specify the ordering in a way that it can then be passed automatically as context to a rendered template. This makes it possible to automatically render column headers differently if they are being used to order the results.</p>
|
|
<pre class="prettyprint lang-py"><code>class UserListView(generics.ListAPIView):
|
|
queryset = User.objects.all()
|
|
serializer_class = UserSerializer
|
|
filter_backends = (filters.OrderingFilter,)
|
|
ordering = ('username',)
|
|
</code></pre>
|
|
<p>The <code>ordering</code> attribute may be either a string or a list/tuple of strings.</p>
|
|
<hr />
|
|
<h2 id="djangoobjectpermissionsfilter">DjangoObjectPermissionsFilter</h2>
|
|
<p>The <code>DjangoObjectPermissionsFilter</code> is intended to be used together with the <a href="http://pythonhosted.org/django-guardian/"><code>django-guardian</code></a> package, with custom <code>'view'</code> permissions added. The filter will ensure that querysets only returns objects for which the user has the appropriate view permission.</p>
|
|
<p>This filter class must be used with views that provide either a <code>queryset</code> or a <code>model</code> attribute.</p>
|
|
<p>If you're using <code>DjangoObjectPermissionsFilter</code>, you'll probably also want to add an appropriate object permissions class, to ensure that users can only operate on instances if they have the appropriate object permissions. The easiest way to do this is to subclass <code>DjangoObjectPermissions</code> and add <code>'view'</code> permissions to the <code>perms_map</code> attribute.</p>
|
|
<p>A complete example using both <code>DjangoObjectPermissionsFilter</code> and <code>DjangoObjectPermissions</code> might look something like this.</p>
|
|
<p><strong>permissions.py</strong>:</p>
|
|
<pre class="prettyprint lang-py"><code>class CustomObjectPermissions(permissions.DjangoObjectPermissions):
|
|
"""
|
|
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
|
|
"""
|
|
perms_map = {
|
|
'GET': ['%(app_label)s.view_%(model_name)s'],
|
|
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
|
|
'HEAD': ['%(app_label)s.view_%(model_name)s'],
|
|
'POST': ['%(app_label)s.add_%(model_name)s'],
|
|
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
|
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
|
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
|
}
|
|
</code></pre>
|
|
<p><strong>views.py</strong>:</p>
|
|
<pre class="prettyprint lang-py"><code>class EventViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
Viewset that only lists events if user has 'view' permissions, and only
|
|
allows operations on individual events if user has appropriate 'view', 'add',
|
|
'change' or 'delete' permissions.
|
|
"""
|
|
queryset = Event.objects.all()
|
|
serializer = EventSerializer
|
|
filter_backends = (filters.DjangoObjectPermissionsFilter,)
|
|
permission_classes = (myapp.permissions.CustomObjectPermissions,)
|
|
</code></pre>
|
|
<p>For more information on adding <code>'view'</code> permissions for models, see the <a href="http://pythonhosted.org/django-guardian/userguide/assign.html">relevant section</a> of the <code>django-guardian</code> documentation, and <a href="http://blog.nyaruka.com/adding-a-view-permission-to-django-models">this blogpost</a>.</p>
|
|
<hr />
|
|
<h1 id="custom-generic-filtering">Custom generic filtering</h1>
|
|
<p>You can also provide your own generic filtering backend, or write an installable app for other developers to use.</p>
|
|
<p>To do so override <code>BaseFilterBackend</code>, and override the <code>.filter_queryset(self, request, queryset, view)</code> method. The method should return a new, filtered queryset.</p>
|
|
<p>As well as allowing clients to perform searches and filtering, generic filter backends can be useful for restricting which objects should be visible to any given request or user.</p>
|
|
<h2 id="example">Example</h2>
|
|
<p>For example, you might need to restrict users to only being able to see objects they created.</p>
|
|
<pre class="prettyprint lang-py"><code>class IsOwnerFilterBackend(filters.BaseFilterBackend):
|
|
"""
|
|
Filter that only allows users to see their own objects.
|
|
"""
|
|
def filter_queryset(self, request, queryset, view):
|
|
return queryset.filter(owner=request.user)
|
|
</code></pre>
|
|
<p>We could achieve the same behavior by overriding <code>get_queryset()</code> on the views, but using a filter backend allows you to more easily add this restriction to multiple views, or to apply it across the entire API.</p>
|
|
<h1 id="third-party-packages">Third party packages</h1>
|
|
<p>The following third party packages provide additional filter implementations.</p>
|
|
<h2 id="django-rest-framework-chain">Django REST framework chain</h2>
|
|
<p>The <a href="https://github.com/philipn/django-rest-framework-chain">django-rest-framework-chain package</a> works together with the <code>DjangoFilterBackend</code> class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field.</p>
|
|
</div><!--/span-->
|
|
</div><!--/row-->
|
|
</div><!--/.fluid-container-->
|
|
</div><!--/.body content-->
|
|
|
|
<div id="push"></div>
|
|
</div><!--/.wrapper -->
|
|
|
|
<footer class="span12">
|
|
<p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</a></p>
|
|
</footer>
|
|
|
|
<!-- Le javascript
|
|
================================================== -->
|
|
<!-- Placed at the end of the document so the pages load faster -->
|
|
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/jquery-1.8.1-min.js"></script>
|
|
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/prettify-1.0.js"></script>
|
|
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/bootstrap-2.1.1-min.js"></script>
|
|
|
|
<script>
|
|
//$('.side-nav').scrollspy()
|
|
var shiftWindow = function() { scrollBy(0, -50) };
|
|
if (location.hash) shiftWindow();
|
|
window.addEventListener("hashchange", shiftWindow);
|
|
|
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
|
event.stopPropagation();
|
|
});
|
|
|
|
// Dynamically force sidenav to no higher than browser window
|
|
$('.side-nav').css('max-height', window.innerHeight - 130);
|
|
|
|
$(function(){
|
|
$(window).resize(function(){
|
|
$('.side-nav').css('max-height', window.innerHeight - 130);
|
|
});
|
|
});
|
|
</script>
|
|
</body></html>
|