broadcast-areas: package works with groups

Signed-off-by: Toby Lorne <toby.lornewelch-richards@digital.cabinet-office.gov.uk>
This commit is contained in:
Toby Lorne
2020-07-31 13:21:21 +01:00
parent 58915ac968
commit 8ecbfe5996
3 changed files with 129 additions and 53 deletions

View File

@@ -7,7 +7,7 @@ from notifications_utils.safe_string import make_string_safe_for_id
from .repo import BroadcastAreasRepository from .repo import BroadcastAreasRepository
class IdentifiableMixin: class SortableMixin:
def __repr__(self): def __repr__(self):
return f'{self.__class__.__name__}(<{self.id}>)' return f'{self.__class__.__name__}(<{self.id}>)'
@@ -15,14 +15,7 @@ class IdentifiableMixin:
def __lt__(self, other): def __lt__(self, other):
# Implementing __lt__ means any classes inheriting from this # Implementing __lt__ means any classes inheriting from this
# method are sortable # method are sortable
return self.id < other.id return self.name < other.name
class IdFromNameMixin:
@property
def id(self):
return make_string_safe_for_id(self.name)
class GetItemByIdMixin: class GetItemByIdMixin:
@@ -33,7 +26,7 @@ class GetItemByIdMixin:
raise KeyError(id) raise KeyError(id)
class BroadcastArea(IdentifiableMixin): class BroadcastArea(SortableMixin):
def __init__(self, row): def __init__(self, row):
id, name, feature, simple_feature = row id, name, feature, simple_feature = row
@@ -110,13 +103,23 @@ class BroadcastArea(IdentifiableMixin):
return self._simple_geofeature return self._simple_geofeature
@property
def sub_areas(self):
return [
BroadcastArea(row)
for row in BroadcastAreasRepository().get_all_areas_for_group(self.id)
]
class BroadcastAreaLibrary(SerialisedModelCollection, IdentifiableMixin, IdFromNameMixin, GetItemByIdMixin):
class BroadcastAreaLibrary(SerialisedModelCollection, SortableMixin, GetItemByIdMixin):
model = BroadcastArea model = BroadcastArea
def __init__(self, library_name): def __init__(self, row):
self.name = library_name id, name, is_group = row
self.id = id
self.name = name
self.is_group = bool(is_group)
def get_examples(self): def get_examples(self):
return BroadcastAreasRepository().get_library_description(self.id) return BroadcastAreasRepository().get_library_description(self.id)

View File

