Features: 1) None;

Fixes: 1) None;

Extra: 1) Removed the entire "geo" module, including migrations, model definitions, admin configurations, utilities, documentation, templates, translations, and related files. Moved functionality to "core".
This commit is contained in:
Egor Pavlovich Gorbunov 2025-05-20 08:00:44 +03:00
parent cf0069a532
commit 0ab8738520
97 changed files with 464 additions and 3089 deletions

View file

@ -27,10 +27,3 @@ db_backups/
services_data/
static/
media/
node_modules//geo/data/admin1CodesASCII.txt
/geo/data/admin2Codes.txt
/geo/data/allCountries.zip
/geo/data/alternateNames.zip
/geo/data/cities5000.zip
/geo/data/countryInfo.txt
/geo/data/hierarchy.zip

1
.gitignore vendored
View file

@ -81,7 +81,6 @@ services_data/postgres/*
services_data/redis/*
static
!core/static
!geo/static
!payments/static
!vibes_auth/static
media

View file

@ -3,6 +3,7 @@ from constance.admin import ConstanceAdmin as BaseConstanceAdmin
from django.apps import apps
from django.contrib import admin
from django.contrib.admin import ModelAdmin, TabularInline
from django.contrib.gis.admin import GISModelAdmin
from django.urls import path
from django.utils.translation import gettext_lazy as _
from mptt.admin import DraggableMPTTAdmin
@ -11,6 +12,7 @@ from evibes.settings import CONSTANCE_CONFIG, LANGUAGES
from .forms import OrderForm, OrderProductForm, VendorForm
from .models import (
Address,
Attribute,
AttributeGroup,
AttributeValue,
@ -360,6 +362,21 @@ class ProductImageAdmin(BasicModelAdmin):
autocomplete_fields = ("product",)
@admin.register(Address)
class AddressAdmin(GISModelAdmin):
list_display = ("street", "city", "region", "country", "user")
list_filter = ("country", "region")
search_fields = ("raw_data", "street", "city", "postal_code", "user__email")
gis_widget_kwargs = {
"attrs": {
"default_lon": 37.61556,
"default_lat": 55.75222,
"default_zoom": 6,
}
}
class ConstanceAdmin(BaseConstanceAdmin):
def get_urls(self):
info = f"{self.model._meta.app_label}_{self.model._meta.model_name}"

View file

@ -5,6 +5,9 @@ from rest_framework import status
from core.docs.drf import BASE_ERRORS
from core.serializers import (
AddOrderProductSerializer,
AddressAutocompleteInputSerializer,
AddressSerializer,
AddressSuggestionSerializer,
AddWishlistProductSerializer,
AttributeDetailSerializer,
AttributeGroupDetailSerializer,
@ -438,3 +441,59 @@ PRODUCT_SCHEMA = {
},
),
}
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,
},
),
}

View file

@ -11,12 +11,13 @@ from graphene_django.utils import camelize
from core.elasticsearch import process_query
from core.graphene import BaseMutation
from core.graphene.object_types import OrderType, ProductType, SearchResultsType, WishlistType
from core.models import Category, Order, Product, Wishlist
from core.graphene.object_types import AddressType, OrderType, ProductType, SearchResultsType, WishlistType
from core.models import Address, Category, Order, Product, Wishlist
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 core.utils.nominatim import fetch_address_suggestions
from payments.graphene.object_types import TransactionType
logger = logging.getLogger(__name__)
@ -455,6 +456,70 @@ class DeleteProduct(BaseMutation):
return DeleteProduct(ok=True)
class CreateAddress(BaseMutation):
class Arguments:
raw_data = String(
required=True,
description=_("original address string provided by the user")
)
address = Field(AddressType)
@staticmethod
def mutate(_parent, info, raw_data):
user = info.context.user if info.context.user.is_authenticated else None
address = Address.objects.create(
raw_data=raw_data,
user=user
)
return CreateAddress(address=address)
class DeleteAddress(BaseMutation):
class Arguments:
uuid = UUID(required=True)
success = Boolean()
@staticmethod
def mutate(_parent, info, uuid):
try:
address = Address.objects.get(uuid=uuid)
if (
info.context.user.is_superuser
or info.context.user.has_perm("core.delete_address")
or info.context.user == address.user
):
address.delete()
return DeleteAddress(success=True)
raise PermissionDenied(permission_denied_message)
except Address.DoesNotExist:
name = "Address"
raise Http404(_(f"{name} does not exist: {uuid}"))
class AutocompleteAddress(BaseMutation):
class Arguments:
q = String()
limit = 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)
class ContactUs(BaseMutation):
class Arguments:
email = String(required=True)

View file

@ -24,9 +24,8 @@ from core.models import (
Promotion,
Stock,
Vendor,
Wishlist,
Wishlist, Address,
)
from geo.graphene.object_types import AddressType
logger = __import__("logging").getLogger(__name__)
@ -359,6 +358,33 @@ class ProductType(DjangoObjectType):
return self.quantity or 0
class AddressType(DjangoObjectType):
latitude = Float(description=_("Latitude (Y coordinate)"))
longitude = Float(description=_("Longitude (X coordinate)"))
class Meta:
model = Address
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
def resolve_longitude(self, info):
return self.location.x if self.location else None
class AttributeValueType(DjangoObjectType):
value = String(description=_("attribute value"))

View file

@ -18,12 +18,15 @@ from core.filters import (
from core.graphene.mutations import (
AddOrderProduct,
AddWishlistProduct,
AutocompleteAddress,
BuyOrder,
BuyProduct,
BuyWishlist,
CacheOperator,
ContactUs,
CreateAddress,
CreateProduct,
DeleteAddress,
DeleteProduct,
RemoveAllOrderProducts,
RemoveAllWishlistProducts,
@ -70,7 +73,6 @@ 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 (

View file

@ -114,7 +114,7 @@ class Command(BaseCommand):
action="append",
required=True,
metavar="APP",
help="App label for translation, e.g. core, geo."
help="App label for translation, e.g. core, payments."
)
def handle(self, *args, **options) -> None:

View file

@ -76,7 +76,7 @@ class Command(BaseCommand):
action='append',
required=True,
metavar='APP',
help='App label(s) to scan, e.g. core, geo'
help='App label(s) to scan, e.g. core, payments'
)
parser.add_argument(
'-p', '--path',

View file

@ -0,0 +1,61 @@
import requests
from constance import config
from django.contrib.gis.geos import Point
from django.db import models
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
)

View file

@ -15,10 +15,6 @@ import core.validators
class Migration(migrations.Migration):
initial = True
dependencies = [
('geo', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Feedback',
@ -991,14 +987,6 @@ class Migration(migrations.Migration):
('buy_time',
models.DateTimeField(blank=True, default=None, help_text='the timestamp when the order was finalized',
null=True, verbose_name='buy time')),
('billing_address',
models.ForeignKey(blank=True, help_text='the billing address used for this order', null=True,
on_delete=django.db.models.deletion.CASCADE, related_name='billing_address_order',
to='geo.address', verbose_name='billing address')),
('shipping_address',
models.ForeignKey(blank=True, help_text='the shipping address used for this order', null=True,
on_delete=django.db.models.deletion.CASCADE, related_name='shipping_address_order',
to='geo.address', verbose_name='shipping address')),
],
options={
'verbose_name': 'order',

View file

@ -1,4 +1,4 @@
# Generated by Django 5.2 on 2025-05-19 11:38
# Generated by Django 5.2 on 2025-05-20 04:57
import uuid
@ -10,9 +10,8 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('core', '0018_alter_order_human_readable_id'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@ -38,20 +37,20 @@ class Migration(migrations.Migration):
('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.PointField(blank=True, geography=True,
help_text='Geolocation point: (longitude, latitude)',
('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',
('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)),
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={
'verbose_name': 'address',
'verbose_name_plural': 'addresses',
'indexes': [models.Index(fields=['location'], name='geo_address_locatio_83b064_idx')],
'indexes': [models.Index(fields=['location'], name='core_addres_locatio_eb6b39_idx')],
},
),
]

View file

@ -0,0 +1,27 @@
# Generated by Django 5.2 on 2025-05-20 04:59
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0019_address'),
]
operations = [
migrations.AddField(
model_name='order',
name='billing_address',
field=models.ForeignKey(blank=True, help_text='the billing address used for this order', null=True,
on_delete=django.db.models.deletion.CASCADE, related_name='billing_address_order',
to='core.address', verbose_name='billing address'),
),
migrations.AddField(
model_name='order',
name='shipping_address',
field=models.ForeignKey(blank=True, help_text='the shipping address used for this order', null=True,
on_delete=django.db.models.deletion.CASCADE, related_name='shipping_address_order',
to='core.address', verbose_name='shipping address'),
),
]

View file

@ -4,6 +4,7 @@ import logging
from typing import Self
from constance import config
from django.contrib.gis.db.models import PointField
from django.contrib.postgres.indexes import GinIndex
from django.core.cache import cache
from django.core.exceptions import BadRequest, ValidationError
@ -28,6 +29,7 @@ from django.db.models import (
PositiveIntegerField,
TextField,
)
from django.db.models.indexes import Index
from django.http import Http404
from django.utils import timezone
from django.utils.encoding import force_bytes
@ -40,11 +42,11 @@ from mptt.models import MPTTModel
from core.abstract import NiceModel
from core.choices import ORDER_PRODUCT_STATUS_CHOICES, ORDER_STATUS_CHOICES
from core.errors import DisabledCommerceError, NotEnoughMoneyError
from core.managers import AddressManager
from core.utils import generate_human_readable_id, get_product_uuid_as_path, get_random_code
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
from payments.models import Transaction
logger = logging.getLogger(__name__)
@ -433,7 +435,7 @@ class Order(NiceModel):
is_publicly_visible = False
billing_address = ForeignKey(
"geo.Address",
"core.Address",
on_delete=CASCADE,
blank=True,
null=True,
@ -450,7 +452,7 @@ class Order(NiceModel):
verbose_name=_("applied promo code"),
)
shipping_address = ForeignKey(
"geo.Address",
"core.Address",
on_delete=CASCADE,
blank=True,
null=True,
@ -1225,3 +1227,52 @@ class Documentary(NiceModel):
@property
def file_type(self):
return self.document.name.split(".")[-1] or _("unresolved")
class Address(NiceModel):
street = CharField(_("street"), max_length=255, null=True) # noqa: DJ001
district = CharField(_("district"), max_length=255, null=True) # noqa: DJ001
city = CharField(_("city"), max_length=100, null=True) # noqa: DJ001
region = CharField(_("region"), max_length=100, null=True) # noqa: DJ001
postal_code = CharField(_("postal code"), max_length=20, null=True) # noqa: DJ001
country = CharField(_("country"), max_length=40, null=True) # noqa: DJ001
location = PointField(
geography=True,
srid=4326,
null=True,
blank=True,
help_text=_("geolocation point: (longitude, latitude)")
)
raw_data = JSONField(
blank=True,
null=True,
help_text=_("full JSON response from geocoder for this address")
)
api_response = JSONField(
blank=True,
null=True,
help_text=_("stored JSON response from the geocoding service")
)
user = ForeignKey(
to="vibes_auth.User",
on_delete=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

View file

@ -1,6 +1,18 @@
from rest_framework.fields import BooleanField, CharField, Field, IntegerField, JSONField, ListField, UUIDField
from rest_framework.fields import (
BooleanField,
CharField,
DictField,
Field,
FloatField,
IntegerField,
JSONField,
ListField,
UUIDField,
)
from rest_framework.serializers import ListSerializer, Serializer
from core.models import Address
from .detail import * # noqa: F403
from .simple import * # noqa: F403
@ -91,3 +103,65 @@ class BuyAsBusinessOrderSerializer(Serializer):
billing_business_address_uuid = CharField(required=False)
shipping_business_address_uuid = CharField(required=False)
payment_method = CharField(required=True)
class AddressAutocompleteInputSerializer(Serializer):
q = CharField(
required=True
)
limit = IntegerField(
required=False,
min_value=1,
max_value=10,
default=5
)
class AddressSuggestionSerializer(Serializer):
display_name = CharField()
lat = FloatField()
lon = FloatField()
address = DictField(child=CharField())
class AddressSerializer(ModelSerializer): # noqa: F405
latitude = FloatField(source="location.y", read_only=True)
longitude = FloatField(source="location.x", read_only=True)
class Meta:
model = Address
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 AddressCreateSerializer(ModelSerializer): # noqa: F405
raw_data = CharField(
write_only=True,
max_length=512,
)
class Meta:
model = Address
fields = ["raw_data", "user"]
def create(self, validated_data):
raw = validated_data.pop("raw_data")
return Address.objects.create(raw_data=raw, **validated_data)

View file

Before

Width:  |  Height:  |  Size: 122 B

After

Width:  |  Height:  |  Size: 122 B

View file

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 269 B

View file

Before

Width:  |  Height:  |  Size: 126 B

After

Width:  |  Height:  |  Size: 126 B

View file

Before

Width:  |  Height:  |  Size: 99 B

After

Width:  |  Height:  |  Size: 99 B

View file

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 288 B

View file

Before

Width:  |  Height:  |  Size: 261 B

After

Width:  |  Height:  |  Size: 261 B

View file

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

View file

Before

Width:  |  Height:  |  Size: 108 B

After

Width:  |  Height:  |  Size: 108 B

View file

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

View file

Before

Width:  |  Height:  |  Size: 112 B

After

Width:  |  Height:  |  Size: 112 B

View file

Before

Width:  |  Height:  |  Size: 239 B

After

Width:  |  Height:  |  Size: 239 B

View file

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 282 B

View file

Before

Width:  |  Height:  |  Size: 99 B

After

Width:  |  Height:  |  Size: 99 B

View file

Before

Width:  |  Height:  |  Size: 98 B

After

Width:  |  Height:  |  Size: 98 B

View file

Before

Width:  |  Height:  |  Size: 513 B

After

Width:  |  Height:  |  Size: 513 B

View file

Before

Width:  |  Height:  |  Size: 108 B

After

Width:  |  Height:  |  Size: 108 B

View file

Before

Width:  |  Height:  |  Size: 99 B

After

Width:  |  Height:  |  Size: 99 B

View file

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 225 B

View file

@ -16,6 +16,7 @@ from rest_framework_xml.renderers import XMLRenderer
from rest_framework_yaml.renderers import YAMLRenderer
from core.docs.drf.viewsets import (
ADDRESS_SCHEMA,
ATTRIBUTE_GROUP_SCHEMA,
ATTRIBUTE_SCHEMA,
ATTRIBUTE_VALUE_SCHEMA,
@ -26,6 +27,7 @@ from core.docs.drf.viewsets import (
)
from core.filters import BrandFilter, CategoryFilter, OrderFilter, ProductFilter
from core.models import (
Address,
Attribute,
AttributeGroup,
AttributeValue,
@ -46,6 +48,9 @@ from core.models import (
from core.permissions import EvibesPermission
from core.serializers import (
AddOrderProductSerializer,
AddressAutocompleteInputSerializer,
AddressCreateSerializer,
AddressSerializer,
AddWishlistProductSerializer,
AttributeDetailSerializer,
AttributeGroupDetailSerializer,
@ -80,6 +85,7 @@ from core.serializers import (
)
from core.utils import format_attributes
from core.utils.messages import permission_denied_message
from core.utils.nominatim import fetch_address_suggestions
from payments.serializers import TransactionProcessSerializer
@ -461,3 +467,34 @@ class WishlistViewSet(EvibesViewSet):
return Response(status=status.HTTP_200_OK, data=WishlistDetailSerializer(wishlist).data)
except Order.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
@extend_schema_view(**ADDRESS_SCHEMA)
class AddressViewSet(EvibesViewSet):
queryset = Address.objects.all()
serializer_class = AddressSerializer
def get_serializer_class(self):
if self.action == 'create':
return AddressCreateSerializer
if self.action == 'autocomplete':
return AddressAutocompleteInputSerializer
return AddressSerializer
@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"]
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,
)
return Response(suggestions, status=status.HTTP_200_OK)

View file

@ -14,20 +14,21 @@ urlpatterns = [
path(r"graphql/", csrf_exempt(CustomGraphQLView.as_view(graphiql=True, schema=schema))),
path(
r"docs/",
SpectacularAPIView.as_view(urlconf="evibes.api_urls", custom_settings=SPECTACULAR_PLATFORM_SETTINGS),
SpectacularAPIView.as_view(urlconf="evibes.api_urls",
custom_settings=SPECTACULAR_PLATFORM_SETTINGS),
name="schema-platform",
),
path(r"docs/swagger/", CustomSwaggerView.as_view(url_name="schema-platform"), name="swagger-ui-platform"),
path(r"docs/swagger/", CustomSwaggerView.as_view(url_name="schema-platform"),
name="swagger-ui-platform"),
path(r"docs/redoc/", CustomRedocView.as_view(url_name="schema-platform"), name="redoc-ui-platform"),
path(r"i18n/", include("django.conf.urls.i18n")),
path(r"favicon.ico", favicon_view),
path(r"", index),
path(r"", include("core.api_urls")),
path(r"auth/", include("vibes_auth.urls")),
path(r"geo/", include("geo.urls")),
path(r"payments/", include("payments.urls")),
path(r"blog/", include("blog.urls")),
] + i18n_patterns(path("admin/", admin.site.urls))
] + i18n_patterns(path("admin/", admin.site.urls))
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View file

@ -91,7 +91,6 @@ INSTALLED_APPS = [
"django_mailbox",
"graphene_django",
"core",
"geo",
"payments",
"vibes_auth",
"blog",
@ -120,7 +119,6 @@ TEMPLATES = [
"DIRS": [
BASE_DIR / "vibes_auth/templates",
BASE_DIR / "core/templates",
BASE_DIR / "geo/templates",
BASE_DIR / "payments/templates",
],
"APP_DIRS": True,
@ -249,39 +247,32 @@ DAISY_SETTINGS = {
"priority": 1,
"apps": "",
},
"geo": {
"icon": "fa fa-solid fa-globe",
"hide": False,
"app": "geo",
"priority": 2,
"apps": "",
},
"payments": {
"icon": "fa fa-solid fa-wallet",
"hide": False,
"app": "payments",
"priority": 3,
"priority": 2,
"apps": "",
},
"blog": {
"icon": "fa fa-solid fa-book",
"hide": False,
"app": "blog",
"priority": 4,
"priority": 3,
"apps": "",
},
"core": {
"icon": "fa fa-solid fa-house",
"hide": False,
"app": "core",
"priority": 5,
"priority": 4,
"apps": "",
},
"vibes_auth": {
"icon": "fa fa-solid fa-user",
"hide": False,
"app": "vibes_auth",
"priority": 6,
"priority": 5,
"apps": "",
},
},

View file

@ -32,5 +32,4 @@ else:
"vibes_auth.*": {"ops": {"fetch", "get"}, "timeout": 60 * 60},
"auth.permission": {"ops": "all", "timeout": 60 * 60},
"core.*": {"ops": "all", "timeout": 60 * 60},
"geo.*": {"ops": "all", "timeout": 60 * 60},
}

View file

View file

@ -1,19 +0,0 @@
from django.contrib import admin
from django.contrib.gis.admin import GISModelAdmin
from .models import Address
@admin.register(Address)
class AddressAdmin(GISModelAdmin):
list_display = ("street", "city", "region", "country", "user")
list_filter = ("country", "region")
search_fields = ("raw_data", "street", "city", "postal_code", "user__email")
gis_widget_kwargs = {
"attrs": {
"default_lon": 37.61556,
"default_lat": 55.75222,
"default_zoom": 6,
}
}

View file

@ -1,8 +0,0 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class GeoConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "geo"
verbose_name = _("geodata")

View file

View file

@ -1,14 +0,0 @@
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,
}

View file

View file

@ -1,62 +0,0 @@
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,
},
),
}

View file

View file

@ -1,76 +0,0 @@
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
from geo.utils.nominatim import fetch_address_suggestions
class CreateAddress(graphene.Mutation):
class Arguments:
raw_data = graphene.String(
required=True,
description=_("original address string provided by the user")
)
address = graphene.Field(AddressType)
@staticmethod
def mutate(_parent, info, raw_data):
user = info.context.user if info.context.user.is_authenticated else None
address = Address.objects.create(
raw_data=raw_data,
user=user
)
return CreateAddress(address=address)
class DeleteAddress(BaseMutation):
class Arguments:
uuid = graphene.UUID(required=True)
success = graphene.Boolean()
@staticmethod
def mutate(_parent, info, uuid):
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
):
address.delete()
return DeleteAddress(success=True)
raise PermissionDenied(permission_denied_message)
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)

View file

@ -1,55 +0,0 @@
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
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 (110)"),
default_value=5
)
class AddressSuggestionType(ObjectType):
display_name = String()
lat = Float()
lon = Float()
address = GenericScalar(
description=_("the address breakdown as key/value pairs")
)
class AddressType(DjangoObjectType):
latitude = graphene.Float(description=_("Latitude (Y coordinate)"))
longitude = graphene.Float(description=_("Longitude (X coordinate)"))
class Meta:
model = Address
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
def resolve_longitude(self, info):
return self.location.x if self.location else None

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: ARABIC <CONTACT@FUREUNOIR.COM>\n"
"Language: ARABIC\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "البيانات الجغرافية"
#: geo/conf.py:407
msgid "Name"
msgstr "الاسم"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "الاختصار"
#: geo/conf.py:409
msgid "Link"
msgstr "رابط إلى"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "رمز (مطار) IATA (المطار)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "رمز (مطار) منظمة الطيران المدني الدولي (ICAO)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "الرمز (المطار) FAAC (المطار)"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} غير موجود: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} غير موجود: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} غير موجود: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "المعرف الفريد"
#: geo/models.py:48
msgid "active"
msgstr "نشط"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "يجب أن تنفذ الفئات الفرعية ل 'المكان' طريقة سلوغفايفي"
#: geo/models.py:123
msgid "continent"
msgstr "القارة"
#: geo/models.py:124
msgid "continents"
msgstr "القارات"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "البلد"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "الدول"
#: geo/models.py:182
msgid "region"
msgstr "المنطقة"
#: geo/models.py:183
msgid "regions"
msgstr "المناطق"
#: geo/models.py:205
msgid "subregion"
msgstr "المنطقة الفرعية"
#: geo/models.py:206
msgid "subregions"
msgstr "المناطق الفرعية"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "المدينة"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "المدن"
#: geo/models.py:270
msgid "district"
msgstr "المنطقة"
#: geo/models.py:271
msgid "districts"
msgstr "المقاطعات"
#: geo/models.py:344
msgid "postal code"
msgstr "الرمز البريدي"
#: geo/models.py:345
msgid "postal codes"
msgstr "الرموز البريدية"
#: geo/models.py:391
msgid "address"
msgstr "العنوان"
#: geo/models.py:392
msgid "addresses"
msgstr "العناوين"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: CZECH <CONTACT@FUREUNOIR.COM>\n"
"Language: CZECH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodata"
#: geo/conf.py:407
msgid "Name"
msgstr "Název"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Zkratka"
#: geo/conf.py:409
msgid "Link"
msgstr "Odkaz na"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "Kód IATA (letiště)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "Kód ICAO (letiště)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (letiště) Kód"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} neexistuje: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} neexistuje: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} neexistuje: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Jedinečné ID"
#: geo/models.py:48
msgid "active"
msgstr "Aktivní"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Podtřídy třídy `Místo` musí implementovat metodu slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Kontinent"
#: geo/models.py:124
msgid "continents"
msgstr "Kontinenty"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Země"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Země"
#: geo/models.py:182
msgid "region"
msgstr "Region"
#: geo/models.py:183
msgid "regions"
msgstr "Regiony"
#: geo/models.py:205
msgid "subregion"
msgstr "Subregion"
#: geo/models.py:206
msgid "subregions"
msgstr "Subregiony"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Město"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Města"
#: geo/models.py:270
msgid "district"
msgstr "Okres"
#: geo/models.py:271
msgid "districts"
msgstr "Okresy"
#: geo/models.py:344
msgid "postal code"
msgstr "Poštovní směrovací číslo"
#: geo/models.py:345
msgid "postal codes"
msgstr "Poštovní směrovací čísla"
#: geo/models.py:391
msgid "address"
msgstr "Adresa"
#: geo/models.py:392
msgid "addresses"
msgstr "Adresy"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: DANISH <CONTACT@FUREUNOIR.COM>\n"
"Language: DANISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodata"
#: geo/conf.py:407
msgid "Name"
msgstr "Navn"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Forkortelse"
#: geo/conf.py:409
msgid "Link"
msgstr "Link til"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "IATA-kode (lufthavn)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "ICAO-kode (lufthavn)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (lufthavn) Kode"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} findes ikke: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} findes ikke: {postal_code_uuid}."
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} findes ikke: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Unikt ID"
#: geo/models.py:48
msgid "active"
msgstr "Aktiv"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Underklasser af `Place` skal implementere slugify-metoden"
#: geo/models.py:123
msgid "continent"
msgstr "Kontinent"
#: geo/models.py:124
msgid "continents"
msgstr "Kontinenter"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Land"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Lande"
#: geo/models.py:182
msgid "region"
msgstr "Region"
#: geo/models.py:183
msgid "regions"
msgstr "Regioner"
#: geo/models.py:205
msgid "subregion"
msgstr "Underregion"
#: geo/models.py:206
msgid "subregions"
msgstr "Underregioner"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "By"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Byer"
#: geo/models.py:270
msgid "district"
msgstr "Distrikt"
#: geo/models.py:271
msgid "districts"
msgstr "Distrikter"
#: geo/models.py:344
msgid "postal code"
msgstr "Postnummer"
#: geo/models.py:345
msgid "postal codes"
msgstr "Postnumre"
#: geo/models.py:391
msgid "address"
msgstr "Adresse"
#: geo/models.py:392
msgid "addresses"
msgstr "Adresser"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodaten"
#: geo/conf.py:407
msgid "Name"
msgstr "Name"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abkürzung"
#: geo/conf.py:409
msgid "Link"
msgstr "Link zu"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "IATA (Flughafen) Code"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "ICAO (Flughafen) Code"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (Flughafen) Code"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} existiert nicht: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} existiert nicht: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} existiert nicht: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Eindeutige ID"
#: geo/models.py:48
msgid "active"
msgstr "Aktiv"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Unterklassen von `Place` müssen die slugify-Methode implementieren"
#: geo/models.py:123
msgid "continent"
msgstr "Kontinent"
#: geo/models.py:124
msgid "continents"
msgstr "Kontinente"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Land"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Länder"
#: geo/models.py:182
msgid "region"
msgstr "Region"
#: geo/models.py:183
msgid "regions"
msgstr "Regionen"
#: geo/models.py:205
msgid "subregion"
msgstr "Unterregion"
#: geo/models.py:206
msgid "subregions"
msgstr "Teilregionen"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Stadt"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Städte"
#: geo/models.py:270
msgid "district"
msgstr "Bezirk"
#: geo/models.py:271
msgid "districts"
msgstr "Bezirke"
#: geo/models.py:344
msgid "postal code"
msgstr "Postleitzahl"
#: geo/models.py:345
msgid "postal codes"
msgstr "Postleitzahlen"
#: geo/models.py:391
msgid "address"
msgstr "Adresse"
#: geo/models.py:392
msgid "addresses"
msgstr "Adressen"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
# ,fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodata"
#: geo/conf.py:407
msgid "Name"
msgstr "Name"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abbreviation"
#: geo/conf.py:409
msgid "Link"
msgstr "Link"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "IATA (Airport) Code"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "ICAO (Airport) Code"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (Airport) Code"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} does not exist: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} does not exist: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} does not exist: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Unique ID"
#: geo/models.py:48
msgid "active"
msgstr "Active"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Subclasses of `Place` must implement slugify method"
#: geo/models.py:123
msgid "continent"
msgstr "Continent"
#: geo/models.py:124
msgid "continents"
msgstr "Continents"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Country"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Countries"
#: geo/models.py:182
msgid "region"
msgstr "Region"
#: geo/models.py:183
msgid "regions"
msgstr "Regions"
#: geo/models.py:205
msgid "subregion"
msgstr "Subregion"
#: geo/models.py:206
msgid "subregions"
msgstr "Subregions"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "City"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Cities"
#: geo/models.py:270
msgid "district"
msgstr "District"
#: geo/models.py:271
msgid "districts"
msgstr "Districts"
#: geo/models.py:344
msgid "postal code"
msgstr "Postal code"
#: geo/models.py:345
msgid "postal codes"
msgstr "Postal codes"
#: geo/models.py:391
msgid "address"
msgstr "Address"
#: geo/models.py:392
msgid "addresses"
msgstr "Addresses"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodata"
#: geo/conf.py:407
msgid "Name"
msgstr "Name"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abbreviation"
#: geo/conf.py:409
msgid "Link"
msgstr "Link to"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "IATA (Airport) Code"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "ICAO (Airport) Code"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (Airport) Code"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} does not exist: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} does not exist: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} does not exist: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Unique ID"
#: geo/models.py:48
msgid "active"
msgstr "Active"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Subclasses of `Place` must implement slugify method"
#: geo/models.py:123
msgid "continent"
msgstr "Continent"
#: geo/models.py:124
msgid "continents"
msgstr "Continents"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Country"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Countries"
#: geo/models.py:182
msgid "region"
msgstr "Region"
#: geo/models.py:183
msgid "regions"
msgstr "Regions"
#: geo/models.py:205
msgid "subregion"
msgstr "Subregion"
#: geo/models.py:206
msgid "subregions"
msgstr "Subregions"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "City"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Cities"
#: geo/models.py:270
msgid "district"
msgstr "District"
#: geo/models.py:271
msgid "districts"
msgstr "Districts"
#: geo/models.py:344
msgid "postal code"
msgstr "Postal code"
#: geo/models.py:345
msgid "postal codes"
msgstr "Postal codes"
#: geo/models.py:391
msgid "address"
msgstr "Address"
#: geo/models.py:392
msgid "addresses"
msgstr "Addresses"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: SPANISH <CONTACT@FUREUNOIR.COM>\n"
"Language: SPANISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodatos"
#: geo/conf.py:407
msgid "Name"
msgstr "Nombre"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abreviatura"
#: geo/conf.py:409
msgid "Link"
msgstr "Enlace"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "Código IATA (aeropuerto)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "Código OACI (Aeropuerto)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (Aeropuerto) Código"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} no existe: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} no existe: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} no existe: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Identificación única"
#: geo/models.py:48
msgid "active"
msgstr "Activo"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Las subclases de `Place` deben implementar el método slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Continente"
#: geo/models.py:124
msgid "continents"
msgstr "Continentes"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "País"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Países"
#: geo/models.py:182
msgid "region"
msgstr "Región"
#: geo/models.py:183
msgid "regions"
msgstr "Regiones"
#: geo/models.py:205
msgid "subregion"
msgstr "Subregion"
#: geo/models.py:206
msgid "subregions"
msgstr "Subregiones"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Ciudad"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Ciudades"
#: geo/models.py:270
msgid "district"
msgstr "Distrito"
#: geo/models.py:271
msgid "districts"
msgstr "Distritos"
#: geo/models.py:344
msgid "postal code"
msgstr "Código postal"
#: geo/models.py:345
msgid "postal codes"
msgstr "Códigos postales"
#: geo/models.py:391
msgid "address"
msgstr "Dirección"
#: geo/models.py:392
msgid "addresses"
msgstr "Direcciones"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Géodonnées"
#: geo/conf.py:407
msgid "Name"
msgstr "Nom"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abréviation"
#: geo/conf.py:409
msgid "Link"
msgstr "Lien vers"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "Code IATA (aéroport)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "Code OACI (aéroport)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "Code FAAC (Aéroport)"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} n'existe pas : {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} n'existe pas : {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} n'existe pas : {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Unique ID"
#: geo/models.py:48
msgid "active"
msgstr "Actif"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Les sous-classes de `Place` doivent implémenter la méthode slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Continent"
#: geo/models.py:124
msgid "continents"
msgstr "Continents"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Pays"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Pays"
#: geo/models.py:182
msgid "region"
msgstr "Région"
#: geo/models.py:183
msgid "regions"
msgstr "Régions"
#: geo/models.py:205
msgid "subregion"
msgstr "Sous-région"
#: geo/models.py:206
msgid "subregions"
msgstr "Sous-régions"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Ville"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Villes"
#: geo/models.py:270
msgid "district"
msgstr "District"
#: geo/models.py:271
msgid "districts"
msgstr "Districts"
#: geo/models.py:344
msgid "postal code"
msgstr "Code postal"
#: geo/models.py:345
msgid "postal codes"
msgstr "Codes postaux"
#: geo/models.py:391
msgid "address"
msgstr "Adresse"
#: geo/models.py:392
msgid "addresses"
msgstr "Adresses"

View file

@ -1,137 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: geo/apps.py:8
msgid "geodata"
msgstr ""
#: geo/conf.py:407
msgid "Name"
msgstr ""
#: geo/conf.py:408
msgid "Abbreviation"
msgstr ""
#: geo/conf.py:409
msgid "Link"
msgstr ""
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr ""
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr ""
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr ""
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr ""
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr ""
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr ""
#: geo/models.py:47
msgid "UUID"
msgstr ""
#: geo/models.py:48
msgid "active"
msgstr ""
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr ""
#: geo/models.py:123
msgid "continent"
msgstr ""
#: geo/models.py:124
msgid "continents"
msgstr ""
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr ""
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr ""
#: geo/models.py:182
msgid "region"
msgstr ""
#: geo/models.py:183
msgid "regions"
msgstr ""
#: geo/models.py:205
msgid "subregion"
msgstr ""
#: geo/models.py:206
msgid "subregions"
msgstr ""
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr ""
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr ""
#: geo/models.py:270
msgid "district"
msgstr ""
#: geo/models.py:271
msgid "districts"
msgstr ""
#: geo/models.py:344
msgid "postal code"
msgstr ""
#: geo/models.py:345
msgid "postal codes"
msgstr ""
#: geo/models.py:391
msgid "address"
msgstr ""
#: geo/models.py:392
msgid "addresses"
msgstr ""

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: ITALIAN <CONTACT@FUREUNOIR.COM>\n"
"Language: ITALIAN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodati"
#: geo/conf.py:407
msgid "Name"
msgstr "Nome"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abbreviazione"
#: geo/conf.py:409
msgid "Link"
msgstr "Collegamento a"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "IATA (Airport) Code"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "ICAO (Airport) Code"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "Codice FAAC (Aeroporto)"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} non esiste: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} non esiste: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} non esiste: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "ID univoco"
#: geo/models.py:48
msgid "active"
msgstr "Attivo"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Le sottoclassi di `Place` devono implementare il metodo slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Continente"
#: geo/models.py:124
msgid "continents"
msgstr "Continenti"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Paese"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Paesi"
#: geo/models.py:182
msgid "region"
msgstr "Regione"
#: geo/models.py:183
msgid "regions"
msgstr "Regioni"
#: geo/models.py:205
msgid "subregion"
msgstr "Sottoregione"
#: geo/models.py:206
msgid "subregions"
msgstr "Sottoregioni"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Città"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Città"
#: geo/models.py:270
msgid "district"
msgstr "Distretto"
#: geo/models.py:271
msgid "districts"
msgstr "Distretti"
#: geo/models.py:344
msgid "postal code"
msgstr "Codice postale"
#: geo/models.py:345
msgid "postal codes"
msgstr "Codici postali"
#: geo/models.py:391
msgid "address"
msgstr "Indirizzo"
#: geo/models.py:392
msgid "addresses"
msgstr "Indirizzi"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "ジオデータ"
#: geo/conf.py:407
msgid "Name"
msgstr "名称"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "略語"
#: geo/conf.py:409
msgid "Link"
msgstr "リンク"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "IATA空港コード"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "ICAO空港コード"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (空港) コード"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name}が存在しません:{city_uuid}が存在しません。"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name}が存在しません:{postal_code_uuid}が存在しません。"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name}が存在しません:{uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "ユニークID"
#: geo/models.py:48
msgid "active"
msgstr "アクティブ"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Place` のサブクラスは slugify メソッドを実装しなければならない。"
#: geo/models.py:123
msgid "continent"
msgstr "大陸"
#: geo/models.py:124
msgid "continents"
msgstr "大陸"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "国名"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "国名"
#: geo/models.py:182
msgid "region"
msgstr "地域"
#: geo/models.py:183
msgid "regions"
msgstr "地域"
#: geo/models.py:205
msgid "subregion"
msgstr "サブリージョン"
#: geo/models.py:206
msgid "subregions"
msgstr "サブリージョン"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "都市"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "都市"
#: geo/models.py:270
msgid "district"
msgstr "地区"
#: geo/models.py:271
msgid "districts"
msgstr "地区"
#: geo/models.py:344
msgid "postal code"
msgstr "郵便番号"
#: geo/models.py:345
msgid "postal codes"
msgstr "郵便番号"
#: geo/models.py:391
msgid "address"
msgstr "住所"
#: geo/models.py:392
msgid "addresses"
msgstr "住所"

View file

@ -1,137 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: geo/apps.py:8
msgid "geodata"
msgstr ""
#: geo/conf.py:407
msgid "Name"
msgstr ""
#: geo/conf.py:408
msgid "Abbreviation"
msgstr ""
#: geo/conf.py:409
msgid "Link"
msgstr ""
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr ""
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr ""
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr ""
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr ""
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr ""
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr ""
#: geo/models.py:47
msgid "UUID"
msgstr ""
#: geo/models.py:48
msgid "active"
msgstr ""
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr ""
#: geo/models.py:123
msgid "continent"
msgstr ""
#: geo/models.py:124
msgid "continents"
msgstr ""
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr ""
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr ""
#: geo/models.py:182
msgid "region"
msgstr ""
#: geo/models.py:183
msgid "regions"
msgstr ""
#: geo/models.py:205
msgid "subregion"
msgstr ""
#: geo/models.py:206
msgid "subregions"
msgstr ""
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr ""
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr ""
#: geo/models.py:270
msgid "district"
msgstr ""
#: geo/models.py:271
msgid "districts"
msgstr ""
#: geo/models.py:344
msgid "postal code"
msgstr ""
#: geo/models.py:345
msgid "postal codes"
msgstr ""
#: geo/models.py:391
msgid "address"
msgstr ""
#: geo/models.py:392
msgid "addresses"
msgstr ""

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: DUTCH <CONTACT@FUREUNOIR.COM>\n"
"Language: DUTCH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodata"
#: geo/conf.py:407
msgid "Name"
msgstr "Naam"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Afkorting"
#: geo/conf.py:409
msgid "Link"
msgstr "Koppeling naar"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "IATA (Luchthaven) Code"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "ICAO (Luchthaven) Code"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (Luchthaven) Code"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} bestaat niet: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} bestaat niet: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} bestaat niet: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Uniek ID"
#: geo/models.py:48
msgid "active"
msgstr "Actief"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Subklassen van `Place` moeten de methode slugify implementeren"
#: geo/models.py:123
msgid "continent"
msgstr "Continent"
#: geo/models.py:124
msgid "continents"
msgstr "Continenten"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Land"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Landen"
#: geo/models.py:182
msgid "region"
msgstr "Regio"
#: geo/models.py:183
msgid "regions"
msgstr "Regio's"
#: geo/models.py:205
msgid "subregion"
msgstr "Subregio"
#: geo/models.py:206
msgid "subregions"
msgstr "Subregio's"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Stad"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Steden"
#: geo/models.py:270
msgid "district"
msgstr "District"
#: geo/models.py:271
msgid "districts"
msgstr "Wijken"
#: geo/models.py:344
msgid "postal code"
msgstr "Postcode"
#: geo/models.py:345
msgid "postal codes"
msgstr "Postcodes"
#: geo/models.py:391
msgid "address"
msgstr "Adres"
#: geo/models.py:392
msgid "addresses"
msgstr "Adressen"

View file

@ -1,133 +0,0 @@
#
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: pl-PL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodane"
#: geo/conf.py:407
msgid "Name"
msgstr "Nazwa"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Skrót"
#: geo/conf.py:409
msgid "Link"
msgstr "Link do"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "Kod IATA (lotniska)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "Kod ICAO (lotniska)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "Kod FAAC (lotnisko)"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} nie istnieje: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} nie istnieje: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} nie istnieje: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Unikalny identyfikator"
#: geo/models.py:48
msgid "active"
msgstr "Aktywny"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Podklasy `Place` muszą implementować metodę slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Kontynent"
#: geo/models.py:124
msgid "continents"
msgstr "Kontynenty"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Kraj"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Kraje"
#: geo/models.py:182
msgid "region"
msgstr "Region"
#: geo/models.py:183
msgid "regions"
msgstr "Regiony"
#: geo/models.py:205
msgid "subregion"
msgstr "Podregion"
#: geo/models.py:206
msgid "subregions"
msgstr "Podregiony"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Miasto"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Miasta"
#: geo/models.py:270
msgid "district"
msgstr "Okręg"
#: geo/models.py:271
msgid "districts"
msgstr "Okręgi"
#: geo/models.py:344
msgid "postal code"
msgstr "Kod pocztowy"
#: geo/models.py:345
msgid "postal codes"
msgstr "Kody pocztowe"
#: geo/models.py:391
msgid "address"
msgstr "Adres"
#: geo/models.py:392
msgid "addresses"
msgstr "Adresy"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodados"
#: geo/conf.py:407
msgid "Name"
msgstr "Nome"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abreviação"
#: geo/conf.py:409
msgid "Link"
msgstr "Link para"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "Código IATA (aeroporto)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "Código ICAO (aeroporto)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "Código FAAC (Aeroporto)"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} não existe: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} não existe: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} não existe: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "ID exclusivo"
#: geo/models.py:48
msgid "active"
msgstr "Ativo"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "As subclasses de `Place` devem implementar o método slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Continente"
#: geo/models.py:124
msgid "continents"
msgstr "Continentes"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "País"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Países"
#: geo/models.py:182
msgid "region"
msgstr "Região"
#: geo/models.py:183
msgid "regions"
msgstr "Regiões"
#: geo/models.py:205
msgid "subregion"
msgstr "Sub-região"
#: geo/models.py:206
msgid "subregions"
msgstr "Sub-regiões"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Cidade"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Cidades"
#: geo/models.py:270
msgid "district"
msgstr "Distrito"
#: geo/models.py:271
msgid "districts"
msgstr "Distritos"
#: geo/models.py:344
msgid "postal code"
msgstr "Código postal"
#: geo/models.py:345
msgid "postal codes"
msgstr "Códigos postais"
#: geo/models.py:391
msgid "address"
msgstr "Endereço"
#: geo/models.py:392
msgid "addresses"
msgstr "Endereços"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Geodata"
#: geo/conf.py:407
msgid "Name"
msgstr "Nume și prenume"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Abreviere"
#: geo/conf.py:409
msgid "Link"
msgstr "Link către"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "Cod IATA (Aeroport)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "Cod OACI (Aeroport)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (Aeroport) Cod"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} nu există: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} nu există: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} nu există: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "ID unic"
#: geo/models.py:48
msgid "active"
msgstr "Activ"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Subclasele lui `Place` trebuie să implementeze metoda slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Continent"
#: geo/models.py:124
msgid "continents"
msgstr "Continente"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Țara"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Țări"
#: geo/models.py:182
msgid "region"
msgstr "Regiunea"
#: geo/models.py:183
msgid "regions"
msgstr "Regiuni"
#: geo/models.py:205
msgid "subregion"
msgstr "Subregiune"
#: geo/models.py:206
msgid "subregions"
msgstr "Subregiuni"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Oraș"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Orașe"
#: geo/models.py:270
msgid "district"
msgstr "Districtul"
#: geo/models.py:271
msgid "districts"
msgstr "Districte"
#: geo/models.py:344
msgid "postal code"
msgstr "Cod poștal"
#: geo/models.py:345
msgid "postal codes"
msgstr "Coduri poștale"
#: geo/models.py:391
msgid "address"
msgstr "Adresă"
#: geo/models.py:392
msgid "addresses"
msgstr "Adrese"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: BRITISH ENGLISH <CONTACT@FUREUNOIR.COM>\n"
"Language: BRITISH ENGLISH\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "Геоданные"
#: geo/conf.py:407
msgid "Name"
msgstr "Имя"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "Аббревиатура"
#: geo/conf.py:409
msgid "Link"
msgstr "Ссылка на"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "Код IATA (аэропорта)"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "Код ИКАО (аэропорта)"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "Код FAAC (аэропорт)"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} не существует: {city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} не существует: {postal_code_uuid}"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} не существует: {uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "Уникальный идентификатор"
#: geo/models.py:48
msgid "active"
msgstr "Активный"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Подклассы `Place` должны реализовывать метод slugify"
#: geo/models.py:123
msgid "continent"
msgstr "Континент"
#: geo/models.py:124
msgid "continents"
msgstr "Континенты"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "Страна"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "Страны"
#: geo/models.py:182
msgid "region"
msgstr "Регион"
#: geo/models.py:183
msgid "regions"
msgstr "Регионы"
#: geo/models.py:205
msgid "subregion"
msgstr "Субрегион"
#: geo/models.py:206
msgid "subregions"
msgstr "Субрегионы"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "Город"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "Города"
#: geo/models.py:270
msgid "district"
msgstr "Округ"
#: geo/models.py:271
msgid "districts"
msgstr "Районы"
#: geo/models.py:344
msgid "postal code"
msgstr "Почтовый индекс"
#: geo/models.py:345
msgid "postal codes"
msgstr "Почтовые индексы"
#: geo/models.py:391
msgid "address"
msgstr "Адрес"
#: geo/models.py:392
msgid "addresses"
msgstr "Адреса"

View file

@ -1,138 +0,0 @@
# eVibes Translations.
# Copyright (C) 2025 Egor "fureunoir" Gorbunov
# This file is distributed under the same license as the eVibes package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-28 16:26+0100\n"
"PO-Revision-Date: 2025-01-30 03:27+0000\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: CHINESE SIMPLIFIED <CONTACT@FUREUNOIR.COM>\n"
"Language: CHINESE SIMPLIFIED\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geo/apps.py:8
msgid "geodata"
msgstr "地理数据"
#: geo/conf.py:407
msgid "Name"
msgstr "名称"
#: geo/conf.py:408
msgid "Abbreviation"
msgstr "缩写"
#: geo/conf.py:409
msgid "Link"
msgstr "链接到"
#: geo/conf.py:413
msgid "IATA (Airport) Code"
msgstr "空运协会(机场)代码"
#: geo/conf.py:414
msgid "ICAO (Airport) Code"
msgstr "国际民航组织(机场)代码"
#: geo/conf.py:415
msgid "FAAC (Airport) Code"
msgstr "FAAC (机场)代码"
#: geo/graphene/mutations.py:30
#, python-brace-format
msgid "{name} does not exist: {city_uuid}"
msgstr "{name} 不存在:{city_uuid}"
#: geo/graphene/mutations.py:33
#, python-brace-format
msgid "{name} does not exist: {postal_code_uuid}"
msgstr "{name} 不存在:{postal_code_uuid}不存在"
#: geo/graphene/mutations.py:92 geo/graphene/mutations.py:117
#, python-brace-format
msgid "{name} does not exist: {uuid}"
msgstr "{name} 不存在:{uuid}"
#: geo/models.py:47
msgid "UUID"
msgstr "唯一 ID"
#: geo/models.py:48
msgid "active"
msgstr "活跃"
#: geo/models.py:54
msgid "subclasses_of_place_must_implement_slugify"
msgstr "Place \"的子类必须实现 slugify 方法"
#: geo/models.py:123
msgid "continent"
msgstr "大陆"
#: geo/models.py:124
msgid "continents"
msgstr "各大洲"
#: geo/models.py:149 geo/models.py:169
msgid "country"
msgstr "国家"
#: geo/models.py:150 geo/models.py:170
msgid "countries"
msgstr "国家"
#: geo/models.py:182
msgid "region"
msgstr "地区"
#: geo/models.py:183
msgid "regions"
msgstr "地区"
#: geo/models.py:205
msgid "subregion"
msgstr "次区域"
#: geo/models.py:206
msgid "subregions"
msgstr "次区域"
#: geo/models.py:237 geo/models.py:253
msgid "city"
msgstr "城市"
#: geo/models.py:238 geo/models.py:254
msgid "cities"
msgstr "城市"
#: geo/models.py:270
msgid "district"
msgstr "地区"
#: geo/models.py:271
msgid "districts"
msgstr "地区"
#: geo/models.py:344
msgid "postal code"
msgstr "邮政编码"
#: geo/models.py:345
msgid "postal codes"
msgstr "邮政编码"
#: geo/models.py:391
msgid "address"
msgstr "地址"
#: geo/models.py:392
msgid "addresses"
msgstr "地址"

View file

@ -1,61 +0,0 @@
import requests
from constance import config
from django.contrib.gis.geos import Point
from django.db import models
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
)

View file

@ -1,31 +0,0 @@
# 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),
),
]

View file

@ -1,56 +0,0 @@
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 core.abstract import NiceModel
from geo.managers import AddressManager
class Address(NiceModel):
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
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

View file

@ -1,65 +0,0 @@
from rest_framework import serializers
from geo.models import Address
class AddressAutocompleteInputSerializer(serializers.Serializer):
q = serializers.CharField(
required=True
)
limit = serializers.IntegerField(
required=False,
min_value=1,
max_value=10,
default=5
)
class AddressSuggestionSerializer(serializers.Serializer):
display_name = serializers.CharField()
lat = serializers.FloatField()
lon = serializers.FloatField()
address = serializers.DictField(child=serializers.CharField())
class AddressSerializer(serializers.ModelSerializer):
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",
"district",
"city",
"region",
"postal_code",
"country",
"latitude",
"longitude",
"raw_data",
"api_response",
"user",
]
read_only_fields = [
"latitude",
"longitude",
"raw_data",
"api_response",
]
class AddressCreateSerializer(serializers.ModelSerializer):
raw_data = serializers.CharField(
write_only=True,
max_length=512,
)
class Meta:
model = Address
fields = ["raw_data", "user"]
def create(self, validated_data):
raw = validated_data.pop("raw_data")
return Address.objects.create(raw_data=raw, **validated_data)

View file

@ -1,3 +0,0 @@
from django.test import TestCase # noqa: F401
# Create your tests here.

View file

@ -1,11 +0,0 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from geo.viewsets import AddressViewSet
geo_router = DefaultRouter()
geo_router.register(r"addresses", AddressViewSet, basename="addresses")
urlpatterns = [
path(r"", include(geo_router.urls)),
]

View file

@ -1,32 +0,0 @@
from django.contrib.gis.db.models import PointField
from django.contrib.gis.geos import Point
from graphene.types.scalars import Scalar
from graphene_django.converter import convert_django_field
from graphql.language import ast
class PointScalar(Scalar):
"""Custom scalar for GeoDjango PointField"""
@staticmethod
def serialize(point):
if not isinstance(point, Point):
raise Exception("Expected a Point instance")
return {"x": point.x, "y": point.y}
@staticmethod
def parse_literal(node):
if isinstance(node, ast.ObjectValue):
return Point(x=node.value["x"], y=node.value["y"])
return None
@staticmethod
def parse_value(value):
if isinstance(value, dict):
return Point(x=value["x"], y=value["y"])
return None
@convert_django_field.register(PointField)
def convert_point_field_to_custom_type(field, registry=None):
return PointScalar(description=field.help_text, required=not field.null)

View file

View file

@ -1,44 +0,0 @@
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.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
@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"]
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,
)
return Response(suggestions, status=status.HTTP_200_OK)

View file

@ -9,7 +9,7 @@ from core.abstract import NiceModel
class Balance(NiceModel):
amount = FloatField(null=False, blank=False, default=0)
user = OneToOneField(
to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="payments_balance"
to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True, related_name="payments_balance"
)
def __str__(self):