Welcome | Get started | Dive into Lino | Contribute | Reference | More

contacts : Managing contacts

The lino_xl.lib.contacts plugin adds functionality for managing contacts. It adds the concepts of partner, person, organization and "contact roles".

We assume you have read the contacts : Managing contacts page of the User Guide.

This is a tested document. The following instructions are used for initialization:

>>> import lino
>>> lino.startup('lino_book.projects.min9.settings')
>>> from django.utils import translation
>>> from lino.api.doctest import *
>>> from django.db.models import Q

Database structure

The main models are Person and Company and their common base Partner.

A RoleType ("Function") where you can configure the available functions. TODO: rename "RoleType" to "Function" or "ContactType"?

A CompanyType model can be used to classify organizations.

Multi-table inheritance

The contacts plugin defines two subclasses of Partner: Person and Company. Applications can define other subclasses for Partner.

The Partner model is not abstract, i.e. you can see a table where persons and organizations are together. This is useful e.g. in accounting reports where all partners are handled equally, without making a difference between natural an legal persons.


This plugin needs lino_xl.lib.countries and lino.modlib.system.

This plugin is being extended by Lino Welfare in lino_welfare.modlib.contacts or by Lino Voga in lino_voga.modlib.contacts.

Contact functions

The demo database defines the following contact functions:

>>> rt.show(contacts.RoleTypes)
==== ============= ================== ===================== ====================
 ID   Designation   Designation (de)   Designation (fr)      Authorized to sign
---- ------------- ------------------ --------------------- --------------------
 1    CEO           Geschäftsführer    Gérant                Yes
 2    Director      Direktor           Directeur             Yes
 3    Secretary     Sekretär           Secrétaire            No
 4    IT manager    EDV-Manager        Gérant informatique   No
 5    President     Präsident          Président             Yes
==== ============= ================== ===================== ====================

The site operator

When this plugin is installed, the site manager usually creates a Company that represents the site operator, and have the field SiteConfig.site_company point to it (as explained in How to specify the site operator).

>>> siteop = settings.SITE.site_config.site_company
>>> siteop.__class__.__name__
>>> print(siteop)
Rumma & Ko OÜ
>>> for obj in siteop.get_signers():
...     print("{}, {}".format(obj.person.get_full_name(), obj.type))
Mrs Erna Ärgerlich, CEO

Models and views

class lino_xl.lib.contacts.Partner

The database model used to represent a business partner.


The full name of this partner.

Subclasses may hide this field and fill it automatically. For example on a Person, Lino automatically sets the name field to <last_name>, <first_name>, and the field is usually hidden for end users. Even when hidden, it can be used for alphabetic sorting.


An optional name prefix. For organisations this is inserted before the name, for persons this is inserted between first name and last name.

See lino.mixins.human.Human.get_last_name_prefix().


The primary email address.


The primary phone number.

Note that Lino does not ignore formatting characters in phone numbers when searching. For example, if you enter "087/12.34.56" as a phone number, then a search for phone number containing "1234" will not find it.


The primary mobile phone number.


The language to use when communicating with this partner.


The general account to suggest as default value in purchase invoices from this partner.

This field exists only when lino_xl.lib.ledger is installed, which uses it as the invoice_account_field_name for TradeTypes.purchases.

Two fields exist only when lino_xl.lib.vat is installed:


The default VAT regime to use on invoices for this partner.


The national VAT identification number of this partner.

class lino_xl.lib.contacts.Partners

The detail layout of the Partners table is not set by default. Especially accounting applications will set it to 'contacts.PartnerDetail'.

That's because the Partners view that shows companies and persons merged together is useful only for certain accounting reports.

class lino_xl.lib.contacts.Person

The database model used to represent a person.


See Partner.name.

class lino_xl.lib.contacts.Persons

Shows all persons.

class lino_xl.lib.contacts.Company

The database model used to represent an organization.

The verbose name is "Organization" while the internal name is "Company" for historical reasons and because that's easier to type.

Inherits from Partner.


Pointer to the CompanyType.


Return an iterable over the contact persons who can sign business documents (i.e. exercise a signer function) for this organization.

If today is specified and with_roles_history is True, return only the contact persons that were exercising a signer function at the given date.

contact person represents a person that signs contracts, invoices or other business documents for the site operator.