@@ -1,3 +1,4 @@
import geojson
import os import os
from pathlib import Path from pathlib import Path
import sqlite3 import sqlite3
@@ -20,7 +21,15 @@ class BroadcastAreasRepository(object):
conn.execute(""" conn.execute("""
CREATE TABLE broadcast_area_libraries ( CREATE TABLE broadcast_area_libraries (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
name TEXT NOT NULL name TEXT NOT NULL,
is_group BOOLEAN NOT NULL
)""")
conn.execute("""
CREATE TABLE broadcast_area_library_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
broadcast_area_library_id TEXT NOT NULL
)""") )""")
conn.execute(""" conn.execute("""
@@ -28,11 +37,15 @@ class BroadcastAreasRepository(object):
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
broadcast_area_library_id TEXT NOT NULL, broadcast_area_library_id TEXT NOT NULL,
broadcast_area_library_group_id TEXT,
feature_geojson TEXT NOT NULL, feature_geojson TEXT NOT NULL,
simple_feature_geojson TEXT NOT NULL, simple_feature_geojson TEXT NOT NULL,
FOREIGN KEY (broadcast_area_library_id) FOREIGN KEY (broadcast_area_library_id)
REFERENCES broadcast_area_libraries(id) REFERENCES broadcast_area_libraries(id),
FOREIGN KEY (broadcast_area_library_group_id)
REFERENCES broadcast_area_library_groups(id)
)""") )""")
conn.execute(""" conn.execute("""
@@ -40,36 +53,49 @@ class BroadcastAreasRepository(object):
ON broadcast_areas (broadcast_area_library_id); ON broadcast_areas (broadcast_area_library_id);
""") """)
def insert_broadcast_area_library(self, broadcast_area_name): conn.execute("""
broadcast_area_id = make_string_safe_for_id(broadcast_area_name) CREATE INDEX broadcast_areas_broadcast_area_library_group_id
ON broadcast_areas (broadcast_area_library_group_id);
""")
def insert_broadcast_area_library(self, id, name, is_group):
q = """ q = """
INSERT INTO broadcast_area_libraries (id, name) INSERT INTO broadcast_area_libraries (id, name, is_group)
VALUES (?, ?) VALUES (?, ?, ?)
""" """
with self.conn() as conn: with self.conn() as conn:
conn.execute(q, (broadcast_area_id, broadcast_area_name)) conn.execute(q, (id, name, is_group))
def insert_broadcast_area_library_group(self, id, name, library_id):
q = """
INSERT INTO broadcast_area_library_groups
(id, name, broadcast_area_library_id)
VALUES (?, ?, ?)
"""
with self.conn() as conn:
conn.execute(q, (id, name, library_id))
def insert_broadcast_areas(self, areas): def insert_broadcast_areas(self, areas):
q = """ q = """
INSERT INTO broadcast_areas ( INSERT INTO broadcast_areas (
id, name, id, name,
broadcast_area_library_id, feature_geojson, simple_feature_geojson broadcast_area_library_id, broadcast_area_library_group_id,
feature_geojson, simple_feature_geojson
) )
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
""" """
with self.conn() as conn: with self.conn() as conn:
for name, area_name, feature, simple_feature in areas: for id, name, area_id, group, feature, simple_feature in areas:
id = make_string_safe_for_id(name)
area_id = make_string_safe_for_id(area_name)
conn.execute(q, ( conn.execute(q, (
id, name, area_id, id, name,
geojson.dumps(feature), area_id, group,
geojson.dumps(simple_feature), geojson.dumps(feature), geojson.dumps(simple_feature),
)) ))
def query(self, sql, *args): def query(self, sql, *args):
@@ -79,9 +105,9 @@ class BroadcastAreasRepository(object):
return cursor.fetchall() return cursor.fetchall()
def get_libraries(self): def get_libraries(self):
q = "SELECT id, name FROM broadcast_area_libraries" q = "SELECT id, name, is_group FROM broadcast_area_libraries"
results = self.query(q) results = self.query(q)
libraries = [row[1] for row in results] libraries = [(row[0], row[1], row[2]) for row in results]
return sorted(libraries) return sorted(libraries)
def get_library_description(self, library_id): def get_library_description(self, library_id):
@@ -139,6 +165,7 @@ class BroadcastAreasRepository(object):
SELECT id, name, feature_geojson, simple_feature_geojson SELECT id, name, feature_geojson, simple_feature_geojson
FROM broadcast_areas FROM broadcast_areas
WHERE broadcast_area_library_id = ? WHERE broadcast_area_library_id = ?
AND broadcast_area_library_group_id IS NULL
""" """
results = self.query(q, library_id) results = self.query(q, library_id)
@@ -149,3 +176,35 @@ class BroadcastAreasRepository(object):
] ]
return areas return areas
def get_all_areas_for_group(self, group_id):
q = """
SELECT id, name, feature_geojson, simple_feature_geojson
FROM broadcast_areas
WHERE broadcast_area_library_group_id = ?
"""
results = self.query(q, group_id)
areas = [
(row[0], row[1], row[2], row[3])
for row in results
]
return areas
def get_all_groups_for_library(self, library_id):
q = """
SELECT id, name
FROM broadcast_area_library_groups
WHERE broadcast_area_library_id = ?
"""
results = self.query(q, library_id)
areas = [
(row[0], row[1])
for row in results
]
return areas

View File

