mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 20:40:14 +03:00
Merge branch 'master' into localizedfloatfield
This commit is contained in:
commit
855dfcd2f2
|
@ -160,16 +160,6 @@ Or add the filter backend to an individual View or ViewSet.
|
|||
...
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
|
||||
If you are using the browsable API or admin API you may also want to install `django-crispy-forms`, which will enhance the presentation of the filter forms in HTML views, by allowing them to render Bootstrap 3 HTML.
|
||||
|
||||
pip install django-crispy-forms
|
||||
|
||||
With crispy forms installed and added to Django's `INSTALLED_APPS`, the browsable API will present a filtering control for `DjangoFilterBackend`, like so:
|
||||
|
||||

|
||||
|
||||
#### Specifying filter fields
|
||||
|
||||
If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, or viewset, listing the set of fields you wish to filter against.
|
||||
|
||||
class ProductList(generics.ListAPIView):
|
||||
|
@ -182,80 +172,10 @@ This will automatically create a `FilterSet` class for the given fields, and wil
|
|||
|
||||
http://example.com/api/products?category=clothing&in_stock=True
|
||||
|
||||
#### Specifying a FilterSet
|
||||
For more advanced filtering requirements you can specify a `FilterSet` class that should be used by the view.
|
||||
You can read more about `FilterSet`s in the [django-filter documentation][django-filter-docs].
|
||||
It's also recommended that you read the section on [DRF integration][django-filter-drf-docs].
|
||||
|
||||
For more advanced filtering requirements you can specify a `FilterSet` class that should be used by the view. For example:
|
||||
|
||||
import django_filters
|
||||
from myapp.models import Product
|
||||
from myapp.serializers import ProductSerializer
|
||||
from rest_framework import generics
|
||||
|
||||
class ProductFilter(django_filters.rest_framework.FilterSet):
|
||||
min_price = django_filters.NumberFilter(name="price", lookup_expr='gte')
|
||||
max_price = django_filters.NumberFilter(name="price", lookup_expr='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_backends = (django_filters.rest_framework.DjangoFilterBackend,)
|
||||
filter_class = ProductFilter
|
||||
|
||||
|
||||
Which will allow you to make requests such as:
|
||||
|
||||
http://example.com/api/products?category=clothing&max_price=10.00
|
||||
|
||||
You can also span relationships using `django-filter`, let's assume that each
|
||||
product has foreign key to `Manufacturer` model, so we create filter that
|
||||
filters using `Manufacturer` name. For example:
|
||||
|
||||
import django_filters
|
||||
from myapp.models import Product
|
||||
from myapp.serializers import ProductSerializer
|
||||
from rest_framework import generics
|
||||
|
||||
class ProductFilter(django_filters.rest_framework.FilterSet):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ['category', 'in_stock', 'manufacturer__name']
|
||||
|
||||
This enables us to make queries like:
|
||||
|
||||
http://example.com/api/products?manufacturer__name=foo
|
||||
|
||||
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 `FilterSet` class:
|
||||
|
||||
import django_filters
|
||||
from myapp.models import Product
|
||||
from myapp.serializers import ProductSerializer
|
||||
from rest_framework import generics
|
||||
|
||||
class ProductFilter(django_filters.rest_framework.FilterSet):
|
||||
manufacturer = django_filters.CharFilter(name="manufacturer__name")
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ['category', 'in_stock', 'manufacturer']
|
||||
|
||||
And now you can execute:
|
||||
|
||||
http://example.com/api/products?manufacturer=foo
|
||||
|
||||
For more details on using filter sets see the [django-filter documentation][django-filter-docs].
|
||||
|
||||
---
|
||||
|
||||
**Hints & Tips**
|
||||
|
||||
* By default filtering is not enabled. If you want to use `DjangoFilterBackend` remember to make sure it is installed by using the `'DEFAULT_FILTER_BACKENDS'` setting.
|
||||
* When using boolean fields, you should use the values `True` and `False` in the URL query parameters, rather than `0`, `1`, `true` or `false`. (The allowed boolean values are currently hardwired in Django's [NullBooleanSelect implementation][nullbooleanselect].)
|
||||
* `django-filter` supports filtering across relationships, using Django's double-underscore syntax.
|
||||
|
||||
---
|
||||
|
||||
## SearchFilter
|
||||
|
||||
|
@ -461,6 +381,7 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
|
|||
[cite]: https://docs.djangoproject.com/en/stable/topics/db/queries/#retrieving-specific-objects-with-filters
|
||||
[django-filter]: https://github.com/alex/django-filter
|
||||
[django-filter-docs]: https://django-filter.readthedocs.io/en/latest/index.html
|
||||
[django-filter-drf-docs]: https://django-filter.readthedocs.io/en/develop/guide/rest_framework.html
|
||||
[guardian]: https://django-guardian.readthedocs.io/
|
||||
[view-permissions]: https://django-guardian.readthedocs.io/en/latest/userguide/assign.html
|
||||
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
|
||||
|
|
|
@ -270,7 +270,7 @@ Let's take a look at the routes our `CustomReadOnlyRouter` would generate for a
|
|||
lookup_field = 'username'
|
||||
|
||||
@detail_route()
|
||||
def group_names(self, request):
|
||||
def group_names(self, request, pk=None):
|
||||
"""
|
||||
Returns a list of all the group names that the given
|
||||
user belongs to.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
|
@ -261,7 +261,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
* [drf-haystack][drf-haystack] - Haystack search for Django Rest Framework
|
||||
* [django-rest-framework-version-transforms][django-rest-framework-version-transforms] - Enables the use of delta transformations for versioning of DRF resource representations.
|
||||
* [django-rest-messaging][django-rest-messaging], [django-rest-messaging-centrifugo][django-rest-messaging-centrifugo] and [django-rest-messaging-js][django-rest-messaging-js] - A real-time pluggable messaging service using DRM.
|
||||
|
||||
* [djangorest-alchemy][djangorest-alchemy] - SQLAlchemy support for REST framework.
|
||||
|
||||
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
|
||||
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
|
||||
|
@ -332,3 +332,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
||||
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields
|
||||
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
|
||||
[djangorest-alchemy]: https://github.com/dealertrack/djangorest-alchemy
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Optional packages which may be used with REST framework.
|
||||
markdown==2.6.4
|
||||
django-guardian==1.4.8
|
||||
django-filter==1.0.0
|
||||
django-filter==1.0.2
|
||||
coreapi==2.2.4
|
||||
coreschema==0.0.4
|
||||
|
|
|
@ -50,12 +50,17 @@ if django_filters:
|
|||
DeprecationWarning
|
||||
)
|
||||
return super(FilterSet, self).__init__(*args, **kwargs)
|
||||
|
||||
DFBase = django_filters.rest_framework.DjangoFilterBackend
|
||||
|
||||
else:
|
||||
def FilterSet():
|
||||
assert False, 'django-filter must be installed to use the `FilterSet` class'
|
||||
|
||||
DFBase = BaseFilterBackend
|
||||
|
||||
class DjangoFilterBackend(BaseFilterBackend):
|
||||
|
||||
class DjangoFilterBackend(DFBase):
|
||||
"""
|
||||
A filter backend that uses django-filter.
|
||||
"""
|
||||
|
@ -69,9 +74,7 @@ class DjangoFilterBackend(BaseFilterBackend):
|
|||
DeprecationWarning
|
||||
)
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
|
||||
return DjangoFilterBackend(*args, **kwargs)
|
||||
return super(DjangoFilterBackend, cls).__new__(cls, *args, **kwargs)
|
||||
|
||||
|
||||
class SearchFilter(BaseFilterBackend):
|
||||
|
|
|
@ -246,7 +246,9 @@ class EndpointInspector(object):
|
|||
Return a list of the valid HTTP methods for this endpoint.
|
||||
"""
|
||||
if hasattr(callback, 'actions'):
|
||||
return [method.upper() for method in callback.actions.keys()]
|
||||
actions = set(callback.actions.keys())
|
||||
http_method_names = set(callback.cls.http_method_names)
|
||||
return [method.upper() for method in actions & http_method_names]
|
||||
|
||||
return [
|
||||
method for method in
|
||||
|
|
|
@ -138,7 +138,7 @@ def get_field_kwargs(field_name, model_field):
|
|||
if not isinstance(validator, validators.MaxValueValidator)
|
||||
]
|
||||
|
||||
# Ensure that max_value is passed explicitly as a keyword arg,
|
||||
# Ensure that min_value is passed explicitly as a keyword arg,
|
||||
# rather than as a validator.
|
||||
min_value = next((
|
||||
validator.limit_value for validator in validator_kwarg
|
||||
|
|
|
@ -201,6 +201,21 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
|
|||
assert response.data == self.data
|
||||
assert len(w) == 0
|
||||
|
||||
@unittest.skipUnless(django_filters, 'django-filter not installed')
|
||||
def test_backend_mro(self):
|
||||
class CustomBackend(filters.DjangoFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
assert False, "custom filter_queryset should run"
|
||||
|
||||
class DFFilterFieldsRootView(FilterFieldsRootView):
|
||||
filter_backends = (CustomBackend,)
|
||||
|
||||
view = DFFilterFieldsRootView.as_view()
|
||||
request = factory.get('/')
|
||||
|
||||
with pytest.raises(AssertionError, message="custom filter_queryset should run"):
|
||||
view(request).render()
|
||||
|
||||
@unittest.skipUnless(django_filters, 'django-filter not installed')
|
||||
def test_get_filtered_fields_root_view(self):
|
||||
"""
|
||||
|
|
|
@ -246,6 +246,11 @@ class PermissionDeniedExampleViewSet(ExampleViewSet):
|
|||
permission_classes = [DenyAllUsingPermissionDenied]
|
||||
|
||||
|
||||
class MethodLimitedViewSet(ExampleViewSet):
|
||||
permission_classes = []
|
||||
http_method_names = ['get', 'head', 'options']
|
||||
|
||||
|
||||
class ExampleListView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
|
@ -368,6 +373,61 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
|
|||
assert schema == expected
|
||||
|
||||
|
||||
@unittest.skipUnless(coreapi, 'coreapi is not installed')
|
||||
class TestSchemaGeneratorWithMethodLimitedViewSets(TestCase):
|
||||
def setUp(self):
|
||||
router = DefaultRouter()
|
||||
router.register('example1', MethodLimitedViewSet, base_name='example1')
|
||||
self.patterns = [
|
||||
url(r'^', include(router.urls))
|
||||
]
|
||||
|
||||
def test_schema_for_regular_views(self):
|
||||
"""
|
||||
Ensure that schema generation works for ViewSet classes
|
||||
with method limitation by Django CBV's http_method_names attribute
|
||||
"""
|
||||
generator = SchemaGenerator(title='Example API', patterns=self.patterns)
|
||||
request = factory.get('/example1/')
|
||||
schema = generator.get_schema(Request(request))
|
||||
|
||||
expected = coreapi.Document(
|
||||
url='http://testserver/example1/',
|
||||
title='Example API',
|
||||
content={
|
||||
'example1': {
|
||||
'list': coreapi.Link(
|
||||
url='/example1/',
|
||||
action='get',
|
||||
fields=[
|
||||
coreapi.Field('page', required=False, location='query', schema=coreschema.Integer(title='Page', description='A page number within the paginated result set.')),
|
||||
coreapi.Field('page_size', required=False, location='query', schema=coreschema.Integer(title='Page size', description='Number of results to return per page.')),
|
||||
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
|
||||
]
|
||||
),
|
||||
'custom_list_action': coreapi.Link(
|
||||
url='/example1/custom_list_action/',
|
||||
action='get'
|
||||
),
|
||||
'custom_list_action_multiple_methods': {
|
||||
'read': coreapi.Link(
|
||||
url='/example1/custom_list_action_multiple_methods/',
|
||||
action='get'
|
||||
)
|
||||
},
|
||||
'read': coreapi.Link(
|
||||
url='/example1/{id}/',
|
||||
action='get',
|
||||
fields=[
|
||||
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
assert schema == expected
|
||||
|
||||
|
||||
@unittest.skipUnless(coreapi, 'coreapi is not installed')
|
||||
class TestSchemaGeneratorWithRestrictedViewSets(TestCase):
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue
Block a user