├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── build_test_data.py ├── build_test_data.sh ├── cities ├── __init__.py ├── admin.py ├── conf.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── cities.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_continent_models_and_foreign_keys.py │ ├── 0003_add_verbose_name_and_related_names.py │ ├── 0004_rename_languages_to_language_codes.py │ ├── 0005_add_foreignkeys_to_postalcode.py │ ├── 0006_typify_alt_names_and_add_is_historic.py │ ├── 0007_add_currency_and_postal_code_fields_to_country_model.py │ ├── 0008_add_code_to_district.py │ ├── 0009_add_slug_fields_to_models.py │ ├── 0010_adjust_unique_attributes.py │ ├── 0011_auto_20180108_0706.py │ ├── 0012_alter_country_neighbours.py │ └── __init__.py ├── models.py ├── plugin │ ├── __init__.py │ ├── postal_code_ca.py │ └── reset_queries.py ├── south_migrations │ ├── 0001_initial.py │ └── __init__.py └── util.py ├── example ├── __init__.py ├── manage.py ├── settings.py ├── templates │ ├── base.html │ ├── countries.html │ └── list.html └── urls.py ├── setup.cfg ├── setup.py ├── test_project ├── data │ ├── UA.txt │ ├── admin1CodesASCII.txt │ ├── admin2Codes.txt │ ├── allCountries.txt │ ├── alternateNames.txt │ ├── cities1000.txt │ ├── countryInfo.txt │ └── hierarchy.txt ├── manage.py └── test_app │ ├── __init__.py │ ├── apps.py │ ├── mixins.py │ ├── models.py │ ├── settings.py │ ├── tests │ ├── __init__.py │ ├── test_custom_continent_data.py │ ├── test_manage_command.py │ └── test_models.py │ ├── urls.py │ ├── utils.py │ └── wsgi.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Python files 2 | *.pyc 3 | __pycache__ 4 | 5 | *.egg-info 6 | .tox 7 | build 8 | dist 9 | 10 | # WebDAV file system cache 11 | .DAV/ 12 | .env 13 | 14 | # Environments 15 | .venv 16 | venv/ 17 | ENV/ 18 | 19 | # Data files 20 | /cities/data/* 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | 3 | language: python 4 | 5 | services: 6 | - postgresql 7 | 8 | addons: 9 | postgresql: "10" 10 | apt: 11 | packages: 12 | - postgresql-10-postgis-2.4 13 | 14 | python: 15 | - "3.6" 16 | - "3.7" 17 | - "3.8" 18 | - "3.9" 19 | - "3.10" 20 | 21 | env: 22 | global: 23 | - PGPORT=5432 24 | - PGUSER=postgres 25 | jobs: 26 | - DJANGO_VERSION='Django>=2.2,<2.3' 27 | - DJANGO_VERSION='Django>=3.1,<3.2' 28 | - DJANGO_VERSION='Django>=3.2,<4.0' 29 | - DJANGO_VERSION='Django>=4.0,<4.1' 30 | 31 | jobs: 32 | exclude: 33 | - python: "3.6" 34 | env: DJANGO_VERSION='Django>=4.0,<4.1' 35 | - python: "3.7" 36 | env: DJANGO_VERSION='Django>=4.0,<4.1' 37 | - python: "3.10" 38 | env: DJANGO_VERSION='Django>=2.2,<2.3' 39 | env: DJANGO_VERSION='Django>=3.1,<3.2' 40 | 41 | install: 42 | - pip install $DJANGO_VERSION 43 | - pip install -q django-model-utils flake8 psycopg2 six swapper tox tqdm 44 | 45 | before_script: 46 | # - psql -U postgres -c "CREATE EXTENSION postgis;" 47 | - psql -c 'create database django_cities;' -U postgres 48 | - psql -U postgres -c 'CREATE EXTENSION postgis;' -d django_cities 49 | 50 | script: 51 | - flake8 52 | - PYTHONPATH=. python test_project/manage.py test test_app --noinput 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog # 2 | 3 | ## v0.6.2 ## 4 | 5 | - Fix Django missing migration, thanks @RafaPinzon93 6 | - Updated Python version classifiers, thanks @leogregianin 7 | 8 | ## v0.6.1 ## 9 | 10 | ### Added ### 11 | 12 | - Added support for Django 4.0 13 | 14 | ## v0.6 ## 15 | 16 | ### Added ### 17 | 18 | - Added `filter_horizontal` to neighbours field in Country model 19 | - Added support for Django 3.0 20 | 21 | ### Changed ### 22 | 23 | - Improved the neighbours admin page 24 | - Updated Travis test matrix and supported/compatibility table in README 25 | - Linting fixups and added Travis check for linting 26 | - Updated Travis config to run on Xenial 27 | 28 | ### Removed ### 29 | 30 | - Python 2 support 31 | - Python 3.3-3.5 support and testing 32 | - Django 1.7-1.10 33 | - Django 2.0-2.1 34 | 35 | ## Previous versions ## 36 | 37 | ### Changed 38 | - added ``cities.plugin.reset_queries.Plugin`` that calls reset_queries randomly (default chance is 0.000002 per imported city or district). See CITIES_PLUGINS in Configuration example for details 39 | - It's now possible to specify several files to be downloaded and processed. See Configuration example for details. 40 | 41 | ## [0.4.1] - 2014-07-06 42 | 43 | - Last version without changelog. 44 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Checklist 2 | 3 | - [ ] I have verified that I am using a GIS-enabled database, such as PostGIS or Spatialite. 4 | - [ ] I have verified that that issue exists against the `master` branch of django-cities. 5 | - [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate. 6 | - [ ] I have reduced the issue to the simplest possible case. 7 | - [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.) 8 | 9 | ## Steps to reproduce 10 | 11 | ## Expected behavior 12 | 13 | ## Actual behavior 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Ben Dowling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | recursive-include cities *.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-cities 2 | 3 | ## Place models and worldwide place data for Django 4 | 5 | [![PyPI version](https://badge.fury.io/py/django-cities.svg)](https://badge.fury.io/py/django-cities) [![Build status](https://travis-ci.org/coderholic/django-cities.svg?branch=master)](https://travis-ci.org/coderholic/django-cities.svg?branch=master) 6 | 7 | ---- 8 | 9 | django-cities provides you with place related models (eg. Country, Region, City) and data (from [GeoNames](http://www.geonames.org/)) that can be used in your django projects. 10 | 11 | This package officially supports all currently supported versions of Python/Django: 12 | 13 | | Python | 3.6 | 3.7 | 3.8 | 3.9 | 3.10 | 14 | | :------------ | ------------------- | --------------------- | --------------------- | --------------------- | --------------------- | 15 | | Django 2.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | 16 | | Django 3.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | 17 | | Django 3.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 18 | | Django 4.0 | :x: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 19 | 20 | | Key | | 21 | | :-------------------: | :------------------------------------------------------------------ | 22 | | :white_check_mark: | Officially supported, tested, and passing | 23 | | :large_blue_circle: | Tested and passing, but not officially supported | 24 | | :x: | Known incompatibilities | 25 | 26 | Authored by [Ben Dowling](http://www.coderholic.com), and some great [contributors](https://github.com/coderholic/django-cities/contributors). 27 | 28 | See some of the data in action at [city.io](http://city.io) and [country.io](http://country.io). 29 | 30 | ---- 31 | 32 | * [Requirements](#requirements) 33 | * [Installation](#installation) 34 | * [Configuration](#configuration) 35 | * [Migration Configuration](#migration-configuration) 36 | * [Swappable Models](#swappable-models) 37 | * [Alternative Name Types](#alternative-name-types) 38 | * [Continent Data](#continent-data) 39 | * [Run Migrations](#run-migrations) 40 | * [Import Configuration](#import-configuration) 41 | * [Download Directory](#download-directory) 42 | * [Download Files](#download-files) 43 | * [Currency Data](#currency-data) 44 | * [Countries That No Longer Exist](#countries-that-no-longer-exist) 45 | * [Postal Code Validation](#postal-code-validation) 46 | * [Custom `slugify()` function](#custom-slugify-function) 47 | * [Cities Without Regions](#cities-without-regions) 48 | * [Languages/Locales To Import](#languageslocales-to-import) 49 | * [Limit Imported Postal Codes](#limit-imported-postal-codes) 50 | * [Plugins](#plugins) 51 | * [Import Data](#import-data) 52 | * [Writing Plugins](#writing-plugins) 53 | * [Examples](#examples) 54 | * [Third Party Apps/Extensions](#third-party-apps--extensions) 55 | * [TODO](#todo) 56 | * [Notes](#notes) 57 | * [Running Tests](#running-tests) 58 | * [Release Notes](#release-notes) 59 | 60 | ---- 61 | 62 | ## Requirements 63 | 64 | Your database must support spatial queries, see the [GeoDjango documentation](https://docs.djangoproject.com/en/dev/ref/contrib/gis/) for details and setup instructions. 65 | 66 | 67 | 68 | ## Installation 69 | 70 | Clone this repository into your project: 71 | 72 | ```bash 73 | git clone https://github.com/coderholic/django-cities.git 74 | ``` 75 | 76 | Download the zip file and unpack it: 77 | 78 | ```bash 79 | wget https://github.com/coderholic/django-cities/archive/master.zip 80 | unzip master.zip 81 | ``` 82 | 83 | Install with pip: 84 | 85 | ```bash 86 | pip install django-cities 87 | ``` 88 | 89 | ## Configuration 90 | 91 | You'll need to enable GeoDjango. See that [documentation](https://docs.djangoproject.com/en/stable/ref/contrib/gis/tutorial/#setting-up) for guidance. 92 | 93 | You'll need to add `cities` to `INSTALLED_APPS` in your projects `settings.py` file: 94 | 95 | ```python 96 | INSTALLED_APPS = ( 97 | # ... 98 | 'cities', 99 | # ... 100 | ) 101 | ``` 102 | 103 | ### Migration Configuration 104 | 105 | These settings should be reviewed and set or modified BEFORE any migrations have been run. 106 | 107 | #### Swappable Models 108 | 109 | Some users may wish to override some of the default models to add data, override default model methods, or add custom managers. This project supports swapping models out using the [django-swappable-models project](https://github.com/wq/django-swappable-models). 110 | 111 | To swap models out, first define your own custom model in your custom cities app. You will need to subclass the appropriate base model from `cities.models`: 112 | 113 | Here's an example `my_cities_app/models.py`: 114 | 115 | ```python 116 | from django.db import models 117 | 118 | from cities.models import BaseCountry 119 | 120 | 121 | class CustomCountryModel(BaseCountry, models.Model): 122 | more_data = models.TextField() 123 | 124 | class Meta(BaseCountry.Meta): 125 | pass 126 | ``` 127 | 128 | Then you will need to configure your project by setting the appropriate option: 129 | 130 | | Model | Setting Name | Default Value | 131 | | :-------- | :----------------------- | :----------------- | 132 | | Continent | `CITIES_CONTINENT_MODEL` | `cities.Continent` | 133 | | Country | `CITIES_COUNTRY_MODEL` | `cities.Country` | 134 | | City | `CITIES_CITY_MODEL` | `cities.City` | 135 | 136 | So to use the `CustomCountryModel` we defined above, we would add the dotted **model** string to our project's `settings.py`: 137 | 138 | ```python 139 | # ... 140 | 141 | CITIES_COUNTRY_MODEL = 'my_cities_app.CustomCountryModel' 142 | 143 | # ... 144 | ``` 145 | 146 | The dotted model string is simply the dotted import path with the `.models` substring removed, just `.`. 147 | 148 | Once you have set the option in your `settings.py`, all appropriate foreign keys in django-cities will point to your custom model. So in the above example, the foreign keys `Region.country`, `City.country`, and `PostalCode.country` will all automatically point to the `CustomCountryModel`. This means that you do NOT need to customize any dependent models if you don't want to. 149 | 150 | #### Alternative Name Types 151 | 152 | The Geonames data for alternative names contain additional information, such as links to external websites (mostly Wikipedia articles) and pronunciation guides (pinyin). However, django-cities only uses and imports a subset of those types. Since some users may wish to use them all, the `CITIES_ALTERNATIVE_NAME_TYPES` and `CITIES_AIRPORT_TYPES` settings can be used to define the alternative name types in the database. 153 | 154 | These settings should be specified as a tuple of tuple choices: 155 | 156 | ```python 157 | CITIES_AIRPORT_TYPES = ( 158 | ('iata', _("IATA (Airport) Code")), 159 | ('icao', _("ICAO (Airport) Code")), 160 | ('faac', _("FAAC (Airport) Code")), 161 | ) 162 | 163 | CITIES_ALTERNATIVE_NAME_TYPES = ( 164 | ('name', _("Name")), 165 | ('abbr', _("Abbreviation")), 166 | ('link', _("Link")), 167 | ) 168 | ``` 169 | 170 | If `CITIES_INCLUDE_AIRPORT_CODES` is set to `True`, the choices in `CITIES_AIRPORT_TYPES` will be appended to the `CITIES_ALTERNATIVE_NAME_TYPES` choices. Otherwise, no airport types are imported. 171 | 172 | The Geonames data also contains alternative names that are purely numeric. 173 | 174 | The `CITIES_INCLUDE_NUMERIC_ALTERNATIVE_NAMES` setting controls whether or not purely numeric alternative names are imported. Set to `True` to import them, and to `False` to skip them. 175 | 176 | #### Continent Data 177 | 178 | Since continent data rarely (if ever) changes, the continent data is loaded directly from Python data structures included with the django-cities distribution. However, there are different continent models with different numbers of continents. Therefore, some users may wish to override the default settings by setting the `CITIES_CONTINENT_DATA` to a Python dictionary where the keys are the continent code and the values are (name, geonameid) tuples. 179 | 180 | For an overview of different continent models, please see the Wikipedia article on Continents: 181 | 182 | https://en.wikipedia.org/wiki/Continent#Number 183 | 184 | The following is the default continent data in [`cities/conf.py`](https://github.com/coderholic/django-cities/blob/master/cities/conf.py#L178): 185 | 186 | ```python 187 | CITIES_CONTINENT_DATA = { 188 | 'AF': ('Africa', 6255146), 189 | 'AS': ('Asia', 6255147), 190 | 'EU': ('Europe', 6255148), 191 | 'NA': ('North America', 6255149), 192 | 'OC': ('Oceania', 6255151), 193 | 'SA': ('South America', 6255150), 194 | 'AN': ('Antarctica', 6255152), 195 | } 196 | ``` 197 | 198 | Note that if you do not use these default settings, you will need to register a plugin with a `country_pre` method to adjust the continent ID for country models before countries are processed and saved to the database by the import script. Please contribute your plugin back upstream to this project so that others may benefit from your work by creating a pull request containing your plugin and any relevant documentation for it. 199 | 200 | ### Run Migrations 201 | 202 | After you have configured all migration settings, run 203 | 204 | ```bash 205 | python manage.py migrate cities 206 | ``` 207 | 208 | to create the required database tables and add the continent data to its table. 209 | 210 | 211 | 212 | ### Import Configuration 213 | 214 | These settings should also be reviewed and set or modified before importing any data. Changing these settings after importing data may not have the intended effect. 215 | 216 | #### Download Directory 217 | 218 | Specify a download directory (used to specify a writable directory). 219 | 220 | Default: `cities/data` 221 | 222 | You may want to use this if you are on a cloud services provider, or if django-cities is installed on a read-only medium. 223 | 224 | Note that this path must be an absolute path. 225 | 226 | ```python 227 | CITIES_DATA_DIR = '/var/data' 228 | ``` 229 | 230 | #### Download Files 231 | 232 | You can override the files the import command uses to process data: 233 | 234 | ```python 235 | CITIES_FILES = { 236 | # ... 237 | 'city': { 238 | 'filename': 'cities1000.zip', 239 | 'urls': ['http://download.geonames.org/export/dump/'+'{filename}'] 240 | }, 241 | # ... 242 | } 243 | ``` 244 | 245 | It is also possible to specify multiple filenames to process. Note that these files are processed in the order they are specified, so duplicate data in files specified later in the list will overwrite data from files specified earlier in the list. 246 | 247 | ```python 248 | CITIES_FILES = { 249 | # ... 250 | 'city': { 251 | 'filenames': ["US.zip", "GB.zip", ], 252 | 'urls': ['http://download.geonames.org/export/dump/'+'{filename}'] 253 | }, 254 | # ... 255 | } 256 | ``` 257 | 258 | Note that you do not need to specify all keys in the `CITIES_FILES` dictionary. Any keys you do not specify will use their default values as defined in [`cities/conf.py`](https://github.com/coderholic/django-cities/blob/master/cities/conf.py#L26). 259 | 260 | #### Currency Data 261 | 262 | The Geonames data includes currency data, but it is limited to the currency code (example: "USD") and the currency name (example: "Dollar"). The django-cities package offers the ability to import currency symbols (example: "$") with the country model. 263 | 264 | However, like the continent data, since this rarely changes, the currency symbols are loaded directly from Python data structures included with the django-cities distribution in the `CITIES_CURRENCY_SYMBOLS` setting. Users can override this setting if they wish to add or modify the imported currency symbols. 265 | 266 | For default values see the included [`cities/conf.py` file](https://github.com/coderholic/django-cities/blob/master/cities/conf.py#L189). 267 | 268 | ```python 269 | CITIES_CURRENCY_SYMBOLS = { 270 | "AED": "د.إ", "AFN": "؋", "ALL": "L", "AMD": "դր.", "ANG": "ƒ", "AOA": "Kz", 271 | "ARS": "$", "AUD": "$", "AWG": "ƒ", "AZN": "m", 272 | "BAM": "KM", "BBD": "$", "BDT": "৳", "BGN": "лв", "BHD": "ب.د", "BIF": "Fr", 273 | # ... 274 | "UAH": "₴", "UGX": "Sh", "USD": "$", "UYU": "$", "UZS": "лв", 275 | ``` 276 | 277 | #### Countries That No Longer Exist 278 | 279 | The Geonames data includes countries that no longer exist. At this time, those countries are the Dutch Antilles (`AN`) and Serbia and Montenegro (`CS`). If you wish to import those countries, set the `CITIES_NO_LONGER_EXISTENT_COUNTRY_CODES` to an empty list (`[]`). 280 | 281 | Default: `['CS', 'AN']` 282 | 283 | ```python 284 | CITIES_NO_LONGER_EXISTENT_COUNTRY_CODES = ['CS', 'AN'] 285 | ``` 286 | 287 | #### Postal Code Validation 288 | 289 | The Geonames data contains country postal code formats and regular expressions, as well as postal codes. Some of these postal codes do not match the regular expression of their country. Users who wish to ignore invalid postal codes when importing data can set the `CITIES_VALIDATE_POSTAL_CODES` setting to `True` to skip importing postal codes that do not validate the country postal code regular expression. 290 | 291 | If you have regional knowledge of postal codes that do not validate, please either update the postal code itself or the country postal codes regular expression on the Geonames website. Doing this will help all Geonames users (including this project but also every other Geonames user). 292 | 293 | ```python 294 | CITIES_VALIDATE_POSTAL_CODES = True 295 | ``` 296 | 297 | #### Custom `slugify()` Function 298 | 299 | You may wish to customize the slugs generated by django-cities. To do so, you will need to write your own `slugify()` function and specify its dotted import path in the `CITIES_SLUGIFY_FUNCTION`: 300 | 301 | ```python 302 | CITIES_SLUGIFY_FUNCTION = 'cities.util.default_slugify' 303 | ``` 304 | 305 | Your customized slugify function should accept two arguments: the object itself and the slug generated by the object itself. It should return the final slug as a string. 306 | 307 | Because the slugify function contains code that would be reused by multiple objects, there is only a single slugify function for all of the objects in django-cities. To generate different slugs for different types of objects, test against the object's class name (`obj.__class__.__name__`). 308 | 309 | Default slugify function (see [`cities/util.py`](https://github.com/coderholic/django-cities/tree/master/cities/util.py#L35)): 310 | 311 | ```python 312 | # SLUGIFY REGEXES 313 | 314 | to_und_rgx = re.compile(r"[']", re.UNICODE) 315 | slugify_rgx = re.compile(r'[^-\w._~]', re.UNICODE) 316 | multi_dash_rgx = re.compile(r'-{2,}', re.UNICODE) 317 | dash_und_rgx = re.compile(r'[-_]_', re.UNICODE) 318 | und_dash_rgx = re.compile(r'[-_]-', re.UNICODE) 319 | starting_chars_rgx = re.compile(r'^[-._]*', re.UNICODE) 320 | ending_chars_rgx = re.compile(r'[-._]*$', re.UNICODE) 321 | 322 | 323 | def default_slugify(obj, value): 324 | if value is None: 325 | return None 326 | 327 | value = force_text(unicode_func(value)) 328 | value = unicodedata.normalize('NFKC', value.strip()) 329 | value = re.sub(to_und_rgx, '_', value) 330 | value = re.sub(slugify_rgx, '-', value) 331 | value = re.sub(multi_dash_rgx, '-', value) 332 | value = re.sub(dash_und_rgx, '_', value) 333 | value = re.sub(und_dash_rgx, '_', value) 334 | value = re.sub(starting_chars_rgx, '', value) 335 | value = re.sub(ending_chars_rgx, '', value) 336 | return mark_safe(value) 337 | ``` 338 | 339 | #### Cities Without Regions 340 | 341 | Note: This used to be `CITIES_IGNORE_EMPTY_REGIONS`. 342 | 343 | Some cities in the Geonames data files do not have region information. By default, these cities are imported as normal (they still have foreign keys to their country), but if you wish to *avoid* importing these cities, set `CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS` to `True`: 344 | 345 | ```python 346 | # Import cities without region (default False) 347 | CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS = True 348 | ``` 349 | 350 | #### Languages/Locales To Import 351 | 352 | Limit imported alternative names by languages/locales 353 | 354 | Note that many alternative names in the Geonames data do not specify a language code, so if you manually specify language codes and do not include `und`, you may not import as many alternative names as you want. 355 | 356 | Special values: 357 | 358 | * `ALL` - import all alternative names 359 | * `und` - alternative names that do not specify a language code. When imported, these alternative names will be assigned a language code of `und`. If this language code is not specified, alternative names that do not specify a language code are not imported. 360 | * `LANGUAGES` - a "shortcut" to import all alternative names specified in the `LANGUAGES` setting in your Django project's `settings.py` 361 | 362 | For a full list of ISO639-1 language codes, see the [iso-languagecodes.txt](http://download.geonames.org/export/dump/iso-languagecodes.txt) file on Geonames. 363 | 364 | ```python 365 | CITIES_LOCALES = ['en', 'und', 'LANGUAGES'] 366 | ``` 367 | 368 | #### Limit Imported Postal Codes 369 | 370 | Limit the imported postal codes to specific countries 371 | 372 | Special value: 373 | 374 | * `ALL` - import all postal codes 375 | 376 | ```python 377 | CITIES_POSTAL_CODES = ['US', 'CA'] 378 | ``` 379 | 380 | #### Plugins 381 | 382 | You can write your own plugins to process data before and after it is written to the database. See the section on [Writing Plugins](#writing-plugins) for details. 383 | 384 | To activate plugins, you need to add their dotted import strings to the `CITIES_PLUGINS` option. This example activates the `postal_code_ca` and `reset_queries` plugins that come with django-cities: 385 | 386 | ```python 387 | CITIES_PLUGINS = [ 388 | # Canadian postal codes need region codes remapped to match geonames 389 | 'cities.plugin.postal_code_ca.Plugin', 390 | # Reduce memory usage when importing large datasets (e.g. "allCountries.zip") 391 | 'cities.plugin.reset_queries.Plugin', 392 | ] 393 | ``` 394 | 395 | Note that some plugins may use their own configuration options: 396 | 397 | ```python 398 | # This setting may be specified if you use 'cities.plugin.reset_queries.Plugin' 399 | CITIES_PLUGINS_RESET_QUERIES_CHANCE = 1.0 / 1000000 400 | ``` 401 | 402 | ### Import Data 403 | 404 | After you have configured all import settings, run 405 | 406 | ```bash 407 | python manage.py cities --import=all 408 | ``` 409 | 410 | to import all of the place data. 411 | 412 | You may also import specific object types: 413 | 414 | ```bash 415 | python manage.py cities --import=country 416 | ``` 417 | 418 | ```bash 419 | python manage.py cities --import=city 420 | ``` 421 | 422 | **NOTE:** This can take a long time, although there are progress bars drawn in the terminal. 423 | 424 | Specifically, importing postal codes can take one or two orders of magnitude more time than importing other objects. 425 | 426 | 427 | 428 | ## Writing Plugins 429 | 430 | You can write plugins that modify data before and after it is processed by the import script. For example, you can use this to adjust the continent a country belongs to, or you can use it to add or modify any additional data if you customize and override any django-cities models. 431 | 432 | A plugin is simply a Python class that has implemented one or more hook functions as members. Hooks can either modify data before it is processed by the import script, or modify the database after the object has been saved to the database by the import script. By raising `cities.conf.HookException`, plugins can skip one piece of data. 433 | 434 | Here's a table of all available hooks: 435 | 436 | | Model | Pre Hook Name | Post Hook Name | 437 | | ----------------- | ----------------- | ------------------ | 438 | | `Country` | `country_pre` | `country_post` | 439 | | `Region` | `region_pre` | `region_post` | 440 | | `Subregion` | `subregion_pre` | `subregion_post` | 441 | | `City` | `city_pre` | `city_post` | 442 | | `District` | `district_pre` | `district_post` | 443 | | `PostalCode` | `postal_code_pre` | `postal_code_post` | 444 | | `AlternativeName` | `alt_name_pre` | `alt_name_post` | 445 | 446 | The argument signatures for `_pre` hooks and `_post` hooks differ. All `_pre` hooks have the following argument signature: 447 | 448 | ```python 449 | class ...Plugin(object): 450 | model_pre(self, parser, item) 451 | ``` 452 | 453 | whereas all `_post` hooks also have the saved model instance available to them: 454 | 455 | ```python 456 | class ...Plugin(object): 457 | model_post(self, parser, _instance, item) 458 | ``` 459 | 460 | Arguments passed to hooks: 461 | 462 | * `self` - the plugin object itself 463 | * `parser` - the instance of the `cities.Command` management command 464 | * `_instance` - instance of model that was created based on `item` 465 | * `item` - Python dictionary with data for row being processed 466 | 467 | Note that the argument names are simply conventions, you are free to rename them to whatever you wish as long as you keep their order. 468 | 469 | Here is a complete skeleton plugin class example: 470 | 471 | ```python 472 | class CompleteSkeletonPlugin(object): 473 | """ 474 | Skeleton plugin for django-cities that has hooks for all object types, and 475 | does not modify any import data or existing objects in the database. 476 | """ 477 | # Note: Only ONE of these methods needs to be defined. If a method is not 478 | # defined, the import command will avoid calling the undefined method. 479 | 480 | def country_pre(self, parser, imported_data_dict): 481 | pass 482 | 483 | def country_post(self, parser, country_instance, imported_data_dict): 484 | pass 485 | 486 | def region_pre(self, parser, imported_data_dict): 487 | pass 488 | 489 | def region_post(self, parser, region_instance, imported_data_dict): 490 | pass 491 | 492 | def subregion_pre(self, parser, imported_data_dict): 493 | pass 494 | 495 | def subregion_post(self, parser, subregion_instance, imported_data_dict): 496 | pass 497 | 498 | def city_pre(self, parser, imported_data_dict): 499 | pass 500 | 501 | def city_post(self, parser, city_instance, imported_data_dict): 502 | pass 503 | 504 | def district_pre(self, parser, imported_data_dict): 505 | pass 506 | 507 | def district_post(self, parser, district_instance, imported_data_dict): 508 | pass 509 | 510 | def alt_name_pre(self, parser, imported_data_dict): 511 | pass 512 | 513 | def alt_name_post(self, parser, alt_name_instance, imported_data_dict): 514 | pass 515 | 516 | def postal_code_pre(self, parser, imported_data_dict): 517 | pass 518 | 519 | def postal_code_post(self, parser, postal_code_instance, imported_data_dict): 520 | pass 521 | ``` 522 | 523 | Silly example: 524 | 525 | ```python 526 | from cities.conf import HookException 527 | 528 | class DorothyPlugin(object): 529 | """ 530 | This plugin skips importing cities that are not in Kansas, USA. 531 | 532 | There's no place like home. 533 | """ 534 | def city_pre(self, parser, import_dict): 535 | if import_dict['cc2'] == 'US' and import_dict['admin1Code'] != 'KS': 536 | raise HookException("Ignoring cities not in Kansas, USA") # Raising a HookException skips importing the item 537 | else: 538 | # Modify the value of the data before it is written to the database 539 | import_dict['admin1Code'] = 'KS' 540 | 541 | def city_post(self, parser, city, import_data): 542 | # Checks if the region foreign key for the city database row is NULL 543 | if city.region is None: 544 | # Set it to Kansas 545 | city.region = Region.objects.get(country__code='US', code='KS') 546 | # Re-save any existing items that aren't in Kansas 547 | city.save() 548 | ``` 549 | 550 | Once you have written a plugin, you will need to activate it by specifying its dotted import string in the `CITIES_PLUGINS` setting. See the [Plugins](#plugins) section for details. 551 | 552 | 553 | 554 | ## Examples 555 | 556 | This repository contains an example project which lets you browse the place hierarchy. See the [`example directory`](https://github.com/coderholic/django-cities/tree/master/example). Below are some small snippets to show you the kind of queries that are possible once you have imported data: 557 | 558 | 559 | ```python 560 | # Find the 5 most populated countries in the World 561 | >>> Country.objects.order_by('-population')[:5] 562 | [, , , 563 | , ] 564 | 565 | # Find what country the .ly TLD belongs to 566 | >>> Country.objects.get(tld='ly') 567 | 568 | 569 | # 5 Nearest cities to London 570 | >>> london = City.objects.filter(country__name='United Kingdom').get(name='London') 571 | >>> nearest = City.objects.distance(london.location).exclude(id=london.id).order_by('distance')[:5] 572 | 573 | # All cities in a state or county 574 | >>> City.objects.filter(country__code="US", region__code="TX") 575 | >>> City.objects.filter(country__name="United States", subregion__name="Orange County") 576 | 577 | # Get all countries in Japanese preferring official names if available, 578 | # fallback on ASCII names: 579 | >>> [country.alt_names_ja.get_preferred(default=country.name) for country in Country.objects.all()] 580 | 581 | # Alternate names for the US in English, Spanish and German 582 | >>> [x.name for x in Country.objects.get(code='US').alt_names.filter(language_code='de')] 583 | [u'USA', u'Vereinigte Staaten'] 584 | >>> [x.name for x in Country.objects.get(code='US').alt_names.filter(language_code='es')] 585 | [u'Estados Unidos'] 586 | >>> [x.name for x in Country.objects.get(code='US').alt_names.filter(language_code='en')] 587 | [u'United States of America', u'America', u'United States'] 588 | 589 | # Alternative names for Vancouver, Canada 590 | >>> City.objects.get(name='Vancouver', country__code='CA').alt_names.all() 591 | [, , 592 | , , 593 | , , 594 | , , 595 | , , 596 | , , 597 | , , 598 | , , 599 | , , 600 | , , 601 | '...(remaining elements truncated)...'] 602 | 603 | # Get zip codes near Mountain View, CA 604 | >>> PostalCode.objects.distance(City.objects.get(name='Mountain View', region__name='California').location).order_by('distance')[:5] 605 | [, , , 606 | , ] 607 | ``` 608 | 609 | 610 | 611 | ## Third-party Apps / Extensions 612 | 613 | These are apps that build on top of the `django-cities`. Useful for essentially extending what `django-cities` can do. 614 | 615 | * [django-airports](https://github.com/bashu/django-airports) provides you with airport related model and data (from OpenFlights) that can be used in your Django projects. 616 | 617 | 618 | 619 | ## TODO 620 | 621 | In increasing order of difficulty: 622 | 623 | * Add tests for the plugins we ship with 624 | * Minimize number of attributes on abstract base models and adjust import script accordingly 625 | * Steal/modify all of the [contrib apps from django-contrib-light](https://github.com/yourlabs/django-cities-light/blob/stable/3.x.x/cities_light/contrib) (Django REST Framework integration, chained selects, and autocomplete) 626 | * Integrate [libpostal](https://github.com/openvenues/libpostal) to extract Country/City/District/Postal Code from an address string 627 | 628 | 629 | 630 | ## Notes 631 | 632 | Some datasets are very large (> 100 MB) and take time to download/import. 633 | 634 | Data will only be downloaded/imported if it is newer than your data, and only matching rows will be overwritten. 635 | 636 | The cities manage command has options, see `--help`. Verbosity is controlled through the `LOGGING` setting. 637 | 638 | 639 | 640 | ## Running Tests 641 | 642 | 1. Install postgres, postgis and libgdal-dev 643 | 2. Create `django_cities` database: 644 | 645 | sudo su -l postgres 646 | # Enter your password 647 | createuser -d -s -P some_username 648 | # Enter password 649 | createdb -T template0 -E utf-8 -l en_US.UTF-8 -O multitest django_cities 650 | psql -c 'create extension postgis;' -d django_cities 651 | 652 | 3. Run tests: 653 | 654 | POSTGRES_USER=some_username POSTGRES_PASSWORD='password from createuser step' tox 655 | 656 | # If you have changed example data files then you should push your 657 | # changes to github and specify commit and repo variables: 658 | TRAVIS_COMMIT=`git rev-parse HEAD` TRAVIS_REPO_SLUG='github-username/django-cities' POSTGRES_USER=some_username POSTGRES_PASSWORD='password from createuser ste' tox 659 | 660 | As an alternative to installing and running PostgreSQL system-wide, 661 | you can run the tests against a transient Docker instance: 662 | 663 | ```bash 664 | docker run --rm -p 127.0.0.1:5432:5432 mdillon/postgis 665 | ``` 666 | 667 | ### Useful test options: 668 | 669 | * `TRAVIS_LOG_LEVEL` - defaults to `INFO`, but set to `DEBUG` to see a (very) large and (very) complete log of the import script 670 | * `CITIES_FILES` - set the base urls to a `file://` path to use local files without modifying any other settings 671 | 672 | 673 | ## Release Notes 674 | 675 | ### 0.4.1 676 | 677 | Use Django's native migrations 678 | 679 | #### Upgrading from 0.4.1 680 | 681 | Upgrading from 0.4.1 is likely to cause problems trying to apply a migration when the tables already exist. In this case a fake migration needs to be applied: 682 | 683 | ```bash 684 | python manage.py migrate cities 0001 --fake 685 | ``` 686 | 687 | ### 0.4 688 | 689 | ** **This release of django-cities is not backwards compatible with previous versions** ** 690 | 691 | The country model has some new fields: 692 | - elevation 693 | - area 694 | - currency 695 | - currency_name 696 | - languages 697 | - neighbours 698 | - capital 699 | - phone 700 | 701 | Alternative name support has been completely overhauled. The code and usage should now be much simpler. See the updated examples below. 702 | 703 | The code field no longer contains the parent code. Eg. the code for California, US is now "CA". In the previous release it was "US.CA". 704 | 705 | These changes mean that upgrading from a previous version isn't simple. All of the place IDs are the same though, so if you do want to upgrade it should be possible. 706 | -------------------------------------------------------------------------------- /build_test_data.py: -------------------------------------------------------------------------------- 1 | import mmap 2 | import os 3 | import re 4 | import sys 5 | 6 | from tqdm import tqdm 7 | 8 | from cities.conf import files 9 | 10 | 11 | def get_line_number(file_path): 12 | with open(file_path, 'r+') as fp: 13 | buf = mmap.mmap(fp.fileno(), 0) 14 | lines = 0 15 | while buf.readline(): 16 | lines += 1 17 | return lines 18 | 19 | 20 | try: 21 | original_file = sys.argv[1] 22 | except IndexError: 23 | original_file = None 24 | 25 | test_data_dir = os.path.join('test_project', 'data') 26 | 27 | new_filename = '{}.new'.format(os.path.basename(original_file)) 28 | new_filepath = os.path.join(test_data_dir, new_filename) 29 | 30 | # Luckily this regex applies to both hierarchy.txt and alternativeNames.txt 31 | file_rgx = re.compile(r'^(?:[^\t]+\t){1}([^\t]+)\t(?:en|und|ru|adm|\t)', 32 | re.IGNORECASE | re.UNICODE) 33 | 34 | # Bail early if we haven't been given a file to read 35 | if original_file is None: 36 | print("You must specify the full original file (usually hierarchy.txt or " 37 | "alternativeNames.txt) as the first argument.\n\nExiting.") 38 | 39 | exit(-1) 40 | 41 | # Bail early if the file exists 42 | if new_filepath and os.path.exists(new_filepath): 43 | print("This script writes {}, but that file already exists. Please move " 44 | "(or remove) that file and rerun this script.\n\nExiting.".format( 45 | new_filepath)) 46 | 47 | exit(-1) 48 | 49 | # Read all of the affected geonameids 50 | geonameids = [] 51 | for _type in ('region', 'subregion', 'city'): 52 | filename = files[_type]['filename'] 53 | filepath = os.path.join(test_data_dir, filename) 54 | 55 | column = files[_type]['fields'].index('geonameid') 56 | 57 | rgx = re.compile(r'^(?:[^\t]+\t){{{}}}([^\t\n]+)(?:[\t\n])'.format(column)) 58 | 59 | num_lines = get_line_number(filepath) 60 | 61 | with open(filepath, 'r') as f: 62 | # Not using .read() here causes f to be read as an iterable, which is 63 | # exactly what we want because the file may be large 64 | for line in tqdm(f, total=num_lines, 65 | desc="Collecting geonameids from {}".format(filename)): 66 | m = rgx.match(line) 67 | 68 | if m: 69 | geonameids.append(m.group(1)) 70 | 71 | # For all of the collected geonameids, write out matching lines from the 72 | # original file 73 | with open(original_file, 'r') as rf: 74 | # Check for file existence again, immediately before we write to it 75 | if os.path.exists(new_filepath): 76 | print("This script writes {}, but that file already exists. Please " 77 | "move (or remove) that file and rerun this script.".format( 78 | new_filepath)) 79 | 80 | exit(-1) 81 | 82 | num_lines = get_line_number(original_file) 83 | 84 | # Write out matching lines to the new file 85 | with open(new_filepath, 'a+') as wf: 86 | for line in tqdm(rf, total=num_lines, 87 | desc="Writing geonameids"): 88 | m = file_rgx.match(line) 89 | 90 | if m and m.group(1) in geonameids: 91 | wf.write(line) 92 | -------------------------------------------------------------------------------- /build_test_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DJANGO_SETTINGS_MODULE=test_project.test_app.settings python ./build_test_data.py $1 4 | -------------------------------------------------------------------------------- /cities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/cities/__init__.py -------------------------------------------------------------------------------- /cities/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | import swapper 4 | 5 | from .models import (Continent, Country, Region, Subregion, City, District, 6 | PostalCode, AlternativeName) 7 | 8 | 9 | class CitiesAdmin(admin.ModelAdmin): 10 | raw_id_fields = ['alt_names'] 11 | 12 | 13 | class ContinentAdmin(CitiesAdmin): 14 | list_display = ['name', 'code'] 15 | 16 | 17 | class CountryAdmin(CitiesAdmin): 18 | list_display = ['name', 'code', 'code3', 'tld', 'phone', 'continent', 'area', 'population'] 19 | search_fields = ['name', 'code', 'code3', 'tld', 'phone'] 20 | filter_horizontal = ['neighbours'] 21 | 22 | 23 | class RegionAdmin(CitiesAdmin): 24 | ordering = ['name_std'] 25 | list_display = ['name_std', 'code', 'country'] 26 | search_fields = ['name', 'name_std', 'code'] 27 | 28 | 29 | class SubregionAdmin(CitiesAdmin): 30 | ordering = ['name_std'] 31 | list_display = ['name_std', 'code', 'region'] 32 | search_fields = ['name', 'name_std', 'code'] 33 | raw_id_fields = ['alt_names', 'region'] 34 | 35 | 36 | class CityAdmin(CitiesAdmin): 37 | ordering = ['name_std'] 38 | list_display = ['name_std', 'subregion', 'region', 'country', 'population'] 39 | search_fields = ['name', 'name_std'] 40 | raw_id_fields = ['alt_names', 'region', 'subregion'] 41 | 42 | 43 | class DistrictAdmin(CitiesAdmin): 44 | raw_id_fields = ['alt_names', 'city'] 45 | list_display = ['name_std', 'city'] 46 | search_fields = ['name', 'name_std'] 47 | 48 | 49 | class AltNameAdmin(admin.ModelAdmin): 50 | ordering = ['name'] 51 | list_display = ['name', 'language_code', 'is_preferred', 'is_short', 'is_historic'] 52 | list_filter = ['is_preferred', 'is_short', 'is_historic', 'language_code'] 53 | search_fields = ['name'] 54 | 55 | 56 | class PostalCodeAdmin(CitiesAdmin): 57 | ordering = ['code'] 58 | list_display = ['code', 'subregion_name', 'region_name', 'country'] 59 | search_fields = ['code', 'country__name', 'region_name', 'subregion_name'] 60 | 61 | 62 | if not swapper.is_swapped('cities', 'Continent'): 63 | admin.site.register(Continent, ContinentAdmin) 64 | if not swapper.is_swapped('cities', 'Country'): 65 | admin.site.register(Country, CountryAdmin) 66 | admin.site.register(Region, RegionAdmin) 67 | admin.site.register(Subregion, SubregionAdmin) 68 | if not swapper.is_swapped('cities', 'City'): 69 | admin.site.register(City, CityAdmin) 70 | admin.site.register(District, DistrictAdmin) 71 | admin.site.register(AlternativeName, AltNameAdmin) 72 | admin.site.register(PostalCode, PostalCodeAdmin) 73 | -------------------------------------------------------------------------------- /cities/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from importlib import import_module 4 | from collections import defaultdict 5 | 6 | import django 7 | from django.conf import settings as django_settings 8 | from django.core.exceptions import ImproperlyConfigured 9 | if float('.'.join(map(str, django.VERSION[:2]))) < 3: 10 | from django.utils.translation import ugettext_lazy as _ 11 | else: 12 | from django.utils.translation import gettext_lazy as _ 13 | 14 | __all__ = [ 15 | 'city_types', 'district_types', 16 | 'import_opts', 'import_opts_all', 'HookException', 'settings', 17 | 'ALTERNATIVE_NAME_TYPES', 'CONTINENT_DATA', 'CURRENCY_SYMBOLS', 18 | 'INCLUDE_AIRPORT_CODES', 'INCLUDE_NUMERIC_ALTERNATIVE_NAMES', 19 | 'NO_LONGER_EXISTENT_COUNTRY_CODES', 'SKIP_CITIES_WITH_EMPTY_REGIONS', 20 | 'SLUGIFY_FUNCTION', 'VALIDATE_POSTAL_CODES', 21 | ] 22 | 23 | url_bases = { 24 | 'geonames': { 25 | 'dump': 'http://download.geonames.org/export/dump/', 26 | 'zip': 'http://download.geonames.org/export/zip/', 27 | }, 28 | } 29 | 30 | files = { 31 | 'country': { 32 | 'filename': 'countryInfo.txt', 33 | 'urls': [url_bases['geonames']['dump'] + '{filename}', ], 34 | 'fields': [ 35 | 'code', 36 | 'code3', 37 | 'codeNum', 38 | 'fips', 39 | 'name', 40 | 'capital', 41 | 'area', 42 | 'population', 43 | 'continent', 44 | 'tld', 45 | 'currencyCode', 46 | 'currencyName', 47 | 'phone', 48 | 'postalCodeFormat', 49 | 'postalCodeRegex', 50 | 'languages', 51 | 'geonameid', 52 | 'neighbours', 53 | 'equivalentFips' 54 | ] 55 | }, 56 | 'region': { 57 | 'filename': 'admin1CodesASCII.txt', 58 | 'urls': [url_bases['geonames']['dump'] + '{filename}', ], 59 | 'fields': [ 60 | 'code', 61 | 'name', 62 | 'asciiName', 63 | 'geonameid', 64 | ] 65 | }, 66 | 'subregion': { 67 | 'filename': 'admin2Codes.txt', 68 | 'urls': [url_bases['geonames']['dump'] + '{filename}', ], 69 | 'fields': [ 70 | 'code', 71 | 'name', 72 | 'asciiName', 73 | 'geonameid', 74 | ] 75 | }, 76 | 'city': { 77 | 'filename': 'cities5000.zip', 78 | 'urls': [url_bases['geonames']['dump'] + '{filename}', ], 79 | 'fields': [ 80 | 'geonameid', 81 | 'name', 82 | 'asciiName', 83 | 'alternateNames', 84 | 'latitude', 85 | 'longitude', 86 | 'featureClass', 87 | 'featureCode', 88 | 'countryCode', 89 | 'cc2', 90 | 'admin1Code', 91 | 'admin2Code', 92 | 'admin3Code', 93 | 'admin4Code', 94 | 'population', 95 | 'elevation', 96 | 'gtopo30', 97 | 'timezone', 98 | 'modificationDate' 99 | ] 100 | }, 101 | 'hierarchy': { 102 | 'filename': 'hierarchy.zip', 103 | 'urls': [url_bases['geonames']['dump'] + '{filename}', ], 104 | 'fields': [ 105 | 'parent', 106 | 'child', 107 | 'type', 108 | ] 109 | }, 110 | 'alt_name': { 111 | 'filename': 'alternateNames.zip', 112 | 'urls': [url_bases['geonames']['dump'] + '{filename}', ], 113 | 'fields': [ 114 | 'nameid', 115 | 'geonameid', 116 | 'language', 117 | 'name', 118 | 'isPreferred', 119 | 'isShort', 120 | 'isColloquial', 121 | 'isHistoric', 122 | ] 123 | }, 124 | 'postal_code': { 125 | 'filename': 'allCountries.zip', 126 | 'urls': [url_bases['geonames']['zip'] + '{filename}', ], 127 | 'fields': [ 128 | 'countryCode', 129 | 'postalCode', 130 | 'placeName', 131 | 'admin1Name', 132 | 'admin1Code', 133 | 'admin2Name', 134 | 'admin2Code', 135 | 'admin3Name', 136 | 'admin3Code', 137 | 'latitude', 138 | 'longitude', 139 | 'accuracy', 140 | ] 141 | } 142 | } 143 | 144 | country_codes = [ 145 | 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 146 | 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 147 | 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 148 | 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 149 | 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 150 | 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 151 | 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 152 | 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 153 | 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 154 | 'JE', 'JM', 'JO', 'JP', 155 | 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'XK', 'KW', 'KY', 'KZ', 156 | 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 157 | 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 158 | 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 159 | 'OM', 160 | 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 161 | 'QA', 162 | 'RE', 'RO', 'RS', 'RU', 'RW', 163 | 'SA', 'SB', 'SC', 'SD', 'SS', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SX', 'SY', 'SZ', 164 | 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 165 | 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 166 | 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 167 | 'WF', 'WS', 168 | 'YE', 'YT', 169 | 'ZA', 'ZM', 'ZW', 170 | ] 171 | 172 | _ALTERNATIVE_NAME_TYPES = ( 173 | ('name', _("Name")), 174 | ('abbr', _("Abbreviation")), 175 | ('link', _("Link")), 176 | ) 177 | 178 | _AIRPORT_TYPES = ( 179 | ('iata', _("IATA (Airport) Code")), 180 | ('icao', _("ICAO (Airport) Code")), 181 | ('faac', _("FAAC (Airport) Code")), 182 | ) 183 | 184 | CONTINENT_DATA = { 185 | 'AF': ('Africa', 6255146), 186 | 'AS': ('Asia', 6255147), 187 | 'EU': ('Europe', 6255148), 188 | 'NA': ('North America', 6255149), 189 | 'OC': ('Oceania', 6255151), 190 | 'SA': ('South America', 6255150), 191 | 'AN': ('Antarctica', 6255152), 192 | } 193 | 194 | _CURRENCY_SYMBOLS = { 195 | "AED": "د.إ", "AFN": "؋", "ALL": "L", "AMD": "դր.", "ANG": "ƒ", "AOA": "Kz", 196 | "ARS": "$", "AUD": "$", "AWG": "ƒ", "AZN": "m", 197 | "BAM": "KM", "BBD": "$", "BDT": "৳", "BGN": "лв", "BHD": "ب.د", "BIF": "Fr", 198 | "BMD": "$", "BND": "$", "BOB": "Bs.", "BRL": "R$", "BSD": "$", "BTN": "Nu", 199 | "BWP": "P", "BYR": "Br", "BZD": "$", 200 | "CAD": "$", "CDF": "Fr", "CHF": "Fr", "CLP": "$", "CNY": "¥", "COP": "$", 201 | "CRC": "₡", "CUP": "$", "CVE": "$, Esc", "CZK": "Kč", 202 | "DJF": "Fr", "DKK": "kr", "DOP": "$", "DZD": "د.ج", 203 | "EEK": "KR", "EGP": "£,ج.م", "ERN": "Nfk", "ETB": "Br", "EUR": "€", 204 | "FJD": "$", "FKP": "£", 205 | "GBP": "£", "GEL": "ლ", "GHS": "₵", "GIP": "£", "GMD": "D", "GNF": "Fr", 206 | "GTQ": "Q", "GYD": "$", 207 | "HKD": "$", "HNL": "L", "HRK": "kn", "HTG": "G", "HUF": "Ft", 208 | "IDR": "Rp", "ILS": "₪", "INR": "₨", "IQD": "ع.د", "IRR": "﷼", "ISK": "kr", 209 | "JMD": "$", "JOD": "د.ا", "JPY": "¥", 210 | "KES": "Sh", "KGS": "лв", "KHR": "៛", "KMF": "Fr", "KPW": "₩", "KRW": "₩", 211 | "KWD": "د.ك", "KYD": "$", "KZT": "Т", 212 | "LAK": "₭", "LBP": "ل.ل", "LKR": "ரூ", "LRD": "$", "LSL": "L", "LTL": "Lt", 213 | "LVL": "Ls", "LYD": "ل.د", 214 | "MAD": "د.م.", "MDL": "L", "MGA": "Ar", "MKD": "ден", "MMK": "K", 215 | "MNT": "₮", "MOP": "P", "MRO": "UM", "MUR": "₨", "MVR": "ރ.", "MWK": "MK", 216 | "MXN": "$", "MYR": "RM", "MZN": "MT", 217 | "NAD": "$", "NGN": "₦", "NIO": "C$", "NOK": "kr", "NPR": "₨", "NZD": "$", 218 | "OMR": "ر.ع.", 219 | "PAB": "B/.", "PEN": "S/.", "PGK": "K", "PHP": "₱", "PKR": "₨", "PLN": "zł", 220 | "PYG": "₲", 221 | "QAR": "ر.ق", 222 | "RON": "RON", "RSD": "RSD", "RUB": "р.", "RWF": "Fr", 223 | "SAR": "ر.س", "SBD": "$", "SCR": "₨", "SDG": "S$", "SEK": "kr", "SGD": "$", 224 | "SHP": "£", "SLL": "Le", "SOS": "Sh", "SRD": "$", "STD": "Db", 225 | "SYP": "£, ل.س", "SZL": "L", 226 | "THB": "฿", "TJS": "ЅМ", "TMT": "m", "TND": "د.ت", "TOP": "T$", "TRY": "₤", 227 | "TTD": "$", "TWD": "$", "TZS": "Sh", 228 | "UAH": "₴", "UGX": "Sh", "USD": "$", "UYU": "$", "UZS": "лв", 229 | "VEF": "Bs", "VND": "₫", "VUV": "Vt", 230 | "WST": "T", 231 | "XAF": "Fr", "XCD": "$", "XOF": "Fr", "XPF": "Fr", 232 | "YER": "﷼", 233 | "ZAR": "R", "ZMK": "ZK", "ZWL": "$", 234 | } 235 | 236 | _NO_LONGER_EXISTENT_COUNTRY_CODES = ['CS', 'AN'] 237 | 238 | _SLUGIFY_FUNCTION = getattr(django_settings, 'CITIES_SLUGIFY_FUNCTION', 'cities.util.default_slugify') 239 | 240 | # See http://www.geonames.org/export/codes.html 241 | city_types = ['PPL', 'PPLA', 'PPLC', 'PPLA2', 'PPLA3', 'PPLA4', 'PPLG'] 242 | district_types = ['PPLX'] 243 | 244 | # Command-line import options 245 | import_opts = [ 246 | 'all', 247 | 'country', 248 | 'region', 249 | 'subregion', 250 | 'city', 251 | 'district', 252 | 'alt_name', 253 | 'postal_code', 254 | ] 255 | 256 | import_opts_all = [ 257 | 'country', 258 | 'region', 259 | 'subregion', 260 | 'city', 261 | 'district', 262 | 'alt_name', 263 | 'postal_code', 264 | ] 265 | 266 | 267 | # Raise inside a hook (with an error message) to skip the current line of data. 268 | class HookException(Exception): 269 | pass 270 | 271 | 272 | # Hook functions that a plugin class may define 273 | plugin_hooks = [ 274 | 'country_pre', 'country_post', # noqa: E241 275 | 'region_pre', 'region_post', # noqa: E241 276 | 'subregion_pre', 'subregion_post', # noqa: E241 277 | 'city_pre', 'city_post', # noqa: E241 278 | 'district_pre', 'district_post', # noqa: E241 279 | 'alt_name_pre', 'alt_name_post', # noqa: E241 280 | 'postal_code_pre', 'postal_code_post', # noqa: E241 281 | ] 282 | 283 | 284 | def create_settings(): 285 | def get_locales(self): 286 | if hasattr(django_settings, "CITIES_LOCALES"): 287 | locales = django_settings.CITIES_LOCALES[:] 288 | else: 289 | locales = ['en', 'und'] 290 | 291 | try: 292 | locales.remove('LANGUAGES') 293 | locales += [e[0] for e in django_settings.LANGUAGES] 294 | except Exception: 295 | pass 296 | 297 | return set([e.lower() for e in locales]) 298 | 299 | res = type('settings', (), { 300 | 'locales': property(get_locales), 301 | }) 302 | 303 | res.files = files.copy() 304 | if hasattr(django_settings, "CITIES_FILES"): 305 | for key in list(django_settings.CITIES_FILES.keys()): 306 | if 'filenames' in django_settings.CITIES_FILES[key] and 'filename' in django_settings.CITIES_FILES[key]: 307 | raise ImproperlyConfigured( 308 | "Only one key should be specified for '%s': 'filename' of 'filenames'. Both specified instead" % key 309 | ) 310 | res.files[key].update(django_settings.CITIES_FILES[key]) 311 | if 'filenames' in django_settings.CITIES_FILES[key]: 312 | del res.files[key]['filename'] 313 | 314 | if hasattr(django_settings, "CITIES_DATA_DIR"): 315 | res.data_dir = django_settings.CITIES_DATA_DIR 316 | 317 | if hasattr(django_settings, "CITIES_POSTAL_CODES"): 318 | res.postal_codes = set([e.upper() for e in django_settings.CITIES_POSTAL_CODES]) 319 | else: 320 | res.postal_codes = set(['ALL']) 321 | 322 | return res() 323 | 324 | 325 | def create_plugins(): 326 | settings.plugins = defaultdict(list) 327 | for plugin in django_settings.CITIES_PLUGINS: 328 | module_path, classname = plugin.rsplit('.', 1) 329 | module = import_module(module_path) 330 | class_ = getattr(module, classname) 331 | obj = class_() 332 | [settings.plugins[hook].append(obj) for hook in plugin_hooks if hasattr(obj, hook)] 333 | 334 | 335 | settings = create_settings() 336 | if hasattr(django_settings, "CITIES_PLUGINS"): 337 | create_plugins() 338 | 339 | if hasattr(django_settings, 'CITIES_IGNORE_EMPTY_REGIONS'): 340 | raise Exception("CITIES_IGNORE_EMPTY_REGIONS was ambiguous and has been moved to CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS") 341 | 342 | SKIP_CITIES_WITH_EMPTY_REGIONS = getattr(django_settings, 'CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS', False) 343 | 344 | # Users may way to import historical countries 345 | NO_LONGER_EXISTENT_COUNTRY_CODES = getattr( 346 | django_settings, 'CITIES_NO_LONGER_EXISTENT_COUNTRY_CODES', 347 | _NO_LONGER_EXISTENT_COUNTRY_CODES) 348 | 349 | # Users may not want to include airport codes as alternative city names 350 | INCLUDE_AIRPORT_CODES = getattr(django_settings, 'CITIES_INCLUDE_AIRPORT_CODES', True) 351 | 352 | if INCLUDE_AIRPORT_CODES: 353 | _ALTERNATIVE_NAME_TYPES += _AIRPORT_TYPES 354 | 355 | # A `Choices` object (from `django-model-utils`) 356 | ALTERNATIVE_NAME_TYPES = getattr(django_settings, 'CITIES_ALTERNATIVE_NAME_TYPES', _ALTERNATIVE_NAME_TYPES) 357 | 358 | INCLUDE_NUMERIC_ALTERNATIVE_NAMES = getattr(django_settings, 'CITIES_INCLUDE_NUMERIC_ALTERNATIVE_NAMES', True) 359 | 360 | # Allow users to override specified contents 361 | CONTINENT_DATA.update(getattr(django_settings, 'CITIES_CONTINENT_DATA', {})) 362 | 363 | CURRENCY_SYMBOLS = getattr(django_settings, 'CITIES_CURRENCY_SYMBOLS', _CURRENCY_SYMBOLS) 364 | 365 | module_name, _, function_name = _SLUGIFY_FUNCTION.rpartition('.') 366 | SLUGIFY_FUNCTION = getattr(import_module(module_name), function_name) 367 | 368 | # Users who want better postal codes can flip this on (developers of 369 | # django-cities itself probably will), but most probably won't want to 370 | VALIDATE_POSTAL_CODES = getattr(django_settings, 'CITIES_VALIDATE_POSTAL_CODES', False) 371 | DJANGO_VERSION = float('.'.join(map(str, django.VERSION[:2]))) 372 | -------------------------------------------------------------------------------- /cities/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/cities/management/__init__.py -------------------------------------------------------------------------------- /cities/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/cities/management/commands/__init__.py -------------------------------------------------------------------------------- /cities/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.gis.db import models 2 | 3 | 4 | class AlternativeNameManager(models.Manager): 5 | def get_queryset(self): 6 | return super(AlternativeNameManager, self).get_queryset().exclude(kind='link') 7 | -------------------------------------------------------------------------------- /cities/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.contrib.gis.db.models.fields 6 | 7 | import swapper 8 | 9 | from cities.models import SET_NULL_OR_CASCADE 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | swapper.dependency('cities', 'City'), 16 | swapper.dependency('cities', 'Country'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='AlternativeName', 22 | fields=[ 23 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 24 | ('name', models.CharField(max_length=256)), 25 | ('language', models.CharField(max_length=100)), 26 | ('is_preferred', models.BooleanField(default=False)), 27 | ('is_short', models.BooleanField(default=False)), 28 | ('is_colloquial', models.BooleanField(default=False)), 29 | ], 30 | ), 31 | migrations.CreateModel( 32 | name='City', 33 | fields=[ 34 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 35 | ('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)), 36 | ('slug', models.CharField(max_length=200)), 37 | ('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)), 38 | ('location', django.contrib.gis.db.models.fields.PointField(srid=4326)), 39 | ('population', models.IntegerField()), 40 | ('elevation', models.IntegerField(null=True)), 41 | ('kind', models.CharField(max_length=10)), 42 | ('timezone', models.CharField(max_length=40)), 43 | ('alt_names', models.ManyToManyField(to='cities.AlternativeName')), 44 | ], 45 | options={ 46 | 'swappable': swapper.swappable_setting('cities', 'City'), 47 | 'verbose_name_plural': 'cities', 48 | }, 49 | ), 50 | migrations.CreateModel( 51 | name='Country', 52 | fields=[ 53 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 54 | ('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)), 55 | ('slug', models.CharField(max_length=200)), 56 | ('code', models.CharField(max_length=2, db_index=True)), 57 | ('code3', models.CharField(max_length=3, db_index=True)), 58 | ('population', models.IntegerField()), 59 | ('area', models.IntegerField(null=True)), 60 | ('currency', models.CharField(max_length=3, null=True)), 61 | ('currency_name', models.CharField(max_length=50, null=True)), 62 | ('languages', models.CharField(max_length=250, null=True)), 63 | ('phone', models.CharField(max_length=20)), 64 | ('continent', models.CharField(max_length=2)), 65 | ('tld', models.CharField(max_length=5)), 66 | ('capital', models.CharField(max_length=100)), 67 | ('alt_names', models.ManyToManyField(to='cities.AlternativeName')), 68 | ('neighbours', models.ManyToManyField(related_name='neighbours_rel_+', to=swapper.get_model_name('cities', 'Country'))), 69 | ], 70 | options={ 71 | 'ordering': ['name'], 72 | 'swappable': swapper.swappable_setting('cities', 'Country'), 73 | 'verbose_name_plural': 'countries', 74 | }, 75 | ), 76 | migrations.CreateModel( 77 | name='District', 78 | fields=[ 79 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 80 | ('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)), 81 | ('slug', models.CharField(max_length=200)), 82 | ('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)), 83 | ('location', django.contrib.gis.db.models.fields.PointField(srid=4326)), 84 | ('population', models.IntegerField()), 85 | ('alt_names', models.ManyToManyField(to='cities.AlternativeName')), 86 | ('city', models.ForeignKey(to=swapper.get_model_name('cities', 'City'), on_delete=SET_NULL_OR_CASCADE)), 87 | ], 88 | options={ 89 | 'abstract': False, 90 | }, 91 | ), 92 | migrations.CreateModel( 93 | name='PostalCode', 94 | fields=[ 95 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 96 | ('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)), 97 | ('slug', models.CharField(max_length=200)), 98 | ('code', models.CharField(max_length=20)), 99 | ('location', django.contrib.gis.db.models.fields.PointField(srid=4326)), 100 | ('region_name', models.CharField(max_length=100, db_index=True)), 101 | ('subregion_name', models.CharField(max_length=100, db_index=True)), 102 | ('district_name', models.CharField(max_length=100, db_index=True)), 103 | ('alt_names', models.ManyToManyField(to='cities.AlternativeName')), 104 | ('country', models.ForeignKey(related_name='postal_codes', to=swapper.get_model_name('cities', 'Country'), on_delete=SET_NULL_OR_CASCADE)), 105 | ], 106 | options={ 107 | 'abstract': False, 108 | }, 109 | ), 110 | migrations.CreateModel( 111 | name='Region', 112 | fields=[ 113 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 114 | ('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)), 115 | ('slug', models.CharField(max_length=200)), 116 | ('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)), 117 | ('code', models.CharField(max_length=200, db_index=True)), 118 | ('alt_names', models.ManyToManyField(to='cities.AlternativeName')), 119 | ('country', models.ForeignKey(to=swapper.get_model_name('cities', 'Country'), on_delete=SET_NULL_OR_CASCADE)), 120 | ], 121 | options={ 122 | 'abstract': False, 123 | }, 124 | ), 125 | migrations.CreateModel( 126 | name='Subregion', 127 | fields=[ 128 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 129 | ('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)), 130 | ('slug', models.CharField(max_length=200)), 131 | ('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)), 132 | ('code', models.CharField(max_length=200, db_index=True)), 133 | ('alt_names', models.ManyToManyField(to='cities.AlternativeName')), 134 | ('region', models.ForeignKey(to='cities.Region', on_delete=SET_NULL_OR_CASCADE)), 135 | ], 136 | options={ 137 | 'abstract': False, 138 | }, 139 | ), 140 | migrations.AddField( 141 | model_name='city', 142 | name='country', 143 | field=models.ForeignKey(to=swapper.get_model_name('cities', 'Country'), on_delete=SET_NULL_OR_CASCADE), 144 | ), 145 | migrations.AddField( 146 | model_name='city', 147 | name='region', 148 | field=models.ForeignKey(blank=True, to='cities.Region', null=True, on_delete=SET_NULL_OR_CASCADE), 149 | ), 150 | migrations.AddField( 151 | model_name='city', 152 | name='subregion', 153 | field=models.ForeignKey(blank=True, to='cities.Subregion', null=True, on_delete=SET_NULL_OR_CASCADE), 154 | ), 155 | ] 156 | -------------------------------------------------------------------------------- /cities/migrations/0002_continent_models_and_foreign_keys.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.2 on 2016-03-29 19:49 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | import swapper 10 | 11 | from ..util import add_continents as util_add_continents 12 | 13 | 14 | def get_model(apps, name): 15 | model_tuple = swapper.split(swapper.get_model_name('cities', name)) 16 | return apps.get_model(*model_tuple) 17 | 18 | 19 | def add_continents(apps, schema_editor): 20 | util_add_continents(get_model(apps, 'Continent')) 21 | 22 | 23 | def rm_continents(apps, schema_editor): 24 | # The table is going to be nuked anyway, we just don't want RunPython() 25 | # to throw an exception on backwards migrations 26 | pass 27 | 28 | 29 | def add_continent_fks(apps, schema_editor): 30 | Country = get_model(apps, 'Country') 31 | Continent = get_model(apps, 'Continent') 32 | 33 | for continent in Continent.objects.all(): 34 | Country.objects.filter(continent_code=continent.code).update(continent=continent) 35 | 36 | 37 | def rm_continent_fks(apps, schema_editor): 38 | Country = get_model(apps, 'Country') 39 | Continent = get_model(apps, 'Continent') 40 | 41 | for continent in Continent.objects.all(): 42 | Country.objects.filter(continent=continent).update(continent_code=continent.code) 43 | 44 | 45 | class Migration(migrations.Migration): 46 | 47 | dependencies = [ 48 | ('cities', '0001_initial'), 49 | swapper.dependency('cities', 'Country'), 50 | ] 51 | 52 | operations = [ 53 | migrations.CreateModel( 54 | name='Continent', 55 | fields=[ 56 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 57 | ('name', models.CharField(db_index=True, max_length=200, verbose_name='ascii name')), 58 | ('slug', models.CharField(max_length=200, unique=True)), 59 | ('code', models.CharField(db_index=True, max_length=2, unique=True)), 60 | ], 61 | options={ 62 | 'abstract': False, 63 | 'swappable': swapper.swappable_setting('cities', 'Continent'), 64 | }, 65 | ), 66 | migrations.AddField( 67 | model_name='continent', 68 | name='alt_names', 69 | field=models.ManyToManyField(related_name='cities_continents', to='cities.AlternativeName'), 70 | ), 71 | migrations.RenameField( 72 | model_name='country', 73 | old_name='continent', 74 | new_name='continent_code', 75 | ), 76 | migrations.AddField( 77 | model_name='country', 78 | name='continent', 79 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='countries', to=swapper.get_model_name('cities', 'Continent')), 80 | ), 81 | migrations.RunPython(add_continents, rm_continents), 82 | migrations.RunPython(add_continent_fks, rm_continent_fks), 83 | migrations.RemoveField( 84 | model_name='country', 85 | name='continent_code', 86 | ), 87 | ] 88 | -------------------------------------------------------------------------------- /cities/migrations/0003_add_verbose_name_and_related_names.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-23 23:24 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | import swapper 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('cities', '0002_continent_models_and_foreign_keys'), 15 | swapper.dependency('cities', 'Continent'), 16 | swapper.dependency('cities', 'Country'), 17 | swapper.dependency('cities', 'City'), 18 | ] 19 | 20 | operations = [ 21 | migrations.AlterField( 22 | model_name='city', 23 | name='country', 24 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cities', to=swapper.get_model_name('cities', 'Country')), 25 | ), 26 | migrations.AlterField( 27 | model_name='city', 28 | name='region', 29 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cities', to='cities.Region'), 30 | ), 31 | migrations.AlterField( 32 | model_name='city', 33 | name='subregion', 34 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cities', to='cities.Subregion'), 35 | ), 36 | migrations.AlterField( 37 | model_name='continent', 38 | name='alt_names', 39 | field=models.ManyToManyField(to='cities.AlternativeName'), 40 | ), 41 | migrations.AlterField( 42 | model_name='continent', 43 | name='slug', 44 | field=models.CharField(max_length=200), 45 | ), 46 | migrations.AlterField( 47 | model_name='country', 48 | name='tld', 49 | field=models.CharField(max_length=5, verbose_name='TLD'), 50 | ), 51 | migrations.AlterField( 52 | model_name='district', 53 | name='city', 54 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='districts', to=swapper.get_model_name('cities', 'City')), 55 | ), 56 | migrations.AlterField( 57 | model_name='region', 58 | name='country', 59 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='regions', to=swapper.get_model_name('cities', 'Country')), 60 | ), 61 | migrations.AlterField( 62 | model_name='subregion', 63 | name='region', 64 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subregions', to='cities.Region'), 65 | ), 66 | ] 67 | -------------------------------------------------------------------------------- /cities/migrations/0004_rename_languages_to_language_codes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-23 23:31 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cities', '0003_add_verbose_name_and_related_names'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='alternativename', 17 | old_name='language', 18 | new_name='language_code', 19 | ), 20 | migrations.RenameField( 21 | model_name='country', 22 | old_name='languages', 23 | new_name='language_codes', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /cities/migrations/0005_add_foreignkeys_to_postalcode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-23 23:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | import swapper 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('cities', '0004_rename_languages_to_language_codes'), 15 | swapper.dependency('cities', 'City'), 16 | ] 17 | 18 | operations = [ 19 | migrations.AddField( 20 | model_name='postalcode', 21 | name='city', 22 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to=swapper.get_model_name('cities', 'City')), 23 | ), 24 | migrations.AddField( 25 | model_name='postalcode', 26 | name='district', 27 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to='cities.District'), 28 | ), 29 | migrations.AddField( 30 | model_name='postalcode', 31 | name='region', 32 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to='cities.Region'), 33 | ), 34 | migrations.AddField( 35 | model_name='postalcode', 36 | name='subregion', 37 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to='cities.Subregion'), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /cities/migrations/0006_typify_alt_names_and_add_is_historic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-24 12:15 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | import swapper 9 | 10 | from ..conf import ALTERNATIVE_NAME_TYPES 11 | 12 | 13 | class Migration(migrations.Migration): 14 | 15 | dependencies = [ 16 | ('cities', '0005_add_foreignkeys_to_postalcode'), 17 | swapper.dependency('cities', 'City'), 18 | ] 19 | 20 | operations = [ 21 | migrations.AddField( 22 | model_name='alternativename', 23 | name='is_historic', 24 | field=models.BooleanField(default=False), 25 | ), 26 | migrations.AddField( 27 | model_name='alternativename', 28 | name='kind', 29 | field=models.CharField(choices=ALTERNATIVE_NAME_TYPES, default='name', max_length=4), 30 | ), 31 | migrations.AlterField( 32 | model_name='alternativename', 33 | name='name', 34 | field=models.CharField(max_length=255), 35 | ), 36 | migrations.AlterField( 37 | model_name='postalcode', 38 | name='city', 39 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to=swapper.get_model_name('cities', 'City')), 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /cities/migrations/0007_add_currency_and_postal_code_fields_to_country_model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-24 18:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cities', '0006_typify_alt_names_and_add_is_historic'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='country', 17 | name='currency_symbol', 18 | field=models.CharField(blank=True, max_length=31, null=True), 19 | ), 20 | migrations.AddField( 21 | model_name='country', 22 | name='postal_code_format', 23 | field=models.CharField(default='', max_length=127), 24 | preserve_default=False, 25 | ), 26 | migrations.AddField( 27 | model_name='country', 28 | name='postal_code_regex', 29 | field=models.CharField(default='', max_length=255), 30 | preserve_default=False, 31 | ), 32 | migrations.AlterField( 33 | model_name='country', 34 | name='currency_name', 35 | field=models.CharField(blank=True, max_length=50, null=True), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /cities/migrations/0008_add_code_to_district.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-26 03:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cities', '0007_add_currency_and_postal_code_fields_to_country_model'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='district', 17 | name='code', 18 | field=models.CharField(blank=True, db_index=True, max_length=200, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /cities/migrations/0009_add_slug_fields_to_models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-27 13:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cities', '0008_add_code_to_district'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='alternativename', 17 | name='slug', 18 | field=models.CharField(blank=True, max_length=255, null=True), 19 | preserve_default=False, 20 | ), 21 | migrations.AlterField( 22 | model_name='city', 23 | name='slug', 24 | field=models.CharField(blank=True, max_length=255, null=True), 25 | ), 26 | migrations.AlterField( 27 | model_name='continent', 28 | name='slug', 29 | field=models.CharField(blank=True, max_length=255, null=True), 30 | ), 31 | migrations.AlterField( 32 | model_name='country', 33 | name='slug', 34 | field=models.CharField(blank=True, max_length=255, null=True), 35 | ), 36 | migrations.AlterField( 37 | model_name='district', 38 | name='slug', 39 | field=models.CharField(blank=True, max_length=255, null=True), 40 | ), 41 | migrations.AlterField( 42 | model_name='postalcode', 43 | name='slug', 44 | field=models.CharField(blank=True, max_length=255, null=True), 45 | ), 46 | migrations.AlterField( 47 | model_name='region', 48 | name='slug', 49 | field=models.CharField(blank=True, max_length=255, null=True), 50 | ), 51 | migrations.AlterField( 52 | model_name='subregion', 53 | name='slug', 54 | field=models.CharField(blank=True, max_length=255, null=True), 55 | ), 56 | ] 57 | -------------------------------------------------------------------------------- /cities/migrations/0010_adjust_unique_attributes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-12-20 08:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | UNIQUE_SLUG_MODELS = ['Continent', 'Country', 'Region', 'Subregion', 'District', 9 | 'City', 'PostalCode', 'AlternativeName'] 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ('cities', '0009_add_slug_fields_to_models'), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name='country', 21 | name='code', 22 | field=models.CharField(db_index=True, max_length=2, unique=True), 23 | ), 24 | migrations.AlterField( 25 | model_name='country', 26 | name='code3', 27 | field=models.CharField(db_index=True, max_length=3, unique=True), 28 | ), 29 | migrations.AlterUniqueTogether( 30 | name='city', 31 | unique_together=set([('country', 'region', 'subregion', 'id', 'name')]), 32 | ), 33 | migrations.AlterUniqueTogether( 34 | name='district', 35 | unique_together=set([('city', 'name')]), 36 | ), 37 | migrations.AlterUniqueTogether( 38 | name='postalcode', 39 | unique_together=set([ 40 | ('country', 'region_name', 'subregion_name', 'district_name', 'name', 'id', 'code'), 41 | ('country', 'region', 'subregion', 'city', 'district', 'name', 'id', 'code'), 42 | ]), 43 | ), 44 | migrations.AlterUniqueTogether( 45 | name='region', 46 | unique_together=set([('country', 'name')]), 47 | ), 48 | migrations.AlterUniqueTogether( 49 | name='subregion', 50 | unique_together=set([('region', 'id', 'name')]), 51 | ), 52 | ] 53 | 54 | # The previous migration created the slug field as 55 | # 56 | # CharField(max_length=255, unique=True) 57 | # 58 | # but I removed the unique=True for new users. This checks the previous 59 | # state of the slug field on the models, and if the field has unique=True 60 | # (eg: existing users), it removes the unique attribute. For slug fields 61 | # that don't have unique=True (eg: new users), it skips them. 62 | def database_forwards(self, app_label, schema_editor, from_state, to_state): 63 | # Before we do any database operations, add the appropriate slug field 64 | # modifications to the migration operations. 65 | self.operations + [ 66 | migrations.AlterField( 67 | model_name=model_name.lower(), 68 | name='slug', 69 | field=models.CharField(max_length=255)) 70 | for model_name in UNIQUE_SLUG_MODELS 71 | # This is the check that matters. It checks the result of the 72 | # previous 0009 migration (which may or may not have unique=True 73 | # set on slug fields) for the unique attribute, and skips it if 74 | # they don't. 75 | if from_state.apps.get_model(app_label, self.model_name)._meta.get_field('slug').unique 76 | ] 77 | 78 | super(Migration, self).database_forwards(app_label, schema_editor, from_state, to_state) 79 | -------------------------------------------------------------------------------- /cities/migrations/0011_auto_20180108_0706.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-01-08 07:06 2 | 3 | import cities.models 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cities', '0010_adjust_unique_attributes'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='city', 17 | name='country', 18 | field=models.ForeignKey(on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='cities', to=settings.CITIES_COUNTRY_MODEL), 19 | ), 20 | migrations.AlterField( 21 | model_name='city', 22 | name='region', 23 | field=models.ForeignKey(blank=True, null=True, on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='cities', to='cities.Region'), 24 | ), 25 | migrations.AlterField( 26 | model_name='city', 27 | name='subregion', 28 | field=models.ForeignKey(blank=True, null=True, on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='cities', to='cities.Subregion'), 29 | ), 30 | migrations.AlterField( 31 | model_name='country', 32 | name='continent', 33 | field=models.ForeignKey(null=True, on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='countries', to=settings.CITIES_CONTINENT_MODEL), 34 | ), 35 | migrations.AlterField( 36 | model_name='district', 37 | name='city', 38 | field=models.ForeignKey(on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='districts', to=settings.CITIES_CITY_MODEL), 39 | ), 40 | migrations.AlterField( 41 | model_name='postalcode', 42 | name='city', 43 | field=models.ForeignKey(blank=True, null=True, on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to=settings.CITIES_CITY_MODEL), 44 | ), 45 | migrations.AlterField( 46 | model_name='postalcode', 47 | name='district', 48 | field=models.ForeignKey(blank=True, null=True, on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to='cities.District'), 49 | ), 50 | migrations.AlterField( 51 | model_name='postalcode', 52 | name='region', 53 | field=models.ForeignKey(blank=True, null=True, on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to='cities.Region'), 54 | ), 55 | migrations.AlterField( 56 | model_name='postalcode', 57 | name='subregion', 58 | field=models.ForeignKey(blank=True, null=True, on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to='cities.Subregion'), 59 | ), 60 | migrations.AlterField( 61 | model_name='region', 62 | name='country', 63 | field=models.ForeignKey(on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='regions', to=settings.CITIES_COUNTRY_MODEL), 64 | ), 65 | migrations.AlterField( 66 | model_name='subregion', 67 | name='region', 68 | field=models.ForeignKey(on_delete=cities.models.SET_NULL_OR_CASCADE, related_name='subregions', to='cities.Region'), 69 | ), 70 | ] 71 | -------------------------------------------------------------------------------- /cities/migrations/0012_alter_country_neighbours.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-12-06 20:56 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('cities', '0011_auto_20180108_0706'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='country', 16 | name='neighbours', 17 | field=models.ManyToManyField(related_name='_cities_country_neighbours_+', to=settings.CITIES_COUNTRY_MODEL), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /cities/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/cities/migrations/__init__.py -------------------------------------------------------------------------------- /cities/models.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | from string import ascii_uppercase, digits 3 | 4 | from .conf import (ALTERNATIVE_NAME_TYPES, SLUGIFY_FUNCTION, DJANGO_VERSION) 5 | 6 | 7 | if DJANGO_VERSION < 4: 8 | try: 9 | from django.utils.encoding import force_unicode as force_text 10 | except (NameError, ImportError): 11 | from django.utils.encoding import force_text 12 | else: 13 | from django.utils.encoding import force_str as force_text 14 | 15 | from django.db import transaction 16 | from django.contrib.gis.db.models import PointField 17 | from django.db import models 18 | from django.contrib.gis.geos import Point 19 | 20 | from model_utils import Choices 21 | import swapper 22 | 23 | from .managers import AlternativeNameManager 24 | from .util import unicode_func 25 | 26 | __all__ = [ 27 | 'Point', 'Continent', 'Country', 'Region', 'Subregion', 'City', 'District', 28 | 'PostalCode', 'AlternativeName', 29 | ] 30 | 31 | 32 | if DJANGO_VERSION < 2: 33 | from django.contrib.gis.db.models import GeoManager 34 | else: 35 | from django.db.models import Manager as GeoManager 36 | 37 | slugify_func = SLUGIFY_FUNCTION 38 | 39 | 40 | def SET_NULL_OR_CASCADE(collector, field, sub_objs, using): 41 | if field.null is True: 42 | models.SET_NULL(collector, field, sub_objs, using) 43 | else: 44 | models.CASCADE(collector, field, sub_objs, using) 45 | 46 | 47 | class SlugModel(models.Model): 48 | slug = models.CharField(blank=True, max_length=255, null=True) 49 | 50 | class Meta: 51 | abstract = True 52 | 53 | def slugify(self): 54 | raise NotImplementedError("Subclasses of Place must implement slugify()") 55 | 56 | def save(self, *args, **kwargs): 57 | self.slug = slugify_func(self, self.slugify()) 58 | # If the slug contains the object's ID and we are creating a new object, 59 | # save it twice: once to get an ID, another to set the object's slug 60 | if self.slug is None and getattr(self, 'slug_contains_id', False): 61 | with transaction.atomic(): 62 | # We first give a randomized slug with a prefix just in case 63 | # users need to find invalid slugs 64 | self.slug = 'invalid-{}'.format(''.join(choice(ascii_uppercase + digits) for i in range(20))) 65 | super(SlugModel, self).save(*args, **kwargs) 66 | self.slug = slugify_func(self, self.slugify()) 67 | 68 | # If the 'force_insert' flag was passed, don't pass it again: 69 | # doing so will attempt to re-insert with the same primary key, 70 | # which will cause an IntegrityError. 71 | kwargs.pop('force_insert', None) 72 | super(SlugModel, self).save(*args, **kwargs) 73 | else: 74 | # This is a performance optimization - we avoid the transaction if 75 | # the self.slug is not None 76 | super(SlugModel, self).save(*args, **kwargs) 77 | 78 | 79 | class Place(models.Model): 80 | name = models.CharField(max_length=200, db_index=True, verbose_name="ascii name") 81 | alt_names = models.ManyToManyField('AlternativeName') 82 | 83 | objects = GeoManager() 84 | 85 | class Meta: 86 | abstract = True 87 | 88 | @property 89 | def hierarchy(self): 90 | """Get hierarchy, root first""" 91 | lst = self.parent.hierarchy if self.parent else [] 92 | lst.append(self) 93 | return lst 94 | 95 | def get_absolute_url(self): 96 | return "/".join([place.slug for place in self.hierarchy]) 97 | 98 | def __str__(self): 99 | return force_text(self.name) 100 | 101 | def save(self, *args, **kwargs): 102 | if hasattr(self, 'clean'): 103 | self.clean() 104 | super(Place, self).save(*args, **kwargs) 105 | 106 | 107 | class BaseContinent(Place, SlugModel): 108 | code = models.CharField(max_length=2, unique=True, db_index=True) 109 | 110 | def __str__(self): 111 | return force_text(self.name) 112 | 113 | class Meta: 114 | abstract = True 115 | 116 | def slugify(self): 117 | return self.name 118 | 119 | 120 | class Continent(BaseContinent): 121 | class Meta(BaseContinent.Meta): 122 | swappable = swapper.swappable_setting('cities', 'Continent') 123 | 124 | 125 | class BaseCountry(Place, SlugModel): 126 | code = models.CharField(max_length=2, db_index=True, unique=True) 127 | code3 = models.CharField(max_length=3, db_index=True, unique=True) 128 | population = models.IntegerField() 129 | area = models.IntegerField(null=True) 130 | currency = models.CharField(max_length=3, null=True) 131 | currency_name = models.CharField(max_length=50, blank=True, null=True) 132 | currency_symbol = models.CharField(max_length=31, blank=True, null=True) 133 | language_codes = models.CharField(max_length=250, null=True) 134 | phone = models.CharField(max_length=20) 135 | continent = models.ForeignKey(swapper.get_model_name('cities', 'Continent'), 136 | null=True, 137 | related_name='countries', 138 | on_delete=SET_NULL_OR_CASCADE) 139 | tld = models.CharField(max_length=5, verbose_name='TLD') 140 | postal_code_format = models.CharField(max_length=127) 141 | postal_code_regex = models.CharField(max_length=255) 142 | capital = models.CharField(max_length=100) 143 | neighbours = models.ManyToManyField("self", related_name='_cities_country_neighbours_+') 144 | 145 | class Meta: 146 | abstract = True 147 | ordering = ['name'] 148 | verbose_name_plural = "countries" 149 | 150 | @property 151 | def parent(self): 152 | return None 153 | 154 | def __str__(self): 155 | return force_text(self.name) 156 | 157 | def clean(self): 158 | self.tld = self.tld.lower() 159 | 160 | def slugify(self): 161 | return self.name 162 | 163 | 164 | class Country(BaseCountry): 165 | class Meta(BaseCountry.Meta): 166 | swappable = swapper.swappable_setting('cities', 'Country') 167 | 168 | 169 | class Region(Place, SlugModel): 170 | name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name") 171 | code = models.CharField(max_length=200, db_index=True) 172 | country = models.ForeignKey(swapper.get_model_name('cities', 'Country'), 173 | related_name='regions', 174 | on_delete=SET_NULL_OR_CASCADE) 175 | 176 | class Meta: 177 | unique_together = (('country', 'name'),) 178 | 179 | @property 180 | def parent(self): 181 | return self.country 182 | 183 | def full_code(self): 184 | return unicode_func(".".join([self.parent.code, self.code])) 185 | 186 | def slugify(self): 187 | return '{}_({})'.format( 188 | unicode_func(self.name), 189 | unicode_func(self.full_code())) 190 | 191 | 192 | class Subregion(Place, SlugModel): 193 | slug_contains_id = True 194 | 195 | name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name") 196 | code = models.CharField(max_length=200, db_index=True) 197 | region = models.ForeignKey(Region, 198 | related_name='subregions', 199 | on_delete=SET_NULL_OR_CASCADE) 200 | 201 | class Meta: 202 | unique_together = (('region', 'id', 'name'),) 203 | 204 | @property 205 | def parent(self): 206 | return self.region 207 | 208 | def full_code(self): 209 | return ".".join([self.parent.parent.code, self.parent.code, self.code]) 210 | 211 | def slugify(self): 212 | return unicode_func('{}-{}').format( 213 | unicode_func(self.id), 214 | unicode_func(self.name)) 215 | 216 | 217 | class BaseCity(Place, SlugModel): 218 | slug_contains_id = True 219 | 220 | name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name") 221 | country = models.ForeignKey(swapper.get_model_name('cities', 'Country'), 222 | related_name='cities', 223 | on_delete=SET_NULL_OR_CASCADE) 224 | region = models.ForeignKey(Region, 225 | null=True, 226 | blank=True, 227 | related_name='cities', 228 | on_delete=SET_NULL_OR_CASCADE) 229 | subregion = models.ForeignKey(Subregion, 230 | null=True, 231 | blank=True, 232 | related_name='cities', 233 | on_delete=SET_NULL_OR_CASCADE) 234 | location = PointField() 235 | population = models.IntegerField() 236 | elevation = models.IntegerField(null=True) 237 | kind = models.CharField(max_length=10) # http://www.geonames.org/export/codes.html 238 | timezone = models.CharField(max_length=40) 239 | 240 | class Meta: 241 | abstract = True 242 | unique_together = (('country', 'region', 'subregion', 'id', 'name'),) 243 | verbose_name_plural = "cities" 244 | 245 | @property 246 | def parent(self): 247 | return self.region 248 | 249 | def slugify(self): 250 | if self.id: 251 | return '{}-{}'.format(self.id, unicode_func(self.name)) 252 | return None 253 | 254 | 255 | class City(BaseCity): 256 | class Meta(BaseCity.Meta): 257 | swappable = swapper.swappable_setting('cities', 'City') 258 | 259 | 260 | class District(Place, SlugModel): 261 | slug_contains_id = True 262 | 263 | name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name") 264 | code = models.CharField(blank=True, db_index=True, max_length=200, null=True) 265 | location = PointField() 266 | population = models.IntegerField() 267 | city = models.ForeignKey(swapper.get_model_name('cities', 'City'), 268 | related_name='districts', 269 | on_delete=SET_NULL_OR_CASCADE) 270 | 271 | class Meta: 272 | unique_together = (('city', 'name'),) 273 | 274 | @property 275 | def parent(self): 276 | return self.city 277 | 278 | def slugify(self): 279 | if self.id: 280 | return '{}-{}'.format(self.id, unicode_func(self.name)) 281 | return None 282 | 283 | 284 | class AlternativeName(SlugModel): 285 | slug_contains_id = True 286 | 287 | KIND = Choices(*ALTERNATIVE_NAME_TYPES) 288 | 289 | name = models.CharField(max_length=255) 290 | kind = models.CharField(max_length=4, choices=KIND, default=KIND.name) 291 | language_code = models.CharField(max_length=100) 292 | is_preferred = models.BooleanField(default=False) 293 | is_short = models.BooleanField(default=False) 294 | is_colloquial = models.BooleanField(default=False) 295 | is_historic = models.BooleanField(default=False) 296 | 297 | objects = AlternativeNameManager() 298 | 299 | def __str__(self): 300 | return "%s (%s)" % (force_text(self.name), force_text(self.language_code)) 301 | 302 | def slugify(self): 303 | if self.id: 304 | return '{}-{}'.format(self.id, unicode_func(self.name)) 305 | return None 306 | 307 | 308 | class PostalCode(Place, SlugModel): 309 | slug_contains_id = True 310 | 311 | code = models.CharField(max_length=20) 312 | location = PointField() 313 | 314 | country = models.ForeignKey(swapper.get_model_name('cities', 'Country'), 315 | related_name='postal_codes', 316 | on_delete=SET_NULL_OR_CASCADE) 317 | 318 | # Region names for each admin level, region may not exist in DB 319 | region_name = models.CharField(max_length=100, db_index=True) 320 | subregion_name = models.CharField(max_length=100, db_index=True) 321 | district_name = models.CharField(max_length=100, db_index=True) 322 | 323 | region = models.ForeignKey(Region, 324 | blank=True, 325 | null=True, 326 | related_name='postal_codes', 327 | on_delete=SET_NULL_OR_CASCADE) 328 | subregion = models.ForeignKey(Subregion, 329 | blank=True, 330 | null=True, 331 | related_name='postal_codes', 332 | on_delete=SET_NULL_OR_CASCADE) 333 | city = models.ForeignKey(swapper.get_model_name('cities', 'City'), 334 | blank=True, 335 | null=True, 336 | related_name='postal_codes', 337 | on_delete=SET_NULL_OR_CASCADE) 338 | district = models.ForeignKey(District, 339 | blank=True, 340 | null=True, 341 | related_name='postal_codes', 342 | on_delete=SET_NULL_OR_CASCADE) 343 | 344 | objects = GeoManager() 345 | 346 | class Meta: 347 | unique_together = ( 348 | ('country', 'region', 'subregion', 'city', 'district', 'name', 'id', 'code'), 349 | ('country', 'region_name', 'subregion_name', 'district_name', 'name', 'id', 'code'), 350 | ) 351 | 352 | @property 353 | def parent(self): 354 | return self.country 355 | 356 | @property 357 | def name_full(self): 358 | """Get full name including hierarchy""" 359 | return force_text(', '.join(reversed(self.names))) 360 | 361 | @property 362 | def names(self): 363 | """Get a hierarchy of non-null names, root first""" 364 | return [e for e in [ 365 | force_text(self.country), 366 | force_text(self.region_name), 367 | force_text(self.subregion_name), 368 | force_text(self.district_name), 369 | force_text(self.name), 370 | ] if e] 371 | 372 | def __str__(self): 373 | return force_text(self.code) 374 | 375 | def slugify(self): 376 | if self.id: 377 | return '{}-{}'.format(self.id, unicode_func(self.code)) 378 | return None 379 | -------------------------------------------------------------------------------- /cities/plugin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/cities/plugin/__init__.py -------------------------------------------------------------------------------- /cities/plugin/postal_code_ca.py: -------------------------------------------------------------------------------- 1 | code_map = { 2 | 'AB': '01', 3 | 'BC': '02', 4 | 'MB': '03', 5 | 'NB': '04', 6 | 'NL': '05', 7 | 'NS': '07', 8 | 'ON': '08', 9 | 'PE': '09', 10 | 'QC': '10', 11 | 'SK': '11', 12 | 'YT': '12', 13 | 'NT': '13', 14 | 'NU': '14', 15 | } 16 | 17 | 18 | class Plugin: 19 | def postal_code_pre(self, parser, item): 20 | country_code = item['countryCode'] 21 | if country_code != 'CA': 22 | return 23 | item['admin1Code'] = code_map[item['admin1Code']] 24 | -------------------------------------------------------------------------------- /cities/plugin/reset_queries.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Call django.db.reset_queries randomly. Default chance is 0.000002 (0.0002%). 4 | 5 | This plugin may be useful when processing all geonames database. 6 | To process all geonames database and include cities that do not specify population 7 | or when their population is less than 1000 people use following settings: 8 | 9 | CITIES_FILES = { 10 | 'city': { 11 | 'filename': 'allCountries.zip', 12 | 'urls': ['http://download.geonames.org/export/dump/'+'{filename}'] 13 | }, 14 | } 15 | 16 | Settings variable CITIES_PLUGINS_RESET_QUERIES_CHANCE may be used to override 17 | default chance: 18 | 19 | CITIES_PLUGINS_RESET_QUERIES_CHANCE = 1.0 / 1000000 20 | 21 | """ 22 | 23 | import random 24 | 25 | from django.db import reset_queries 26 | from django.conf import settings 27 | 28 | reset_chance = getattr(settings, 'CITIES_PLUGINS_RESET_QUERIES_CHANCE', 0.000002) 29 | 30 | 31 | class Plugin: 32 | 33 | def random_reset(self): 34 | if random.random() <= reset_chance: 35 | reset_queries() 36 | 37 | def city_post(self, parser, city, item): 38 | self.random_reset() 39 | 40 | def district_post(self, parser, district, item): 41 | self.random_reset() 42 | -------------------------------------------------------------------------------- /cities/south_migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | from django.db import models 5 | 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | # Adding model 'Country' 11 | db.create_table(u'cities_country', ( 12 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 13 | ('name', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 14 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=200)), 15 | ('code', self.gf('django.db.models.fields.CharField')(max_length=2, db_index=True)), 16 | ('code3', self.gf('django.db.models.fields.CharField')(max_length=3, db_index=True)), 17 | ('population', self.gf('django.db.models.fields.IntegerField')()), 18 | ('area', self.gf('django.db.models.fields.IntegerField')(null=True)), 19 | ('currency', self.gf('django.db.models.fields.CharField')(max_length=3, null=True)), 20 | ('currency_name', self.gf('django.db.models.fields.CharField')(max_length=50, null=True)), 21 | ('languages', self.gf('django.db.models.fields.CharField')(max_length=250, null=True)), 22 | ('phone', self.gf('django.db.models.fields.CharField')(max_length=20)), 23 | ('continent', self.gf('django.db.models.fields.CharField')(max_length=2)), 24 | ('tld', self.gf('django.db.models.fields.CharField')(max_length=5)), 25 | ('capital', self.gf('django.db.models.fields.CharField')(max_length=100)), 26 | )) 27 | db.send_create_signal(u'cities', ['Country']) 28 | 29 | # Adding M2M table for field alt_names on 'Country' 30 | m2m_table_name = db.shorten_name(u'cities_country_alt_names') 31 | db.create_table(m2m_table_name, ( 32 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 33 | ('country', models.ForeignKey(orm[u'cities.country'], null=False)), 34 | ('alternativename', models.ForeignKey(orm[u'cities.alternativename'], null=False)) 35 | )) 36 | db.create_unique(m2m_table_name, ['country_id', 'alternativename_id']) 37 | 38 | # Adding M2M table for field neighbours on 'Country' 39 | m2m_table_name = db.shorten_name(u'cities_country_neighbours') 40 | db.create_table(m2m_table_name, ( 41 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 42 | ('from_country', models.ForeignKey(orm[u'cities.country'], null=False)), 43 | ('to_country', models.ForeignKey(orm[u'cities.country'], null=False)) 44 | )) 45 | db.create_unique(m2m_table_name, ['from_country_id', 'to_country_id']) 46 | 47 | # Adding model 'Region' 48 | db.create_table(u'cities_region', ( 49 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 50 | ('name', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 51 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=200)), 52 | ('name_std', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 53 | ('code', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 54 | ('country', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cities.Country'])), 55 | )) 56 | db.send_create_signal(u'cities', ['Region']) 57 | 58 | # Adding M2M table for field alt_names on 'Region' 59 | m2m_table_name = db.shorten_name(u'cities_region_alt_names') 60 | db.create_table(m2m_table_name, ( 61 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 62 | ('region', models.ForeignKey(orm[u'cities.region'], null=False)), 63 | ('alternativename', models.ForeignKey(orm[u'cities.alternativename'], null=False)) 64 | )) 65 | db.create_unique(m2m_table_name, ['region_id', 'alternativename_id']) 66 | 67 | # Adding model 'Subregion' 68 | db.create_table(u'cities_subregion', ( 69 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 70 | ('name', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 71 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=200)), 72 | ('name_std', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 73 | ('code', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 74 | ('region', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cities.Region'])), 75 | )) 76 | db.send_create_signal(u'cities', ['Subregion']) 77 | 78 | # Adding M2M table for field alt_names on 'Subregion' 79 | m2m_table_name = db.shorten_name(u'cities_subregion_alt_names') 80 | db.create_table(m2m_table_name, ( 81 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 82 | ('subregion', models.ForeignKey(orm[u'cities.subregion'], null=False)), 83 | ('alternativename', models.ForeignKey(orm[u'cities.alternativename'], null=False)) 84 | )) 85 | db.create_unique(m2m_table_name, ['subregion_id', 'alternativename_id']) 86 | 87 | # Adding model 'City' 88 | db.create_table(u'cities_city', ( 89 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 90 | ('name', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 91 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=200)), 92 | ('name_std', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 93 | ('location', self.gf('django.contrib.gis.db.models.fields.PointField')()), 94 | ('population', self.gf('django.db.models.fields.IntegerField')()), 95 | ('region', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cities.Region'], null=True, blank=True)), 96 | ('subregion', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cities.Subregion'], null=True, blank=True)), 97 | ('country', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cities.Country'])), 98 | ('elevation', self.gf('django.db.models.fields.IntegerField')(null=True)), 99 | ('kind', self.gf('django.db.models.fields.CharField')(max_length=10)), 100 | ('timezone', self.gf('django.db.models.fields.CharField')(max_length=40)), 101 | )) 102 | db.send_create_signal(u'cities', ['City']) 103 | 104 | # Adding M2M table for field alt_names on 'City' 105 | m2m_table_name = db.shorten_name(u'cities_city_alt_names') 106 | db.create_table(m2m_table_name, ( 107 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 108 | ('city', models.ForeignKey(orm[u'cities.city'], null=False)), 109 | ('alternativename', models.ForeignKey(orm[u'cities.alternativename'], null=False)) 110 | )) 111 | db.create_unique(m2m_table_name, ['city_id', 'alternativename_id']) 112 | 113 | # Adding model 'District' 114 | db.create_table(u'cities_district', ( 115 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 116 | ('name', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 117 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=200)), 118 | ('name_std', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 119 | ('location', self.gf('django.contrib.gis.db.models.fields.PointField')()), 120 | ('population', self.gf('django.db.models.fields.IntegerField')()), 121 | ('city', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cities.City'])), 122 | )) 123 | db.send_create_signal(u'cities', ['District']) 124 | 125 | # Adding M2M table for field alt_names on 'District' 126 | m2m_table_name = db.shorten_name(u'cities_district_alt_names') 127 | db.create_table(m2m_table_name, ( 128 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 129 | ('district', models.ForeignKey(orm[u'cities.district'], null=False)), 130 | ('alternativename', models.ForeignKey(orm[u'cities.alternativename'], null=False)) 131 | )) 132 | db.create_unique(m2m_table_name, ['district_id', 'alternativename_id']) 133 | 134 | # Adding model 'AlternativeName' 135 | db.create_table(u'cities_alternativename', ( 136 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 137 | ('name', self.gf('django.db.models.fields.CharField')(max_length=256)), 138 | ('language', self.gf('django.db.models.fields.CharField')(max_length=100)), 139 | ('is_preferred', self.gf('django.db.models.fields.BooleanField')(default=False)), 140 | ('is_short', self.gf('django.db.models.fields.BooleanField')(default=False)), 141 | ('is_colloquial', self.gf('django.db.models.fields.BooleanField')(default=False)), 142 | )) 143 | db.send_create_signal(u'cities', ['AlternativeName']) 144 | 145 | # Adding model 'PostalCode' 146 | db.create_table(u'cities_postalcode', ( 147 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 148 | ('name', self.gf('django.db.models.fields.CharField')(max_length=200, db_index=True)), 149 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=200)), 150 | ('code', self.gf('django.db.models.fields.CharField')(max_length=20)), 151 | ('location', self.gf('django.contrib.gis.db.models.fields.PointField')()), 152 | ('country', self.gf('django.db.models.fields.related.ForeignKey')(related_name='postal_codes', to=orm['cities.Country'])), 153 | ('region_name', self.gf('django.db.models.fields.CharField')(max_length=100, db_index=True)), 154 | ('subregion_name', self.gf('django.db.models.fields.CharField')(max_length=100, db_index=True)), 155 | ('district_name', self.gf('django.db.models.fields.CharField')(max_length=100, db_index=True)), 156 | )) 157 | db.send_create_signal(u'cities', ['PostalCode']) 158 | 159 | # Adding M2M table for field alt_names on 'PostalCode' 160 | m2m_table_name = db.shorten_name(u'cities_postalcode_alt_names') 161 | db.create_table(m2m_table_name, ( 162 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 163 | ('postalcode', models.ForeignKey(orm[u'cities.postalcode'], null=False)), 164 | ('alternativename', models.ForeignKey(orm[u'cities.alternativename'], null=False)) 165 | )) 166 | db.create_unique(m2m_table_name, ['postalcode_id', 'alternativename_id']) 167 | 168 | def backwards(self, orm): 169 | # Deleting model 'Country' 170 | db.delete_table(u'cities_country') 171 | 172 | # Removing M2M table for field alt_names on 'Country' 173 | db.delete_table(db.shorten_name(u'cities_country_alt_names')) 174 | 175 | # Removing M2M table for field neighbours on 'Country' 176 | db.delete_table(db.shorten_name(u'cities_country_neighbours')) 177 | 178 | # Deleting model 'Region' 179 | db.delete_table(u'cities_region') 180 | 181 | # Removing M2M table for field alt_names on 'Region' 182 | db.delete_table(db.shorten_name(u'cities_region_alt_names')) 183 | 184 | # Deleting model 'Subregion' 185 | db.delete_table(u'cities_subregion') 186 | 187 | # Removing M2M table for field alt_names on 'Subregion' 188 | db.delete_table(db.shorten_name(u'cities_subregion_alt_names')) 189 | 190 | # Deleting model 'City' 191 | db.delete_table(u'cities_city') 192 | 193 | # Removing M2M table for field alt_names on 'City' 194 | db.delete_table(db.shorten_name(u'cities_city_alt_names')) 195 | 196 | # Deleting model 'District' 197 | db.delete_table(u'cities_district') 198 | 199 | # Removing M2M table for field alt_names on 'District' 200 | db.delete_table(db.shorten_name(u'cities_district_alt_names')) 201 | 202 | # Deleting model 'AlternativeName' 203 | db.delete_table(u'cities_alternativename') 204 | 205 | # Deleting model 'PostalCode' 206 | db.delete_table(u'cities_postalcode') 207 | 208 | # Removing M2M table for field alt_names on 'PostalCode' 209 | db.delete_table(db.shorten_name(u'cities_postalcode_alt_names')) 210 | 211 | models = { 212 | u'cities.alternativename': { 213 | 'Meta': {'object_name': 'AlternativeName'}, 214 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 215 | 'is_colloquial': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 216 | 'is_preferred': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 217 | 'is_short': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 218 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 219 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) 220 | }, 221 | u'cities.city': { 222 | 'Meta': {'object_name': 'City'}, 223 | 'alt_names': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cities.AlternativeName']", 'symmetrical': 'False'}), 224 | 'country': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cities.Country']"}), 225 | 'elevation': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 226 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 227 | 'kind': ('django.db.models.fields.CharField', [], {'max_length': '10'}), 228 | 'location': ('django.contrib.gis.db.models.fields.PointField', [], {}), 229 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 230 | 'name_std': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 231 | 'population': ('django.db.models.fields.IntegerField', [], {}), 232 | 'region': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cities.Region']", 'null': 'True', 'blank': 'True'}), 233 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 234 | 'subregion': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cities.Subregion']", 'null': 'True', 'blank': 'True'}), 235 | 'timezone': ('django.db.models.fields.CharField', [], {'max_length': '40'}) 236 | }, 237 | u'cities.country': { 238 | 'Meta': {'ordering': "['name']", 'object_name': 'Country'}, 239 | 'alt_names': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cities.AlternativeName']", 'symmetrical': 'False'}), 240 | 'area': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 241 | 'capital': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 242 | 'code': ('django.db.models.fields.CharField', [], {'max_length': '2', 'db_index': 'True'}), 243 | 'code3': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), 244 | 'continent': ('django.db.models.fields.CharField', [], {'max_length': '2'}), 245 | 'currency': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}), 246 | 'currency_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}), 247 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 248 | 'languages': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True'}), 249 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 250 | 'neighbours': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'neighbours_rel_+'", 'to': u"orm['cities.Country']"}), 251 | 'phone': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 252 | 'population': ('django.db.models.fields.IntegerField', [], {}), 253 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 254 | 'tld': ('django.db.models.fields.CharField', [], {'max_length': '5'}) 255 | }, 256 | u'cities.district': { 257 | 'Meta': {'object_name': 'District'}, 258 | 'alt_names': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cities.AlternativeName']", 'symmetrical': 'False'}), 259 | 'city': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cities.City']"}), 260 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 261 | 'location': ('django.contrib.gis.db.models.fields.PointField', [], {}), 262 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 263 | 'name_std': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 264 | 'population': ('django.db.models.fields.IntegerField', [], {}), 265 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '200'}) 266 | }, 267 | u'cities.postalcode': { 268 | 'Meta': {'object_name': 'PostalCode'}, 269 | 'alt_names': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cities.AlternativeName']", 'symmetrical': 'False'}), 270 | 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 271 | 'country': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postal_codes'", 'to': u"orm['cities.Country']"}), 272 | 'district_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}), 273 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 274 | 'location': ('django.contrib.gis.db.models.fields.PointField', [], {}), 275 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 276 | 'region_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}), 277 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 278 | 'subregion_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}) 279 | }, 280 | u'cities.region': { 281 | 'Meta': {'object_name': 'Region'}, 282 | 'alt_names': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cities.AlternativeName']", 'symmetrical': 'False'}), 283 | 'code': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 284 | 'country': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cities.Country']"}), 285 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 286 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 287 | 'name_std': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 288 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '200'}) 289 | }, 290 | u'cities.subregion': { 291 | 'Meta': {'object_name': 'Subregion'}, 292 | 'alt_names': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['cities.AlternativeName']", 'symmetrical': 'False'}), 293 | 'code': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 294 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 295 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 296 | 'name_std': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), 297 | 'region': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cities.Region']"}), 298 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '200'}) 299 | } 300 | } 301 | 302 | complete_apps = ['cities'] 303 | -------------------------------------------------------------------------------- /cities/south_migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/cities/south_migrations/__init__.py -------------------------------------------------------------------------------- /cities/util.py: -------------------------------------------------------------------------------- 1 | import re 2 | import six 3 | import sys 4 | import unicodedata 5 | from math import radians, sin, cos, acos 6 | from django import VERSION as DJANGO_VERSION 7 | 8 | if DJANGO_VERSION < (4, 0): 9 | try: 10 | from django.utils.encoding import force_unicode as force_text 11 | except (NameError, ImportError): 12 | from django.utils.encoding import force_text 13 | else: 14 | from django.utils.encoding import force_str as force_text 15 | 16 | from django.utils.safestring import mark_safe, SafeText 17 | 18 | from .conf import CONTINENT_DATA 19 | 20 | 21 | if sys.version_info < (3, 0): 22 | unicode_func = unicode # noqa: F821 23 | else: 24 | unicode_func = str 25 | 26 | 27 | # GEO DISTANCE 28 | 29 | earth_radius_km = 6371.009 30 | 31 | 32 | def geo_distance(a, b): 33 | """Distance between two geo points in km. (p.x = long, p.y = lat)""" 34 | a_y = radians(a.y) 35 | b_y = radians(b.y) 36 | delta_x = radians(a.x - b.x) 37 | cos_x = (sin(a_y) * sin(b_y) + 38 | cos(a_y) * cos(b_y) * cos(delta_x)) 39 | return acos(cos_x) * earth_radius_km 40 | 41 | 42 | # ADD CONTINENTS FUNCTION 43 | 44 | def add_continents(continent_model): 45 | for ccode, cdata in CONTINENT_DATA.items(): 46 | try: 47 | c = continent_model.objects.get(code=ccode) 48 | except continent_model.DoesNotExist: 49 | c = continent_model() 50 | c.id = cdata[1] 51 | c.name = cdata[0] 52 | c.code = ccode 53 | c.slug = c.name 54 | c.save() 55 | 56 | 57 | # SLUGIFY REGEXES 58 | 59 | to_und_rgx = re.compile(r"[']", re.UNICODE) 60 | slugify_rgx = re.compile(r'[^-\w._~]', re.UNICODE) 61 | multi_dash_rgx = re.compile(r'-{2,}', re.UNICODE) 62 | dash_und_rgx = re.compile(r'[-_]_', re.UNICODE) 63 | und_dash_rgx = re.compile(r'[-_]-', re.UNICODE) 64 | starting_chars_rgx = re.compile(r'^[-._]*', re.UNICODE) 65 | ending_chars_rgx = re.compile(r'[-._]*$', re.UNICODE) 66 | 67 | 68 | def default_slugify(obj, value): 69 | if value is None: 70 | return None 71 | 72 | value = force_text(unicode_func(value)) 73 | value = unicodedata.normalize('NFKC', value.strip()) 74 | value = re.sub(to_und_rgx, '_', value) 75 | value = re.sub(slugify_rgx, '-', value) 76 | value = re.sub(multi_dash_rgx, '-', value) 77 | value = re.sub(dash_und_rgx, '_', value) 78 | value = re.sub(und_dash_rgx, '_', value) 79 | value = re.sub(starting_chars_rgx, '', value) 80 | value = re.sub(ending_chars_rgx, '', value) 81 | return mark_safe(value) 82 | 83 | 84 | if DJANGO_VERSION < (1, 10): 85 | from django.utils.functional import allow_lazy 86 | default_slugify = allow_lazy(default_slugify, six.text_type, SafeText) 87 | else: 88 | from django.utils.functional import keep_lazy 89 | default_slugify = keep_lazy(six.text_type, SafeText)(default_slugify) 90 | 91 | 92 | # DJANGO BACKWARDS-COMPATIBLE PATTERNS 93 | 94 | def patterns(prefix, *args): 95 | if DJANGO_VERSION < (1, 9): 96 | from django.conf.urls import patterns as django_patterns 97 | return django_patterns(prefix, *args) 98 | elif prefix != '': 99 | raise Exception("You need to update your URLConf to be a list of URL " 100 | "objects") 101 | else: 102 | return list(args) 103 | -------------------------------------------------------------------------------- /example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/example/__init__.py -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /example/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import django 3 | 4 | 5 | def rel(path): 6 | return os.path.join(os.path.abspath(os.path.dirname(__file__)), path) 7 | 8 | 9 | DEBUG = True 10 | TEMPLATE_DEBUG = DEBUG 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.contrib.gis.db.backends.postgis', 15 | 'HOST': 'localhost', 16 | 'NAME': '', 17 | 'USER': '', 18 | 'PASSWORD': '', 19 | 'OPTIONS': { 20 | 'autocommit': True, 21 | } 22 | } 23 | } 24 | 25 | TEMPLATE_DIRS = (rel("templates"),) 26 | TIME_ZONE = 'UTC' 27 | LANGUAGE_CODE = 'en-us' 28 | 29 | SITE_ID = 1 30 | 31 | SECRET_KEY = 'YOUR_SECRET_KEY' 32 | 33 | ROOT_URLCONF = 'urls' 34 | 35 | INSTALLED_APPS = ( 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.messages', 39 | 'django.contrib.sessions', 40 | 'django.contrib.admin', 41 | 'django.contrib.gis', 42 | 'cities', 43 | ) 44 | 45 | if django.VERSION < (1, 7): 46 | INSTALLED_APPS += ( 47 | 'south', 48 | ) 49 | 50 | CITIES_POSTAL_CODES = ['ALL'] 51 | CITIES_LOCALES = ['ALL'] 52 | 53 | CITIES_PLUGINS = [ 54 | 'cities.plugin.postal_code_ca.Plugin', # Canada postal codes need region codes remapped to match geonames 55 | ] 56 | 57 | LOGGING = { 58 | 'version': 1, 59 | 'disable_existing_loggers': False, 60 | 'formatters': { 61 | 'simple': { 62 | 'format': '%(levelname)s %(message)s' 63 | }, 64 | }, 65 | 'handlers': { 66 | 'log_to_stdout': { 67 | 'level': 'DEBUG', 68 | 'class': 'logging.StreamHandler', 69 | 'formatter': 'simple', 70 | }, 71 | }, 72 | 'loggers': { 73 | 'cities': { 74 | 'handlers': ['log_to_stdout'], 75 | 'level': 'INFO', 76 | 'propagate': True, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/templates/base.html: -------------------------------------------------------------------------------- 1 | {% block content %} 2 | {% endblock %} 3 | -------------------------------------------------------------------------------- /example/templates/countries.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Countries

