Skip to content

ModelInfoSerializer ​

Overview ​

A serializer for providing metadata about models, including fields, actions, and permissions.

This is a read-only serializer.

Effectively, this is a custom model serializer for content types.

Meta ​

Source ​

server/vueda/info/serializers.py:149

canonical ​

Signature ​

canonical(unknown)

Parameters ​

NameTypeRequiredDefaultDescription
unknownyes

customize_schema_request_data ​

Signature ​

customize_schema_request_data(self, request_data)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
request_datayes

Source ​

server/vueda/info/serializers.py:1630

customize_schema_response_data ​

Signature ​

customize_schema_response_data(self, auto_schema, response_data)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
auto_schemayes
response_datayes

Source ​

server/vueda/info/serializers.py:1637

get_annotation_ordering_type ​

The semantic type of a queryset annotation a client may order by.

An annotation has no model field behind it to read a type from, so the type comes from the expression's own output_field. Django resolves that for most expressions and refuses to guess for some — a Coalesce over mixed types raises FieldError rather than pick one — so an annotation whose type can't be resolved falls back to "alpha", the same way a column of an unmapped type does.

A path that applies transforms to an annotation, such as ordered__year, is typed from the output of its last transform. ordered.year is numeric even when ordered is a date.

Signature ​

get_annotation_ordering_type(queryset, annotation_name)

Parameters ​

NameTypeRequiredDefaultDescription
querysetyes
annotation_nameyes

Source ​

server/vueda/info/serializers.py:697

get_choices_data ​

Get the choices data for a field, widget, or filter object.

We are careful to avoid evaluating querysets, as this can be expensive. Accessing .choices will evaluate the queryset for certain fields and filter objects.

:param field: A rest_framework field. :type field: rest_framework.fields.Field :param widget: A django.forms widget. :type widget: django.forms.widgets.Widget :param filter_obj: A django_filters filter object. :type filter_obj: django_filters.filters.Filter :return: The literal choices, or True if the choices are queryset-based, or False if there are no choices. :rtype: Union[List, Tuple, bool]

Signature ​

get_choices_data(field, widget, filter_obj)

Parameters ​

NameTypeRequiredDefaultDescription
fieldyes
widgetyes
filter_objnoNone

Source ​

server/vueda/info/serializers.py:1147

get_choices_meta ​

Get the metadata for queryset-backed choices.

