Skip to content

Commit

Permalink
Populate address truth source from admin edits (#511)
Browse files Browse the repository at this point in the history
* Populate address truth source from admin edits

* Allow selecting APs and sectors when adding links in the admin UI

* Show AP links on the map

* Add links between access points at spreadsheet import time

* Add test for map display of AP links

* Fix mypy types

* Fix non-determinism in tests

* Kick CI

* Merge migrations
  • Loading branch information
Andrew-Dickinson authored Sep 16, 2024
1 parent ad5e4cb commit 1b0bc6f
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 1 deletion.
22 changes: 22 additions & 0 deletions src/meshapi/admin/models/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from meshapi.models import Building
from meshapi.widgets import AutoPopulateLocationWidget, PanoramaViewer
from meshdb.utils.spreadsheet_import.building.constants import AddressTruthSource

from ..inlines import BuildingLOSInline, InstallInline
from ..ranked_search import RankedSearchMixin
Expand Down Expand Up @@ -142,3 +143,24 @@ def get_form(
return form

filter_horizontal = ("nodes",)

def save_model(self, request: HttpRequest, obj: Building, form: ModelForm, change: bool) -> None:
address_fields_changed = any(
field in form.changed_data
for field in [
"street_address",
"city",
"state",
"zip_code",
]
)

if address_fields_changed:
# If the admin UI edited (or added) the address fields, we attribute this edit to
# "Human Entry" to keep the truth sources up to date
if obj.address_truth_sources is None:
obj.address_truth_sources = []

obj.address_truth_sources += [AddressTruthSource.HumanEntry.value]

super().save_model(request, obj, form, change)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.2.9 on 2024-09-07 18:22

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("meshapi", "0025_rebuild_building_node_m2m_to_fix_dupes"),
]

operations = [
migrations.AlterField(
model_name="building",
name="address_truth_sources",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(
choices=[
("OSMNominatim", "OSMNominatim"),
("OSMNominatimZIPOnly", "OSMNominatimZIPOnly"),
("NYCPlanningLabs", "NYCPlanningLabs"),
("PeliasStringParsing", "PeliasStringParsing"),
("ReverseGeocodeFromCoordinates", "ReverseGeocodeFromCoordinates"),
("HumanEntry", "HumanEntry"),
]
),
help_text="A list of strings that answers the question: How was the content of the street address, city, state, and ZIP fields determined? This is useful in understanding the level of validation applied to spreadsheet imported data. Possible values are: OSMNominatim, OSMNominatimZIPOnly, NYCPlanningLabs, PeliasStringParsing, ReverseGeocodeFromCoordinates, HumanEntry. Check the import script for details",
size=None,
),
),
]
12 changes: 12 additions & 0 deletions src/meshapi/migrations/0030_merge_20240916_1938.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generated by Django 4.2.13 on 2024-09-16 23:38

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("meshapi", "0026_alter_building_address_truth_sources"),
("meshapi", "0029_merge_20240916_1919"),
]

operations = []
2 changes: 1 addition & 1 deletion src/meshapi/models/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Meta:
models.CharField(
choices=list((src.value, src.name) for src in AddressTruthSource),
),
help_text="A list of strings that answers the question: How was the content of"
help_text="A list of strings that answers the question: How was the content of "
"the street address, city, state, and ZIP fields determined? This is useful in "
"understanding the level of validation applied to spreadsheet imported data. Possible values are: "
f"{', '.join(src.value for src in AddressTruthSource)}. Check the import script for details",
Expand Down
121 changes: 121 additions & 0 deletions src/meshapi/tests/test_admin_change_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from meshapi.models import LOS, AccessPoint, Building, Device, Install, Link, Member, Node, Sector
from meshapi_hooks.hooks import CelerySerializerHook
from meshdb.utils.spreadsheet_import.building.constants import AddressTruthSource

from .sample_data import sample_building, sample_device, sample_install, sample_member, sample_node

Expand Down Expand Up @@ -461,3 +462,123 @@ def test_add_new_planned_node_no_nn(self):
self.assertEqual(node.install_date, datetime.date(2022, 2, 23))
self.assertEqual(node.abandon_date, datetime.date(2022, 2, 23))
self.assertEqual(node.notes, "Test notes")

def test_add_building(self):
change_url = "/admin/meshapi/building/add/"
response = self._call(change_url, 200)
form_soup = bs4.BeautifulSoup(response.content.decode()).find(id="building_form")
fill_in_admin_form(
form_soup,
{
"id_street_address": "123 Test Street",
"id_city": "New York",
"id_state": "NY",
"id_zip_code": "10001",
"id_bin": "12345678",
"id_latitude": "0",
"id_longitude": "0",
"id_altitude": "0",
"id_notes": "Test notes",
"id_panoramas": json.dumps(["http://example.com"]),
"id_primary_node": str(self.node1.id),
# "id_nodes": "Test notes",
},
)
response = self._submit_form(change_url, form_soup, 302)
building_id = response.url.split("/")[-3]
building = Building.objects.get(id=building_id)

