From d4b15249fef19c9c83b0d8c30330ba3364354d16 Mon Sep 17 00:00:00 2001 From: Dave Gaeddert Date: Fri, 5 Jul 2024 16:32:47 -0500 Subject: [PATCH] save --- bolt-charts/LICENSE | 28 +++++++ bolt-charts/README.md | 3 + bolt-charts/bolt/charts/README.md | 1 + bolt-charts/bolt/charts/__init__.py | 0 bolt-charts/pyproject.toml | 21 ++++++ bolt-staff/bolt/staff/admin/cards/charts.py | 74 ++++++++++++++++++- .../staff/admin/templates/admin/base.html | 9 ++- .../admin/templates/admin/cards/trend.html | 5 ++ bolt/bolt/templates/README.md | 2 +- 9 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 bolt-charts/LICENSE create mode 100644 bolt-charts/README.md create mode 100644 bolt-charts/bolt/charts/README.md create mode 100644 bolt-charts/bolt/charts/__init__.py create mode 100644 bolt-charts/pyproject.toml create mode 100644 bolt-staff/bolt/staff/admin/templates/admin/cards/trend.html diff --git a/bolt-charts/LICENSE b/bolt-charts/LICENSE new file mode 100644 index 0000000000..e5391c216b --- /dev/null +++ b/bolt-charts/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Dropseed, LLC + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bolt-charts/README.md b/bolt-charts/README.md new file mode 100644 index 0000000000..0ff88a7fae --- /dev/null +++ b/bolt-charts/README.md @@ -0,0 +1,3 @@ + + +# bolt-charts diff --git a/bolt-charts/bolt/charts/README.md b/bolt-charts/bolt/charts/README.md new file mode 100644 index 0000000000..5fc1bcd99f --- /dev/null +++ b/bolt-charts/bolt/charts/README.md @@ -0,0 +1 @@ +# bolt-charts diff --git a/bolt-charts/bolt/charts/__init__.py b/bolt-charts/bolt/charts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bolt-charts/pyproject.toml b/bolt-charts/pyproject.toml new file mode 100644 index 0000000000..3b6f193a5e --- /dev/null +++ b/bolt-charts/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] + +name = "bolt-charts" +packages = [ + { include = "bolt" }, +] + +version = "0.0.0" +description = "" +authors = ["Dave Gaeddert "] +license = "MIT" +# readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +plotly = ">5.0.0" +pandas = "*" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/bolt-staff/bolt/staff/admin/cards/charts.py b/bolt-staff/bolt/staff/admin/cards/charts.py index 5bbf98477f..c1b8e0e1f1 100644 --- a/bolt-staff/bolt/staff/admin/cards/charts.py +++ b/bolt-staff/bolt/staff/admin/cards/charts.py @@ -1,6 +1,78 @@ import datetime - +import plotly.express as px +import pandas as pd from .base import Card +from bolt.db.models import Count +from bolt.db.models.functions import TruncDate + +class Chart: + def render_html(self): + config = { + "displayModeBar": False, + "scrollZoom": False, + "responsive": True, + # "staticPlot": True, + } + return self.get_figure().to_html(full_html=False, config=config)#, include_plotlyjs=False) + + def render_image(self): + return self.get_figure().to_image(format="png") + + def __html__(self): + return self.render_html() + + +class BarChart(Chart): + def __init__(self, *, dataframe, x, y): + self.dataframe = dataframe + self.x = x + self.y = y + + def get_figure(self): + fig = px.bar(self.dataframe, x=self.x, y=self.y) + return fig + + + +class TrendCard(Card): + template_name = "admin/cards/trend.html" + + model = None + trend_field = "created_at" + + # default behavior can be querysets and models? + # override if custom objects, but rare? + + def get_template_context(self): + context = super().get_template_context() + context["chart"] = self.get_chart() + return context + + def get_chart(self): + filters = { + f"{self.trend_field}__range": self.datetime_range.as_tuple(), + } + data = ( + self.model.objects.filter(**filters) + .annotate(date=TruncDate(self.trend_field)) + .values("date") + .annotate(count=Count("id")) + .order_by("date") + ) + + dataframe = pd.DataFrame.from_records( + data, + columns=["date", "count"], + ) + + # fill the zeroes for the missing dates + dataframe = dataframe.set_index("date").reindex(self.datetime_range).fillna(0).reset_index() + + return BarChart( + dataframe=dataframe, + x="date", + y="count", + ) class ChartCard(Card): diff --git a/bolt-staff/bolt/staff/admin/templates/admin/base.html b/bolt-staff/bolt/staff/admin/templates/admin/base.html index 950d4f0324..071a439317 100644 --- a/bolt-staff/bolt/staff/admin/templates/admin/base.html +++ b/bolt-staff/bolt/staff/admin/templates/admin/base.html @@ -67,7 +67,7 @@ {{ request.user }} - Log out + Log out
@@ -145,6 +145,13 @@

+ {# + +
+
+
+
+ #}
{% for card in cards %}
diff --git a/bolt-staff/bolt/staff/admin/templates/admin/cards/trend.html b/bolt-staff/bolt/staff/admin/templates/admin/cards/trend.html new file mode 100644 index 0000000000..e54fb6a54c --- /dev/null +++ b/bolt-staff/bolt/staff/admin/templates/admin/cards/trend.html @@ -0,0 +1,5 @@ +{% extends "admin/cards/base.html" %} + +{% block content %} +{{ chart }} +{% endblock %} diff --git a/bolt/bolt/templates/README.md b/bolt/bolt/templates/README.md index b64e022454..4c6f4f7100 100644 --- a/bolt/bolt/templates/README.md +++ b/bolt/bolt/templates/README.md @@ -1,6 +1,6 @@ # Templates -Render HTML templates using Jinja. +Render templates using Jinja2. Templates are typically rendered in `TemplateViews`, but you can also render them directly to strings for emails or other use cases.