---
url: "https://vueda.dev/v3/reference/api/py/vueda.core.formatted_name.md"
title: "core.formatted_name"
---

# core.formatted_name

## Overview

Resolution of ``formatted_name`` query paths to the database paths behind them.

## `annotate_formatted_name` {#annotate_formatted_name}

Annotate ``formatted_name`` on ``queryset`` when its model has a path to annotate, apply
``formatted_name_select_related`` when its model declares one, and return ``queryset`` unchanged
when neither applies.

Shared by ``FormattedNameManager.get_queryset`` (every queryset the model itself builds),
``VuedaViewSet.get_queryset`` (the root queryset DRF builds),
``VuedaListSerializer.to_representation`` (a "many" expand fetched through a related manager), and
the prefetch-plan builder in ``vueda.core.viewsets`` (an expand's ``Prefetch`` queryset), so
all four agree on when ``formatted_name`` needs the annotation or the ``select_related`` rather
than each re-deriving it.

``formatted_name_annotation_path`` is the rule for the annotation half, so a model this declines
to annotate is the same model ``VuedaOrderingFilter`` and ``FormattedNamePathFilterSetMixin``
rewrite instead of leaving on a column — see that function for the two shapes that need no
annotation and would raise if annotated anyway.

The ``select_related`` half is independent of the annotation: a model resolving
``formatted_name`` through ``get_formatted_name()`` has no database path to annotate at all, but
may still declare ``formatted_name_select_related`` as a tuple of relation paths — the same paths
it would pass to ``queryset.select_related()`` itself — so the relations that method traverses
are joined in the same query instead of one extra query per row per relation.

:param queryset: The queryset to annotate.
:type queryset: django.db.models.QuerySet
:return: The queryset with whichever of the annotation and the ``select_related`` its model
    declares applied, or ``queryset`` itself when it declares neither.
:rtype: django.db.models.QuerySet

### Signature {#annotate_formatted_name-signature}

`annotate_formatted_name(queryset)`

#### Parameters {#annotate_formatted_name-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="annotate_formatted_name-param-queryset">queryset</span> |  | yes |  |  |

### Source {#annotate_formatted_name-source}

`server/vueda/core/formatted_name.py:77`

## `formatted_name_annotation_path` {#formatted_name_annotation_path}

The path to annotate as ``formatted_name`` on ``model``'s querysets, or ``None`` when there is
nothing to annotate.

This is the single rule behind every ``formatted_name`` annotation VUEDA adds —
``FormattedNameManager.get_queryset`` for the model's own querysets, and
``VuedaViewSet.get_queryset`` for the ones DRF builds — so a model's ``formatted_name`` means the
same thing to the database wherever the queryset came from.

``None`` covers both cases that need no annotation, and both would raise if annotated anyway:

- the model reaches its formatted name some way other than a lookup expression (its own
  ``GeneratedField`` column, a ``get_formatted_name()`` method, or neither), so there is no path
  to annotate;
- the model declares a lookup expression *and* keeps a ``formatted_name`` column. Annotating over
  an existing field name raises ``ValueError``, so nothing is annotated and the column is what a
  response serializes. No system check reports that pairing: ``_validate_model_formatted_name``
  only inspects a model that sets ``formatted_name = None`` on its own class, which this one by
  definition does not.

Declining the annotation is not the same as the column winning. With no annotation for them to
recognize, ``VuedaOrderingFilter`` and ``FormattedNamePathFilterSetMixin`` rewrite
``formatted_name`` to the lookup expression rather than leaving it on the column — so on such a
model a query orders and filters by the expression while the serialized value comes from the
column. Declare one or the other.

:param model: The model whose querysets are being built.
:type model: Type[django.db.models.Model]
:return: The path to annotate as ``formatted_name``, or ``None``.
:rtype: Optional[str]

### Signature {#formatted_name_annotation_path-signature}

`formatted_name_annotation_path(model)`

#### Parameters {#formatted_name_annotation_path-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="formatted_name_annotation_path-param-model">model</span> |  | yes |  |  |

### Source {#formatted_name_annotation_path-source}

`server/vueda/core/formatted_name.py:33`

## `path_multiplies_rows` {#path_multiplies_rows}

Whether a query path traverses a relation that can reach more than one row.

Joining through a reverse foreign key, a many-to-many, or a ``GenericRelation`` produces a row
per related object, so a queryset that annotates, orders, or filters across one silently returns
more rows than the table holds. A single-valued relation is fine however it is declared: a
nullable foreign key produces a ``LEFT OUTER JOIN`` and still matches at most one row.

This is the one rule behind every place VUEDA refuses a ``formatted_name`` path — the
``vueda_info.E008`` system check that validates a ``formatted_name_lookup_expression``, and
``resolve_formatted_name_path``, which declines to rewrite a related path reached this way. Both
read it from here so a path one refuses is a path the other refuses.

Column totals ask the same question of a path that may end in transforms, so they use
``vueda.core.paths.query_path_multiplies_rows`` instead, which reads the mark that Django's own
``Query.names_to_path`` puts on each multi-valued join. Both count reverse foreign keys,
many-to-many fields, and ``GenericRelation`` as multi-valued.

Takes the resolved fields rather than a path, because both callers have already resolved one and
resolving is what raises for a path that names nothing.

:param fields: The fields the path traverses, as returned by ``get_fields_from_path``.
:type fields: Iterable[django.db.models.Field]
:return: True when any field in the path can reach more than one row.
:rtype: bool

### Signature {#path_multiplies_rows-signature}

`path_multiplies_rows(fields)`

#### Parameters {#path_multiplies_rows-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="path_multiplies_rows-param-fields">fields</span> |  | yes |  |  |

### Source {#path_multiplies_rows-source}

`server/vueda/core/formatted_name.py:120`

## `resolve_formatted_name_path` {#resolve_formatted_name_path}

The database path behind a query path ending in ``formatted_name``, taken from the
``formatted_name_lookup_expression`` of the model that segment belongs to.

``formatted_name`` is a display name every VUEDA model exposes, but it reaches the database three
different ways: as its own ``GeneratedField`` column; as the path named by
``formatted_name_lookup_expression``; or as a ``get_formatted_name()`` method, which computes it
in Python and so has no database path at all. Only the second one needs translating, and this is
what translates it — for the metadata that types the field, for the system checks that validate a
declaration, and for the ordering and filtering backends that put the path into a query.

``None`` when there is nothing to translate: the path doesn't end in ``formatted_name``, the model
it belongs to has no lookup expression (a column of its own, or a method, or neither), or the
prefix names no path on ``model``. Callers fall back to resolving ``field_name`` as-is, which
either works or raises the error it deserves.

Two restrictions keep the translated path something a query can actually use:

A lookup expression is followed once and never chained through the next model's own lookup
expression, because the annotation ``VuedaViewSet.get_queryset`` builds from it is a plain
``F(lookup_expression)``: every segment of that path has to be a real column for Django to resolve
it either.

A prefix has to reach the model through single-valued relations. Following a multi-valued one
(a reverse foreign key, a many-to-many) would join a row per related object and silently multiply
the rows a list request returns, so such a path is left unresolved and reported as unorderable
and unfilterable instead.

Only the prefix is checked here, because only the prefix is this call's to inspect: the lookup
expression spliced onto it belongs to the related model, and a multi-valued one there would
multiply rows just the same. That half is validated once at startup, against the model that
declares it, by the ``vueda_info.E008`` system check.

:param model: The model the path starts from.
:type model: Type[django.db.models.Model]
:param field_name: The query path to resolve, e.g. ``"customer__formatted_name"``.
:type field_name: str
:return: The database path to use in place of ``field_name``, or ``None`` when there is none.
:rtype: Optional[str]

### Signature {#resolve_formatted_name_path-signature}

`resolve_formatted_name_path(model, field_name)`

#### Parameters {#resolve_formatted_name_path-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="resolve_formatted_name_path-param-model">model</span> |  | yes |  |  |
| <span id="resolve_formatted_name_path-param-field_name">field_name</span> |  | yes |  |  |

### Source {#resolve_formatted_name_path-source}

`server/vueda/core/formatted_name.py:182`

## `resolve_formatted_name_transform_path` {#resolve_formatted_name_transform_path}

The database path behind a query path that reaches ``formatted_name`` and may continue with
transforms, such as ``customer__formatted_name__unaccent``.

The part of the path up to its first ``formatted_name`` segment is rewritten by
``resolve_formatted_name_path``, and the rest of the path is appended to the result unchanged.
So ``customer__formatted_name__unaccent`` becomes ``customer__data__formatted_name__unaccent``
when Customer declares ``formatted_name_lookup_expression = "data__formatted_name"``. A path that
ends in ``formatted_name`` is rewritten exactly as ``resolve_formatted_name_path`` rewrites it.

The ordering backend, the ordering metadata, and ``FormattedNamePathFilterSetMixin`` all use this,
so a sort and a filter on the same declared path query the same column.

The names after ``formatted_name`` are not checked. A name that is not a transform stays in the
returned path, and resolving that path fails the same way any other misspelled path fails.

``None`` when the path has no ``formatted_name`` segment, or when ``resolve_formatted_name_path``
has nothing to rewrite for the part up to it.

:param model: The model the path starts from.
:type model: Type[django.db.models.Model]
:param field_name: The query path to resolve, e.g. ``"customer__formatted_name__unaccent"``.
:type field_name: str
:return: The database path to use in place of ``field_name``. None when there is nothing to
    rewrite.
:rtype: Optional[str]

### Signature {#resolve_formatted_name_transform_path-signature}

`resolve_formatted_name_transform_path(model, field_name)`

#### Parameters {#resolve_formatted_name_transform_path-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="resolve_formatted_name_transform_path-param-model">model</span> |  | yes |  |  |
| <span id="resolve_formatted_name_transform_path-param-field_name">field_name</span> |  | yes |  |  |

### Source {#resolve_formatted_name_transform_path-source}

`server/vueda/core/formatted_name.py:248`

## `split_alias_path` {#split_alias_path}

Split a query path whose last segment is ``alias`` into the prefix to put back on whatever the
alias expands to, the model that segment belongs to, and the fields the prefix traverses.

``None`` when the path doesn't end in ``alias``. Raises ``FieldDoesNotExist`` or
``NotRelationField`` when the prefix names no path on ``model``, the same way resolving any other
path would.

:param model: The model the path starts from.
:type model: Type[django.db.models.Model]
:param field_name: The query path to split, e.g. ``"customer__formatted_name"``.
:type field_name: str
:param alias: The trailing segment to split on, e.g. ``"pk"`` or ``"formatted_name"``.
:type alias: str
:return: ``(prefix, alias_model, prefix_fields)``, or ``None`` when the path doesn't end in ``alias``.
:rtype: Optional[Tuple[str, Type[django.db.models.Model], List[django.db.models.Field]]]

### Signature {#split_alias_path-signature}

`split_alias_path(model, field_name, alias)`

#### Parameters {#split_alias_path-parameters}

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| <span id="split_alias_path-param-model">model</span> |  | yes |  |  |
| <span id="split_alias_path-param-field_name">field_name</span> |  | yes |  |  |
| <span id="split_alias_path-param-alias">alias</span> |  | yes |  |  |

### Source {#split_alias_path-source}

`server/vueda/core/formatted_name.py:150`

## Source

`server/vueda/core/formatted_name.py`
