aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/api-guide/viewsets.md2
-rw-r--r--docs/topics/release-notes.md25
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/filters.py2
-rw-r--r--tests/test_filters.py50
5 files changed, 78 insertions, 3 deletions
diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md
index bbf92c6c..4fd7aa84 100644
--- a/docs/api-guide/viewsets.md
+++ b/docs/api-guide/viewsets.md
@@ -178,7 +178,7 @@ The actions provided by the `ModelViewSet` class are `.list()`, `.retrieve()`,
#### Example
-Because `ModelViewSet` extends `GenericAPIView`, you'll normally need to provide at least the `queryset` and `serializer_class` attributes, or the `model` attribute shortcut. For example:
+Because `ModelViewSet` extends `GenericAPIView`, you'll normally need to provide at least the `queryset` and `serializer_class` attributes. For example:
class AccountViewSet(viewsets.ModelViewSet):
"""
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 88732df8..51eb45c3 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -41,6 +41,19 @@ You can determine your currently installed version using `pip freeze`:
## 3.0.x series
+### 3.0.5
+
+**Date**: [10th February 2015][3.0.5-milestone].
+
+* Fix a bug where `_closable_objects` breaks pickling. ([#1850][gh1850], [#2492][gh2492])
+* Allow non-standard `User` models with `Throttling`. ([#2524][gh2524])
+* Support custom `User.db_table` in TokenAuthentication migration. ([#2479][gh2479])
+* Fix misleading `AttributeError` tracebacks on `Request` objects. ([#2530][gh2530], [#2108][gh2108])
+* `ManyRelatedField.get_value` clearing field on partial update. ([#2475][gh2475])
+* Removed '.model' shortcut from code. ([#2486][gh2486])
+* Fix `detail_route` and `list_route` mutable argument. ([#2518][gh2518])
+* Prefetching the user object when getting the token in `TokenAuthentication`. ([#2519][gh2519])
+
### 3.0.4
**Date**: [28th January 2015][3.0.4-milestone].
@@ -142,6 +155,7 @@ For older release notes, [please see the version 2.x documentation](old-release-
[3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22
[3.0.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.3+Release%22
[3.0.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.4+Release%22
+[3.0.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.5+Release%22
<!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
@@ -229,3 +243,14 @@ For older release notes, [please see the version 2.x documentation](old-release-
[gh2399]: https://github.com/tomchristie/django-rest-framework/issues/2399
[gh2388]: https://github.com/tomchristie/django-rest-framework/issues/2388
[gh2360]: https://github.com/tomchristie/django-rest-framework/issues/2360
+<!-- 3.0.5 -->
+[gh1850]: https://github.com/tomchristie/django-rest-framework/issues/1850
+[gh2108]: https://github.com/tomchristie/django-rest-framework/issues/2108
+[gh2475]: https://github.com/tomchristie/django-rest-framework/issues/2475
+[gh2479]: https://github.com/tomchristie/django-rest-framework/issues/2479
+[gh2486]: https://github.com/tomchristie/django-rest-framework/issues/2486
+[gh2492]: https://github.com/tomchristie/django-rest-framework/issues/2492
+[gh2518]: https://github.com/tomchristie/django-rest-framework/issues/2518
+[gh2519]: https://github.com/tomchristie/django-rest-framework/issues/2519
+[gh2524]: https://github.com/tomchristie/django-rest-framework/issues/2524
+[gh2530]: https://github.com/tomchristie/django-rest-framework/issues/2530
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 57e5421b..9b58f09f 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
-__version__ = '3.0.4'
+__version__ = '3.0.5'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2015 Tom Christie'
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index 2bcf3699..9a84efa2 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -104,7 +104,7 @@ class SearchFilter(BaseFilterBackend):
for search_term in self.get_search_terms(request):
or_queries = [models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups]
- queryset = queryset.filter(reduce(operator.or_, or_queries))
+ queryset = queryset.filter(reduce(operator.or_, or_queries)).distinct()
return queryset
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 355f02ce..e7cb0c79 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -429,6 +429,56 @@ class SearchFilterTests(TestCase):
reload_module(filters)
+class AttributeModel(models.Model):
+ label = models.CharField(max_length=32)
+
+
+class SearchFilterModelM2M(models.Model):
+ title = models.CharField(max_length=20)
+ text = models.CharField(max_length=100)
+ attributes = models.ManyToManyField(AttributeModel)
+
+
+class SearchFilterM2MSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = SearchFilterModelM2M
+
+
+class SearchFilterM2MTests(TestCase):
+ def setUp(self):
+ # Sequence of title/text/attributes is:
+ #
+ # z abc [1, 2, 3]
+ # zz bcd [1, 2, 3]
+ # zzz cde [1, 2, 3]
+ # ...
+ for idx in range(3):
+ label = 'w' * (idx + 1)
+ AttributeModel(label=label)
+
+ for idx in range(10):
+ title = 'z' * (idx + 1)
+ text = (
+ chr(idx + ord('a')) +
+ chr(idx + ord('b')) +
+ chr(idx + ord('c'))
+ )
+ SearchFilterModelM2M(title=title, text=text).save()
+ SearchFilterModelM2M.objects.get(title='zz').attributes.add(1, 2, 3)
+
+ def test_m2m_search(self):
+ class SearchListView(generics.ListAPIView):
+ queryset = SearchFilterModelM2M.objects.all()
+ serializer_class = SearchFilterM2MSerializer
+ filter_backends = (filters.SearchFilter,)
+ search_fields = ('=title', 'text', 'attributes__label')
+
+ view = SearchListView.as_view()
+ request = factory.get('/', {'search': 'zz'})
+ response = view(request)
+ self.assertEqual(len(response.data), 1)
+
+
class OrderingFilterModel(models.Model):
title = models.CharField(max_length=20)
text = models.CharField(max_length=100)