---
url: "https://vueda.dev/v3/reference/api/py/vueda.info.field_resolution.md"
title: "info.field_resolution"
---

# info.field_resolution

## Overview

Shared resolution of a serializer field's backing Django model field.

Used by both ``ModelInfoSerializer.get_model_fields_data`` (to derive ``type_db``/``type_model``
metadata) and the ``vueda_info`` system check (to warn about a path that fails to resolve), so the
two cannot disagree about whether a given field's path resolves.

``field.source`` and a model's ``<field>_lookup_expression`` are resolved through two different
mechanisms, because they have different runtime semantics:

- ``field.source`` is DRF's own attribute lookup (``rest_framework.fields.get_attribute``), which
  chains plain ``getattr()`` across ``source.split('.')``. It is walked here the same way, so this
  module never claims a source resolves when DRF's own runtime traversal would not reach it.
- A ``<field>_lookup_expression`` is fed directly to ``models.F()`` for queryset annotation (see
  ``annotate_formatted_name`` in ``vueda.core.models``) and to Django admin's ``lookup_field()``.
  It is resolved here through Django's own query-expression machinery (``F(...).resolve_expression``),
  the same mechanism ``annotate_formatted_name`` uses, so this module never claims an expression is
  broken when the ORM would actually accept it (a relation, or a transform such as ``"__year"``).

## `resolve_serializer_field_model_field` {#resolve_serializer_field_model_field}

Resolve the concrete, terminal Django model field a serializer field's value is drawn from.

Returns a ``(model_field, unresolved_path)`` pair:

- A field bound to the whole object (``field.source == "*"``) is never model-backed by design.
  ``SerializerMethodField`` forces this; a plain ``Field`` subclass that computes its own value
  from the instance can set it explicitly the same way (``ObjectRevisionField`` in
  ``vueda.history.revision`` does). Returns ``(None, None)`` -- callers must not treat this as
  an unresolved path worth a warning.
- A model declaring ``<field_name>_lookup_expression`` (the ``formatted_name`` mechanism)
  takes priority over ``field.source`` and is resolved via Django's own query-expression
  semantics (see ``_resolve_lookup_expression``).
- Otherwise, ``field.source`` (a dotted path for a nested/traversing source, or a plain field
  name for the common case) is walked via DRF's own attribute-lookup semantics (see
  ``_walk_source``).

``unresolved_path`` carries the path that failed to resolve, for the system check to name in
its warning. A field is only exempt from reporting when it opts out of model-backing by one of
two explicit conventions -- never merely because ``field.source`` happened to default to
``field_name`` rather than being set explicitly:

- ``field.source == "*"`` (``SerializerMethodField`` forces this; a plain ``Field`` subclass
  that computes its own value can set it the same way -- ``ObjectRevisionField``,
  ``AvailableActionsField``, and ``AvailableTransitionField`` all do, since each overrides
  ``get_attribute()`` outright and never reads ``source`` at runtime).
- A model defining a callable ``get_<field_name>()``, mirroring the ``get_formatted_name()``
  convention ``FormattedNameBaseModel._get_formatted_name`` already reads this way: a model may
  opt out of a generated column (for example ``formatted_name = None``) in favor of a plain
  method, and that method is exactly as legitimate a source of the value as a real column.

Absent either opt-out, an unresolved path is reported regardless of whether ``field.source``
was set explicitly or left to DRF's default -- a field name that fails to resolve is exactly as
likely to be a typo or a stale rename as an explicit ``source=`` that fails partway through a
relation, and DRF's default gives no way to tell the difference from the outside.

``<field>_lookup_expression`` is resolved through Django's own query-expression semantics (see
``_resolve_lookup_expression``) rather than ``_walk_source``, since it is fed directly to
``models.F()`` for queryset annotation and to Django admin's ``lookup_field()``, both of which
resolve purely at the database level and cannot reach a ``@property`` or method.

``field`` may be unbound (as returned by a bare ``serializer_instance.get_fields()`` call,
which does not run DRF's binding step) so ``field.source`` may still be ``None`` rather than
already defaulted to ``field_name`` -- this mirrors DRF's own ``Field.bind()`` default.

### Signature {#resolve_serializer_field_model_field-signature}

`resolve_serializer_field_model_field(model, field_name, field)`

#### Parameters {#resolve_serializer_field_model_field-parameters}

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

### Source {#resolve_serializer_field_model_field-source}

`server/vueda/info/field_resolution.py:91`

## Source

`server/vueda/info/field_resolution.py`
