from django import forms
from django.db.models import Min
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
import pandas as pd
import plotly.graph_objects as go
from dashboards.component import Form, Map, Text
from dashboards.component.chart import ChartSerializer
from dashboards.component.layout import Card, ComponentLayout
from dashboards.dashboard import Dashboard
from dashboards.forms import DashboardForm
from dashboards.registry import registry
from example.packages.models import Package, PackageStage
def get_package(filters):
_id = (filters or {}).get("package_id")
qs = Package.objects
if _id:
qs = qs.filter(id=_id)
return qs.first()
class PackageForm(DashboardForm):
package_id = forms.ModelChoiceField(
queryset=Package.objects.all(),
help_text=_("Select a package id to view its details and map below"),
empty_label=None,
)
def get_detail_table(*args, filters=None, **kwargs):
package = get_package(filters)
if not package:
content = "<p>Select a package to see it's details.</p>"
else:
stages = (
PackageStage.objects.filter(location__package=package)
.exclude(description="Current position")
.order_by("time_offset")
)
_now = now()
rows = map(
lambda s: (
"<tr>"
+ f"<td style='text-align: left; padding-right: 1em'>{s.location.label}</td>"
+ f"<td style='text-align: left; padding-right: 1em'>{s.description}</td>"
+ f"<td style='white-space: nowrap'>{(_now + s.time_offset).strftime('%d/%m/%y %H:%M')}</td>"
+ "</tr>"
),
stages,
)
content = f"<table class='table'><tbody>{''.join(rows)}</tbody></table>"
return f"<h2>Package detail</h2>{content}"
class PackageMapSerializer(ChartSerializer):
def get_data(self, *args, filters=None, **kwargs) -> pd.DataFrame:
package = get_package(filters)
if not package:
return None
_now = now()
locations = package.locations.annotate(
first_stage_date=Min("stages__time_offset")
).order_by("first_stage_date")
return pd.DataFrame(
[
{
"lat": location.location.y,
"lon": location.location.x,
"text": f"{location.label}<br><br>"
+ "<br>".join(
f"{stage.description}: {(_now + stage.time_offset).strftime('%d/%m/%y %H:%M')}"
for stage in location.stages.order_by("time_offset")
),
}
for location in locations
]
)
def to_fig(self, data):
fig = go.Figure()
if data is None:
fig.add_trace(go.Scattergeo())
else:
fig.add_trace(
go.Scattergeo(
lon=data["lon"],
lat=data["lat"],
hoverinfo="text",
text=data["text"],
mode="lines+markers",
)
)
fig.update_layout(
geo={
"lonaxis": {
"range": [min(data["lon"]) - 10, max(data["lon"]) + 10],
},
"lataxis": {
"range": [min(data["lat"]) - 10, max(data["lat"]) + 10],
},
}
)
return fig
class PackagesMapDashboard(Dashboard):
package_form = Form(form=PackageForm, dependents=["package_map", "package_details"])
package_details = Text(value=get_detail_table, mark_safe=True)
package_map = Map(value=PackageMapSerializer)
class Meta:
name = "Package Map"
class Layout(Dashboard.Layout):
components = ComponentLayout(
Card("package_form", grid_css_classes="span-12"),
Card("package_details", grid_css_classes="span-6"),
Card("package_map", grid_css_classes="span-6"),
)
registry.register(PackagesMapDashboard)