Skip to content

Commit

Permalink
Merge pull request #11 from wmo-raf/develop
Browse files Browse the repository at this point in the history
Implement adding custom datasets to geomanager datasets list via custom wagtail hooks
  • Loading branch information
erick-otenyo authored Nov 10, 2023
2 parents 2eccd70 + a26f24a commit 9a8da33
Show file tree
Hide file tree
Showing 18 changed files with 1,069 additions and 45 deletions.
2 changes: 1 addition & 1 deletion geomanager/models/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import uuid

from django.db import models
from django.urls.base import get_script_prefix, reverse
from django.urls.base import reverse
from django.utils.translation import gettext_lazy as _
from django_extensions.db.models import TimeStampedModel
from modelcluster.fields import ParentalKey
Expand Down
8 changes: 8 additions & 0 deletions geomanager/viewsets/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
from rest_framework import mixins, viewsets
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from wagtail import hooks
from wagtailcache.cache import cache_page

from geomanager import serializers
from geomanager.models import Dataset
from geomanager.models.core import GeomanagerSettings, Metadata

geomanager_register_datasets_hook_name = "register_geomanager_datasets"


class DatasetViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = Dataset.objects.filter(published=True)
Expand Down Expand Up @@ -55,6 +58,11 @@ def list(self, request, *args, **kwargs):
"metadata": lm_settings.cap_metadata.pk if lm_settings.cap_metadata else None
}})

for fn in hooks.get_hooks(geomanager_register_datasets_hook_name):
hook_datasets = fn(request)
for dataset in hook_datasets:
datasets.append(dataset)

return Response({"datasets": datasets, "config": config})


Expand Down
27 changes: 27 additions & 0 deletions sandbox/home/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from django import forms
from django.utils.translation import gettext_lazy as _


class StationsUploadForm(forms.Form):
shp_zip = forms.FileField(required=True, label=_("Stations Shapefile ZIP"),
widget=forms.FileInput(attrs={'accept': '.zip'}))


class StationColumnsForm(forms.Form):
columns = forms.JSONField(required=False, widget=forms.HiddenInput)
name_column = forms.ChoiceField(required=False, label=_("Station name field"))

def __init__(self, *args, **kwargs):
column_choices = None
if "column_choices" in kwargs:
column_choices = kwargs.get("column_choices")
kwargs.pop("column_choices")

super().__init__(*args, **kwargs)

if column_choices:
choices = [("", "--------")]
choices.extend(column_choices)
self.fields['name_column'].choices = choices
else:
self.fields['name_column'].widget = forms.HiddenInput()
41 changes: 41 additions & 0 deletions sandbox/home/migrations/0005_stationspage_stationsettings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 4.1.10 on 2023-11-09 10:40

from django.db import migrations, models
import django.db.models.deletion
import geomanager.fields
import wagtail.contrib.routable_page.models


class Migration(migrations.Migration):

dependencies = [
('wagtailcore', '0089_log_entry_data_json_null_to_object'),
('home', '0004_delete_stationspage'),
]

operations = [
migrations.CreateModel(
name='StationsPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=(wagtail.contrib.routable_page.models.RoutablePageMixin, 'wagtailcore.page'),
),
migrations.CreateModel(
name='StationSettings',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('columns', models.JSONField(blank=True, null=True)),
('geom_type', models.CharField(blank=True, max_length=100, null=True)),
('bounds', geomanager.fields.ListField(blank=True, max_length=256, null=True)),
('name_column', models.CharField(blank=True, max_length=100, null=True)),
('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.site')),
],
options={
'abstract': False,
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.1.10 on 2023-11-10 08:47

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('geomanager', '0030_delete_stationsettings'),
('home', '0005_stationspage_stationsettings'),
]

operations = [
migrations.AddField(
model_name='stationsettings',
name='geomanager_layer_metadata',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='geomanager.metadata', verbose_name='Stations Layer Metadata'),
),
migrations.AddField(
model_name='stationsettings',
name='geomanager_subcategory',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='geomanager.subcategory', verbose_name='Stations Layer SubCategory'),
),
migrations.AddField(
model_name='stationsettings',
name='layer_title',
field=models.CharField(blank=True, default='Stations', max_length=100, null=True, verbose_name='Stations Layer Title'),
),
migrations.AddField(
model_name='stationsettings',
name='show_on_mapviewer',
field=models.BooleanField(default=False, help_text='Check to show stations data on Mapviewer', verbose_name='Show on Mapviewer'),
),
]
208 changes: 208 additions & 0 deletions sandbox/home/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,216 @@
from django.contrib.gis.db import models
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from django_tables2 import tables, LazyPaginator, TemplateColumn
from wagtail.admin.panels import FieldPanel
from wagtail.contrib.routable_page.models import path, RoutablePageMixin
from wagtail.contrib.settings.models import BaseSiteSetting
from wagtail.contrib.settings.registry import register_setting
from wagtail.models import Page
from wagtailcache.cache import cache_page

from geomanager.fields import ListField
from geomanager.models import SubCategory, Metadata
from geomanager.utils.vector_utils import get_model_field


@method_decorator(cache_page, name="serve")
class HomePage(Page):
pass


@register_setting
class StationSettings(BaseSiteSetting):
stations_table_name = "home_station"
db_schema = "public"

columns = models.JSONField(blank=True, null=True)
geom_type = models.CharField(max_length=100, blank=True, null=True)
bounds = ListField(max_length=256, blank=True, null=True)
name_column = models.CharField(max_length=100, blank=True, null=True)

