Strict adherence to canonical ActivityNames, to avoid the problem where Spothole was giving out activities like ["Towers", "TOWERS"]. All uses of ActivityName should now be canonical not arbitrary strings, and attempts to convert unknown strings to activitynames will be logged for me to check out.

This commit is contained in:
Ian Renton
2026-09-25 09:16:53 +01:00
parent 7417140a3d
commit 760c2412d3
17 changed files with 144 additions and 145 deletions
+9 -3
View File
@@ -1,7 +1,12 @@
import logging
from data.activities import ACTIVITIES
from data.activity import Activity
logger = logging.getLogger(__name__)
def get_activity_by_name(name):
def get_activity_by_name(name: str | None) -> Activity | None:
"""Utility function to resolve an arbitrary, case-insensitive activity name string (e.g. from a spot comment, a
provider, or an API request) to the matching known Activity. Returns None if no match is found."""
@@ -10,10 +15,11 @@ def get_activity_by_name(name):
for activity_name, activity in ACTIVITIES.items():
if activity_name.upper() == name.upper():
return activity
logger.warning(f"Unknown activity name '{name}', developer may need to add support for this!")
return None
def get_ref_regex_for_activity(activity):
def get_ref_regex_for_activity(activity: str | None) -> str | None:
"""Utility function to get the regex string for an activity reference for a named activity. If no match is
found, None will be returned."""
@@ -21,7 +27,7 @@ def get_ref_regex_for_activity(activity):
return found.ref_regex if found else None
def get_icon_for_activity(activity):
def get_icon_for_activity(activity: str | None) -> str | None:
"""Utility function to get the icon for a named activity. If no match is found, None will be returned."""
found = get_activity_by_name(activity)
+2 -2
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from core.enums import ActivityRefType
from core.enums import ActivityName, ActivityRefType
@dataclass
@@ -9,7 +9,7 @@ class ActivityRef:
name and a lookup URL."""
# Activity that this reference is in, e.g. "POTA".
activity: str
activity: ActivityName
# Reference ID, e.g. "GB-0001".
id: str | None = None
# Name of the reference, e.g. "Null Country Park", if known.
+18 -10
View File
@@ -8,10 +8,11 @@ import pytz
from pyhamtools.locator import latlong_to_locator, locator_to_latlong
from core.activity_lookup_helper import populate_missing_activity_ref_info
from core.activity_utils import get_icon_for_activity
from core.activity_utils import get_activity_by_name, get_icon_for_activity
from core.call_lookup_helper import get_call_info
from core.enums import Continent
from core.enums import ActivityName, Continent
from core.utils import get_flag_for_dxcc
from data.activity_ref import ActivityRef
logger = logging.getLogger(__name__)
@@ -70,9 +71,9 @@ class Alert:
# e.g. a POTA and WWFF dual activation. This is a list so we can maintain the order items were added, but needs to
# be set-like to avoid dupes, and there's no Python class that handles that properly. So we use a list, but handle
# the uniqueness logic manually, so you must use add_activity() to add to it instead of adding directly.
activities: list = field(default_factory=list)
activities: list[ActivityName] = field(default_factory=list)
# Activity references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO.
activity_refs: list = field(default_factory=list)
activity_refs: list[ActivityRef] = field(default_factory=list)
# Timing info
@@ -96,9 +97,11 @@ class Alert:
icon: str | None = None
def __post_init__(self):
"""Normalise the activities list, removing any duplicates while keeping the order."""
"""Normalise the activities list, converting any activity names provided as strings to their canonical
ActivityName (dropping any we don't know about) and removing any duplicates while keeping the order."""
self.activities = list(dict.fromkeys(self.activities)) if self.activities else []
found_activities = [get_activity_by_name(activity) for activity in self.activities or []]
self.activities = list(dict.fromkeys(found.name for found in found_activities if found))
def infer_missing(self, credentials=None):
"""Infer missing parameters where possible"""
@@ -155,7 +158,7 @@ class Alert:
# Add the activities of any activity refs we have to the alert's list of activities.
for activity_ref in self.activity_refs:
if activity_ref and activity_ref.activity:
if activity_ref:
self.add_activity(activity_ref.activity)
# DX Grid to lat/lon and vice versa in case one is missing
@@ -197,11 +200,16 @@ class Alert:
except Exception:
logger.exception("Exception while inferring missing data from spot")
def add_activity(self, activity):
def add_activity(self, activity: ActivityName | None):
"""Add an activity to the activities list, so long as it's not blank and not already there. The list is kept in
insertion order, so the first activity added is treated as the "primary" one."""
insertion order, so the first activity added is treated as the "primary" one. Only canonical ActivityNames are
accepted otherwise we risk sending unknown stuff to API clients."""
if activity and activity not in self.activities:
if not activity:
return
if not isinstance(activity, ActivityName):
raise TypeError(f"add_activity() requires an ActivityName, got {type(activity).__name__} {activity!r}")
if activity not in self.activities:
self.activities.append(activity)
def to_json(self):
+30 -18
View File
@@ -5,6 +5,7 @@ import re
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from math import isnan
from typing import cast
import pytz
from pyhamtools.locator import latlong_to_locator, locator_to_latlong
@@ -129,9 +130,9 @@ class Spot:
# e.g. a POTA and WWFF dual activation. This is a list so we can maintain the order items were added, but needs to
# be set-like to avoid dupes, and there's no Python class that handles that properly. So we use a list, but handle
# the uniqueness logic manually, so you must use add_activity() to add to it instead of adding directly.
activities: list = field(default_factory=list)
activities: list[ActivityName] = field(default_factory=list)
# Activity references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO.
activity_refs: list = field(default_factory=list)
activity_refs: list[ActivityRef] = field(default_factory=list)
# Timing info
@@ -161,14 +162,22 @@ class Spot:
def __post_init__(self):
"""Normalise fields that don't survive a plain dict to Spot conversion. This is used in the "add spot" API
endpoint where the client is submitting JSON, and we want to recreate a full Spot object, including nested
objects such as the activity_refs list, and de-duplicating the activities list."""
objects such as the activity_refs list, and de-duplicating the activities list. Activity names provided as
strings are converted to their canonical ActivityName. Any activities, or activity refs, for an activity we
don't know about are dropped."""
self.activities = list(dict.fromkeys(self.activities)) if self.activities else []
if self.activity_refs:
self.activity_refs = [
activity_ref if isinstance(activity_ref, ActivityRef) else ActivityRef(**activity_ref)
for activity_ref in self.activity_refs
]
found_activities = [get_activity_by_name(activity) for activity in self.activities or []]
self.activities = list(dict.fromkeys(found.name for found in found_activities if found))
# When created from JSON, activity refs arrive as dicts rather than ActivityRef objects.
activity_refs = []
for activity_ref in cast("list[ActivityRef | dict]", self.activity_refs or []):
if isinstance(activity_ref, ActivityRef):
activity_refs.append(activity_ref)
elif found_activity := get_activity_by_name(activity_ref.get("activity")):
activity_ref_data = dict(activity_ref)
activity_ref_data["activity"] = found_activity.name
activity_refs.append(ActivityRef(**activity_ref_data))
self.activity_refs = activity_refs
def infer_missing(self, credentials=None):
"""Infer missing parameters where possible"""
@@ -274,13 +283,12 @@ class Spot:
# Add the activities of any activity refs we have to the top-level activities list.
for activity_ref in self.activity_refs:
if activity_ref.activity:
self.add_activity(activity_ref.activity.upper())
self.add_activity(activity_ref.activity)
# See if we already have an activity reference, but the comment looks like it contains more for the same
# activity. This should catch e.g. POTA comments like "2-fer: GB-0001 GB-0002".
if self.comment and self.activity_refs and self.activity_refs[0].activity:
activity = self.activity_refs[0].activity.upper()
if self.comment and self.activity_refs:
activity = self.activity_refs[0].activity
regex = get_ref_regex_for_activity(activity)
if regex:
all_comment_ref_matches = re.finditer(r"(?<!\w)(" + regex + r")(?!\w)", self.comment, re.IGNORECASE)
@@ -476,7 +484,7 @@ class Spot:
# Determine a "QTH" string. If we have an activity ref, pick the first one and turn it into a suitable
# string, otherwise see what they have set on an online lookup service.
if self.activity_refs:
qth = self.activity_refs[0].id
qth = self.activity_refs[0].id or ""
if self.activity_refs[0].name:
qth += f" {self.activity_refs[0].name}"
self.dx_qth = qth
@@ -539,11 +547,16 @@ class Spot:
except Exception:
logger.exception("Exception while inferring missing data from spot")
def add_activity(self, activity):
def add_activity(self, activity: ActivityName | None):
"""Add an activity to the activities list, so long as it's not blank and not already there. The list is kept in
insertion order, so the first activity added is treated as the "primary" one."""
insertion order, so the first activity added is treated as the "primary" one. Only canonical ActivityNames are
accepted otherwise we risk sending unknown stuff to API clients."""
if activity and activity not in self.activities:
if not activity:
return
if not isinstance(activity, ActivityName):
raise TypeError(f"add_activity() requires an ActivityName, got {type(activity).__name__} {activity!r}")
if activity not in self.activities:
self.activities.append(activity)
def to_json(self):
@@ -555,7 +568,6 @@ class Spot:
"""Append an activity ref to the list, so long as it's not already there."""
new_activity_ref.id = new_activity_ref.id.strip().upper()
new_activity_ref.activity = new_activity_ref.activity.strip().upper()
if new_activity_ref.id == "":
return
for activity_ref in self.activity_refs:
+9 -17
View File
@@ -3,6 +3,7 @@ from datetime import datetime
import pytz
from core.activity_utils import get_activity_by_name
from core.enums import ActivityName
from data.activity_ref import ActivityRef
from data.alert import Alert
@@ -37,7 +38,13 @@ class ParksNPeaks(HTTPAlertProvider):
datetime.strptime(source_alert["alTime"], "%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC).timestamp()
)
activity_refs = [ActivityRef(id=ref_id, activity=activity, name=ref_name)]
# We can only add a reference if we know the activity it's for
found_activity = get_activity_by_name(activity)
activities = []
activity_refs = []
if found_activity is not None:
activities = [found_activity.name]
activity_refs = [ActivityRef(id=ref_id, activity=found_activity.name, name=ref_name)]
# Convert to our alert format
alert = Alert(
@@ -46,26 +53,11 @@ class ParksNPeaks(HTTPAlertProvider):
dx_calls=[source_alert["CallSign"].upper()],
freqs_modes=f"{source_alert['Freq']} {source_alert['MODE']}",
comment=source_alert["Comments"],
activities=[activity] if activity else [],
activities=activities,
activity_refs=activity_refs,
start_time=start_time,
)
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if activity and activity not in [
ActivityName.POTA,
ActivityName.SOTA,
ActivityName.WWFF,
ActivityName.HEMA,
ActivityName.SIOTA,
ActivityName.ZLOTA,
ActivityName.KRMNPA,
ActivityName.SANPCPA,
ActivityName.LLOTA,
ActivityName.QRP,
]:
logger.warning(f"PNP alert found with activity {activity}, developer needs to add support for this!")
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to
# the alert list. Note that while ZLOTA has its own spots API, it doesn't have its own alerts API. So that
# means the PnP *spot* provider rejects ZLOTA spots here, but the PnP *alerts* provider here allows ZLOTA.
+28 -42
View File
@@ -68,15 +68,6 @@ class GMA(HTTPSpotProvider):
# Filter out some weird mode strings
mode=Mode.from_name(source_spot["MODE"].upper()) if "<>" not in source_spot["MODE"] else None,
comment=source_spot["TEXT"],
activity_refs=[
ActivityRef(
id=source_spot["REF"],
activity="",
name=source_spot["NAME"],
latitude=lat,
longitude=lon,
)
],
time=time,
dx_latitude=lat,
dx_longitude=lon,
@@ -98,57 +89,52 @@ class GMA(HTTPSpotProvider):
and ref_response.text != "\n"
):
ref_info = ref_response.json()
if spot.activity_refs and ref_info and "reftype" in ref_info:
if ref_info and "reftype" in ref_info:
activity: ActivityName | None = None
ref_type: ActivityRefType | None = None
match ref_info["reftype"]:
case "Summit":
# Summits are a bit complicated, they can be SOTA or GMA depending on the
# separate "sota" field:
if "sota" in ref_info and ref_info["sota"] != "":
spot.activity_refs[0].activity = ActivityName.SOTA
spot.activity_refs[0].ref_type = ActivityRefType.SUMMIT
spot.add_activity(ActivityName.SOTA)
activity, ref_type = ActivityName.SOTA, ActivityRefType.SUMMIT
else:
spot.activity_refs[0].activity = ActivityName.GMA
spot.activity_refs[0].ref_type = ActivityRefType.SUMMIT
spot.add_activity(ActivityName.GMA)
activity, ref_type = ActivityName.GMA, ActivityRefType.SUMMIT
case "POTA":
spot.activity_refs[0].activity = ActivityName.POTA
spot.activity_refs[0].ref_type = ActivityRefType.PARK
spot.add_activity(ActivityName.POTA)
activity, ref_type = ActivityName.POTA, ActivityRefType.PARK
case "WWFF":
spot.activity_refs[0].activity = ActivityName.WWFF
spot.activity_refs[0].ref_type = ActivityRefType.PARK
spot.add_activity(ActivityName.WWFF)
activity, ref_type = ActivityName.WWFF, ActivityRefType.PARK
case "IOTA Island":
spot.activity_refs[0].activity = ActivityName.IOTA
spot.activity_refs[0].ref_type = ActivityRefType.ISLAND
spot.add_activity(ActivityName.IOTA)
activity, ref_type = ActivityName.IOTA, ActivityRefType.ISLAND
case "GMA Island":
spot.activity_refs[0].activity = ActivityName.GMA_ISLANDS
spot.activity_refs[0].ref_type = ActivityRefType.ISLAND
spot.add_activity(ActivityName.GMA_ISLANDS)
activity, ref_type = ActivityName.GMA_ISLANDS, ActivityRefType.ISLAND
case "Lighthouse (ILLW)":
spot.activity_refs[0].activity = ActivityName.ILLW
spot.activity_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.add_activity(ActivityName.ILLW)
activity, ref_type = ActivityName.ILLW, ActivityRefType.LIGHTHOUSE
case "Lighthouse (ARLHS)":
spot.activity_refs[0].activity = ActivityName.ARLHS
spot.activity_refs[0].ref_type = ActivityRefType.LIGHTHOUSE
spot.add_activity(ActivityName.ARLHS)
activity, ref_type = ActivityName.ARLHS, ActivityRefType.LIGHTHOUSE
case "Castle":
spot.activity_refs[0].activity = ActivityName.WCA
spot.activity_refs[0].ref_type = ActivityRefType.CASTLE
spot.add_activity(ActivityName.WCA)
activity, ref_type = ActivityName.WCA, ActivityRefType.CASTLE
case "Mill":
spot.activity_refs[0].activity = ActivityName.MOTA
spot.activity_refs[0].ref_type = ActivityRefType.MILL
spot.add_activity(ActivityName.MOTA)
activity, ref_type = ActivityName.MOTA, ActivityRefType.MILL
case _:
logger.warning(
f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!"
)
spot.activity_refs[0].activity = ref_info["reftype"]
spot.add_activity(ref_info["reftype"])
# Now we know the activity, add the reference to the spot. If it's an activity we
# don't know, there's no ActivityName for it, so we can't add a reference.
if activity is not None:
spot.activity_refs = [
ActivityRef(
id=source_spot["REF"],
activity=activity,
ref_type=ref_type,
name=source_spot["NAME"],
latitude=lat,
longitude=lon,
)
]
spot.add_activity(activity)
elif not ref_response.from_cache:
if not ref_response.ok:
+7 -20
View File
@@ -6,6 +6,7 @@ from typing import ClassVar
import pytz
import requests
from core.activity_utils import get_activity_by_name
from core.constants import HTTP_HEADERS
from core.enums import ActivityName, Mode
from data.activity_ref import ActivityRef
@@ -67,16 +68,19 @@ class ParksNPeaks(HTTPSpotProvider):
# Record activity information
activity = source_spot["actClass"].upper()
found_activity = get_activity_by_name(activity)
ref_id = source_spot["actSiteID"]
if activity:
spot.add_activity(activity)
if found_activity is not None:
spot.add_activity(found_activity.name)
if ref_id:
# We can only add a reference if we know the activity it's for
if ref_id and found_activity is not None:
activity_refs = [
ActivityRef(
id=ref_id,
activity=activity,
activity=found_activity.name,
# Free text location is not present in all spots, so only add it if it's set
name=source_spot["actLocation"]
if "actLocation" in source_spot and source_spot["actLocation"] != ""
@@ -96,23 +100,6 @@ class ParksNPeaks(HTTPSpotProvider):
):
spot.comment = source_spot["actLocation"]
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if activity not in [
ActivityName.POTA,
ActivityName.SOTA,
ActivityName.WWFF,
ActivityName.HEMA,
ActivityName.SIOTA,
ActivityName.ZLOTA,
ActivityName.KRMNPA,
ActivityName.SANPCPA,
ActivityName.LLOTA,
ActivityName.QRP,
]:
logger.warning(
f"PNP spot found with activity {activity}, developer needs to add support for this!"
)
# Add new spot to the list
new_spots.append(spot)
return new_spots
+20 -11
View File
@@ -1,13 +1,17 @@
import json
import logging
from datetime import datetime
import pytz
from core.enums import Mode
from core.activity_utils import get_activity_by_name
from core.enums import ActivityName, Mode
from data.activity_ref import ActivityRef
from data.spot import Spot
from providers.spot.websocket_spot_provider import WebsocketSpotProvider
logger = logging.getLogger(__name__)
class XOTA(WebsocketSpotProvider):
"""Spot provider for servers based on the "xOTA" software at https://github.com/nischu/xOTA/
@@ -17,12 +21,17 @@ class XOTA(WebsocketSpotProvider):
is why we also provide an activity_ref_prefix in our config. This is applied to the reference ID, so e.g. "T-01"
at C3 might become "C3 T-01". This allows us to provide location lookups for TOTA at several conferences."""
ACTIVITY = None
ACTIVITY: ActivityName | None = None
def __init__(self, provider_config):
name = provider_config.get("name", "xOTA")
super().__init__(name, provider_config, provider_config["url"])
self.ACTIVITY = str(provider_config["activity"]) if "activity" in provider_config else None
found_activity = get_activity_by_name(provider_config.get("activity"))
self.ACTIVITY = found_activity.name if found_activity else None
if not self.ACTIVITY:
logger.error(
"XOTA provider has no activity reference, this is a config problem - your config needs to specify a known activity type!"
)
self._activity_ref_prefix = (
str(provider_config["activity_ref_prefix"]) if "activity_ref_prefix" in provider_config else ""
)
@@ -31,20 +40,20 @@ class XOTA(WebsocketSpotProvider):
string = b.decode("utf-8")
source_spot = json.loads(string)
ref_id = f"{self._activity_ref_prefix} {source_spot['reference']['title']}"
activity = self.ACTIVITY
activities = []
activity_refs = []
if activity is not None:
activities = [activity]
activity_refs = [ActivityRef(id=ref_id, activity=activity, url=source_spot["reference"]["website"])]
spot = Spot(
source=self.name,
source_id=source_spot["id"],
dx_call=source_spot["stationCallSign"].upper(),
freq=float(source_spot["freq"]) * 1000,
mode=Mode.from_name(source_spot["mode"].upper()),
activities=[self.ACTIVITY] if self.ACTIVITY else [],
activity_refs=[
ActivityRef(
id=ref_id,
activity=self.ACTIVITY or "",
url=source_spot["reference"]["website"],
)
],
activities=activities,
activity_refs=activity_refs,
time=datetime.now(pytz.UTC).timestamp(),
qrt=source_spot["state"] != "active",
)
+1 -1
View File
@@ -77,7 +77,7 @@
</div>
<script src="/static/js/add-spot.js?v=1790319283"></script>
<script src="/static/js/add-spot.js?v=1790324213"></script>
<script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -85,7 +85,7 @@
</div>
<script src="/static/js/alerts.js?v=1790319283"></script>
<script src="/static/js/alerts.js?v=1790324213"></script>
<script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -76,8 +76,8 @@
</div>
<script src="/static/js/spotsbandsandmap.js?v=1790319283"></script>
<script src="/static/js/bands.js?v=1790319283"></script>
<script src="/static/js/spotsbandsandmap.js?v=1790324213"></script>
<script src="/static/js/bands.js?v=1790324213"></script>
<script>$(document).ready(function () {
$("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script>
+5 -5
View File
@@ -1,6 +1,6 @@
{% extends "skeleton.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/css/style.css?v=1790319283" type="text/css">
<link rel="stylesheet" href="/static/css/style.css?v=1790324213" type="text/css">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -16,10 +16,10 @@
window.fetchEventSource = fetchEventSource;
</script>
<script src="/static/js/utils.js?v=1790319283"></script>
<script src="/static/js/ui-ham.js?v=1790319283"></script>
<script src="/static/js/geo.js?v=1790319283"></script>
<script src="/static/js/common.js?v=1790319283"></script>
<script src="/static/js/utils.js?v=1790324213"></script>
<script src="/static/js/ui-ham.js?v=1790324213"></script>
<script src="/static/js/geo.js?v=1790324213"></script>
<script src="/static/js/common.js?v=1790324213"></script>
{% end %}
{% block body %}
<div class="container">
+1 -1
View File
@@ -284,7 +284,7 @@
</div>
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/static/js/conditions.js?v=1790319283"></script>
<script src="/static/js/conditions.js?v=1790324213"></script>
<script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -113,8 +113,8 @@
const CARTODB_API_KEY = "{{ web_ui_options.get('cartodb_api_key', '') }}";
</script>
<script src="/static/js/spotsbandsandmap.js?v=1790319283"></script>
<script src="/static/js/map.js?v=1790319283"></script>
<script src="/static/js/spotsbandsandmap.js?v=1790324213"></script>
<script src="/static/js/map.js?v=1790324213"></script>
<script>$(document).ready(function () {
$("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -125,8 +125,8 @@
</div>
<script src="/static/js/spotsbandsandmap.js?v=1790319283"></script>
<script src="/static/js/spots.js?v=1790319283"></script>
<script src="/static/js/spotsbandsandmap.js?v=1790324213"></script>
<script src="/static/js/spots.js?v=1790324213"></script>
<script>$(document).ready(function () {
$("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -96,7 +96,7 @@
</div>
</div>
<script src="/static/js/status.js?v=1790319283"></script>
<script src="/static/js/status.js?v=1790324213"></script>
<script>
$(document).ready(function () {
$("#nav-link-status").addClass("active");
+6 -7
View File
@@ -7,7 +7,7 @@ from tornado import httputil
from tornado.web import Application
from core.activity_lookup_helper import populate_missing_activity_ref_info
from core.activity_utils import get_activity_by_name, get_ref_regex_for_activity
from core.activity_utils import get_activity_by_name
from core.call_lookup_helper import get_call_info
from core.geo_utils import (
lat_lon_for_grid_sw_corner_plus_size,
@@ -82,12 +82,11 @@ class APILookupActivityRefHandler(tornado.web.RequestHandler):
# "activity" and "id" query params must exist, the activity must be known, and if we have a reference regex for
# that activity, the provided id must match it.
if "activity" in query_params and "id" in query_params:
activity = str(query_params.get("activity")).upper()
found_activity = get_activity_by_name(str(query_params.get("activity")))
ref_id = str(query_params.get("id")).upper()
if get_activity_by_name(activity):
if not get_ref_regex_for_activity(activity) or re.match(
get_ref_regex_for_activity(activity), ref_id
):
if found_activity is not None:
activity = found_activity.name
if not found_activity.ref_regex or re.match(found_activity.ref_regex, ref_id):
data = populate_missing_activity_ref_info(ActivityRef(id=ref_id, activity=activity))
self.write(safe_json_dumps(data))
@@ -99,7 +98,7 @@ class APILookupActivityRefHandler(tornado.web.RequestHandler):
)
self.set_status(422)
else:
self.write(safe_json_dumps(f"Error - activity '{activity}' is not known."))
self.write(safe_json_dumps(f"Error - activity '{query_params.get('activity')}' is not known."))
self.set_status(422)
else:
self.write(safe_json_dumps("Error - activity and id must be provided"))