«Еще одна» — потому что мне кажется, что я что-то упускаю и, в действительности, есть хорошее, но неизвестное мне решение “из коробки”. Тем не менее, вот мой рецепт:
В первую очередь, возник вопрос — где брать список городов. Исторически, я остановил свой выбор на geonames.org c creative commons лицензией. OpenStreetMaps от него отказались, из-за возможных “патентных” претензий от Google, где Geonames берет часть данных. (Тем не менее, на http://www.openstreetmap.org/ он используется как альтернативный источник информации.)
Учитывая поверхностность интересов (в данном случае был нужен просто город, как поле профайла, без геоопераций вроде поиска вхождений в область и т.д.), нужно преобразовать данные geonames в подходящий нам, простой формат.
Я не тратил время на поиск красивого решения и сделал все просто, с помощью SQL и дополнительной базы:
1. Создаем базу geonames и импортируем туда данные (http://forum.geonames.org/gforum/posts/list/732.page)
На этом можно остановиться и использовать их в таком виде, а можно преобразовать чтобыподчинить привязать к своей логике.
2. Преобразуем. Не претендую на единственно правильное решение, некоторые, вызывающие технические вопросы страны я порезал:
Очевидный минус — необходимость написания функционала для обновления данных. Найденные мной решения заточены под PostgreSQL.
Родной full-text поиск MySQL не справлялся с быстрым поиском по такому количеству населенных пунктов, поэтому я использовал Sphinxsearch (думаю, подойдет любой другой Solr)
Конфиг индекса и источника:
Нам понадобятся:
django-selectable
django-sphinx
django-profiles – для моего конкретного случая с профайлом
Поставим оба приложения, добавим их в INSTALLED_APPS.
Django-sphinx требует номера версии API в settings.py
для Sphinx 0.9.9:
создадим модель для города и страны в приложении, отвечающем за гео логику/модели
(geo в моем случае)
Если поле города нужно для профайла, добавляем его и модель в приложение, которое занимается профайлами, у меня это usermanage:
и форму профайла:
и в settings.py
подробнее о настройке django-profiles тут
django-selectable требует настройки urls.py
Создадим lookup.py и метод в нем для реализации запросов к базе и начального заполнения поля:
В forms.py профайла нужно импортировать lookups и поля/виджеты selectable
Для работы django-selectable необходимы библиотеки jquery, добавляем их в base.html, предварительно скачав jquery и jquery-ui (jquery.dj.selectable.js — идет в django-selectable)
Если все прошло успешно, то на выходе получится что-то такое:
или даже такое (поле alternatenames содержит варианты названий на разных языках)
Логичным продолжением, я вижу добавление в модель admin1 (административная единица первого уровня) для отделения населенных пунктов с одинаковым названием, тесты jemeter для замеров производительности и написание reusable application, хотя бы доработав django-geonames для MySQL, однако на последнем Kyiv.py я неожиданно осознал, что теперь путь проекта лежит в сторону geodjango, PostgreSQL и PostGIS, так как роль города в профайле становится больше, чем просто информационная.
P.S. Если кого-то интересует тестовый проект-демо, выложу.
Ссылки:
Список потенциальных источников данных OSM
github и bitbucket страницы описания соответствующих проектов.
Спасибо за внимание.
Данные
В первую очередь, возник вопрос — где брать список городов. Исторически, я остановил свой выбор на geonames.org c creative commons лицензией. OpenStreetMaps от него отказались, из-за возможных “патентных” претензий от Google, где Geonames берет часть данных. (Тем не менее, на http://www.openstreetmap.org/ он используется как альтернативный источник информации.)
Учитывая поверхностность интересов (в данном случае был нужен просто город, как поле профайла, без геоопераций вроде поиска вхождений в область и т.д.), нужно преобразовать данные geonames в подходящий нам, простой формат.
Я не тратил время на поиск красивого решения и сделал все просто, с помощью SQL и дополнительной базы:
1. Создаем базу geonames и импортируем туда данные (http://forum.geonames.org/gforum/posts/list/732.page)
На этом можно остановиться и использовать их в таком виде, а можно преобразовать чтобы
2. Преобразуем. Не претендую на единственно правильное решение, некоторые, вызывающие технические вопросы страны я порезал:
-- Country import
insert into common_country(id,name,code,population,latitude,longitude,alternatenames) select geonameid as id,name,country as code, population,latitude,longitude,alternatenames from geonames.geoname gn where (gn.fcode IN ('PCLI','PCLIX','PCLD','PCLS','PCLF','PCL','TERR'));
delete from common_country where id in (2077507,2170371,7910334,7909807);
create unique index common_country_idx on common_country (id);
create index common_country_code_idx on common_country(code);
-- South Korea Fix
update common_country set alternatenames = concat(alternatenames,"Korea, Republic of") where id = 1835841;
-- City import
insert into common_city(id,name,country_id,alternatenames,latitude,longitude, adm) select gn.geonameid as id, gn.name, c.id as country_id, gn.alternatenames, gn.latitude, gn.longitude, admin1 as adm from geonames.geoname gn left join common_country as c on gn.country=c.code where (gn.fcode in ('PPL','PPLC','PPLA','PPLA2','PPLA3','PPLA4'));
Очевидный минус — необходимость написания функционала для обновления данных. Найденные мной решения заточены под PostgreSQL.
Средства поиска
Родной full-text поиск MySQL не справлялся с быстрым поиском по такому количеству населенных пунктов, поэтому я использовал Sphinxsearch (думаю, подойдет любой другой Solr)
Конфиг индекса и источника:
source geo_city
{
type = mysql
sql_host = localhost
sql_user = citylist
sql_pass = citylist
sql_db = citylist
sql_port =
sql_query_pre = SET NAMES utf8
sql_query_post =
sql_query = \
SELECT id, name, country_id, alternatenames, latitude, longitude\
FROM geo_city
sql_query_info = SELECT * FROM `geo_city` WHERE `id` = $id
# ForeignKey's
sql_attr_uint = country_id
}
index common_city
{
source = geo_city
path = /var/lib/sphinxsearch/data/geo_city
docinfo = extern
morphology = none
stopwords =
min_word_len = 2
charset_type = utf-8
min_prefix_len = 2
min_infix_len = 0
prefix_fields = name, alternatenames
enable_star = 1
}
Представление
Нам понадобятся:
django-selectable
django-sphinx
django-profiles – для моего конкретного случая с профайлом
Поставим оба приложения, добавим их в INSTALLED_APPS.
Django-sphinx требует номера версии API в settings.py
для Sphinx 0.9.9:
SPHINX_API_VERSION = 0x116
создадим модель для города и страны в приложении, отвечающем за гео логику/модели
(geo в моем случае)
class Country(models.Model):
name = models.CharField(max_length=200)
code = models.CharField(max_length=10)
population = models.IntegerField()
latitude = models.FloatField()
longitude = models.FloatField()
alternatenames = models.CharField(max_length=2000, blank=True, default='')
def __str__(self):
return unicode(self.name).encode('utf-8')
def __unicode__(self):
return unicode(self.name)
class City(models.Model):
name = models.CharField(max_length=200)
country = models.ForeignKey(Country)
alternatenames = models.CharField(max_length=2000, blank=True, default='')
latitude = models.FloatField(default=0)
longitude = models.FloatField(default=0)
adm = models.CharField(max_length=200)
search = SphinxSearch(weights={
'name': 100,
'alternatenames': 80
})
def __str__(self):
return unicode(self.name).encode('utf-8')
def __unicode__(self):
return unicode(self.name)
Если поле города нужно для профайла, добавляем его и модель в приложение, которое занимается профайлами, у меня это usermanage:
class CustomUserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
full_name = models.CharField(max_length=200, blank=True)
city = models.ForeignKey(City, blank=True, null=True)
country = models.ForeignKey(Country, blank=True, null=True, editable=False)
date_registered = models.DateField(editable=False, auto_now_add=True)
и форму профайла:
class UserProfileForm(forms.ModelForm):
''' Form to edit profile'''
full_name = forms.CharField(widget=forms.TextInput())
city = selectable.AutoCompleteSelectField(
label='City please',
lookup_class = common.lookups.CityLookup,
required=False,
)
def clean_city(self):
"""
Convert city code to city object
"""
city_id = int(self.data["city_1"].encode("utf8"))
city = City.objects.get(id=city_id)
return city
class Meta:
model = CustomUserProfile
exclude = ("user",)
и в settings.py
AUTH_PROFILE_MODULE = 'usermanage.CustomUserProfile'
подробнее о настройке django-profiles тут
django-selectable требует настройки urls.py
(r'^selectable/', include('selectable.urls')),
Создадим lookup.py и метод в нем для реализации запросов к базе и начального заполнения поля:
class CityLookup(LookupBase):
model = City
item = None
def get_query(self, request, term):
qs = self.model.search.query(term + "*")
return qs
def get_queryset(self):
return None
def get_item_id(self, item):
return item.id
def get_item_value(self, item):
if (not self.item):
return smart_unicode(item)
return smart_unicode(self.item.name)
def get_item_label(self, item):
return u"%s, %s" % (item.name, item.country)
def get_item(self, value):
item = None
if value:
try:
item = City.objects.get(id=value)
self.item = item
except IndexError:
pass
return item
try:
registry.register(CityLookup)
except:
pass
В forms.py профайла нужно импортировать lookups и поля/виджеты selectable
import selectable.forms as selectable
import geo.lookups</code>
Для работы django-selectable необходимы библиотеки jquery, добавляем их в base.html, предварительно скачав jquery и jquery-ui (jquery.dj.selectable.js — идет в django-selectable)
<script type="text/javascript" src="/js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery-ui.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.dj.selectable.js"></script>
Если все прошло успешно, то на выходе получится что-то такое:
или даже такое (поле alternatenames содержит варианты названий на разных языках)
Логичным продолжением, я вижу добавление в модель admin1 (административная единица первого уровня) для отделения населенных пунктов с одинаковым названием, тесты jemeter для замеров производительности и написание reusable application, хотя бы доработав django-geonames для MySQL, однако на последнем Kyiv.py я неожиданно осознал, что теперь путь проекта лежит в сторону geodjango, PostgreSQL и PostGIS, так как роль города в профайле становится больше, чем просто информационная.
P.S. Если кого-то интересует тестовый проект-демо, выложу.
Ссылки:
Список потенциальных источников данных OSM
github и bitbucket страницы описания соответствующих проектов.
Спасибо за внимание.