class lino_xl.lib.contacts.Companies

Base table for all tables on organizations.

class lino_xl.lib.contacts.Role

The database model used to represent a contact person.


The organization where this person has this role.


The function of this person in this organization.


The person having this role in this organization.

This is a learning foreign key. See Automatically creating contact persons


When this person started to exercise this function in this organization.

This is a dummy field when Plugin.with_roles_history is False.


When this person stopped to exercise this function in this organization.

This is a dummy field when Plugin.with_roles_history is False.

class lino_xl.lib.contacts.RoleType

The database model used to represent a contact function.


A translatable designation. Used e.g. in document templates for contracts.


Whether this is a signer function.

class lino_xl.lib.contacts.PartnerRelated

Abstract model mixin for things that are related to one and only one partner.


The recipient of this document. This can be a person, an organization or any type of business partner.

A pointer to lino_xl.lib.contacts.Partner.


Alias for the partner

Exporting contacts as vcard files

class lino_xl.lib.contacts.ExportVCardFile

Download all records as a .vcf file which you can import to another contacts application.

This action exists on every list of partners when your application has use_vcard_export set to True.

User roles

class lino_xl.lib.contacts.SimpleContactsUser

A user who has access to basic contacts functionality.

class lino_xl.lib.contacts.ContactsUser

A user who has access to full contacts functionality.

class lino_xl.lib.contacts.ContactsStaff

A user who can configure contacts functionality.

Filtering partners

class lino_xl.lib.contacts.PartnerEvents

A choicelist of observable partner events.


See Filtering partners regarding ledger movements in ledger: General accounting. This choice exists only when lino_xl.lib.ledger is installed.

Other models

class lino_xl.lib.contacts.CompanyTypes
class lino_xl.lib.contacts.CompanyType

A type of organization. Used by Company.type field.

Model mixins

class lino_xl.lib.contacts.ContactRelated

Model mixin for things that relate to either a private person or a company, the latter potentially represented by a contact person having a given role in that company. Typical usages are invoices or contracts.

Adds 3 database fields and two virtual fields.


Pointer to Company.


Pointer to Person.


The optional Role of the contact_person within company.


(Virtual field) The "legal partner", i.e. usually the company, except when that field is empty, in which case partner contains the contact_person. If both fields are empty, then partner contains None.


(Virtual field) The Addressable object to use when printing a postal address for this. This is typically either the company or contact_person (if one of these fields is non-empty). It may also be a lino_xl.lib.contacts.models.Role object.

Difference between partner and recipient: an invoice can be issued and addressed to a given person in a company (i.e. a Role object), but accountants want to know the juristic person, which is either the company or a private person (if no company specified), but not a combination of both.

class lino_xl.lib.contacts.PartnerDocument

Deprecated. Adds two fields 'partner' and 'person' to this model, making it something that refers to a "partner". person means a "contact person" for the partner.

Civil state

>>> from lino_xl.lib.contacts.choicelists import CivilStates
>>> show_choicelist(CivilStates)
======= ==================== ==================== ============================= =============================
 value   name                 en                   de                            fr
------- -------------------- -------------------- ----------------------------- -----------------------------
 10      single               Single               Ledig                         célibataire
 20      married              Married              Verheiratet                   marié
 30      widowed              Widowed              Verwitwet                     veuf/veuve
 40      divorced             Divorced             Geschieden                    divorcé
 50      separated            Separated            Getrennt von Tisch und Bett   Séparé de corps et de biens
 51      separated_de_facto   De facto separated   Faktisch getrennt             Séparé de fait
 60      cohabitating         Cohabitating         Zusammenwohnend               Cohabitant
======= ==================== ==================== ============================= =============================

Automatically creating contact persons

Some examples of how the name is parsed when Lino automatically creates a lino_xl.contacts.Role.person:

>>> ar = rt.login("robin")
>>> pprint(contacts.Person.choice_text_to_dict("joe smith", ar))
{'first_name': 'Joe', 'last_name': 'Smith'}
>>> pprint(contacts.Person.choice_text_to_dict("Joe W. Smith", ar))
{'first_name': 'Joe W.', 'last_name': 'Smith'}
>>> pprint(contacts.Person.choice_text_to_dict("Joe", ar))
Traceback (most recent call last):
django.core.exceptions.ValidationError: ['Cannot find first and last name in "Joe"']
>>> pprint(contacts.Person.choice_text_to_dict("Guido van Rossum", ar))
{'first_name': 'Guido', 'last_name': 'van Rossum'}

