diff options
| -rw-r--r-- | docs/api-guide/viewsets.md | 2 | ||||
| -rw-r--r-- | docs/topics/release-notes.md | 25 | ||||
| -rw-r--r-- | rest_framework/__init__.py | 2 | ||||
| -rw-r--r-- | rest_framework/filters.py | 2 | ||||
| -rw-r--r-- | tests/test_filters.py | 50 |
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) |