Only queryset-backed choices get metadata. A field whose choices are literal values (ChoiceFilter, TypedChoiceFilter, a model field's choices, ...) reports those values directly, so it needs no app_label/model pointer for the client to follow.

Filters that build their choices from the values currently stored in a column (AllValuesFilter, AllValuesMultipleFilter) also look like literal choices here, but their set is dynamic and must be read from the choices endpoint. Their callers detect them by type and resolve the metadata themselves; do not try to detect them here. In particular, do not test for a model attribute: BaseFilterSet.__init__ assigns model to every filter it holds, so that test passes for all filters on an instantiated filterset.

:param field: The field that the choices are attached to. :type field: Union[django_filters.filters.Filter, rest_framework.fields.Field] :param choices: The choices data or True if the choices are queryset-based or False if there are no choices. :type choices: Union[List, Tuple, bool] :return: The metadata for the queryset-backed choices, or None if the choices are not queryset-backed. :rtype: Optional[django.db.models.options.Options]

Signature ​

get_choices_meta(field, choices)

Parameters ​

NameTypeRequiredDefaultDescription
fieldyes
choicesyes

Source ​

server/vueda/info/serializers.py:1222

get_default_ordering_data ​

Metadata for every term of a default ordering (a model's Meta.ordering, or a viewset's own ordering). The result is empty when any one of those terms can't be resolved.

A default ordering only means something as a whole: rows arrive sorted by the first term, then by the second, and so on. Reporting only the terms that do resolve would tell the client the rows are sorted in an order they aren't, so an unresolvable term drops the whole default ordering rather than part of it. A term that names a field the model does not have is unresolvable, and the vueda_info.E006 system check reports it. A term that names several fields at once, such as a Concat of two columns, is unresolvable as well: it has no single name that stands for the sort it performs (see UnnameableOrderingTermError). VuedaOrderingFilter still accepts an explicit ?o= request on each of those fields. What is dropped is the claim about how the rows currently arrive, not the fields themselves.

A term naming an annotation that queryset carries, or a transform on one, is reported under that name and typed from the annotation (see get_ordering_data). queryset is the viewset's own, built without a request, so it carries every annotation that its get_queryset and its manager add. A model's Meta.ordering with no viewset to read is resolved without one, and a name there that isn't a model field drops the default as any unknown name does.

Signature ​

get_default_ordering_data(self, model, ordering, queryset)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
modelyes
orderingyes
querysetnoNone

Source ​

server/vueda/info/serializers.py:721

get_default_ordering_field_names ​

Every field a default ordering references, mapped to that field's semantic type, in the order the terms name them. An annotation that queryset carries counts as a field here, typed the way get_ordering_data types it.

This is the field-by-field view of a default ordering, as opposed to the term-by-term view get_default_ordering_data reports. The two differ for a term that names more than one field: such a term has no single name to report the default under (see UnnameableOrderingTermError), but each field it names is still a valid explicit ?o= target, so each is still a field to advertise.

A name that is an annotation on queryset, or a transform on one, is included under that name, typed the way get_ordering_data types it. A path that resolves to neither a field nor an annotation is skipped, for the same reason it is elsewhere: DRF would raise on it, so there is nothing to offer. The vueda_info.E006 system check reports the declaration.

:param model: The model the ordering is declared against. :type model: Type[django.db.models.Model] :param ordering: The default ordering declaration. :type ordering: Union[str, Iterable] :param queryset: The viewset's own queryset, whose annotations the ordering may name. None for a model's Meta.ordering with no viewset to read. :type queryset: Optional[django.db.models.QuerySet] :return: Field path -> semantic type, in declaration order. :rtype: Dict[str, str]

Signature ​

get_default_ordering_field_names(self, model, ordering, queryset)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
modelyes
orderingyes
querysetnoNone

Source ​

server/vueda/info/serializers.py:992

get_default_ordering_field_sources ​

Replicates rest_framework.filters.OrderingFilter.get_default_valid_fields: the field sources DRF allows ordering on when a viewset doesn't declare ordering_fields.

The serializer is built with this serializer's own context, which carries the view. Reading fields runs get_fields(), and a serializer may need the view to decide what those fields are. DRF passes a context here for the same reason.

The view in that context is the model-info viewset, not the viewset being described, so a serializer that varies its field set by view.action is resolved against retrieve on /info/ rather than against list on the endpoint the client will call. Such a serializer can advertise a different set of ordering fields than that endpoint accepts. There is no context that fits every serializer. DRF's own get_default_valid_fields passes {"request": request} with no view at all. Passing that here would make the two agree for a serializer that accepts a missing view. It would also make /info/ raise for a serializer that requires a view, which already fails on the real list request. Declare ordering_fields on a viewset whose serializer varies its fields by action, which takes both this method and DRF's out of the picture.

Signature ​

get_default_ordering_field_sources(self, model)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
modelyes

Source ​

server/vueda/info/serializers.py:1041

get_model_actions ​

Get the actions for a model and their own metadata. Actions will be sorted by method action, followed by sorted extra actions.

Each CRUD action is checked against its own required permission through :func:vueda.core.permissions.check_action_permission, called with no instance -- model-scope discovery decides through has_permission alone, the same model-level check a CRUD action's own per-object discovery relies on before it ever reaches a row, so a matching workflow-state grant can settle a model-level denial here too. A model-level action describes what a requester might do on some instance of the model, not a guarantee that holds for every instance; the per-object surfaces (an object's own available_actions) decide that separately, through has_object_permission.

Signature ​