self.assertEqual(building.street_address, "123 Test Street")
self.assertEqual(building.city, "New York")
self.assertEqual(building.state, "NY")
self.assertEqual(building.zip_code, "10001")
self.assertEqual(building.bin, 12345678)
self.assertEqual(building.latitude, 0)
self.assertEqual(building.longitude, 0)
self.assertEqual(building.altitude, 0)
self.assertEqual(building.notes, "Test notes")
self.assertEqual(building.panoramas, ["http://example.com"])
self.assertEqual(building.address_truth_sources, [AddressTruthSource.HumanEntry.value])
self.assertEqual(building.primary_node, self.node1)
self.assertEqual(list(building.nodes.all()), [self.node1])

def test_change_building_but_not_address(self):
change_url = f"/admin/meshapi/building/{self.building_1.id}/change/"
response = self._call(change_url, 200)
form_soup = bs4.BeautifulSoup(response.content.decode()).find(id="building_form")
fill_in_admin_form(
form_soup,
{
"id_street_address": self.building_1.street_address,
"id_city": self.building_1.city,
"id_state": self.building_1.state,
"id_zip_code": str(self.building_1.zip_code),
"id_bin": "12345678",
"id_latitude": "0",
"id_longitude": "0",
"id_altitude": "0",
"id_notes": "Test notes",
"id_panoramas": json.dumps(["http://example.com"]),
"id_primary_node": str(self.node1.id),
# "id_nodes": "Test notes",
},
)
response = self._submit_form(change_url, form_soup, 302)
building_id = response.url.split("/")[-3]
building = Building.objects.get(id=building_id)

self.assertEqual(building.street_address, "3333 Chom St")
self.assertEqual(building.city, "Brooklyn")
self.assertEqual(building.state, "NY")
self.assertEqual(building.zip_code, "11111")
self.assertEqual(building.bin, 12345678)
self.assertEqual(building.latitude, 0)
self.assertEqual(building.longitude, 0)
self.assertEqual(building.altitude, 0)
self.assertEqual(building.notes, "Test notes")
self.assertEqual(building.panoramas, ["http://example.com"])
self.assertEqual(building.address_truth_sources, [AddressTruthSource.NYCPlanningLabs.value])
self.assertEqual(building.primary_node, self.node1)
self.assertEqual(list(building.nodes.all()), [self.node1])

def test_change_building_address(self):
change_url = f"/admin/meshapi/building/{self.building_1.id}/change/"
response = self._call(change_url, 200)
form_soup = bs4.BeautifulSoup(response.content.decode()).find(id="building_form")
fill_in_admin_form(
form_soup,
{
"id_street_address": "666 ABC St",
"id_city": "New York",
"id_state": "NY",
"id_zip_code": "10001",
"id_bin": "12345678",
"id_latitude": "0",
"id_longitude": "0",
"id_altitude": "0",
"id_notes": "Test notes",
"id_panoramas": json.dumps(["http://example.com"]),
"id_primary_node": str(self.node1.id),
# "id_nodes": "Test notes",
},
)
response = self._submit_form(change_url, form_soup, 302)
building_id = response.url.split("/")[-3]
building = Building.objects.get(id=building_id)

self.assertEqual(building.street_address, "666 ABC St")
self.assertEqual(building.city, "New York")
self.assertEqual(building.state, "NY")
self.assertEqual(building.zip_code, "10001")
self.assertEqual(building.bin, 12345678)
self.assertEqual(building.latitude, 0)
self.assertEqual(building.longitude, 0)
self.assertEqual(building.altitude, 0)
self.assertEqual(building.notes, "Test notes")
self.assertEqual(building.panoramas, ["http://example.com"])
self.assertEqual(
building.address_truth_sources,
[AddressTruthSource.NYCPlanningLabs.value, AddressTruthSource.HumanEntry.value],
)
self.assertEqual(building.primary_node, self.node1)
self.assertEqual(list(building.nodes.all()), [self.node1])
1 change: 1 addition & 0 deletions src/meshdb/utils/spreadsheet_import/building/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class AddressTruthSource(Enum):
NYCPlanningLabs = "NYCPlanningLabs"
PeliasStringParsing = "PeliasStringParsing"
ReverseGeocodeFromCoordinates = "ReverseGeocodeFromCoordinates"
HumanEntry = "HumanEntry"


@dataclasses.dataclass
Expand Down

0 comments on commit 1b0bc6f

Please sign in to comment.