Django项目使用的地点模型和全球地点数据
项目描述
django-cities
Django项目使用的地点模型和全球地点数据
django-cities提供与地点相关的模型(例如国家、地区、城市)和数据(来自GeoNames),这些可以在您的django项目中使用。
此包官方支持Python/Django的所有当前支持版本。
Python | 3.6 | 3.7 | 3.8 | 3.9 | 3.10 |
---|---|---|---|---|---|
Python Django 2.2 | :white_check_mark | :white_check_mark | :white_check_mark | :white_check_mark | :x |
Python Django 3.1 | :white_check_mark | :white_check_mark | :white_check_mark | :white_check_mark | :x |
Python Django 3.2 | :white_check_mark | :white_check_mark | :white_check_mark | :white_check_mark | :white_check_mark |
Python Django 4.0 | :x | :x | :white_check_mark | :white_check_mark | :white_check_mark |
关键字 | |
---|---|
:white_check_mark | 官方支持、经过测试且通过 |
:large_blue_circle | 经过测试且通过,但非官方支持 |
:x | 已知不兼容性 |
由Ben Dowling编写,以及一些优秀的贡献者。
在city.io和country.io查看一些数据。
需求
您的数据库必须支持空间查询,有关详细信息和方法设置,请参阅GeoDjango文档。
安装
将此存储库克隆到您的项目中
git clone https://github.com/coderholic/django-cities.git
下载zip文件并解压
wget https://github.com/coderholic/django-cities/archive/master.zip
unzip master.zip
使用pip安装
pip install django-cities
配置
您需要启用GeoDjango。有关指导,请参阅该文档。
您需要在项目的settings.py
文件中将cities
添加到INSTALLED_APPS
。
INSTALLED_APPS = (
# ...
'cities',
# ...
)
迁移配置
应在运行任何迁移之前审查并设置或修改这些设置。
可交换的模型
一些用户可能希望覆盖某些默认模型以添加数据、覆盖默认模型方法或添加自定义管理器。该项目支持使用django-swappable-models项目来交换模型。
要交换模型,首先在自定义城市应用程序中定义自己的自定义模型。您需要从cities.models
中继承适当的基模型。
以下是一个示例my_cities_app/models.py
from django.db import models
from cities.models import BaseCountry
class CustomCountryModel(BaseCountry, models.Model):
more_data = models.TextField()
class Meta(BaseCountry.Meta):
pass
然后您需要通过设置适当的选项来配置您的项目
模型 | 设置名称 | 默认值 |
---|---|---|
大陆 | CITIES_CONTINENT_MODEL |
cities.Continent |
国家 | CITIES_COUNTRY_MODEL |
cities.Country |
城市 | CITIES_CITY_MODEL |
cities.City |
因此,要使用我们上面定义的CustomCountryModel
,我们需要将点分模型字符串添加到项目的settings.py
中
# ...
CITIES_COUNTRY_MODEL = 'my_cities_app.CustomCountryModel'
# ...
点分模型字符串只是带有.models
子字符串的点分导入路径,即<app_label>.<model_class_name>
。
一旦在settings.py
中设置了选项,django-cities中所有适当的外键都将指向您的自定义模型。因此,在上面的示例中,外键Region.country
、City.country
和PostalCode.country
都将自动指向CustomCountryModel
。这意味着如果您不想自定义任何相关模型,则不需要对其进行自定义。
替代名称类型
替代名称的Geonames数据包含附加信息,例如链接到外部网站(主要是维基百科文章)和发音指南(拼音)。然而,django-cities只使用和导入这些类型的一个子集。由于一些用户可能希望使用所有这些类型,因此可以使用CITIES_ALTERNATIVE_NAME_TYPES
和CITIES_AIRPORT_TYPES
设置在数据库中定义替代名称类型。
应将这些设置指定为元组的元组选项
CITIES_AIRPORT_TYPES = (
('iata', _("IATA (Airport) Code")),
('icao', _("ICAO (Airport) Code")),
('faac', _("FAAC (Airport) Code")),
)
CITIES_ALTERNATIVE_NAME_TYPES = (
('name', _("Name")),
('abbr', _("Abbreviation")),
('link', _("Link")),
)
如果将CITIES_INCLUDE_AIRPORT_CODES
设置为True
,则CITIES_AIRPORT_TYPES
中的选择将被追加到CITIES_ALTERNATIVE_NAME_TYPES
的选择中。否则,不导入任何机场类型。
Geonames数据还包含纯数字的替代名称。
CITIES_INCLUDE_NUMERIC_ALTERNATIVE_NAMES
设置控制是否导入纯数字的替代名称。设置为True
以导入它们,设置为False
以跳过它们。
大陆数据
由于大洲数据很少(如果有的话)更改,大洲数据直接从django-cities发行版中包含的Python数据结构加载。但是,有不同数量的大洲的不同大洲模型。因此,一些用户可能希望通过将CITIES_CONTINENT_DATA
设置为Python字典来覆盖默认设置,其中键是大洲代码,值是(名称,geonameid)元组。
有关不同大洲模型的概述,请参阅维基百科上的大洲文章
https://zh.wikipedia.org/wiki/大洲#数量
以下是cities/conf.py
中的默认大洲数据(查看)
CITIES_CONTINENT_DATA = {
'AF': ('Africa', 6255146),
'AS': ('Asia', 6255147),
'EU': ('Europe', 6255148),
'NA': ('North America', 6255149),
'OC': ('Oceania', 6255151),
'SA': ('South America', 6255150),
'AN': ('Antarctica', 6255152),
}
注意,如果您不使用这些默认设置,您需要在导入脚本将国家处理并保存到数据库之前,通过设置带有country_pre
方法的插件来调整国家模型的大洲ID。请通过创建包含您的插件及其相关文档的pull request将您的插件贡献回此项目,以便其他人可以通过创建pull request从您的作品中受益。
运行迁移
配置所有迁移设置后,运行以下命令
python manage.py migrate cities
以创建所需的数据库表并将大洲数据添加到其表中。
导入配置
在导入任何数据之前,也应审查并设置或修改这些设置。导入数据后更改这些设置可能不会产生预期的效果。
下载目录
指定下载目录(用于指定可写目录)。
默认:cities/data
如果您在云服务提供商上,或者django-cities安装在只读介质上,则可能需要使用此设置。
注意,此路径必须是绝对路径。
CITIES_DATA_DIR = '/var/data'
下载文件
您可以使用导入命令使用的文件覆盖文件
CITIES_FILES = {
# ...
'city': {
'filename': 'cities1000.zip',
'urls': ['http://download.geonames.org/export/dump/'+'{filename}']
},
# ...
}
还可以指定多个文件名以进行处理。请注意,这些文件按指定顺序处理,因此列表中指定较晚的文件中的重复数据将覆盖列表中指定较早的文件中的数据。
CITIES_FILES = {
# ...
'city': {
'filenames': ["US.zip", "GB.zip", ],
'urls': ['http://download.geonames.org/export/dump/'+'{filename}']
},
# ...
}
请注意,您不需要在CITIES_FILES
字典中指定所有键。您未指定的任何键将使用在cities/conf.py
中定义的默认值。
货币数据
Geonames数据包括货币数据,但它仅限于货币代码(例如:“USD”)和货币名称(例如:“美元”)。
然而,类似于大洲数据,由于它很少更改,货币符号直接从django-cities发行版中包含的Python数据结构加载到CITIES_CURRENCY_SYMBOLS
设置中。如果用户希望添加或修改导入的货币符号,则可以覆盖此设置。
有关默认值,请参阅包含的cities/conf.py
文件(查看)。
CITIES_CURRENCY_SYMBOLS = {
"AED": "د.إ", "AFN": "؋", "ALL": "L", "AMD": "դր.", "ANG": "ƒ", "AOA": "Kz",
"ARS": "$", "AUD": "$", "AWG": "ƒ", "AZN": "m",
"BAM": "KM", "BBD": "$", "BDT": "৳", "BGN": "лв", "BHD": "ب.د", "BIF": "Fr",
# ...
"UAH": "₴", "UGX": "Sh", "USD": "$", "UYU": "$", "UZS": "лв",
不再存在的国家
Geonames数据包括不再存在的国家。目前,这些国家是荷属安的列斯(AN
)和塞尔维亚和黑山(CS
)。如果您希望导入这些国家,请将CITIES_NO_LONGER_EXISTENT_COUNTRY_CODES
设置为空列表([]
)。
默认:['CS', 'AN']
CITIES_NO_LONGER_EXISTENT_COUNTRY_CODES = ['CS', 'AN']
邮政编码验证
Geonames数据包含国家邮政编码格式和正则表达式,以及邮政编码。其中一些邮政编码与其国家的不匹配正则表达式。当导入数据时,希望忽略无效的邮政编码的用户可以将CITIES_VALIDATE_POSTAL_CODES
设置设为True
,以跳过导入不验证国家邮政编码正则表达式的邮政编码。
如果您了解不验证的邮政编码,请在Geonames网站上更新邮政编码本身或国家邮政编码的正则表达式。这样做将帮助所有Geonames用户(包括本项目以及其他所有Geonames用户)。
CITIES_VALIDATE_POSTAL_CODES = True
自定义slugify()
函数
您可能希望自定义django-cities生成的slug。要实现这一点,您需要编写自己的slugify()
函数,并在CITIES_SLUGIFY_FUNCTION
中指定其点分导入路径。
CITIES_SLUGIFY_FUNCTION = 'cities.util.default_slugify'
您自定义的slugify函数应接受两个参数:对象本身和由对象本身生成的slug。它应返回一个字符串作为最终的slug。
由于slugify函数包含将被多个对象复用的代码,因此django-cities中只有一个slugify函数用于所有对象。要为不同类型的对象生成不同的slug,请测试对象的类名(obj.__class__.__name__
)。
默认slugify函数(见cities/util.py
)
# SLUGIFY REGEXES
to_und_rgx = re.compile(r"[']", re.UNICODE)
slugify_rgx = re.compile(r'[^-\w._~]', re.UNICODE)
multi_dash_rgx = re.compile(r'-{2,}', re.UNICODE)
dash_und_rgx = re.compile(r'[-_]_', re.UNICODE)
und_dash_rgx = re.compile(r'[-_]-', re.UNICODE)
starting_chars_rgx = re.compile(r'^[-._]*', re.UNICODE)
ending_chars_rgx = re.compile(r'[-._]*$', re.UNICODE)
def default_slugify(obj, value):
if value is None:
return None
value = force_text(unicode_func(value))
value = unicodedata.normalize('NFKC', value.strip())
value = re.sub(to_und_rgx, '_', value)
value = re.sub(slugify_rgx, '-', value)
value = re.sub(multi_dash_rgx, '-', value)
value = re.sub(dash_und_rgx, '_', value)
value = re.sub(und_dash_rgx, '_', value)
value = re.sub(starting_chars_rgx, '', value)
value = re.sub(ending_chars_rgx, '', value)
return mark_safe(value)
无地区城市的处理
注意:这以前是CITIES_IGNORE_EMPTY_REGIONS
。
Geonames数据文件中的一些城市没有区域信息。默认情况下,这些城市会以正常方式导入(它们仍然有与国家的外键),但如果您希望避免导入这些城市,可以将CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS
设置为True
# Import cities without region (default False)
CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS = True
要导入的语言/地区
通过语言/区域限制导入的替代名称
请注意,Geonames数据中的许多替代名称没有指定语言代码,因此如果您手动指定语言代码而未包括und
,则可能无法导入您想要的那么多替代名称。
特殊值
ALL
- 导入所有替代名称und
- 未指定语言代码的替代名称。当导入时,这些替代名称将被分配一个und
语言代码。如果未指定此语言代码,则不导入未指定语言代码的替代名称。LANGUAGES
- 一个导入Django项目settings.py
中LANGUAGES
设置中指定的所有替代名称的“快捷方式”
有关ISO639-1语言代码的完整列表,请参阅Geonames上的iso-languagecodes.txt文件。
CITIES_LOCALES = ['en', 'und', 'LANGUAGES']
限制导入的邮政编码
将导入的邮政编码限制为特定国家
特殊值
ALL
- 导入所有邮政编码
CITIES_POSTAL_CODES = ['US', 'CA']
插件
您可以编写自己的插件来在数据写入数据库之前和之后处理数据。有关详细信息,请参阅编写插件部分。
要激活插件,您需要将它们的点分导入字符串添加到CITIES_PLUGINS
选项中。此示例激活了django-cities附带的postal_code_ca
和reset_queries
插件
CITIES_PLUGINS = [
# Canadian postal codes need region codes remapped to match geonames
'cities.plugin.postal_code_ca.Plugin',
# Reduce memory usage when importing large datasets (e.g. "allCountries.zip")
'cities.plugin.reset_queries.Plugin',
]
请注意,一些插件可能使用自己的配置选项
# This setting may be specified if you use 'cities.plugin.reset_queries.Plugin'
CITIES_PLUGINS_RESET_QUERIES_CHANCE = 1.0 / 1000000
导入数据
配置完所有导入设置后,运行
python manage.py cities --import=all
以导入所有地点数据。
您还可以导入特定类型的对象
python manage.py cities --import=country
python manage.py cities --import=city
注意:这可能需要很长时间,尽管终端中有进度条。
具体来说,导入邮政编码可能比导入其他对象花费一个或两个数量级的更多时间。
编写插件
您可以编写插件来修改导入脚本处理前后的数据。例如,您可以使用此功能调整国家所属的大陆,或者如果自定义并覆盖了任何django-cities模型,您还可以使用它来添加或修改任何额外的数据。
插件简单来说就是一个实现了一个或多个钩子函数的Python类。钩子函数可以在导入脚本处理数据之前修改数据,或者导入脚本将对象保存到数据库之后修改数据库。通过抛出 cities.conf.HookException
,插件可以跳过某条数据。
以下是所有可用的钩子列表
模型 | 预处理钩子名称 | 后处理钩子名称 |
---|---|---|
国家 |
country_pre |
country_post |
区域 |
region_pre |
region_post |
子区域 |
subregion_pre |
subregion_post |
城市 |
城市_pre |
city_post |
区域 |
district_pre |
district_post |
邮政编码 |
postal_code_pre |
postal_code_post |
备用名称 |
alt_name_pre |
alt_name_post |
_pre
钩子和 _post
钩子的参数签名不同。所有 _pre
钩子具有以下参数签名
class ...Plugin(object):
model_pre(self, parser, item)
而所有 _post
钩子还可以访问已保存的模型实例
class ...Plugin(object):
model_post(self, parser, <model>_instance, item)
传递给钩子的参数
self
- 插件对象本身parser
-cities.Command
管理命令的实例<model>_instance
- 根据item
创建的模型的实例item
- 包含正在处理行数据的Python字典
请注意,参数名称仅是约定,您可以自由地将它们重命名为您想要的任何名称,只要保持它们的顺序。
以下是一个完整的插件类示例
class CompleteSkeletonPlugin(object):
"""
Skeleton plugin for django-cities that has hooks for all object types, and
does not modify any import data or existing objects in the database.
"""
# Note: Only ONE of these methods needs to be defined. If a method is not
# defined, the import command will avoid calling the undefined method.
def country_pre(self, parser, imported_data_dict):
pass
def country_post(self, parser, country_instance, imported_data_dict):
pass
def region_pre(self, parser, imported_data_dict):
pass
def region_post(self, parser, region_instance, imported_data_dict):
pass
def subregion_pre(self, parser, imported_data_dict):
pass
def subregion_post(self, parser, subregion_instance, imported_data_dict):
pass
def city_pre(self, parser, imported_data_dict):
pass
def city_post(self, parser, city_instance, imported_data_dict):
pass
def district_pre(self, parser, imported_data_dict):
pass
def district_post(self, parser, district_instance, imported_data_dict):
pass
def alt_name_pre(self, parser, imported_data_dict):
pass
def alt_name_post(self, parser, alt_name_instance, imported_data_dict):
pass
def postal_code_pre(self, parser, imported_data_dict):
pass
def postal_code_post(self, parser, postal_code_instance, imported_data_dict):
pass
愚蠢的例子
from cities.conf import HookException
class DorothyPlugin(object):
"""
This plugin skips importing cities that are not in Kansas, USA.
There's no place like home.
"""
def city_pre(self, parser, import_dict):
if import_dict['cc2'] == 'US' and import_dict['admin1Code'] != 'KS':
raise HookException("Ignoring cities not in Kansas, USA") # Raising a HookException skips importing the item
else:
# Modify the value of the data before it is written to the database
import_dict['admin1Code'] = 'KS'
def city_post(self, parser, city, import_data):
# Checks if the region foreign key for the city database row is NULL
if city.region is None:
# Set it to Kansas
city.region = Region.objects.get(country__code='US', code='KS')
# Re-save any existing items that aren't in Kansas
city.save()
编写插件后,您需要通过在 CITIES_PLUGINS
设置中指定其点分导入字符串来激活它。有关详细信息,请参阅插件部分。
示例
此存储库包含一个示例项目,该项目允许您浏览地点层次结构。请参阅示例目录
。以下是一些小片段,展示了导入数据后可能进行的查询类型
# Find the 5 most populated countries in the World
>>> Country.objects.order_by('-population')[:5]
[<Country: China>, <Country: India>, <Country: United States>,
<Country: Indonesia>, <Country: Brazil>]
# Find what country the .ly TLD belongs to
>>> Country.objects.get(tld='ly')
<Country: Libya>
# 5 Nearest cities to London
>>> london = City.objects.filter(country__name='United Kingdom').get(name='London')
>>> nearest = City.objects.distance(london.location).exclude(id=london.id).order_by('distance')[:5]
# All cities in a state or county
>>> City.objects.filter(country__code="US", region__code="TX")
>>> City.objects.filter(country__name="United States", subregion__name="Orange County")
# Get all countries in Japanese preferring official names if available,
# fallback on ASCII names:
>>> [country.alt_names_ja.get_preferred(default=country.name) for country in Country.objects.all()]
# Alternate names for the US in English, Spanish and German
>>> [x.name for x in Country.objects.get(code='US').alt_names.filter(language_code='de')]
[u'USA', u'Vereinigte Staaten']
>>> [x.name for x in Country.objects.get(code='US').alt_names.filter(language_code='es')]
[u'Estados Unidos']
>>> [x.name for x in Country.objects.get(code='US').alt_names.filter(language_code='en')]
[u'United States of America', u'America', u'United States']
# Alternative names for Vancouver, Canada
>>> City.objects.get(name='Vancouver', country__code='CA').alt_names.all()
[<AlternativeName: 溫哥華 (yue)>, <AlternativeName: Vankuver (uz)>,
<AlternativeName: Ванкувер (ce)>, <AlternativeName: 溫哥華 (zh)>,
<AlternativeName: वैंकूवर (hi)>, <AlternativeName: Ванкувер (tt)>,
<AlternativeName: Vankuveris (lt)>, <AlternativeName: Fankoever (fy)>,
<AlternativeName: فانكوفر (arz)>, <AlternativeName: Ванкувер (mn)>,
<AlternativeName: ဗန်ကူးဗားမ_ (my)>, <AlternativeName: व्हँकूव्हर (mr)>,
<AlternternativeName: வான்கூவர் (ta)>, <AlternativeName: فانكوفر (ar)>,
<AlternativeName: Vankuver (az)>, <AlternativeName: Горад Ванкувер (be)>,
<AlternativeName: ভ্যানকুভার (bn)>, <AlternativeName: แวนคูเวอร์ (th)>,
<Al <AlternativeName: Ванкувер (uk)>, <AlternativeName: ਵੈਨਕੂਵਰ (pa)>,
'...(remaining elements truncated)...']
# Get zip codes near Mountain View, CA
>>> PostalCode.objects.distance(City.objects.get(name='Mountain View', region__name='California').location).order_by('distance')[:5]
[<PostalCode: 94040>, <PostalCode: 94041>, <PostalCode: 94043>,
<PostalCode: 94024>, <PostalCode: 94022>]
第三方应用程序/扩展
这些是构建在 django-cities
之上的应用程序。对于本质上扩展 django-cities
可以做什么非常有用。
- django-airports 为您提供了与机场相关的模型和数据(来自OpenFlights),可用于您的Django项目。
待办事项
按难度递增顺序
- 为我们提供的插件编写测试
- 最小化抽象基模型上的属性数量,并相应地调整导入脚本
- 窃取/修改所有来自 django-contrib-light的contrib应用程序(Django REST框架集成,链式选择和自动完成)
- 集成 libpostal 以从地址字符串中提取国家/城市/区域/邮政编码
备注
某些数据集非常大(> 100 MB),下载/导入需要时间。
只有当数据比您当前的数据新时,才会下载/导入数据,并且只有匹配的行会被覆盖。
cities管理命令有选项,请参阅 --help
。详细程度由 LOGGING
设置控制。
运行测试
-
安装postgres、postgis和libgdal-dev
-
创建
django_cities
数据库sudo su -l postgres # Enter your password createuser -d -s -P some_username # Enter password createdb -T template0 -E utf-8 -l en_US.UTF-8 -O multitest django_cities psql -c 'create extension postgis;' -d django_cities
-
运行测试
POSTGRES_USER=some_username POSTGRES_PASSWORD='password from createuser step' tox # If you have changed example data files then you should push your # changes to github and specify commit and repo variables: TRAVIS_COMMIT=`git rev-parse HEAD` TRAVIS_REPO_SLUG='github-username/django-cities' POSTGRES_USER=some_username POSTGRES_PASSWORD='password from createuser ste' tox
作为安装和运行PostgreSQL系统范围的替代方案,您可以对瞬态Docker实例运行测试
docker run --rm -p 127.0.0.1:5432:5432 mdillon/postgis
有用的测试选项
TRAVIS_LOG_LEVEL
- 默认为INFO
,但设置为DEBUG
以查看导入脚本的(非常)大和(非常)完整的日志CITIES_FILES
- 将基本URL设置为file://
路径以使用本地文件而无需修改任何其他设置
版本说明
0.4.1
使用Django的本地迁移
从0.4.1升级
从0.4.1版本升级可能会导致在表已存在时尝试应用迁移时出现问题。在这种情况下,需要应用一个假迁移。
python manage.py migrate cities 0001 --fake
0.4
** 本版django-cities与之前的版本不兼容 **
国家模型有一些新字段
- 海拔
- 面积
- 货币
- 货币名称
- 语言
- 邻国
- 首都
- 电话
替代名称支持已完全重写。代码和用法现在应该更简单。请参阅下面的更新示例。
代码字段不再包含父代码。例如,美国加利福尼亚的代码现在是"CA"。在之前的版本中是"US.CA"。
这些更改意味着从先前版本升级并不简单。尽管如此,所有地点ID都是相同的,因此如果您确实想要升级,应该是可能的。
项目详情
django_cities-0.6.2.tar.gz的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 0f5113cbd9ed156a3e50cc5cdc41e14a3d10ac0942d4c861b77803d93cd336f7 |
|
MD5 | e195485c6ad8e229bc4cd2abfacfb85d |
|
BLAKE2b-256 | da6c266326c76f54b7792f244235dc1fa9837cfa7b98c7a0258d3adeaa39d42c |