From b40404009237410db681e9a43be0d1af66ce6476 Mon Sep 17 00:00:00 2001 From: Alan Ibarra Date: Fri, 13 Mar 2026 12:27:16 -0700 Subject: [PATCH 1/6] Fix issue with docker postgis --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index a743cce..1c541f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,7 @@ services: postgres: container_name: map-postgres image: postgis/postgis + platform: linux/amd64 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s From 51b1e526bb997bdf2baa7e7f8200240c95fe4ef3 Mon Sep 17 00:00:00 2001 From: Alan Ibarra Date: Fri, 13 Mar 2026 19:52:49 -0700 Subject: [PATCH 2/6] create serializer for geojson data --- map/serializers.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/map/serializers.py b/map/serializers.py index 03dd912..79911b9 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -3,32 +3,28 @@ from map.models import CommunityArea, RestaurantPermit +class CommunityAreaListSerializer(serializers.ListSerializer): + def to_representation(self, data): + result = [] + for item in data: + child_data = self.child.to_representation(item) + name = child_data["name"] + result.append({name: {"area_id": child_data["area_id"], "num_permits": child_data["num_permits"]}}) + return result + + class CommunityAreaSerializer(serializers.ModelSerializer): class Meta: model = CommunityArea - fields = ["name", "num_permits"] + fields = ["name", "area_id", "num_permits"] + list_serializer_class = CommunityAreaListSerializer num_permits = serializers.SerializerMethodField() def get_num_permits(self, obj): - """ - TODO: supplement each community area object with the number - of permits issued in the given year. - - e.g. The endpoint /map-data/?year=2017 should return something like: - [ - { - "ROGERS PARK": { - area_id: 17, - num_permits: 2 - }, - "BEVERLY": { - area_id: 72, - num_permits: 2 - }, - ... - } - ] - """ - - pass + area_id = obj.area_id + num_permits = RestaurantPermit.objects.filter(community_area_id=area_id, issue_date__year=self.context["year"]).count() + return num_permits + + + From 74b42b7b62350ad9e341e529e63370e5ee8075ea Mon Sep 17 00:00:00 2001 From: Alan Ibarra Date: Fri, 13 Mar 2026 19:53:20 -0700 Subject: [PATCH 3/6] write test for endpoint --- tests/test_views.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/test_views.py b/tests/test_views.py index 24cc64e..819ea59 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -7,6 +7,12 @@ from map.models import CommunityArea, RestaurantPermit +EXPECTED_DATA = { + "Beverly": {"area_id": 1, "num_permits": 2}, + "Lincoln Park": {"area_id": 2, "num_permits": 3}, +} + + @pytest.mark.django_db def test_map_data_view(): # Create some test community areas @@ -36,6 +42,15 @@ def test_map_data_view(): client = APIClient() response = client.get(reverse("map_data", query={"year": 2021})) - # TODO: Complete the test by asserting that the /map-data/ endpoint - # returns the correct number of permits for Beverly and Lincoln - # Park in 2021 + data = response.data + + beverly_item = next((item for item in data if "Beverly" in item), None) + beverly_data = beverly_item["Beverly"] if beverly_item else None + + lincoln_park_item = next((item for item in data if "Lincoln Park" in item), None) + lincoln_park_data = lincoln_park_item["Lincoln Park"] if lincoln_park_item else None + + assert beverly_data is not None + assert lincoln_park_data is not None + assert beverly_data["num_permits"] == EXPECTED_DATA["Beverly"]["num_permits"] + assert lincoln_park_data["num_permits"] == EXPECTED_DATA["Lincoln Park"]["num_permits"] From f252705fcc0cef60ff99e6d79675ba8a2a595f54 Mon Sep 17 00:00:00 2001 From: Alan Ibarra Date: Fri, 13 Mar 2026 19:54:00 -0700 Subject: [PATCH 4/6] hookup the data and display --- map/static/js/App.js | 17 ++-- map/static/js/RestaurantPermitMap.js | 137 ++++++++++++++++----------- 2 files changed, 93 insertions(+), 61 deletions(-) diff --git a/map/static/js/App.js b/map/static/js/App.js index 5a8ec40..22a3489 100644 --- a/map/static/js/App.js +++ b/map/static/js/App.js @@ -1,8 +1,13 @@ -import React from "react" -import { createRoot } from "react-dom/client" +import React from "react"; +import { createRoot } from "react-dom/client"; -import RestaurantPermitMap from "./RestaurantPermitMap" +import RestaurantPermitMap from "./RestaurantPermitMap"; -const container = document.getElementById("map") -const root = createRoot(container) -root.render() +const container = document.getElementById("map"); +const root = createRoot(container); +root.render( + // Would use ReactErrorBoundary library to catch errors anywhere in RestaurantPermitMap and display a fallback UI. + // + , + // +); diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 57f8ea0..ee8b53d 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -1,25 +1,19 @@ -import React, { useEffect, useState } from "react" +import React, { useEffect, useState } from "react"; -import { MapContainer, TileLayer, GeoJSON } from "react-leaflet" +import { MapContainer, TileLayer, GeoJSON } from "react-leaflet"; -import "leaflet/dist/leaflet.css" +import "leaflet/dist/leaflet.css"; -import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson" +import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson"; -function YearSelect({ setFilterVal }) { - // Filter by the permit issue year for each restaurant - const startYear = 2026 - const years = [...Array(11).keys()].map((increment) => { - return startYear - increment - }) - const options = years.map((year) => { - return ( - - ) - }) +const START_YEAR = 2026; + +// Generates years from 2026 to 2016 +const YEAR_OPTIONS = [...Array(11).keys()].map((increment) => { + return START_YEAR - increment; +}); +function YearSelect({ setFilterVal }) { return ( <>