From 3b22e332cabf6e5d147e06b85cff016a5578b512 Mon Sep 17 00:00:00 2001 From: Michael Grotton Date: Wed, 25 Mar 2026 20:17:28 -0500 Subject: [PATCH 1/4] update CommunityAreaSerializer to include num_permits (Task 1) --- map/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/map/serializers.py b/map/serializers.py index 03dd912..d65ed9f 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -29,6 +29,9 @@ def get_num_permits(self, obj): ... } ] + """ + num_permits = RestaurantPermit.objects.filter(community_area_id=obj.area_id).filter(issue_date__year=self.context.get("year")).count() + return num_permits pass From ae5dac752d0e0fbf56ebf5c3cdae1763a6fd0ee4 Mon Sep 17 00:00:00 2001 From: Michael Grotton Date: Wed, 25 Mar 2026 20:37:11 -0500 Subject: [PATCH 2/4] fetch yearly data and display summary statistics (Tasks 3, 4) --- map/static/js/RestaurantPermitMap.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 57f8ea0..b0dc1ea 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -43,14 +43,17 @@ export default function RestaurantPermitMap() { const [year, setYear] = useState(2026) const yearlyDataEndpoint = `/map-data/?year=${year}` + const maxNumPermits = Math.max(...currentYearData.map((area) => area.num_permits)); + const totalNumPermits = currentYearData.reduce((total, area) => total + area.num_permits, 0); useEffect(() => { - fetch() + fetch(yearlyDataEndpoint) .then((res) => res.json()) .then((data) => { /** * TODO: Fetch the data needed to supply to map with data */ + setCurrentYearData(data); }) }, [yearlyDataEndpoint]) @@ -81,11 +84,10 @@ export default function RestaurantPermitMap() { <>

- Restaurant permits issued this year: {/* TODO: display this value */} + Restaurant permits issued this year: {currentYearData.length > 0 && totalNumPermits}

- Maximum number of restaurant permits in a single area: - {/* TODO: display this value */} + Maximum number of restaurant permits in a single area: {currentYearData.length > 0 && maxNumPermits}

Date: Wed, 25 Mar 2026 22:07:22 -0500 Subject: [PATCH 3/4] add shading/popups to the map and clean-up (Task 5) --- map/serializers.py | 23 +---------------------- map/static/js/RestaurantPermitMap.js | 28 ++++++++++------------------ 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/map/serializers.py b/map/serializers.py index d65ed9f..633048a 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -11,27 +11,6 @@ class Meta: 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 - }, - ... - } - ] - - """ - num_permits = RestaurantPermit.objects.filter(community_area_id=obj.area_id).filter(issue_date__year=self.context.get("year")).count() - return num_permits + return RestaurantPermit.objects.filter(community_area_id=obj.area_id).filter(issue_date__year=self.context.get("year")).count() pass diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index b0dc1ea..01d1090 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -43,39 +43,31 @@ export default function RestaurantPermitMap() { const [year, setYear] = useState(2026) const yearlyDataEndpoint = `/map-data/?year=${year}` - const maxNumPermits = Math.max(...currentYearData.map((area) => area.num_permits)); + const maxNumPermits = currentYearData.reduce((max, area) => (area.num_permits > max ? area.num_permits : max), 0); const totalNumPermits = currentYearData.reduce((total, area) => total + area.num_permits, 0); useEffect(() => { fetch(yearlyDataEndpoint) .then((res) => res.json()) .then((data) => { - /** - * TODO: Fetch the data needed to supply to map with data - */ setCurrentYearData(data); }) }, [yearlyDataEndpoint]) function getColor(percentageOfPermits) { - /** - * TODO: Use this function in setAreaInteraction to set a community - * area's color using the communityAreaColors constant above - */ + // dynamically convert percentage to indices from 0 through communityAreaColors.length-1 + // enforce max index of communityAreaColors.length-1 with Math.min() + + return communityAreaColors[Math.floor(Math.min(percentageOfPermits * communityAreaColors.length, communityAreaColors.length-1))]; } function setAreaInteraction(feature, layer) { - /** - * TODO: Use the methods below to: - * 1) Shade each community area according to what percentage of - * permits were issued there in the selected year - * 2) On hover, display a popup with the community area's raw - * permit count for the year - */ - layer.setStyle() - layer.on("", () => { - layer.bindPopup("") + const communityNumPermits = currentYearData.find(area => area.name === feature.properties.community)?.num_permits || 0; + + layer.setStyle({color: "black", weight:1, fillColor: getColor(communityNumPermits / maxNumPermits), fillOpacity: 0.7}); + layer.on("mouseover", () => { + layer.bindPopup(`${feature.properties.community}
Permits: ${communityNumPermits}`); layer.openPopup() }) } From 378eec405cb748f59ac409b3c75d4a291bf496b2 Mon Sep 17 00:00:00 2001 From: Michael Grotton Date: Thu, 26 Mar 2026 00:10:35 -0500 Subject: [PATCH 4/4] add test for /map-data endpoint (Task 2) --- map/static/js/RestaurantPermitMap.js | 2 +- tests/test_views.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 01d1090..7779e6a 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -56,7 +56,7 @@ export default function RestaurantPermitMap() { function getColor(percentageOfPermits) { - // dynamically convert percentage to indices from 0 through communityAreaColors.length-1 + // dynamically convert percentage to indices from 0 through communityAreaColors.length-1 to access communityAreaColors // enforce max index of communityAreaColors.length-1 with Math.min() return communityAreaColors[Math.floor(Math.min(percentageOfPermits * communityAreaColors.length, communityAreaColors.length-1))]; diff --git a/tests/test_views.py b/tests/test_views.py index 24cc64e..a4422f0 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -36,6 +36,8 @@ 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 + for area in response.data: + if area["name"] == "Beverly": + assert area["num_permits"] == 2 + elif area["name"] == "Lincoln Park": + assert area["num_permits"] == 3