core.formatted_name
Overview
Resolution of formatted_name query paths to the database paths behind them.
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 (a "many" 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(queryset)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| queryset | yes |
Source
server/vueda/core/formatted_name.py:76
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
GeneratedFieldcolumn, aget_formatted_name()method, or neither), so there is no path to annotate; - the model declares a lookup expression and keeps a
formatted_namecolumn. Annotating over an existing field name raisesValueError, so nothing is annotated and the column is what a response serializes. No system check reports that pairing:_validate_model_formatted_nameonly inspects a model that setsformatted_name = Noneon 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(model)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| model | yes |
Source
server/vueda/core/formatted_name.py:32
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.
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(fields)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| fields | yes |
Source
server/vueda/core/formatted_name.py:119
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(model, field_name)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| model | yes | ||
| field_name | yes |
Source
server/vueda/core/formatted_name.py:176
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(model, field_name, alias)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| model | yes | ||
| field_name | yes | ||
| alias | yes |
Source
server/vueda/core/formatted_name.py:144
Source
server/vueda/core/formatted_name.py