Features: Full Geo rework.
Fixes: 1) Address redundant migration logic by dropping stale migrations; 2) Cleanup swapper-based dependencies in Geo models; Extra: Streamlined database initialization by resetting all previous migrations.
This commit is contained in:
parent
ec4fa17d72
commit
564ce0706b
56 changed files with 537 additions and 55711 deletions
|
|
@ -17,7 +17,6 @@ from core.utils import format_attributes, is_url_safe
|
|||
from core.utils.caching import web_cache
|
||||
from core.utils.emailing import contact_us_email
|
||||
from core.utils.messages import permission_denied_message
|
||||
from geo.graphene.object_types import UnregisteredCustomerAddressInput
|
||||
from payments.graphene.object_types import TransactionType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -220,24 +219,26 @@ class BuyUnregisteredOrder(BaseMutation):
|
|||
customer_name = String(required=True)
|
||||
customer_email = String(required=True)
|
||||
customer_phone = String(required=True)
|
||||
customer_billing_address = UnregisteredCustomerAddressInput(required=True)
|
||||
customer_shipping_address = UnregisteredCustomerAddressInput(required=False)
|
||||
customer_billing_address = String(required=False)
|
||||
customer_shipping_address = String(required=False)
|
||||
payment_method = String(required=True)
|
||||
is_business = Boolean(required=False)
|
||||
|
||||
transaction = Field(TransactionType, required=False)
|
||||
|
||||
@staticmethod
|
||||
def mutate(_parent, info, products, customer_name, customer_email, customer_phone, customer_billing_address,
|
||||
payment_method, customer_shipping_address=None, promocode_uuid=None):
|
||||
payment_method, customer_shipping_address=None, promocode_uuid=None, is_business=False):
|
||||
order = Order.objects.create(status="MOMENTAL")
|
||||
transaction = order.buy_without_registration(products=products,
|
||||
promocode_uuid=promocode_uuid,
|
||||
customer_name=customer_name,
|
||||
customer_email=customer_email,
|
||||
customer_phone=customer_phone,
|
||||
customer_billing_address=customer_billing_address,
|
||||
customer_shipping_address=customer_shipping_address,
|
||||
payment_method=payment_method)
|
||||
billing_customer_address=customer_billing_address,
|
||||
shipping_customer_address=customer_shipping_address,
|
||||
payment_method=payment_method,
|
||||
is_business=is_business)
|
||||
return BuyUnregisteredOrder(transaction=transaction)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ from core.utils import get_project_parameters
|
|||
from core.utils.languages import get_flag_by_language
|
||||
from core.utils.messages import permission_denied_message
|
||||
from evibes.settings import LANGUAGES
|
||||
from geo.graphene.mutations import AutocompleteAddress, CreateAddress, DeleteAddress
|
||||
from payments.graphene.mutations import Deposit
|
||||
from vibes_auth.filters import UserFilter
|
||||
from vibes_auth.graphene.mutations import (
|
||||
|
|
@ -304,6 +305,9 @@ class Mutation(ObjectType):
|
|||
create_product = CreateProduct.Field()
|
||||
update_product = UpdateProduct.Field()
|
||||
delete_product = DeleteProduct.Field()
|
||||
create_address = CreateAddress.Field()
|
||||
delete_address = DeleteAddress.Field()
|
||||
autocomplete_address = AutocompleteAddress.Field()
|
||||
|
||||
|
||||
schema = Schema(query=Query, mutation=Mutation)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('geo', '0013_alter_city_options_alternativename_active_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
100
core/models.py
100
core/models.py
|
|
@ -44,7 +44,7 @@ from core.utils import generate_human_readable_id, get_product_uuid_as_path, get
|
|||
from core.utils.lists import FAILED_STATUSES
|
||||
from core.validators import validate_category_image_dimensions
|
||||
from evibes.settings import CURRENCY_CODE
|
||||
from geo.models import Address, City, Country, PostalCode, Region
|
||||
from geo.models import Address
|
||||
from payments.models import Transaction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -624,6 +624,31 @@ class Order(NiceModel):
|
|||
raise Http404(_("promocode does not exist"))
|
||||
return promocode.use(self)
|
||||
|
||||
def apply_addresses(self, billing_address_uuid, shipping_address_uuid):
|
||||
|
||||
try:
|
||||
if not self.is_whole_digital and not any([shipping_address_uuid, billing_address_uuid]):
|
||||
raise ValueError(_("you can only buy physical products with shipping address specified"))
|
||||
|
||||
if billing_address_uuid and not shipping_address_uuid:
|
||||
shipping_address = Address.objects.get(uuid=billing_address_uuid)
|
||||
billing_address = shipping_address
|
||||
|
||||
elif shipping_address_uuid and not billing_address_uuid:
|
||||
billing_address = Address.objects.get(uuid=shipping_address_uuid)
|
||||
shipping_address = billing_address
|
||||
|
||||
else:
|
||||
billing_address = Address.objects.get(uuid=billing_address_uuid)
|
||||
shipping_address = Address.objects.get(uuid=shipping_address_uuid)
|
||||
|
||||
self.billing_address = billing_address
|
||||
self.shipping_address = shipping_address
|
||||
self.save()
|
||||
|
||||
except Address.DoesNotExist:
|
||||
raise Http404(_("address does not exist"))
|
||||
|
||||
def buy(
|
||||
self, force_balance: bool = False, force_payment: bool = False, promocode_uuid: str | None = None,
|
||||
billing_address: str | None = None, shipping_address: str | None = None, **kwargs
|
||||
|
|
@ -634,24 +659,7 @@ class Order(NiceModel):
|
|||
if (not force_balance and not force_payment) or (force_balance and force_payment):
|
||||
raise ValueError(_("invalid force value"))
|
||||
|
||||
if not self.is_whole_digital and not any([shipping_address, billing_address]):
|
||||
raise ValueError(_("you can only buy physical products with shipping address specified"))
|
||||
|
||||
if shipping_address and not billing_address:
|
||||
shipping_address = Address.objects.get(uuid=shipping_address)
|
||||
billing_address = shipping_address
|
||||
|
||||
elif billing_address and not shipping_address:
|
||||
billing_address = Address.objects.get(uuid=billing_address)
|
||||
shipping_address = billing_address
|
||||
|
||||
else:
|
||||
billing_address = Address.objects.get(uuid=billing_address)
|
||||
shipping_address = Address.objects.get(uuid=shipping_address)
|
||||
|
||||
self.billing_address = billing_address
|
||||
self.shipping_address = shipping_address
|
||||
self.save()
|
||||
self.apply_addresses(billing_address, shipping_address)
|
||||
|
||||
if self.total_quantity < 1:
|
||||
raise ValueError(_("you cannot purchase an empty order!"))
|
||||
|
|
@ -709,49 +717,10 @@ class Order(NiceModel):
|
|||
if payment_method not in cache.get("payment_methods"):
|
||||
raise ValueError(_("invalid payment method"))
|
||||
|
||||
billing_customer_address = kwargs.pop("billing_customer_address")
|
||||
billing_customer_city = billing_customer_address.pop("customer_city")
|
||||
billing_customer_country = billing_customer_address.pop("customer_country")
|
||||
billing_customer_postal_code = billing_customer_address.pop("customer_postal_code")
|
||||
billing_customer_address_line = billing_customer_address.pop("customer_address_line")
|
||||
billing_customer_address_uuid = kwargs.get("billing_customer_address")
|
||||
shipping_customer_address_uuid = kwargs.get("shipping_customer_address")
|
||||
|
||||
if not all(
|
||||
[
|
||||
billing_customer_city,
|
||||
billing_customer_country,
|
||||
billing_customer_postal_code,
|
||||
billing_customer_address_line,
|
||||
]
|
||||
):
|
||||
raise ValueError(_("you cannot create a momental order without providing a billing address"))
|
||||
|
||||
billing_address = Address.objects.get_or_create(
|
||||
user=None,
|
||||
country=Country.objects.get(code=billing_customer_country),
|
||||
region=Region.objects.get(code=billing_customer_city),
|
||||
city=City.objects.get(name=billing_customer_city),
|
||||
postal_code=PostalCode.objects.get(code=billing_customer_postal_code),
|
||||
street=billing_customer_address_line,
|
||||
)
|
||||
|
||||
shipping_customer_address = kwargs.pop("shipping_customer_address")
|
||||
shipping_customer_city = shipping_customer_address.pop("customer_city")
|
||||
shipping_customer_country = shipping_customer_address.pop("customer_country")
|
||||
shipping_customer_postal_code = shipping_customer_address.pop("customer_postal_code")
|
||||
shipping_customer_address_line = shipping_customer_address.pop("сustomer_address_line")
|
||||
|
||||
if not shipping_customer_address:
|
||||
shipping_address = billing_address
|
||||
|
||||
else:
|
||||
shipping_address = Address.objects.get_or_create(
|
||||
user=None,
|
||||
country=Country.objects.get(code=shipping_customer_country),
|
||||
region=Region.objects.get(code=shipping_customer_city),
|
||||
city=City.objects.get(name=billing_customer_city),
|
||||
postal_code=PostalCode.objects.get(code=shipping_customer_postal_code),
|
||||
street=shipping_customer_address_line,
|
||||
)
|
||||
self.apply_addresses(billing_customer_address_uuid, shipping_customer_address_uuid)
|
||||
|
||||
for product_uuid in products:
|
||||
self.add_product(product_uuid)
|
||||
|
|
@ -759,8 +728,6 @@ class Order(NiceModel):
|
|||
amount = self.apply_promocode(promocode_uuid) if promocode_uuid else self.total_price
|
||||
|
||||
self.status = "CREATED"
|
||||
self.shipping_address = shipping_address
|
||||
self.billing_address = billing_address
|
||||
self.attributes.update(
|
||||
{
|
||||
"customer_name": customer_name,
|
||||
|
|
@ -1042,9 +1009,12 @@ class PromoCode(NiceModel):
|
|||
return "percent"
|
||||
|
||||
def use(self, order: Order) -> float:
|
||||
|
||||
if self.used_on:
|
||||
raise ValueError(_("promocode already used"))
|
||||
|
||||
amount = order.total_price
|
||||
|
||||
match self.discount_type:
|
||||
case "percent":
|
||||
amount -= round(amount * (self.discount_percent / 100), 2)
|
||||
|
|
@ -1056,7 +1026,8 @@ class PromoCode(NiceModel):
|
|||
order.save()
|
||||
case _:
|
||||
raise ValueError(_(f"invalid discount type for promocode {self.uuid}"))
|
||||
self.used_on = datetime.datetime(datetime.datetime(self.used_on).year, 1, 1)
|
||||
|
||||
self.used_on = datetime.datetime.now()
|
||||
self.save()
|
||||
return amount
|
||||
|
||||
|
|
@ -1196,6 +1167,7 @@ class Wishlist(NiceModel):
|
|||
product = Product.objects.get(uuid=product_uuid)
|
||||
if product not in self.products.all():
|
||||
return self
|
||||
self.products.remove(product)
|
||||
except Product.DoesNotExist:
|
||||
name = "Product"
|
||||
raise Http404(_(f"{name} does not exist: {product_uuid}"))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
from rest_framework.fields import BooleanField, CharField, Field, IntegerField, JSONField, ListField, UUIDField
|
||||
from rest_framework.serializers import ListSerializer, Serializer
|
||||
|
||||
from geo.serializers import UnregisteredCustomerAddressSerializer
|
||||
|
||||
from .detail import * # noqa: F403
|
||||
from .simple import * # noqa: F403
|
||||
|
||||
|
|
@ -80,8 +78,8 @@ class BuyUnregisteredOrderSerializer(Serializer):
|
|||
customer_name = CharField(required=True)
|
||||
customer_email = CharField(required=True)
|
||||
customer_phone_number = CharField(required=True)
|
||||
billing_customer_address = UnregisteredCustomerAddressSerializer(required=True)
|
||||
shipping_customer_address = UnregisteredCustomerAddressSerializer(required=False)
|
||||
billing_customer_address_uuid = CharField(required=False)
|
||||
shipping_customer_address_uuid = CharField(required=False)
|
||||
payment_method = CharField(required=True)
|
||||
|
||||
|
||||
|
|
@ -90,6 +88,6 @@ class BuyAsBusinessOrderSerializer(Serializer):
|
|||
business_inn = CharField(required=True)
|
||||
business_email = CharField(required=True)
|
||||
business_phone_number = CharField(required=True)
|
||||
billing_business_address = UnregisteredCustomerAddressSerializer(required=True)
|
||||
shipping_business_address = UnregisteredCustomerAddressSerializer(required=False)
|
||||
billing_business_address_uuid = CharField(required=False)
|
||||
shipping_business_address_uuid = CharField(required=False)
|
||||
payment_method = CharField(required=True)
|
||||
|
|
|
|||
|
|
@ -214,8 +214,8 @@ class BuyAsBusinessView(APIView):
|
|||
customer_name=serializer.validated_data.get("customer_name"),
|
||||
customer_email=serializer.validated_data.get("customer_email"),
|
||||
customer_phone=serializer.validated_data.get("customer_phone"),
|
||||
customer_billing_address=serializer.validated_data.get("customer_billing_address"),
|
||||
customer_shipping_address=serializer.validated_data.get("customer_shipping_address"),
|
||||
customer_billing_address=serializer.validated_data.get("customer_billing_address_uuid"),
|
||||
customer_shipping_address=serializer.validated_data.get("customer_shipping_address_uuid"),
|
||||
payment_method=serializer.validated_data.get("payment_method"),
|
||||
is_business=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -269,8 +269,8 @@ class OrderViewSet(EvibesViewSet):
|
|||
customer_name=serializer.validated_data.get("customer_name"),
|
||||
customer_email=serializer.validated_data.get("customer_email"),
|
||||
customer_phone=serializer.validated_data.get("customer_phone"),
|
||||
customer_billing_address=serializer.validated_data.get("customer_billing_address"),
|
||||
customer_shipping_address=serializer.validated_data.get("customer_shipping_address"),
|
||||
billing_customer_address=serializer.validated_data.get("billing_customer_address_uuid"),
|
||||
shipping_customer_address=serializer.validated_data.get("shipping_customer_address_uuid"),
|
||||
payment_method=serializer.validated_data.get("payment_method"),
|
||||
)
|
||||
return Response(status=status.HTTP_202_ACCEPTED, data=TransactionProcessSerializer(transaction).data)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ CORS_ALLOW_HEADERS = [
|
|||
]
|
||||
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") if not DEBUG else None
|
||||
X_FRAME_OPTIONS = "SAMEORIGIN"
|
||||
|
||||
UNSAFE_CACHE_KEYS = []
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ CONSTANCE_CONFIG = {
|
|||
"PAYMENT_GATEWAY_MINIMUM": (getenv("PAYMENT_GATEWAY_MINIMUM", 5.0), "Payment gateway minimum amount"),
|
||||
"PAYMENT_GATEWAY_MAXIMUM": (getenv("PAYMENT_GATEWAY_MAXIMUM", 500.0), "Payment gateway maximum amount"),
|
||||
"EXCHANGE_RATE_API_KEY": (getenv("EXCHANGE_RATE_API_KEY", "example token"), "Exchange rate API key"),
|
||||
"NOMINATIM_URL": (getenv("NOMINATIM_URL", ""), "OpenStreetMap Nominatim API URL"),
|
||||
"OPENAI_API_KEY": (getenv("OPENAI_API_KEY", "example key"), "OpenAI API Key"),
|
||||
"ABSTRACT_API_KEY": (
|
||||
getenv("ABSTRACT_API_KEY", "example key"),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,4 @@ ELASTICSEARCH_DSL = {
|
|||
|
||||
ELASTICSEARCH_DSL_AUTOSYNC = True
|
||||
ELASTICSEARCH_DSL_PARALLEL = False
|
||||
ELASTICSEARCH_DSL_SIGNAL_PROCESSOR = (
|
||||
"django_elasticsearch_dsl.signals.CelerySignalProcessor"
|
||||
)
|
||||
ELASTICSEARCH_DSL_SIGNAL_PROCESSOR = "evibes.signal_processors.SelectiveSignalProcessor"
|
||||
|
|
|
|||
21
evibes/signal_processors.py
Normal file
21
evibes/signal_processors.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from django.db import models
|
||||
from django_elasticsearch_dsl.registries import registry
|
||||
from django_elasticsearch_dsl.signals import CelerySignalProcessor
|
||||
|
||||
|
||||
class SelectiveSignalProcessor(CelerySignalProcessor):
|
||||
def setup(self):
|
||||
for doc in registry.get_documents():
|
||||
model = doc.django.model
|
||||
models.signals.post_save.connect(self.handle_save, sender=model, weak=False)
|
||||
models.signals.post_delete.connect(self.handle_delete, sender=model, weak=False)
|
||||
models.signals.pre_delete.connect(self.handle_pre_delete, sender=model, weak=False)
|
||||
models.signals.m2m_changed.connect(self.handle_m2m_changed, sender=model, weak=False)
|
||||
|
||||
def teardown(self):
|
||||
for doc in registry.get_documents():
|
||||
model = doc.django.model
|
||||
models.signals.post_save.disconnect(self.handle_save, sender=model)
|
||||
models.signals.post_delete.disconnect(self.handle_delete, sender=model)
|
||||
models.signals.pre_delete.disconnect(self.handle_pre_delete, sender=model)
|
||||
models.signals.m2m_changed.disconnect(self.handle_m2m_changed, sender=model)
|
||||
121
geo/admin.py
121
geo/admin.py
|
|
@ -1,116 +1,19 @@
|
|||
import swapper
|
||||
from django.contrib import admin
|
||||
from django.contrib.gis.admin import GISModelAdmin
|
||||
|
||||
from core.admin import BasicModelAdmin
|
||||
|
||||
from .models import (
|
||||
Address,
|
||||
AlternativeName,
|
||||
City,
|
||||
Continent,
|
||||
Country,
|
||||
District,
|
||||
PostalCode,
|
||||
Region,
|
||||
Subregion,
|
||||
)
|
||||
|
||||
|
||||
class CitiesAdmin(admin.ModelAdmin):
|
||||
raw_id_fields = ["alt_names"]
|
||||
|
||||
|
||||
class ContinentAdmin(CitiesAdmin):
|
||||
list_display = ["name", "code"]
|
||||
|
||||
|
||||
class CountryAdmin(CitiesAdmin):
|
||||
list_display = [
|
||||
"name",
|
||||
"code",
|
||||
"code3",
|
||||
"tld",
|
||||
"phone",
|
||||
"continent",
|
||||
"area",
|
||||
"population",
|
||||
]
|
||||
search_fields = ["name", "code", "code3", "tld", "phone"]
|
||||
filter_horizontal = ["neighbours"]
|
||||
|
||||
|
||||
class RegionAdmin(CitiesAdmin):
|
||||
ordering = ["name_std"]
|
||||
list_display = ["name_std", "code", "country"]
|
||||
search_fields = ["name", "name_std", "code"]
|
||||
|
||||
|
||||
class SubregionAdmin(CitiesAdmin):
|
||||
ordering = ["name_std"]
|
||||
list_display = ["name_std", "code", "region"]
|
||||
search_fields = ["name", "name_std", "code"]
|
||||
raw_id_fields = ["alt_names", "region"]
|
||||
|
||||
|
||||
class CityAdmin(CitiesAdmin):
|
||||
ordering = ["name_std"]
|
||||
list_display = ["name_std", "subregion", "region", "country", "population"]
|
||||
search_fields = ["name", "name_std"]
|
||||
raw_id_fields = ["alt_names", "region", "subregion"]
|
||||
|
||||
|
||||
class DistrictAdmin(CitiesAdmin):
|
||||
raw_id_fields = ["alt_names", "city"]
|
||||
list_display = ["name_std", "city"]
|
||||
search_fields = ["name", "name_std"]
|
||||
|
||||
|
||||
class AltNameAdmin(admin.ModelAdmin):
|
||||
ordering = ["name"]
|
||||
list_display = ["name", "language_code", "is_preferred", "is_short", "is_historic"]
|
||||
list_filter = ["is_preferred", "is_short", "is_historic", "language_code"]
|
||||
search_fields = ["name"]
|
||||
|
||||
|
||||
class PostalCodeAdmin(CitiesAdmin):
|
||||
ordering = ["code"]
|
||||
list_display = ["code", "subregion_name", "region_name", "country"]
|
||||
search_fields = [
|
||||
"code",
|
||||
"country__name",
|
||||
"region_name",
|
||||
"subregion_name",
|
||||
"region__name",
|
||||
]
|
||||
from .models import Address
|
||||
|
||||
|
||||
@admin.register(Address)
|
||||
class AddressAdmin(BasicModelAdmin, GISModelAdmin):
|
||||
list_display = ("street", "city", "region", "postal_code", "country", "user")
|
||||
search_fields = (
|
||||
"street",
|
||||
"city__name",
|
||||
"postal_code__name",
|
||||
"country__name",
|
||||
"user__email",
|
||||
)
|
||||
list_filter = ("city", "region", "country")
|
||||
ordering = ("country", "city", "street")
|
||||
class AddressAdmin(GISModelAdmin):
|
||||
list_display = ("street", "city", "region", "country", "user")
|
||||
list_filter = ("country", "region")
|
||||
search_fields = ("raw_data", "street", "city", "postal_code", "user__email")
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
return qs.prefetch_related("country", "city", "region", "postal_code", "user")
|
||||
|
||||
|
||||
if not swapper.is_swapped("geo", "Continent"):
|
||||
admin.site.register(Continent, ContinentAdmin)
|
||||
if not swapper.is_swapped("geo", "Country"):
|
||||
admin.site.register(Country, CountryAdmin)
|
||||
admin.site.register(Region, RegionAdmin)
|
||||
admin.site.register(Subregion, SubregionAdmin)
|
||||
if not swapper.is_swapped("geo", "City"):
|
||||
admin.site.register(City, CityAdmin)
|
||||
admin.site.register(District, DistrictAdmin)
|
||||
admin.site.register(AlternativeName, AltNameAdmin)
|
||||
admin.site.register(PostalCode, PostalCodeAdmin)
|
||||
gis_widget_kwargs = {
|
||||
"attrs": {
|
||||
"default_lon": 37.61556,
|
||||
"default_lat": 55.75222,
|
||||
"default_zoom": 6,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
730
geo/conf.py
730
geo/conf.py
|
|
@ -1,730 +0,0 @@
|
|||
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])))
|
||||
File diff suppressed because it is too large
Load diff
47206
geo/data/admin2Codes.txt
47206
geo/data/admin2Codes.txt
File diff suppressed because it is too large
Load diff
|
|
@ -1,302 +0,0 @@
|
|||
# ================================
|
||||
#
|
||||
#
|
||||
# CountryCodes:
|
||||
# ============
|
||||
#
|
||||
# The official ISO country code for the United Kingdom is 'GB'. The code 'UK' is reserved.
|
||||
#
|
||||
# A list of dependent countries is available here:
|
||||
# https://spreadsheets.google.com/ccc?key=pJpyPy-J5JSNhe7F_KxwiCA&hl=en
|
||||
#
|
||||
#
|
||||
# The countrycode XK temporarily stands for Kosvo:
|
||||
# http://geonames.wordpress.com/2010/03/08/xk-country-code-for-kosovo/
|
||||
#
|
||||
#
|
||||
# CS (Serbia and Montenegro) with geonameId = 8505033 no longer exists.
|
||||
# AN (the Netherlands Antilles) with geonameId = 8505032 was dissolved on 10 October 2010.
|
||||
#
|
||||
#
|
||||
# Currencies :
|
||||
# ============
|
||||
#
|
||||
# A number of territories are not included in ISO 4217, because their currencies are not per se an independent currency,
|
||||
# but a variant of another currency. These currencies are:
|
||||
#
|
||||
# 1. FO : Faroese krona (1:1 pegged to the Danish krone)
|
||||
# 2. GG : Guernsey pound (1:1 pegged to the pound sterling)
|
||||
# 3. JE : Jersey pound (1:1 pegged to the pound sterling)
|
||||
# 4. IM : Isle of Man pound (1:1 pegged to the pound sterling)
|
||||
# 5. TV : Tuvaluan dollar (1:1 pegged to the Australian dollar).
|
||||
# 6. CK : Cook Islands dollar (1:1 pegged to the New Zealand dollar).
|
||||
#
|
||||
# The following non-ISO codes are, however, sometimes used: GGP for the Guernsey pound,
|
||||
# JEP for the Jersey pound and IMP for the Isle of Man pound (http://en.wikipedia.org/wiki/ISO_4217)
|
||||
#
|
||||
#
|
||||
# A list of currency symbols is available here : http://forum.geonames.org/gforum/posts/list/437.page
|
||||
# another list with fractional units is here: http://forum.geonames.org/gforum/posts/list/1961.page
|
||||
#
|
||||
#
|
||||
# Languages :
|
||||
# ===========
|
||||
#
|
||||
# The column 'languages' lists the languages spoken in a country ordered by the number of speakers. The language code is a 'locale'
|
||||
# where any two-letter primary-tag is an ISO-639 language abbreviation and any two-letter initial subtag is an ISO-3166 country code.
|
||||
#
|
||||
# Example : es-AR is the Spanish variant spoken in Argentina.
|
||||
#
|
||||
#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
|
||||
AD AND 020 AN Andorra Andorra la Vella 468 77006 EU .ad EUR Euro 376 AD### ^(?:AD)*(\d{3})$ ca 3041565 ES,FR
|
||||
AE ARE 784 AE United Arab Emirates Abu Dhabi 82880 9630959 AS .ae AED Dirham 971 ##### ##### ^\d{5}-\d{5}$ ar-AE,fa,en,hi,ur 290557 SA,OM
|
||||
AF AFG 004 AF Afghanistan Kabul 647500 37172386 AS .af AFN Afghani 93 fa-AF,ps,uz-AF,tk 1149361 TM,CN,IR,TJ,PK,UZ
|
||||
AG ATG 028 AC Antigua and Barbuda St. John's 443 96286 NA .ag XCD Dollar +1-268 en-AG 3576396
|
||||
AI AIA 660 AV Anguilla The Valley 102 13254 NA .ai XCD Dollar +1-264 AI-#### ^(?:AZ)*(\d{4})$ en-AI 3573511
|
||||
AL ALB 008 AL Albania Tirana 28748 2866376 EU .al ALL Lek 355 #### ^(\d{4})$ sq,el 783754 MK,GR,ME,RS,XK
|
||||
AM ARM 051 AM Armenia Yerevan 29800 2951776 AS .am AMD Dram 374 ###### ^(\d{6})$ hy 174982 GE,IR,AZ,TR
|
||||
AO AGO 024 AO Angola Luanda 1246700 30809762 AF .ao AOA Kwanza 244 pt-AO 3351879 CD,NA,ZM,CG
|
||||
AQ ATA 010 AY Antarctica 14000000 0 AN .aq 6697173
|
||||
AR ARG 032 AR Argentina Buenos Aires 2766890 44494502 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
|
||||
AS ASM 016 AQ American Samoa Pago Pago 199 55465 OC .as USD Dollar +1-684 #####-#### 96799 en-AS,sm,to 5880801
|
||||
AT AUT 040 AU Austria Vienna 83858 8847037 EU .at EUR Euro 43 #### ^(\d{4})$ de-AT,hr,hu,sl 2782113 CH,DE,HU,SK,CZ,IT,SI,LI
|
||||
AU AUS 036 AS Australia Canberra 7686850 24992369 OC .au AUD Dollar 61 #### ^(\d{4})$ en-AU 2077456
|
||||
AW ABW 533 AA Aruba Oranjestad 193 105845 NA .aw AWG Guilder 297 nl-AW,pap,es,en 3577279
|
||||
AX ALA 248 Aland Islands Mariehamn 1580 26711 EU .ax EUR Euro +358-18 ##### ^(?:FI)*(\d{5})$ sv-AX 661882 FI
|
||||
AZ AZE 031 AJ Azerbaijan Baku 86600 9942334 AS .az AZN Manat 994 AZ #### ^(?:AZ )*(\d{4})$ az,ru,hy 587116 GE,IR,AM,TR,RU
|
||||
BA BIH 070 BK Bosnia and Herzegovina Sarajevo 51129 3323929 EU .ba BAM Marka 387 ##### ^(\d{5})$ bs,hr-BA,sr-BA 3277605 HR,ME,RS
|
||||
BB BRB 052 BB Barbados Bridgetown 431 286641 NA .bb BBD Dollar +1-246 BB##### ^(?:BB)*(\d{5})$ en-BB 3374084
|
||||
BD BGD 050 BG Bangladesh Dhaka 144000 161356039 AS .bd BDT Taka 880 #### ^(\d{4})$ bn-BD,en 1210997 MM,IN
|
||||
BE BEL 056 BE Belgium Brussels 30510 11422068 EU .be EUR Euro 32 #### ^(\d{4})$ nl-BE,fr-BE,de-BE 2802361 DE,NL,LU,FR
|
||||
BF BFA 854 UV Burkina Faso Ouagadougou 274200 19751535 AF .bf XOF Franc 226 fr-BF,mos 2361809 NE,BJ,GH,CI,TG,ML
|
||||
BG BGR 100 BU Bulgaria Sofia 110910 7000039 EU .bg BGN Lev 359 #### ^(\d{4})$ bg,tr-BG,rom 732800 MK,GR,RO,TR,RS
|
||||
BH BHR 048 BA Bahrain Manama 665 1569439 AS .bh BHD Dinar 973 ####|### ^(\d{3}\d?)$ ar-BH,en,fa,ur 290291
|
||||
BI BDI 108 BY Burundi Gitega 27830 11175378 AF .bi BIF Franc 257 fr-BI,rn 433561 TZ,CD,RW
|
||||
BJ BEN 204 BN Benin Porto-Novo 112620 11485048 AF .bj XOF Franc 229 fr-BJ 2395170 NE,TG,BF,NG
|
||||
BL BLM 652 TB Saint Barthelemy Gustavia 21 8450 NA .gp EUR Euro 590 ##### ^(\d{5})$ fr 3578476
|
||||
BM BMU 060 BD Bermuda Hamilton 53 63968 NA .bm BMD Dollar +1-441 @@ ## ^([A-Z]{2}\d{2})$ en-BM,pt 3573345
|
||||
BN BRN 096 BX Brunei Bandar Seri Begawan 5770 428962 AS .bn BND Dollar 673 @@#### ^([A-Z]{2}\d{4})$ ms-BN,en-BN 1820814 MY
|
||||
BO BOL 068 BL Bolivia Sucre 1098580 11353142 SA .bo BOB Boliviano 591 es-BO,qu,ay 3923057 PE,CL,PY,BR,AR
|
||||
BQ BES 535 Bonaire, Saint Eustatius and Saba 328 18012 NA .bq USD Dollar 599 nl,pap,en 7626844
|
||||
BR BRA 076 BR Brazil Brasilia 8511965 209469333 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
|
||||
BS BHS 044 BF Bahamas Nassau 13940 385640 NA .bs BSD Dollar +1-242 en-BS 3572887
|
||||
BT BTN 064 BT Bhutan Thimphu 47000 754394 AS .bt BTN Ngultrum 975 dz 1252634 CN,IN
|
||||
BV BVT 074 BV Bouvet Island 49 0 AN .bv NOK Krone 3371123
|
||||
BW BWA 072 BC Botswana Gaborone 600370 2254126 AF .bw BWP Pula 267 en-BW,tn-BW 933860 ZW,ZA,NA
|
||||
BY BLR 112 BO Belarus Minsk 207600 9485386 EU .by BYN Belarusian ruble 375 ###### ^(\d{6})$ be,ru 630336 PL,LT,UA,RU,LV
|
||||
BZ BLZ 084 BH Belize Belmopan 22966 383071 NA .bz BZD Dollar 501 en-BZ,es 3582678 GT,MX
|
||||
CA CAN 124 CA Canada Ottawa 9984670 37058856 NA .ca CAD Dollar 1 @#@ #@# ^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ]) ?(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$ en-CA,fr-CA,iu 6251999 US
|
||||
CC CCK 166 CK Cocos Islands West Island 14 628 AS .cc AUD Dollar 61 #### ^(\d{4})$ ms-CC,en 1547376
|
||||
CD COD 180 CG Democratic Republic of the Congo Kinshasa 2345410 84068091 AF .cd CDF Franc 243 fr-CD,ln,ktu,kg,sw,lua 203312 TZ,CF,SS,RW,ZM,BI,UG,CG,AO
|
||||
CF CAF 140 CT Central African Republic Bangui 622984 4666377 AF .cf XAF Franc 236 fr-CF,sg,ln,kg 239880 TD,SD,CD,SS,CM,CG
|
||||
CG COG 178 CF Republic of the Congo Brazzaville 342000 5244363 AF .cg XAF Franc 242 fr-CG,kg,ln-CG 2260494 CF,GA,CD,CM,AO
|
||||
CH CHE 756 SZ Switzerland Bern 41290 8516543 EU .ch CHF Franc 41 #### ^(\d{4})$ de-CH,fr-CH,it-CH,rm 2658434 DE,IT,LI,FR,AT
|
||||
CI CIV 384 IV Ivory Coast Yamoussoukro 322460 25069229 AF .ci XOF Franc 225 fr-CI 2287781 LR,GH,GN,BF,ML
|
||||
CK COK 184 CW Cook Islands Avarua 240 21388 OC .ck NZD Dollar 682 en-CK,mi 1899402
|
||||
CL CHL 152 CI Chile Santiago 756950 18729160 SA .cl CLP Peso 56 ####### ^(\d{7})$ es-CL 3895114 PE,BO,AR
|
||||
CM CMR 120 CM Cameroon Yaounde 475440 25216237 AF .cm XAF Franc 237 en-CM,fr-CM 2233387 TD,CF,GA,GQ,CG,NG
|
||||
CN CHN 156 CH China Beijing 9596960 1411778724 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
|
||||
CO COL 170 CO Colombia Bogota 1138910 49648685 SA .co COP Peso 57 ###### ^(\d{6})$ es-CO 3686110 EC,PE,PA,BR,VE
|
||||
CR CRI 188 CS Costa Rica San Jose 51100 4999441 NA .cr CRC Colon 506 ##### ^(\d{5})$ es-CR,en 3624060 PA,NI
|
||||
CU CUB 192 CU Cuba Havana 110860 11338138 NA .cu CUP Peso 53 CP ##### ^(?:CP)*(\d{5})$ es-CU,pap 3562981 US
|
||||
CV CPV 132 CV Cabo Verde Praia 4033 543767 AF .cv CVE Escudo 238 #### ^(\d{4})$ pt-CV 3374766
|
||||
CW CUW 531 UC Curacao Willemstad 444 159849 NA .cw ANG Guilder 599 nl,pap 7626836
|
||||
CX CXR 162 KT Christmas Island Flying Fish Cove 135 1500 OC .cx AUD Dollar 61 #### ^(\d{4})$ en,zh,ms-CX 2078138
|
||||
CY CYP 196 CY Cyprus Nicosia 9250 1189265 EU .cy EUR Euro 357 #### ^(\d{4})$ el-CY,tr-CY,en 146669
|
||||
CZ CZE 203 EZ Czechia Prague 78866 10625695 EU .cz CZK Koruna 420 ### ## ^\d{3}\s?\d{2}$ cs,sk 3077311 PL,DE,SK,AT
|
||||
DE DEU 276 GM Germany Berlin 357021 82927922 EU .de EUR Euro 49 ##### ^(\d{5})$ de 2921044 CH,PL,NL,DK,BE,CZ,LU,FR,AT
|
||||
DJ DJI 262 DJ Djibouti Djibouti 23000 958920 AF .dj DJF Franc 253 fr-DJ,ar,so-DJ,aa 223816 ER,ET,SO
|
||||
DK DNK 208 DA Denmark Copenhagen 43094 5797446 EU .dk DKK Krone 45 #### ^(\d{4})$ da-DK,en,fo,de-DK 2623032 DE
|
||||
DM DMA 212 DO Dominica Roseau 754 71625 NA .dm XCD Dollar +1-767 en-DM 3575830
|
||||
DO DOM 214 DR Dominican Republic Santo Domingo 48730 10627165 NA .do DOP Peso +1-809 and 1-829 ##### ^(\d{5})$ es-DO 3508796 HT
|
||||
DZ DZA 012 AG Algeria Algiers 2381740 42228429 AF .dz DZD Dinar 213 ##### ^(\d{5})$ ar-DZ 2589581 NE,EH,LY,MR,TN,MA,ML
|
||||
EC ECU 218 EC Ecuador Quito 283560 17084357 SA .ec USD Dollar 593 @####@ ^([a-zA-Z]\d{4}[a-zA-Z])$ es-EC 3658394 PE,CO
|
||||
EE EST 233 EN Estonia Tallinn 45226 1320884 EU .ee EUR Euro 372 ##### ^(\d{5})$ et,ru 453733 RU,LV
|
||||
EG EGY 818 EG Egypt Cairo 1001450 98423595 AF .eg EGP Pound 20 ##### ^(\d{5})$ ar-EG,en,fr 357994 LY,SD,IL,PS
|
||||
EH ESH 732 WI Western Sahara El-Aaiun 266000 273008 AF .eh MAD Dirham 212 ar,mey 2461445 DZ,MR,MA
|
||||
ER ERI 232 ER Eritrea Asmara 121320 6209262 AF .er ERN Nakfa 291 aa-ER,ar,tig,kun,ti-ER 338010 ET,SD,DJ
|
||||
ES ESP 724 SP Spain Madrid 504782 46723749 EU .es EUR Euro 34 ##### ^(\d{5})$ es-ES,ca,gl,eu,oc 2510769 AD,PT,GI,FR,MA
|
||||
ET ETH 231 ET Ethiopia Addis Ababa 1127127 109224559 AF .et ETB Birr 251 #### ^(\d{4})$ am,en-ET,om-ET,ti-ET,so-ET,sid 337996 ER,KE,SD,SS,SO,DJ
|
||||
FI FIN 246 FI Finland Helsinki 337030 5518050 EU .fi EUR Euro 358 ##### ^(?:FI)*(\d{5})$ fi-FI,sv-FI,smn 660013 NO,RU,SE
|
||||
FJ FJI 242 FJ Fiji Suva 18270 883483 OC .fj FJD Dollar 679 en-FJ,fj 2205218
|
||||
FK FLK 238 FK Falkland Islands Stanley 12173 2638 SA .fk FKP Pound 500 FIQQ 1ZZ FIQQ 1ZZ en-FK 3474414
|
||||
FM FSM 583 FM Micronesia Palikir 702 112640 OC .fm USD Dollar 691 ##### ^(\d{5})$ en-FM,chk,pon,yap,kos,uli,woe,nkr,kpg 2081918
|
||||
FO FRO 234 FO Faroe Islands Torshavn 1399 48497 EU .fo DKK Krone 298 ### ^(?:FO)*(\d{3})$ fo,da-FO 2622320
|
||||
FR FRA 250 FR France Paris 547030 66987244 EU .fr EUR Euro 33 ##### ^(\d{5})$ fr-FR,frp,br,co,ca,eu,oc 3017382 CH,DE,BE,LU,IT,AD,MC,ES
|
||||
GA GAB 266 GB Gabon Libreville 267667 2119275 AF .ga XAF Franc 241 fr-GA 2400553 CM,GQ,CG
|
||||
GB GBR 826 UK United Kingdom London 244820 66488991 EU .uk GBP Pound 44 @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA ^([Gg][Ii][Rr]\s?0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))\s?[0-9][A-Za-z]{2})$ en-GB,cy-GB,gd 2635167 IE
|
||||
GD GRD 308 GJ Grenada St. George's 344 111454 NA .gd XCD Dollar +1-473 en-GD 3580239
|
||||
GE GEO 268 GG Georgia Tbilisi 69700 3731000 AS .ge GEL Lari 995 #### ^(\d{4})$ ka,ru,hy,az 614540 AM,AZ,TR,RU
|
||||
GF GUF 254 FG French Guiana Cayenne 91000 195506 SA .gf EUR Euro 594 ##### ^((97|98)3\d{2})$ fr-GF 3381670 SR,BR
|
||||
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,nrf 3042362
|
||||
GH GHA 288 GH Ghana Accra 239460 29767108 AF .gh GHS Cedi 233 en-GH,ak,ee,tw 2300660 CI,TG,BF
|
||||
GI GIB 292 GI Gibraltar Gibraltar 6.5 33718 EU .gi GIP Pound 350 GX11 1AA GX11 1AA en-GI,es,it,pt 2411586 ES
|
||||
GL GRL 304 GL Greenland Nuuk 2166086 56025 NA .gl DKK Krone 299 #### ^(\d{4})$ kl,da-GL,en 3425505
|
||||
GM GMB 270 GA Gambia Banjul 11300 2280102 AF .gm GMD Dalasi 220 en-GM,mnk,wof,wo,ff 2413451 SN
|
||||
GN GIN 324 GV Guinea Conakry 245857 12414318 AF .gn GNF Franc 224 fr-GN 2420477 LR,SN,SL,CI,GW,ML
|
||||
GP GLP 312 GP Guadeloupe Basse-Terre 1780 443000 NA .gp EUR Euro 590 ##### ^((97|98)\d{3})$ fr-GP 3579143
|
||||
GQ GNQ 226 EK Equatorial Guinea Malabo 28051 1308974 AF .gq XAF Franc 240 es-GQ,fr,pt 2309096 GA,CM
|
||||
GR GRC 300 GR Greece Athens 131940 10727668 EU .gr EUR Euro 30 ### ## ^(\d{5})$ el-GR,en,fr 390903 AL,MK,TR,BG
|
||||
GS SGS 239 SX South Georgia and the South Sandwich Islands Grytviken 3903 30 AN .gs GBP Pound SIQQ 1ZZ SIQQ 1ZZ en 3474415
|
||||
GT GTM 320 GT Guatemala Guatemala City 108890 17247807 NA .gt GTQ Quetzal 502 ##### ^(\d{5})$ es-GT 3595528 MX,HN,BZ,SV
|
||||
GU GUM 316 GQ Guam Hagatna 549 165768 OC .gu USD Dollar +1-671 969## ^(969\d{2})$ en-GU,ch-GU 4043988
|
||||
GW GNB 624 PU Guinea-Bissau Bissau 36120 1874309 AF .gw XOF Franc 245 #### ^(\d{4})$ pt-GW,pov 2372248 SN,GN
|
||||
GY GUY 328 GY Guyana Georgetown 214970 779004 SA .gy GYD Dollar 592 en-GY 3378535 SR,BR,VE
|
||||
HK HKG 344 HK Hong Kong Hong Kong 1092 7491609 AS .hk HKD Dollar 852 ###### ^(\d{6})$ zh-HK,yue,zh,en 1819730
|
||||
HM HMD 334 HM Heard Island and McDonald Islands 412 0 AN .hm AUD Dollar #### ^(\d{4})$ 1547314
|
||||
HN HND 340 HO Honduras Tegucigalpa 112090 9587522 NA .hn HNL Lempira 504 ##### ^(\d{6})$ es-HN,cab,miq 3608932 GT,NI,SV
|
||||
HR HRV 191 HR Croatia Zagreb 56542 3871833 EU .hr EUR Euro 385 ##### ^(?:HR)*(\d{5})$ hr-HR,sr 3202326 HU,SI,BA,ME,RS
|
||||
HT HTI 332 HA Haiti Port-au-Prince 27750 11123176 NA .ht HTG Gourde 509 HT#### ^(?:HT)*(\d{4})$ ht,fr-HT 3723988 DO
|
||||
HU HUN 348 HU Hungary Budapest 93030 9768785 EU .hu HUF Forint 36 #### ^(\d{4})$ hu-HU 719819 SK,SI,RO,UA,HR,AT,RS
|
||||
ID IDN 360 ID Indonesia Jakarta 1919440 267663435 AS .id IDR Rupiah 62 ##### ^(\d{5})$ id,en,nl,jv 1643084 PG,TL,MY
|
||||
IE IRL 372 EI Ireland Dublin 70280 4853506 EU .ie EUR Euro 353 @@@ @@@@ ^(D6W|[AC-FHKNPRTV-Y][0-9]{2})\s?([AC-FHKNPRTV-Y0-9]{4}) en-IE,ga-IE 2963597 GB
|
||||
IL ISR 376 IS Israel Jerusalem 20770 8883800 AS .il ILS Shekel 972 ####### ^(\d{7}|\d{5})$ he,ar-IL,en-IL, 294640 SY,JO,LB,EG,PS
|
||||
IM IMN 833 IM Isle of Man Douglas 572 84077 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
|
||||
IN IND 356 IN India New Delhi 3287590 1352617328 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
|
||||
IO IOT 086 IO British Indian Ocean Territory Diego Garcia 60 4000 AS .io USD Dollar 246 BBND 1ZZ BBND 1ZZ en-IO 1282588
|
||||
IQ IRQ 368 IZ Iraq Baghdad 437072 38433600 AS .iq IQD Dinar 964 ##### ^(\d{5})$ ar-IQ,ku,hy 99237 SY,SA,IR,JO,TR,KW
|
||||
IR IRN 364 IR Iran Tehran 1648000 81800269 AS .ir IRR Rial 98 ########## ^(\d{10})$ fa-IR,ku 130758 TM,AF,IQ,AM,PK,AZ,TR
|
||||
IS ISL 352 IC Iceland Reykjavik 103000 353574 EU .is ISK Krona 354 ### ^(\d{3})$ is,en,de,da,sv,no 2629691
|
||||
IT ITA 380 IT Italy Rome 301230 60431283 EU .it EUR Euro 39 ##### ^(\d{5})$ it-IT,de-IT,fr-IT,sc,ca,co,sl 3175395 CH,VA,SI,SM,FR,AT
|
||||
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,fr,nrf 3042142
|
||||
JM JAM 388 JM Jamaica Kingston 10991 2934855 NA .jm JMD Dollar +1-876 en-JM 3489940
|
||||
JO JOR 400 JO Jordan Amman 92300 9956011 AS .jo JOD Dinar 962 ##### ^(\d{5})$ ar-JO,en 248816 SY,SA,IQ,IL,PS
|
||||
JP JPN 392 JA Japan Tokyo 377835 126529100 AS .jp JPY Yen 81 ###-#### ^\d{3}-\d{4}$ ja 1861060
|
||||
KE KEN 404 KE Kenya Nairobi 582650 51393010 AF .ke KES Shilling 254 ##### ^(\d{5})$ en-KE,sw-KE 192950 ET,TZ,SS,SO,UG
|
||||
KG KGZ 417 KG Kyrgyzstan Bishkek 198500 6315800 AS .kg KGS Som 996 ###### ^(\d{6})$ ky,uz,ru 1527747 CN,TJ,UZ,KZ
|
||||
KH KHM 116 CB Cambodia Phnom Penh 181040 16249798 AS .kh KHR Riels 855 ##### ^(\d{5})$ km,fr,en 1831722 LA,TH,VN
|
||||
KI KIR 296 KR Kiribati Tarawa 811 115847 OC .ki AUD Dollar 686 en-KI,gil 4030945
|
||||
KM COM 174 CN Comoros Moroni 2170 832322 AF .km KMF Franc 269 ar,fr-KM 921929
|
||||
KN KNA 659 SC Saint Kitts and Nevis Basseterre 261 52441 NA .kn XCD Dollar +1-869 en-KN 3575174
|
||||
KP PRK 408 KN North Korea Pyongyang 120540 25549819 AS .kp KPW Won 850 ###-### ^(\d{6})$ ko-KP 1873107 CN,KR,RU
|
||||
KR KOR 410 KS South Korea Seoul 98480 51635256 AS .kr KRW Won 82 ##### ^(\d{5})$ ko-KR,en 1835841 KP
|
||||
XK XKX 0 KV Kosovo Pristina 10908 1845300 EU EUR Euro sq,sr 831053 RS,AL,MK,ME
|
||||
KW KWT 414 KU Kuwait Kuwait City 17820 4137309 AS .kw KWD Dinar 965 ##### ^(\d{5})$ ar-KW,en 285570 SA,IQ
|
||||
KY CYM 136 CJ Cayman Islands George Town 262 64174 NA .ky KYD Dollar +1-345 en-KY 3580718
|
||||
KZ KAZ 398 KZ Kazakhstan Nur-Sultan 2717300 18276499 AS .kz KZT Tenge 7 ###### ^(\d{6})$ kk,ru 1522867 TM,CN,KG,UZ,RU
|
||||
LA LAO 418 LA Laos Vientiane 236800 7061507 AS .la LAK Kip 856 ##### ^(\d{5})$ lo,fr,en 1655842 CN,MM,KH,TH,VN
|
||||
LB LBN 422 LE Lebanon Beirut 10400 6848925 AS .lb LBP Pound 961 #### ####|#### ^(\d{4}(\d{4})?)$ ar-LB,fr-LB,en,hy 272103 SY,IL
|
||||
LC LCA 662 ST Saint Lucia Castries 616 181889 NA .lc XCD Dollar +1-758 en-LC 3576468
|
||||
LI LIE 438 LS Liechtenstein Vaduz 160 37910 EU .li CHF Franc 423 #### ^(\d{4})$ de-LI 3042058 CH,AT
|
||||
LK LKA 144 CE Sri Lanka Colombo 65610 21670000 AS .lk LKR Rupee 94 ##### ^(\d{5})$ si,ta,en 1227603
|
||||
LR LBR 430 LI Liberia Monrovia 111370 4818977 AF .lr LRD Dollar 231 #### ^(\d{4})$ en-LR 2275384 SL,CI,GN
|
||||
LS LSO 426 LT Lesotho Maseru 30355 2108132 AF .ls LSL Loti 266 ### ^(\d{3})$ en-LS,st,zu,xh 932692 ZA
|
||||
LT LTU 440 LH Lithuania Vilnius 65200 2789533 EU .lt EUR Euro 370 LT-##### ^(?:LT)*(\d{5})$ lt,ru,pl 597427 PL,BY,RU,LV
|
||||
LU LUX 442 LU Luxembourg Luxembourg 2586 607728 EU .lu EUR Euro 352 L-#### ^(?:L-)?\d{4}$ lb,de-LU,fr-LU 2960313 DE,BE,FR
|
||||
LV LVA 428 LG Latvia Riga 64589 1926542 EU .lv EUR Euro 371 LV-#### ^(?:LV)*(\d{4})$ lv,ru,lt 458258 LT,EE,BY,RU
|
||||
LY LBY 434 LY Libya Tripoli 1759540 6678567 AF .ly LYD Dinar 218 ar-LY,it,en 2215636 TD,NE,DZ,SD,TN,EG
|
||||
MA MAR 504 MO Morocco Rabat 446550 36029138 AF .ma MAD Dirham 212 ##### ^(\d{5})$ ar-MA,ber,fr 2542007 DZ,EH,ES
|
||||
MC MCO 492 MN Monaco Monaco 1.95 38682 EU .mc EUR Euro 377 ##### ^(\d{5})$ fr-MC,en,it 2993457 FR
|
||||
MD MDA 498 MD Moldova Chisinau 33843 3545883 EU .md MDL Leu 373 MD-#### ^MD-\d{4}$ ro,ru,gag,tr 617790 RO,UA
|
||||
ME MNE 499 MJ Montenegro Podgorica 14026 622345 EU .me EUR Euro 382 ##### ^(\d{5})$ sr,hu,bs,sq,hr,rom 3194884 AL,HR,BA,RS,XK
|
||||
MF MAF 663 RN Saint Martin Marigot 53 37264 NA .gp EUR Euro 590 ##### ^(\d{5})$ fr 3578421 SX
|
||||
MG MDG 450 MA Madagascar Antananarivo 587040 26262368 AF .mg MGA Ariary 261 ### ^(\d{3})$ fr-MG,mg 1062947
|
||||
MH MHL 584 RM Marshall Islands Majuro 181.3 58413 OC .mh USD Dollar 692 #####-#### ^969\d{2}(-\d{4})$ mh,en-MH 2080185
|
||||
MK MKD 807 MK North Macedonia Skopje 25333 2082958 EU .mk MKD Denar 389 #### ^(\d{4})$ mk,sq,tr,rmm,sr 718075 AL,GR,BG,RS,XK
|
||||
ML MLI 466 ML Mali Bamako 1240000 19077690 AF .ml XOF Franc 223 fr-ML,bm 2453866 SN,NE,DZ,CI,GN,MR,BF
|
||||
MM MMR 104 BM Myanmar Nay Pyi Taw 678500 53708395 AS .mm MMK Kyat 95 ##### ^(\d{5})$ my 1327865 CN,LA,TH,BD,IN
|
||||
MN MNG 496 MG Mongolia Ulaanbaatar 1565000 3170208 AS .mn MNT Tugrik 976 ###### ^(\d{6})$ mn,ru 2029969 CN,RU
|
||||
MO MAC 446 MC Macao Macao 254 631636 AS .mo MOP Pataca 853 ###### ^(\d{6})$ zh,zh-MO,pt 1821275
|
||||
MP MNP 580 CQ Northern Mariana Islands Saipan 477 56882 OC .mp USD Dollar +1-670 ##### ^9695\d{1}$ fil,tl,zh,ch-MP,en-MP 4041468
|
||||
MQ MTQ 474 MB Martinique Fort-de-France 1100 432900 NA .mq EUR Euro 596 ##### ^(\d{5})$ fr-MQ 3570311
|
||||
MR MRT 478 MR Mauritania Nouakchott 1030700 4403319 AF .mr MRU Ouguiya 222 ar-MR,fuc,snk,fr,mey,wo 2378080 SN,DZ,EH,ML
|
||||
MS MSR 500 MH Montserrat Plymouth 102 9341 NA .ms XCD Dollar +1-664 en-MS 3578097
|
||||
MT MLT 470 MT Malta Valletta 316 483530 EU .mt EUR Euro 356 @@@ #### ^[A-Z]{3}\s?\d{4}$ mt,en-MT 2562770
|
||||
MU MUS 480 MP Mauritius Port Louis 2040 1265303 AF .mu MUR Rupee 230 en-MU,bho,fr 934292
|
||||
MV MDV 462 MV Maldives Male 300 515696 AS .mv MVR Rufiyaa 960 ##### ^(\d{5})$ dv,en 1282028
|
||||
MW MWI 454 MI Malawi Lilongwe 118480 17563749 AF .mw MWK Kwacha 265 ###### ^(\d{6})$ ny,yao,tum,swk 927384 TZ,MZ,ZM
|
||||
MX MEX 484 MX Mexico Mexico City 1972550 126190788 NA .mx MXN Peso 52 ##### ^(\d{5})$ es-MX 3996063 GT,US,BZ
|
||||
MY MYS 458 MY Malaysia Kuala Lumpur 329750 31528585 AS .my MYR Ringgit 60 ##### ^(\d{5})$ ms-MY,en,zh,ta,te,ml,pa,th 1733045 BN,TH,ID
|
||||
MZ MOZ 508 MZ Mozambique Maputo 801590 29495962 AF .mz MZN Metical 258 #### ^(\d{4})$ pt-MZ,vmw 1036973 ZW,TZ,SZ,ZA,ZM,MW
|
||||
NA NAM 516 WA Namibia Windhoek 825418 2448255 AF .na NAD Dollar 264 en-NA,af,de,hz,naq 3355338 ZA,BW,ZM,AO
|
||||
NC NCL 540 NC New Caledonia Noumea 19060 284060 OC .nc XPF Franc 687 ##### ^(\d{5})$ fr-NC 2139685
|
||||
NE NER 562 NG Niger Niamey 1267000 22442948 AF .ne XOF Franc 227 #### ^(\d{4})$ fr-NE,ha,kr,dje 2440476 TD,BJ,DZ,LY,BF,NG,ML
|
||||
NF NFK 574 NF Norfolk Island Kingston 34.6 1828 OC .nf AUD Dollar 672 #### ^(\d{4})$ en-NF 2155115
|
||||
NG NGA 566 NI Nigeria Abuja 923768 195874740 AF .ng NGN Naira 234 ###### ^(\d{6})$ en-NG,ha,yo,ig,ff 2328926 TD,NE,BJ,CM
|
||||
NI NIC 558 NU Nicaragua Managua 129494 6465513 NA .ni NIO Cordoba 505 ###-###-# ^(\d{7})$ es-NI,en 3617476 CR,HN
|
||||
NL NLD 528 NL The Netherlands Amsterdam 41526 17231017 EU .nl EUR Euro 31 #### @@ ^(\d{4}\s?[a-zA-Z]{2})$ nl-NL,fy-NL 2750405 DE,BE
|
||||
NO NOR 578 NO Norway Oslo 324220 5314336 EU .no NOK Krone 47 #### ^(\d{4})$ no,nb,nn,se,fi 3144096 FI,RU,SE
|
||||
NP NPL 524 NP Nepal Kathmandu 140800 28087871 AS .np NPR Rupee 977 ##### ^(\d{5})$ ne,en 1282988 CN,IN
|
||||
NR NRU 520 NR Nauru Yaren 21 12704 OC .nr AUD Dollar 674 NRU68 NRU68 na,en-NR 2110425
|
||||
NU NIU 570 NE Niue Alofi 260 2166 OC .nu NZD Dollar 683 #### ^(\d{4})$ niu,en-NU 4036232
|
||||
NZ NZL 554 NZ New Zealand Wellington 268680 4885500 OC .nz NZD Dollar 64 #### ^(\d{4})$ en-NZ,mi 2186224
|
||||
OM OMN 512 MU Oman Muscat 212460 4829483 AS .om OMR Rial 968 ### ^(\d{3})$ ar-OM,en,bal,ur 286963 SA,YE,AE
|
||||
PA PAN 591 PM Panama Panama City 78200 4176873 NA .pa PAB Balboa 507 ##### ^(\d{5})$ es-PA,en 3703430 CR,CO
|
||||
PE PER 604 PE Peru Lima 1285220 31989256 SA .pe PEN Sol 51 ##### ^(\d{5})$ es-PE,qu,ay 3932488 EC,CL,BO,BR,CO
|
||||
PF PYF 258 FP French Polynesia Papeete 4167 277679 OC .pf XPF Franc 689 ##### ^((97|98)7\d{2})$ fr-PF,ty 4030656
|
||||
PG PNG 598 PP Papua New Guinea Port Moresby 462840 8606316 OC .pg PGK Kina 675 ### ^(\d{3})$ en-PG,ho,meu,tpi 2088628 ID
|
||||
PH PHL 608 RP Philippines Manila 300000 106651922 AS .ph PHP Peso 63 #### ^(\d{4})$ tl,en-PH,fil,ceb,ilo,hil,war,pam,bik,bcl,pag,mrw,tsg,mdh,cbk,krj,sgd,msb,akl,ibg,yka,mta,abx 1694008
|
||||
PK PAK 586 PK Pakistan Islamabad 803940 212215030 AS .pk PKR Rupee 92 ##### ^(\d{5})$ ur-PK,en-PK,pa,sd,ps,brh 1168579 CN,AF,IR,IN
|
||||
PL POL 616 PL Poland Warsaw 312685 37978548 EU .pl PLN Zloty 48 ##-### ^\d{2}-\d{3}$ pl 798544 DE,LT,SK,CZ,BY,UA,RU
|
||||
PM SPM 666 SB Saint Pierre and Miquelon Saint-Pierre 242 7012 NA .pm EUR Euro 508 ##### ^(97500)$ fr-PM 3424932
|
||||
PN PCN 612 PC Pitcairn Adamstown 47 46 OC .pn NZD Dollar 870 PCRN 1ZZ PCRN 1ZZ en-PN 4030699
|
||||
PR PRI 630 RQ Puerto Rico San Juan 9104 3195153 NA .pr USD Dollar +1-787 and 1-939 #####-#### ^00[679]\d{2}(?:-\d{4})?$ en-PR,es-PR 4566966
|
||||
PS PSE 275 WE Palestinian Territory East Jerusalem 5970 4569087 AS .ps ILS Shekel 970 ar-PS 6254930 JO,IL,EG
|
||||
PT PRT 620 PO Portugal Lisbon 92391 10281762 EU .pt EUR Euro 351 ####-### ^\d{4}-\d{3}\s?[a-zA-Z]{0,25}$ pt-PT,mwl 2264397 ES
|
||||
PW PLW 585 PS Palau Melekeok 458 17907 OC .pw USD Dollar 680 96940 ^(96940)$ pau,sov,en-PW,tox,ja,fil,zh 1559582
|
||||
PY PRY 600 PA Paraguay Asuncion 406750 6956071 SA .py PYG Guarani 595 #### ^(\d{4})$ es-PY,gn 3437598 BO,BR,AR
|
||||
QA QAT 634 QA Qatar Doha 11437 2781677 AS .qa QAR Rial 974 ar-QA,es 289688 SA
|
||||
RE REU 638 RE Reunion Saint-Denis 2517 776948 AF .re EUR Euro 262 ##### ^((97|98)(4|7|8)\d{2})$ fr-RE 935317
|
||||
RO ROU 642 RO Romania Bucharest 237500 19473936 EU .ro RON Leu 40 ###### ^(\d{6})$ ro,hu,rom 798549 MD,HU,UA,BG,RS
|
||||
RS SRB 688 RI Serbia Belgrade 88361 6982084 EU .rs RSD Dinar 381 ##### ^(\d{5})$ sr,hu,bs,rom 6290252 AL,HU,MK,RO,HR,BA,BG,ME,XK
|
||||
RU RUS 643 RS Russia Moscow 17100000 144478050 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,kbd,krc,av,sah,nog 2017370 GE,CN,BY,UA,KZ,LV,PL,EE,LT,FI,MN,NO,AZ,KP
|
||||
RW RWA 646 RW Rwanda Kigali 26338 12301939 AF .rw RWF Franc 250 rw,en-RW,fr-RW,sw 49518 TZ,CD,BI,UG
|
||||
SA SAU 682 SA Saudi Arabia Riyadh 1960582 33699947 AS .sa SAR Rial 966 ##### ^(\d{5})$ ar-SA 102358 QA,OM,IQ,YE,JO,AE,KW
|
||||
SB SLB 090 BP Solomon Islands Honiara 28450 652858 OC .sb SBD Dollar 677 en-SB,tpi 2103350
|
||||
SC SYC 690 SE Seychelles Victoria 455 96762 AF .sc SCR Rupee 248 en-SC,fr-SC 241170
|
||||
SD SDN 729 SU Sudan Khartoum 1861484 41801533 AF .sd SDG Pound 249 ##### ^(\d{5})$ ar-SD,en,fia 366755 SS,TD,EG,ET,ER,LY,CF
|
||||
SS SSD 728 OD South Sudan Juba 644329 8260490 AF .ss SSP Pound 211 en 7909807 CD,CF,ET,KE,SD,UG
|
||||
SE SWE 752 SW Sweden Stockholm 449964 10183175 EU .se SEK Krona 46 ### ## ^(?:SE)?\d{3}\s\d{2}$ sv-SE,se,sma,fi-SE 2661886 NO,FI
|
||||
SG SGP 702 SN Singapore Singapore 692.7 5638676 AS .sg SGD Dollar 65 ###### ^(\d{6})$ cmn,en-SG,ms-SG,ta-SG,zh-SG 1880251
|
||||
SH SHN 654 SH Saint Helena Jamestown 410 7460 AF .sh SHP Pound 290 STHL 1ZZ ^(STHL1ZZ)$ en-SH 3370751
|
||||
SI SVN 705 SI Slovenia Ljubljana 20273 2067372 EU .si EUR Euro 386 #### ^(?:SI)*(\d{4})$ sl,sh 3190538 HU,IT,HR,AT
|
||||
SJ SJM 744 SV Svalbard and Jan Mayen Longyearbyen 62049 2550 EU .sj NOK Krone 47 #### ^(\d{4})$ no,ru 607072
|
||||
SK SVK 703 LO Slovakia Bratislava 48845 5447011 EU .sk EUR Euro 421 ### ## ^\d{3}\s?\d{2}$ sk,hu 3057568 PL,HU,CZ,UA,AT
|
||||
SL SLE 694 SL Sierra Leone Freetown 71740 7650154 AF .sl SLL Leone 232 en-SL,men,tem 2403846 LR,GN
|
||||
SM SMR 674 SM San Marino San Marino 61.2 33785 EU .sm EUR Euro 378 4789# ^(4789\d)$ it-SM 3168068 IT
|
||||
SN SEN 686 SG Senegal Dakar 196190 15854360 AF .sn XOF Franc 221 ##### ^(\d{5})$ fr-SN,wo,fuc,mnk 2245662 GN,MR,GW,GM,ML
|
||||
SO SOM 706 SO Somalia Mogadishu 637657 15008154 AF .so SOS Shilling 252 @@ ##### ^([A-Z]{2}\d{5})$ so-SO,ar-SO,it,en-SO 51537 ET,KE,DJ
|
||||
SR SUR 740 NS Suriname Paramaribo 163270 575991 SA .sr SRD Dollar 597 nl-SR,en,srn,hns,jv 3382998 GY,BR,GF
|
||||
ST STP 678 TP Sao Tome and Principe Sao Tome 1001 197700 AF .st STN Dobra 239 pt-ST 2410758
|
||||
SV SLV 222 ES El Salvador San Salvador 21040 6420744 NA .sv USD Dollar 503 CP #### ^(?:CP)*(\d{4})$ es-SV 3585968 GT,HN
|
||||
SX SXM 534 NN Sint Maarten Philipsburg 21 40654 NA .sx ANG Guilder 599 nl,en 7609695 MF
|
||||
SY SYR 760 SY Syria Damascus 185180 16906283 AS .sy SYP Pound 963 ar-SY,ku,hy,arc,fr,en 163843 IQ,JO,IL,TR,LB
|
||||
SZ SWZ 748 WZ Eswatini Mbabane 17363 1136191 AF .sz SZL Lilangeni 268 @### ^([A-Z]\d{3})$ en-SZ,ss-SZ 934841 ZA,MZ
|
||||
TC TCA 796 TK Turks and Caicos Islands Cockburn Town 430 37665 NA .tc USD Dollar +1-649 TKCA 1ZZ ^(TKCA 1ZZ)$ en-TC 3576916
|
||||
TD TCD 148 CD Chad N'Djamena 1284000 15477751 AF .td XAF Franc 235 TKCA 1ZZ ^(TKCA 1ZZ)$ fr-TD,ar-TD,sre 2434508 NE,LY,CF,SD,CM,NG
|
||||
TF ATF 260 FS French Southern Territories Port-aux-Francais 7829 140 AN .tf EUR Euro fr 1546748
|
||||
TG TGO 768 TO Togo Lome 56785 7889094 AF .tg XOF Franc 228 fr-TG,ee,hna,kbp,dag,ha 2363686 BJ,GH,BF
|
||||
TH THA 764 TH Thailand Bangkok 514000 69428524 AS .th THB Baht 66 ##### ^(\d{5})$ th,en 1605651 LA,MM,KH,MY
|
||||
TJ TJK 762 TI Tajikistan Dushanbe 143100 9100837 AS .tj TJS Somoni 992 ###### ^(\d{6})$ tg,ru 1220409 CN,AF,KG,UZ
|
||||
TK TKL 772 TL Tokelau 10 1466 OC .tk NZD Dollar 690 tkl,en-TK 4031074
|
||||
TL TLS 626 TT Timor Leste Dili 15007 1267972 OC .tl USD Dollar 670 tet,pt-TL,id,en 1966436 ID
|
||||
TM TKM 795 TX Turkmenistan Ashgabat 488100 5850908 AS .tm TMT Manat 993 ###### ^(\d{6})$ tk,ru,uz 1218197 AF,IR,UZ,KZ
|
||||
TN TUN 788 TS Tunisia Tunis 163610 11565204 AF .tn TND Dinar 216 #### ^(\d{4})$ ar-TN,fr 2464461 DZ,LY
|
||||
TO TON 776 TN Tonga Nuku'alofa 748 103197 OC .to TOP Pa'anga 676 to,en-TO 4032283
|
||||
TR TUR 792 TU Turkey Ankara 780580 82319724 AS .tr TRY Lira 90 ##### ^(\d{5})$ tr-TR,ku,diq,az,av 298795 SY,GE,IQ,IR,GR,AM,AZ,BG
|
||||
TT TTO 780 TD Trinidad and Tobago Port of Spain 5128 1389858 NA .tt TTD Dollar +1-868 en-TT,hns,fr,es,zh 3573591
|
||||
TV TUV 798 TV Tuvalu Funafuti 26 11508 OC .tv AUD Dollar 688 tvl,en,sm,gil 2110297
|
||||
TW TWN 158 TW Taiwan Taipei 35980 23451837 AS .tw TWD Dollar 886 ##### ^(\d{5})$ zh-TW,zh,nan,hak 1668284
|
||||
TZ TZA 834 TZ Tanzania Dodoma 945087 56318348 AF .tz TZS Shilling 255 sw-TZ,en,ar 149590 MZ,KE,CD,RW,ZM,BI,UG,MW
|
||||
UA UKR 804 UP Ukraine Kyiv 603700 44622516 EU .ua UAH Hryvnia 380 ##### ^(\d{5})$ uk,ru-UA,rom,pl,hu 690791 PL,MD,HU,SK,BY,RO,RU
|
||||
UG UGA 800 UG Uganda Kampala 236040 42723139 AF .ug UGX Shilling 256 en-UG,lg,sw,ar 226074 TZ,KE,SS,CD,RW
|
||||
UM UMI 581 United States Minor Outlying Islands 0 0 OC .um USD Dollar 1 en-UM 5854968
|
||||
US USA 840 US United States Washington 9629091 327167434 NA .us USD Dollar 1 #####-#### ^\d{5}(-\d{4})?$ en-US,es-US,haw,fr 6252001 CA,MX,CU
|
||||
UY URY 858 UY Uruguay Montevideo 176220 3449299 SA .uy UYU Peso 598 ##### ^(\d{5})$ es-UY 3439705 BR,AR
|
||||
UZ UZB 860 UZ Uzbekistan Tashkent 447400 32955400 AS .uz UZS Som 998 ###### ^(\d{6})$ uz,ru,tg 1512440 TM,AF,KG,TJ,KZ
|
||||
VA VAT 336 VT Vatican Vatican City 0.44 921 EU .va EUR Euro 379 ##### ^(\d{5})$ la,it,fr 3164670 IT
|
||||
VC VCT 670 VC Saint Vincent and the Grenadines Kingstown 389 110211 NA .vc XCD Dollar +1-784 en-VC,fr 3577815
|
||||
VE VEN 862 VE Venezuela Caracas 912050 28870195 SA .ve VES Bolivar Soberano 58 #### ^(\d{4})$ es-VE 3625428 GY,BR,CO
|
||||
VG VGB 092 VI British Virgin Islands Road Town 153 29802 NA .vg USD Dollar +1-284 en-VG 3577718
|
||||
VI VIR 850 VQ U.S. Virgin Islands Charlotte Amalie 352 106977 NA .vi USD Dollar +1-340 #####-#### ^008\d{2}(?:-\d{4})?$ en-VI 4796775
|
||||
VN VNM 704 VM Vietnam Hanoi 329560 95540395 AS .vn VND Dong 84 ###### ^(\d{6})$ vi,en,fr,zh,km 1562822 CN,LA,KH
|
||||
VU VUT 548 NH Vanuatu Port Vila 12200 292680 OC .vu VUV Vatu 678 bi,en-VU,fr-VU 2134431
|
||||
WF WLF 876 WF Wallis and Futuna Mata Utu 274 16025 OC .wf XPF Franc 681 ##### ^(986\d{2})$ wls,fud,fr-WF 4034749
|
||||
WS WSM 882 WS Samoa Apia 2944 196130 OC .ws WST Tala 685 AS 96799 AS 96799 sm,en-WS 4034894
|
||||
YE YEM 887 YM Yemen Sanaa 527970 28498687 AS .ye YER Rial 967 ar-YE 69543 SA,OM
|
||||
YT MYT 175 MF Mayotte Mamoudzou 374 279471 AF .yt EUR Euro 262 ##### ^(\d{5})$ fr-YT 1024031
|
||||
ZA ZAF 710 SF South Africa Pretoria 1219912 57779622 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
|
||||
ZM ZMB 894 ZA Zambia Lusaka 752614 17351822 AF .zm ZMW Kwacha 260 ##### ^(\d{5})$ en-ZM,bem,loz,lun,lue,ny,toi 895949 ZW,TZ,MZ,CD,NA,MW,AO
|
||||
ZW ZWE 716 ZI Zimbabwe Harare 390580 14439018 AF .zw ZWL Dollar 263 en-ZW,sn,nr,nd 878675 ZA,MZ,BW,ZM
|
||||
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
|
||||
AN ANT 530 NT Netherlands Antilles Willemstad 960 300000 NA .an ANG Guilder 599 nl-AN,en,es 8505032 GP
|
||||
14
geo/docs/drf/__init__.py
Normal file
14
geo/docs/drf/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from drf_spectacular.utils import inline_serializer
|
||||
from rest_framework import status
|
||||
from rest_framework.fields import CharField
|
||||
|
||||
error = inline_serializer("error", fields={"detail": CharField()})
|
||||
|
||||
BASE_ERRORS = {
|
||||
status.HTTP_400_BAD_REQUEST: error,
|
||||
status.HTTP_401_UNAUTHORIZED: error,
|
||||
status.HTTP_403_FORBIDDEN: error,
|
||||
status.HTTP_404_NOT_FOUND: error,
|
||||
status.HTTP_405_METHOD_NOT_ALLOWED: error,
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR: error,
|
||||
}
|
||||
62
geo/docs/drf/viewsets.py
Normal file
62
geo/docs/drf/viewsets.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import status
|
||||
|
||||
from core.docs.drf import BASE_ERRORS
|
||||
from geo.serializers import AddressAutocompleteInputSerializer, AddressSerializer, AddressSuggestionSerializer
|
||||
|
||||
ADDRESS_SCHEMA = {
|
||||
"list": extend_schema(
|
||||
summary=_("list all addresses"),
|
||||
responses={
|
||||
status.HTTP_200_OK: AddressSerializer(many=True),
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
"retrieve": extend_schema(
|
||||
summary=_("retrieve a single address"),
|
||||
responses={
|
||||
status.HTTP_200_OK: AddressSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
"create": extend_schema(
|
||||
summary=_("create a new address"),
|
||||
request=AddressSerializer,
|
||||
responses={
|
||||
status.HTTP_201_CREATED: AddressSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
"destroy": extend_schema(
|
||||
summary=_("delete an address"),
|
||||
responses={
|
||||
status.HTTP_204_NO_CONTENT: {},
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
"update": extend_schema(
|
||||
summary=_("update an entire address"),
|
||||
request=AddressSerializer,
|
||||
responses={
|
||||
status.HTTP_200_OK: AddressSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
"partial_update": extend_schema(
|
||||
summary=_("partially update an address"),
|
||||
request=AddressSerializer,
|
||||
responses={
|
||||
status.HTTP_200_OK: AddressSerializer,
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
"autocomplete": extend_schema(
|
||||
summary=_("autocomplete address suggestions"),
|
||||
request=AddressAutocompleteInputSerializer,
|
||||
responses={
|
||||
status.HTTP_200_OK: AddressSuggestionSerializer(many=True),
|
||||
**BASE_ERRORS,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import django_filters
|
||||
from django_filters import OrderingFilter
|
||||
|
||||
from geo.models import City, Country, PostalCode, Region
|
||||
|
||||
|
||||
class CountryFilter(django_filters.FilterSet):
|
||||
uuid = django_filters.CharFilter(lookup_expr="exact")
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
code = django_filters.CharFilter(lookup_expr="iexact")
|
||||
|
||||
order_by = OrderingFilter(fields=(("uuid", "uuid"), ("name", "name"), ("?", "random")))
|
||||
|
||||
class Meta:
|
||||
model = Country
|
||||
fields = ["uuid", "name", "code"]
|
||||
|
||||
|
||||
class RegionFilter(django_filters.FilterSet):
|
||||
uuid = django_filters.CharFilter(lookup_expr="exact")
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
country_name = django_filters.CharFilter(lookup_expr="exact", field_name="country__name")
|
||||
country_code = django_filters.CharFilter(lookup_expr="exact", field_name="country__code")
|
||||
|
||||
order_by = OrderingFilter(fields=(("uuid", "uuid"), ("name", "name"), ("?", "random")))
|
||||
|
||||
class Meta:
|
||||
model = Region
|
||||
fields = ["uuid", "name", "country_name", "country_code"]
|
||||
|
||||
|
||||
class CityFilter(django_filters.FilterSet):
|
||||
uuid = django_filters.CharFilter(lookup_expr="exact")
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
region_name = django_filters.CharFilter(lookup_expr="exact", field_name="region__name")
|
||||
region_code = django_filters.CharFilter(lookup_expr="exact", field_name="region__code")
|
||||
|
||||
order_by = OrderingFilter(fields=(("uuid", "uuid"), ("name", "name"), ("?", "random")))
|
||||
|
||||
class Meta:
|
||||
model = City
|
||||
fields = ["uuid", "name", "region_name", "region_code"]
|
||||
|
||||
|
||||
class PostalCodeFilter(django_filters.FilterSet):
|
||||
uuid = django_filters.CharFilter(lookup_expr="exact")
|
||||
code = django_filters.CharFilter(lookup_expr="icontains")
|
||||
city_name = django_filters.CharFilter(lookup_expr="iexact", field_name="city__name")
|
||||
region_name = django_filters.CharFilter(lookup_expr="iexact", field_name="region__name")
|
||||
country_name = django_filters.CharFilter(lookup_expr="iexact", field_name="country__name")
|
||||
|
||||
class Meta:
|
||||
model = PostalCode
|
||||
fields = ["uuid", "code", "city_name", "region_name", "country_name"]
|
||||
|
|
@ -1,95 +1,35 @@
|
|||
import graphene
|
||||
from django.core.exceptions import BadRequest
|
||||
from django.http import Http404
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from graphene.types.generic import GenericScalar
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from core.graphene import BaseMutation
|
||||
from core.utils.messages import permission_denied_message
|
||||
from geo.graphene.object_types import AddressType
|
||||
from geo.models import Address, City, PostalCode
|
||||
from geo.utils import PointScalar
|
||||
from geo.models import Address
|
||||
from geo.utils.nominatim import fetch_address_suggestions
|
||||
|
||||
|
||||
class CreateAddress(BaseMutation):
|
||||
class CreateAddress(graphene.Mutation):
|
||||
class Arguments:
|
||||
location = PointScalar()
|
||||
street = graphene.String()
|
||||
city_uuid = graphene.UUID()
|
||||
postal_code_uuid = graphene.UUID()
|
||||
raw_data = graphene.String(
|
||||
required=True,
|
||||
description=_("original address string provided by the user")
|
||||
)
|
||||
|
||||
address = graphene.Field(AddressType)
|
||||
|
||||
@staticmethod
|
||||
def mutate(_parent, info, street, city_uuid, postal_code_uuid):
|
||||
if info.context.user.is_authenticated:
|
||||
try:
|
||||
city = City.objects.get(uuid=city_uuid)
|
||||
postal_code = PostalCode.objects.get(uuid=postal_code_uuid)
|
||||
except City.DoesNotExist:
|
||||
name = "City"
|
||||
raise Http404(_(f"{name} does not exist: {city_uuid}"))
|
||||
except PostalCode.DoesNotExist:
|
||||
name = "PostalCode"
|
||||
raise Http404(_(f"{name} does not exist: {postal_code_uuid}"))
|
||||
def mutate(_parent, info, raw_data):
|
||||
user = info.context.user if info.context.user.is_authenticated else None
|
||||
|
||||
address = Address.objects.create(
|
||||
street=street,
|
||||
city=city,
|
||||
postal_code=postal_code,
|
||||
region=city.region,
|
||||
country=city.country,
|
||||
user=info.context.user,
|
||||
)
|
||||
|
||||
return CreateAddress(address=address)
|
||||
|
||||
else:
|
||||
raise PermissionDenied(permission_denied_message)
|
||||
|
||||
|
||||
class UpdateAddress(BaseMutation):
|
||||
class Arguments:
|
||||
uuid = graphene.UUID(required=True)
|
||||
location = PointScalar()
|
||||
street = graphene.String()
|
||||
city_uuid = graphene.UUID()
|
||||
postal_code_uuid = graphene.UUID()
|
||||
|
||||
address = graphene.Field(AddressType)
|
||||
|
||||
@staticmethod
|
||||
def mutate(_parent, info, uuid, location=None, street=None, city_uuid=None, postal_code_uuid=None):
|
||||
try:
|
||||
address = Address.objects.get(uuid=uuid)
|
||||
|
||||
if (
|
||||
info.context.user.is_superuser
|
||||
or info.context.user.has_perm("geo.change_address")
|
||||
or info.context.user == address.user
|
||||
):
|
||||
address = Address.objects.get(uuid=uuid)
|
||||
|
||||
if location is not None:
|
||||
address.location = location
|
||||
|
||||
if street is not None:
|
||||
address.street = street
|
||||
|
||||
if city_uuid is not None:
|
||||
address.city = City.objects.get(uuid=city_uuid)
|
||||
|
||||
if postal_code_uuid is not None:
|
||||
address.postal_code = PostalCode.objects.get(uuid=postal_code_uuid)
|
||||
|
||||
address.save()
|
||||
|
||||
return UpdateAddress(address=address)
|
||||
|
||||
raise PermissionDenied(permission_denied_message)
|
||||
|
||||
except Address.DoesNotExist:
|
||||
name = "Address"
|
||||
raise Http404(_(f"{name} does not exist: {uuid}"))
|
||||
address = Address.objects.create(
|
||||
raw_data=raw_data,
|
||||
user=user
|
||||
)
|
||||
return CreateAddress(address=address)
|
||||
|
||||
|
||||
class DeleteAddress(BaseMutation):
|
||||
|
|
@ -103,9 +43,9 @@ class DeleteAddress(BaseMutation):
|
|||
try:
|
||||
address = Address.objects.get(uuid=uuid)
|
||||
if (
|
||||
info.context.user.is_superuser
|
||||
or info.context.user.has_perm("geo.delete_address")
|
||||
or info.context.user == address.user
|
||||
info.context.user.is_superuser
|
||||
or info.context.user.has_perm("geo.delete_address")
|
||||
or info.context.user == address.user
|
||||
):
|
||||
address.delete()
|
||||
return DeleteAddress(success=True)
|
||||
|
|
@ -115,3 +55,22 @@ class DeleteAddress(BaseMutation):
|
|||
except Address.DoesNotExist:
|
||||
name = "Address"
|
||||
raise Http404(_(f"{name} does not exist: {uuid}"))
|
||||
|
||||
|
||||
class AutocompleteAddress(BaseMutation):
|
||||
class Arguments:
|
||||
q = graphene.String()
|
||||
limit = graphene.Int()
|
||||
|
||||
suggestions = GenericScalar()
|
||||
|
||||
@staticmethod
|
||||
def mutate(_parent, info, q, limit):
|
||||
if 1 > limit > 10:
|
||||
raise BadRequest(_("limit must be between 1 and 10"))
|
||||
try:
|
||||
suggestions = fetch_address_suggestions(query=q, limit=limit)
|
||||
except Exception as e:
|
||||
raise BadRequest(f"geocoding error: {e!s}") from e
|
||||
|
||||
return AutocompleteAddress(suggestions=suggestions)
|
||||
|
|
|
|||
|
|
@ -1,51 +1,55 @@
|
|||
from graphene import InputObjectType, String, relay
|
||||
import graphene
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from graphene import Float, ObjectType, String
|
||||
from graphene.types.generic import GenericScalar
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
from geo.models import Address, City, Country, PostalCode, Region
|
||||
from geo.utils import PointScalar
|
||||
from geo.models import Address
|
||||
|
||||
|
||||
class CountryType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Country
|
||||
interfaces = (relay.Node,)
|
||||
fields = "__all__"
|
||||
class AddressAutocompleteInput(graphene.InputObjectType):
|
||||
q = graphene.String(
|
||||
required=True,
|
||||
description=_("partial or full address string to search for")
|
||||
)
|
||||
limit = graphene.Int(
|
||||
required=False,
|
||||
description=_("maximum number of suggestions to return (1–10)"),
|
||||
default_value=5
|
||||
)
|
||||
|
||||
|
||||
class CityType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = City
|
||||
interfaces = (relay.Node,)
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class RegionType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Region
|
||||
interfaces = (relay.Node,)
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class PostalCodeType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = PostalCode
|
||||
interfaces = (relay.Node,)
|
||||
fields = "__all__"
|
||||
class AddressSuggestionType(ObjectType):
|
||||
display_name = String()
|
||||
lat = Float()
|
||||
lon = Float()
|
||||
address = GenericScalar(
|
||||
description=_("the address breakdown as key/value pairs")
|
||||
)
|
||||
|
||||
|
||||
class AddressType(DjangoObjectType):
|
||||
location = PointScalar()
|
||||
latitude = graphene.Float(description=_("Latitude (Y coordinate)"))
|
||||
longitude = graphene.Float(description=_("Longitude (X coordinate)"))
|
||||
|
||||
class Meta:
|
||||
model = Address
|
||||
interfaces = (relay.Node,)
|
||||
exclude = ("user", "billing_address_order", "shipping_address_order")
|
||||
filter_fields = ["city__name", "country__name", "postal_code"]
|
||||
fields = (
|
||||
"uuid",
|
||||
"street",
|
||||
"district",
|
||||
"city",
|
||||
"region",
|
||||
"postal_code",
|
||||
"country",
|
||||
"raw_data",
|
||||
"api_response",
|
||||
"user",
|
||||
)
|
||||
read_only_fields = ("api_response",)
|
||||
|
||||
def resolve_latitude(self, info):
|
||||
return self.location.y if self.location else None
|
||||
|
||||
class UnregisteredCustomerAddressInput(InputObjectType):
|
||||
billing_customer_city = String(required=True)
|
||||
billing_customer_region = String(required=True)
|
||||
billing_customer_country = String(required=True)
|
||||
billing_customer_postal_code = String(required=True)
|
||||
billing_customer_address_line = String(required=True)
|
||||
def resolve_longitude(self, info):
|
||||
return self.location.x if self.location else None
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,61 @@
|
|||
from django.contrib.gis.db import models
|
||||
import requests
|
||||
from constance import config
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.db import models
|
||||
|
||||
|
||||
class AlternativeNameManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super(AlternativeNameManager, self).get_queryset().exclude(kind="link") # noqa: UP008
|
||||
class AddressManager(models.Manager):
|
||||
def create(self, raw_data: str, **kwargs):
|
||||
"""
|
||||
Create an Address instance by geocoding the provided raw address string.
|
||||
|
||||
Args:
|
||||
raw_data (str): The raw address input from the user (e.g., '36 Mornington Rd Loughton England').
|
||||
**kwargs: Additional fields to pass to the Address model (e.g., user).
|
||||
"""
|
||||
if not raw_data:
|
||||
raise ValueError("'raw_data' (address string) must be provided.")
|
||||
|
||||
# Query Nominatim
|
||||
params = {
|
||||
'format': 'json',
|
||||
'addressdetails': 1,
|
||||
'q': raw_data,
|
||||
}
|
||||
resp = requests.get(config.NOMINATIM_URL, params=params)
|
||||
resp.raise_for_status()
|
||||
results = resp.json()
|
||||
if not results:
|
||||
raise ValueError(f"No geocoding result for address: {raw_data}")
|
||||
data = results[0]
|
||||
|
||||
# Parse address components
|
||||
addr = data.get('address', {})
|
||||
street = addr.get('road') or addr.get('pedestrian') or ''
|
||||
district = addr.get('city_district') or addr.get('suburb') or ''
|
||||
city = addr.get('city') or addr.get('town') or addr.get('village') or ''
|
||||
region = addr.get('state') or addr.get('region') or ''
|
||||
postal_code = addr.get('postcode') or ''
|
||||
country = addr.get('country') or ''
|
||||
|
||||
# Parse location
|
||||
try:
|
||||
lat = float(data.get('lat'))
|
||||
lon = float(data.get('lon'))
|
||||
location = Point(lon, lat, srid=4326)
|
||||
except (TypeError, ValueError):
|
||||
location = None
|
||||
|
||||
# Create the model instance, storing both the input string and full API response
|
||||
return super().create(
|
||||
raw_data=raw_data,
|
||||
street=street,
|
||||
district=district,
|
||||
city=city,
|
||||
region=region,
|
||||
postal_code=postal_code,
|
||||
country=country,
|
||||
location=location,
|
||||
api_response=data,
|
||||
**kwargs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,155 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
# Generated by Django 5.2 on 2025-05-19 11:38
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.contrib.gis.db.models.fields
|
||||
|
||||
import swapper
|
||||
|
||||
from geo.models import SET_NULL_OR_CASCADE
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
swapper.dependency('geo', 'City'),
|
||||
swapper.dependency('geo', 'Country'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AlternativeName',
|
||||
name='Address',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=256)),
|
||||
('language', models.CharField(max_length=100)),
|
||||
('is_preferred', models.BooleanField(default=False)),
|
||||
('is_short', models.BooleanField(default=False)),
|
||||
('is_colloquial', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='City',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)),
|
||||
('slug', models.CharField(max_length=200)),
|
||||
('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)),
|
||||
('location', django.contrib.gis.db.models.fields.PointField(srid=4326)),
|
||||
('population', models.IntegerField()),
|
||||
('elevation', models.IntegerField(null=True)),
|
||||
('kind', models.CharField(max_length=10)),
|
||||
('timezone', models.CharField(max_length=40)),
|
||||
('alt_names', models.ManyToManyField(to='geo.AlternativeName')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False,
|
||||
help_text='unique id is used to surely identify any database object',
|
||||
primary_key=True, serialize=False, verbose_name='unique id')),
|
||||
('is_active', models.BooleanField(default=True,
|
||||
help_text="if set to false, this object can't be seen by users without needed permission",
|
||||
verbose_name='is active')),
|
||||
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True,
|
||||
help_text='when the object first appeared on the database',
|
||||
verbose_name='created')),
|
||||
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True,
|
||||
help_text='when the object was last modified',
|
||||
verbose_name='modified')),
|
||||
('street', models.CharField(max_length=255, null=True, verbose_name='street')),
|
||||
('district', models.CharField(max_length=255, null=True, verbose_name='district')),
|
||||
('city', models.CharField(max_length=100, null=True, verbose_name='city')),
|
||||
('region', models.CharField(max_length=100, null=True, verbose_name='region')),
|
||||
('postal_code', models.CharField(max_length=20, null=True, verbose_name='postal code')),
|
||||
('country', models.CharField(max_length=40, null=True, verbose_name='country')),
|
||||
('location', django.contrib.gis.db.models.fields.PointField(blank=True, geography=True,
|
||||
help_text='Geolocation point: (longitude, latitude)',
|
||||
null=True, srid=4326)),
|
||||
('raw_data', models.JSONField(blank=True, help_text='Full JSON response from geocoder for this address',
|
||||
null=True)),
|
||||
('api_response',
|
||||
models.JSONField(blank=True, help_text='Stored JSON response from the geocoding service', null=True)),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'swappable': swapper.swappable_setting('geo', 'City'),
|
||||
'verbose_name_plural': 'geo',
|
||||
'verbose_name': 'address',
|
||||
'verbose_name_plural': 'addresses',
|
||||
'indexes': [models.Index(fields=['location'], name='geo_address_locatio_83b064_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Country',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)),
|
||||
('slug', models.CharField(max_length=200)),
|
||||
('code', models.CharField(max_length=2, db_index=True)),
|
||||
('code3', models.CharField(max_length=3, db_index=True)),
|
||||
('population', models.IntegerField()),
|
||||
('area', models.IntegerField(null=True)),
|
||||
('currency', models.CharField(max_length=3, null=True)),
|
||||
('currency_name', models.CharField(max_length=50, null=True)),
|
||||
('languages', models.CharField(max_length=250, null=True)),
|
||||
('phone', models.CharField(max_length=20)),
|
||||
('continent', models.CharField(max_length=2)),
|
||||
('tld', models.CharField(max_length=5)),
|
||||
('capital', models.CharField(max_length=100)),
|
||||
('alt_names', models.ManyToManyField(to='geo.AlternativeName')),
|
||||
('neighbours', models.ManyToManyField(related_name='neighbours_rel_+', to=swapper.get_model_name('geo', 'Country'))),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'swappable': swapper.swappable_setting('geo', 'Country'),
|
||||
'verbose_name_plural': 'countries',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='District',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)),
|
||||
('slug', models.CharField(max_length=200)),
|
||||
('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)),
|
||||
('location', django.contrib.gis.db.models.fields.PointField(srid=4326)),
|
||||
('population', models.IntegerField()),
|
||||
('alt_names', models.ManyToManyField(to='geo.AlternativeName')),
|
||||
('city', models.ForeignKey(to=swapper.get_model_name('geo', 'City'), on_delete=SET_NULL_OR_CASCADE)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PostalCode',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)),
|
||||
('slug', models.CharField(max_length=200)),
|
||||
('code', models.CharField(max_length=20)),
|
||||
('location', django.contrib.gis.db.models.fields.PointField(srid=4326)),
|
||||
('region_name', models.CharField(max_length=100, db_index=True)),
|
||||
('subregion_name', models.CharField(max_length=100, db_index=True)),
|
||||
('district_name', models.CharField(max_length=100, db_index=True)),
|
||||
('alt_names', models.ManyToManyField(to='geo.AlternativeName')),
|
||||
('country', models.ForeignKey(related_name='postal_codes', to=swapper.get_model_name('geo', 'Country'), on_delete=SET_NULL_OR_CASCADE)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Region',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)),
|
||||
('slug', models.CharField(max_length=200)),
|
||||
('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)),
|
||||
('code', models.CharField(max_length=200, db_index=True)),
|
||||
('alt_names', models.ManyToManyField(to='geo.AlternativeName')),
|
||||
('country', models.ForeignKey(to=swapper.get_model_name('geo', 'Country'), on_delete=SET_NULL_OR_CASCADE)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Subregion',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=200, verbose_name='ascii name', db_index=True)),
|
||||
('slug', models.CharField(max_length=200)),
|
||||
('name_std', models.CharField(max_length=200, verbose_name='standard name', db_index=True)),
|
||||
('code', models.CharField(max_length=200, db_index=True)),
|
||||
('alt_names', models.ManyToManyField(to='geo.AlternativeName')),
|
||||
('region', models.ForeignKey(to='geo.Region', on_delete=SET_NULL_OR_CASCADE)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='city',
|
||||
name='country',
|
||||
field=models.ForeignKey(to=swapper.get_model_name('geo', 'Country'), on_delete=SET_NULL_OR_CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='city',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, to='geo.Region', null=True, on_delete=SET_NULL_OR_CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='city',
|
||||
name='subregion',
|
||||
field=models.ForeignKey(blank=True, to='geo.Subregion', null=True, on_delete=SET_NULL_OR_CASCADE),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 5.2 on 2025-05-19 11:40
|
||||
|
||||
import django.contrib.gis.db.models.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('geo', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='api_response',
|
||||
field=models.JSONField(blank=True, help_text='stored JSON response from the geocoding service', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='location',
|
||||
field=django.contrib.gis.db.models.fields.PointField(blank=True, geography=True,
|
||||
help_text='geolocation point: (longitude, latitude)',
|
||||
null=True, srid=4326),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='raw_data',
|
||||
field=models.JSONField(blank=True, help_text='full JSON response from geocoder for this address',
|
||||
null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.2 on 2016-03-29 19:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.contrib.gis.db.models.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
import swapper
|
||||
|
||||
from ..util import add_continents as util_add_continents
|
||||
|
||||
|
||||
def get_model(apps, name):
|
||||
model_tuple = swapper.split(swapper.get_model_name('geo', name))
|
||||
return apps.get_model(*model_tuple)
|
||||
|
||||
|
||||
def add_continents(apps, schema_editor):
|
||||
util_add_continents(get_model(apps, 'Continent'))
|
||||
|
||||
|
||||
def rm_continents(apps, schema_editor):
|
||||
# The table is going to be nuked anyway, we just don't want RunPython()
|
||||
# to throw an exception on backwards migrations
|
||||
pass
|
||||
|
||||
|
||||
def add_continent_fks(apps, schema_editor):
|
||||
Country = get_model(apps, 'Country')
|
||||
Continent = get_model(apps, 'Continent')
|
||||
|
||||
for continent in Continent.objects.all():
|
||||
Country.objects.filter(continent_code=continent.code).update(continent=continent)
|
||||
|
||||
|
||||
def rm_continent_fks(apps, schema_editor):
|
||||
Country = get_model(apps, 'Country')
|
||||
Continent = get_model(apps, 'Continent')
|
||||
|
||||
for continent in Continent.objects.all():
|
||||
Country.objects.filter(continent=continent).update(continent_code=continent.code)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0001_initial'),
|
||||
swapper.dependency('geo', 'Country'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Continent',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(db_index=True, max_length=200, verbose_name='ascii name')),
|
||||
('slug', models.CharField(max_length=200, unique=True)),
|
||||
('code', models.CharField(db_index=True, max_length=2, unique=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'swappable': swapper.swappable_setting('geo', 'Continent'),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='continent',
|
||||
name='alt_names',
|
||||
field=models.ManyToManyField(related_name='geo_continents', to='geo.AlternativeName'),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='country',
|
||||
old_name='continent',
|
||||
new_name='continent_code',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='country',
|
||||
name='continent',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='countries', to=swapper.get_model_name('geo', 'Continent')),
|
||||
),
|
||||
migrations.RunPython(add_continents, rm_continents),
|
||||
migrations.RunPython(add_continent_fks, rm_continent_fks),
|
||||
migrations.RemoveField(
|
||||
model_name='country',
|
||||
name='continent_code',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-10-23 23:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
import swapper
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0002_continent_models_and_foreign_keys'),
|
||||
swapper.dependency('geo', 'Continent'),
|
||||
swapper.dependency('geo', 'Country'),
|
||||
swapper.dependency('geo', 'City'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='country',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='geo', to=swapper.get_model_name('geo', 'Country')),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='geo', to='geo.Region'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='subregion',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='geo', to='geo.Subregion'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='continent',
|
||||
name='alt_names',
|
||||
field=models.ManyToManyField(to='geo.AlternativeName'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='continent',
|
||||
name='slug',
|
||||
field=models.CharField(max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='tld',
|
||||
field=models.CharField(max_length=5, verbose_name='TLD'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='district',
|
||||
name='city',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='districts', to=swapper.get_model_name('geo', 'City')),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='region',
|
||||
name='country',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='regions', to=swapper.get_model_name('geo', 'Country')),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subregion',
|
||||
name='region',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subregions', to='geo.Region'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-10-23 23:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0003_add_verbose_name_and_related_names'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='alternativename',
|
||||
old_name='language',
|
||||
new_name='language_code',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='country',
|
||||
old_name='languages',
|
||||
new_name='language_codes',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-10-23 23:39
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
import swapper
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0004_rename_languages_to_language_codes'),
|
||||
swapper.dependency('geo', 'City'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='postalcode',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to=swapper.get_model_name('geo', 'City')),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='postalcode',
|
||||
name='district',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to='geo.District'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='postalcode',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to='geo.Region'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='postalcode',
|
||||
name='subregion',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to='geo.Subregion'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-10-24 12:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
import swapper
|
||||
|
||||
from ..conf import ALTERNATIVE_NAME_TYPES
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0005_add_foreignkeys_to_postalcode'),
|
||||
swapper.dependency('geo', 'City'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='alternativename',
|
||||
name='is_historic',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='alternativename',
|
||||
name='kind',
|
||||
field=models.CharField(choices=ALTERNATIVE_NAME_TYPES, default='name', max_length=4),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='alternativename',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postalcode',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='postal_codes', to=swapper.get_model_name('geo', 'City')),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-10-24 18:36
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0006_typify_alt_names_and_add_is_historic'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='country',
|
||||
name='currency_symbol',
|
||||
field=models.CharField(blank=True, max_length=31, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='country',
|
||||
name='postal_code_format',
|
||||
field=models.CharField(default='', max_length=127),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='country',
|
||||
name='postal_code_regex',
|
||||
field=models.CharField(default='', max_length=255),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='currency_name',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-10-26 03:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0007_add_currency_and_postal_code_fields_to_country_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='district',
|
||||
name='code',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=200, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-10-27 13:23
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0008_add_code_to_district'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='alternativename',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='continent',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='district',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postalcode',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='region',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subregion',
|
||||
name='slug',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-12-20 08:10
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
UNIQUE_SLUG_MODELS = ['Continent', 'Country', 'Region', 'Subregion', 'District',
|
||||
'City', 'PostalCode', 'AlternativeName']
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0009_add_slug_fields_to_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='code',
|
||||
field=models.CharField(db_index=True, max_length=2, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='code3',
|
||||
field=models.CharField(db_index=True, max_length=3, unique=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='city',
|
||||
unique_together=set([('country', 'region', 'subregion', 'id', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='district',
|
||||
unique_together=set([('city', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='postalcode',
|
||||
unique_together=set([
|
||||
('country', 'region_name', 'subregion_name', 'district_name', 'name', 'id', 'code'),
|
||||
('country', 'region', 'subregion', 'city', 'district', 'name', 'id', 'code'),
|
||||
]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='region',
|
||||
unique_together=set([('country', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='subregion',
|
||||
unique_together=set([('region', 'id', 'name')]),
|
||||
),
|
||||
]
|
||||
|
||||
# The previous migration created the slug field as
|
||||
#
|
||||
# CharField(max_length=255, unique=True)
|
||||
#
|
||||
# but I removed the unique=True for new users. This checks the previous
|
||||
# state of the slug field on the models, and if the field has unique=True
|
||||
# (eg: existing users), it removes the unique attribute. For slug fields
|
||||
# that don't have unique=True (eg: new users), it skips them.
|
||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||
# Before we do any database operations, add the appropriate slug field
|
||||
# modifications to the migration operations.
|
||||
self.operations + [
|
||||
migrations.AlterField(
|
||||
model_name=model_name.lower(),
|
||||
name='slug',
|
||||
field=models.CharField(max_length=255))
|
||||
for model_name in UNIQUE_SLUG_MODELS
|
||||
# This is the check that matters. It checks the result of the
|
||||
# previous 0009 migration (which may or may not have unique=True
|
||||
# set on slug fields) for the unique attribute, and skips it if
|
||||
# they don't.
|
||||
if from_state.apps.get_model(app_label, self.model_name)._meta.get_field('slug').unique
|
||||
]
|
||||
|
||||
super(Migration, self).database_forwards(app_label, schema_editor, from_state, to_state)
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
# Generated by Django 2.0 on 2018-01-08 07:06
|
||||
|
||||
import geo.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0010_adjust_unique_attributes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='country',
|
||||
field=models.ForeignKey(on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='geo', to=settings.GEO_COUNTRY_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='geo', to='geo.Region'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='subregion',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='geo', to='geo.Subregion'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='continent',
|
||||
field=models.ForeignKey(null=True, on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='countries', to=settings.GEO_CONTINENT_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='district',
|
||||
name='city',
|
||||
field=models.ForeignKey(on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='districts', to=settings.GEO_CITY_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postalcode',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to=settings.GEO_CITY_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postalcode',
|
||||
name='district',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to='geo.District'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postalcode',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to='geo.Region'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postalcode',
|
||||
name='subregion',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='postal_codes', to='geo.Subregion'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='region',
|
||||
name='country',
|
||||
field=models.ForeignKey(on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='regions', to=settings.GEO_COUNTRY_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subregion',
|
||||
name='region',
|
||||
field=models.ForeignKey(on_delete=geo.models.SET_NULL_OR_CASCADE, related_name='subregions', to='geo.Region'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 4.1 on 2022-12-06 20:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0011_auto_20180108_0706'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='neighbours',
|
||||
field=models.ManyToManyField(related_name='_geo_country_neighbours_+', to=settings.GEO_COUNTRY_MODEL),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
# Generated by Django 5.1.3 on 2024-12-13 04:41
|
||||
|
||||
import django.contrib.gis.db.models.fields
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0012_alter_country_neighbours'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='city',
|
||||
options={'verbose_name_plural': 'cities'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='alternativename',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='alternativename',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='city',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='city',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='continent',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='continent',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='country',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='country',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='district',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='district',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='postalcode',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='postalcode',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='region',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='region',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subregion',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True, verbose_name='active'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subregion',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='alternativename',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='city',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='continent',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='country',
|
||||
name='neighbours',
|
||||
field=models.ManyToManyField(to=settings.GEO_COUNTRY_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='district',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postalcode',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='region',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subregion',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Address',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, help_text='nice_model_uuid_help_text_primary_key_unique_id_generated_for_each_record', primary_key=True, serialize=False, verbose_name='nice_model_uuid_verbose_name_unique_identifier')),
|
||||
('is_active', models.BooleanField(default=True, help_text='nice_model_is_active_help_text_specifies_if_this_record_is_currently_active', verbose_name='nice_model_is_active_verbose_name_record_state')),
|
||||
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, help_text='nice_model_created_help_text_timestamp_when_this_record_was_first_created', verbose_name='nice_model_created_verbose_name_creation_timestamp')),
|
||||
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, help_text='nice_model_modified_help_text_timestamp_when_this_record_was_last_updated', verbose_name='nice_model_modified_verbose_name_last_modified_timestamp')),
|
||||
('street', models.CharField(max_length=255)),
|
||||
('location', django.contrib.gis.db.models.fields.PointField(geography=True, null=True, srid=4326)),
|
||||
('city', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.GEO_CITY_MODEL)),
|
||||
('country', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.GEO_COUNTRY_MODEL)),
|
||||
('postal_code', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='geo.postalcode')),
|
||||
('region', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='geo.region')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Address',
|
||||
'verbose_name_plural': 'Addresses',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 11:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0013_alter_city_options_alternativename_active_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='address',
|
||||
options={'verbose_name': 'address', 'verbose_name_plural': 'addresses'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='city',
|
||||
options={'verbose_name': 'city', 'verbose_name_plural': 'cities'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='continent',
|
||||
options={'verbose_name': 'continent', 'verbose_name_plural': 'continents'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='country',
|
||||
options={'ordering': ['name'], 'verbose_name': 'country', 'verbose_name_plural': 'countries'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='district',
|
||||
options={'verbose_name': 'district', 'verbose_name_plural': 'districts'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='postalcode',
|
||||
options={'verbose_name': 'postal code', 'verbose_name_plural': 'postal codes'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='region',
|
||||
options={'verbose_name': 'region', 'verbose_name_plural': 'regions'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='subregion',
|
||||
options={'verbose_name': 'subregion', 'verbose_name_plural': 'subregions'},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 11:38
|
||||
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('geo', '0014_alter_address_options_alter_city_options_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='address',
|
||||
name='user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='created',
|
||||
field=django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, help_text='when the object first appeared on the database', verbose_name='created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True, help_text="if set to false, this object can't be seen by users without needed permission", verbose_name='is active'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='modified',
|
||||
field=django_extensions.db.fields.ModificationDateTimeField(auto_now=True, help_text='when the object was last modified', verbose_name='modified'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, help_text='unique id is used to surely identify any database object', primary_key=True, serialize=False, verbose_name='unique id'),
|
||||
),
|
||||
]
|
||||
426
geo/models.py
426
geo/models.py
|
|
@ -1,392 +1,56 @@
|
|||
import uuid
|
||||
from random import choice
|
||||
from string import ascii_uppercase, digits
|
||||
|
||||
import swapper
|
||||
from django.contrib.gis.db.models import PointField
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.db import models, transaction
|
||||
from django.utils.encoding import force_str as force_text
|
||||
from django.contrib.gis.db import models as gis_models
|
||||
from django.db import models
|
||||
from django.db.models import Index
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from model_utils import Choices
|
||||
|
||||
from core.abstract import NiceModel
|
||||
|
||||
from .conf import ALTERNATIVE_NAME_TYPES, SLUGIFY_FUNCTION
|
||||
from .managers import AlternativeNameManager
|
||||
from .util import unicode_func
|
||||
|
||||
__all__ = [
|
||||
"Address",
|
||||
"AlternativeName",
|
||||
"City",
|
||||
"Continent",
|
||||
"Country",
|
||||
"District",
|
||||
"Point",
|
||||
"PostalCode",
|
||||
"Region",
|
||||
"Subregion",
|
||||
]
|
||||
|
||||
from django.db.models import PROTECT, BooleanField, Model, UUIDField
|
||||
from django.db.models import Manager as GeoManager
|
||||
|
||||
slugify_func = SLUGIFY_FUNCTION
|
||||
|
||||
|
||||
def SET_NULL_OR_CASCADE(collector, field, sub_objs, using): # noqa: N802
|
||||
if field.null is True:
|
||||
models.SET_NULL(collector, field, sub_objs, using)
|
||||
else:
|
||||
models.CASCADE(collector, field, sub_objs, using)
|
||||
|
||||
|
||||
class SlugModel(Model):
|
||||
slug = models.CharField(blank=True, max_length=255, null=True) # noqa: DJ001
|
||||
uuid = UUIDField(verbose_name=_("UUID"), default=uuid.uuid4, editable=False)
|
||||
active = BooleanField(default=True, verbose_name=_("active"))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def slugify(self):
|
||||
raise NotImplementedError(_("subclasses_of_place_must_implement_slugify"))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.slug = slugify_func(self, self.slugify())
|
||||
# If the slug contains the object's ID and we are creating a new object,
|
||||
# save it twice: once to get an ID, another to set the object's slug
|
||||
if self.slug is None and getattr(self, "slug_contains_id", False):
|
||||
with transaction.atomic():
|
||||
# We first give a randomized slug with a prefix just in case
|
||||
# users need to find invalid slugs
|
||||
self.slug = "invalid-{}".format("".join(choice(ascii_uppercase + digits) for i in range(20)))
|
||||
super(SlugModel, self).save(*args, **kwargs) # noqa: UP008
|
||||
self.slug = slugify_func(self, self.slugify())
|
||||
|
||||
# If the 'force_insert' flag was passed, don't pass it again:
|
||||
# doing so will attempt to re-insert with the same primary key,
|
||||
# which will cause an IntegrityError.
|
||||
kwargs.pop("force_insert", None)
|
||||
super(SlugModel, self).save(*args, **kwargs) # noqa: UP008
|
||||
else:
|
||||
# This is a performance optimization - we avoid the transaction if
|
||||
# the self.slug is not None
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Place(Model):
|
||||
name = models.CharField(max_length=200, db_index=True, verbose_name="ascii name")
|
||||
alt_names = models.ManyToManyField("AlternativeName")
|
||||
|
||||
objects = GeoManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def hierarchy(self):
|
||||
"""Get hierarchy, root first"""
|
||||
lst = self.parent.hierarchy if self.parent else []
|
||||
lst.append(self)
|
||||
return lst
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/".join([place.slug for place in self.hierarchy])
|
||||
|
||||
def __str__(self):
|
||||
return force_text(self.name)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if hasattr(self, "clean"):
|
||||
self.clean()
|
||||
super(Place, self).save(*args, **kwargs) # noqa: UP008
|
||||
|
||||
|
||||
class BaseContinent(Place, SlugModel):
|
||||
code = models.CharField(max_length=2, unique=True, db_index=True)
|
||||
|
||||
def __str__(self):
|
||||
return force_text(self.name)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def slugify(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Continent(BaseContinent):
|
||||
class Meta(BaseContinent.Meta):
|
||||
swappable = swapper.swappable_setting("geo", "Continent")
|
||||
verbose_name = _("continent")
|
||||
verbose_name_plural = _("continents")
|
||||
|
||||
|
||||
class BaseCountry(Place, SlugModel):
|
||||
code = models.CharField(max_length=2, db_index=True, unique=True)
|
||||
code3 = models.CharField(max_length=3, db_index=True, unique=True)
|
||||
population = models.IntegerField()
|
||||
area = models.IntegerField(null=True)
|
||||
currency = models.CharField(max_length=3, null=True) # noqa: DJ001
|
||||
currency_name = models.CharField(max_length=50, blank=True, null=True) # noqa: DJ001
|
||||
currency_symbol = models.CharField(max_length=31, blank=True, null=True) # noqa: DJ001
|
||||
language_codes = models.CharField(max_length=250, null=True) # noqa: DJ001
|
||||
phone = models.CharField(max_length=20)
|
||||
continent = models.ForeignKey(
|
||||
swapper.get_model_name("geo", "Continent"), null=True, related_name="countries", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
tld = models.CharField(max_length=5, verbose_name="TLD")
|
||||
postal_code_format = models.CharField(max_length=127)
|
||||
postal_code_regex = models.CharField(max_length=255)
|
||||
capital = models.CharField(max_length=100)
|
||||
neighbours = models.ManyToManyField("self")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ["name"]
|
||||
verbose_name = _("country")
|
||||
verbose_name_plural = _("countries")
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return force_text(self.name)
|
||||
|
||||
def clean(self):
|
||||
self.tld = self.tld.lower()
|
||||
|
||||
def slugify(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Country(BaseCountry):
|
||||
class Meta(BaseCountry.Meta):
|
||||
swappable = swapper.swappable_setting("geo", "Country")
|
||||
verbose_name = _("country")
|
||||
verbose_name_plural = _("countries")
|
||||
|
||||
|
||||
class Region(Place, SlugModel):
|
||||
name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name")
|
||||
code = models.CharField(max_length=200, db_index=True)
|
||||
country = models.ForeignKey(
|
||||
swapper.get_model_name("geo", "Country"), related_name="regions", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("country", "name"),)
|
||||
verbose_name = _("region")
|
||||
verbose_name_plural = _("regions")
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.country
|
||||
|
||||
def full_code(self):
|
||||
return unicode_func(".".join([self.parent.code, self.code]))
|
||||
|
||||
def slugify(self):
|
||||
return f"{unicode_func(self.name)}_({unicode_func(self.full_code())})"
|
||||
|
||||
|
||||
class Subregion(Place, SlugModel):
|
||||
slug_contains_id = True
|
||||
|
||||
name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name")
|
||||
code = models.CharField(max_length=200, db_index=True)
|
||||
region = models.ForeignKey(Region, related_name="subregions", on_delete=SET_NULL_OR_CASCADE)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("region", "id", "name"),)
|
||||
verbose_name = _("subregion")
|
||||
verbose_name_plural = _("subregions")
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.region
|
||||
|
||||
def full_code(self):
|
||||
return ".".join([self.parent.parent.code, self.parent.code, self.code])
|
||||
|
||||
def slugify(self):
|
||||
return unicode_func("{}-{}").format(unicode_func(self.id), unicode_func(self.name))
|
||||
|
||||
|
||||
class BaseCity(Place, SlugModel):
|
||||
slug_contains_id = True
|
||||
|
||||
name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name")
|
||||
country = models.ForeignKey(
|
||||
swapper.get_model_name("geo", "Country"), related_name="geo", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
region = models.ForeignKey(Region, null=True, blank=True, related_name="geo", on_delete=SET_NULL_OR_CASCADE)
|
||||
subregion = models.ForeignKey(Subregion, null=True, blank=True, related_name="geo", on_delete=SET_NULL_OR_CASCADE)
|
||||
location = PointField()
|
||||
population = models.IntegerField()
|
||||
elevation = models.IntegerField(null=True)
|
||||
kind = models.CharField(max_length=10) # http://www.geonames.org/export/codes.html
|
||||
timezone = models.CharField(max_length=40)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
unique_together = (("country", "region", "subregion", "id", "name"),)
|
||||
verbose_name = _("city")
|
||||
verbose_name_plural = _("cities")
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.region
|
||||
|
||||
def slugify(self):
|
||||
if self.id:
|
||||
return f"{self.id}-{unicode_func(self.name)}"
|
||||
return None
|
||||
|
||||
|
||||
class City(BaseCity):
|
||||
class Meta(BaseCity.Meta):
|
||||
swappable = swapper.swappable_setting("geo", "City")
|
||||
verbose_name = _("city")
|
||||
verbose_name_plural = _("cities")
|
||||
|
||||
|
||||
class District(Place, SlugModel):
|
||||
slug_contains_id = True
|
||||
|
||||
name_std = models.CharField(max_length=200, db_index=True, verbose_name="standard name")
|
||||
code = models.CharField(blank=True, db_index=True, max_length=200, null=True) # noqa: DJ001
|
||||
location = PointField()
|
||||
population = models.IntegerField()
|
||||
city = models.ForeignKey(
|
||||
swapper.get_model_name("geo", "City"), related_name="districts", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("city", "name"),)
|
||||
verbose_name = _("district")
|
||||
verbose_name_plural = _("districts")
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.city
|
||||
|
||||
def slugify(self):
|
||||
if self.id:
|
||||
return f"{self.id}-{unicode_func(self.name)}"
|
||||
return None
|
||||
|
||||
|
||||
class AlternativeName(SlugModel):
|
||||
slug_contains_id = True
|
||||
|
||||
KIND = Choices(*ALTERNATIVE_NAME_TYPES)
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
kind = models.CharField(max_length=4, choices=KIND, default=KIND.name)
|
||||
language_code = models.CharField(max_length=100)
|
||||
is_preferred = models.BooleanField(default=False)
|
||||
is_short = models.BooleanField(default=False)
|
||||
is_colloquial = models.BooleanField(default=False)
|
||||
is_historic = models.BooleanField(default=False)
|
||||
|
||||
objects = AlternativeNameManager()
|
||||
|
||||
def __str__(self):
|
||||
return f"{force_text(self.name)} ({force_text(self.language_code)})"
|
||||
|
||||
def slugify(self):
|
||||
return None or f"{self.id}-{unicode_func(self.name)}"
|
||||
|
||||
|
||||
class PostalCode(Place, SlugModel):
|
||||
slug_contains_id = True
|
||||
|
||||
code = models.CharField(max_length=20)
|
||||
location = PointField()
|
||||
|
||||
country = models.ForeignKey(
|
||||
swapper.get_model_name("geo", "Country"), related_name="postal_codes", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
|
||||
# Region names for each admin level, region may not exist in DB
|
||||
region_name = models.CharField(max_length=100, db_index=True)
|
||||
subregion_name = models.CharField(max_length=100, db_index=True)
|
||||
district_name = models.CharField(max_length=100, db_index=True)
|
||||
|
||||
region = models.ForeignKey(
|
||||
Region, blank=True, null=True, related_name="postal_codes", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
subregion = models.ForeignKey(
|
||||
Subregion, blank=True, null=True, related_name="postal_codes", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
city = models.ForeignKey(
|
||||
swapper.get_model_name("geo", "City"),
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="postal_codes",
|
||||
on_delete=SET_NULL_OR_CASCADE,
|
||||
)
|
||||
district = models.ForeignKey(
|
||||
District, blank=True, null=True, related_name="postal_codes", on_delete=SET_NULL_OR_CASCADE
|
||||
)
|
||||
|
||||
objects = GeoManager()
|
||||
|
||||
class Meta:
|
||||
unique_together = (
|
||||
("country", "region", "subregion", "city", "district", "name", "id", "code"),
|
||||
("country", "region_name", "subregion_name", "district_name", "name", "id", "code"),
|
||||
)
|
||||
verbose_name = _("postal code")
|
||||
verbose_name_plural = _("postal codes")
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.country
|
||||
|
||||
@property
|
||||
def name_full(self):
|
||||
"""Get full name including hierarchy"""
|
||||
return force_text(", ".join(reversed(self.names)))
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
"""Get a hierarchy of non-null names, root first"""
|
||||
return [
|
||||
e
|
||||
for e in [
|
||||
force_text(self.country),
|
||||
force_text(self.region_name),
|
||||
force_text(self.subregion_name),
|
||||
force_text(self.district_name),
|
||||
force_text(self.name),
|
||||
]
|
||||
if e
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return force_text(self.code)
|
||||
|
||||
def slugify(self):
|
||||
return None or f"{self.id}-{unicode_func(self.code)}"
|
||||
from geo.managers import AddressManager
|
||||
|
||||
|
||||
class Address(NiceModel):
|
||||
street = models.CharField(max_length=255)
|
||||
city = models.ForeignKey("geo.City", on_delete=PROTECT, blank=False, null=False)
|
||||
region = models.ForeignKey("geo.Region", on_delete=PROTECT, blank=False, null=False)
|
||||
postal_code = models.ForeignKey("geo.PostalCode", on_delete=PROTECT, blank=False, null=False)
|
||||
country = models.ForeignKey("geo.Country", on_delete=PROTECT, blank=False, null=False)
|
||||
location = PointField(geography=True, null=True)
|
||||
user = models.ForeignKey(to="vibes_auth.User", on_delete=models.CASCADE, blank=False, null=True)
|
||||
street = models.CharField(_("street"), max_length=255, null=True) # noqa: DJ001
|
||||
district = models.CharField(_("district"), max_length=255, null=True) # noqa: DJ001
|
||||
city = models.CharField(_("city"), max_length=100, null=True) # noqa: DJ001
|
||||
region = models.CharField(_("region"), max_length=100, null=True) # noqa: DJ001
|
||||
postal_code = models.CharField(_("postal code"), max_length=20, null=True) # noqa: DJ001
|
||||
country = models.CharField(_("country"), max_length=40, null=True) # noqa: DJ001
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.street}, {self.city}, {self.country}"
|
||||
location = gis_models.PointField(
|
||||
geography=True,
|
||||
srid=4326,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_("geolocation point: (longitude, latitude)")
|
||||
)
|
||||
|
||||
raw_data = models.JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("full JSON response from geocoder for this address")
|
||||
)
|
||||
|
||||
api_response = models.JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("stored JSON response from the geocoding service")
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
to="vibes_auth.User",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
objects = AddressManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("address")
|
||||
verbose_name_plural = _("addresses")
|
||||
indexes = [
|
||||
Index(fields=["location"]),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
base = f"{self.street}, {self.city}, {self.country}"
|
||||
return f"{base} for {self.user.email}" if self.user else base
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
code_map = {
|
||||
"AB": "01",
|
||||
"BC": "02",
|
||||
"MB": "03",
|
||||
"NB": "04",
|
||||
"NL": "05",
|
||||
"NS": "07",
|
||||
"ON": "08",
|
||||
"PE": "09",
|
||||
"QC": "10",
|
||||
"SK": "11",
|
||||
"YT": "12",
|
||||
"NT": "13",
|
||||
"NU": "14",
|
||||
}
|
||||
|
||||
|
||||
class Plugin:
|
||||
def postal_code_pre(self, parser, item):
|
||||
country_code = item["countryCode"]
|
||||
if country_code != "CA":
|
||||
return
|
||||
item["admin1Code"] = code_map[item["admin1Code"]]
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
"""Call django.db.reset_queries randomly. Default chance is 0.000002 (0.0002%).
|
||||
|
||||
This plugin may be useful when processing all geonames database.
|
||||
To process all geonames database and include cities that do not specify population
|
||||
or when their population is less than 1000 people use following settings:
|
||||
|
||||
CITIES_FILES = {
|
||||
'city': {
|
||||
'filename': 'allCountries.zip',
|
||||
'urls': ['http://download.geonames.org/export/dump/'+'{filename}']
|
||||
},
|
||||
}
|
||||
|
||||
Settings variable CITIES_PLUGINS_RESET_QUERIES_CHANCE may be used to override
|
||||
default chance:
|
||||
|
||||
CITIES_PLUGINS_RESET_QUERIES_CHANCE = 1.0 / 1000000
|
||||
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import reset_queries
|
||||
|
||||
reset_chance = getattr(settings, "CITIES_PLUGINS_RESET_QUERIES_CHANCE", 0.000002)
|
||||
|
||||
|
||||
class Plugin:
|
||||
def random_reset(self):
|
||||
if random.random() <= reset_chance:
|
||||
reset_queries()
|
||||
|
||||
def city_post(self, parser, city, item):
|
||||
self.random_reset()
|
||||
|
||||
def district_post(self, parser, district, item):
|
||||
self.random_reset()
|
||||
|
|
@ -1,70 +1,65 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from geo.models import Address, City, Country, PostalCode, Region
|
||||
from geo.models import Address
|
||||
|
||||
|
||||
class AddressCountrySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Country
|
||||
fields = ("name",)
|
||||
class AddressAutocompleteInputSerializer(serializers.Serializer):
|
||||
q = serializers.CharField(
|
||||
required=True
|
||||
)
|
||||
limit = serializers.IntegerField(
|
||||
required=False,
|
||||
min_value=1,
|
||||
max_value=10,
|
||||
default=5
|
||||
)
|
||||
|
||||
|
||||
class AddressRegionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Region
|
||||
fields = ("name",)
|
||||
|
||||
|
||||
class AddressCitySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = City
|
||||
fields = ("name",)
|
||||
|
||||
|
||||
class AddressPostalCodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PostalCode
|
||||
fields = ("code",)
|
||||
class AddressSuggestionSerializer(serializers.Serializer):
|
||||
display_name = serializers.CharField()
|
||||
lat = serializers.FloatField()
|
||||
lon = serializers.FloatField()
|
||||
address = serializers.DictField(child=serializers.CharField())
|
||||
|
||||
|
||||
class AddressSerializer(serializers.ModelSerializer):
|
||||
country = AddressCountrySerializer()
|
||||
city = AddressCitySerializer()
|
||||
region = AddressRegionSerializer()
|
||||
postal_code = AddressPostalCodeSerializer()
|
||||
latitude = serializers.FloatField(source="location.y", read_only=True)
|
||||
longitude = serializers.FloatField(source="location.x", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Address
|
||||
fields = ("uuid", "street", "city", "region", "postal_code", "country")
|
||||
fields = [
|
||||
"uuid",
|
||||
"street",
|
||||
"district",
|
||||
"city",
|
||||
"region",
|
||||
"postal_code",
|
||||
"country",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"raw_data",
|
||||
"api_response",
|
||||
"user",
|
||||
]
|
||||
read_only_fields = [
|
||||
"latitude",
|
||||
"longitude",
|
||||
"raw_data",
|
||||
"api_response",
|
||||
]
|
||||
|
||||
|
||||
class CountrySerializer(serializers.ModelSerializer):
|
||||
class AddressCreateSerializer(serializers.ModelSerializer):
|
||||
raw_data = serializers.CharField(
|
||||
write_only=True,
|
||||
max_length=512,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Country
|
||||
fields = "__all__"
|
||||
model = Address
|
||||
fields = ["raw_data", "user"]
|
||||
|
||||
|
||||
class RegionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Region
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class CitySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = City
|
||||
exclude = ("location",)
|
||||
|
||||
|
||||
class PostalCodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PostalCode
|
||||
exclude = ("location",)
|
||||
|
||||
|
||||
class UnregisteredCustomerAddressSerializer(serializers.Serializer):
|
||||
billing_customer_city = serializers.CharField(required=True)
|
||||
billing_customer_region = serializers.CharField(required=True)
|
||||
billing_customer_country = serializers.CharField(required=True)
|
||||
billing_customer_postal_code = serializers.CharField(required=True)
|
||||
billing_customer_address_line = serializers.CharField(required=True)
|
||||
def create(self, validated_data):
|
||||
raw = validated_data.pop("raw_data")
|
||||
return Address.objects.create(raw_data=raw, **validated_data)
|
||||
|
|
|
|||
|
|
@ -1,395 +0,0 @@
|
|||
from django.db import models # noqa: N999
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
def forwards(self, orm):
|
||||
# Adding model 'Country'
|
||||
db.create_table(
|
||||
"geo_country",
|
||||
(
|
||||
("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
|
||||
("name", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("slug", self.gf("django.db.models.fields.CharField")(max_length=200)),
|
||||
("code", self.gf("django.db.models.fields.CharField")(max_length=2, db_index=True)),
|
||||
("code3", self.gf("django.db.models.fields.CharField")(max_length=3, db_index=True)),
|
||||
("population", self.gf("django.db.models.fields.IntegerField")()),
|
||||
("area", self.gf("django.db.models.fields.IntegerField")(null=True)),
|
||||
("currency", self.gf("django.db.models.fields.CharField")(max_length=3, null=True)),
|
||||
("currency_name", self.gf("django.db.models.fields.CharField")(max_length=50, null=True)),
|
||||
("languages", self.gf("django.db.models.fields.CharField")(max_length=250, null=True)),
|
||||
("phone", self.gf("django.db.models.fields.CharField")(max_length=20)),
|
||||
("continent", self.gf("django.db.models.fields.CharField")(max_length=2)),
|
||||
("tld", self.gf("django.db.models.fields.CharField")(max_length=5)),
|
||||
("capital", self.gf("django.db.models.fields.CharField")(max_length=100)),
|
||||
),
|
||||
)
|
||||
db.send_create_signal("geo", ["Country"])
|
||||
|
||||
# Adding M2M table for field alt_names on 'Country'
|
||||
m2m_table_name = db.shorten_name("geo_country_alt_names")
|
||||
db.create_table(
|
||||
m2m_table_name,
|
||||
(
|
||||
("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
|
||||
("country", models.ForeignKey(orm["geo.country"], null=False)),
|
||||
("alternativename", models.ForeignKey(orm["geo.alternativename"], null=False)),
|
||||
),
|
||||
)
|
||||
db.create_unique(m2m_table_name, ["country_id", "alternativename_id"])
|
||||
|
||||
# Adding M2M table for field neighbours on 'Country'
|
||||
m2m_table_name = db.shorten_name("geo_country_neighbours")
|
||||
db.create_table(
|
||||
m2m_table_name,
|
||||
(
|
||||
("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
|
||||
("from_country", models.ForeignKey(orm["geo.country"], null=False)),
|
||||
("to_country", models.ForeignKey(orm["geo.country"], null=False)),
|
||||
),
|
||||
)
|
||||
db.create_unique(m2m_table_name, ["from_country_id", "to_country_id"])
|
||||
|
||||
# Adding model 'Region'
|
||||
db.create_table(
|
||||
"geo_region",
|
||||
(
|
||||
("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
|
||||
("name", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("slug", self.gf("django.db.models.fields.CharField")(max_length=200)),
|
||||
("name_std", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("code", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("country", self.gf("django.db.models.fields.related.ForeignKey")(to=orm["geo.Country"])),
|
||||
),
|
||||
)
|
||||
db.send_create_signal("geo", ["Region"])
|
||||
|
||||
# Adding M2M table for field alt_names on 'Region'
|
||||
m2m_table_name = db.shorten_name("geo_region_alt_names")
|
||||
db.create_table(
|
||||
m2m_table_name,
|
||||
(
|
||||
("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
|
||||
("region", models.ForeignKey(orm["geo.region"], null=False)),
|
||||
("alternativename", models.ForeignKey(orm["geo.alternativename"], null=False)),
|
||||
),
|
||||
)
|
||||
db.create_unique(m2m_table_name, ["region_id", "alternativename_id"])
|
||||
|
||||
# Adding model 'Subregion'
|
||||
db.create_table(
|
||||
"geo_subregion",
|
||||
(
|
||||
("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
|
||||
("name", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("slug", self.gf("django.db.models.fields.CharField")(max_length=200)),
|
||||
("name_std", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("code", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("region", self.gf("django.db.models.fields.related.ForeignKey")(to=orm["geo.Region"])),
|
||||
),
|
||||
)
|
||||
db.send_create_signal("geo", ["Subregion"])
|
||||
|
||||
# Adding M2M table for field alt_names on 'Subregion'
|
||||
m2m_table_name = db.shorten_name("geo_subregion_alt_names")
|
||||
db.create_table(
|
||||
m2m_table_name,
|
||||
(
|
||||
("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
|
||||
("subregion", models.ForeignKey(orm["geo.subregion"], null=False)),
|
||||
("alternativename", models.ForeignKey(orm["geo.alternativename"], null=False)),
|
||||
),
|
||||
)
|
||||
db.create_unique(m2m_table_name, ["subregion_id", "alternativename_id"])
|
||||
|
||||
# Adding model 'City'
|
||||
db.create_table(
|
||||
"geo_city",
|
||||
(
|
||||
("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
|
||||
("name", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("slug", self.gf("django.db.models.fields.CharField")(max_length=200)),
|
||||
("name_std", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("location", self.gf("django.contrib.gis.db.models.fields.PointField")()),
|
||||
("population", self.gf("django.db.models.fields.IntegerField")()),
|
||||
(
|
||||
"region",
|
||||
self.gf("django.db.models.fields.related.ForeignKey")(to=orm["geo.Region"], null=True, blank=True),
|
||||
),
|
||||
(
|
||||
"subregion",
|
||||
self.gf("django.db.models.fields.related.ForeignKey")(
|
||||
to=orm["geo.Subregion"], null=True, blank=True
|
||||
),
|
||||
),
|
||||
("country", self.gf("django.db.models.fields.related.ForeignKey")(to=orm["geo.Country"])),
|
||||
("elevation", self.gf("django.db.models.fields.IntegerField")(null=True)),
|
||||
("kind", self.gf("django.db.models.fields.CharField")(max_length=10)),
|
||||
("timezone", self.gf("django.db.models.fields.CharField")(max_length=40)),
|
||||
),
|
||||
)
|
||||
db.send_create_signal("geo", ["City"])
|
||||
|
||||
# Adding M2M table for field alt_names on 'City'
|
||||
m2m_table_name = db.shorten_name("geo_city_alt_names")
|
||||
db.create_table(
|
||||
m2m_table_name,
|
||||
(
|
||||
("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
|
||||
("city", models.ForeignKey(orm["geo.city"], null=False)),
|
||||
("alternativename", models.ForeignKey(orm["geo.alternativename"], null=False)),
|
||||
),
|
||||
)
|
||||
db.create_unique(m2m_table_name, ["city_id", "alternativename_id"])
|
||||
|
||||
# Adding model 'District'
|
||||
db.create_table(
|
||||
"geo_district",
|
||||
(
|
||||
("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
|
||||
("name", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("slug", self.gf("django.db.models.fields.CharField")(max_length=200)),
|
||||
("name_std", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("location", self.gf("django.contrib.gis.db.models.fields.PointField")()),
|
||||
("population", self.gf("django.db.models.fields.IntegerField")()),
|
||||
("city", self.gf("django.db.models.fields.related.ForeignKey")(to=orm["geo.City"])),
|
||||
),
|
||||
)
|
||||
db.send_create_signal("geo", ["District"])
|
||||
|
||||
# Adding M2M table for field alt_names on 'District'
|
||||
m2m_table_name = db.shorten_name("geo_district_alt_names")
|
||||
db.create_table(
|
||||
m2m_table_name,
|
||||
(
|
||||
("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
|
||||
("district", models.ForeignKey(orm["geo.district"], null=False)),
|
||||
("alternativename", models.ForeignKey(orm["geo.alternativename"], null=False)),
|
||||
),
|
||||
)
|
||||
db.create_unique(m2m_table_name, ["district_id", "alternativename_id"])
|
||||
|
||||
# Adding model 'AlternativeName'
|
||||
db.create_table(
|
||||
"geo_alternativename",
|
||||
(
|
||||
("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
|
||||
("name", self.gf("django.db.models.fields.CharField")(max_length=256)),
|
||||
("language", self.gf("django.db.models.fields.CharField")(max_length=100)),
|
||||
("is_preferred", self.gf("django.db.models.fields.BooleanField")(default=False)),
|
||||
("is_short", self.gf("django.db.models.fields.BooleanField")(default=False)),
|
||||
("is_colloquial", self.gf("django.db.models.fields.BooleanField")(default=False)),
|
||||
),
|
||||
)
|
||||
db.send_create_signal("geo", ["AlternativeName"])
|
||||
|
||||
# Adding model 'PostalCode'
|
||||
db.create_table(
|
||||
"geo_postalcode",
|
||||
(
|
||||
("id", self.gf("django.db.models.fields.AutoField")(primary_key=True)),
|
||||
("name", self.gf("django.db.models.fields.CharField")(max_length=200, db_index=True)),
|
||||
("slug", self.gf("django.db.models.fields.CharField")(max_length=200)),
|
||||
("code", self.gf("django.db.models.fields.CharField")(max_length=20)),
|
||||
("location", self.gf("django.contrib.gis.db.models.fields.PointField")()),
|
||||
(
|
||||
"country",
|
||||
self.gf("django.db.models.fields.related.ForeignKey")(
|
||||
related_name="postal_codes", to=orm["geo.Country"]
|
||||
),
|
||||
),
|
||||
("region_name", self.gf("django.db.models.fields.CharField")(max_length=100, db_index=True)),
|
||||
("subregion_name", self.gf("django.db.models.fields.CharField")(max_length=100, db_index=True)),
|
||||
("district_name", self.gf("django.db.models.fields.CharField")(max_length=100, db_index=True)),
|
||||
),
|
||||
)
|
||||
db.send_create_signal("geo", ["PostalCode"])
|
||||
|
||||
# Adding M2M table for field alt_names on 'PostalCode'
|
||||
m2m_table_name = db.shorten_name("geo_postalcode_alt_names")
|
||||
db.create_table(
|
||||
m2m_table_name,
|
||||
(
|
||||
("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
|
||||
("postalcode", models.ForeignKey(orm["geo.postalcode"], null=False)),
|
||||
("alternativename", models.ForeignKey(orm["geo.alternativename"], null=False)),
|
||||
),
|
||||
)
|
||||
db.create_unique(m2m_table_name, ["postalcode_id", "alternativename_id"])
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'Country'
|
||||
db.delete_table("geo_country")
|
||||
|
||||
# Removing M2M table for field alt_names on 'Country'
|
||||
db.delete_table(db.shorten_name("geo_country_alt_names"))
|
||||
|
||||
# Removing M2M table for field neighbours on 'Country'
|
||||
db.delete_table(db.shorten_name("geo_country_neighbours"))
|
||||
|
||||
# Deleting model 'Region'
|
||||
db.delete_table("geo_region")
|
||||
|
||||
# Removing M2M table for field alt_names on 'Region'
|
||||
db.delete_table(db.shorten_name("geo_region_alt_names"))
|
||||
|
||||
# Deleting model 'Subregion'
|
||||
db.delete_table("geo_subregion")
|
||||
|
||||
# Removing M2M table for field alt_names on 'Subregion'
|
||||
db.delete_table(db.shorten_name("geo_subregion_alt_names"))
|
||||
|
||||
# Deleting model 'City'
|
||||
db.delete_table("geo_city")
|
||||
|
||||
# Removing M2M table for field alt_names on 'City'
|
||||
db.delete_table(db.shorten_name("geo_city_alt_names"))
|
||||
|
||||
# Deleting model 'District'
|
||||
db.delete_table("geo_district")
|
||||
|
||||
# Removing M2M table for field alt_names on 'District'
|
||||
db.delete_table(db.shorten_name("geo_district_alt_names"))
|
||||
|
||||
# Deleting model 'AlternativeName'
|
||||
db.delete_table("geo_alternativename")
|
||||
|
||||
# Deleting model 'PostalCode'
|
||||
db.delete_table("geo_postalcode")
|
||||
|
||||
# Removing M2M table for field alt_names on 'PostalCode'
|
||||
db.delete_table(db.shorten_name("geo_postalcode_alt_names"))
|
||||
|
||||
models = {
|
||||
"geo.alternativename": {
|
||||
"Meta": {"object_name": "AlternativeName"},
|
||||
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
|
||||
"is_colloquial": ("django.db.models.fields.BooleanField", [], {"default": "False"}),
|
||||
"is_preferred": ("django.db.models.fields.BooleanField", [], {"default": "False"}),
|
||||
"is_short": ("django.db.models.fields.BooleanField", [], {"default": "False"}),
|
||||
"language": ("django.db.models.fields.CharField", [], {"max_length": "100"}),
|
||||
"name": ("django.db.models.fields.CharField", [], {"max_length": "256"}),
|
||||
},
|
||||
"geo.city": {
|
||||
"Meta": {"object_name": "City"},
|
||||
"alt_names": (
|
||||
"django.db.models.fields.related.ManyToManyField",
|
||||
[],
|
||||
{"to": "orm['geo.AlternativeName']", "symmetrical": "False"},
|
||||
),
|
||||
"country": ("django.db.models.fields.related.ForeignKey", [], {"to": "orm['geo.Country']"}),
|
||||
"elevation": ("django.db.models.fields.IntegerField", [], {"null": "True"}),
|
||||
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
|
||||
"kind": ("django.db.models.fields.CharField", [], {"max_length": "10"}),
|
||||
"location": ("django.contrib.gis.db.models.fields.PointField", [], {}),
|
||||
"name": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"name_std": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"population": ("django.db.models.fields.IntegerField", [], {}),
|
||||
"region": (
|
||||
"django.db.models.fields.related.ForeignKey",
|
||||
[],
|
||||
{"to": "orm['geo.Region']", "null": "True", "blank": "True"},
|
||||
),
|
||||
"slug": ("django.db.models.fields.CharField", [], {"max_length": "200"}),
|
||||
"subregion": (
|
||||
"django.db.models.fields.related.ForeignKey",
|
||||
[],
|
||||
{"to": "orm['geo.Subregion']", "null": "True", "blank": "True"},
|
||||
),
|
||||
"timezone": ("django.db.models.fields.CharField", [], {"max_length": "40"}),
|
||||
},
|
||||
"geo.country": {
|
||||
"Meta": {"ordering": "['name']", "object_name": "Country"},
|
||||
"alt_names": (
|
||||
"django.db.models.fields.related.ManyToManyField",
|
||||
[],
|
||||
{"to": "orm['geo.AlternativeName']", "symmetrical": "False"},
|
||||
),
|
||||
"area": ("django.db.models.fields.IntegerField", [], {"null": "True"}),
|
||||
"capital": ("django.db.models.fields.CharField", [], {"max_length": "100"}),
|
||||
"code": ("django.db.models.fields.CharField", [], {"max_length": "2", "db_index": "True"}),
|
||||
"code3": ("django.db.models.fields.CharField", [], {"max_length": "3", "db_index": "True"}),
|
||||
"continent": ("django.db.models.fields.CharField", [], {"max_length": "2"}),
|
||||
"currency": ("django.db.models.fields.CharField", [], {"max_length": "3", "null": "True"}),
|
||||
"currency_name": ("django.db.models.fields.CharField", [], {"max_length": "50", "null": "True"}),
|
||||
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
|
||||
"languages": ("django.db.models.fields.CharField", [], {"max_length": "250", "null": "True"}),
|
||||
"name": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"neighbours": (
|
||||
"django.db.models.fields.related.ManyToManyField",
|
||||
[],
|
||||
{"related_name": "'neighbours_rel_+'", "to": "orm['geo.Country']"},
|
||||
),
|
||||
"phone": ("django.db.models.fields.CharField", [], {"max_length": "20"}),
|
||||
"population": ("django.db.models.fields.IntegerField", [], {}),
|
||||
"slug": ("django.db.models.fields.CharField", [], {"max_length": "200"}),
|
||||
"tld": ("django.db.models.fields.CharField", [], {"max_length": "5"}),
|
||||
},
|
||||
"geo.district": {
|
||||
"Meta": {"object_name": "District"},
|
||||
"alt_names": (
|
||||
"django.db.models.fields.related.ManyToManyField",
|
||||
[],
|
||||
{"to": "orm['geo.AlternativeName']", "symmetrical": "False"},
|
||||
),
|
||||
"city": ("django.db.models.fields.related.ForeignKey", [], {"to": "orm['geo.City']"}),
|
||||
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
|
||||
"location": ("django.contrib.gis.db.models.fields.PointField", [], {}),
|
||||
"name": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"name_std": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"population": ("django.db.models.fields.IntegerField", [], {}),
|
||||
"slug": ("django.db.models.fields.CharField", [], {"max_length": "200"}),
|
||||
},
|
||||
"geo.postalcode": {
|
||||
"Meta": {"object_name": "PostalCode"},
|
||||
"alt_names": (
|
||||
"django.db.models.fields.related.ManyToManyField",
|
||||
[],
|
||||
{"to": "orm['geo.AlternativeName']", "symmetrical": "False"},
|
||||
),
|
||||
"code": ("django.db.models.fields.CharField", [], {"max_length": "20"}),
|
||||
"country": (
|
||||
"django.db.models.fields.related.ForeignKey",
|
||||
[],
|
||||
{"related_name": "'postal_codes'", "to": "orm['geo.Country']"},
|
||||
),
|
||||
"district_name": ("django.db.models.fields.CharField", [], {"max_length": "100", "db_index": "True"}),
|
||||
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
|
||||
"location": ("django.contrib.gis.db.models.fields.PointField", [], {}),
|
||||
"name": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"region_name": ("django.db.models.fields.CharField", [], {"max_length": "100", "db_index": "True"}),
|
||||
"slug": ("django.db.models.fields.CharField", [], {"max_length": "200"}),
|
||||
"subregion_name": ("django.db.models.fields.CharField", [], {"max_length": "100", "db_index": "True"}),
|
||||
},
|
||||
"geo.region": {
|
||||
"Meta": {"object_name": "Region"},
|
||||
"alt_names": (
|
||||
"django.db.models.fields.related.ManyToManyField",
|
||||
[],
|
||||
{"to": "orm['geo.AlternativeName']", "symmetrical": "False"},
|
||||
),
|
||||
"code": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"country": ("django.db.models.fields.related.ForeignKey", [], {"to": "orm['geo.Country']"}),
|
||||
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
|
||||
"name": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"name_std": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"slug": ("django.db.models.fields.CharField", [], {"max_length": "200"}),
|
||||
},
|
||||
"geo.subregion": {
|
||||
"Meta": {"object_name": "Subregion"},
|
||||
"alt_names": (
|
||||
"django.db.models.fields.related.ManyToManyField",
|
||||
[],
|
||||
{"to": "orm['geo.AlternativeName']", "symmetrical": "False"},
|
||||
),
|
||||
"code": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"id": ("django.db.models.fields.AutoField", [], {"primary_key": "True"}),
|
||||
"name": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"name_std": ("django.db.models.fields.CharField", [], {"max_length": "200", "db_index": "True"}),
|
||||
"region": ("django.db.models.fields.related.ForeignKey", [], {"to": "orm['geo.Region']"}),
|
||||
"slug": ("django.db.models.fields.CharField", [], {"max_length": "200"}),
|
||||
},
|
||||
}
|
||||
|
||||
complete_apps = ["geo"]
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from geo.viewsets import AddressViewSet, CityViewSet, CountryViewSet, PostalCodeViewSet, StateViewSet
|
||||
from geo.viewsets import AddressViewSet
|
||||
|
||||
geo_router = DefaultRouter()
|
||||
geo_router.register(r"addresses", AddressViewSet, basename="addresses")
|
||||
geo_router.register(r"countries", CountryViewSet, basename="countries")
|
||||
geo_router.register(r"states", StateViewSet, basename="states")
|
||||
geo_router.register(r"cities", CityViewSet, basename="cities")
|
||||
geo_router.register(r"postal_codes", PostalCodeViewSet, basename="postal_codes")
|
||||
|
||||
urlpatterns = [
|
||||
path(r"", include(geo_router.urls)),
|
||||
|
|
|
|||
101
geo/util.py
101
geo/util.py
|
|
@ -1,101 +0,0 @@
|
|||
import re
|
||||
import unicodedata
|
||||
from math import acos, cos, radians, sin
|
||||
|
||||
import six
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
|
||||
if DJANGO_VERSION < (4, 0):
|
||||
try:
|
||||
from django.utils.encoding import force_unicode as force_text
|
||||
except (NameError, ImportError):
|
||||
from django.utils.encoding import force_text
|
||||
else:
|
||||
from django.utils.encoding import force_str as force_text
|
||||
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
|
||||
from .conf import CONTINENT_DATA
|
||||
|
||||
unicode_func = str
|
||||
|
||||
|
||||
# GEO DISTANCE
|
||||
|
||||
earth_radius_km = 6371.009
|
||||
|
||||
|
||||
def geo_distance(a, b):
|
||||
"""Distance between two geo points in km. (p.x = long, p.y = lat)"""
|
||||
a_y = radians(a.y)
|
||||
b_y = radians(b.y)
|
||||
delta_x = radians(a.x - b.x)
|
||||
cos_x = sin(a_y) * sin(b_y) + cos(a_y) * cos(b_y) * cos(delta_x)
|
||||
return acos(cos_x) * earth_radius_km
|
||||
|
||||
|
||||
# ADD CONTINENTS FUNCTION
|
||||
|
||||
|
||||
def add_continents(continent_model):
|
||||
for ccode, cdata in CONTINENT_DATA.items():
|
||||
try:
|
||||
c = continent_model.objects.get(code=ccode)
|
||||
except continent_model.DoesNotExist:
|
||||
c = continent_model()
|
||||
c.id = cdata[1]
|
||||
c.name = cdata[0]
|
||||
c.code = ccode
|
||||
c.slug = c.name
|
||||
c.save()
|
||||
|
||||
|
||||
# SLUGIFY REGEXES
|
||||
|
||||
to_und_rgx = re.compile(r"[']", re.UNICODE)
|
||||
slugify_rgx = re.compile(r"[^-\w._~]", re.UNICODE)
|
||||
multi_dash_rgx = re.compile(r"-{2,}", re.UNICODE)
|
||||
dash_und_rgx = re.compile(r"[-_]_", re.UNICODE)
|
||||
und_dash_rgx = re.compile(r"[-_]-", re.UNICODE)
|
||||
starting_chars_rgx = re.compile(r"^[-._]*", re.UNICODE)
|
||||
ending_chars_rgx = re.compile(r"[-._]*$", re.UNICODE)
|
||||
|
||||
|
||||
def default_slugify(obj, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
value = force_text(unicode_func(value))
|
||||
value = unicodedata.normalize("NFKC", value.strip())
|
||||
value = re.sub(to_und_rgx, "_", value)
|
||||
value = re.sub(slugify_rgx, "-", value)
|
||||
value = re.sub(multi_dash_rgx, "-", value)
|
||||
value = re.sub(dash_und_rgx, "_", value)
|
||||
value = re.sub(und_dash_rgx, "_", value)
|
||||
value = re.sub(starting_chars_rgx, "", value)
|
||||
value = re.sub(ending_chars_rgx, "", value)
|
||||
return mark_safe(value)
|
||||
|
||||
|
||||
if DJANGO_VERSION < (1, 10):
|
||||
from django.utils.functional import allow_lazy
|
||||
|
||||
default_slugify = allow_lazy(default_slugify, six.text_type, SafeText)
|
||||
else:
|
||||
from django.utils.functional import keep_lazy
|
||||
|
||||
default_slugify = keep_lazy(six.text_type, SafeText)(default_slugify)
|
||||
|
||||
|
||||
# DJANGO BACKWARDS-COMPATIBLE PATTERNS
|
||||
|
||||
|
||||
def patterns(prefix, *args):
|
||||
if DJANGO_VERSION < (1, 9):
|
||||
from django.conf.urls import patterns as django_patterns
|
||||
|
||||
return django_patterns(prefix, *args)
|
||||
elif prefix != "":
|
||||
raise Exception("You need to update your URLConf to be a list of URL objects")
|
||||
else:
|
||||
return list(args)
|
||||
34
geo/utils/nominatim.py
Normal file
34
geo/utils/nominatim.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from typing import Dict, List
|
||||
|
||||
import requests
|
||||
from constance import config
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
def fetch_address_suggestions(
|
||||
query: str,
|
||||
limit: int = 5
|
||||
) -> List[Dict]:
|
||||
if not config.NOMINATIM_URL:
|
||||
raise ValueError(_("NOMINATIM_URL must be configured."))
|
||||
|
||||
url = config.NOMINATIM_URL.rstrip('/') + '/search'
|
||||
params = {
|
||||
'format': 'json',
|
||||
'addressdetails': 1,
|
||||
'q': query,
|
||||
'limit': limit,
|
||||
}
|
||||
response = requests.get(url, params=params)
|
||||
response.raise_for_status()
|
||||
results = response.json()
|
||||
|
||||
suggestions = []
|
||||
for item in results:
|
||||
suggestions.append({
|
||||
'display_name': item.get('display_name'),
|
||||
'lat': item.get('lat'),
|
||||
'lon': item.get('lon'),
|
||||
'address': item.get('address', {}),
|
||||
})
|
||||
return suggestions
|
||||
|
|
@ -1,36 +1,44 @@
|
|||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import AllowAny, IsAdminUser
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.utils import extend_schema_view
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
|
||||
from core.permissions import IsOwner
|
||||
from geo.models import Address, City, Region
|
||||
from geo.serializers import AddressSerializer, CitySerializer, CountrySerializer, PostalCodeSerializer, RegionSerializer
|
||||
from geo.docs.drf.viewsets import ADDRESS_SCHEMA
|
||||
from geo.models import Address
|
||||
from geo.serializers import AddressAutocompleteInputSerializer, AddressCreateSerializer, AddressSerializer
|
||||
from geo.utils.nominatim import fetch_address_suggestions
|
||||
|
||||
|
||||
@extend_schema_view(**ADDRESS_SCHEMA)
|
||||
class AddressViewSet(viewsets.ModelViewSet):
|
||||
queryset = Address.objects.all()
|
||||
serializer_class = AddressSerializer
|
||||
permission_classes = [IsOwner, IsAdminUser]
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'create':
|
||||
return AddressCreateSerializer
|
||||
if self.action == 'autocomplete':
|
||||
return AddressAutocompleteInputSerializer
|
||||
return AddressSerializer
|
||||
|
||||
class CountryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Address.objects.all()
|
||||
serializer_class = CountrySerializer
|
||||
permission_classes = [AllowAny]
|
||||
@action(detail=False, methods=["get"], url_path="autocomplete")
|
||||
def autocomplete(self, request):
|
||||
serializer = AddressAutocompleteInputSerializer(data=request.query_params)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
q = serializer.validated_data["q"]
|
||||
limit = serializer.validated_data["limit"]
|
||||
|
||||
class StateViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Region.objects.all()
|
||||
serializer_class = RegionSerializer
|
||||
permission_classes = [AllowAny]
|
||||
try:
|
||||
suggestions = fetch_address_suggestions(query=q, limit=limit)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"detail": _(f"Geocoding error: {e}")},
|
||||
status=status.HTTP_502_BAD_GATEWAY,
|
||||
)
|
||||
|
||||
|
||||
class CityViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = City.objects.all()
|
||||
serializer_class = CitySerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
|
||||
class PostalCodeViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Address.objects.all()
|
||||
serializer_class = PostalCodeSerializer
|
||||
permission_classes = [AllowAny]
|
||||
return Response(suggestions, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
Loading…
Reference in a new issue