Skip to content

Commit

Permalink
Merge pull request #748 from chris48s/issue744
Browse files Browse the repository at this point in the history
Sort addresses in API outputs
  • Loading branch information
symroe committed May 1, 2017
2 parents a58e64e + 50785dd commit 43a4955
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 75 deletions.
4 changes: 3 additions & 1 deletion polling_stations/apps/api/postcode.py
Expand Up @@ -7,6 +7,7 @@
from councils.models import Council
from data_finder.views import LogLookUpMixin
from data_finder.helpers import (
AddressSorter,
geocode,
PostcodeError,
RateLimitError,
Expand Down Expand Up @@ -34,7 +35,8 @@ def get_object(self, **kwargs):

def generate_addresses(self, routing_helper):
if routing_helper.route_type == "multiple_addresses":
return [address for address in routing_helper.addresses]
sorter = AddressSorter(routing_helper.addresses)
return sorter.natural_sort()
return []

def generate_polling_station(self, routing_helper, council, location):
Expand Down
18 changes: 12 additions & 6 deletions polling_stations/apps/data_finder/helpers.py
Expand Up @@ -5,6 +5,7 @@
import requests
import time
from collections import namedtuple
from operator import itemgetter

from django.conf import settings
from django.contrib.gis.geos import Point
Expand Down Expand Up @@ -255,10 +256,12 @@ def geocode(postcode):


class AddressSorter:
# Class for sorting sort a list of tuples
# containing addresses (defined by key function)
# Class for sorting sort a list of address objects
# in a human-readable order.

def __init__(self, addresses):
self.addresses = addresses

def convert(self, text):
# if text is numeric, covert to an int
# this allows us to sort numbers in int order, not string order
Expand All @@ -267,7 +270,7 @@ def convert(self, text):
def alphanum_key(self, tup):
# split the desired component of tup (defined by key function)
# into a listof numeric and text components
return [ self.convert(c) for c in filter(None, re.split('([0-9]+)', self.key(tup))) ]
return [ self.convert(c) for c in filter(None, re.split('([0-9]+)', tup[1])) ]

def swap_fields(self, item):
lst = self.alphanum_key(item)
Expand All @@ -281,9 +284,12 @@ def swap_fields(self, item):
lst[0] = str(lst[0])
return lst

def natural_sort(self, lst, key):
self.key = key
return sorted(lst, key = self.swap_fields)
def natural_sort(self):
sorted_list = sorted(
[(address, address.address) for address in self.addresses],
key=self.swap_fields
)
return [address[0] for address in sorted_list]


class EveryElectionWrapper:
Expand Down
124 changes: 64 additions & 60 deletions polling_stations/apps/data_finder/tests/test_address_sorter.py
@@ -1,120 +1,124 @@
from operator import itemgetter
from collections import namedtuple
from django.test import TestCase
from data_finder.helpers import AddressSorter
from pollingstations.models import ResidentialAddress

class AddressSorterTest(TestCase):
Address = namedtuple('Address', ['id', 'address'])

def setUp(self):
self.sorter = AddressSorter()
self.key = itemgetter(1)
class AddressSorterTest(TestCase):

def test_numeric_order(self):
# Addresses should sort in numeric order, not string order
in_list = [
(1, "10, THE SQUARE, BOGNOR REGIS"),
(2, "1, THE SQUARE, BOGNOR REGIS"),
(3, "2, THE SQUARE, BOGNOR REGIS"),
Address(id=1, address="10, THE SQUARE, BOGNOR REGIS"),
Address(id=2, address="1, THE SQUARE, BOGNOR REGIS"),
Address(id=3, address="2, THE SQUARE, BOGNOR REGIS"),
]

expected = [
(2, "1, THE SQUARE, BOGNOR REGIS"),
(3, "2, THE SQUARE, BOGNOR REGIS"),
(1, "10, THE SQUARE, BOGNOR REGIS"),
Address(id=2, address="1, THE SQUARE, BOGNOR REGIS"),
Address(id=3, address="2, THE SQUARE, BOGNOR REGIS"),
Address(id=1, address="10, THE SQUARE, BOGNOR REGIS"),
]

result = self.sorter.natural_sort(in_list, self.key)
sorter = AddressSorter(in_list)
result = sorter.natural_sort()

self.assertEqual(expected, result)

def test_group_by_street(self):
# Numbered addresses should group by street/building
in_list = [
(1, "1 Haynes House Mount Pleasant"),
(2, "1 Partridge House Mount Pleasant"),
(3, "3 Haynes House Mount Pleasant"),
(4, "2 Partridge House Mount Pleasant"),
(5, "2 Haynes House Mount Pleasant"),
Address(id=1, address="1 Haynes House Mount Pleasant"),
Address(id=2, address="1 Partridge House Mount Pleasant"),
Address(id=3, address="3 Haynes House Mount Pleasant"),
Address(id=4, address="2 Partridge House Mount Pleasant"),
Address(id=5, address="2 Haynes House Mount Pleasant"),
]

expected = [
(1, "1 Haynes House Mount Pleasant"),
(5, "2 Haynes House Mount Pleasant"),
(3, "3 Haynes House Mount Pleasant"),
(2, "1 Partridge House Mount Pleasant"),
(4, "2 Partridge House Mount Pleasant"),
Address(id=1, address="1 Haynes House Mount Pleasant"),
Address(id=5, address="2 Haynes House Mount Pleasant"),
Address(id=3, address="3 Haynes House Mount Pleasant"),
Address(id=2, address="1 Partridge House Mount Pleasant"),
Address(id=4, address="2 Partridge House Mount Pleasant"),
]

result = self.sorter.natural_sort(in_list, self.key)
sorter = AddressSorter(in_list)
result = sorter.natural_sort()

self.assertEqual(expected, result)

def test_group_in_numbered_buildings(self):
# Numbered addresses should group inside numbered buildings
in_list = [
(1, "1 Southlands Court Birchfield Road"),
(2, "1 233 The Beeches Birchfield Road"),
(3, "207 Birchfield Road"),
(4, "203 Birchfield Road"),
(5, "2 233 The Beeches Birchfield Road"),
(6, "2 Southlands Court Birchfield Road"),
Address(id=1, address="1 Southlands Court Birchfield Road"),
Address(id=2, address="1 233 The Beeches Birchfield Road"),
Address(id=3, address="207 Birchfield Road"),
Address(id=4, address="203 Birchfield Road"),
Address(id=5, address="2 233 The Beeches Birchfield Road"),
Address(id=6, address="2 Southlands Court Birchfield Road"),
]

expected = [
(2, "1 233 The Beeches Birchfield Road"),
(5, "2 233 The Beeches Birchfield Road"),
(1, "1 Southlands Court Birchfield Road"),
(6, "2 Southlands Court Birchfield Road"),
(4, "203 Birchfield Road"),
(3, "207 Birchfield Road"),
Address(id=2, address="1 233 The Beeches Birchfield Road"),
Address(id=5, address="2 233 The Beeches Birchfield Road"),
Address(id=1, address="1 Southlands Court Birchfield Road"),
Address(id=6, address="2 Southlands Court Birchfield Road"),
Address(id=4, address="203 Birchfield Road"),
Address(id=3, address="207 Birchfield Road"),
]

result = self.sorter.natural_sort(in_list, self.key)
sorter = AddressSorter(in_list)
result = sorter.natural_sort()

self.assertEqual(expected, result)

def test_number_suffix(self):
# Numbered addresses with number suffix should sort in
# numeric order and then alphabetically by suffix
in_list = [
(1, "200A Evesham Road"),
(2, "190A Evesham Road"),
(3, "The Forge Mill Evesham Road"),
(4, "202A Evesham Road"),
(5, "190C Evesham Road"),
(6, "190B Evesham Road"),
Address(id=1, address="200A Evesham Road"),
Address(id=2, address="190A Evesham Road"),
Address(id=3, address="The Forge Mill Evesham Road"),
Address(id=4, address="202A Evesham Road"),
Address(id=5, address="190C Evesham Road"),
Address(id=6, address="190B Evesham Road"),
]

expected = [
(2, "190A Evesham Road"),
(6, "190B Evesham Road"),
(5, "190C Evesham Road"),
(1, "200A Evesham Road"),
(4, "202A Evesham Road"),
(3, "The Forge Mill Evesham Road"),
Address(id=2, address="190A Evesham Road"),
Address(id=6, address="190B Evesham Road"),
Address(id=5, address="190C Evesham Road"),
Address(id=1, address="200A Evesham Road"),
Address(id=4, address="202A Evesham Road"),
Address(id=3, address="The Forge Mill Evesham Road"),
]

result = self.sorter.natural_sort(in_list, self.key)
sorter = AddressSorter(in_list)
result = sorter.natural_sort()

self.assertEqual(expected, result)

def test_prefixed_numbers(self):
# Prefixed numbers (e.g: flats) should sort by number
in_list = [
(1, "Flat 10 Knapton House North Walsham Road"),
(2, "Gardeners Cottage Knapton House North Walsham Road"),
(3, "Old Coach House North Walsham Road"),
(4, "Flat 1 Knapton House North Walsham Road"),
(5, "Flat 2 Knapton House North Walsham Road"),
Address(id=1, address="Flat 10 Knapton House North Walsham Road"),
Address(id=2, address="Gardeners Cottage Knapton House North Walsham Road"),
Address(id=3, address="Old Coach House North Walsham Road"),
Address(id=4, address="Flat 1 Knapton House North Walsham Road"),
Address(id=5, address="Flat 2 Knapton House North Walsham Road"),
]

expected = [
(4, "Flat 1 Knapton House North Walsham Road"),
(5, "Flat 2 Knapton House North Walsham Road"),
(1, "Flat 10 Knapton House North Walsham Road"),
(2, "Gardeners Cottage Knapton House North Walsham Road"),
(3, "Old Coach House North Walsham Road"),
Address(id=4, address="Flat 1 Knapton House North Walsham Road"),
Address(id=5, address="Flat 2 Knapton House North Walsham Road"),
Address(id=1, address="Flat 10 Knapton House North Walsham Road"),
Address(id=2, address="Gardeners Cottage Knapton House North Walsham Road"),
Address(id=3, address="Old Coach House North Walsham Road"),
]

result = self.sorter.natural_sort(in_list, self.key)
sorter = AddressSorter(in_list)
result = sorter.natural_sort()

self.assertEqual(expected, result)
13 changes: 5 additions & 8 deletions polling_stations/apps/data_finder/views.py
Expand Up @@ -3,8 +3,6 @@
import re
import requests

from operator import itemgetter

from django.conf import settings
from django.contrib.gis.geos import Point
from django.core.urlresolvers import reverse
Expand Down Expand Up @@ -377,14 +375,13 @@ def get_form(self, form_class):
postcode=self.kwargs['postcode']
)

select_addresses = [(element.slug, element.address) for element in addresses]
sorter = AddressSorter()
select_addresses = sorter.natural_sort(select_addresses, itemgetter(1))

if not addresses:
raise Http404
else:
return form_class(select_addresses, self.kwargs['postcode'], **self.get_form_kwargs())

sorter = AddressSorter(addresses)
addresses = sorter.natural_sort()
select_addresses = [(element.slug, element.address) for element in addresses]
return form_class(select_addresses, self.kwargs['postcode'], **self.get_form_kwargs())

def form_valid(self, form):
self.success_url = reverse(
Expand Down

0 comments on commit 43a4955

Please sign in to comment.