show_on_mapviewer = models.BooleanField(default=False, verbose_name=_("Show on Mapviewer"),
help_text=_("Check to show stations data on Mapviewer"))
layer_title = models.CharField(max_length=100, blank=True, null=True, default="Stations",
verbose_name=_("Stations Layer Title"))
geomanager_subcategory = models.ForeignKey(SubCategory, null=True, blank=True,
verbose_name=_("Stations Layer SubCategory"),
on_delete=models.SET_NULL)
geomanager_layer_metadata = models.ForeignKey(Metadata, on_delete=models.SET_NULL, blank=True, null=True,
verbose_name=_("Stations Layer Metadata"))

panels = [
FieldPanel("show_on_mapviewer"),
FieldPanel("layer_title"),
FieldPanel("geomanager_subcategory"),
FieldPanel("geomanager_layer_metadata"),
]

@cached_property
def full_table_name(self):
return f"{self.db_schema}.{self.stations_table_name}"

@cached_property
def stations_vector_tiles_url(self):
base_url = reverse("station_tiles", args=(0, 0, 0)).replace("/0/0/0", r"/{z}/{x}/{y}")
return base_url

def get_station_model(self):
fields = self.station_fields_factory()

attrs = {
**fields,
"managed": False,
"__module__": "home"
}

station_model = type("Station", (models.Model,), attrs)

return station_model

def station_fields_factory(self):
geom_type = self.geom_type or "Point"
fields = {
"geom": get_model_field(geom_type)()
}

if isinstance(self.columns, list):
for column in self.columns:
data_type = column.get("data_type")
name = column.get("name")
label = column.get("label") or name
if data_type:
model_field = get_model_field(column.get("data_type"))

if model_field:
field_kwargs = {"verbose_name": label}
if name == "gid":
field_kwargs.update({"primary_key": True})
fields.update({name: model_field(**field_kwargs)})

return fields

@cached_property
def station_columns_list(self):
station_columns = []
if self.columns and isinstance(self.columns, list):
for column in self.columns:
name = column.get("name")
if name:
station_columns.append(name)
return station_columns

@cached_property
def station_table_columns_list(self):
table_columns = []
if self.columns and isinstance(self.columns, list):
for column in self.columns:
name = column.get("name")
label = column.get("label")
table = column.get("table")
if name and table:
table_columns.append({"name": name, "label": label})
return table_columns

@cached_property
def station_popup_columns_list(self):
popup_columns = []
if self.columns and isinstance(self.columns, list):
for column in self.columns:
name = column.get("name")
label = column.get("label")
popup = column.get("popup")
if name and popup:
popup_columns.append({"name": name, "label": label})
return popup_columns


class StationsPage(RoutablePageMixin, Page):
template = "stations/stations_list_page.html"
parent_page_types = ["home.HomePage"]
subpage_types = []
max_count = 1

content_panels = Page.content_panels

@path('')
def all_stations(self, request, *args, **kwargs):
context = {}
station_settings = StationSettings.for_request(request)

stations_vector_tiles_url = request.scheme + '://' + request.get_host() + station_settings.stations_vector_tiles_url

context.update({
"mapConfig": {
"stationBounds": station_settings.bounds or [],
"stationsVectorTilesUrl": stations_vector_tiles_url,
},
})

# get stations model
station_model = station_settings.get_station_model()

# get all columns
station_table_columns_list = station_settings.station_table_columns_list

table_fields = [field.get("name") for field in station_table_columns_list]

page_url = request.build_absolute_uri(self.url)

class StationTable(tables.Table):
detail_url = TemplateColumn('<a href="" target="_blank"></a>')

class Meta:
model = station_model
fields = table_fields

def render_detail_url(self, value, record):
record_pk_suffix = str(record.gid)
if page_url and not page_url.endswith("/"):
record_pk_suffix = f"/{record_pk_suffix}"
url = page_url + record_pk_suffix
return format_html(
"<a href='{}'>View detail</a>",
url
)

stations_table = StationTable(station_model.objects.all())

try:
stations_table.paginate(page=request.GET.get("page", 1), per_page=50, paginator_class=LazyPaginator)
except Exception as e:
print(e)
stations_table = None

context.update({
"stations_table": stations_table,
"popup_fields": station_settings.station_popup_columns_list
})

return self.render(request, context_overrides={**context})

@path('<int:station_pk>/')
def station_detail(self, request, station_pk):
station_settings = StationSettings.for_request(request)

# get stations model
station_model = station_settings.get_station_model()

station = station_model.objects.filter(pk=station_pk)

if station.exists:
station = station.first()
else:
station = None

context = {
"station": station,
"columns": station_settings.columns,
"bounds": station_settings.bounds,
"station_name_column": station_settings.name_column
}

return self.render(request, template="stations/station_detail_page.html", context_overrides=context)
28 changes: 28 additions & 0 deletions sandbox/home/static/css/stations_list_page.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.stations-map {
height: 600px;
width: 100%;
}

@media only screen and (max-width: 600px) {
.stations-map {
height: 400px;
}
}

.station-popup-content {
display: flex;
flex-wrap: wrap;
flex-direction: column;
}

.station-popup-content p {
word-break: break-all;
white-space: normal;
margin: 0 !important;
padding-bottom: 4px;
}


.stations-list table td a {
text-decoration: underline;
}
Loading

0 comments on commit 9a8da33

Please sign in to comment.