django-querysetsequence¶
Release v0.15 (What’s new? <changelog>).
Getting Started¶
django-querysetsequence adds helpers for treating multiple disparate QuerySet
obejcts as a single QuerySet
. This is useful for passing into APIs that only
accepted a single QuerySet
.
The QuerySetSequence
wrapper is used to combine multiple QuerySet
instances.
Overview¶
QuerySetSequence
aims to provide the same behavior as Django’s QuerySets
,
but applied across multiple QuerySet
instances.
Supported features:
- Methods that take a list of fields (e.g.
filter()
,exclude()
,get()
,order_by()
) must use fields that are common across all sub-QuerySets
. - Relationships across related models work (e.g.
'foo__bar'
,'foo'
, or'foo_id'
). syntax). - The sub-
QuerySets
are evaluated as late as possible (e.g. during iteration, slicing, pickling,repr()
/len()
/list()
/bool()
calls). - Public
QuerySet
API methods that are untested/unimplemented raiseNotImplementedError
.
Basic Usage¶
# Import QuerySetSequence
from queryset_sequence import QuerySetSequence
# Create QuerySets you want to chain.
from .models import SomeModel, OtherModel
# Chain them together.
query = QuerySetSequence(SomeModel.objects.all(), OtherModel.objects.all())
# Use query as if it were a QuerySet! E.g. in a ListView.
Project Information¶
django-querysetsequence is released under the ISC license, its documentation lives on Read the Docs, the code on GitHub, and the latest release on PyPI. It supports Python 3.6+, Django 2.2/3.1/3.2/4.0, and is optionally compatible with Django REST Framework 3.9+.
Some ways that you can contribute:
- Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
- Fork the repository on GitHub to start making your changes.
- Write a test which shows that the bug was fixed or that the feature works as expected.
- Send a pull request and bug the maintainer until it gets merged and published.
Full Table of Contents¶
Example usage¶
Below is a fuller example of how to use a QuerySetSequence
. Two similar, but
not identical models exist (Article
and Book
):
class Author(models.Model):
name = models.CharField(max_length=50)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)
def __str__(self):
return "%s by %s" % (self.title, self.author)
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.ForeignKey(Author)
release = models.DateField(auto_now_add=True)
def __str__(self):
return "%s by %s" % (self.title, self.author)
We’ll also want some data to illustrate how QuerySetSequence
works:
# Create some data.
alice = Author.objects.create(name='Alice')
article = Article.objects.create(title='Dancing with Django', author=alice)
bob = Author.objects.create(name='Bob')
article = Article.objects.create(title='Django-isms', author=bob)
article = Book.objects.create(title='Biography', author=bob)
# Create some QuerySets.
books = Book.objects.all()
articles = Article.objects.all()
By wrapping a QuerySet
of each into a QuerySetSequence
they can be treated
as a single QuerySet
, for example we can filter to a particular author’s work, or
alphabetize all all articles and books together.
# Combine them into a single iterable.
published_works = QuerySetSequence(books, articles)
# Find Bob's titles.
bob_works = published_works.filter(author=bob)
# Still an iterable.
print([w.title for w in bob_works]) # prints: ['Biography', 'Django-isms']
# Alphabetize the QuerySet.
published_works = published_works.order_by('title')
print([w.title for w in published_works]) # prints ['Biography', 'Dancing with Django', 'Django-isms']
Django REST Framework integration¶
django-querysetsequence comes with a custom CursorPagination
class that
helps integration with Django REST Framework. It is optimized to iterate over a
QuerySetSequence
first by QuerySet
and then by the normal ordering
configuration. This uses the optimized code-path for iteration that avoids
interleaving the individual QuerySets
. For example:
from queryset_sequence.pagination import SequenceCursorPagination
class PublicationPagination(SequenceCursorPagination):
ordering = ['author', 'title']
class PublicationViewSet(viewsets.ModelViewSet):
pagination_class = PublicationPagination
def get_queryset(self):
# This will return all Books first, then all Articles. Each of those
# is individually ordered by ``author``, then ``title``.
return QuerySetSequence(Book.objects.all(), Article.objects.all())
API Reference¶
Much of the QuerySet
API is implemented by QuerySetSequence
, but it is
not fully compatible.
Summary of Supported APIs¶
Method | Implemented? | Notes |
---|---|---|
filter() |
✓ | See [1] for information on the QuerySet lookup: '#' . |
exclude() |
✓ | See [1] for information on the QuerySet lookup: '#' . |
annotate() |
✓ | |
alias() |
✗ | |
order_by() |
✓ | Does not support random ordering (e.g. order_by('?') ). See [1] for
information on the QuerySet lookup: '#' . |
reverse() |
✓ | |
distinct() |
✓ | Does not support calling distinct() if there are multiple underlying
QuerySet instances of the same model. |
values() |
✓ | See [1] for information on including the QuerySet index: '#' . |
values_list() |
✓ | See [1] for information on including the QuerySet index: '#' . |
dates() |
✗ | |
datetimes() |
✗ | |
none() |
✓ | |
all() |
✓ | |
union() |
✗ | |
intersection() |
✗ | |
difference() |
✗ | |
select_related() |
✓ | |
prefetch_related() |
✓ | |
extra() |
✓ | |
defer() |
✓ | |
only() |
✓ | |
using() |
✓ | |
select_for_update() |
✗ | |
raw() |
✗ |
Operator | Implemented? | Notes |
---|---|---|
AND (& ) |
✓ | A QuerySetSequence can be combined with a QuerySet . The
QuerySets in the QuerySetSequence are filtered to ones matching
the same Model . Each of those is ANDed with the other QuerySet . |
OR (| ) |
✓ | A QuerySetSequence can be combined with a QuerySet or
QuerySetSequence . When combining with a QuerySet , it is added to
the QuerySetSequence . Combiningg with another QuerySetSequence
adds together the two underlying sets of QuerySets . |
Method | Implemented? | Notes |
---|---|---|
get() |
✓ | See [1] for information on the QuerySet lookup: '#' . |
create() |
✗ | Cannot be implemented in QuerySetSequence . |
get_or_create() |
✗ | Cannot be implemented in QuerySetSequence . |
update_or_create() |
✗ | Cannot be implemented in QuerySetSequence . |
bulk_create() |
✗ | Cannot be implemented in QuerySetSequence . |
bulk_update() |
✗ | Cannot be implemented in QuerySetSequence . |
count() |
✓ | |
in_bulk() |
✗ | Cannot be implemented in QuerySetSequence . |
iterator() |
✓ | |
latest() |
✓ | If no fields are given, get_latest_by on each model is required to
be identical. |
earliest() |
✓ | See the docuemntation for latest() . |
first() |
✓ | If no ordering is set this is essentially the same as calling
first() on the first QuerySet , if there is an ordering, the
result of first() for each QuerySet is compared and the “first”
value is returned. |
last() |
✓ | See the documentation for first() . |
aggregate() |
✗ | |
exists() |
✓ | |
contains() |
✓ | |
update() |
✓ | |
delete() |
✓ | |
as_manager() |
✓ | |
explain() |
✓ |
Method | Notes |
---|---|
get_querysets() |
Returns the list of QuerySet objects that comprise the sequence.
Note, if any methods have been called which modify the
QuerySetSequence , the QuerySet objects returned by this
method will be similarly modified. The order of the QuerySet
objects within the list is not guaranteed. |
[1] | (1, 2, 3, 4, 5, 6)
A few examples are below: # Order first by QuerySet, then by the value of the 'title' field.
QuerySetSequence(...).order_by('#', 'title')
# Filter out the first QuerySet.
QuerySetSequence(...).filter(**{'#__gt': 0})
Note Ordering first by Warning Not all lookups are supported when using
|
Changelog¶
0.15 (2021-12-10)¶
0.14 (2021-02-26)¶
Features¶
Bugfixes¶
- Support calling
filter()
withQ()
objects. Contributed by @jpic. (#76)
0.12 (2019-12-20)¶
0.11 (2019-04-25)¶
Miscellaneous¶
- Support Django 2.2. Contributed by @michael-k. (#51)
- Support Django REST Framework 3.9. Contributed by @michael-k. (#51)
- Support Python 3.7. Contributed by @michael-k. (#51)
- Drop support for Django REST Framework < 3.6.3. Contributed by @michael-k. (#51)
- Drop support for Python 3.4. Contributed by @michael-k. (#51)
0.10 (2018-10-09)¶
Features¶
- Support
first()
,last()
,latest()
, andearliest()
methods. (#40, #49) - Support the
&
and|
operators. (#41) - Support
defer()
andonly()
methods to control which fields are returned. (#44) - Support calling
using()
to switch databases for an entireQuerySetSequence
. (#44) - Support calling
extra()`, ``update()
, andannotate()
which get applied to eachQuerySet
. (#46, #47) - Support calling
explain()
on Django >= 2.1. (#48)
0.9 (2018-09-20)¶
Bugfixes¶
- Stop using the internals of QuerySet for better forward compatibility. This change
means that
QuerySetSequence
is no longer a sub-class ofQuerySet
and should improve interactions with other packages which modifyQuerySet
. (#38)
Miscellaneous¶
0.8 (2017-09-05)¶
Features¶
- Optimize iteration when not slicing a
QuerySetSequence
. Contributed by @EvgeneOskin. (#29)
Miscellaneous¶
- Support Django 1.11. Contributed by @michael-k. (#26, #32)
- Support Django REST Framework 3.5 and 3.6. (#26)
0.7.2 (2017-04-04)¶
0.7 (2016-10-20)¶
Features¶
- Allow filtering / querying / ordering by the order of the
QuerySets
in theQuerySetSequence
by using'#'
. This allows for additional optimizations when using third-party applications, e.g. Django REST Framework. (#10, #14, #15, #16) - Django REST Framework integration: includes a subclass of the
CursorPagination
from Django REST Framework underqueryset_sequence.pagination.SequenceCursorPagination
which is designed to work efficiently with aQuerySetSequence
by first ordering by internalQuerySet
, then by theordering
attribute. (#17) - Move
queryset_sequence
to an actual module in order to hide some implementation details. (#11)
0.6 (2016-06-07)¶
Features¶
- Allow specifying the
Model
to use when instantiating aQuerySetSequence
. This is required for compatibility with some third-party applications that check themodel
field for equality, e.g. when using theDjangoFilterBackend
with Django REST Framework. Contributed by @CountZachula. (#6) - Support
prefetch_related
. (#7)
0.5 (2016-02-21)¶
0.3 (2016-01-29)¶
Features¶
- Raises
NotImplementedError
forQuerySet
methods thatQuerySetSequence
does not implement. - Support
reverse()
to reverse the item ordering - Support
none()
to return anEmptyQuerySet
- Support
exists()
to check if aQuerySetSequence
has any results. - Support
select_related
to follow foreign-key relationships when generating results.
0.2.4 (2016-01-21)¶
Features¶
- Support
order_by()
that references a related model (e.g. aForeignKey
relationship usingfoo
orfoo_id
syntaxes) - Support
order_by()
that references a field on a related model (e.g.foo__bar
)
Miscellaneous¶
- Add support for Django 1.9.1
0.2 (2016-01-08)¶
Bugfixes¶
- Do not try to instantiate
EmptyQuerySet
.
Miscellaneous¶
- Fixed packaging for pypi.
0.1 (2016-01-07)¶
- Initial release to support Django 1.8.8
The initial commits on based on DjangoSnippets and other code:
- DjangoSnippet 1103 by mattdw.
- DjangoSnippet 1253 by
joonas and some bugfixes in the comments:
- Updated per comment 1553 by nosa_manuel.
- Updated per comment 4642 by esquevin.
- DjangoSnippet 1933 by t_rybik.
- django-ko-demo from The Atlantic by @fdintino.