DaoMail - путь письма
социальная почтовая служба (beta-версия)
весь DaoMail
вход / регистрация
Гость
ваша подписка (0
реклама
Django Framework / [Из песочницы] Еще одна реализация поля «город» для Django
| text | html

web-архив: по темам » культура, искусство » музыка » популярная музыка » это письмо

2011-09-19 16:45:55

версия для печати

image «Еще одна» — потому что мне кажется, что я что-то упускаю и, в действительности, есть хорошее, но неизвестное мне решение “из коробки”. Тем не менее, вот мой рецепт:

Данные

В первую очередь, возник вопрос — где брать список городов. Исторически, я остановил свой выбор на 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>

Если все прошло успешно, то на выходе получится что-то такое:

image

или даже такое (поле alternatenames содержит варианты названий на разных языках)

image

Логичным продолжением, я вижу добавление в модель admin1 (административная единица первого уровня) для отделения населенных пунктов с одинаковым названием, тесты jemeter для замеров производительности и написание reusable application, хотя бы доработав django-geonames для MySQL, однако на последнем Kyiv.py я неожиданно осознал, что теперь путь проекта лежит в сторону geodjango, PostgreSQL и PostGIS, так как роль города в профайле становится больше, чем просто информационная.

P.S. Если кого-то интересует тестовый проект-демо, выложу.

Ссылки:

Список потенциальных источников данных OSM

github и bitbucket страницы описания соответствующих проектов.

Спасибо за внимание.

,

,

Источник



web-архив: по темам » культура, искусство » музыка » популярная музыка » это письмо








© 2004-2024 DaoMail.ru