FormattedNameManager
Overview
Default manager for FormattedNameBaseModel, which annotates formatted_name onto every queryset of a model that reaches its formatted name through formatted_name_lookup_expression.
A model with a formatted_name GeneratedField has that column already, so this manager annotates nothing for it (see formatted_name_annotation_path). One that computes the value in Python with get_formatted_name() has no database path to annotate either, but may still declare formatted_name_select_related, which this manager applies the same as annotate_formatted_name does everywhere else. The annotation itself only matters for the third form, where formatted_name is a name for some other path — a column on this model, or one reached through single-valued relations.
Annotating here rather than only in VuedaViewSet.get_queryset is what makes formatted_name usable everywhere a queryset is, not just on a list request: Meta.ordering = ["formatted_name"] compiles, a ModelChoiceFilter rendering its choices sorts by it, and a management command, a reverse relation, dumpdata, or the admin sees the same value the API does. Without it, ordering declared on the model would raise FieldError on every queryset the viewset didn't build.
Replacing the default manager. Django uses the first manager declared on a model as its default, so a model that declares its own objects shadows this one and loses the annotation. Subclass this manager rather than models.Manager when a VUEDA model needs a manager of its own::
class WidgetManager(FormattedNameManager):
def get_queryset(self):
return super().get_queryset().filter(archived=False)
class Widget(VuedaModel):
formatted_name = None
formatted_name_lookup_expression = "label"
objects = WidgetManager()
The same applies to a manager built with Manager.from_queryset(): pass FormattedNameManager as the base (FormattedNameManager.from_queryset(WidgetQuerySet)). A manager built with QuerySet.as_manager() has no base to pass. Replace WidgetQuerySet.as_manager() with FormattedNameManager.from_queryset(WidgetQuerySet)(), which builds the same manager on top of this one.
A manager declared on an abstract base shadows this one just as readily, and is the easier case to miss, since the model that names a lookup expression can be several classes away from the one that names the manager. VUEDAUserManager sits on a model with a formatted_name column, and SentItemManager on one that computes it with get_formatted_name(), so neither has an annotation to lose.
Every manager needs the annotation, not only the default. A second manager, such as unfulfilled = UnfulfilledOrderManager(), builds querysets of its own. Ordering or filtering them by formatted_name raises FieldError unless that manager annotates it too. The vueda_core.E019 system check reports each manager that does not, by name, rather than leaving it to fail at query time. It checks every installed model, whether or not anything registered it.
Opting a manager out. Some managers never use formatted_name. Examples are a manager that feeds a data export, or one that sends data to or receives data from another system's API. When the lookup expression crosses a relation, the annotation also adds a join that such a manager does not want. Set used_without_formatted_name = True on the manager's class to leave it out of vueda_core.E019::
class OrderExportManager(models.Manager):
used_without_formatted_name = True
class Order(VuedaModel):
formatted_name = None
formatted_name_lookup_expression = "customer__name"
objects = FormattedNameManager()
export = OrderExportManager()
Declare objects above the opted-out manager. Django makes the first manager a model declares its default, ahead of the objects this base provides, so a model that declares only export would make it the default manager. Setting Meta.default_manager_name = "objects" also works, and then the order of the declarations does not matter.
Only the value True opts out. Subclasses inherit the attribute. A subclass that sets it back to False is checked again.
The opt-out has no effect on the default manager. Related managers, the admin, and dumpdata all build their querysets from the default manager, so it always needs the annotation.
An opted-out manager still has to run with the model's Meta.ordering. Django applies Meta.ordering to the querysets of every manager. When it names formatted_name, every query from an opted-out manager raises FieldError, even a plain all(). The vueda_core.E020 system check reports that manager. Give its get_queryset() an order_by() of its own, or give it the annotation.
A viewset built on an opted-out manager still returns formatted_name, because VuedaViewSet.get_queryset adds the annotation itself. A model that VUEDA never displays can inherit Django's models.Model instead, and no formatted_name check runs on it. The guide "Data That VUEDA Does Not Display" compares the two options.
The base manager is not this manager. Django builds Model._base_manager itself, as a plain models.Manager, unless Meta.base_manager_name names one — so it carries no formatted_name annotation even on a model whose objects is this manager. That is deliberate on Django's part: the base manager is what fetches related objects, precisely because a default manager may filter them out, and Django doesn't use it when querying on a related model.
A model with a Meta.ordering naming formatted_name is the one that has to care, since a base-manager queryset carrying that ordering has no annotation to sort and raises FieldError. Anything reached through get() is safe, because get() clears ordering: refresh_from_db and dereferencing a foreign key both go that way, and so do select_related and prefetch_related, which order by nothing of the related model's own. What is not safe is a base-manager queryset that gets evaluated as a whole, which is the related-object collection a cascade delete performs (Collector.related_objects returns a plain _base_manager queryset, which Collector.collect evaluates without clearing ordering). dumpdata --all reads the base manager but replaces the ordering with the primary key, so it is safe too. Set Meta.base_manager_name = "objects" on a model that needs those paths to work, which makes Django use this manager for them too. Ordering by the lookup expression's own path instead of by formatted_name avoids the problem without touching the base manager. The vueda_core.E017 system check reports a model that ordered by formatted_name and did neither, so the failure surfaces at startup rather than on a delete.
get_queryset
Return a new QuerySet object. Subclasses can override this method to customize the behavior of the Manager.
Signature
get_queryset(self)
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| self | yes |
Source
server/vueda/core/models.py:286
Source
server/vueda/core/models.py:171