django-querysetsequence¶
Release v0.17 (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.7+, Django 3.2/4.0/4.1/4.2, and is optionally compatible with Django REST Framework 3.11+.
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
.
To handle exceptions and filtering correctly, a model
must be specified when creating
the QuerySetSequence
. Note that an abstract model may be used.
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(), model=Book)
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: '#' . |
aget() |
✓ | |
create() |
✗ | Cannot be implemented in QuerySetSequence . |
acreate() |
✗ | Cannot be implemented in QuerySetSequence . |
get_or_create() |
✗ | Cannot be implemented in QuerySetSequence . |
aget_or_create() |
✗ | Cannot be implemented in QuerySetSequence . |
update_or_create() |
✗ | Cannot be implemented in QuerySetSequence . |
aupdate_or_create() |
✗ | Cannot be implemented in QuerySetSequence . |
bulk_create() |
✗ | Cannot be implemented in QuerySetSequence . |
abulk_create() |
✗ | Cannot be implemented in QuerySetSequence . |
bulk_update() |
✗ | Cannot be implemented in QuerySetSequence . |
abulk_update() |
✗ | Cannot be implemented in QuerySetSequence . |
count() |
✓ | |
acount() |
✓ | |
in_bulk() |
✗ | Cannot be implemented in QuerySetSequence . |
ain_bulk() |
✗ | Cannot be implemented in QuerySetSequence . |
iterator() |
✓ | |
aiterator() |
✗ | |
latest() |
✓ | If no fields are given, get_latest_by on each model is required to
be identical. |
alatest() |
✗ | |
earliest() |
✓ | See the docuemntation for latest() . |
aearliest() |
✗ | |
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. |
afirst() |
✗ | |
last() |
✓ | See the documentation for first() . |
alast() |
✗ | |
aggregate() |
✗ | |
aaggregate() |
✗ | |
exists() |
✓ | |
aexists() |
✓ | |
contains() |
✓ | |
acontains() |
✗ | |
update() |
✓ | |
aupdate() |
✗ | |
delete() |
✓ | |
adelete() |
✗ | |
as_manager() |
✓ | |
explain() |
✓ | |
aexplain() |
✗ |
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.17 (2023-06-27)¶
Bugfixes¶
None
values are now appropriately sorted first or last (depending on database support. Contributed by @vuongdv-spinshell. (#97)
Improvements¶
- Initial support for asynchronous queries. (#99, #103)
0.15 (2021-12-10)¶
0.14 (2021-02-26)¶
Improvements¶
Bugfixes¶
- Support calling
filter()
withQ()
objects. Contributed by @jpic. (#76)
0.12 (2019-12-20)¶
0.11 (2019-04-25)¶
Improvements¶
Maintenance¶
- Support Python 3.7. Contributed by @michael-k. (#51)
- Support Django 2.2. Contributed by @michael-k. (#51)
- Support Django REST Framework 3.9. Contributed by @michael-k. (#51)
- Drop support for Python 3.4. Contributed by @michael-k. (#51)
- Drop support for Django REST Framework < 3.6.3. Contributed by @michael-k. (#51)
0.10 (2018-10-09)¶
Improvements¶
- 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)¶
0.8 (2017-09-05)¶
Improvements¶
- Optimize iteration when not slicing a
QuerySetSequence
. Contributed by @EvgeneOskin. (#29)
Maintenance¶
- 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)¶
Improvements¶
- 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)
0.6 (2016-06-07)¶
Improvements¶
- 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)¶
Improvements¶
- Raises
NotImplementedError
forQuerySet
methods thatQuerySetSequence
does not implement. (e2c67c5, b376b87) - Support
reverse()
to reverse the item ordering. (f27b2c7) - Support
none()
to return anEmptyQuerySet
. (6171c11) - Support
exists()
to check if aQuerySetSequence
has any results. (1aa705b) - Support
select_related
to follow foreign-key relationships when generating results. (ad54d5e)
0.2 (2016-01-08)¶
0.1 (2016-01-07)¶
- Support Django 1.8.0.
- Various bug fixes and tests.
The initial commits on based on DjangoSnippets and other code:
DjangoSnippet 1103 by mattdw. foo_7a081bfcfc0eff2aba4d550632d9733786c65ac8
-
foo_8d989bcc36140573a0f4d5f1e0e1e99e9a90a9f4
- Updated per comment 1553 by nosa_manuel. foo_ff258ca20f2a5c8e536a744fb9b64fba87046ef5
- Updated per comment 4642 by esquevin. foo_04b5fe14a5e8803c2b11259ff60c095fb9da8ce3
DjangoSnippet 1933 by t_rybik. foo_93f5575b3661bd2334960767eadf4a1ba03bfb8f
django-ko-demo from The Atlantic by @fdintino. foo_0b875aeb8aaea20ba47fc2fbc285d078aee42240