1 |
1 |
import bz2
|
|
2 |
import datetime
|
2 |
3 |
import unicodedata
|
3 |
4 |
|
|
5 |
from jsonfield import JSONField
|
4 |
6 |
from requests import RequestException
|
5 |
7 |
|
6 |
8 |
from django.db import connection, models
|
... | ... | |
49 |
51 |
class Meta:
|
50 |
52 |
verbose_name = _('Base Adresse Web Service')
|
51 |
53 |
|
|
54 |
@staticmethod
|
|
55 |
def format_address_data(data):
|
|
56 |
result = {}
|
|
57 |
result['lon'] = str(data['geometry']['coordinates'][0])
|
|
58 |
result['lat'] = str(data['geometry']['coordinates'][1])
|
|
59 |
result['address'] = {'country': 'France'}
|
|
60 |
for prop, value in data['properties'].items():
|
|
61 |
if prop in ('city', 'postcode', 'citycode'):
|
|
62 |
result['address'][prop] = value
|
|
63 |
elif prop == 'housenumber':
|
|
64 |
result['address']['house_number'] = value
|
|
65 |
elif prop == 'label':
|
|
66 |
result['text'] = result['display_name'] = value
|
|
67 |
elif prop == 'name':
|
|
68 |
house_number = data['properties'].get('housenumber')
|
|
69 |
if house_number and value.startswith(house_number):
|
|
70 |
value = value[len(house_number):].strip()
|
|
71 |
result['address']['road'] = value
|
|
72 |
elif prop == 'id':
|
|
73 |
result['id'] = value
|
|
74 |
return result
|
|
75 |
|
52 |
76 |
@endpoint(pattern='(?P<q>.+)?$',
|
53 |
77 |
description=_('Geocoding'),
|
54 |
78 |
parameters={
|
55 |
|
'q': {'description': _('Address'), 'example_value': '169 rue du chateau, paris'}
|
|
79 |
'id': {'description': _('Address identifier')},
|
|
80 |
'q': {'description': _('Address'), 'example_value': '169 rue du chateau, paris'},
|
|
81 |
'page_limit': {'description': _('Maximum number of results to return. Must be '
|
|
82 |
'lower than 20.')},
|
|
83 |
'zipcode': {'description': _('Zipcode'), 'example_value': '75014'},
|
|
84 |
'lat': {'description': _('Prioritize results according to coordinates. "lat" '
|
|
85 |
'parameter must be present.')},
|
|
86 |
'lon': {'description': _('Prioritize results according to coordinates. "lon" '
|
|
87 |
'parameter must be present.')},
|
56 |
88 |
})
|
57 |
|
def search(self, request, q, zipcode='', lat=None, lon=None, **kwargs):
|
|
89 |
def addresses(self, request, id=None, q=None, zipcode='', lat=None, lon=None,
|
|
90 |
page_limit=5, **kwargs):
|
58 |
91 |
if kwargs.get('format', 'json') != 'json':
|
59 |
92 |
raise NotImplementedError()
|
|
93 |
if id is not None:
|
|
94 |
try:
|
|
95 |
address = AddressCacheModel.objects.get(api_id=id)
|
|
96 |
except AddressCacheModel.DoesNotExist:
|
|
97 |
return {'err': _('Address ID not found')}
|
|
98 |
address.update_timestamp()
|
|
99 |
return {'data': [address.data]}
|
60 |
100 |
|
61 |
101 |
if not q:
|
62 |
|
return []
|
|
102 |
return {'data': []}
|
|
103 |
|
|
104 |
if page_limit > 20:
|
|
105 |
page_limit = 20
|
63 |
106 |
|
64 |
107 |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.service_url)
|
65 |
108 |
path = urlparse.urljoin(path, 'search/')
|
66 |
|
query_args = {'q': q, 'limit': 1}
|
|
109 |
query_args = {'q': q, 'limit': page_limit}
|
67 |
110 |
if zipcode:
|
68 |
111 |
query_args['postcode'] = zipcode
|
69 |
112 |
if lat and lon:
|
... | ... | |
78 |
121 |
for feature in result_response.json().get('features'):
|
79 |
122 |
if not feature['geometry']['type'] == 'Point':
|
80 |
123 |
continue # skip unknown
|
81 |
|
result.append({
|
82 |
|
'lon': str(feature['geometry']['coordinates'][0]),
|
83 |
|
'lat': str(feature['geometry']['coordinates'][1]),
|
84 |
|
'display_name': feature['properties']['label'],
|
85 |
|
})
|
86 |
|
break
|
|
124 |
data = self.format_address_data(feature)
|
|
125 |
result.append(data)
|
|
126 |
AddressCacheModel.objects.update_or_create(api_id=data['id'], data=data)
|
87 |
127 |
|
88 |
|
return result
|
|
128 |
return {'data': result}
|
|
129 |
|
|
130 |
@endpoint(pattern='(?P<q>.+)?$', description=_('Deprecated, use /addresses/'),)
|
|
131 |
def search(self, request, q, zipcode='', lat=None, lon=None, **kwargs):
|
|
132 |
result = self.addresses(request, q=q, zipcode=zipcode, lat=lat, lon=lon, page_limit=1, **kwargs)
|
|
133 |
return result['data']
|
89 |
134 |
|
90 |
135 |
@endpoint(description=_('Reverse geocoding'),
|
91 |
136 |
parameters={
|
... | ... | |
107 |
152 |
for feature in result_response.json().get('features'):
|
108 |
153 |
if not feature['geometry']['type'] == 'Point':
|
109 |
154 |
continue # skip unknown
|
110 |
|
result = {}
|
111 |
|
result['lon'] = str(feature['geometry']['coordinates'][0])
|
112 |
|
result['lat'] = str(feature['geometry']['coordinates'][1])
|
113 |
|
result['address'] = {'country': 'France'}
|
114 |
|
for prop in feature['properties']:
|
115 |
|
if prop in ('city', 'postcode', 'citycode'):
|
116 |
|
result['address'][prop] = feature['properties'][prop]
|
117 |
|
elif prop == 'housenumber':
|
118 |
|
result['address']['house_number'] = feature['properties'][prop]
|
119 |
|
elif prop == 'label':
|
120 |
|
result['display_name'] = feature['properties'][prop]
|
121 |
|
elif prop == 'name':
|
122 |
|
house_number = feature['properties'].get('housenumber')
|
123 |
|
value = feature['properties'][prop]
|
124 |
|
if house_number and value.startswith(house_number):
|
125 |
|
value = value[len(house_number):].strip()
|
126 |
|
result['address']['road'] = value
|
|
155 |
result = self.format_address_data(feature)
|
127 |
156 |
return result
|
128 |
157 |
|
129 |
158 |
@endpoint(description=_('Streets from zipcode'),
|
... | ... | |
368 |
397 |
code=data['code'], zipcode=zipcode, defaults=defaults)
|
369 |
398 |
CityModel.objects.filter(last_update__lt=start_update).delete()
|
370 |
399 |
|
|
400 |
def clean_addresses_cache(self):
|
|
401 |
old_addresses = AddressCacheModel.objects.filter(
|
|
402 |
timestamp__lt=timezone.now() - datetime.timedelta(hours=1)
|
|
403 |
)
|
|
404 |
old_addresses.delete()
|
|
405 |
|
371 |
406 |
def hourly(self):
|
372 |
407 |
super(BaseAdresse, self).hourly()
|
|
408 |
self.clean_addresses_cache()
|
373 |
409 |
# don't wait for daily job to grab data
|
374 |
410 |
if self.get_zipcodes() and not self.get_streets_queryset().exists():
|
375 |
411 |
self.update_streets_data()
|
... | ... | |
489 |
525 |
|
490 |
526 |
def __str__(self):
|
491 |
527 |
return '%s %s' % (self.zipcode, self.name)
|
|
528 |
|
|
529 |
|
|
530 |
class AddressCacheModel(models.Model):
|
|
531 |
api_id = models.CharField(max_length=30, unique=True)
|
|
532 |
data = JSONField()
|
|
533 |
timestamp = models.DateTimeField(auto_now=True)
|
|
534 |
|
|
535 |
def update_timestamp(self):
|
|
536 |
self.save()
|
492 |
|
-
|