From fe88f0290024fdffd82d299d905f1ffc8cff52ca Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 30 Jan 2020 10:26:30 +0100 Subject: [PATCH] base_adresse: add /addresses/ endpoint (#39387) Compatible with wcs API. --- .../migrations/0016_addresscachemodel.py | 25 +++++ passerelle/apps/base_adresse/models.py | 101 +++++++++++++----- 2 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 passerelle/apps/base_adresse/migrations/0016_addresscachemodel.py diff --git a/passerelle/apps/base_adresse/migrations/0016_addresscachemodel.py b/passerelle/apps/base_adresse/migrations/0016_addresscachemodel.py new file mode 100644 index 00000000..6cae5a10 --- /dev/null +++ b/passerelle/apps/base_adresse/migrations/0016_addresscachemodel.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2020-01-30 11:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('base_adresse', '0015_auto_20191206_1244'), + ] + + operations = [ + migrations.CreateModel( + name='AddressCacheModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('api_id', models.CharField(max_length=30, unique=True)), + ('data', jsonfield.fields.JSONField(default=dict)), + ('timestamp', models.DateTimeField(auto_now=True)), + ], + ), + ] diff --git a/passerelle/apps/base_adresse/models.py b/passerelle/apps/base_adresse/models.py index 65d70158..6c96634e 100644 --- a/passerelle/apps/base_adresse/models.py +++ b/passerelle/apps/base_adresse/models.py @@ -1,6 +1,8 @@ import bz2 +import datetime import unicodedata +from jsonfield import JSONField from requests import RequestException from django.db import connection, models @@ -49,21 +51,62 @@ class BaseAdresse(BaseResource): class Meta: verbose_name = _('Base Adresse Web Service') + @staticmethod + def format_address_data(data): + result = {} + result['lon'] = str(data['geometry']['coordinates'][0]) + result['lat'] = str(data['geometry']['coordinates'][1]) + result['address'] = {'country': 'France'} + for prop, value in data['properties'].items(): + if prop in ('city', 'postcode', 'citycode'): + result['address'][prop] = value + elif prop == 'housenumber': + result['address']['house_number'] = value + elif prop == 'label': + result['text'] = result['display_name'] = value + elif prop == 'name': + house_number = data['properties'].get('housenumber') + if house_number and value.startswith(house_number): + value = value[len(house_number):].strip() + result['address']['road'] = value + elif prop == 'id': + result['id'] = value + return result + @endpoint(pattern='(?P.+)?$', description=_('Geocoding'), parameters={ - 'q': {'description': _('Address'), 'example_value': '169 rue du chateau, paris'} + 'id': {'description': _('Address identifier')}, + 'q': {'description': _('Address'), 'example_value': '169 rue du chateau, paris'}, + 'page_limit': {'description': _('Maximum number of results to return. Must be ' + 'lower than 20.')}, + 'zipcode': {'description': _('Zipcode'), 'example_value': '75014'}, + 'lat': {'description': _('Prioritize results according to coordinates. "lat" ' + 'parameter must be present.')}, + 'lon': {'description': _('Prioritize results according to coordinates. "lon" ' + 'parameter must be present.')}, }) - def search(self, request, q, zipcode='', lat=None, lon=None, **kwargs): + def addresses(self, request, id=None, q=None, zipcode='', lat=None, lon=None, + page_limit=5, **kwargs): if kwargs.get('format', 'json') != 'json': raise NotImplementedError() + if id is not None: + try: + address = AddressCacheModel.objects.get(api_id=id) + except AddressCacheModel.DoesNotExist: + return {'err': _('Address ID not found')} + address.update_timestamp() + return {'data': [address.data]} if not q: - return [] + return {'data': []} + + if page_limit > 20: + page_limit = 20 scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.service_url) path = urlparse.urljoin(path, 'search/') - query_args = {'q': q, 'limit': 1} + query_args = {'q': q, 'limit': page_limit} if zipcode: query_args['postcode'] = zipcode if lat and lon: @@ -78,14 +121,16 @@ class BaseAdresse(BaseResource): for feature in result_response.json().get('features'): if not feature['geometry']['type'] == 'Point': continue # skip unknown - result.append({ - 'lon': str(feature['geometry']['coordinates'][0]), - 'lat': str(feature['geometry']['coordinates'][1]), - 'display_name': feature['properties']['label'], - }) - break + data = self.format_address_data(feature) + result.append(data) + AddressCacheModel.objects.update_or_create(api_id=data['id'], data=data) - return result + return {'data': result} + + @endpoint(pattern='(?P.+)?$', description=_('Deprecated, use /addresses/'),) + def search(self, request, q, zipcode='', lat=None, lon=None, **kwargs): + result = self.addresses(request, q=q, zipcode=zipcode, lat=lat, lon=lon, page_limit=1, **kwargs) + return result['data'] @endpoint(description=_('Reverse geocoding'), parameters={ @@ -107,23 +152,7 @@ class BaseAdresse(BaseResource): for feature in result_response.json().get('features'): if not feature['geometry']['type'] == 'Point': continue # skip unknown - result = {} - result['lon'] = str(feature['geometry']['coordinates'][0]) - result['lat'] = str(feature['geometry']['coordinates'][1]) - result['address'] = {'country': 'France'} - for prop in feature['properties']: - if prop in ('city', 'postcode', 'citycode'): - result['address'][prop] = feature['properties'][prop] - elif prop == 'housenumber': - result['address']['house_number'] = feature['properties'][prop] - elif prop == 'label': - result['display_name'] = feature['properties'][prop] - elif prop == 'name': - house_number = feature['properties'].get('housenumber') - value = feature['properties'][prop] - if house_number and value.startswith(house_number): - value = value[len(house_number):].strip() - result['address']['road'] = value + result = self.format_address_data(feature) return result @endpoint(description=_('Streets from zipcode'), @@ -368,8 +397,15 @@ class BaseAdresse(BaseResource): code=data['code'], zipcode=zipcode, defaults=defaults) CityModel.objects.filter(last_update__lt=start_update).delete() + def clean_addresses_cache(self): + old_addresses = AddressCacheModel.objects.filter( + timestamp__lt=timezone.now() - datetime.timedelta(hours=1) + ) + old_addresses.delete() + def hourly(self): super(BaseAdresse, self).hourly() + self.clean_addresses_cache() # don't wait for daily job to grab data if self.get_zipcodes() and not self.get_streets_queryset().exists(): self.update_streets_data() @@ -489,3 +525,12 @@ class CityModel(UnaccentNameMixin, models.Model): def __str__(self): return '%s %s' % (self.zipcode, self.name) + + +class AddressCacheModel(models.Model): + api_id = models.CharField(max_length=30, unique=True) + data = JSONField() + timestamp = models.DateTimeField(auto_now=True) + + def update_timestamp(self): + self.save() -- 2.20.1