4 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /example/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {% if place %} 4 | Places » 5 | {% endif %} 6 | {% for object in place.hierarchy %} 7 | {% if not forloop.last %} 8 | {{ object.name }} » 9 | {% else %} 10 | {{ object.name }} 11 | {% endif %} 12 | {% endfor %} 13 | 18 | 19 | {% if place.location %} 20 | 21 | {% endif %} 22 | {% if nearby %} 23 |

Nearby Places

24 | {% for near in nearby %} 25 |

{{ near.name }} ({{ near.distance.mi }} miles)

26 | {% endfor %} 27 | {% endif %} 28 | {% if postal %} 29 |

Postal Codes

30 | {% for near in postal %} 31 |

{{ near.code }} {{ near.name }} ({{ near.distance.mi }} miles)

32 | {% endfor %} 33 | {% endif %} 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /example/urls.py: -------------------------------------------------------------------------------- 1 | from django import VERSION as DJANGO_VERSION 2 | try: 3 | from django.conf.urls import url 4 | except ImportError: 5 | from django.urls import re_path as url 6 | from django.conf.urls import include 7 | from django.contrib import admin 8 | from django.views.generic import ListView 9 | from cities.models import (Country, Region, City, District, PostalCode) 10 | 11 | 12 | def patterns(prefix, *args): 13 | if DJANGO_VERSION < (1, 9): 14 | from django.conf.urls import patterns as django_patterns 15 | return django_patterns(prefix, *args) 16 | elif prefix != '': 17 | raise Exception("You need to update your URLConf to be a list of URL " 18 | "objects") 19 | else: 20 | return list(args) 21 | 22 | 23 | class PlaceListView(ListView): 24 | template_name = "list.html" 25 | 26 | def get_queryset(self): 27 | if not self.args or not self.args[0]: 28 | self.place = None 29 | return Country.objects.all() 30 | args = self.args[0].split("/") 31 | 32 | country = Country.objects.get(slug=args[0]) 33 | if len(args) == 1: 34 | self.place = country 35 | return Region.objects.filter(country=country).order_by('name') 36 | 37 | region = Region.objects.get(country=country, slug=args[1]) 38 | if len(args) == 2: 39 | self.place = region 40 | return City.objects.filter(region=region).order_by('name') 41 | 42 | city = City.objects.get(region=region, slug=args[2]) 43 | self.place = city 44 | return District.objects.filter(city=city).order_by('name') 45 | 46 | def get_context_data(self, **kwargs): 47 | context = super(PlaceListView, self).get_context_data(**kwargs) 48 | context['place'] = self.place 49 | 50 | if hasattr(self.place, 'location'): 51 | context['nearby'] = City.objects.distance(self.place.location).exclude(id=self.place.id).order_by('distance')[:10] 52 | context['postal'] = PostalCode.objects.distance(self.place.location).order_by('distance')[:10] 53 | return context 54 | 55 | 56 | admin.autodiscover() 57 | 58 | urlpatterns = patterns( 59 | '', 60 | url(r'^admin/', include(admin.site.urls)), 61 | url(r'^(.*)$', PlaceListView.as_view()), 62 | ) 63 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = W504,E501 3 | exclude = build,dist 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import codecs 3 | import os 4 | 5 | 6 | def read(fname): 7 | """ 8 | Utility function to read the README file. 9 | Used for the long_description. It's nice, because now (1) we have a top level 10 | README file and (2) it's easier to type in the README file than to put a raw 11 | string in below ... 12 | """ 13 | return codecs.open(os.path.join(os.path.dirname(__file__), fname), encoding='utf-8').read() 14 | 15 | 16 | setup( 17 | name='django-cities', 18 | version='0.6.2', 19 | description='Place models and worldwide place data for Django', 20 | author='Ben Dowling', 21 | author_email='ben.m.dowling@gmail.com', 22 | url='https://github.com/coderholic/django-cities', 23 | packages=find_packages(exclude=['example']), 24 | install_requires=[ 25 | 'django-model-utils', 26 | 'six', 27 | 'swapper', 28 | 'tqdm', 29 | ], 30 | python_requires='>=3.6', 31 | include_package_data=True, 32 | zip_safe=False, 33 | long_description=read('README.md'), 34 | long_description_content_type="text/markdown", 35 | tests_require=[ 36 | 'flake8', 37 | 'psycopg2', 38 | 'tox', 39 | ], 40 | license="MIT", 41 | keywords="django cities countries regions postal codes geonames", 42 | classifiers=[ 43 | "Development Status :: 4 - Beta", 44 | "Environment :: Web Environment", 45 | "Framework :: Django", 46 | "Intended Audience :: Developers", 47 | "License :: OSI Approved :: MIT License", 48 | "Operating System :: OS Independent", 49 | "Programming Language :: Python", 50 | "Programming Language :: Python :: 3.6", 51 | "Programming Language :: Python :: 3.7", 52 | "Programming Language :: Python :: 3.8", 53 | "Programming Language :: Python :: 3.9", 54 | "Programming Language :: Python :: 3.10", 55 | "Programming Language :: Python :: 3.11", 56 | "Programming Language :: Python :: 3.12", 57 | "Programming Language :: Python :: 3.13", 58 | "Topic :: Internet :: WWW/HTTP", 59 | "Topic :: Software Development :: Libraries :: Python Modules", 60 | ], 61 | ) 62 | -------------------------------------------------------------------------------- /test_project/data/UA.txt: -------------------------------------------------------------------------------- 1 | # truncated version of http://download.geonames.org/export/dump/UA.zip 2 | # please read http://www.geonames.org/export/zip/readme.txt for license info 3 | 697796 Pechersk Raion Pechersk Raion Pechers'kij rajon,Pechersk,Pechersk Raion,Pecherskij rajon,Pecherskiy Rayon,Pechersky,Печерский район,Печерський район 50.41902 30.56602 A ADM2 UA 12 697796 131127 168 Europe/Kiev 2014-02-01 4 | 703448 Kiev Kiev Chijv,Civ,Cív,Gorad Kieu,IEV,Kaenugardur,Keju,Kiebo,Kief,Kieu,Kiev,Kiev osh,Kievi,Kievo,Kiew,Kiiev,Kiiv,Kijev,Kijeva,Kijevas,Kijew,Kijow,Kijuw,Kijv,Kijów,Kijůw,Kiova,Kiovia,Kiyev,Kiyiw,Kiëf,Kjiv,Kueyiv,Kyev,Kyiiv,Kyiv,Kyjev,Kyjiv,Kyjiw,Kyèv,Kænugarður,Kíev,Kîev,Küyiv,ji fu,kheiyf,kiefu,kiv,kiva,kiyebha,kiyepeu,kyf,kyiva,kyyf,qyyb,Κίεβο,Горад Кіеў,Кейӳ,Киев,Киев ош,Київ,Кијев,Кыив,Кыйив,Кꙑѥвъ,Կիև,קייב,קיעוו,كىيېۋ,كييف,کیف,کیێڤ,کی‌یف,कीव,क्यीव,কিয়েভ,கீவ்,കീവ്,เคียฟ,ཀིབ།,ကီးယက်မြို့,კიევი,ኪየቭ,キエフ,基輔,키예프 50.45466 30.5238 P PPLC UA 12 2797553 187 Europe/Kiev 2013-07-08 5 | 710734 Chernihivs’ka Oblast’ Chernihivs'ka Oblast' Chernigivs'ka Oblast',Chernigivs'ka oblast',Chernigov Oblast,Chernigovskaja oblast',Chernigovskaya Oblast',Chernigovskaya Oblast’,Chernihiv,Chernihivs'ka Oblast',Chernihivs’ka Oblast’,Черниговская область,Чернігівська Область,Чернігівська область 51.33333 32 A ADM1 UA 02 1156609 112 Europe/Kiev 2015-02-05 6 | 710735 Chernihiv Chernihiv Cernigau,Cernigiv,Cernigivo,Cernigovas,Cernigău,Cernihiv,Cernihivo,Cernihiw,Cernihov,Cerniqov,Chernigiv,Chernigov,Chernihiv,Chernígov,Csernyihiv,Czernihovia,Czernihow,Czernihów,Gorad Charnigau,Tchernihiv,Tjernihiv,Tschernigow,Tschernihiw,Tsernigiv,Tsernihiv,Tsjernihiv,Txernihiv,Txerníhiv,Tšernigiv,Tšernihiv,cheleunihiu,chernigovi,cherunihiu,chrnyhyf,qie er ni ge fu,tshrnyhyf,z'rnyhyb,Çernigiv,Çerniqov,Ĉernigivo,Ĉernihivo,Černigiv,Černigovas,Černihiv,Černihiw,Černihov,Горад Чарнігаў,Чернигов,Чернігів,Чернїгів,Չեռնիգով,צ'רניהיב,تشرنيهيف,چرنیهیف,چرنیہیف,ჩერნიგოვი,チェルニーヒウ,切尔尼戈夫,체르니히우 51.50551 31.28487 P PPLA UA 02 307684 127 Europe/Kiev 2013-05-12 7 | 690791 Ukraine Ukraine 'Iukuleini,An Ucrain,An Úcráin,Ikerene,Ikrɛni,Lukrayaen,Lukrayän,Oekraine,Oekraïne,Okraina,Orileede Ukarini,Orílẹ́ède Ukarini,Oucrinne,Oukrania,Owkraina,U-crai-na,U-crai-na (Ukraine),Ucraegna,Ucraina,Ucraina - Ukraina,Ucrania,Ucrayena,Ucraína,Ucraína - Україна,Ucraïna,Ucràina,Ucrânia,Ucrægna,Ukereen,Ukraina,Ukraine,Ukraine nutome,Ukraini,Ukrainia,Ukrainian Soviet Socialist Republic,Ukrainio,Ukrainskaya Sovetskaya Sotsialisticheskaya Respublika,Ukrainë,Ukrajina,Ukrajna,Ukrajno,Ukrania,Ukranya,Ukrayina,Ukrayn,Ukrayna,Ukraîne,Ukreina,Ukren,Ukreni,Ukrêni,Ukɛrɛni,Wcrain,Wcráin,Yukaran,Yukreini,Yukurayine,awkranya,awkrayn,i'ukre'ina,i'ukrena,i-Ukraine,prathes yukhern,ukeulaina,ukrain,ukrena,ukuraina,ukuraina gong he guo,wu ke lan,yukhern,yukra'in,yukren,yukrena,ywkrayn,ywkryn,Úkraína,ʻIukuleini,Ουκρανία,Украина,Украйна,Украіна,Україна,Украјина,Ѹкраина,Ուկրաինա,אוקראינה,אוקריינע,אוקרײַנע,أوكرانيا,ئۆکرانیا,ئۇكرائىنا,اوكرانيا,اوکراين,اوکراین,یوکرائن,یوکرین,ܐܘܟܪܢܝܐ,उक्रेन,युक्रेन,यूक्रेन,ইউক্রেইন,ইউক্রেন,યૂક્રેન,ୟୁକ୍ରାଇନ୍,உக்ரைன்,యుక్రెన్,ಉಕ್ರೈನ್,ഉക്രൈന്‍,യുക്രെയിന്‍,යුක්රේනය,ประเทศยูเครน,ยูเครน,ຢູເຄຼນ,ཡུ་ཀྲན།,ယူကရိန်း,უკრაინა,ዩክሬን,ᏳᎬᎳᎢᏅ,អ៊ុយក្រែន,ウクライナ,ウクライナ共和国,乌克兰,우크라이나 49 32 A PCLI UA 00 45415596 147 2013-05-24 -------------------------------------------------------------------------------- /test_project/data/admin1CodesASCII.txt: -------------------------------------------------------------------------------- 1 | AD.06 Sant Julià de Loria Sant Julia de Loria 3039162 2 | AD.05 Ordino Ordino 3039676 3 | AD.04 La Massana La Massana 3040131 4 | AD.03 Encamp Encamp 3040684 5 | AD.02 Canillo Canillo 3041203 6 | AD.07 Andorra la Vella Andorra la Vella 3041566 7 | AD.08 Escaldes-Engordany Escaldes-Engordany 3338529 8 | AO.12 Malanje Malanje 2239858 9 | ES.51 Andalusia Andalusia 2593109 10 | IE.L Leinster Leinster 7521314 11 | RU.88 Jaroslavl Jaroslavl 468898 12 | RU.86 Voronezj Voronezj 472039 13 | RU.85 Vologda Vologda 472454 14 | RU.84 Volgograd Volgograd 472755 15 | RU.81 Ulyanovsk Ulyanovsk 479119 16 | RU.80 Udmurtiya Udmurtiya 479613 17 | RU.77 Tverskaya Tverskaya 480041 18 | RU.76 Tula Tula 480508 19 | RU.73 Tatarstan Tatarstan 484048 20 | RU.72 Tambov Tambov 484638 21 | RU.70 Stavropol'skiy Stavropol'skiy 487839 22 | RU.69 Smolensk Smolensk 491684 23 | RU.67 Saratov Saratov 498671 24 | RU.65 Samara Samara 499068 25 | RU.62 Rjazan Rjazan 500059 26 | RU.61 Rostov Rostov 501165 27 | RU.60 Pskov Pskov 504338 28 | RU.90 Perm Perm 511180 29 | RU.57 Penza Penza 511555 30 | RU.56 Orjol Orjol 514801 31 | RU.55 Orenburg Orenburg 515001 32 | RU.52 Novgorod Novgorod 519324 33 | RU.68 North Ossetia North Ossetia 519969 34 | RU.50 Nenetskiy Avtonomnyy Okrug Nenetskiy Avtonomnyy Okrug 522652 35 | RU.49 Murmansk Murmansk 524304 36 | RU.48 Moscow Moscow 524894 37 | RU.47 Moscow Oblast Moscow Oblast 524925 38 | RU.46 Mordoviya Mordoviya 525369 39 | RU.45 Mariy-El Mariy-El 529352 40 | RU.43 Lipetsk Lipetsk 535120 41 | RU.42 Leningradskaya Oblast' Leningradskaya Oblast' 536199 42 | RU.66 St.-Petersburg St.-Petersburg 536203 43 | RU.41 Kursk Kursk 538555 44 | RU.38 Krasnodarskiy Krasnodarskiy 542415 45 | RU.37 Kostroma Kostroma 543871 46 | RU.34 Komi Komi 545854 47 | RU.33 Kirov Kirov 548389 48 | RU.28 Karelia Karelia 552548 49 | RU.27 Karachayevo-Cherkesiya Karachayevo-Cherkesiya 552927 50 | RU.25 Kaluga Kaluga 553899 51 | RU.24 Kalmykiya Kalmykiya 553972 52 | RU.23 Kaliningrad Kaliningrad 554230 53 | RU.22 Kabardino-Balkariya Kabardino-Balkariya 554667 54 | RU.21 Ivanovo Ivanovo 555235 55 | RU.19 Ingushetiya Ingushetiya 556349 56 | RU.51 Nizjnij Novgorod Nizjnij Novgorod 559838 57 | RU.17 Dagestan Dagestan 567293 58 | RU.16 Chuvashia Chuvashia 567395 59 | RU.12 Chechnya Chechnya 569665 60 | RU.10 Brjansk Brjansk 571473 61 | RU.09 Belgorod Belgorod 578071 62 | RU.08 Bashkortostan Bashkortostan 578853 63 | RU.07 Astrakhan Astrakhan 580491 64 | RU.06 Arkhangelskaya Arkhangelskaya 581043 65 | RU.01 Adygeya Adygeya 584222 66 | RU.83 Vladimir Vladimir 826294 67 | RU.87 Yamalo-Nenets Yamalo-Nenets 1486462 68 | RU.78 Tyumenskaya Tyumenskaya 1488747 69 | RU.79 Tyva Tyva 1488873 70 | RU.75 Tomsk Tomsk 1489421 71 | RU.71 Sverdlovsk Sverdlovsk 1490542 72 | RU.54 Omsk Omsk 1496152 73 | RU.53 Novosibirsk Novosibirsk 1496745 74 | RU.40 Kurgan Kurgan 1501312 75 | RU.91 Krasnoyarskiy Krasnoyarskiy 1502020 76 | RU.32 Khanty-Mansia Khanty-Mansia 1503773 77 | RU.31 Khakasiya Khakasiya 1503834 78 | RU.29 Kemerovo Kemerovo 1503900 79 | RU.03 Altai Altai 1506272 80 | RU.13 Chelyabinsk Chelyabinsk 1508290 81 | RU.04 Altai Krai Altai Krai 1511732 82 | RU.63 Chukot Chukot 2013162 83 | RU.59 Primorskiy Primorskiy 2017623 84 | RU.30 Khabarovsk Khabarovsk 2022888 85 | RU.20 Irkutsk Irkutsk 2023468 86 | RU.89 Jewish Autonomous Oblast Jewish Autonomous Oblast 2026639 87 | RU.05 Amur Amur 2027748 88 | RU.11 Respublika Buryatiya Respublika Buryatiya 2050915 89 | RU.64 Sakhalin Sakhalin 2121529 90 | RU.44 Magadan Magadan 2123627 91 | RU.92 Kamtsjatka Kamtsjatka 2125072 92 | RU.15 Chukotka Chukotka 2126099 93 | RU.93 Transbaikal Territory Transbaikal Territory 7779061 94 | UA.27 Zhytomyr Zhytomyr 686966 95 | UA.26 Zaporizhia Zaporizhia 687699 96 | UA.25 Transcarpathia Transcarpathia 687869 97 | UA.24 Volyn Volyn 689064 98 | UA.23 Vinnyts'ka Vinnyts'ka 689559 99 | UA.22 Ternopil Ternopil 691649 100 | UA.21 Sumy Sumy 692196 101 | UA.20 Gorod Sevastopol Gorod Sevastopol 694422 102 | UA.19 Rivne Rivne 695592 103 | UA.18 Poltava Poltava 696634 104 | UA.17 Odessa Odessa 698738 105 | UA.16 Mykolaiv Mykolaiv 700567 106 | UA.15 Lviv Lviv 702549 107 | UA.14 Luhansk Luhansk 702657 108 | UA.13 Kiev Kiev 703446 109 | UA.12 Kyiv City Kyiv City 703447 110 | UA.11 Republic of Crimea Republic of Crimea 703883 111 | UA.10 Kirovohrad Kirovohrad 705811 112 | UA.09 Khmelnytskyi Khmelnytskyi 706370 113 | UA.08 Kherson Kherson 706442 114 | UA.07 Kharkiv Kharkiv 706482 115 | UA.06 Ivano-Frankivsk Ivano-Frankivsk 707470 116 | UA.05 Donetsk Donetsk 709716 117 | UA.04 Dnipropetrovsk Dnipropetrovsk 709929 118 | UA.03 Chernivtsi Chernivtsi 710720 119 | UA.02 Chernihiv Chernihiv 710734 120 | UA.01 Cherkasy Cherkasy 710802 121 | US.AR Arkansas Arkansas 4099753 122 | US.DC Washington, D.C. Washington, D.C. 4138106 123 | US.DE Delaware Delaware 4142224 124 | US.FL Florida Florida 4155751 125 | US.GA Georgia Georgia 4197000 126 | US.KS Kansas Kansas 4273857 127 | US.LA Louisiana Louisiana 4331987 128 | US.MD Maryland Maryland 4361885 129 | US.MO Missouri Missouri 4398678 130 | US.MS Mississippi Mississippi 4436296 131 | US.NC North Carolina North Carolina 4482348 132 | US.OK Oklahoma Oklahoma 4544379 133 | US.SC South Carolina South Carolina 4597040 134 | US.TN Tennessee Tennessee 4662168 135 | US.TX Texas Texas 4736286 136 | US.WV West Virginia West Virginia 4826850 137 | US.AL Alabama Alabama 4829764 138 | US.CT Connecticut Connecticut 4831725 139 | US.IA Iowa Iowa 4862182 140 | US.IL Illinois Illinois 4896861 141 | US.IN Indiana Indiana 4921868 142 | US.ME Maine Maine 4971068 143 | US.MI Michigan Michigan 5001836 144 | US.MN Minnesota Minnesota 5037779 145 | US.NE Nebraska Nebraska 5073708 146 | US.NH New Hampshire New Hampshire 5090174 147 | US.NJ New Jersey New Jersey 5101760 148 | US.NY New York New York 5128638 149 | US.OH Ohio Ohio 5165418 150 | US.RI Rhode Island Rhode Island 5224323 151 | US.VT Vermont Vermont 5242283 152 | US.WI Wisconsin Wisconsin 5279468 153 | US.CA California California 5332921 154 | US.CO Colorado Colorado 5417618 155 | US.NM New Mexico New Mexico 5481136 156 | US.NV Nevada Nevada 5509151 157 | US.UT Utah Utah 5549030 158 | US.AZ Arizona Arizona 5551752 159 | US.ID Idaho Idaho 5596512 160 | US.MT Montana Montana 5667009 161 | US.ND North Dakota North Dakota 5690763 162 | US.OR Oregon Oregon 5744337 163 | US.SD South Dakota South Dakota 5769223 164 | US.WA Washington Washington 5815135 165 | US.WY Wyoming Wyoming 5843591 166 | US.HI Hawaii Hawaii 5855797 167 | US.AK Alaska Alaska 5879092 168 | US.KY Kentucky Kentucky 6254925 169 | US.MA Massachusetts Massachusetts 6254926 170 | US.PA Pennsylvania Pennsylvania 6254927 171 | US.VA Virginia Virginia 6254928 172 | -------------------------------------------------------------------------------- /test_project/data/allCountries.txt: -------------------------------------------------------------------------------- 1 | # These postal codes need different slugs 2 | ES 04001 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 3 | ES 04002 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 4 | ES 04003 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 5 | ES 04004 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 6 | ES 04005 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 7 | ES 04006 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 8 | ES 04007 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 9 | ES 04008 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 10 | ES 04009 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 11 | ES 04070 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 12 | ES 04071 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 13 | ES 04080 Almeria Andalucia AN Almería AL 36.8381 -2.4597 4 14 | # This postal code creates a slug that is > 255 characters long 15 | RU 102104 Москва-Ярославский Вокзал Пждп Цех 3 Уч 3.1 Москва 55.4656 51.2614 1 16 | -------------------------------------------------------------------------------- /test_project/data/countryInfo.txt: -------------------------------------------------------------------------------- 1 | # GeoNames.org Country Information 2 | # ================================ 3 | # 4 | # 5 | # CountryCodes: 6 | # ============ 7 | # 8 | # The official ISO country code for the United Kingdom is 'GB'. The code 'UK' is reserved. 9 | # 10 | # A list of dependent countries is available here: 11 | # https://spreadsheets.google.com/ccc?key=pJpyPy-J5JSNhe7F_KxwiCA&hl=en 12 | # 13 | # 14 | # The countrycode XK temporarily stands for Kosvo: 15 | # http://geonames.wordpress.com/2010/03/08/xk-country-code-for-kosovo/ 16 | # 17 | # 18 | # CS (Serbia and Montenegro) with geonameId = 8505033 no longer exists. 19 | # AN (the Netherlands Antilles) with geonameId = 8505032 was dissolved on 10 October 2010. 20 | # 21 | # 22 | # Currencies : 23 | # ============ 24 | # 25 | # A number of territories are not included in ISO 4217, because their currencies are not per se an independent currency, 26 | # but a variant of another currency. These currencies are: 27 | # 28 | # 1. FO : Faroese krona (1:1 pegged to the Danish krone) 29 | # 2. GG : Guernsey pound (1:1 pegged to the pound sterling) 30 | # 3. JE : Jersey pound (1:1 pegged to the pound sterling) 31 | # 4. IM : Isle of Man pound (1:1 pegged to the pound sterling) 32 | # 5. TV : Tuvaluan dollar (1:1 pegged to the Australian dollar). 33 | # 6. CK : Cook Islands dollar (1:1 pegged to the New Zealand dollar). 34 | # 35 | # The following non-ISO codes are, however, sometimes used: GGP for the Guernsey pound, 36 | # JEP for the Jersey pound and IMP for the Isle of Man pound (http://en.wikipedia.org/wiki/ISO_4217) 37 | # 38 | # 39 | # A list of currency symbols is available here : http://forum.geonames.org/gforum/posts/list/437.page 40 | # another list with fractional units is here: http://forum.geonames.org/gforum/posts/list/1961.page 41 | # 42 | # 43 | # Languages : 44 | # =========== 45 | # 46 | # The column 'languages' lists the languages spoken in a country ordered by the number of speakers. The language code is a 'locale' 47 | # where any two-letter primary-tag is an ISO-639 language abbreviation and any two-letter initial subtag is an ISO-3166 country code. 48 | # 49 | # Example : es-AR is the Spanish variant spoken in Argentina. 50 | # 51 | #ISO ISO3 ISO-Numeric fips Country Capital Area(in sq km) Population Continent tld CurrencyCode CurrencyName Phone Postal Code Format Postal Code Regex Languages geonameid neighbours EquivalentFipsCode 52 | AD AND 020 AN Andorra Andorra la Vella 468 84000 EU .ad EUR Euro 376 AD### ^(?:AD)*(\d{3})$ ca 3041565 ES,FR 53 | AE ARE 784 AE United Arab Emirates Abu Dhabi 82880 4975593 AS .ae AED Dirham 971 ar-AE,fa,en,hi,ur 290557 SA,OM 54 | AF AFG 004 AF Afghanistan Kabul 647500 29121286 AS .af AFN Afghani 93 fa-AF,ps,uz-AF,tk 1149361 TM,CN,IR,TJ,PK,UZ 55 | AG ATG 028 AC Antigua and Barbuda St. John's 443 86754 NA .ag XCD Dollar +1-268 en-AG 3576396 56 | AI AIA 660 AV Anguilla The Valley 102 13254 NA .ai XCD Dollar +1-264 en-AI 3573511 57 | AL ALB 008 AL Albania Tirana 28748 2986952 EU .al ALL Lek 355 sq,el 783754 MK,GR,ME,RS,XK 58 | AM ARM 051 AM Armenia Yerevan 29800 2968000 AS .am AMD Dram 374 ###### ^(\d{6})$ hy 174982 GE,IR,AZ,TR 59 | AO AGO 024 AO Angola Luanda 1246700 13068161 AF .ao AOA Kwanza 244 pt-AO 3351879 CD,NA,ZM,CG 60 | AQ ATA 010 AY Antarctica 14000000 0 AN .aq 6697173 61 | AR ARG 032 AR Argentina Buenos Aires 2766890 41343201 SA .ar ARS Peso 54 @####@@@ ^[A-Z]?\d{4}[A-Z]{0,3}$ es-AR,en,it,de,fr,gn 3865483 CL,BO,UY,PY,BR 62 | AS ASM 016 AQ American Samoa Pago Pago 199 57881 OC .as USD Dollar +1-684 #####-#### 96799 en-AS,sm,to 5880801 63 | AT AUT 040 AU Austria Vienna 83858 8205000 EU .at EUR Euro 43 #### ^(\d{4})$ de-AT,hr,hu,sl 2782113 CH,DE,HU,SK,CZ,IT,SI,LI 64 | AU AUS 036 AS Australia Canberra 7686850 21515754 OC .au AUD Dollar 61 #### ^(\d{4})$ en-AU 2077456 65 | AW ABW 533 AA Aruba Oranjestad 193 71566 NA .aw AWG Guilder 297 nl-AW,es,en 3577279 66 | AX ALA 248 Aland Islands Mariehamn 1580 26711 EU .ax EUR Euro +358-18 ##### ^(?:FI)*(\d{5})$ sv-AX 661882 FI 67 | AZ AZE 031 AJ Azerbaijan Baku 86600 8303512 AS .az AZN Manat 994 AZ #### ^(?:AZ)*(\d{4})$ az,ru,hy 587116 GE,IR,AM,TR,RU 68 | BA BIH 070 BK Bosnia and Herzegovina Sarajevo 51129 4590000 EU .ba BAM Marka 387 ##### ^(\d{5})$ bs,hr-BA,sr-BA 3277605 HR,ME,RS 69 | BB BRB 052 BB Barbados Bridgetown 431 285653 NA .bb BBD Dollar +1-246 BB##### ^(?:BB)*(\d{5})$ en-BB 3374084 70 | BD BGD 050 BG Bangladesh Dhaka 144000 156118464 AS .bd BDT Taka 880 #### ^(\d{4})$ bn-BD,en 1210997 MM,IN 71 | BE BEL 056 BE Belgium Brussels 30510 10403000 EU .be EUR Euro 32 #### ^(\d{4})$ nl-BE,fr-BE,de-BE 2802361 DE,NL,LU,FR 72 | BF BFA 854 UV Burkina Faso Ouagadougou 274200 16241811 AF .bf XOF Franc 226 fr-BF 2361809 NE,BJ,GH,CI,TG,ML 73 | BG BGR 100 BU Bulgaria Sofia 110910 7148785 EU .bg BGN Lev 359 #### ^(\d{4})$ bg,tr-BG,rom 732800 MK,GR,RO,TR,RS 74 | BH BHR 048 BA Bahrain Manama 665 738004 AS .bh BHD Dinar 973 ####|### ^(\d{3}\d?)$ ar-BH,en,fa,ur 290291 75 | BI BDI 108 BY Burundi Bujumbura 27830 9863117 AF .bi BIF Franc 257 fr-BI,rn 433561 TZ,CD,RW 76 | BJ BEN 204 BN Benin Porto-Novo 112620 9056010 AF .bj XOF Franc 229 fr-BJ 2395170 NE,TG,BF,NG 77 | BL BLM 652 TB Saint Barthelemy Gustavia 21 8450 NA .gp EUR Euro 590 ### ### fr 3578476 78 | BM BMU 060 BD Bermuda Hamilton 53 65365 NA .bm BMD Dollar +1-441 @@ ## ^([A-Z]{2}\d{2})$ en-BM,pt 3573345 79 | BN BRN 096 BX Brunei Bandar Seri Begawan 5770 395027 AS .bn BND Dollar 673 @@#### ^([A-Z]{2}\d{4})$ ms-BN,en-BN 1820814 MY 80 | BO BOL 068 BL Bolivia Sucre 1098580 9947418 SA .bo BOB Boliviano 591 es-BO,qu,ay 3923057 PE,CL,PY,BR,AR 81 | BQ BES 535 Bonaire, Saint Eustatius and Saba 328 18012 NA .bq USD Dollar 599 nl,pap,en 7626844 82 | BR BRA 076 BR Brazil Brasilia 8511965 201103330 SA .br BRL Real 55 #####-### ^\d{5}-\d{3}$ pt-BR,es,en,fr 3469034 SR,PE,BO,UY,GY,PY,GF,VE,CO,AR 83 | BS BHS 044 BF Bahamas Nassau 13940 301790 NA .bs BSD Dollar +1-242 en-BS 3572887 84 | BT BTN 064 BT Bhutan Thimphu 47000 699847 AS .bt BTN Ngultrum 975 dz 1252634 CN,IN 85 | BV BVT 074 BV Bouvet Island 49 0 AN .bv NOK Krone 3371123 86 | BW BWA 072 BC Botswana Gaborone 600370 2029307 AF .bw BWP Pula 267 en-BW,tn-BW 933860 ZW,ZA,NA 87 | BY BLR 112 BO Belarus Minsk 207600 9685000 EU .by BYR Ruble 375 ###### ^(\d{6})$ be,ru 630336 PL,LT,UA,RU,LV 88 | BZ BLZ 084 BH Belize Belmopan 22966 314522 NA .bz BZD Dollar 501 en-BZ,es 3582678 GT,MX 89 | CA CAN 124 CA Canada Ottawa 9984670 33679000 NA .ca CAD Dollar 1 @#@ #@# ^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ]) ?(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$ en-CA,fr-CA,iu 6251999 US 90 | CC CCK 166 CK Cocos Islands West Island 14 628 AS .cc AUD Dollar 61 ms-CC,en 1547376 91 | CD COD 180 CG Democratic Republic of the Congo Kinshasa 2345410 70916439 AF .cd CDF Franc 243 fr-CD,ln,kg 203312 TZ,CF,SS,RW,ZM,BI,UG,CG,AO 92 | CF CAF 140 CT Central African Republic Bangui 622984 4844927 AF .cf XAF Franc 236 fr-CF,sg,ln,kg 239880 TD,SD,CD,SS,CM,CG 93 | CG COG 178 CF Republic of the Congo Brazzaville 342000 3039126 AF .cg XAF Franc 242 fr-CG,kg,ln-CG 2260494 CF,GA,CD,CM,AO 94 | CH CHE 756 SZ Switzerland Bern 41290 7581000 EU .ch CHF Franc 41 #### ^(\d{4})$ de-CH,fr-CH,it-CH,rm 2658434 DE,IT,LI,FR,AT 95 | CI CIV 384 IV Ivory Coast Yamoussoukro 322460 21058798 AF .ci XOF Franc 225 fr-CI 2287781 LR,GH,GN,BF,ML 96 | CK COK 184 CW Cook Islands Avarua 240 21388 OC .ck NZD Dollar 682 en-CK,mi 1899402 97 | CL CHL 152 CI Chile Santiago 756950 16746491 SA .cl CLP Peso 56 ####### ^(\d{7})$ es-CL 3895114 PE,BO,AR 98 | CM CMR 120 CM Cameroon Yaounde 475440 19294149 AF .cm XAF Franc 237 en-CM,fr-CM 2233387 TD,CF,GA,GQ,CG,NG 99 | CN CHN 156 CH China Beijing 9596960 1330044000 AS .cn CNY Yuan Renminbi 86 ###### ^(\d{6})$ zh-CN,yue,wuu,dta,ug,za 1814991 LA,BT,TJ,KZ,MN,AF,NP,MM,KG,PK,KP,RU,VN,IN 100 | CO COL 170 CO Colombia Bogota 1138910 47790000 SA .co COP Peso 57 es-CO 3686110 EC,PE,PA,BR,VE 101 | CR CRI 188 CS Costa Rica San Jose 51100 4516220 NA .cr CRC Colon 506 ##### ^(\d{5})$ es-CR,en 3624060 PA,NI 102 | CU CUB 192 CU Cuba Havana 110860 11423000 NA .cu CUP Peso 53 CP ##### ^(?:CP)*(\d{5})$ es-CU 3562981 US 103 | CV CPV 132 CV Cape Verde Praia 4033 508659 AF .cv CVE Escudo 238 #### ^(\d{4})$ pt-CV 3374766 104 | CW CUW 531 UC Curacao Willemstad 444 141766 NA .cw ANG Guilder 599 nl,pap 7626836 105 | CX CXR 162 KT Christmas Island Flying Fish Cove 135 1500 AS .cx AUD Dollar 61 #### ^(\d{4})$ en,zh,ms-CC 2078138 106 | CY CYP 196 CY Cyprus Nicosia 9250 1102677 EU .cy EUR Euro 357 #### ^(\d{4})$ el-CY,tr-CY,en 146669 107 | CZ CZE 203 EZ Czechia Prague 78866 10476000 EU .cz CZK Koruna 420 ### ## ^\d{3}\s?\d{2}$ cs,sk 3077311 PL,DE,SK,AT 108 | DE DEU 276 GM Germany Berlin 357021 81802257 EU .de EUR Euro 49 ##### ^(\d{5})$ de 2921044 CH,PL,NL,DK,BE,CZ,LU,FR,AT 109 | DJ DJI 262 DJ Djibouti Djibouti 23000 740528 AF .dj DJF Franc 253 fr-DJ,ar,so-DJ,aa 223816 ER,ET,SO 110 | DK DNK 208 DA Denmark Copenhagen 43094 5484000 EU .dk DKK Krone 45 #### ^(\d{4})$ da-DK,en,fo,de-DK 2623032 DE 111 | DM DMA 212 DO Dominica Roseau 754 72813 NA .dm XCD Dollar +1-767 en-DM 3575830 112 | DO DOM 214 DR Dominican Republic Santo Domingo 48730 9823821 NA .do DOP Peso +1-809 and 1-829 ##### ^(\d{5})$ es-DO 3508796 HT 113 | DZ DZA 012 AG Algeria Algiers 2381740 34586184 AF .dz DZD Dinar 213 ##### ^(\d{5})$ ar-DZ 2589581 NE,EH,LY,MR,TN,MA,ML 114 | EC ECU 218 EC Ecuador Quito 283560 14790608 SA .ec USD Dollar 593 @####@ ^([a-zA-Z]\d{4}[a-zA-Z])$ es-EC 3658394 PE,CO 115 | EE EST 233 EN Estonia Tallinn 45226 1291170 EU .ee EUR Euro 372 ##### ^(\d{5})$ et,ru 453733 RU,LV 116 | EG EGY 818 EG Egypt Cairo 1001450 80471869 AF .eg EGP Pound 20 ##### ^(\d{5})$ ar-EG,en,fr 357994 LY,SD,IL,PS 117 | EH ESH 732 WI Western Sahara El-Aaiun 266000 273008 AF .eh MAD Dirham 212 ar,mey 2461445 DZ,MR,MA 118 | ER ERI 232 ER Eritrea Asmara 121320 5792984 AF .er ERN Nakfa 291 aa-ER,ar,tig,kun,ti-ER 338010 ET,SD,DJ 119 | ES ESP 724 SP Spain Madrid 504782 46505963 EU .es EUR Euro 34 ##### ^(\d{5})$ es-ES,ca,gl,eu,oc 2510769 AD,PT,GI,FR,MA 120 | ET ETH 231 ET Ethiopia Addis Ababa 1127127 88013491 AF .et ETB Birr 251 #### ^(\d{4})$ am,en-ET,om-ET,ti-ET,so-ET,sid 337996 ER,KE,SD,SS,SO,DJ 121 | FI FIN 246 FI Finland Helsinki 337030 5244000 EU .fi EUR Euro 358 ##### ^(?:FI)*(\d{5})$ fi-FI,sv-FI,smn 660013 NO,RU,SE 122 | FJ FJI 242 FJ Fiji Suva 18270 875983 OC .fj FJD Dollar 679 en-FJ,fj 2205218 123 | FK FLK 238 FK Falkland Islands Stanley 12173 2638 SA .fk FKP Pound 500 en-FK 3474414 124 | FM FSM 583 FM Micronesia Palikir 702 107708 OC .fm USD Dollar 691 ##### ^(\d{5})$ en-FM,chk,pon,yap,kos,uli,woe,nkr,kpg 2081918 125 | FO FRO 234 FO Faroe Islands Torshavn 1399 48228 EU .fo DKK Krone 298 ### ^(?:FO)*(\d{3})$ fo,da-FO 2622320 126 | FR FRA 250 FR France Paris 547030 64768389 EU .fr EUR Euro 33 ##### ^(\d{5})$ fr-FR,frp,br,co,ca,eu,oc 3017382 CH,DE,BE,LU,IT,AD,MC,ES 127 | GA GAB 266 GB Gabon Libreville 267667 1545255 AF .ga XAF Franc 241 fr-GA 2400553 CM,GQ,CG 128 | GB GBR 826 UK United Kingdom London 244820 62348447 EU .uk GBP Pound 44 @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA ^((?:(?:[A-PR-UWYZ][A-HK-Y]\d[ABEHMNPRV-Y0-9]|[A-PR-UWYZ]\d[A-HJKPS-UW0-9])\s\d[ABD-HJLNP-UW-Z]{2})|GIR\s?0AA)$ en-GB,cy-GB,gd 2635167 IE 129 | GD GRD 308 GJ Grenada St. George's 344 107818 NA .gd XCD Dollar +1-473 en-GD 3580239 130 | GE GEO 268 GG Georgia Tbilisi 69700 4630000 AS .ge GEL Lari 995 #### ^(\d{4})$ ka,ru,hy,az 614540 AM,AZ,TR,RU 131 | GF GUF 254 FG French Guiana Cayenne 91000 195506 SA .gf EUR Euro 594 ##### ^((97|98)3\d{2})$ fr-GF 3381670 SR,BR 132 | GG GGY 831 GK Guernsey St Peter Port 78 65228 EU .gg GBP Pound +44-1481 @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA ^((?:(?:[A-PR-UWYZ][A-HK-Y]\d[ABEHMNPRV-Y0-9]|[A-PR-UWYZ]\d[A-HJKPS-UW0-9])\s\d[ABD-HJLNP-UW-Z]{2})|GIR\s?0AA)$ en,fr 3042362 133 | GH GHA 288 GH Ghana Accra 239460 24339838 AF .gh GHS Cedi 233 en-GH,ak,ee,tw 2300660 CI,TG,BF 134 | GI GIB 292 GI Gibraltar Gibraltar 6.5 27884 EU .gi GIP Pound 350 en-GI,es,it,pt 2411586 ES 135 | GL GRL 304 GL Greenland Nuuk 2166086 56375 NA .gl DKK Krone 299 #### ^(\d{4})$ kl,da-GL,en 3425505 136 | GM GMB 270 GA Gambia Banjul 11300 1593256 AF .gm GMD Dalasi 220 en-GM,mnk,wof,wo,ff 2413451 SN 137 | GN GIN 324 GV Guinea Conakry 245857 10324025 AF .gn GNF Franc 224 fr-GN 2420477 LR,SN,SL,CI,GW,ML 138 | GP GLP 312 GP Guadeloupe Basse-Terre 1780 443000 NA .gp EUR Euro 590 ##### ^((97|98)\d{3})$ fr-GP 3579143 139 | GQ GNQ 226 EK Equatorial Guinea Malabo 28051 1014999 AF .gq XAF Franc 240 es-GQ,fr 2309096 GA,CM 140 | GR GRC 300 GR Greece Athens 131940 11000000 EU .gr EUR Euro 30 ### ## ^(\d{5})$ el-GR,en,fr 390903 AL,MK,TR,BG 141 | GS SGS 239 SX South Georgia and the South Sandwich Islands Grytviken 3903 30 AN .gs GBP Pound en 3474415 142 | GT GTM 320 GT Guatemala Guatemala City 108890 13550440 NA .gt GTQ Quetzal 502 ##### ^(\d{5})$ es-GT 3595528 MX,HN,BZ,SV 143 | GU GUM 316 GQ Guam Hagatna 549 159358 OC .gu USD Dollar +1-671 969## ^(969\d{2})$ en-GU,ch-GU 4043988 144 | GW GNB 624 PU Guinea-Bissau Bissau 36120 1565126 AF .gw XOF Franc 245 #### ^(\d{4})$ pt-GW,pov 2372248 SN,GN 145 | GY GUY 328 GY Guyana Georgetown 214970 748486 SA .gy GYD Dollar 592 en-GY 3378535 SR,BR,VE 146 | HK HKG 344 HK Hong Kong Hong Kong 1092 6898686 AS .hk HKD Dollar 852 zh-HK,yue,zh,en 1819730 147 | HM HMD 334 HM Heard Island and McDonald Islands 412 0 AN .hm AUD Dollar 1547314 148 | HN HND 340 HO Honduras Tegucigalpa 112090 7989415 NA .hn HNL Lempira 504 @@#### ^([A-Z]{2}\d{4})$ es-HN 3608932 GT,NI,SV 149 | HR HRV 191 HR Croatia Zagreb 56542 4284889 EU .hr HRK Kuna 385 ##### ^(?:HR)*(\d{5})$ hr-HR,sr 3202326 HU,SI,BA,ME,RS 150 | HT HTI 332 HA Haiti Port-au-Prince 27750 9648924 NA .ht HTG Gourde 509 HT#### ^(?:HT)*(\d{4})$ ht,fr-HT 3723988 DO 151 | HU HUN 348 HU Hungary Budapest 93030 9982000 EU .hu HUF Forint 36 #### ^(\d{4})$ hu-HU 719819 SK,SI,RO,UA,HR,AT,RS 152 | ID IDN 360 ID Indonesia Jakarta 1919440 242968342 AS .id IDR Rupiah 62 ##### ^(\d{5})$ id,en,nl,jv 1643084 PG,TL,MY 153 | IE IRL 372 EI Ireland Dublin 70280 4622917 EU .ie EUR Euro 353 @@@ @@@@ ^[A-Z]\d{2}$|^[A-Z]{3}[A-Z]{4}$ en-IE,ga-IE 2963597 GB 154 | IL ISR 376 IS Israel Jerusalem 20770 7353985 AS .il ILS Shekel 972 ##### ^(\d{5})$ he,ar-IL,en-IL, 294640 SY,JO,LB,EG,PS 155 | IM IMN 833 IM Isle of Man Douglas 572 75049 EU .im GBP Pound +44-1624 @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA ^((?:(?:[A-PR-UWYZ][A-HK-Y]\d[ABEHMNPRV-Y0-9]|[A-PR-UWYZ]\d[A-HJKPS-UW0-9])\s\d[ABD-HJLNP-UW-Z]{2})|GIR\s?0AA)$ en,gv 3042225 156 | IN IND 356 IN India New Delhi 3287590 1173108018 AS .in INR Rupee 91 ###### ^(\d{6})$ en-IN,hi,bn,te,mr,ta,ur,gu,kn,ml,or,pa,as,bh,sat,ks,ne,sd,kok,doi,mni,sit,sa,fr,lus,inc 1269750 CN,NP,MM,BT,PK,BD 157 | IO IOT 086 IO British Indian Ocean Territory Diego Garcia 60 4000 AS .io USD Dollar 246 en-IO 1282588 158 | IQ IRQ 368 IZ Iraq Baghdad 437072 29671605 AS .iq IQD Dinar 964 ##### ^(\d{5})$ ar-IQ,ku,hy 99237 SY,SA,IR,JO,TR,KW 159 | IR IRN 364 IR Iran Tehran 1648000 76923300 AS .ir IRR Rial 98 ########## ^(\d{10})$ fa-IR,ku 130758 TM,AF,IQ,AM,PK,AZ,TR 160 | IS ISL 352 IC Iceland Reykjavik 103000 308910 EU .is ISK Krona 354 ### ^(\d{3})$ is,en,de,da,sv,no 2629691 161 | IT ITA 380 IT Italy Rome 301230 60340328 EU .it EUR Euro 39 ##### ^(\d{5})$ it-IT,de-IT,fr-IT,sc,ca,co,sl 3175395 CH,VA,SI,SM,FR,AT 162 | JE JEY 832 JE Jersey Saint Helier 116 90812 EU .je GBP Pound +44-1534 @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA ^((?:(?:[A-PR-UWYZ][A-HK-Y]\d[ABEHMNPRV-Y0-9]|[A-PR-UWYZ]\d[A-HJKPS-UW0-9])\s\d[ABD-HJLNP-UW-Z]{2})|GIR\s?0AA)$ en,pt 3042142 163 | JM JAM 388 JM Jamaica Kingston 10991 2847232 NA .jm JMD Dollar +1-876 en-JM 3489940 164 | JO JOR 400 JO Jordan Amman 92300 6407085 AS .jo JOD Dinar 962 ##### ^(\d{5})$ ar-JO,en 248816 SY,SA,IQ,IL,PS 165 | JP JPN 392 JA Japan Tokyo 377835 127288000 AS .jp JPY Yen 81 ###-#### ^\d{3}-\d{4}$ ja 1861060 166 | KE KEN 404 KE Kenya Nairobi 582650 40046566 AF .ke KES Shilling 254 ##### ^(\d{5})$ en-KE,sw-KE 192950 ET,TZ,SS,SO,UG 167 | KG KGZ 417 KG Kyrgyzstan Bishkek 198500 5776500 AS .kg KGS Som 996 ###### ^(\d{6})$ ky,uz,ru 1527747 CN,TJ,UZ,KZ 168 | KH KHM 116 CB Cambodia Phnom Penh 181040 14453680 AS .kh KHR Riels 855 ##### ^(\d{5})$ km,fr,en 1831722 LA,TH,VN 169 | KI KIR 296 KR Kiribati Tarawa 811 92533 OC .ki AUD Dollar 686 en-KI,gil 4030945 170 | KM COM 174 CN Comoros Moroni 2170 773407 AF .km KMF Franc 269 ar,fr-KM 921929 171 | KN KNA 659 SC Saint Kitts and Nevis Basseterre 261 51134 NA .kn XCD Dollar +1-869 en-KN 3575174 172 | KP PRK 408 KN North Korea Pyongyang 120540 22912177 AS .kp KPW Won 850 ###-### ^(\d{6})$ ko-KP 1873107 CN,KR,RU 173 | KR KOR 410 KS South Korea Seoul 98480 48422644 AS .kr KRW Won 82 SEOUL ###-### ^(?:SEOUL)*(\d{6})$ ko-KR,en 1835841 KP 174 | XK XKX 0 KV Kosovo Pristina 10908 1800000 EU EUR Euro sq,sr 831053 RS,AL,MK,ME 175 | KW KWT 414 KU Kuwait Kuwait City 17820 2789132 AS .kw KWD Dinar 965 ##### ^(\d{5})$ ar-KW,en 285570 SA,IQ 176 | KY CYM 136 CJ Cayman Islands George Town 262 44270 NA .ky KYD Dollar +1-345 en-KY 3580718 177 | KZ KAZ 398 KZ Kazakhstan Astana 2717300 15340000 AS .kz KZT Tenge 7 ###### ^(\d{6})$ kk,ru 1522867 TM,CN,KG,UZ,RU 178 | LA LAO 418 LA Laos Vientiane 236800 6368162 AS .la LAK Kip 856 ##### ^(\d{5})$ lo,fr,en 1655842 CN,MM,KH,TH,VN 179 | LB LBN 422 LE Lebanon Beirut 10400 4125247 AS .lb LBP Pound 961 #### ####|#### ^(\d{4}(\d{4})?)$ ar-LB,fr-LB,en,hy 272103 SY,IL 180 | LC LCA 662 ST Saint Lucia Castries 616 160922 NA .lc XCD Dollar +1-758 en-LC 3576468 181 | LI LIE 438 LS Liechtenstein Vaduz 160 35000 EU .li CHF Franc 423 #### ^(\d{4})$ de-LI 3042058 CH,AT 182 | LK LKA 144 CE Sri Lanka Colombo 65610 21513990 AS .lk LKR Rupee 94 ##### ^(\d{5})$ si,ta,en 1227603 183 | LR LBR 430 LI Liberia Monrovia 111370 3685076 AF .lr LRD Dollar 231 #### ^(\d{4})$ en-LR 2275384 SL,CI,GN 184 | LS LSO 426 LT Lesotho Maseru 30355 1919552 AF .ls LSL Loti 266 ### ^(\d{3})$ en-LS,st,zu,xh 932692 ZA 185 | LT LTU 440 LH Lithuania Vilnius 65200 2944459 EU .lt EUR Euro 370 LT-##### ^(?:LT)*(\d{5})$ lt,ru,pl 597427 PL,BY,RU,LV 186 | LU LUX 442 LU Luxembourg Luxembourg 2586 497538 EU .lu EUR Euro 352 L-#### ^(?:L-)?\d{4}$ lb,de-LU,fr-LU 2960313 DE,BE,FR 187 | LV LVA 428 LG Latvia Riga 64589 2217969 EU .lv EUR Euro 371 LV-#### ^(?:LV)*(\d{4})$ lv,ru,lt 458258 LT,EE,BY,RU 188 | LY LBY 434 LY Libya Tripoli 1759540 6461454 AF .ly LYD Dinar 218 ar-LY,it,en 2215636 TD,NE,DZ,SD,TN,EG 189 | MA MAR 504 MO Morocco Rabat 446550 33848242 AF .ma MAD Dirham 212 ##### ^(\d{5})$ ar-MA,ber,fr 2542007 DZ,EH,ES 190 | MC MCO 492 MN Monaco Monaco 1.95 32965 EU .mc EUR Euro 377 ##### ^(\d{5})$ fr-MC,en,it 2993457 FR 191 | MD MDA 498 MD Moldova Chisinau 33843 4324000 EU .md MDL Leu 373 MD-#### ^MD-\d{4}$ ro,ru,gag,tr 617790 RO,UA 192 | ME MNE 499 MJ Montenegro Podgorica 14026 666730 EU .me EUR Euro 382 ##### ^(\d{5})$ sr,hu,bs,sq,hr,rom 3194884 AL,HR,BA,RS,XK 193 | MF MAF 663 RN Saint Martin Marigot 53 35925 NA .gp EUR Euro 590 ### ### fr 3578421 SX 194 | MG MDG 450 MA Madagascar Antananarivo 587040 21281844 AF .mg MGA Ariary 261 ### ^(\d{3})$ fr-MG,mg 1062947 195 | MH MHL 584 RM Marshall Islands Majuro 181.3 65859 OC .mh USD Dollar 692 #####-#### ^969\d{2}(-\d{4})$ mh,en-MH 2080185 196 | MK MKD 807 MK Macedonia Skopje 25333 2062294 EU .mk MKD Denar 389 #### ^(\d{4})$ mk,sq,tr,rmm,sr 718075 AL,GR,BG,RS,XK 197 | ML MLI 466 ML Mali Bamako 1240000 13796354 AF .ml XOF Franc 223 fr-ML,bm 2453866 SN,NE,DZ,CI,GN,MR,BF 198 | MM MMR 104 BM Myanmar Nay Pyi Taw 678500 53414374 AS .mm MMK Kyat 95 ##### ^(\d{5})$ my 1327865 CN,LA,TH,BD,IN 199 | MN MNG 496 MG Mongolia Ulan Bator 1565000 3086918 AS .mn MNT Tugrik 976 ###### ^(\d{6})$ mn,ru 2029969 CN,RU 200 | MO MAC 446 MC Macao Macao 254 449198 AS .mo MOP Pataca 853 zh,zh-MO,pt 1821275 201 | MP MNP 580 CQ Northern Mariana Islands Saipan 477 53883 OC .mp USD Dollar +1-670 #####-#### ^9695\d{1}(-\d{4})$ fil,tl,zh,ch-MP,en-MP 4041468 202 | MQ MTQ 474 MB Martinique Fort-de-France 1100 432900 NA .mq EUR Euro 596 ##### ^(\d{5})$ fr-MQ 3570311 203 | MR MRT 478 MR Mauritania Nouakchott 1030700 3205060 AF .mr MRO Ouguiya 222 ar-MR,fuc,snk,fr,mey,wo 2378080 SN,DZ,EH,ML 204 | MS MSR 500 MH Montserrat Plymouth 102 9341 NA .ms XCD Dollar +1-664 en-MS 3578097 205 | MT MLT 470 MT Malta Valletta 316 403000 EU .mt EUR Euro 356 @@@ #### ^[A-Z]{3}\s?\d{4}$ mt,en-MT 2562770 206 | MU MUS 480 MP Mauritius Port Louis 2040 1294104 AF .mu MUR Rupee 230 en-MU,bho,fr 934292 207 | MV MDV 462 MV Maldives Male 300 395650 AS .mv MVR Rufiyaa 960 ##### ^(\d{5})$ dv,en 1282028 208 | MW MWI 454 MI Malawi Lilongwe 118480 15447500 AF .mw MWK Kwacha 265 ny,yao,tum,swk 927384 TZ,MZ,ZM 209 | MX MEX 484 MX Mexico Mexico City 1972550 112468855 NA .mx MXN Peso 52 ##### ^(\d{5})$ es-MX 3996063 GT,US,BZ 210 | MY MYS 458 MY Malaysia Kuala Lumpur 329750 28274729 AS .my MYR Ringgit 60 ##### ^(\d{5})$ ms-MY,en,zh,ta,te,ml,pa,th 1733045 BN,TH,ID 211 | MZ MOZ 508 MZ Mozambique Maputo 801590 22061451 AF .mz MZN Metical 258 #### ^(\d{4})$ pt-MZ,vmw 1036973 ZW,TZ,SZ,ZA,ZM,MW 212 | NA NAM 516 WA Namibia Windhoek 825418 2128471 AF .na NAD Dollar 264 en-NA,af,de,hz,naq 3355338 ZA,BW,ZM,AO 213 | NC NCL 540 NC New Caledonia Noumea 19060 216494 OC .nc XPF Franc 687 ##### ^(\d{5})$ fr-NC 2139685 214 | NE NER 562 NG Niger Niamey 1267000 15878271 AF .ne XOF Franc 227 #### ^(\d{4})$ fr-NE,ha,kr,dje 2440476 TD,BJ,DZ,LY,BF,NG,ML 215 | NF NFK 574 NF Norfolk Island Kingston 34.6 1828 OC .nf AUD Dollar 672 #### ^(\d{4})$ en-NF 2155115 216 | NG NGA 566 NI Nigeria Abuja 923768 154000000 AF .ng NGN Naira 234 ###### ^(\d{6})$ en-NG,ha,yo,ig,ff 2328926 TD,NE,BJ,CM 217 | NI NIC 558 NU Nicaragua Managua 129494 5995928 NA .ni NIO Cordoba 505 ###-###-# ^(\d{7})$ es-NI,en 3617476 CR,HN 218 | NL NLD 528 NL Netherlands Amsterdam 41526 16645000 EU .nl EUR Euro 31 #### @@ ^(\d{4}[A-Z]{2})$ nl-NL,fy-NL 2750405 DE,BE 219 | NO NOR 578 NO Norway Oslo 324220 5009150 EU .no NOK Krone 47 #### ^(\d{4})$ no,nb,nn,se,fi 3144096 FI,RU,SE 220 | NP NPL 524 NP Nepal Kathmandu 140800 28951852 AS .np NPR Rupee 977 ##### ^(\d{5})$ ne,en 1282988 CN,IN 221 | NR NRU 520 NR Nauru Yaren 21 10065 OC .nr AUD Dollar 674 na,en-NR 2110425 222 | NU NIU 570 NE Niue Alofi 260 2166 OC .nu NZD Dollar 683 niu,en-NU 4036232 223 | NZ NZL 554 NZ New Zealand Wellington 268680 4252277 OC .nz NZD Dollar 64 #### ^(\d{4})$ en-NZ,mi 2186224 224 | OM OMN 512 MU Oman Muscat 212460 2967717 AS .om OMR Rial 968 ### ^(\d{3})$ ar-OM,en,bal,ur 286963 SA,YE,AE 225 | PA PAN 591 PM Panama Panama City 78200 3410676 NA .pa PAB Balboa 507 es-PA,en 3703430 CR,CO 226 | PE PER 604 PE Peru Lima 1285220 29907003 SA .pe PEN Sol 51 es-PE,qu,ay 3932488 EC,CL,BO,BR,CO 227 | PF PYF 258 FP French Polynesia Papeete 4167 270485 OC .pf XPF Franc 689 ##### ^((97|98)7\d{2})$ fr-PF,ty 4030656 228 | PG PNG 598 PP Papua New Guinea Port Moresby 462840 6064515 OC .pg PGK Kina 675 ### ^(\d{3})$ en-PG,ho,meu,tpi 2088628 ID 229 | PH PHL 608 RP Philippines Manila 300000 99900177 AS .ph PHP Peso 63 #### ^(\d{4})$ tl,en-PH,fil 1694008 230 | PK PAK 586 PK Pakistan Islamabad 803940 184404791 AS .pk PKR Rupee 92 ##### ^(\d{5})$ ur-PK,en-PK,pa,sd,ps,brh 1168579 CN,AF,IR,IN 231 | PL POL 616 PL Poland Warsaw 312685 38500000 EU .pl PLN Zloty 48 ##-### ^\d{2}-\d{3}$ pl 798544 DE,LT,SK,CZ,BY,UA,RU 232 | PM SPM 666 SB Saint Pierre and Miquelon Saint-Pierre 242 7012 NA .pm EUR Euro 508 ##### ^(97500)$ fr-PM 3424932 233 | PN PCN 612 PC Pitcairn Adamstown 47 46 OC .pn NZD Dollar 870 en-PN 4030699 234 | PR PRI 630 RQ Puerto Rico San Juan 9104 3916632 NA .pr USD Dollar +1-787 and 1-939 #####-#### ^00[679]\d{2}(?:-\d{4})?$ en-PR,es-PR 4566966 235 | PS PSE 275 WE Palestinian Territory East Jerusalem 5970 3800000 AS .ps ILS Shekel 970 ar-PS 6254930 JO,IL,EG 236 | PT PRT 620 PO Portugal Lisbon 92391 10676000 EU .pt EUR Euro 351 ####-### ^\d{4}-\d{3}\s?[a-zA-Z]{0,25}$ pt-PT,mwl 2264397 ES 237 | PW PLW 585 PS Palau Melekeok 458 19907 OC .pw USD Dollar 680 96940 ^(96940)$ pau,sov,en-PW,tox,ja,fil,zh 1559582 238 | PY PRY 600 PA Paraguay Asuncion 406750 6375830 SA .py PYG Guarani 595 #### ^(\d{4})$ es-PY,gn 3437598 BO,BR,AR 239 | QA QAT 634 QA Qatar Doha 11437 840926 AS .qa QAR Rial 974 ar-QA,es 289688 SA 240 | RE REU 638 RE Reunion Saint-Denis 2517 776948 AF .re EUR Euro 262 ##### ^((97|98)(4|7|8)\d{2})$ fr-RE 935317 241 | RO ROU 642 RO Romania Bucharest 237500 21959278 EU .ro RON Leu 40 ###### ^(\d{6})$ ro,hu,rom 798549 MD,HU,UA,BG,RS 242 | RS SRB 688 RI Serbia Belgrade 88361 7344847 EU .rs RSD Dinar 381 ###### ^(\d{6})$ sr,hu,bs,rom 6290252 AL,HU,MK,RO,HR,BA,BG,ME,XK 243 | RU RUS 643 RS Russia Moscow 17100000 140702000 EU .ru RUB Ruble 7 ###### ^(\d{6})$ ru,tt,xal,cau,ady,kv,ce,tyv,cv,udm,tut,mns,bua,myv,mdf,chm,ba,inh,tut,kbd,krc,ava,sah,nog 2017370 GE,CN,BY,UA,KZ,LV,PL,EE,LT,FI,MN,NO,AZ,KP 244 | RW RWA 646 RW Rwanda Kigali 26338 11055976 AF .rw RWF Franc 250 rw,en-RW,fr-RW,sw 49518 TZ,CD,BI,UG 245 | SA SAU 682 SA Saudi Arabia Riyadh 1960582 25731776 AS .sa SAR Rial 966 ##### ^(\d{5})$ ar-SA 102358 QA,OM,IQ,YE,JO,AE,KW 246 | SB SLB 090 BP Solomon Islands Honiara 28450 559198 OC .sb SBD Dollar 677 en-SB,tpi 2103350 247 | SC SYC 690 SE Seychelles Victoria 455 88340 AF .sc SCR Rupee 248 en-SC,fr-SC 241170 248 | SD SDN 729 SU Sudan Khartoum 1861484 35000000 AF .sd SDG Pound 249 ##### ^(\d{5})$ ar-SD,en,fia 366755 SS,TD,EG,ET,ER,LY,CF 249 | SS SSD 728 OD South Sudan Juba 644329 8260490 AF SSP Pound 211 en 7909807 CD,CF,ET,KE,SD,UG, 250 | SE SWE 752 SW Sweden Stockholm 449964 9828655 EU .se SEK Krona 46 ### ## ^(?:SE)?\d{3}\s\d{2}$ sv-SE,se,sma,fi-SE 2661886 NO,FI 251 | SG SGP 702 SN Singapore Singapore 692.7 4701069 AS .sg SGD Dollar 65 ###### ^(\d{6})$ cmn,en-SG,ms-SG,ta-SG,zh-SG 1880251 252 | SH SHN 654 SH Saint Helena Jamestown 410 7460 AF .sh SHP Pound 290 STHL 1ZZ ^(STHL1ZZ)$ en-SH 3370751 253 | SI SVN 705 SI Slovenia Ljubljana 20273 2007000 EU .si EUR Euro 386 #### ^(?:SI)*(\d{4})$ sl,sh 3190538 HU,IT,HR,AT 254 | SJ SJM 744 SV Svalbard and Jan Mayen Longyearbyen 62049 2550 EU .sj NOK Krone 47 #### ^(\d{4})$ no,ru 607072 255 | SK SVK 703 LO Slovakia Bratislava 48845 5455000 EU .sk EUR Euro 421 ### ## ^\d{3}\s?\d{2}$ sk,hu 3057568 PL,HU,CZ,UA,AT 256 | SL SLE 694 SL Sierra Leone Freetown 71740 5245695 AF .sl SLL Leone 232 en-SL,men,tem 2403846 LR,GN 257 | SM SMR 674 SM San Marino San Marino 61.2 31477 EU .sm EUR Euro 378 4789# ^(4789\d)$ it-SM 3168068 IT 258 | SN SEN 686 SG Senegal Dakar 196190 12323252 AF .sn XOF Franc 221 ##### ^(\d{5})$ fr-SN,wo,fuc,mnk 2245662 GN,MR,GW,GM,ML 259 | SO SOM 706 SO Somalia Mogadishu 637657 10112453 AF .so SOS Shilling 252 @@ ##### ^([A-Z]{2}\d{5})$ so-SO,ar-SO,it,en-SO 51537 ET,KE,DJ 260 | SR SUR 740 NS Suriname Paramaribo 163270 492829 SA .sr SRD Dollar 597 nl-SR,en,srn,hns,jv 3382998 GY,BR,GF 261 | ST STP 678 TP Sao Tome and Principe Sao Tome 1001 175808 AF .st STD Dobra 239 pt-ST 2410758 262 | SV SLV 222 ES El Salvador San Salvador 21040 6052064 NA .sv USD Dollar 503 CP #### ^(?:CP)*(\d{4})$ es-SV 3585968 GT,HN 263 | SX SXM 534 NN Sint Maarten Philipsburg 21 37429 NA .sx ANG Guilder 599 nl,en 7609695 MF 264 | SY SYR 760 SY Syria Damascus 185180 22198110 AS .sy SYP Pound 963 ar-SY,ku,hy,arc,fr,en 163843 IQ,JO,IL,TR,LB 265 | SZ SWZ 748 WZ Swaziland Mbabane 17363 1354051 AF .sz SZL Lilangeni 268 @### ^([A-Z]\d{3})$ en-SZ,ss-SZ 934841 ZA,MZ 266 | TC TCA 796 TK Turks and Caicos Islands Cockburn Town 430 20556 NA .tc USD Dollar +1-649 TKCA 1ZZ ^(TKCA 1ZZ)$ en-TC 3576916 267 | TD TCD 148 CD Chad N'Djamena 1284000 10543464 AF .td XAF Franc 235 fr-TD,ar-TD,sre 2434508 NE,LY,CF,SD,CM,NG 268 | TF ATF 260 FS French Southern Territories Port-aux-Francais 7829 140 AN .tf EUR Euro fr 1546748 269 | TG TGO 768 TO Togo Lome 56785 6587239 AF .tg XOF Franc 228 fr-TG,ee,hna,kbp,dag,ha 2363686 BJ,GH,BF 270 | TH THA 764 TH Thailand Bangkok 514000 67089500 AS .th THB Baht 66 ##### ^(\d{5})$ th,en 1605651 LA,MM,KH,MY 271 | TJ TJK 762 TI Tajikistan Dushanbe 143100 7487489 AS .tj TJS Somoni 992 ###### ^(\d{6})$ tg,ru 1220409 CN,AF,KG,UZ 272 | TK TKL 772 TL Tokelau 10 1466 OC .tk NZD Dollar 690 tkl,en-TK 4031074 273 | TL TLS 626 TT East Timor Dili 15007 1154625 OC .tl USD Dollar 670 tet,pt-TL,id,en 1966436 ID 274 | TM TKM 795 TX Turkmenistan Ashgabat 488100 4940916 AS .tm TMT Manat 993 ###### ^(\d{6})$ tk,ru,uz 1218197 AF,IR,UZ,KZ 275 | TN TUN 788 TS Tunisia Tunis 163610 10589025 AF .tn TND Dinar 216 #### ^(\d{4})$ ar-TN,fr 2464461 DZ,LY 276 | TO TON 776 TN Tonga Nuku'alofa 748 122580 OC .to TOP Pa'anga 676 to,en-TO 4032283 277 | TR TUR 792 TU Turkey Ankara 780580 77804122 AS .tr TRY Lira 90 ##### ^(\d{5})$ tr-TR,ku,diq,az,av 298795 SY,GE,IQ,IR,GR,AM,AZ,BG 278 | TT TTO 780 TD Trinidad and Tobago Port of Spain 5128 1228691 NA .tt TTD Dollar +1-868 en-TT,hns,fr,es,zh 3573591 279 | TV TUV 798 TV Tuvalu Funafuti 26 10472 OC .tv AUD Dollar 688 tvl,en,sm,gil 2110297 280 | TW TWN 158 TW Taiwan Taipei 35980 22894384 AS .tw TWD Dollar 886 ##### ^(\d{5})$ zh-TW,zh,nan,hak 1668284 281 | TZ TZA 834 TZ Tanzania Dodoma 945087 41892895 AF .tz TZS Shilling 255 sw-TZ,en,ar 149590 MZ,KE,CD,RW,ZM,BI,UG,MW 282 | UA UKR 804 UP Ukraine Kiev 603700 45415596 EU .ua UAH Hryvnia 380 ##### ^(\d{5})$ uk,ru-UA,rom,pl,hu 690791 PL,MD,HU,SK,BY,RO,RU 283 | UG UGA 800 UG Uganda Kampala 236040 33398682 AF .ug UGX Shilling 256 en-UG,lg,sw,ar 226074 TZ,KE,SS,CD,RW 284 | UM UMI 581 United States Minor Outlying Islands 0 0 OC .um USD Dollar 1 en-UM 5854968 285 | US USA 840 US United States Washington 9629091 310232863 NA .us USD Dollar 1 #####-#### ^\d{5}(-\d{4})?$ en-US,es-US,haw,fr 6252001 CA,MX,CU 286 | UY URY 858 UY Uruguay Montevideo 176220 3477000 SA .uy UYU Peso 598 ##### ^(\d{5})$ es-UY 3439705 BR,AR 287 | UZ UZB 860 UZ Uzbekistan Tashkent 447400 27865738 AS .uz UZS Som 998 ###### ^(\d{6})$ uz,ru,tg 1512440 TM,AF,KG,TJ,KZ 288 | VA VAT 336 VT Vatican Vatican City 0.44 921 EU .va EUR Euro 379 ##### ^(\d{5})$ la,it,fr 3164670 IT 289 | VC VCT 670 VC Saint Vincent and the Grenadines Kingstown 389 104217 NA .vc XCD Dollar +1-784 en-VC,fr 3577815 290 | VE VEN 862 VE Venezuela Caracas 912050 27223228 SA .ve VEF Bolivar 58 #### ^(\d{4})$ es-VE 3625428 GY,BR,CO 291 | VG VGB 092 VI British Virgin Islands Road Town 153 21730 NA .vg USD Dollar +1-284 en-VG 3577718 292 | VI VIR 850 VQ U.S. Virgin Islands Charlotte Amalie 352 108708 NA .vi USD Dollar +1-340 #####-#### ^008\d{2}(?:-\d{4})?$ en-VI 4796775 293 | VN VNM 704 VM Vietnam Hanoi 329560 89571130 AS .vn VND Dong 84 ###### ^(\d{6})$ vi,en,fr,zh,km 1562822 CN,LA,KH 294 | VU VUT 548 NH Vanuatu Port Vila 12200 221552 OC .vu VUV Vatu 678 bi,en-VU,fr-VU 2134431 295 | WF WLF 876 WF Wallis and Futuna Mata Utu 274 16025 OC .wf XPF Franc 681 ##### ^(986\d{2})$ wls,fud,fr-WF 4034749 296 | WS WSM 882 WS Samoa Apia 2944 192001 OC .ws WST Tala 685 sm,en-WS 4034894 297 | YE YEM 887 YM Yemen Sanaa 527970 23495361 AS .ye YER Rial 967 ar-YE 69543 SA,OM 298 | YT MYT 175 MF Mayotte Mamoudzou 374 159042 AF .yt EUR Euro 262 ##### ^(\d{5})$ fr-YT 1024031 299 | ZA ZAF 710 SF South Africa Pretoria 1219912 49000000 AF .za ZAR Rand 27 #### ^(\d{4})$ zu,xh,af,nso,en-ZA,tn,st,ts,ss,ve,nr 953987 ZW,SZ,MZ,BW,NA,LS 300 | ZM ZMB 894 ZA Zambia Lusaka 752614 13460305 AF .zm ZMW Kwacha 260 ##### ^(\d{5})$ en-ZM,bem,loz,lun,lue,ny,toi 895949 ZW,TZ,MZ,CD,NA,MW,AO 301 | ZW ZWE 716 ZI Zimbabwe Harare 390580 13061000 AF .zw ZWL Dollar 263 en-ZW,sn,nr,nd 878675 ZA,MZ,BW,ZM 302 | CS SCG 891 YI Serbia and Montenegro Belgrade 102350 10829175 EU .cs RSD Dinar 381 ##### ^(\d{5})$ cu,hu,sq,sr 8505033 AL,HU,MK,RO,HR,BA,BG 303 | AN ANT 530 NT Netherlands Antilles Willemstad 960 300000 NA .an ANG Guilder 599 nl-AN,en,es 8505032 GP 304 | -------------------------------------------------------------------------------- /test_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /test_project/test_app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | default_app_config = 'test_app.apps.TestAppConfig' 4 | -------------------------------------------------------------------------------- /test_project/test_app/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.apps import AppConfig 3 | 4 | 5 | class TestAppConfig(AppConfig): 6 | name = 'test_app' 7 | verbose_name = "Django Cities test app" 8 | 9 | models_module = None 10 | -------------------------------------------------------------------------------- /test_project/test_app/mixins.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import re 5 | 6 | from cities.models import (Continent, Country, Region, Subregion, City, 7 | District, PostalCode, AlternativeName) 8 | from cities.util import add_continents 9 | 10 | 11 | class NoInvalidSlugsMixin(object): 12 | def test_no_invalid_slugs(self): 13 | self.assertEqual(Country.objects.filter(slug__startswith='invalid').count(), 0) 14 | self.assertEqual(Region.objects.filter(slug__startswith='invalid').count(), 0) 15 | self.assertEqual(Subregion.objects.filter(slug__startswith='invalid').count(), 0) 16 | self.assertEqual(City.objects.filter(slug__startswith='invalid').count(), 0) 17 | self.assertEqual(PostalCode.objects.filter(slug__startswith='invalid').count(), 0) 18 | 19 | 20 | class ContinentsMixin(object): 21 | num_continents = 7 22 | 23 | def setUp(self): 24 | super(ContinentsMixin, self).setUp() 25 | Continent.objects.all().delete() 26 | add_continents(Continent) 27 | 28 | def test_num_continents(self): 29 | self.assertEqual(Continent.objects.count(), self.num_continents) 30 | 31 | 32 | class CountriesMixin(object): 33 | def test_num_countries(self): 34 | self.assertEqual(Country.objects.all().count(), self.num_countries) 35 | 36 | 37 | class RegionsMixin(object): 38 | def test_num_regions(self): 39 | self.assertEqual(Region.objects.count(), self.num_regions) 40 | 41 | def test_num_ad_regions(self): 42 | self.assertEqual( 43 | Region.objects.filter(country__code='AD').count(), 44 | self.num_ad_regions) 45 | 46 | def test_num_ua_regions(self): 47 | self.assertEqual( 48 | Region.objects.filter(country__code='UA').count(), 49 | self.num_ua_regions) 50 | 51 | 52 | class SubregionsMixin(object): 53 | def test_num_subregions(self): 54 | self.assertEqual(Subregion.objects.count(), self.num_subregions) 55 | 56 | 57 | class CitiesMixin(object): 58 | def test_num_cities(self): 59 | self.assertEqual(City.objects.count(), self.num_cities) 60 | 61 | def test_num_ua_cities(self): 62 | self.assertEqual( 63 | City.objects.filter(region__country__code='UA').count(), 64 | self.num_ua_cities) 65 | 66 | 67 | class DistrictsMixin(object): 68 | def test_num_districts(self): 69 | self.assertEqual(District.objects.count(), self.num_districts) 70 | 71 | 72 | class AlternativeNamesMixin(object): 73 | def test_num_alternative_names(self): 74 | self.assertEqual(AlternativeName.objects.count(), self.num_alt_names) 75 | 76 | def test_num_not_und_alternative_names(self): 77 | self.assertEqual( 78 | AlternativeName.objects.exclude(language_code='und').count(), 79 | self.num_not_und_alt_names) 80 | 81 | 82 | class PostalCodesMixin(object): 83 | def test_num_postal_codes(self): 84 | self.assertEqual(PostalCode.objects.count(), self.num_postal_codes) 85 | 86 | def test_postal_code_slugs(self): 87 | pc = PostalCode.objects.get(country__code='RU', code='102104') 88 | self.assertEqual(pc.code, '102104') 89 | self.assertTrue(len(pc.slug) <= 255) 90 | 91 | slug_rgx = re.compile(r'\d+-102104', re.UNICODE) 92 | 93 | slug = PostalCode.objects.get(country__code='RU', code='102104').slug 94 | 95 | # The unittest module in Python 2 does not have an assertRegex 96 | if hasattr(self, 'assertRegex'): 97 | self.assertRegex(slug, slug_rgx) 98 | else: 99 | m = slug_rgx.match(slug) 100 | 101 | self.assertIsNotNone(m) 102 | self.assertEqual(m.group(0)[-7:], '-102104') 103 | -------------------------------------------------------------------------------- /test_project/test_app/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderholic/django-cities/145af3182acbbde8d2bd24ad9ca50d5f5965fc8d/test_project/test_app/models.py -------------------------------------------------------------------------------- /test_project/test_app/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for test_project project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.7/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.7/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '&bloxby)1edwp08=5pwdd9(s1b)y^nwma6f*c&48w99-(z!7&=' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | TEMPLATES = [ 28 | { 29 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 30 | 'OPTIONS': { 31 | 'context_processors': [ 32 | 'django.template.context_processors.debug', 33 | 'django.template.context_processors.request', 34 | 'django.contrib.auth.context_processors.auth', 35 | 'django.contrib.messages.context_processors.messages', 36 | ], 37 | }, 38 | }, 39 | ] 40 | 41 | MIDDLEWARE = [ 42 | "django.contrib.auth.middleware.AuthenticationMiddleware", 43 | "django.contrib.messages.middleware.MessageMiddleware", 44 | "django.contrib.sessions.middleware.SessionMiddleware" 45 | ] 46 | 47 | ALLOWED_HOSTS = [] 48 | 49 | 50 | # Application definition 51 | 52 | INSTALLED_APPS = ( 53 | 'django.contrib.admin', 54 | 'django.contrib.auth', 55 | 'django.contrib.contenttypes', 56 | 'django.contrib.sessions', 57 | 'django.contrib.messages', 58 | 'django.contrib.staticfiles', 59 | 60 | 'cities', 61 | 'model_utils', 62 | 'test_app', 63 | ) 64 | 65 | MIDDLEWARE_CLASSES = ( 66 | 'django.contrib.sessions.middleware.SessionMiddleware', 67 | 'django.middleware.common.CommonMiddleware', 68 | 'django.middleware.csrf.CsrfViewMiddleware', 69 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 70 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 71 | 'django.contrib.messages.middleware.MessageMiddleware', 72 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 73 | ) 74 | 75 | ROOT_URLCONF = 'test_app.urls' 76 | 77 | WSGI_APPLICATION = 'test_project.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.contrib.gis.db.backends.postgis', 86 | 'NAME': 'django_cities', 87 | 'USER': os.environ.get('POSTGRES_USER', 'postgres'), 88 | 'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''), 89 | 'HOST': '127.0.0.1', 90 | 'PORT': 5432, 91 | } 92 | } 93 | 94 | # Internationalization 95 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 96 | 97 | LANGUAGE_CODE = 'en-us' 98 | 99 | TIME_ZONE = 'UTC' 100 | 101 | USE_I18N = True 102 | 103 | USE_L10N = True 104 | 105 | USE_TZ = True 106 | 107 | 108 | # Static files (CSS, JavaScript, Images) 109 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 110 | 111 | STATIC_URL = '/static/' 112 | 113 | 114 | # Logging: 115 | 116 | LOGGING = { 117 | 'version': 1, 118 | 'disable_existing_loggers': True, 119 | 'root': { 120 | 'level': 'ERROR', 121 | 'handlers': ['console'], 122 | }, 123 | 'formatters': { 124 | 'verbose': { 125 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' 126 | }, 127 | }, 128 | 'handlers': { 129 | 'console': { 130 | 'level': 'DEBUG', 131 | 'class': 'logging.StreamHandler', 132 | 'formatter': 'verbose' 133 | } 134 | }, 135 | 'loggers': { 136 | 'django.db.backends': { 137 | 'level': 'ERROR', 138 | 'handlers': ['console'], 139 | 'propagate': False, 140 | }, 141 | 'tests': { 142 | 'level': 'ERROR', 143 | 'handlers': ['console'], 144 | 'propagate': False, 145 | }, 146 | 'cities': { 147 | 'level': os.environ.get('TRAVIS_LOG_LEVEL', 'INFO'), 148 | 'handlers': ['console'], 149 | 'propagate': False, 150 | }, 151 | }, 152 | } 153 | # Cities config: 154 | travis_commit = os.environ.get('TRAVIS_COMMIT') 155 | travis_repo_slug = os.environ.get('TRAVIS_REPO_SLUG', 'coderholic/django-cities') 156 | travis_repo_branch = os.environ.get('TRAVIS_PULL_REQUEST_BRANCH', '') 157 | if travis_repo_branch == '': 158 | travis_repo_branch = os.environ.get('TRAVIS_BRANCH', os.environ.get('TRAVIS_REPO_BRANCH', 'master')) 159 | if os.environ.get('CITIES_DATA_URL_BASE', False): 160 | url_base = os.environ.get('CITIES_DATA_URL_BASE') 161 | elif travis_commit and travis_repo_slug: 162 | url_base = 'https://raw.githubusercontent.com/{repo_slug}/{commit_id}/test_project/data/'.format( 163 | repo_slug=travis_repo_slug, commit_id=travis_commit) 164 | else: 165 | url_base = "https://raw.githubusercontent.com/{repo_slug}/{branch_name}/test_project/data/".format( 166 | repo_slug=travis_repo_slug, 167 | branch_name=travis_repo_branch) 168 | 169 | CITIES_FILES = { 170 | 'country': { 171 | 'filename': 'countryInfo.txt', 172 | 'urls': [url_base + '{filename}', ], 173 | }, 174 | 'region': { 175 | 'filename': 'admin1CodesASCII.txt', 176 | 'urls': [url_base + '{filename}', ], 177 | }, 178 | 'subregion': { 179 | 'filename': 'admin2Codes.txt', 180 | 'urls': [url_base + '{filename}', ], 181 | }, 182 | 'city': { 183 | 'filename': 'cities1000.txt', 184 | 'urls': [url_base + '{filename}', ], 185 | }, 186 | 'hierarchy': { 187 | 'filename': 'hierarchy.txt', 188 | 'urls': [url_base + '{filename}', ], 189 | }, 190 | 'alt_name': { 191 | 'filename': 'alternateNames.txt', 192 | 'urls': [url_base + '{filename}', ], 193 | }, 194 | 'postal_code': { 195 | 'filename': 'allCountries.txt', 196 | 'urls': [url_base + '{filename}', ], 197 | } 198 | } 199 | 200 | CITIES_LOCALES = ['en', 'und', 'link'] 201 | -------------------------------------------------------------------------------- /test_project/test_app/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .test_manage_command import * # noqa: F401,F403 3 | -------------------------------------------------------------------------------- /test_project/test_app/tests/test_custom_continent_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase, override_settings 5 | from django.test.signals import setting_changed 6 | 7 | from ..mixins import NoInvalidSlugsMixin, ContinentsMixin 8 | from ..utils import reload_continent_data 9 | 10 | 11 | setting_changed.connect(reload_continent_data, dispatch_uid='reload_continent_data') 12 | 13 | 14 | class DefaultContinentData( 15 | NoInvalidSlugsMixin, ContinentsMixin, TestCase): 16 | num_continents = 7 17 | 18 | 19 | @override_settings(CITIES_CONTINENT_DATA={ 20 | 'AF': ('Africa', 6255146), 21 | 'EA': ('Eurasia', 6255148), 22 | 'NA': ('North America', 6255149), 23 | 'OC': ('Oceania', 6255151), 24 | 'SA': ('South America', 6255150), 25 | 'AN': ('Antarctica', 6255152), 26 | }) 27 | class EurasianContinentData( 28 | NoInvalidSlugsMixin, ContinentsMixin, TestCase): 29 | num_continents = 6 30 | 31 | 32 | @override_settings(CITIES_CONTINENT_DATA={ 33 | 'AF': ('Africa', 6255146), 34 | 'AS': ('Asia', 6255147), 35 | 'EU': ('Europe', 6255148), 36 | 'AM': ('America', 6255149), 37 | 'OC': ('Oceania', 6255151), 38 | 'AN': ('Antarctica', 6255152), 39 | }) 40 | class AmericanContinentData( 41 | NoInvalidSlugsMixin, ContinentsMixin, TestCase): 42 | num_continents = 6 43 | 44 | 45 | @override_settings(CITIES_CONTINENT_DATA={ 46 | 'AF': ('Africa', 6255146), 47 | 'EA': ('Eurasia', 6255148), 48 | 'NA': ('North America', 6255149), 49 | 'OC': ('Oceania', 6255151), 50 | 'SA': ('South America', 6255150), 51 | }) 52 | class NoAntarcticaContinentData( 53 | NoInvalidSlugsMixin, ContinentsMixin, TestCase): 54 | num_continents = 5 55 | 56 | 57 | @override_settings(CITIES_CONTINENT_DATA={ 58 | 'AE': ('Afroeurasia', 6255148), 59 | 'AM': ('America', 6255149), 60 | 'OC': ('Oceania', 6255151), 61 | 'AN': ('Antarctica', 6255152), 62 | }) 63 | class AfroEurasianContinentData( 64 | NoInvalidSlugsMixin, ContinentsMixin, TestCase): 65 | num_continents = 4 66 | -------------------------------------------------------------------------------- /test_project/test_app/tests/test_manage_command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from unittest import skipIf 5 | 6 | from django import VERSION as django_version 7 | from django.core.management import call_command 8 | from django.test import TestCase, override_settings 9 | from django.test.signals import setting_changed 10 | 11 | from cities.models import (Country, Region, Subregion, City, District, 12 | PostalCode, AlternativeName) 13 | 14 | from ..mixins import ( 15 | NoInvalidSlugsMixin, CountriesMixin, RegionsMixin, SubregionsMixin, 16 | CitiesMixin, DistrictsMixin, AlternativeNamesMixin, PostalCodesMixin) 17 | from ..utils import reload_cities_settings 18 | 19 | 20 | setting_changed.connect(reload_cities_settings, dispatch_uid='reload_cities_settings') 21 | 22 | 23 | @override_settings(CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS=True) 24 | class SkipCitiesWithEmptyRegionsManageCommandTestCase( 25 | NoInvalidSlugsMixin, CountriesMixin, RegionsMixin, SubregionsMixin, 26 | CitiesMixin, DistrictsMixin, TestCase): 27 | num_countries = 250 28 | num_regions = 171 29 | num_ad_regions = 7 30 | num_ua_regions = 27 31 | num_subregions = 4928 32 | num_cities = 117 33 | num_ua_cities = 50 34 | num_districts = 3 35 | 36 | @classmethod 37 | def setUpTestData(cls): 38 | # Run the import command only once 39 | super(SkipCitiesWithEmptyRegionsManageCommandTestCase, cls).setUpTestData() 40 | call_command('cities', force=True, **{ 41 | 'import': 'country,region,subregion,city,district', 42 | }) 43 | cls.counts = { 44 | 'countries': Country.objects.count(), 45 | 'regions': Region.objects.count(), 46 | 'subregions': Subregion.objects.count(), 47 | 'cities': City.objects.count(), 48 | 'districts': District.objects.count(), 49 | 'postal_codes': PostalCode.objects.count(), 50 | } 51 | 52 | 53 | class ManageCommandTestCase( 54 | NoInvalidSlugsMixin, CountriesMixin, RegionsMixin, SubregionsMixin, 55 | CitiesMixin, DistrictsMixin, AlternativeNamesMixin, PostalCodesMixin, 56 | TestCase): 57 | num_countries = 250 58 | num_regions = 171 59 | num_ad_regions = 7 60 | num_ua_regions = 27 61 | num_subregions = 4928 62 | num_cities = 121 63 | num_ua_cities = 50 64 | num_districts = 3 65 | num_alt_names = 2945 66 | num_not_und_alt_names = 579 67 | num_postal_codes = 13 68 | 69 | @classmethod 70 | def setUpTestData(cls): 71 | # Run the import command only once 72 | super(ManageCommandTestCase, cls).setUpTestData() 73 | call_command('cities', force=True, **{ 74 | 'import': 'country,region,subregion,city,district,alt_name,postal_code', 75 | }) 76 | cls.counts = { 77 | 'countries': Country.objects.count(), 78 | 'regions': Region.objects.count(), 79 | 'subregions': Subregion.objects.count(), 80 | 'cities': City.objects.count(), 81 | 'districts': District.objects.count(), 82 | 'alt_names': AlternativeName.objects.count(), 83 | 'postal_codes': PostalCode.objects.count(), 84 | } 85 | 86 | def test_only_en_and_und_alternative_names(self): 87 | self.assertEqual( 88 | AlternativeName.objects.count(), 89 | AlternativeName.objects.filter(language_code__in=['en', 'und']).count()) 90 | 91 | def test_idempotence(self): 92 | call_command('cities', force=True, **{ 93 | 'import': 'country,region,subregion,city,alt_name,postal_code', 94 | }) 95 | self.assertEqual(Country.objects.count(), self.counts['countries']) 96 | self.assertEqual(Region.objects.count(), self.counts['regions']) 97 | self.assertEqual(Subregion.objects.count(), self.counts['subregions']) 98 | self.assertEqual(City.objects.count(), self.counts['cities']) 99 | self.assertEqual(District.objects.count(), self.counts['districts']) 100 | self.assertEqual(AlternativeName.objects.count(), self.counts['alt_names']) 101 | self.assertEqual(PostalCode.objects.count(), self.counts['postal_codes']) 102 | 103 | 104 | # This was tested manually 105 | @skipIf(django_version < (1, 8), "Django < 1.8, skipping test with CITIES_LOCALES=['all']") 106 | @override_settings(CITIES_LOCALES=['all']) 107 | class AllLocalesManageCommandTestCase( 108 | NoInvalidSlugsMixin, CountriesMixin, RegionsMixin, SubregionsMixin, 109 | CitiesMixin, DistrictsMixin, AlternativeNamesMixin, TestCase): 110 | num_countries = 250 111 | num_regions = 171 112 | num_ad_regions = 7 113 | num_ua_regions = 27 114 | num_subregions = 4928 115 | num_cities = 121 116 | num_ua_cities = 50 117 | num_districts = 3 118 | num_alt_names = 7760 119 | num_not_und_alt_names = 5394 120 | 121 | @classmethod 122 | def setUpTestData(cls): 123 | # Run the import command only once 124 | super(AllLocalesManageCommandTestCase, cls).setUpTestData() 125 | call_command('cities', force=True, **{ 126 | 'import': 'country,region,subregion,city,district,alt_name', 127 | }) 128 | cls.counts = { 129 | 'countries': Country.objects.count(), 130 | 'regions': Region.objects.count(), 131 | 'subregions': Subregion.objects.count(), 132 | 'cities': City.objects.count(), 133 | 'districts': District.objects.count(), 134 | 'alt_names': AlternativeName.objects.count(), 135 | } 136 | -------------------------------------------------------------------------------- /test_project/test_app/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.gis.geos import Point 2 | from django.test import TestCase 3 | 4 | from cities import models 5 | 6 | 7 | class SlugModelTest(object): 8 | """ 9 | Common tests for SlugModel subclasses. 10 | """ 11 | 12 | def instantiate(self): 13 | """ 14 | Implement this to return a valid instance of the model under test. 15 | """ 16 | raise NotImplementedError 17 | 18 | def test_save(self): 19 | instance = self.instantiate() 20 | instance.save() 21 | 22 | def test_save_force_insert(self): 23 | """ 24 | Regression test: save() with force_insert=True should work. 25 | """ 26 | instance = self.instantiate() 27 | instance.save(force_insert=True) 28 | 29 | 30 | class ContinentTestCase(SlugModelTest, TestCase): 31 | 32 | def instantiate(self): 33 | return models.Continent() 34 | 35 | 36 | class CountryTestCase(SlugModelTest, TestCase): 37 | 38 | def instantiate(self): 39 | return models.Country( 40 | population=0, 41 | ) 42 | 43 | 44 | class RegionTestCase(SlugModelTest, TestCase): 45 | 46 | def instantiate(self): 47 | country = models.Country( 48 | population=0 49 | ) 50 | country.save() 51 | return models.Region( 52 | country=country, 53 | ) 54 | 55 | 56 | class SubregionTestCase(SlugModelTest, TestCase): 57 | 58 | def instantiate(self): 59 | country = models.Country( 60 | population=0 61 | ) 62 | country.save() 63 | region = models.Region( 64 | country=country, 65 | ) 66 | region.save() 67 | return models.Subregion( 68 | region=region, 69 | ) 70 | 71 | 72 | class CityTestCase(SlugModelTest, TestCase): 73 | 74 | def instantiate(self): 75 | country = models.Country( 76 | population=0 77 | ) 78 | country.save() 79 | return models.City( 80 | country=country, 81 | location=Point(0, 0), 82 | population=0, 83 | ) 84 | 85 | 86 | class DistrictTestCase(SlugModelTest, TestCase): 87 | 88 | def instantiate(self): 89 | country = models.Country( 90 | population=0 91 | ) 92 | country.save() 93 | city = models.City( 94 | country=country, 95 | location=Point(0, 0), 96 | population=0, 97 | ) 98 | city.save() 99 | return models.District( 100 | location=Point(0, 0), 101 | population=0, 102 | city=city, 103 | ) 104 | 105 | 106 | class AlternativeNameTestCase(SlugModelTest, TestCase): 107 | 108 | def instantiate(self): 109 | return models.AlternativeName() 110 | 111 | 112 | class PostalCodeTestCase(SlugModelTest, TestCase): 113 | 114 | def instantiate(self): 115 | country = models.Country( 116 | population=0 117 | ) 118 | country.save() 119 | return models.PostalCode( 120 | location=Point(0, 0), 121 | country=country, 122 | ) 123 | -------------------------------------------------------------------------------- /test_project/test_app/urls.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.conf.urls import url 3 | except ImportError: 4 | from django.urls import re_path as url 5 | 6 | from django.contrib import admin 7 | from django.core.exceptions import ImproperlyConfigured 8 | 9 | from cities.util import patterns 10 | 11 | app_name = "test_app" 12 | 13 | try: 14 | from django.conf.urls import include 15 | # Django < 2.0 16 | urlpatterns = patterns( 17 | '', 18 | # Examples: 19 | # url(r'^$', 'test_project.views.home', name='home'), 20 | # url(r'^blog/', include('blog.urls')), 21 | 22 | url(r'^admin/', include(admin.site.urls)), 23 | ) 24 | except ImproperlyConfigured: 25 | # Django >= 2.0 26 | urlpatterns = patterns( 27 | '', 28 | # Examples: 29 | # url(r'^$', 'test_project.views.home', name='home'), 30 | # url(r'^blog/', include('blog.urls')), 31 | 32 | url(r'^admin/', admin.site.urls), 33 | ) 34 | -------------------------------------------------------------------------------- /test_project/test_app/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | try: # Python 3.4+ 4 | from importlib import reload 5 | except ImportError: 6 | try: # Python 3.0 - 3.3 7 | from imp import reload 8 | except ImportError: 9 | # reload is a builtin in Python 2 10 | pass 11 | 12 | 13 | from cities.conf import CONTINENT_DATA as DEFAULT_CONTINENT_DATA 14 | 15 | 16 | def reload_continent_data(signal, sender, setting, value, enter): 17 | if setting != 'CITIES_CONTINENT_DATA': 18 | return 19 | 20 | if value is None: 21 | value = DEFAULT_CONTINENT_DATA 22 | 23 | # Force reload the conf value 24 | sys.modules['cities.conf'].CONTINENT_DATA = value 25 | sys.modules['cities.util'].CONTINENT_DATA = value 26 | 27 | 28 | def reload_cities_settings(sender, setting, value, enter, **kwargs): 29 | if setting != 'CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS': 30 | return 31 | 32 | reload(sys.modules['cities.conf']) 33 | reload(sys.modules['cities.management.commands.cities']) 34 | -------------------------------------------------------------------------------- /test_project/test_app/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for test_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | from django.core.wsgi import get_wsgi_application 12 | 13 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | {py36,py37,py38,py39}-django22, 4 | {py36,py37,py38,py39}-django31, 5 | {py36,py37,py38,py39,py310}-django32, 6 | {py38,py39,py310}-django40, 7 | 8 | [testenv] 9 | commands=python {toxinidir}/test_project/manage.py test {posargs:test_app} --noinput 10 | passenv = DJANGO_VERSION POSTGRES_USER POSTGRES_PASSWORD TRAVIS_BRANCH 11 | TRAVIS_COMMIT TRAVIS_LOG_LEVEL TRAVIS_PULL_REQUEST_BRANCH 12 | TRAVIS_REPO_SLUG 13 | 14 | deps = 15 | django22: Django>=2.2,<3.0 16 | django31: Django>=3.1,<3.2 17 | django32: Django>=3.2,<4.0 18 | django40: Django>=4.0,<5.0 19 | 20 | psycopg2 21 | --------------------------------------------------------------------------------