passerelle/passerelle/address/models.py

130 lines
4.6 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2021 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from urllib.parse import urljoin
from django.conf import settings
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from passerelle.apps.sector.models import (
MAX_HOUSENUMBER,
PARITY_ALL,
PARITY_EVEN,
PARITY_ODD,
Sectorization,
SectorResource,
)
from passerelle.base.models import BaseResource
from passerelle.utils.api import endpoint
class AddressResource(BaseResource):
sectors = models.ManyToManyField(
SectorResource, blank=True, related_name='resources', verbose_name=_('Sectorizations')
)
category = _('Geographic information system')
class Meta:
abstract = True
@classmethod
def get_manager_form_class(cls, **kwargs):
if not SectorResource.objects.exists():
kwargs['exclude'] = tuple(kwargs.get('exclude') or ()) + ('sectors',)
return super().get_manager_form_class(**kwargs)
def export_json(self):
d = super().export_json()
d['sectors'] = [sector.slug for sector in self.sectors.all()]
return d
@classmethod
def import_json_real(cls, overwrite, instance, d, **kwargs):
sectors = d.pop('sectors', [])
instance = super().import_json_real(overwrite, instance, d, **kwargs)
if instance and overwrite:
instance.sectors.clear()
for slug in sectors:
sector = SectorResource.objects.filter(slug=slug).first()
if sector:
instance.sectors.add(sector)
return instance
def sectorize(self, address):
"""
add 'sectors' entry in address.
address is a Nominatim formatted dict and should contain 'street_id' and
'house_number' in its 'address' dict.
"""
if not self.sectors.exists():
return
address['sectors'] = {}
street_id = address['address'].get('street_id')
if not street_id:
address['sectorization_error'] = 'missing street_id in address'
return
try:
house_number = int(address['address'].get('house_number'))
except (TypeError, ValueError):
house_number = None
query = Sectorization.objects.filter(sector__resource__in=self.sectors.all(), street_id=street_id)
if house_number is not None:
query = query.filter(min_housenumber__lte=house_number, max_housenumber__gte=house_number)
parity = PARITY_ODD if house_number % 2 else PARITY_EVEN
query = query.filter(Q(parity=PARITY_ALL) | Q(parity=parity))
else:
query = query.filter(parity=PARITY_ALL, min_housenumber=0, max_housenumber=MAX_HOUSENUMBER)
for sectorization in query.reverse():
address['sectors'][sectorization.sector.resource.slug] = {
'id': sectorization.sector.slug,
'text': sectorization.sector.title,
}
@endpoint(
name='sectors',
description=_('List related Sectorizations'),
perm='OPEN',
parameters={
'id': {'description': _('Sector Identifier (slug)')},
'q': {'description': _('Filter by Sector Title or Identifier')},
},
)
def sectors_(self, request, q=None, id=None):
query = self.sectors.all()
if id is not None:
query = query.filter(slug=id)
elif q is not None:
query = query.filter(Q(slug__icontains=q) | Q(title__icontains=q))
return {
'data': [
{
'id': resource.slug,
'text': resource.title,
'description': resource.description,
'sectors_url': urljoin(
settings.SITE_BASE_URL, '%ssectors/' % resource.get_absolute_url()
),
}
for resource in query
]
}