Skip to content

Commit

Permalink
Merge pull request #16 from wmo-raf/dev
Browse files Browse the repository at this point in the history
Updates for integration
  • Loading branch information
erick-otenyo committed Mar 15, 2024
2 parents 2e20788 + 78e54f9 commit 9c2ba23
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 145 deletions.
7 changes: 7 additions & 0 deletions forecastmanager/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _

# extracted from https://nrkno.github.io/yr-weather-symbols/
Expand Down Expand Up @@ -183,3 +184,9 @@
WEATHER_CONDITION_CHOICES = [(condition['id'], _(condition['name'])) for condition in WEATHER_CONDITIONS]

WEATHER_CONDITIONS_AS_DICT = {condition['id']: condition for condition in WEATHER_CONDITIONS}

WEATHER_CONDITION_ICONS = [
{"value": condition["id"], **condition,
'icon_url': static("forecastmanager/weathericons/{0}.png".format(condition["id"]))} for condition in
WEATHER_CONDITIONS
]
6 changes: 6 additions & 0 deletions forecastmanager/forecast_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ def data_parameter_values(self):
def periods_as_choices(self):
return [(period.id, period.label) for period in self.periods.all()]

@property
def effective_periods(self):
return [
{"label": period.label, "time": period.forecast_effective_time, "default": period.default}
for period in self.periods.all()]

@property
def weather_conditions_list(self):
weather_conditions = self.weather_conditions.all()
Expand Down
6 changes: 5 additions & 1 deletion forecastmanager/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
)


class ForecastForm(WagtailAdminModelForm):
class ForecastCreateForm(WagtailAdminModelForm):
data = forms.JSONField(widget=forms.HiddenInput)
replace_existing = forms.BooleanField(required=False, initial=True, label=_("Replace existing data if found"))

Expand Down Expand Up @@ -179,3 +179,7 @@ def clean(self):
cleaned_data["data"] = cities

return cleaned_data


class ForecastEditForm(WagtailAdminModelForm):
pass
1 change: 0 additions & 1 deletion forecastmanager/management/commands/generate_forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ def handle(self, *args, **options):
city_forecast = CityForecast(city=city, condition=condition_obj)
for key, value in data_values.get("parameters", {}).items():
if parameters_dict.get(key) is None:
logger.warning(f"parameter: {key} not mapped set in Forecast Settings")
continue

parameter = parameters_dict[key]
Expand Down
16 changes: 16 additions & 0 deletions forecastmanager/migrations/0018_delete_dailyweather.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 4.2.3 on 2024-03-15 06:34

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('forecastmanager', '0017_forecastsetting_weather_detail_page'),
]

operations = [
migrations.DeleteModel(
name='DailyWeather',
),
]
33 changes: 33 additions & 0 deletions forecastmanager/migrations/0019_city_slug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.3 on 2024-03-15 07:48

import django_extensions.db.fields
from django.db import migrations

from forecastmanager.models import City


def migrate_data_forward(apps, schema_editor):
for instance in City.objects.all():
print(f"Generating slug for {instance}")
instance.save()


class Migration(migrations.Migration):
dependencies = [
('forecastmanager', '0018_delete_dailyweather'),
]

operations = [
migrations.AddField(
model_name='city',
name='slug',
field=django_extensions.db.fields.AutoSlugField(blank=True, default=None, editable=False, null=True,
populate_from='name', unique=True),
preserve_default=False
),
migrations.RunPython(
migrate_data_forward,
migrations.RunPython.noop
),

]
45 changes: 6 additions & 39 deletions forecastmanager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@

from django.contrib.gis.db import models
from django.utils.translation import gettext_lazy as _
from django_extensions.db.fields import AutoSlugField
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.admin.panels import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.admin.panels import FieldPanel, InlinePanel
from wagtail.api.v2.utils import get_full_url
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Orderable
from wagtailgeowidget import geocoders
from wagtailgeowidget.helpers import geosgeometry_str_to_struct
from wagtailgeowidget.panels import LeafletPanel, GeoAddressPanel

