API Reference¶
NestedValuesQuerySet¶
A QuerySet with values_nested(). Use it as a manager, or instantiate it for a model you cannot change.
from django_nested_values import NestedValuesQuerySet
class Book(models.Model):
objects = NestedValuesQuerySet.as_manager()
# Ad hoc, without touching the model
rows = list(NestedValuesQuerySet(model=Book).select_related("publisher").values_nested())
NestedValuesQuerySetMixin¶
Adds values_nested() to a custom QuerySet class. List it before QuerySet in the bases.
from django.db.models import QuerySet
from django_nested_values import NestedValuesQuerySetMixin
class BookQuerySet(NestedValuesQuerySetMixin, QuerySet):
def published(self):
return self.filter(is_published=True)
class Book(models.Model):
objects = BookQuerySet.as_manager()
Book.objects.published().prefetch_related("authors").values_nested()
values_nested()¶
Returns a queryset that yields one dict per row instead of a model instance. Each dict holds the concrete columns of the row plus one key per relation named in select_related() or prefetch_related(). values_nested() takes no arguments; the usual queryset methods shape the output:
only()anddefer()choose the columns. The primary key is always included.select_related()nests a foreign key or one-to-one target as a dict, in the same query.prefetch_related()nests any relation Django can prefetch, one extra query per lookup.annotate()andextra(select=...)add their columns to the dict.filter(),order_by(), slicing,first(),get(),count(),iterator()and async iteration work as usual.
Book.objects.values_nested()
# [{"id": 1, "title": "...", "isbn": "...", "price": Decimal("29.99"), "published_date": date(2024, 1, 15), "publisher_id": 1, "editor_id": None}, ...]
Book.objects.only("title").select_related("publisher").prefetch_related("authors").values_nested()
# [{"id": 1, "title": "...", "publisher_id": 1, "publisher": {"id": 1, "name": "...", "country": "..."}, "authors": [{"id": 1, "name": "...", "email": "..."}]}, ...]
values_nested() raises TypeError when called after values() or values_list().
Row Shape¶
- Foreign key columns keep their database name (
publisher_id), also when the relation is nested next to them. - A
NULLforeign key givesNonefor the column andNonefor the nested dict. - Single-valued relations (foreign key, one-to-one in either direction,
GenericForeignKey) nest as a dict orNone. - Multi-valued relations (many-to-many in either direction, reverse foreign key,
GenericRelation) nest as a list of dicts, in the order of the related model's default ordering or of thePrefetchqueryset. - Rows of a reverse foreign key or
GenericRelationomit the column that points back at the parent (book_id, orcontent_type_idandobject_id), unless that column is part of the related model's primary key. - Nested dicts follow the same rules, so
prefetch_related("authors__books")nests a list of books inside every author.
Relation Types¶
| Relation | select_related() |
prefetch_related() |
Nested value |
|---|---|---|---|
ForeignKey, OneToOneField |
✓ | ✓ | dict or None |
| Reverse one-to-one | ✓ | ✓ | dict or None |
| Multi-table inheritance link | ✓ | ✓ | dict or None |
Reverse ForeignKey |
✓ | list of dicts | |
ManyToManyField, both directions, custom through models |
✓ | list of dicts | |
GenericRelation |
✓ | list of dicts | |
GenericForeignKey |
✓ | dict or None |
Foreign keys with to_field, UUID primary keys and composite primary keys work on either side of a relation.
# Reverse one-to-one
Parent.objects.select_related("profile").values_nested()
# {"id": 1, "name": "...", "profile": {"id": 1, "parent_id": 1, "bio": "..."}} # or "profile": None
# Reverse foreign key: the chapter rows omit book_id
Book.objects.prefetch_related("chapters").values_nested()
# {"id": 1, "title": "...", "chapters": [{"id": 1, "title": "Introduction", "number": 1, "page_count": 20}, ...]}
# Generic relation: the tag rows omit content_type_id and object_id
Article.objects.prefetch_related("tags").values_nested()
# {"id": 1, "title": "...", "tags": [{"id": 1, "tag": "python"}, ...]}
GenericForeignKey¶
A plain lookup fetches every content type found in the rows. GenericPrefetch chooses the queryset per model; content types without a queryset are fetched by primary key, as Django does.
from django.contrib.contenttypes.prefetch import GenericPrefetch
TaggedItem.objects.prefetch_related("content_object").values_nested()
# {"id": 1, "tag": "python", "content_type_id": 7, "object_id": 1, "content_object": {"id": 1, "title": "..."}}
TaggedItem.objects.prefetch_related(
GenericPrefetch("content_object", [Article.objects.only("title"), Comment.objects.select_related("article")]),
).values_nested()
A lookup that continues past a GenericForeignKey, such as content_object__comments, needs every row to point at the same model. Rows pointing at different models raise ValueError.
Controlling Columns¶
The Main Model¶
Book.objects.only("title", "price").values_nested()
# {"id": 1, "title": "...", "price": Decimal("29.99")}
Book.objects.defer("isbn", "published_date").values_nested()
select_related() Targets¶
Use the double-underscore form of only() or defer():
Book.objects.only("title", "publisher__name").select_related("publisher").values_nested()
# {"id": 1, "title": "...", "publisher_id": 1, "publisher": {"id": 1, "name": "..."}}
The foreign key column of a select_related() target is loaded even when only() or defer() left it out.
prefetch_related() Targets¶
Use a Prefetch object whose queryset calls only() or defer():
from django.db.models import Prefetch
Book.objects.only("title").prefetch_related(
Prefetch("authors", queryset=Author.objects.only("name")),
).values_nested()
# {"id": 1, "title": "...", "authors": [{"id": 1, "name": "..."}]}
When only() or defer() on the main queryset leaves out a column a prefetch needs, such as publisher_id for prefetch_related("publisher"), the missing values are loaded with one extra query by primary key and stay out of the result.
Prefetch Objects¶
Prefetch works as it does on model instances. The queryset can filter, order, slice, annotate, and use only(), select_related() or prefetch_related(); to_attr names the key.
from django.db.models import Prefetch
Book.objects.prefetch_related(
Prefetch("chapters", queryset=Chapter.objects.filter(page_count__gt=30)),
Prefetch("authors", queryset=Author.objects.order_by("name")[:1], to_attr="first_author"),
Prefetch("publisher", queryset=Publisher.objects.prefetch_related("books")),
).values_nested()
Nested lookups ("authors__books") and Prefetch objects on nested paths (Prefetch("authors__books", queryset=...)) attach at each level.
Query Counts¶
values_nested() runs the same number of queries as prefetch_related() on model instances, with the same SQL for the prefetch queries.
| Relation | Method | Queries |
|---|---|---|
| Foreign key, one-to-one | select_related() |
1 (JOIN) |
| Foreign key, one-to-one | prefetch_related() |
2 |
| Many-to-many, reverse foreign key, generic relation | prefetch_related() |
2 |
| Each further lookup or nesting level | prefetch_related() |
+1 |
# 1 (JOIN for publisher) + 1 (authors) + 1 (tags) = 3 queries
Book.objects.select_related("publisher").prefetch_related("authors", "tags").values_nested()
Large Result Sets¶
iterator(chunk_size=...) streams rows from the database and prefetches each chunk before yielding it. As in Django, chunk_size is required when the queryset has prefetch lookups.
Async iteration works the same way, with async for or aiterator(chunk_size=...):
Multiple Databases¶
using() routes the main query and every prefetch query to that alias. A Prefetch queryset with its own using() keeps its alias.
Errors¶
The errors match prefetch_related() on model instances:
AttributeErrorfor a name that does not exist on the model.ValueErrorfor a name that is not a relation, for a lookup that appears twice with different querysets, for ato_attrthat is the name of a field, and for aGenericPrefetchwith two querysets for the same content type.TypeErrorwhenvalues_nested()followsvalues()orvalues_list().
Requirements¶
django.contrib.contenttypes must be in INSTALLED_APPS. The package imports it for GenericRelation and GenericForeignKey support. The package itself has no models and needs no entry in INSTALLED_APPS.