Welcome | Get started | Dive into Lino | Contribute | Reference
Introduction to choicelists¶
A choicelist is an ordered in-memory list of choices. Each choice has a value, a text and a optionally a name. The value of a choice is what is stored in the database. The text is what the user sees. It is usually translatable. The name can be used to refer to a given choice from program code.
A choicelist looks like a database table to the end user, but they exist in the memory of the server process and are not stored in a database.
Whenever in plain Django you use a choices attribute on a database
field, in Lino you probably prefer using a ChoiceList
instead.
You can use a choicelist for much more than filling the choices
attribute of a database field. You can display a choicelist as a table (using
show
in a doctest or by adding it
to the main menu). You can refer to individual choices programmatically using
their name
. You can subclass the choices and add application logic.
This is a tested document. The following instructions are used for initialization:
>>> from lino import startup
>>> startup('lino_book.projects.min9.settings')
>>> from lino.api.doctest import *
>>> from django.utils import translation
Defining your own ChoiceList¶
>>> from lino.api import _
>>> class MyColors(dd.ChoiceList):
... verbose_name_plural = _("My colors")
>>> MyColors.add_item('01', _("Red"), 'red')
<core.MyColors.red:01>
>>> MyColors.add_item('02', _("Green"), 'green')
<core.MyColors.green:02>
add_item takes at least 2 and optionally a third positional argument:
The first argument (value) is used to store this Choice in a database.
The second argument (text) is what the user sees. It should be translatable.
The optional third argument (names) is used to install this choice as a class attribute on its ChoiceList.
The value must be a string (or None, but that's a special usage).
>>> MyColors.add_item(3, _("Blue"), 'blue')
Traceback (most recent call last):
...
Exception: value must be a string
Lino protects you from accidentally adding a choice with the same value of an existing choice.
>>> MyColors.add_item("02", _("Blue"), 'blue')
Traceback (most recent call last):
...
Exception: Duplicate value '02' in core.MyColors.
Lino protects you from accidentally adding duplicate entries.
>>> MyColors.add_item("03", _("Blue"), 'green')
Traceback (most recent call last):
...
Exception: An attribute named 'green' is already defined in MyColors
>>> MyColors.add_item("03", _("Blue"), 'verbose_name_plural')
Traceback (most recent call last):
...
Exception: An attribute named 'verbose_name_plural' is already defined in MyColors
You may give multiple names (synonyms) to a choice by specifying them as a space-separated list of names. In that case the first name will be the default name.
>>> MyColors.add_item("03", _("Blue"), 'blue blau bleu')
<core.MyColors.blue:03>
>>> MyColors.blue is MyColors.blau
True
>>> rt.show(MyColors)
======= ================ =======
value name text
------- ---------------- -------
01 red Red
02 green Green
03 blue blau bleu Blue
======= ================ =======
The items are sorted by their order of creation, not by their value.
This is visible e.g. in lino.modlib.system.DurationUnits
.
Examples¶
For example Lino's system plugin (lino.modlib.system
) defines a
choicelist Weekdays
, which has 7
choices, one for each day of the week.
>>> rt.show('system.Weekdays')
======= =========== ===========
value name text
------- ----------- -----------
1 monday Monday
2 tuesday Tuesday
3 wednesday Wednesday
4 thursday Thursday
5 friday Friday
6 saturday Saturday
7 sunday Sunday
======= =========== ===========
Another example is the Genders
choicelist defined in the
lino.modlib.system
plugin.
>>> rt.show('system.Genders')
======= ======== ========
value name text
------- -------- --------
M male Male
F female Female
======= ======== ========
Accessing choicelists¶
ChoiceLists are actors. Like every actor, choicelists are never instantiated. They are just the class object itself and as such globally available
You can either import them or use lino.api.rt.models
to access
them (see Accessing plugins for the difference):
>>> rt.models.system.Weekdays
lino.modlib.system.choicelists.Weekdays
>>> from lino.modlib.system.choicelists import Weekdays
>>> Weekdays
lino.modlib.system.choicelists.Weekdays
>>> Weekdays is rt.models.system.Weekdays
True
>>> from lino.modlib.system.choicelists import Genders
>>> Genders is rt.models.system.Genders
True
You can also write code that dynamically resolves a string of type
`app_label.ListName
to resolve them:
>>> rt.models.resolve('system.Weekdays') is Weekdays
True
Defining choicelists¶
Here is how the lino.modlib.system.Weekdays
choicelist has been
defined:
class Weekdays(dd.ChoiceList):
verbose_name = _("Weekday")
add = Weekdays.add_item
add('1', _('Monday'), 'monday')
add('2', _('Tuesday'), 'tuesday')
...
This is the easiest case.
More complex examples, including choicelists with extended choices:
Accessing individual choices¶
Each row of a choicelist is a choice, more precisely an instance
of lino.core.choicelists.Choice
or a subclass thereof.
Each choice has a "value", a "text" and (optionally) a "name".
The value is what gets stored when this choice is assigned to a database field. It must be unique because it is the analog of primary key.
>>> [g.value for g in Genders.objects()]
['M', 'F']
The text is what the user sees. It is a translatable string, implemented using Django's i18n machine:
>>> Genders.male.text.__class__
<class 'django.utils.functional....__proxy__'>
Calling str()
of a choice is (usually) the same as calling
str()
on its text attribute:
>>> [str(g) for g in Genders.objects()]
['Male', 'Female']
The text of a choice depends on the current user language.
>>> with translation.override('fr'):
... [str(g) for g in Genders.objects()]
['Masculin', 'F\xe9minin']
>>> with translation.override('de'):
... [str(g) for g in Genders.objects()]
['M\xe4nnlich', 'Weiblich']
>>> with translation.override('et'):
... [str(g) for g in Genders.objects()]
['Mees', 'Naine']
The text of a choice is a translatable string, while value and name remain unchanged:
>>> with translation.override('fr'):
... rt.show('system.Weekdays')
======= =========== ==========
value name text
------- ----------- ----------
1 monday Lundi
2 tuesday Mardi
3 wednesday Mercredi
4 thursday Jeudi
5 friday Vendredi
6 saturday Samedi
7 sunday Dimanche
======= =========== ==========
Named choices¶
A choice can optionally have a name, which makes it accessible as class attributes on its choicelist so that application code can refer to this particular choice.
>>> Weekdays.monday
<system.Weekdays.monday:1>
>>> Genders.male
<system.Genders.male:M>
>>> [g.name for g in Genders.objects()]
['male', 'female']
>>> [d.name for d in Weekdays.objects()]
['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
Choicelist fields¶
You use the Weekdays
choicelist in a model definition as
follows:
from lino.modlib.system.choicelists import Weekdays
class WeeklyEvent(dd.Model):
...
day_of_week = Weekdays.field(default=Weekdays.monday)
This adds a database field whose value is an instance of
lino.core.choicelists.Choice
.
A choicelist field is similar to a ForeignKey
field in that it uses a
combo box as widget, but instead of pointing to a
database object it points to a Choice
. For the underlying database it
is actually a CharField which contains the value (not the name) of its
choice.
The lino.mixins.human.Human
mixin uses the Genders
choicelist as follows:
class Human(Model):
...
gender = Genders.field(blank=True)
Because lino_xl.lib.contacts.Person
inherits from
Human
, you can use this when you want to select all men:
>>> Person = rt.models.contacts.Person
>>> list(Person.objects.filter(gender=Genders.male))
...
[Person #211 ('Mr Albert Adam'), Person #215 ('Mr Ilja Adam'), Person #114 ('Mr Hans Altenberg'), ...]
Here is a list of all male first names in our contacts database:
>>> sorted({p.first_name for p in Person.objects.filter(gender=Genders.male)})
['Albert', 'Alfons', 'Andreas', 'Bernd', 'Bruno', 'Christian', 'Daniel', 'David', 'Denis', 'Dennis', 'Didier', 'Eberhart', 'Edgar', 'Edgard', 'Emil', 'Erich', 'Erwin', 'Fritz', 'Gregory', 'Guido', 'Hans', 'Henri', 'Hubert', 'Ilja', 'Jan', 'Jean', 'Johann', 'Josef', 'Jérémy', 'Jérôme', 'Karl', 'Kevin', 'Lars', 'Laurent', 'Luc', 'Ludwig', 'Marc', 'Mark', 'Michael', 'Otto', 'Paul', 'Peter', 'Philippe', 'Rik', 'Robin', 'Vincent']
The same for the ladies:
>>> sorted({p.first_name for p in Person.objects.filter(gender=Genders.female)})
['Alice', 'Annette', 'Berta', 'Charlotte', 'Clara', 'Daniela', 'Dora', 'Dorothée', 'Erna', 'Eveline', 'Françoise', 'Gaby', 'Germaine', 'Hedi', 'Hildegard', 'Inge', 'Irene', 'Irma', 'Jacqueline', 'Josefine', 'Laura', 'Line', 'Lisa', 'Marie-Louise', 'Melba', 'Melissa', 'Monique', 'Noémie', 'Odette', 'Pascale', 'Paula', 'Petra', 'Ulrike', 'Õie']
A ChoiceList has an get_list_items()
method which returns an iterator
over its choices:
>>> print(Genders.get_list_items())
[<system.Genders.male:M>, <system.Genders.female:F>]
Customizing choicelists¶
When we say that choicelists are "constant" or "hard-coded", then we should add "for a given Lino site". They can be modified either by a child application or locally by the system administrator.
See workflows_module
and user_types_module
.
Sorting choicelists¶
Lino displays the choices of a choicelist in a combobox in their natural order of how they have been added to the list.
You can explicitly call Choicelist.sort()
to sort them. This makes sense
e.g. in lino_presto.lib.ledger
where we add a new journal group "Orders"
which we want to come before any other journal groups.
Miscellaneous¶
Comparing Choices uses their value (not the name nor text):
>>> UserTypes = rt.models.users.UserTypes
>>> UserTypes.admin > UserTypes.user
True
>>> UserTypes.admin == '900'
True
>>> UserTypes.admin == 'manager'
False
>>> UserTypes.admin == ''
False
Seeing all choicelists in your application¶
>>> from lino.core.kernel import choicelist_choices
>>> pprint(choicelist_choices())
...
[('about.DateFormats', 'about.DateFormats (Date formats)'),
('about.TimeZones', 'about.TimeZones (Time zones)'),
('addresses.AddressTypes', 'addresses.AddressTypes (Address types)'),
('addresses.DataSources', 'addresses.DataSources (Data sources)'),
('cal.DisplayColors', 'cal.DisplayColors (Display colors)'),
('cal.EntryStates', 'cal.EntryStates (Entry states)'),
('cal.EventEvents', 'cal.EventEvents (Observed events)'),
('cal.GuestStates', 'cal.GuestStates (Presence states)'),
('cal.NotifyBeforeUnits', 'cal.NotifyBeforeUnits (Notify Units)'),
('cal.PlannerColumns', 'cal.PlannerColumns (Planner columns)'),
('cal.ReservationStates', 'cal.ReservationStates (States)'),
('cal.TaskStates', 'cal.TaskStates (Task states)'),
('cal.YearMonths', 'cal.YearMonths'),
('calview.Planners', 'calview.Planners'),
('changes.ChangeTypes', 'changes.ChangeTypes (Change Types)'),
('checkdata.Checkers', 'checkdata.Checkers (Data checkers)'),
('comments.CommentEvents', 'comments.CommentEvents (Observed events)'),
('comments.Emotions', 'comments.Emotions (Emotions)'),
('contacts.CivilStates', 'contacts.CivilStates (Civil states)'),
('contacts.PartnerEvents', 'contacts.PartnerEvents (Observed events)'),
('countries.PlaceTypes', 'countries.PlaceTypes'),
('courses.ActivityLayouts', 'courses.ActivityLayouts (Course layouts)'),
('courses.CourseStates', 'courses.CourseStates (Activity states)'),
('courses.EnrolmentStates', 'courses.EnrolmentStates (Enrolment states)'),
('cv.CefLevel', 'cv.CefLevel (CEF levels)'),
('cv.EducationEntryStates', 'cv.EducationEntryStates'),
('cv.HowWell', 'cv.HowWell'),
('excerpts.Shortcuts', 'excerpts.Shortcuts (Excerpt shortcuts)'),
('households.MemberDependencies',
'households.MemberDependencies (Household Member Dependencies)'),
('households.MemberRoles', 'households.MemberRoles (Household member roles)'),
('humanlinks.LinkTypes', 'humanlinks.LinkTypes (Parency types)'),
('ledger.CommonAccounts', 'ledger.CommonAccounts (Common accounts)'),
('ledger.DC', 'ledger.DC (Booking directions)'),
('ledger.JournalGroups', 'ledger.JournalGroups (Journal groups)'),
('ledger.PeriodStates', 'ledger.PeriodStates (States)'),
('ledger.TradeTypes', 'ledger.TradeTypes (Trade types)'),
('ledger.VoucherStates', 'ledger.VoucherStates (Voucher states)'),
('ledger.VoucherTypes', 'ledger.VoucherTypes (Voucher types)'),
('notes.SpecialTypes', 'notes.SpecialTypes (Special note types)'),
('notify.MailModes', 'notify.MailModes (Notification modes)'),
('notify.MessageTypes', 'notify.MessageTypes (Message Types)'),
('pages.NodeTypes', 'pages.NodeTypes (Node types)'),
('phones.ContactDetailTypes',
'phones.ContactDetailTypes (Contact detail types)'),
('printing.BuildMethods', 'printing.BuildMethods'),
('products.BarcodeDrivers', 'products.BarcodeDrivers (Barcode drivers)'),
('products.DeliveryUnits', 'products.DeliveryUnits (Delivery units)'),
('products.PriceFactors', 'products.PriceFactors (Price factors)'),
('products.ProductTypes', 'products.ProductTypes (Product types)'),
('properties.DoYouLike', 'properties.DoYouLike'),
('properties.HowWell', 'properties.HowWell'),
('properties.PropertyAreas', 'properties.PropertyAreas (Property areas)'),
('system.DashboardLayouts', 'system.DashboardLayouts'),
('system.DurationUnits', 'system.DurationUnits'),
('system.Genders', 'system.Genders'),
('system.PeriodEvents', 'system.PeriodEvents (Observed events)'),
('system.Recurrencies', 'system.Recurrencies (Recurrencies)'),
('system.Weekdays', 'system.Weekdays'),
('system.YesNo', 'system.YesNo (Yes or no)'),
('uploads.Shortcuts', 'uploads.Shortcuts (Upload shortcuts)'),
('uploads.UploadAreas', 'uploads.UploadAreas (Upload areas)'),
('users.UserTypes', 'users.UserTypes (User types)'),
('vat.DeclarationFieldsBase',
'vat.DeclarationFieldsBase (Declaration fields)'),
('vat.VatAreas', 'vat.VatAreas (VAT areas)'),
('vat.VatClasses', 'vat.VatClasses (VAT classes)'),
('vat.VatColumns', 'vat.VatColumns (VAT columns)'),
('vat.VatRegimes', 'vat.VatRegimes (VAT regimes)'),
('vat.VatRules', 'vat.VatRules (VAT rules)'),
('xl.Priorities', 'xl.Priorities (Priorities)')]
The lino_xl.lib.properties.PropType.choicelist
field uses this function
for its choices.
ChoiceListField¶
Example on how to use a ChoiceList in your model:
from django.db import models
from lino.modlib.properties.models import HowWell
class KnownLanguage(models.Model):
spoken = HowWell.field(verbose_name=_("spoken"))
written = HowWell.field(verbose_name=_("written"))