The algorithm has already some basic intelligence but plenty of growing potential...

Changing the names of demo persons


Garbles person names in an existing database so that it may be used for a demo.


street2kw() to separates house number and optional flat number from street


>>> from lino_xl.lib.contacts.utils import street2kw
>>> pprint(street2kw("Limburger Weg"))
{'street': 'Limburger Weg'}
>>> pprint(street2kw("Loten 3"))
{'street': 'Loten', 'street_box': '', 'street_no': '3'}
>>> pprint(street2kw("Loten 3A"))
{'street': 'Loten', 'street_box': 'A', 'street_no': '3'}
>>> pprint(street2kw("In den Loten 3A"))
{'street': 'In den Loten', 'street_box': 'A', 'street_no': '3'}
>>> pprint(street2kw("Auf'm Bach"))
{'street': "Auf'm Bach"}
>>> pprint(street2kw("Auf'm Bach 3"))
{'street': "Auf'm Bach", 'street_box': '', 'street_no': '3'}
>>> pprint(street2kw("Auf'm Bach 3a"))
{'street': "Auf'm Bach", 'street_box': 'a', 'street_no': '3'}
>>> pprint(street2kw("Auf'm Bach 3 A"))
{'street': "Auf'm Bach", 'street_box': 'A', 'street_no': '3'}

Some rather special cases:

>>> pprint(street2kw("rue des 600 Franchimontois 1"))
{'street': 'rue des 600 Franchimontois', 'street_box': '', 'street_no': '1'}
>>> pprint(street2kw("Eupener Strasse 321 /A"))
{'street': 'Eupener Strasse', 'street_box': '/A', 'street_no': '321'}
>>> pprint(street2kw("Neustr. 1 (Referenzadr.)"))
{'addr2': '(Referenzadr.)', 'street': 'Neustr.', 'street_no': '1'}

Edge cases:

>>> street2kw("")

Showing birthdays

When contacts.show_birthdays is True, Lino shows the name of persons who have their birthday today, will have it soon or had it recently.


Whether to show upcoming and recent birthdays as a welcome message in the dashboard.

The default value is True unless the application developer changed it.

>>> ar = rt.login("robin")
>>> print("\n".join(contacts.show_birthdays(ar, i2d(20230619))))
<p>Birthdays today: 1967-06-19 *Paul Frisch*, 1987-06-19 *Peter Frisch*, 1999-06-19 *Clara Frisch*</p>
>>> print("\n".join(contacts.show_birthdays(ar, i2d(20231230))))
<p>Recent birthdays: 1971-12-29 *Dora Drosson*</p>
<p>Upcoming birthdays: 1997-01-01 *Philippe Frisch*, 1968-12-31 *Petra Zweith*</p>
>>> print("\n".join(contacts.show_birthdays(ar, i2d(20231229))))
<p>Birthdays today: 1971-12-29 *Dora Drosson*</p>
<p>Upcoming birthdays: 1997-01-01 *Philippe Frisch*, 1968-12-31 *Petra Zweith*</p>
>>> print("\n".join(contacts.show_birthdays(ar, i2d(20240101))))
<p>Birthdays today: 1997-01-01 *Philippe Frisch*</p>
<p>Recent birthdays: 1971-12-29 *Dora Drosson*, 1968-12-31 *Petra Zweith*</p>
<p>Upcoming birthdays: 2001-01-03 *Dennis Frisch*</p>

Don't read this

>>> def show_help_text(a):
...   print(a.help_text)
...   with translation.override('de'):
...     print(a.help_text)
>>> lst = [contacts.Persons.insert_action.action,
...   contacts.Companies.insert_action.action]
>>> for a in lst: show_help_text(a)
Open a dialog window to insert a new Person.
Öffnet ein Dialogfenster, um einen neuen Datensatz (Person) zu erstellen.
Open a dialog window to insert a new Organization.
Öffnet ein Dialogfenster, um einen neuen Datensatz (Organisation) zu erstellen.