@@ -9,22 +9,27 @@ from notifications_utils.broadcast_areas import (
def test_loads_libraries(): def test_loads_libraries():
assert [ assert [
(library.id, library.name) for library in sorted(broadcast_area_libraries) (library.id, library.name, library.is_group) for library in sorted(broadcast_area_libraries)
] == [ ] == [
( (
'counties-and-unitary-authorities-in-england-and-wales', 'counties-and-unitary-authorities-in-england-and-wales',
'Counties and Unitary Authorities in England and Wales'), 'Counties and Unitary Authorities in England and Wales',
False,
),
( (
'countries', 'countries',
'Countries', 'Countries',
False,
), ),
( (
'electoral-wards-of-the-united-kingdom', 'electoral-wards-of-the-united-kingdom',
'Electoral Wards of the United Kingdom', 'Electoral Wards of the United Kingdom',
True,
), ),
( (
'regions-of-england', 'regions-of-england',
'Regions of England', 'Regions of England',
False,
), ),
] ]
@@ -35,10 +40,10 @@ def test_loads_areas_from_library():
broadcast_area_libraries.get('countries') broadcast_area_libraries.get('countries')
) )
] == [ ] == [
('england', 'England'), ('countries-E92000001', 'England'),
('northern-ireland', 'Northern Ireland'), ('countries-N92000002', 'Northern Ireland'),
('scotland', 'Scotland'), ('countries-S92000003', 'Scotland'),
('wales', 'Wales'), ('countries-W92000004', 'Wales'),
] ]
@@ -54,10 +59,10 @@ def test_examples():
@pytest.mark.parametrize('id', ( @pytest.mark.parametrize('id', (
'england', 'countries-E92000001',
'northern-ireland', 'countries-N92000002',
'scotland', 'countries-S92000003',
'wales', 'countries-W92000004',
pytest.param('mercia', marks=pytest.mark.xfail(raises=KeyError)), pytest.param('mercia', marks=pytest.mark.xfail(raises=KeyError)),
)) ))
def test_loads_areas_from_libraries(id): def test_loads_areas_from_libraries(id):
@@ -70,7 +75,11 @@ def test_loads_areas_from_libraries(id):
def test_get_names_of_areas(): def test_get_names_of_areas():
areas = broadcast_area_libraries.get_areas( areas = broadcast_area_libraries.get_areas(
'wales', 'vale-of-glamorgan', 'england', 'essex', 'countries-W92000004',
'electoral-wards-of-the-united-kingdom-W06000014',
'countries-E92000001',
'counties-and-unitary-authorities-in-england-and-wales-E10000012',
) )
assert [area.name for area in sorted(areas)] == [ assert [area.name for area in sorted(areas)] == [
'England', 'Essex', 'Vale of Glamorgan', 'Wales', 'England', 'Essex', 'Vale of Glamorgan', 'Wales',
@@ -90,29 +99,34 @@ def test_get_areas_accepts_lists():
def test_has_polygons(): def test_has_polygons():
assert len( assert len(
broadcast_area_libraries.get_polygons_for_areas_long_lat('england') broadcast_area_libraries.get_polygons_for_areas_long_lat('countries-E92000001')
) == 35 ) == 35
assert len( assert len(
broadcast_area_libraries.get_polygons_for_areas_long_lat('scotland') broadcast_area_libraries.get_polygons_for_areas_long_lat('countries-S92000003')
) == 195 ) == 195
assert len( assert len(
broadcast_area_libraries.get_polygons_for_areas_long_lat('england', 'scotland') broadcast_area_libraries.get_polygons_for_areas_long_lat(
'countries-E92000001',
'countries-S92000003',
)
) == 35 + 195 == 230 ) == 35 + 195 == 230
assert len( assert len(
broadcast_area_libraries.get_polygons_for_areas_long_lat(['england', 'scotland']) broadcast_area_libraries.get_polygons_for_areas_lat_long(
'countries-E92000001',
'countries-S92000003',
)
) == 35 + 195 == 230 ) == 35 + 195 == 230
assert broadcast_area_libraries.get_polygons_for_areas_lat_long('england')[0][0] == [ assert broadcast_area_libraries.get_polygons_for_areas_lat_long('countries-E92000001')[0][0] == [
55.811085, -2.034358 # https://goo.gl/maps/wsf2LUWzYinwydMk8 55.811085, -2.034358 # https://goo.gl/maps/wsf2LUWzYinwydMk8
] ]
def test_polygons_are_enclosed_unless_asked_not_to_be(): def test_polygons_are_enclosed_unless_asked_not_to_be():
england = broadcast_area_libraries.get('countries').get('countries-E92000001')
england = broadcast_area_libraries.get('countries').get('england')
assert len(england.polygons) == len(england.unenclosed_polygons) assert len(england.polygons) == len(england.unenclosed_polygons)
@@ -128,8 +142,8 @@ def test_polygons_are_enclosed_unless_asked_not_to_be():
def test_lat_long_order(): def test_lat_long_order():
lat_long = broadcast_area_libraries.get_polygons_for_areas_lat_long('england') lat_long = broadcast_area_libraries.get_polygons_for_areas_lat_long('countries-E92000001')
long_lat = broadcast_area_libraries.get_polygons_for_areas_long_lat('england') long_lat = broadcast_area_libraries.get_polygons_for_areas_long_lat('countries-E92000001')
assert len(lat_long[0]) == len(long_lat[0]) == 2082 # Coordinates in polygon assert len(lat_long[0]) == len(long_lat[0]) == 2082 # Coordinates in polygon
assert len(lat_long[0][0]) == len(long_lat[0][0]) == 2 # Axes in coordinates assert len(lat_long[0][0]) == len(long_lat[0][0]) == 2 # Axes in coordinates
assert lat_long[0][0] == list(reversed(long_lat[0][0])) assert lat_long[0][0] == list(reversed(long_lat[0][0]))
@@ -137,7 +151,7 @@ def test_lat_long_order():
def test_includes_electoral_wards(): def test_includes_electoral_wards():
areas = broadcast_area_libraries.get_areas(['city-of-london---aldgate']) areas = broadcast_area_libraries.get_areas(['electoral-wards-of-the-united-kingdom-E05009289'])
assert len(areas) == 1 assert len(areas) == 1
@@ -151,4 +165,4 @@ def test_repository_has_all_libraries():
'Countries', 'Countries',
'Electoral Wards of the United Kingdom', 'Electoral Wards of the United Kingdom',
'Regions of England', 'Regions of England',
] == sorted(libraries) ] == sorted([name for _, name, _is_group in libraries])