-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from wmo-raf/develop
Implement adding custom datasets to geomanager datasets list via custom wagtail hooks
- Loading branch information
Showing
18 changed files
with
1,069 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
41
sandbox/home/migrations/0005_stationspage_stationsettings.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
), | ||
] |
35 changes: 35 additions & 0 deletions
35
sandbox/home/migrations/0006_stationsettings_geomanager_layer_metadata_and_more.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.