from .blocks import ExtremeBlock
from .forecast_settings import (
ForecastPeriod,
WeatherCondition,
ForecastDataParameters
)
from .forms import ForecastForm
from .forms import ForecastCreateForm


class City(models.Model):
Expand All @@ -29,6 +28,7 @@ class City(models.Model):
help_text=_("Unique UUID. Auto generated on creation."),
)
name = models.CharField(verbose_name=_("City Name"), max_length=255, null=True, blank=False, unique=True)
slug = AutoSlugField(populate_from='name', null=True, unique=True, default=None, editable=False)
location = models.PointField(verbose_name=_("City Location (Lat, Lng)"))
panels = [
GeoAddressPanel("name", geocoder=geocoders.NOMINATIM),
Expand All @@ -43,10 +43,6 @@ class Meta:
def __str__(self):
return self.name

@property
def clean_name(self):
return self.name.replace(" ", "--")

@property
def coordinates(self):
location = geosgeometry_str_to_struct(str(self.location))
Expand All @@ -62,7 +58,7 @@ def y(self):


class Forecast(ClusterableModel):
base_form_class = ForecastForm
base_form_class = ForecastCreateForm

FORECAST_SOURCE_CHOICES = [
("local", _("NMHSs Forecast")),
Expand Down Expand Up @@ -95,6 +91,7 @@ def get_geojson(self, request=None):
features.append(city_forecast.get_geojson_feature(request))
return {
"type": "FeatureCollection",
"date": self.forecast_date,
"features": features,
}

Expand Down Expand Up @@ -193,33 +190,3 @@ def parsed_value(self):
@property
def value_with_units(self):
return f"{self.parsed_value}{self.parameter.parameter_info.get('unit')}"


class DailyWeather(models.Model):
issued_on = models.DateField(auto_now_add=True, null=True)
forecast_date = models.DateField(_("Forecast Date"), auto_now=False, auto_now_add=False)
forecast_desc = RichTextField(verbose_name=_('Weather Forecast Description'))
summary_date = models.DateField(_("Summary Date"), auto_now=False, auto_now_add=False)
summary_desc = RichTextField(verbose_name=_('Weather Summary Description'))
extreme_date = models.DateField(_("Extreme Date"), auto_now=False, auto_now_add=False, null=True, blank=True)
extremes = StreamField([
('extremes', ExtremeBlock())
], use_json_field=True)

panels = [
MultiFieldPanel([
FieldPanel('summary_date'),
FieldPanel('summary_desc'),
], heading="Weather Summary"),
MultiFieldPanel([
FieldPanel('forecast_date'),
FieldPanel('forecast_desc'),
], heading="Weather Forecast"),
MultiFieldPanel([
FieldPanel('extreme_date'),
FieldPanel('extremes')
], heading="Extremes")
]

def __str__(self):
return f'Daily Weather - Issued on {self.issued_on.strftime("%Y-%m-%d")}'
6 changes: 1 addition & 5 deletions forecastmanager/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@

class CitySerializer(serializers.ModelSerializer):
coordinates = serializers.SerializerMethodField()
clean_name = serializers.SerializerMethodField()

class Meta:
model = City
fields = ('id', 'name', 'coordinates', "clean_name")
fields = ('id', 'name', 'coordinates', "slug")

def get_coordinates(self, obj):
return obj.coordinates

def get_clean_name(self, obj):
return obj.clean_name


class ForecastSerializer(serializers.ModelSerializer):
class Meta:
Expand Down
99 changes: 99 additions & 0 deletions forecastmanager/templates/forecastmanager/edit_forecast.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n wagtailadmin_tags static i18n %}
{% block titletag %}{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}View {{ snippet_type_name }}
{% endblocktrans %}{% endblock %}
{% block content %}
{% include 'wagtailadmin/shared/headers/slim_header.html' %}

{% trans "View" as new_str %}
{% include "wagtailadmin/shared/header.html" with title=new_str subtitle=model_opts.verbose_name icon=header_icon merged=1 only %}


<div class="nice-padding">
<div class="forecast-day-table">


<h1 class="title">
{{ object.forecast_date|date:"l j F" }} - {{ object.effective_period.label }}
</h1>


<div class="w-field__input" data-field-input=""
style="padding: 40px 0;display: flex;flex-direction: column; justify-content: center">
<label for="city_filter" style="margin-bottom: 20px;font-weight: bold;font-size: 16px">
{% trans "Filter by City name" %}
</label>
<input type="text" id="city_filter">
</div>

<div class="table-wrapper">
<table class="listing" id="forecast-table">
<thead>
<tr>
<th>{% trans "City" %} </th>
<th>{% trans "Condition" %} </th>
{% for param in weather_parameters %}
<th>{{ param.name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for city_forecast in object.city_forecasts.all %}
<tr>
<td class="title city-name">
<h2>{{ city_forecast.city.name }}</h2>
</td>
<td class="weather-condition">
<img style="height: 70px;width: 70px" src="{{ city_forecast.condition.icon_url }}"
alt="">
</td>
{% for param in weather_parameters %}
{% for data in city_forecast.data_values.all %}
{% if data.parameter.parameter == param.parameter %}
<td>{{ data.value_with_units }}</td>
{% endif %}
{% endfor %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>



{% endblock %}

{% block extra_css %}
{{ block.super }}
{{ media.css }}

<style>
.forecast-day-table .listing thead th {
font-size: 18px;
font-weight: 600
}

</style>

{% endblock %}
{% block extra_js %}
{{ block.super }}
{% include "wagtailadmin/pages/_editor_js.html" %}
{{ media.js }}

<script>
$(document).ready(function () {
$("#city_filter").on("keyup", function () {
const value = $(this).val().toLowerCase();
$("#forecast-table tbody tr").filter(function () {
$(this).toggle($(this).find(".city-name").text().toLowerCase().indexOf(value) > -1)
});
});
});
</script>


{% endblock %}
10 changes: 9 additions & 1 deletion forecastmanager/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
from django.urls import path

from .views import CityListView, ForecastListView, download_forecast_template
from .views import (
CityListView,
ForecastListView,
download_forecast_template,
weather_icons,
forecast_settings
)

urlpatterns = [
path('api/cities', CityListView.as_view(), name='cities-list'),
path('api/forecasts', ForecastListView.as_view(), name='forecast-list'),
path('api/forecast-settings', forecast_settings, name='forecast-settings'),
path('api/weather-icons', weather_icons, name='weather-icons'),
path('api/forecast_templace.csv', download_forecast_template, name='download-forecast-template'),
]
28 changes: 28 additions & 0 deletions forecastmanager/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

from django.contrib.gis.geos import Point
from django.http import HttpResponse
from django.http import JsonResponse
from django.shortcuts import render, redirect
from django.urls import reverse
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.generics import ListAPIView
from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
from wagtail.api.v2.utils import get_full_url

from forecastmanager.models import City, Forecast
from .constants import WEATHER_CONDITION_ICONS
from .forecast_settings import ForecastSetting
from .forms import CityLoaderForm
from .serializers import CitySerializer, ForecastSerializer
Expand Down Expand Up @@ -46,6 +49,12 @@ def get_queryset(self):

start_date = self.request.query_params.get('start_date')
end_date = self.request.query_params.get('end_date')
effective_time = self.request.query_params.get('effective_time')

if effective_time:
queryset = queryset.filter(effective_period__forecast_effective_time=effective_time)
else:
queryset = queryset.filter(effective_period__default=True)

if start_date:
queryset = queryset.filter(forecast_date__gte=start_date)
Expand Down Expand Up @@ -129,3 +138,22 @@ def load_cities(request):
context.update({"form": form})

return render(request, template_name=template, context=context)


def forecast_settings(request):
context = {}

fm_settings = ForecastSetting.for_request(request)
data_parameters = fm_settings.data_parameter_values
effective_periods = fm_settings.effective_periods

context.update({"parameters": data_parameters, "periods": effective_periods})

return JsonResponse(context)


def weather_icons(request):
options = WEATHER_CONDITION_ICONS
icons = [{"id": icon["id"], "name": icon["name"], "url": get_full_url(request, icon["icon_url"])} for icon in
options]
return JsonResponse(icons, safe=False)
Loading

0 comments on commit 9c2ba23

Please sign in to comment.