Introduction to Combo boxes¶
A combo box is a widget used to edit a field that has a number of choices. It shows a drop-down list with these choices while you are editing. It also supports filtering these choices while you are typing. Unlike a radio button it allows only one choice, not more.
Filtering is done via setting
Model.quick_search_fields, or by overriding
More examples and info can be seen here Chooser examples
The challenge is old: when you have two fields on a model (here country and city on the Person model) and you want the choices for one of these field (here city) to depend on the value of the other field (here country), then you need a context-sensitive chooser.
The Lino solution is you simply define the following function on the Person model:
@dd.chooser() def city_choices(cls, country): return rt.models.combo.City.objects.filter(country=country)
Lino finds all choosers at startup that are decorated with the
dd.chooser decorator (which turns it into
a "chooser" object) and has a name of the form.
Lino matches it to the field using the fieldname in`<fieldname>_choices``. Lino matches the context related fields by positional argument named the same as other fields. ar is also a valid argument name for the chooser. The value will be the action request used in the API call. The request object can be used to
This unit uses the
lino_book.projects.combo demo project.
Here is the
models.py file :
from django.db import models from django.core.exceptions import ValidationError from lino.api import dd, rt class Country(dd.Model): class Meta(object): verbose_name = "Country" verbose_name_plural = "Countries" name = models.CharField(max_length=30) def __str__(self): return self.name class City(dd.Model): class Meta(object): verbose_name_plural = "Cities" country = dd.ForeignKey(Country, on_delete=models.CASCADE) name = models.CharField(max_length=30) def __str__(self): return self.name class Person(dd.Model): class Meta(object): verbose_name_plural = "Persons" name = models.CharField(max_length=100) birthdate = models.DateField(null=True, blank=True) country = dd.ForeignKey(Country, on_delete=models.SET_NULL, null=True) city = dd.ForeignKey(City, on_delete=models.SET_NULL, null=True) @dd.chooser() def city_choices(cls, country): return City.objects.filter(country=country) def create_city_choice(self, text, ar=None): """ Called when an unknown city name was given. """ if self.country is None: raise ValidationError( "Cannot auto-create city %r if country is empty", text) return City.lookup_or_create( 'name', text, country=self.country) def __str__(self): return self.name from .ui import *
Here are the other files used in this unit.
from lino.api import dd from .models import Person, City, Country class Persons(dd.Table): model = Person detail_layout = dd.DetailLayout(""" name country city """, window_size=(50, 'auto')) insert_layout = """ name country city """ class Cities(dd.Table): model = City class Countries(dd.Table): model = Country
__init__.py file specifies how the data views are organized
in the main menu:
from lino.api import ad, _ class Plugin(ad.Plugin): verbose_name = _("Combo") def setup_main_menu(self, site, profile, m): m = m.add_menu(self.app_label, self.verbose_name) m.add_action('combo.Persons') m.add_action('combo.Countries') m.add_action('combo.Cities')
Here is the project's
settings.py file :
from lino.projects.std.settings import * SITE = Site(globals(), 'lino_book.projects.combo') SITE.demo_fixtures = ['demo'] DEBUG = True
And finally the
fixtures/demo.py file contains the data we use
to fill our database:
from lino.api import rt def objects(): Country = rt.models.combo.Country City = rt.models.combo.City be = Country(name="Belgium") yield be ee = Country(name="Estonia") yield ee yield City(name="Eupen", country=be) yield City(name="Brussels", country=be) yield City(name="Gent", country=be) yield City(name="Raeren", country=be) yield City(name="Namur", country=be) yield City(name="Tallinn", country=ee) yield City(name="Tartu", country=ee) yield City(name="Vigala", country=ee)
The files we are going to use in this tutorial are already on your
hard disk in the
Start your development server and your browser, and have a look at the application:
$ go combo $ python manage.py runserver
Explore the application and try to extend it: change things in the code and see what happens.
This is inspired by Vitor Freitas' blog post How to Implement Dependent/Chained Dropdown List with Django.
The remaining samples are here in order to test the project.
>>> from lino import startup >>> startup('lino_book.projects.combo.settings') >>> from lino.api.doctest import *
>>> rt.show('combo.Cities') ==== ========= ========== ID Country name ---- --------- ---------- 1 Belgium Eupen 2 Belgium Brussels 3 Belgium Gent 4 Belgium Raeren 5 Belgium Namur 6 Estonia Tallinn 7 Estonia Tartu 8 Estonia Vigala ==== ========= ==========