You are in luck, I had to do this for one of my projects. Here's a widget I
made for a project where I had the same problem but I had 3 levels,
Country->state->city
For countries that don't have states you can use administrative regions,
but it's also trivial to adapt this to only 2 levels.
*MODELS*
db.define_table('country',
Field('name', length=128, requires=IS_NOT_EMPTY(), notnull=True,
unique=True),
Field('acronym', length=2, requires=IS_NOT_EMPTY()),
format='%(name)s')
db.define_table('state',
Field('name', length=128, requires=IS_NOT_EMPTY(), notnull=True),
Field('acronym', 'string', requires=IS_NOT_EMPTY()),
Field('country','reference country', requires=IS_IN_DB(db, db.country.id,
'%(name)s'), represent=lambda x, row: db.country[x].name),
format='%(name)s')
db.define_table('city',
Field('name', length=128, requires=IS_NOT_EMPTY(), notnull=True),
Field('state', 'reference state', requires=IS_IN_DB(db, db.state.id,
'%(name)s'), represent=lambda x, row: db.state[x].name),
format='%(name)s')
auth.settings.extra_fields['auth_user']= [
Field('city','reference city', label=T('City'), requires=IS_IN_DB(db,
db.city.id, '%(name)s'))]
class LOCATION_SELECTS(DIV):
""" A helper that essentially creates a div with the 3 selects used to
choose a location """
def __init__(self, *args, **attributes):
if '_id' not in attributes:
import random
attributes['_id'] = 'ls' + str(random.random())[2:]
def mk_sel_select(entities, selected, name):
s = SELECT(_name=name)
s.append(OPTION(T('Pick a %s' % name), _value=0))
for entity in entities:
if entity.id == selected:
s.append(OPTION(entity.name, _value=entity.id,
_selected='selected'))
else:
s.append(OPTION(entity.name, _value=entity.id))
return s
DIV.__init__(self, *args, **attributes)
countries = db(db.country.id > 0).select(orderby=db.country.name)
if 'initial_city' in attributes:
sel_city = attributes['initial_city']
rec_city = db.city[sel_city]
sel_state = rec_city.state
sel_country = rec_city.state.country
states = db(db.state.country ==
sel_country).select(orderby=db.state.name)
cities = db(db.city.state == sel_state).select(orderby=db.city.name)
self.components.append(mk_sel_select(countries, sel_country,
'country'))
self.components.append(mk_sel_select(states, sel_state, 'state'))
self.components.append(mk_sel_select(cities, sel_city, 'city'))
else:
self.components.append(SELECT(OPTION(T('Pick a country'),
_value=0), *[OPTION(country.name,_value=country.id) for country in countries],
_name='country'))
self.components.append(SELECT(OPTION(T('Pick a region'), _value=0),
_name='state', _disabled='true'))
self.components.append(SELECT(OPTION(T('Pick a city'), _value=0),
_name='city', _disabled='true'))
def xml(self):
return (DIV.xml(self) +
SCRIPT("""$(document).ready( function() {
var country_sel = $('#%(sid)s select[name="country"]');
var state_sel = $('#%(sid)s select[name="state"]'); var
city_sel = $('#%(sid)s select[name="city"]'); var
state_cache = new Object(); var city_cache = new
Object();
var state_func = function(data) {
state_sel.children('option:gt(0)').remove();
$.each(data.states, function(key, val) {
state_sel.append('<option value="' + data.states[key].id +
'">' + data.states[key].name + '</option>'); });
state_sel.removeAttr('disabled');
}
var city_func = function(data) {
city_sel.children('option:gt(0)').remove();
$.each(data.cities, function(key, val) {
city_sel.append('<option value="' + data.cities[key].id +
'">' + data.cities[key].name + '</option>'); });
city_sel.removeAttr('disabled');
}
country_sel.change(function () {
var val = country_sel.val(); if (val === '0') {
state_sel.children('option:gt(0)').remove()
state_sel.attr('disabled', '');
city_sel.children('option:gt(0)').remove();
city_sel.attr('disabled', ''); return;
}
city_sel.children('option:gt(0)').remove();
city_sel.attr('disabled', '');
if (val in state_cache) {
state_func(state_cache[val]); } else {
$.getJSON('%(getstates)s' + '/' + encodeURIComponent(val),
function(data) { state_cache[val] = data;
state_func(data);
}); } });
state_sel.change(function () {
var val = state_sel.val(); if (val === '0') {
city_sel.children('option:gt(0)').remove();
city_sel.attr('disabled', '');
return; } if (val in
city_cache) { city_func(city_cache[val]);
} else {
$.getJSON('%(getcities)s' + '/' + encodeURIComponent(val), function(data) {
city_cache[val] = data;
city_func(data); });
} }); });
""" % {'sid':self['_id'], 'getstates':URL('default', 'get_states.json'),
'getcities':URL('default', 'get_cities.json')} ).xml())
def city_widget(field, value):
""" A widget for city fields that makes you put the country and state too
"""
try:
value = int(value)
if value <= 0:
raise ValueError()
widget = LOCATION_SELECTS(initial_city=value)
except (ValueError, TypeError) as e:
widget = LOCATION_SELECTS()
widget.components[-1]['requires'] = field.requires
return widget
db.auth_user.city.widget = city_widget
*CONTROLLERS (default.py)*
def get_states():
""" Get the states for a given country id, these might be districts
or whatever makes sense for the country in question. """
session.forget()
result = db(db.state.country == request.args(0)).select(db.state.id,
db.state.name, orderby=db.state.name).as_list()
return {'states': result}
def get_cities():
""" Get the cities for a given state id. """
session.forget()
result = db(db.city.state == request.args(0)).select(db.city.id,
db.city.name, orderby=db.city.name).as_list()
return {'cities': result}
Make sure you allow generic.json for the 2 controllers. And it is done.
--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to the Google Groups
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.