跳转到主要内容

Django项目使用的地点模型和全球地点数据

项目描述

django-cities

Django项目使用的地点模型和全球地点数据

PyPI version Build status


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.iocountry.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.countryCity.countryPostalCode.country都将自动指向CustomCountryModel。这意味着如果您不想自定义任何相关模型,则不需要对其进行自定义。

替代名称类型

替代名称的Geonames数据包含附加信息,例如链接到外部网站(主要是维基百科文章)和发音指南(拼音)。然而,django-cities只使用和导入这些类型的一个子集。由于一些用户可能希望使用所有这些类型,因此可以使用CITIES_ALTERNATIVE_NAME_TYPESCITIES_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.pyLANGUAGES设置中指定的所有替代名称的“快捷方式”

有关ISO639-1语言代码的完整列表,请参阅Geonames上的iso-languagecodes.txt文件。

CITIES_LOCALES = ['en', 'und', 'LANGUAGES']

限制导入的邮政编码

将导入的邮政编码限制为特定国家

特殊值

  • ALL - 导入所有邮政编码
CITIES_POSTAL_CODES = ['US', 'CA']

插件

您可以编写自己的插件来在数据写入数据库之前和之后处理数据。有关详细信息,请参阅编写插件部分。

要激活插件,您需要将它们的点分导入字符串添加到CITIES_PLUGINS选项中。此示例激活了django-cities附带的postal_code_careset_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 设置控制。

运行测试

  1. 安装postgres、postgis和libgdal-dev

  2. 创建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
    
  3. 运行测试

     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 (45.4 kB 查看哈希)

上传时间

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面