Skip to content

core.viewsets

Overview

ViewSet base classes with atomic transactions, row-level filtering, and flex-fields.

Classes

build_prefetch_plan

Derive the select_related/prefetch_related paths needed for serializer's resolved fields against model, so a list or retrieve response's query count no longer grows with the row count for whatever a request's ?e= actually expanded.

Walks serializer.fields rather than re-deriving expansion rules, so it depends on that resolution already having happened: for the root serializer, the caller must have already applied the request's query-param resolution (vueda.core.serializers.ensure_flex_fields_applied -- VuedaViewSet.get_queryset does this before calling here); a recursive call on a nested child gets this for free, because a child's expand/fields/omit values were passed as constructor kwargs and rest_flex_fields applies those automatically the moment .fields is accessed. Either way, a nested serializer only appears in .fields when the response will actually traverse it. A field never named in ?e= (or excluded by a permit_{action}_expands restriction) never becomes one, so it is never planned.

?f= (sparse fields) and ?om= (omit) play no part in this either way, through two separate mechanisms that both happen to have the same effect: FlexFieldsWriteableNestedSerializerMixin.apply_flex_fields re-admits an already-requested expand's own name into the sparse-fields set, as a side effect of defaulting its sub-fields to a wildcard when none are requested; VuedaExpandableFieldsSerializerMixin._get_expanded_field_names does the same for omit, as a side effect of always hiding available_actions from an expanded object. Neither parameter can drop an expand this plan would otherwise cover, nor can either one add one the request never named in ?e=. The plan simply mirrors whatever survives in .fields.

Depth is bounded the same way: a serializer with nothing further expanded at some level has no further nested serializer fields, so the recursion here terminates exactly where rest_flex_fields's own (already-validated) expansion depth does, with no second bound to maintain.

A field's source (defaulting to its name, honoring an explicit source= in its expandable_fields declaration) is resolved against model's actual relations via :func:resolve_relation_path, not against the nested serializer's own declared Meta.model, so a plan is only produced for a source that genuinely names a relation chain.

A GenericForeignKey expand (GenericForeignKeySerializer) resolves its concrete serializer per-instance at representation time, after any queryset planning could run, so it is intentionally not planned here -- it keeps resolving lazily, same as it does today. A GenericRelation (a reverse collection onto a fixed, known model) is a normal to-many relation and is planned like any other.

Returns (select_related, prefetch_related): lists ready to splat into queryset.select_related(*select_related) and queryset.prefetch_related(*prefetch_related).

Signature

build_prefetch_plan(serializer, model)

Parameters

NameTypeRequiredDescription
serializeryes
modelyes

Source

server/vueda/core/viewsets/__init__.py:434

filter_new_prefetch_lookups

Drop any entry in prefetch_related (as returned by :func:build_prefetch_plan) whose cache key queryset -- or an earlier-accepted entry in this same call -- already registers, so VuedaViewSet.get_queryset() never hands Django two different querysets for the same lookup.

prefetch_related raises ValueError: '<lookup>' lookup was already seen with a different queryset the moment two lookups register the same cache key with two different Prefetch.queryset values, and this happens even when one side is a bare string (an implicit default-manager queryset) and the other an explicit Prefetch. A viewset whose own queryset/get_queryset() already prefetches a relation this plan also covers -- most plausibly a relation the application hand-optimized before this plan existed -- would otherwise crash the first time a request actually resolves that relation. Deferring to the existing lookup keeps whatever customization it carries (including its own formatted_name annotation, if it needs one) rather than overriding it with the plan's default. Two plan entries can collide the same way -- a serializer that aliases one relation under two expandable-field names produces two Prefetch objects for the same path -- so an entry this call already accepted also counts as "already registered" for the entries that follow it. Django's cache key is the lookup's own prefetch_to, not its full path, so a lookup that sets to_attr registers under that alias and never collides with a plan entry for the same path under its default attribute name.

Reads queryset._prefetch_related_lookups, a private Django attribute with no public equivalent; it is a plain tuple of str/Prefetch entries across the Django versions this package supports (5.2, 6.0, 6.1).

Signature

filter_new_prefetch_lookups(queryset, prefetch_related)

Parameters

NameTypeRequiredDescription
querysetyes
prefetch_relatedyes

Source

server/vueda/core/viewsets/__init__.py:535

resolve_relation_path

Walk a serializer field's dotted source against model's relations, one segment per dot, the same traversal DRF's own attribute resolution performs. Returns (orm_path, leaf_model, is_to_one): orm_path is source with dots replaced by the __ lookup separator, leaf_model is the model the last segment relates to, and is_to_one says whether every segment in the chain is single-valued (forward foreign key or one-to-one, either direction) -- the condition under which the whole chain can live in select_related rather than needing prefetch_related.

Returns None when source does not resolve to a chain of relations -- a plain column, a dotted path through a Python property or method, or a path through a GenericForeignKey. Such a source cannot be turned into a queryset plan and is left for the ORM to resolve lazily at representation time, same as it does today.

Signature

resolve_relation_path(model, source)

Parameters

NameTypeRequiredDescription
modelyes
sourceyes

Source

server/vueda/core/viewsets/__init__.py:405

Source

server/vueda/core/viewsets/__init__.py

Documents matching: server v3.0.0a1.post1client v3.0.0-alpha.2