diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b88eb71..e9de9e4 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -5,6 +5,7 @@ Changelog ~~~~~~~~~~~~~~~~ * Support filtering with Q objects (Shohan Dutta Roy) * Support random ordering with `.order_by("?")` (Shohan Dutta Roy) +* Support `distinct()` on querysets (Shohan Dutta Roy) * Fix: Correctly handle filtering on fields on related models when those fields have names that match a lookup type (Andy Babic) * Fix: Correctly handle null foreign keys when traversing related fields (Andy Babic) diff --git a/modelcluster/queryset.py b/modelcluster/queryset.py index 85a1992..d3da065 100644 --- a/modelcluster/queryset.py +++ b/modelcluster/queryset.py @@ -672,6 +672,18 @@ def order_by(self, *fields): sort_by_fields(clone.results, fields) return clone + def distinct(self, *fields): + unique_results = [] + if not fields: + fields = [field.name for field in self.model._meta.fields if not field.primary_key] + seen_keys = set() + for result in self.results: + key = tuple(str(extract_field_value(result, field)) for field in fields) + if key not in seen_keys: + seen_keys.add(key) + unique_results.append(result) + return self.get_clone(results=unique_results) + # a standard QuerySet will store the results in _result_cache on running the query; # this is effectively the same as self.results on a FakeQuerySet, and so we'll make # _result_cache an alias of self.results for the benefit of Django internals that diff --git a/tests/tests/test_cluster.py b/tests/tests/test_cluster.py index 8cce2ca..e78d4ed 100644 --- a/tests/tests/test_cluster.py +++ b/tests/tests/test_cluster.py @@ -864,6 +864,28 @@ def test_meta_ordering(self): albums = [album.name for album in beatles.albums.all()] self.assertEqual(['With The Beatles', 'Please Please Me', 'Abbey Road'], albums) + def test_distinct_with_no_fields(self): + beatles = Band(name='The Beatles', albums=[ + Album(name='Please Please Me', sort_order=1), + Album(name='With The Beatles', sort_order=2), + Album(name='Abbey Road', sort_order=2), + ]) + + albums = [album.name for album in beatles.albums.order_by('sort_order').distinct()] + self.assertEqual(['Please Please Me', 'With The Beatles', 'Abbey Road'], albums) + + def test_distinct_with_fields(self): + beatles = Band(name='The Beatles', albums=[ + Album(name='Please Please Me', sort_order=1), + Album(name='With The Beatles', sort_order=2), + Album(name='Abbey Road', sort_order=2), + ]) + albums = [album.name for album in beatles.albums.order_by('sort_order').distinct('sort_order')] + self.assertEqual(['Please Please Me', 'With The Beatles'], albums) + + albums = [album.name for album in beatles.albums.order_by('sort_order').distinct('name')] + self.assertEqual(['Please Please Me', 'With The Beatles', 'Abbey Road'], albums) + def test_parental_key_checks_clusterable_model(self): from django.core import checks from django.db import models