from collections import defaultdict from contextlib import suppress from importlib import import_module import django from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured if float(".".join(map(str, django.VERSION[:2]))) < 3: from django.utils.translation import ugettext_lazy as _ else: from django.utils.translation import gettext_lazy as _ __all__ = [ "ALTERNATIVE_NAME_TYPES", "CONTINENT_DATA", "CURRENCY_SYMBOLS", "INCLUDE_AIRPORT_CODES", "INCLUDE_NUMERIC_ALTERNATIVE_NAMES", "NO_LONGER_EXISTENT_COUNTRY_CODES", "SKIP_CITIES_WITH_EMPTY_REGIONS", "SLUGIFY_FUNCTION", "VALIDATE_POSTAL_CODES", "HookError", "city_types", "district_types", "import_opts", "import_opts_all", "settings", ] url_bases = { "geonames": { "dump": "http://download.geonames.org/export/dump/", "zip": "http://download.geonames.org/export/zip/", }, } files = { "country": { "filename": "countryInfo.txt", "urls": [url_bases["geonames"]["dump"] + "{filename}"], "fields": [ "code", "code3", "codeNum", "fips", "name", "capital", "area", "population", "continent", "tld", "currencyCode", "currencyName", "phone", "postalCodeFormat", "postalCodeRegex", "languages", "geonameid", "neighbours", "equivalentFips", ], }, "region": { "filename": "admin1CodesASCII.txt", "urls": [url_bases["geonames"]["dump"] + "{filename}"], "fields": [ "code", "name", "asciiName", "geonameid", ], }, "subregion": { "filename": "admin2Codes.txt", "urls": [url_bases["geonames"]["dump"] + "{filename}"], "fields": [ "code", "name", "asciiName", "geonameid", ], }, "city": { "filename": "cities5000.zip", "urls": [url_bases["geonames"]["dump"] + "{filename}"], "fields": [ "geonameid", "name", "asciiName", "alternateNames", "latitude", "longitude", "featureClass", "featureCode", "countryCode", "cc2", "admin1Code", "admin2Code", "admin3Code", "admin4Code", "population", "elevation", "gtopo30", "timezone", "modificationDate", ], }, "hierarchy": { "filename": "hierarchy.zip", "urls": [url_bases["geonames"]["dump"] + "{filename}"], "fields": [ "parent", "child", "type", ], }, "alt_name": { "filename": "alternateNames.zip", "urls": [url_bases["geonames"]["dump"] + "{filename}"], "fields": [ "nameid", "geonameid", "language", "name", "isPreferred", "isShort", "isColloquial", "isHistoric", ], }, "postal_code": { "filename": "allCountries.zip", "urls": [url_bases["geonames"]["zip"] + "{filename}"], "fields": [ "countryCode", "postalCode", "placeName", "admin1Name", "admin1Code", "admin2Name", "admin2Code", "admin3Name", "admin3Code", "latitude", "longitude", "accuracy", ], }, } country_codes = [ "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "XK", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SS", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW", ] _ALTERNATIVE_NAME_TYPES = ( ("name", _("Name")), ("abbr", _("Abbreviation")), ("link", _("Link")), ) _AIRPORT_TYPES = ( ("iata", _("IATA (Airport) Code")), ("icao", _("ICAO (Airport) Code")), ("faac", _("FAAC (Airport) Code")), ) CONTINENT_DATA = { "AF": ("Africa", 6255146), "AS": ("Asia", 6255147), "EU": ("Europe", 6255148), "NA": ("North America", 6255149), "OC": ("Oceania", 6255151), "SA": ("South America", 6255150), "AN": ("Antarctica", 6255152), } _CURRENCY_SYMBOLS = { "AED": "د.إ", "AFN": "؋", "ALL": "L", "AMD": "դր.", "ANG": "ƒ", "AOA": "Kz", "ARS": "$", "AUD": "$", "AWG": "ƒ", "AZN": "m", "BAM": "KM", "BBD": "$", "BDT": "৳", "BGN": "лв", "BHD": "ب.د", "BIF": "Fr", "BMD": "$", "BND": "$", "BOB": "Bs.", "BRL": "R$", "BSD": "$", "BTN": "Nu", "BWP": "P", "BYR": "Br", "BZD": "$", "CAD": "$", "CDF": "Fr", "CHF": "Fr", "CLP": "$", "CNY": "¥", "COP": "$", "CRC": "₡", "CUP": "$", "CVE": "$, Esc", "CZK": "Kč", "DJF": "Fr", "DKK": "kr", "DOP": "$", "DZD": "د.ج", "EEK": "KR", "EGP": "£,ج.م", "ERN": "Nfk", "ETB": "Br", "EUR": "€", "FJD": "$", "FKP": "£", "GBP": "£", "GEL": "ლ", "GHS": "₵", "GIP": "£", "GMD": "D", "GNF": "Fr", "GTQ": "Q", "GYD": "$", "HKD": "$", "HNL": "L", "HRK": "kn", "HTG": "G", "HUF": "Ft", "IDR": "Rp", "ILS": "₪", "INR": "₨", "IQD": "ع.د", "IRR": "﷼", "ISK": "kr", "JMD": "$", "JOD": "د.ا", "JPY": "¥", "KES": "Sh", "KGS": "лв", "KHR": "៛", "KMF": "Fr", "KPW": "₩", "KRW": "₩", "KWD": "د.ك", "KYD": "$", "KZT": "Т", "LAK": "₭", "LBP": "ل.ل", "LKR": "ரூ", "LRD": "$", "LSL": "L", "LTL": "Lt", "LVL": "Ls", "LYD": "ل.د", "MAD": "د.م.", "MDL": "L", "MGA": "Ar", "MKD": "ден", "MMK": "K", "MNT": "₮", "MOP": "P", "MRO": "UM", "MUR": "₨", "MVR": "ރ.", "MWK": "MK", "MXN": "$", "MYR": "RM", "MZN": "MT", "NAD": "$", "NGN": "₦", "NIO": "C$", "NOK": "kr", "NPR": "₨", "NZD": "$", "OMR": "ر.ع.", "PAB": "B/.", "PEN": "S/.", "PGK": "K", "PHP": "₱", "PKR": "₨", "PLN": "zł", "PYG": "₲", "QAR": "ر.ق", "RON": "RON", "RSD": "RSD", "RUB": "р.", "RWF": "Fr", "SAR": "ر.س", "SBD": "$", "SCR": "₨", "SDG": "S$", "SEK": "kr", "SGD": "$", "SHP": "£", "SLL": "Le", "SOS": "Sh", "SRD": "$", "STD": "Db", "SYP": "£, ل.س", "SZL": "L", "THB": "฿", "TJS": "ЅМ", "TMT": "m", "TND": "د.ت", "TOP": "T$", "TRY": "₤", "TTD": "$", "TWD": "$", "TZS": "Sh", "UAH": "₴", "UGX": "Sh", "USD": "$", "UYU": "$", "UZS": "лв", "VEF": "Bs", "VND": "₫", "VUV": "Vt", "WST": "T", "XAF": "Fr", "XCD": "$", "XOF": "Fr", "XPF": "Fr", "YER": "﷼", "ZAR": "R", "ZMK": "ZK", "ZWL": "$", } _NO_LONGER_EXISTENT_COUNTRY_CODES = ["CS", "AN"] _SLUGIFY_FUNCTION = getattr(django_settings, "CITIES_SLUGIFY_FUNCTION", "geo.util.default_slugify") # See http://www.geonames.org/export/codes.html city_types = ["PPL", "PPLA", "PPLC", "PPLA2", "PPLA3", "PPLA4", "PPLG"] district_types = ["PPLX"] # Command-line import options import_opts = [ "all", "country", "region", "subregion", "city", "district", "alt_name", "postal_code", ] import_opts_all = [ "country", "region", "subregion", "city", "district", "alt_name", "postal_code", ] # Raise inside a hook (with an error message) to skip the current line of data. class HookError(Exception): pass # Hook functions that a plugin class may define plugin_hooks = [ "country_pre", "country_post", "region_pre", "region_post", "subregion_pre", "subregion_postcity_pre", "city_post", "district_pre", "district_post", "alt_name_pre", "alt_name_post", "postal_code_pre", "postal_code_post", ] def create_settings(): def get_locales(self): locales = django_settings.CITIES_LOCALES[:] if hasattr(django_settings, "CITIES_LOCALES") else ["en", "und"] with suppress(Exception): locales.remove("LANGUAGES") locales += [e[0] for e in django_settings.LANGUAGES] return {[e.lower() for e in locales]} res = type( "settings", (), { "locales": property(get_locales), }, ) res.files = files.copy() if hasattr(django_settings, "CITIES_FILES"): for key in list(django_settings.CITIES_FILES.keys()): if "filenames" in django_settings.CITIES_FILES[key] and "filename" in django_settings.CITIES_FILES[key]: raise ImproperlyConfigured( f"Only one key should be specified for '{key}': 'filename' of 'filenames'. Both specified instead" ) res.files[key].update(django_settings.CITIES_FILES[key]) if "filenames" in django_settings.CITIES_FILES[key]: del res.files[key]["filename"] if hasattr(django_settings, "CITIES_DATA_DIR"): res.data_dir = django_settings.CITIES_DATA_DIR if hasattr(django_settings, "CITIES_POSTAL_CODES"): res.postal_codes = {e.upper() for e in django_settings.CITIES_POSTAL_CODES} else: res.postal_codes = {"ALL"} return res() def create_plugins(): settings.plugins = defaultdict(list) for plugin in django_settings.CITIES_PLUGINS: module_path, classname = plugin.rsplit(".", 1) module = import_module(module_path) class_ = getattr(module, classname) obj = class_() [settings.plugins[hook].append(obj) for hook in plugin_hooks if hasattr(obj, hook)] settings = create_settings() if hasattr(django_settings, "CITIES_PLUGINS"): create_plugins() if hasattr(django_settings, "CITIES_IGNORE_EMPTY_REGIONS"): raise Exception( "CITIES_IGNORE_EMPTY_REGIONS was ambiguous and has been moved to CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS" ) SKIP_CITIES_WITH_EMPTY_REGIONS = getattr(django_settings, "CITIES_SKIP_CITIES_WITH_EMPTY_REGIONS", False) # Users may way to import historical countries NO_LONGER_EXISTENT_COUNTRY_CODES = getattr( django_settings, "CITIES_NO_LONGER_EXISTENT_COUNTRY_CODES", _NO_LONGER_EXISTENT_COUNTRY_CODES ) # Users may not want to include airport codes as alternative city names INCLUDE_AIRPORT_CODES = getattr(django_settings, "CITIES_INCLUDE_AIRPORT_CODES", True) if INCLUDE_AIRPORT_CODES: _ALTERNATIVE_NAME_TYPES += _AIRPORT_TYPES # A `Choices` object (from `django-model-utils`) ALTERNATIVE_NAME_TYPES = getattr(django_settings, "CITIES_ALTERNATIVE_NAME_TYPES", _ALTERNATIVE_NAME_TYPES) INCLUDE_NUMERIC_ALTERNATIVE_NAMES = getattr(django_settings, "CITIES_INCLUDE_NUMERIC_ALTERNATIVE_NAMES", True) # Allow users to override specified contents CONTINENT_DATA.update(getattr(django_settings, "CITIES_CONTINENT_DATA", {})) CURRENCY_SYMBOLS = getattr(django_settings, "CITIES_CURRENCY_SYMBOLS", _CURRENCY_SYMBOLS) module_name, _, function_name = _SLUGIFY_FUNCTION.rpartition(".") SLUGIFY_FUNCTION = getattr(import_module(module_name), function_name) # Users who want better postal codes can flip this on (developers of # django-cities itself probably will), but most probably won't want to VALIDATE_POSTAL_CODES = getattr(django_settings, "CITIES_VALIDATE_POSTAL_CODES", False) DJANGO_VERSION = float(".".join(map(str, django.VERSION[:2])))