Нет описания

geo.py 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from __future__ import annotations
  2. import json
  3. import os
  4. from functools import lru_cache
  5. from typing import List, Tuple, Dict, Optional
  6. from django.conf import settings
  7. def _json_path(filename: str) -> str:
  8. return os.path.join(settings.BASE_DIR, 'static', 'json', filename)
  9. @lru_cache(maxsize=1)
  10. def load_countries() -> List[dict]:
  11. with open(_json_path('countries.json'), 'r', encoding='utf-8') as f:
  12. return json.load(f)
  13. @lru_cache(maxsize=1)
  14. def load_states() -> List[dict]:
  15. with open(_json_path('states.json'), 'r', encoding='utf-8') as f:
  16. return json.load(f)
  17. @lru_cache(maxsize=1)
  18. def load_cities() -> List[dict]:
  19. with open(_json_path('cities.json'), 'r', encoding='utf-8') as f:
  20. return json.load(f)
  21. @lru_cache(maxsize=1)
  22. def _index_states_by_country() -> Dict[str, List[Tuple[str, str]]]:
  23. idx: Dict[str, List[Tuple[str, str]]] = {}
  24. for s in load_states():
  25. cc = s.get('country_code')
  26. code = s.get('iso2') or s.get('state_code')
  27. name = s.get('name')
  28. if not (cc and code and name):
  29. continue
  30. idx.setdefault(cc, []).append((str(code), name))
  31. return idx
  32. @lru_cache(maxsize=1)
  33. def _index_cities_by_country_state() -> Dict[Tuple[str, str], List[Tuple[str, str]]]:
  34. idx: Dict[Tuple[str, str], List[Tuple[str, str]]] = {}
  35. for c in load_cities():
  36. cc = c.get('country_code')
  37. sc = (c.get('state_code') or '').upper()
  38. name = c.get('name')
  39. if not (cc and sc and name):
  40. continue
  41. key = (cc, sc)
  42. idx.setdefault(key, []).append((name, name))
  43. return idx
  44. def country_label(code: str) -> Optional[str]:
  45. code = (code or '').upper()
  46. for c in load_countries():
  47. if c.get('iso2') == code:
  48. return c.get('name')
  49. return None
  50. def state_label(country_code: str, state_code: str) -> Optional[str]:
  51. cc = (country_code or '').upper()
  52. sc = (state_code or '').upper()
  53. for s in load_states():
  54. if s.get('country_code') == cc and s.get('iso2', '').upper() == sc:
  55. return s.get('name')
  56. if s.get('country_code') == cc and s.get('state_code', '').upper() == sc:
  57. return s.get('name')
  58. return None
  59. def list_countries(q: str | None = None) -> List[Tuple[str, str]]:
  60. items: List[Tuple[str, str]] = []
  61. ql = (q or '').strip().lower()
  62. for c in load_countries():
  63. code = c.get('iso2')
  64. name = c.get('name')
  65. if not code or not name:
  66. continue
  67. if ql and ql not in name.lower() and ql not in code.lower():
  68. continue
  69. items.append((code, name))
  70. return items
  71. def list_states(country_code: str | None, q: str | None = None) -> List[Tuple[str, str]]:
  72. items: List[Tuple[str, str]] = []
  73. if not country_code:
  74. return items
  75. cc = country_code.upper()
  76. ql = (q or '').strip().lower()
  77. pool = _index_states_by_country().get(cc, [])
  78. if not ql:
  79. return list(pool)
  80. for code, name in pool:
  81. if ql and ql not in name.lower() and ql not in (code or '').lower():
  82. continue
  83. items.append((code, name))
  84. return items
  85. def list_cities(country_code: str | None, state_code: str | None, q: str | None = None) -> List[Tuple[str, str]]:
  86. items: List[Tuple[str, str]] = []
  87. if not (country_code and state_code):
  88. return items
  89. cc = country_code.upper()
  90. sc = state_code.upper()
  91. ql = (q or '').strip().lower()
  92. pool = _index_cities_by_country_state().get((cc, sc), [])
  93. if not ql:
  94. # Return first 50 to avoid huge payloads without query
  95. return pool[:50]
  96. for code, name in pool:
  97. if ql in name.lower():
  98. items.append((code, name))
  99. return items