get_model_actions(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceyes

Source ​

server/vueda/info/serializers.py:453

get_model_column_totals ​

The column totals a list request may ask for.

A section of its own rather than a flag on each entry of model_fields, because the two don't line up: model_fields is built from the canonical serializer, while column_totals lives on the viewset and names its totals after the client's columns. A total named product_price that no serializer field matches would have nowhere to be reported, and a client's display columns include foo__bar names model_fields doesn't carry either.

fields are the declared total names, in declaration order; a client asks for the ones its visible columns can render.

The section reports which totals exist, not the parameter that asks for them. A client sends the parameter from its own constant, so a project that changes settings.COLUMN_TOTALS_PARAM needs a matching client change. Reporting the parameter name here is part of the wider parameter-name discovery work, which lands after v3.0.0 rather than a parameter at a time.

A viewset with no column_totals, or one whose declaration isn't a mapping, reports no fields -- the same thing its list action offers. vueda_info.E013 reports the misconfigured declaration itself.

Read from the column_totals attribute rather than through VuedaViewSet.get_declared_column_totals(), which is what list and the OpenAPI schema call. Registration stores the viewset class, and that hook is an instance method whose override is free to depend on the request -- there is no instance here to ask, and no request to ask about. So column_totals is the advertised set: a viewset narrowing its totals per request through that hook still advertises everything it declares, and a client asking for one the hook withheld gets the 400 that get_requested_column_totals raises. A total that no declaration carries cannot be advertised here at all.

Signature ​

get_model_column_totals(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceyes

Source ​

server/vueda/info/serializers.py:576

get_model_expands ​

Get the expands for a model and their own metadata.

Signature ​

get_model_expands(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceyes

Source ​

server/vueda/info/serializers.py:539

get_model_field_choices ​

Get the choices for a model field.

:param field: A rest_framework field. :type field: rest_framework.fields.Field :param widget: A django.forms widget. :type widget: django.forms.widgets.Widget :param serializer: A rest_framework serializer. :type serializer: rest_framework.serializers.Serializer :return: Returns a tuple, with the first value being the choices list or True if the choices are model-based, or False if there are no choices. The second value is the metadata for the model-based choices. :rtype: Tuple[Union[List, Tuple, bool], Optional[django.db.models.options.Options]]

Signature ​

get_model_field_choices(self, field, widget, serializer)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
fieldyes
widgetyes
serializeryes

Source ​

server/vueda/info/serializers.py:1254

get_model_fields ​

Get the fields for a model and their own metadata.

Signature ​

get_model_fields(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceyes

Source ​

server/vueda/info/serializers.py:430

get_model_fields_data ​

Signature ​

get_model_fields_data(self, serializer, excluded_fields, context)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
serializeryes
excluded_fieldsnofrozenset()
contextnoNone

Source ​

server/vueda/info/serializers.py:346

get_model_fields_db_field_type ​

Signature ​

get_model_fields_db_field_type(field_name, model_field, many)

Parameters ​

NameTypeRequiredDefaultDescription
field_nameyes
model_fieldyes
manyyes

Source ​

server/vueda/info/serializers.py:282

get_model_fields_max_data ​

Signature ​

get_model_fields_max_data(self, field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:253

get_model_fields_min_data ​

Signature ​

get_model_fields_min_data(self, field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:224

get_model_fields_model_field_type ​

Signature ​

get_model_fields_model_field_type(field_name, model_field, many)

Parameters ​

NameTypeRequiredDefaultDescription
field_nameyes
model_fieldyes
manyyes

Source ​

server/vueda/info/serializers.py:302

get_model_fields_serializer_field_type ​

Signature ​

get_model_fields_serializer_field_type(field_name, model_field, many)

Parameters ​

NameTypeRequiredDefaultDescription
field_nameyes
model_fieldyes
manyyes

Source ​

server/vueda/info/serializers.py:320

get_model_filtering ​

Get the filtering fields for a model and their own metadata.

Signature ​

get_model_filtering(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceyes

Source ​

server/vueda/info/serializers.py:1456

get_model_filtering_choices ​

Signature ​

get_model_filtering_choices(self, filterset, filter_obj, field, widget)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
filtersetyes
filter_objyes
fieldyes
widgetyes

Source ​

server/vueda/info/serializers.py:1306

get_model_filtering_decimal_places ​

Signature ​

get_model_filtering_decimal_places(field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:1337

get_model_filtering_error_messages ​

Signature ​

get_model_filtering_error_messages(filter_obj, field)

Parameters ​

NameTypeRequiredDefaultDescription
filter_objyes
fieldyes

Source ​

server/vueda/info/serializers.py:1348

get_model_filtering_fallback_label ​

A title cased label built from filter_name, for a filter whose path django-filter can't name.

It is built the way django_filters.utils.label_for_filter builds one, with the filter's name in place of the verbose field names: exclude first for a negated filter, then the name made readable the way Django labels a form field that declares no label (django.forms.utils.pretty_name), then the lookup expression in words. A lookup expression that the name already ends with is taken off the name first, so customer_formatted_name_icontains reads Customer Formatted Name Contains rather than repeating the lookup. A dot in a public filter name reads as a space.

:param filter_obj: The filter to label. :type filter_obj: django_filters.filters.Filter :param filter_name: The filter's name, as model_filtering reports it. :type filter_name: str :return: The label. :rtype: str

Signature ​

get_model_filtering_fallback_label(filter_obj, filter_name)

Parameters ​

NameTypeRequiredDefaultDescription
filter_objyes
filter_nameyes

Source ​

server/vueda/info/serializers.py:1079

get_model_filtering_input_type ​

Signature ​

get_model_filtering_input_type(filter_obj, field, widget)

Parameters ​

NameTypeRequiredDefaultDescription
filter_objyes
fieldyes
widgetyes

Source ​

server/vueda/info/serializers.py:1362

get_model_filtering_label ​

The filter's declared label, or a title cased label generated from its field name and lookup expression when it doesn't declare one.

declared_label has to come from the filter as declared on the filterset class, not from the bound copy a filterset instance holds. Filter.label is a property that generates a label with label_for_filter the first time it is read on a model-bound filter and then caches it onto the filter, and building Filter.field reads it. So by the time we get here every bound filter reports a label, and none of them can still tell us whether that label was declared or generated.

django-filter builds a label from the verbose names of the model fields that the path names. A path it can't follow, such as a related formatted_name reached through a lookup expression or a path that ends in a transform, gives it nothing to name, and it labels the filter [invalid name]. The label falls back to filter_name instead, made readable the way Django labels a form field that declares no label (django.forms.utils.pretty_name): customer_name becomes Customer Name. A dotted public name reads the same way, so customer.formatted_name becomes Customer Formatted Name.

Signature ​

get_model_filtering_label(filter_obj, model, declared_label, filter_name)

Parameters ​

NameTypeRequiredDefaultDescription
filter_objyes
modelyes
declared_labelnoNone
filter_namenoNone

Source ​

server/vueda/info/serializers.py:1112

get_model_filtering_max_digits ​

Signature ​

get_model_filtering_max_digits(field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:1375

get_model_filtering_max_length ​

Signature ​

get_model_filtering_max_length(field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:1386

get_model_filtering_max_value ​

Signature ​

get_model_filtering_max_value(self, field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:1397

get_model_filtering_min_length ​

Signature ​

get_model_filtering_min_length(field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:1406

get_model_filtering_min_value ​

Signature ​

get_model_filtering_min_value(self, field, model_field)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
fieldyes
model_fieldyes

Source ​

server/vueda/info/serializers.py:1417

get_model_filtering_type ​

Signature ​

get_model_filtering_type(self, filter_name, filter_obj, field)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
filter_nameyes
filter_objyes
fieldyes

Source ​

server/vueda/info/serializers.py:1297

get_model_filtering_validators ​

Signature ​

get_model_filtering_validators(field)

Parameters ​

NameTypeRequiredDefaultDescription
fieldyes

Source ​

server/vueda/info/serializers.py:1426

get_model_ordering ​

Return the model_ordering section: the ordering a list request gets when it sends no o, and every field that o may name.

default lists field names in order. It comes from the viewset's ordering when that is declared, and from the model's Meta.ordering otherwise. The two are never merged, and a viewset ordering that fails to resolve does not fall back to the model's. A model with no canonical viewset still reports its Meta.ordering, and fields then holds only the fields that ordering reads.

fields lists a name and a type per field, built from the viewset's ordering_fields the way DRF's OrderingFilter reads that attribute:

  • An explicit list reports each entry, whether it is a plain name or a (field_name, label) pair. The label is never reported.
  • "__all__" reports the model's own fields by field name (a foreign key as customer, never customer_id) and every annotation that the viewset's get_queryset adds.
  • An undeclared ordering_fields (None, DRF's default) reports the canonical serializer's readable fields by source (see get_default_ordering_field_sources). Model properties and fields with no orderable path are left out.
  • An empty list offers nothing beyond the default ordering's fields.

Every field that the default ordering reads is added to fields, because VuedaOrderingFilter accepts o for each of them. Only the fields named in the reported default carry ascending.

Names and types follow these rules:

  • Each name is the dotted public form that a client sends back in o.
  • "pk" is reported as the field behind it: id, or each field of a CompositePrimaryKey (see expand_ordering_pk).
  • formatted_name is reported under that name when the model stores it as a column or reaches it through formatted_name_lookup_expression, and is typed from the column it lands on. A formatted_name computed by get_formatted_name() has no column, so it is left out (see ordering_path_field). A related formatted_name is reported under its declared path, dotted.
  • A path that ends in a transform is reported under its own dotted path, typed from the transform's output field. created__year is reported as {"name": "created.year", "type": "numeric"}.
  • A scalar function over one column is reported under that column, typed from the column, with ascending from the term's direction. Lower("name").desc() is reported as {"name": "name", "type": "alpha", "ascending": false}, and Length("name") is typed alpha.
  • An annotation is typed from its output_field, or "alpha" when Django cannot resolve one (see get_annotation_ordering_type). A path that applies transforms to an annotation, such as ordered__year, is reported as ordered.year and typed from the last transform's output. Both are reported whether ordering_fields or the default ordering names them.
  • "?" in ordering_fields names no field, so it is not reported, although DRF accepts o=? for it and returns the rows in random order.
  • An entry that resolves to no field is left out, and the vueda_info.E006 system check reports it.

default is reported whole or not at all (see get_default_ordering_data). It is empty when any term resolves to no field or annotation, or reads more than one column or none. Each column that a multi-column term reads still appears in fields, without ascending.

The queryset is built from the viewset class with no request. A get_queryset that reads self.request raises here, so the model-info request fails. The vueda_info.E006 system check skips that viewset for the same reason.

Signature ​

get_model_ordering(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceyes

Source ​

server/vueda/info/serializers.py:774

get_model_permissions ​

Get the permissions for a model. Read-only serializers only ever expose list/retrieve actions, so their create/update/delete permissions (which may still exist in the database, since the model itself keeps the standard CRUD permission set) are filtered out here rather than restricted on the model.

Signature ​

get_model_permissions(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceyes

Source ​

server/vueda/info/serializers.py:193

get_ordering_data ​

Metadata for one ordering term: the field name a client sends to request it, that field's semantic type, and (unless include_ascending is false) the direction the term sorts in.

A term is a plain field name, optionally - prefixed, or any expression order_by() takes — an F(...).asc(nulls_first=True), or a scalar function such as Lower("name") or Coalesce("nickname", Value("")). Ordering expressions are mentioned near the bottom of https://docs.djangoproject.com/en/5.2/ref/models/options/#ordering.

The name reported is the dotted public form of the field path as declared, not the path it resolved through: a formatted_name ordering is reported as formatted_name, which is the name the client sends back in ?o= (dotted, for a path that crosses a relation) and the name the queryset annotation carries, not the lookup expression behind it. The type comes from the column that path lands on, so it describes the field the client orders by rather than what a function wrapped around it returns — Length("name") reports name as alpha, not the integer the expression sorts on.

A path that ends in a transform is different, because the transform is part of the name that the client sends back. created__year is reported as created.year and typed from the transform's output field, so it is numeric rather than datetime.

When queryset is given, a term that names one of its annotations, or a transform on one, is reported under that name and typed from the annotation (see get_annotation_ordering_type). A path that resolves as a model field is typed from the field first, so a formatted_name reached through a lookup expression keeps the type of the column it lands on. Without queryset, an annotation name resolves to nothing, as any unknown name does.

Raises UnnameableOrderingTermError for a term that references no field or more than one, since neither has a single name a client could send back. A path with no ?o= name that reads back as the same path raises it too (see has_public_ordering_name). Raises FieldDoesNotExist for a path that names nothing.

Signature ​

get_ordering_data(self, model, order_by, include_ascending, queryset)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
modelyes
order_byyes
include_ascendingnoTrue
querysetnoNone

Source ​

server/vueda/info/serializers.py:619

get_schema_operation_parameters ​

Signature ​

get_schema_operation_parameters(self, operation_id, parameters)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
operation_idyes
parametersno()

Source ​

server/vueda/info/serializers.py:1619

get_verbose_name ​

Signature ​

get_verbose_name(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceobjectyes

Returns ​

str

Source ​

server/vueda/info/serializers.py:166

get_verbose_name_plural ​

Signature ​

get_verbose_name_plural(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceobjectyes

Returns ​

str

Source ​

server/vueda/info/serializers.py:169

get_workflow_enabled ​

Whether the model enables class Vueda.Workflow, so a client may offer workflow controls.

This describes the interface, not what the viewer may do. The workflow endpoints still decide which transitions a user may see and take.

Signature ​

get_workflow_enabled(self, instance)

Parameters ​

NameTypeRequiredDefaultDescription
selfyes
instanceobjectyes

Returns ​

bool

Source ​

server/vueda/info/serializers.py:172

names_queryset_annotation ​

Whether path names an annotation that queryset carries, either by its own name or with transforms that Django can apply to it, such as launched__year.

A plain annotation name always counts, even when Django can't determine its type. Such an annotation, such as a Coalesce over mixed types, makes every query on the queryset fail with Django's own FieldError until it declares an output_field, so it is not an ordering mistake. A path with transforms counts only when they resolve, so a misspelled launched__yaer names nothing.

Signature ​

names_queryset_annotation(queryset, path)

Parameters ​

NameTypeRequiredDefaultDescription
querysetyes
pathyes

Source ​

server/vueda/info/serializers.py:682

data ​

Source ​

server/vueda/info/serializers.py:180

verbose_name ​

verbose_name_plural ​

workflow_enabled ​

Source ​

server/vueda/info/serializers.py:136