import datetime
from django.db.models import Case, Count, F, IntegerField, Value, When
from django.utils.timezone import now
import numpy as np
import plotly.express as px
from dashboards.component import Chart, Table
from dashboards.component.chart import ChartSerializer
from dashboards.component.layout import Card, ComponentLayout
from dashboards.component.table import TableSerializer
from dashboards.dashboard import Dashboard
from dashboards.registry import registry
from example.packages.models import Package, PackageStage
class PackageChartSerializer(ChartSerializer):
class Meta:
fields = ["state", "count"]
model = Package
title = "State"
def get_queryset(self, *args, **kwargs):
return (
Package.objects.values("state")
.annotate(
count=Count("pk"),
order=Case(
When(state=Package.PackageStates.received, then=0),
When(state=Package.PackageStates.in_transit, then=1),
When(state=Package.PackageStates.delivered, then=2),
output_field=IntegerField(),
),
)
.order_by("order")
)
def to_fig(self, df):
fig = px.bar(
df,
x="state",
y="count",
text_auto=True,
)
fig.update_layout(xaxis_title=None, yaxis_title=None, hovermode=False)
return fig
class WeightVsPriceChartSerializer(ChartSerializer):
class Meta:
fields = ["weight", "price", "id"]
model = Package
title = "Parcel Weight Vs Price"
def get_queryset(self, *args, **kwargs):
return Package.objects.filter(state=Package.PackageStates.delivered)
def to_fig(self, df):
df["price"] = df["price"].astype(float)
df["weight"] = df["weight"].astype(float)
df["profit"] = df["Price"] = np.random.uniform(
low=1, high=df["price"] - 1, size=len(df)
)
df["percent_profit"] = round(df["profit"] / df["price"] * 100, 2)
fig = px.scatter(
df,
y="weight",
x="price",
size="percent_profit",
color="weight",
hover_name="id",
)
fig.update_layout(
yaxis_title="Weight (Kilos)",
xaxis_title="Price (£)",
)
return fig
class DeliveryScheduleChartSerializer(ChartSerializer):
class Meta:
fields = ["location__country", "status", "count"]
model = Package
title = "Delivery Schedule"
def get_queryset(self, *args, **kwargs):
current_date = now()
qs = (
PackageStage.objects.filter(location__label="Destination")
.annotate(
actual_datetime=current_date - F("time_offset"),
expected_datetime=current_date - F("expected_offset"),
status=Case(
When(
actual_datetime__lt=F("expected_datetime__date"),
then=Value("Early"),
),
When(
actual_datetime__date=F("expected_datetime__date"),
then=Value("On time"),
),
When(
actual_datetime__date=F("expected_datetime__date")
+ datetime.timedelta(days=1),
then=Value("24 hrs delay"),
),
When(
actual_datetime__date=F("expected_datetime__date")
+ datetime.timedelta(days=2),
then=Value("48 hrs delay"),
),
When(
actual_datetime__date__gt=F("expected_datetime__date")
+ datetime.timedelta(days=2),
then=Value("Significantly delay"),
),
),
)
.values("location__country", "status")
.annotate(
count=Count("pk"),
)
)
return qs.order_by("location__country")[0:25]
def to_fig(self, df):
df["status"] = df["status"].astype(str)
fig = px.bar(
df, y="location__country", x="count", color="status", orientation="h"
)
fig.update_layout(
yaxis_title="Country",
xaxis_title=None,
)
return fig
class PackagesReceivedTableSerializer(TableSerializer):
def get_queryset(self, *args, **kwargs):
return Package.objects.filter(state=Package.PackageStates.received)
class Meta:
columns = {
"id": "#ID",
"weight": "Weight (kg)",
"price": "Price",
}
order = ["id"]
class PackagesDashboard(Dashboard):
package_chart = Chart(value=PackageChartSerializer)
weight_vs_price = Chart(value=WeightVsPriceChartSerializer)
received_table = Table(value=PackagesReceivedTableSerializer)
delivery_schedule = Chart(value=DeliveryScheduleChartSerializer)
class Meta:
name = "Packages"
class Layout(Dashboard.Layout):
components = ComponentLayout(
Card("package_chart", grid_css_classes="span-6"),
Card(
"received_table",
heading="Awaiting Shipping",
grid_css_classes="span-6",
),
Card("weight_vs_price", grid_css_classes="span-12"),
Card("delivery_schedule", grid_css_classes="span-12"),
)
registry.register(PackagesDashboard)