Skip to content

Commit

Permalink
DBC22-1804: populate data, serializer, and starting frontend logic
Browse files Browse the repository at this point in the history
  • Loading branch information
tyrel-oxd committed Mar 2, 2024
1 parent e1ba9bf commit 4aa26c6
Show file tree
Hide file tree
Showing 24 changed files with 546 additions and 16 deletions.
120 changes: 120 additions & 0 deletions src/backend/apps/feed/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import logging
from pprint import pprint
from typing import Dict
from urllib.parse import urljoin

import httpx
import requests
from apps.feed.constants import (
CURRENT_WEATHER,
CURRENT_WEATHER_STATIONS,
DIT,
INLAND_FERRY,
OPEN511,
Expand All @@ -14,6 +17,7 @@
)
from apps.feed.serializers import (
CarsClosureEventSerializer,
CurrentWeatherSerializer,
EventAPISerializer,
EventFeedSerializer,
FerryAPISerializer,
Expand All @@ -25,6 +29,23 @@
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response

SERIALIZER_TO_DATASET_MAPPING = {
"air_temperature": ("air_temp", "Air Temp"),
"road_temperature": ("sfc_temp", "Pavement Temp"),
"precipitation": ("pcpn_amt_pst1hr", "Precip Hourly"),
"snow": ("snwfl_amt_pst1hr", "Snowfall (hourly)"),
"wind_direction": ("wnd_dir", "Wind Direction (current)"),
"average_wind": ("wnd_spd", "Wind Speed (current)"),
"maximum_wind": ("max_wnd_spd_pst1hr", "Wind Speed (max)")
}

DATASETNAMES = [value[0] for value in SERIALIZER_TO_DATASET_MAPPING.values()]

DISPLAYNAME_MAPPING = {value[0]: value[1] for value in SERIALIZER_TO_DATASET_MAPPING.values()}

SERIALIZER_MAPPING = {value[0]: key for key, value in SERIALIZER_TO_DATASET_MAPPING.items()}


logger = logging.getLogger(__name__)


Expand All @@ -51,6 +72,12 @@ def __init__(self):
REGIONAL_WEATHER_AREAS: {
"base_url": settings.DRIVEBC_WEATHER_AREAS_API_BASE_URL,
},
CURRENT_WEATHER: {
"base_url": settings.DRIVEBC_WEATHER_CURRENT_API_BASE_URL,
},
CURRENT_WEATHER_STATIONS: {
"base_url": settings.DRIVEBC_WEATHER_CURRENT_STATIONS_API_BASE_URL
}
}

def _get_auth_headers(self, resource_type):
Expand Down Expand Up @@ -323,3 +350,96 @@ def get_regional_weather_list(self):
REGIONAL_WEATHER, 'regionalweather', RegionalWeatherSerializer,
{"format": "json", "limit": 500}
)

# Current Weather
def get_current_weather_list_feed(self, resource_type, resource_name, serializer_cls, params=None):
"""Get data feed for list of objects."""
area_code_endpoint = settings.DRIVEBC_WEATHER_CURRENT_STATIONS_API_BASE_URL
# Obtain Access Token
token_url = settings.DRIVEBC_WEATHER_API_TOKEN_URL
client_id = settings.WEATHER_CLIENT_ID
client_secret = settings.WEATHER_CLIENT_SECRET

token_params = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
}

try:
response = requests.post(token_url, data=token_params)
response.raise_for_status()
token_data = response.json()
access_token = token_data.get("access_token")
except requests.RequestException as e:
return Response({"error": f"Error obtaining access token: {str(e)}"}, status=500)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
external_api_url = area_code_endpoint
headers = {"Authorization": f"Bearer {access_token}"}
try:
response = requests.get(external_api_url, headers=headers)
response.raise_for_status()
data = response.json()
json_response = data
json_objects = []

for station in json_response:
station_number = station["WeatherStationNumber"]
api_endpoint = settings.DRIVEBC_WEATHER_CURRENT_API_BASE_URL + f"{station_number}"
# Reget access token in case the previous token expired
try:
response = requests.post(token_url, data=token_params)
response.raise_for_status()
token_data = response.json()
access_token = token_data.get("access_token")
except requests.RequestException as e:
return Response({"error": f"Error obtaining access token: {str(e)}"}, status=500)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
headers = {"Authorization": f"Bearer {access_token}"}
try:
response = requests.get(api_endpoint, headers=headers)
data = response.json()
datasets = data.get("Datasets") if data else None
elevation = data.get('WeatherStation').get("Elevation")
Longitude = data.get('WeatherStation').get("Longitude")
Latitude = data.get('WeatherStation').get("Latitude")
weather_station_name = data.get('WeatherStation').get("WeatherStationName")
location_description = data.get('WeatherStation').get("LocationDescription")
# filtering down dataset to just SensorTypeName and DataSetName
filtered_dataset = {}
if datasets is not None:
for dataset in datasets:
if (dataset["DataSetName"] in DATASETNAMES
and DISPLAYNAME_MAPPING[dataset["DataSetName"]] == dataset["DisplayName"]):
filtered_dataset[SERIALIZER_MAPPING[dataset["DataSetName"]]] = {
"value": dataset["Value"], "unit": dataset["Unit"],
}

current_weather_data = {
'weather_station_name': weather_station_name,
'elevation': elevation,
'location_description': location_description,
'datasets': filtered_dataset,
'location_longitude': Longitude,
'location_latitude': Latitude,

}
serializer = serializer_cls(data=current_weather_data,
many=isinstance(current_weather_data, list))
json_objects.append(current_weather_data)
except requests.RequestException as e:
print(f"Error making API call for Area Code {station_number}: {e}")
try:
serializer.is_valid(raise_exception=True)
pprint(json_objects)
return json_objects
except (KeyError, ValidationError):
field_errors = serializer.errors
for field, errors in field_errors.items():
print(f"Field: {field}, Errors: {errors}")
except requests.RequestException as e:
return Response({"error": f"Error fetching data from weather API: {str(e)}"}, status=500)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

