Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
More about combo boxes¶
A combo box is a widget used to edit a field that has a list 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.
Lino renders two types of fields using combo boxes: foreign key fields and choicelist fields.
The combo box on a foreign key can optionally be “learning”, i.e. it can accept new values and store them in the database. See How to define a learning foreign key.
Filtering results¶
Filtering in a combo box is done via setting Model.quick_search_fields
,
or by overriding Model.quick_search_filter()
.
Context-sensitive Comboboxes¶
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 FOO_choices (where FOO is the name of a database field on
this model).
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
Then Lino then does the dirty work of generating appropriate JavaScript and HTML code and the views which respond to the AJAX calls.
Screenshots¶
Source code¶
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.
The ui.py
file specifies a data table for every model:
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
The __init__.py
file specifies how the data tables 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, ar=None):
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)
Exercise¶
The files we are going to use in this tutorial are already on your
hard disk in the lino_book.projects.combo
package.
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.
Discussion¶
This is inspired by Vitor Freitas’ blog post How to Implement Dependent/Chained Dropdown List with Django.
Don’t read on¶
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
==== ========= ==========