This page explains a concept we call remote fields.
A tested document¶
This is a tested document. The following instructions are used for initialization:
>>> from lino import startup >>> startup('lino_book.projects.apc.settings.doctests') >>> from lino.api.doctest import * >>> from django.db.models import Q >>> translation.activate("en")
Django has lookups that span relationships¶
Let's say you want to see your sales invoices to clients in Eupen.
For this you can do the following:
>>> eupen = countries.Place.objects.get(name="Eupen") >>> qs = sales.VatProductInvoice.objects.filter(partner__city=eupen) >>> qs.count() 44
Above code is equivalent (but much more efficient) to the following code:
>>> len([invoice for invoice in sales.VatProductInvoice.objects.all() ... if invoice.partner.city == eupen]) 44
This is plain Django knowledge, documented in Lookups that span relationships.
Lino extends this idea by allowing to also specify layout elements using this syntax.
For example if you want, in your
sales.Invoices table, a column showing
the city of the partner of each invoice, you can simply specify
partner__city as a field name in your
>>> rt.show(sales.Invoices, ... column_names="id partner partner__city total_incl", limit=5) ===== ================== ============= ============== ID Partner Locality Total to pay ----- ------------------ ------------- -------------- 177 da Vinci David 4730 Raeren 1 299,08 176 da Vinci David 4730 Raeren 647,35 175 di Rupo Didier 4730 Raeren 338,80 174 Radermacher Jean 4730 Raeren 822,57 173 Radermacher Inge 4730 Raeren 2 468,18 **5 575,98** ===== ================== ============= ==============
- remote field
A virtual field created automatically on the fly by pointing to a field in a related object.
A remote field is a chain of subfields to be walked through for each row.
The intermediate subfields of a remote field must be pointers (either ForeignKey or OneToOneField). The last subfield is called the leaf field.
A remote field is an instance of
- leaf field
The last subfield of a remote field. This field determines the return type of the remote field itself.
You can use remote fields also in a detail layout. When all their
intermeditate subfields are OneToOneField, they are editable. For example the
detail layout of partners in Lino Voga has a field
salesrule__paper_type where you can set the paper type to be used for this
partner in new invoices. In this case,
Partner.salesrule is a
OneToOneField pointing to the one and only
lino_xl.lib.invoicing.SalesRule instance for this partner.
You can also use remote fields as actor parameters. This is useful mostly
when the leaf field is either a ForeignKey or a ChoiceListField. For
journal__room as simple actor parameter:
class Order(...): @classmethod def get_simple_parameters(cls): for f in super(Order, cls).get_simple_parameters(): yield f yield 'journal__room'
You can even use a remote field pointing to a virtual field as
actor parameter, But that virtual field must have a
return_type of ForeignKey, and you must also
lino.core.model.Model.setup_parameters() method on your model that
defines this field. That's because remote fields are kind of volatile fields
that get created on the fly when a layout asks for them. In Lino Presto we
have a usage example where we add a parameter field
tables on the
cal.Event model. That why we extend the
from lino.core.fields import make_remote_field @classmethod def setup_parameters(cls, params): super(Event, cls).setup_parameters(params) params['project__municipality'] = make_remote_field(cls, 'project__municipality') @classmethod def get_request_queryset(cls, ar, **filter): qs = super(Event, cls).get_request_queryset(ar, **filter) pv = ar.param_values if pv.project__municipality: places = pv.project__municipality.whole_clan() qs = qs.filter(project__isnull=False, project__city__in=places) return qs
Some general documentation about
This setting tells Lino what to do when it encounters a wrong field name in a layout specification. It will anyway raise an Exception, but the difference is the content of the error message.
The default value for this setting is True. In that case the error message reports only a summary of the original exception and tells you in which layout it happens. Because that's your application code and probably the place where the bug is hidden.
>>> settings.SITE.catch_layout_exceptions True
>>> rt.show(sales.Invoices, ... column_names="id partner foo total_incl") Traceback (most recent call last): ... Exception: lino.core.layouts.ColumnsLayout on lino_xl.lib.sales.models.Invoices has no data element 'foo'
>>> rt.show(sales.Invoices, ... column_names="id partner partner__foo total_incl") Traceback (most recent call last): ... Exception: lino.core.layouts.ColumnsLayout on lino_xl.lib.sales.models.Invoices has no data element 'partner__foo'
>>> settings.SITE.catch_layout_exceptions = False >>> rt.show(sales.Invoices, ... column_names="id partner partner__foo total_incl") Traceback (most recent call last): ... Exception: Invalid RemoteField contacts.Partner.partner__foo (no field foo in contacts.Partner)
Skipped because after 20200430 there is no longer a difference in the exception message.
Note that Lino's remote fields have nothing to do with
remote_field of a ForeignKey field.