def get_current_weather_list(self):
return self.get_current_weather_list_feed(
CURRENT_WEATHER, 'currentweather', CurrentWeatherSerializer,
{"format": "json", "limit": 500}
)
4 changes: 3 additions & 1 deletion src/backend/apps/feed/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
INLAND_FERRY = "inland_ferry"
REGIONAL_WEATHER = "regional_weather"
REGIONAL_WEATHER_AREAS = "regional_weather_areas"
CURRENT_WEATHER = "current_weather"
CURRENT_WEATHER_STATIONS = "current_weather_stations"

DIRECTIONS = {
'in both directions': 'BOTH',
'northbound': 'N',
'southbound': 'S',
'eastbound': 'E',
'westbound': 'W',
}
}
17 changes: 16 additions & 1 deletion src/backend/apps/feed/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
WebcamRegionField,
WebcamRegionGroupField,
)
from apps.weather.models import RegionalWeather
from apps.weather.models import CurrentWeather, RegionalWeather
from rest_framework import serializers


Expand Down Expand Up @@ -189,3 +189,18 @@ class Meta:
'forecast_group',
'hourly_forecast_group',
)


# Current Weather serializer
class CurrentWeatherSerializer(serializers.Serializer):
class Meta:
model = CurrentWeather
fields = (
'id',
'location_latitude',
'location_longitude',
'weather_station_name',
'elevation',
'location_description',
'datasets'
)
3 changes: 2 additions & 1 deletion src/backend/apps/weather/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from apps.weather.models import RegionalWeather
from apps.weather.models import CurrentWeather, RegionalWeather
from django.contrib import admin
from django.contrib.admin import ModelAdmin

Expand All @@ -8,3 +8,4 @@ class WeatherAdmin(ModelAdmin):


admin.site.register(RegionalWeather, WeatherAdmin)
admin.site.register(CurrentWeather, WeatherAdmin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from apps.weather.tasks import populate_all_current_weather_data
from django.core.management.base import BaseCommand


class Command(BaseCommand):
def handle(self, *args, **options):
populate_all_current_weather_data()
14 changes: 14 additions & 0 deletions src/backend/apps/weather/management/commands/test_current.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pprint import pprint

from apps.weather.models import CurrentWeather
from apps.weather.serializers import CurrentWeatherSerializer
from django.core.management.base import BaseCommand


class Command(BaseCommand):

def handle(self, *args, **options):
c = CurrentWeather.objects.get(id=275)
serializer = CurrentWeatherSerializer(c)
serializer = serializer.data
pprint(serializer)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 4.2.3 on 2024-02-29 18:55

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


class Migration(migrations.Migration):

replaces = [('weather', '0001_initial'), ('weather', '0002_rename_location_code_regionalweather_code_and_more'), ('weather', '0003_rename_location_name_regionalweather_name'), ('weather', '0004_currentweather'), ('weather', '0005_alter_currentweather_datasets')]

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='RegionalWeather',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('code', models.CharField(max_length=10, null=True)),
('location_latitude', models.CharField(max_length=10, null=True)),
('location_longitude', models.CharField(max_length=10, null=True)),
('name', models.CharField(max_length=100, null=True)),
('region', models.CharField(max_length=255, null=True)),
('observation_name', models.CharField(max_length=50, null=True)),
('observation_zone', models.CharField(max_length=10, null=True)),
('observation_utc_offset', models.IntegerField(null=True)),
('observation_text_summary', models.CharField(max_length=255, null=True)),
('conditions', models.JSONField(null=True)),
('forecast_group', models.JSONField(null=True)),
('hourly_forecast_group', models.JSONField(null=True)),
('location', django.contrib.gis.db.models.fields.PointField(null=True, srid=4326)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='CurrentWeather',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('weather_station_name', models.CharField(max_length=100)),
('elevation', models.IntegerField(null=True)),
('location_description', models.TextField(null=True)),
('datasets', models.JSONField(default=[], null=True)),
],
options={
'abstract': False,
},
),
]
28 changes: 28 additions & 0 deletions src/backend/apps/weather/migrations/0004_currentweather.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.3 on 2024-02-22 23:26

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('weather', '0003_rename_location_name_regionalweather_name'),
]

operations = [
migrations.CreateModel(
name='CurrentWeather',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('weather_station_name', models.CharField(max_length=100)),
('elevation', models.IntegerField(null=True)),
('location_description', models.TextField(null=True)),
('datasets', models.JSONField(default=[])),
],
options={
'abstract': False,
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.3 on 2024-02-26 17:55

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('weather', '0004_currentweather'),
]

operations = [
migrations.AlterField(
model_name='currentweather',
name='datasets',
field=models.JSONField(default=[], null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.3 on 2024-02-29 23:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('weather', '0001_squashed_0005_alter_currentweather_datasets'),
]

operations = [
migrations.AddField(
model_name='currentweather',
name='location_latitude',
field=models.CharField(max_length=10, null=True),
),
migrations.AddField(
model_name='currentweather',
name='location_longitude',
field=models.CharField(max_length=10, null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.3 on 2024-03-01 23:17

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('weather', '0006_currentweather_location_latitude_and_more'),
]

operations = [
migrations.AlterField(
model_name='currentweather',
name='location_latitude',
field=models.CharField(max_length=20, null=True),
),
migrations.AlterField(
model_name='currentweather',
name='location_longitude',
field=models.CharField(max_length=20, null=True),
),
]
Loading

0 comments on commit 4aa26c6

Please sign in to comment.