Skip to content

Commit

Permalink
Completed report charts implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
davewalker5 committed Feb 5, 2022
1 parent b45811a commit fca18a2
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 32 deletions.
6 changes: 4 additions & 2 deletions src/naturerec_model/logic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from .species_status_ratings import create_species_status_rating, get_species_status_rating, \
list_species_status_ratings, close_species_status_rating
from .job_statuses import create_job_status, complete_job_status, list_job_status
from .reports import location_individuals_report, location_days_report, save_report_barchart
from .reports import location_individuals_report, location_days_report, save_report_barchart, \
get_report_barchart_base64


__all__ = [
Expand Down Expand Up @@ -44,5 +45,6 @@
"list_job_status",
"location_individuals_report",
"location_days_report",
"save_report_barchart"
"save_report_barchart",
"get_report_barchart_base64"
]
44 changes: 42 additions & 2 deletions src/naturerec_model/logic/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
"""

import datetime
import base64
import uuid
import tempfile
import os
import pandas as pd
import matplotlib.pyplot as plt
from ..model import Engine, Sighting

# Need to do this to prevent it from attempting to start the Matplotlib GUI
import matplotlib

matplotlib.use('Agg')

import matplotlib.pyplot as plt


def location_individuals_report(from_date, location_id, category_id, to_date=None):
"""
Expand Down Expand Up @@ -74,7 +84,7 @@ def location_days_report(from_date, location_id, category_id, to_date=None):

def save_report_barchart(report_df, y_column_name, x_label, y_label, title, image_path, x_column_name=None):
"""
Export a PNG image containing the data for a report
Export a PNG image containing a report barchart
:param report_df: Report dataframe
:param y_column_name: Name of the column containing the Y-axis values
Expand Down Expand Up @@ -106,3 +116,33 @@ def save_report_barchart(report_df, y_column_name, x_label, y_label, title, imag

# Save to the specified file in PNG format
plt.savefig(image_path, format='png', dpi=300)


def get_image_base64(image_path):
"""
Given the path to an image file, read it and return the base-64 representation of its contents
:param image_path: Path to the image file to read
:return: Base-64 representation of the image
"""
with open(image_path, mode="rb") as f:
return base64.b64encode(f.read())


def get_report_barchart_base64(report_df, y_column_name, x_label, y_label, title, x_column_name=None):
"""
Return the base-64 representation of a PNG image containing a report barchart
:param report_df: Report dataframe
:param y_column_name: Name of the column containing the Y-axis values
:param x_label: X-axis label
:param y_label: Y-axis label
:param title: TItle
:param x_column_name: Name of the column containing the X-axis labels or None to use the index
:return: String containing the Base-64 representation of the barchart
"""
image_path = os.path.join(tempfile.gettempdir(), f"{str(uuid.uuid4())}.png")
save_report_barchart(report_df, y_column_name, x_label, y_label, title, image_path, x_column_name)
barchart_base64 = get_image_base64(image_path).decode("utf-8")
os.unlink(image_path)
return barchart_base64
40 changes: 28 additions & 12 deletions src/naturerec_web/reports/reports_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
"""

from flask import Blueprint, render_template, request
from naturerec_model.logic import list_locations
from naturerec_model.logic import list_categories
from naturerec_model.logic import location_individuals_report, location_days_report
from naturerec_model.logic import list_locations, get_location
from naturerec_model.logic import list_categories, get_category
from naturerec_model.logic import location_individuals_report, location_days_report, get_report_barchart_base64
from naturerec_model.model import Sighting
from naturerec_web.request_utils import get_posted_date, get_posted_int

reports_bp = Blueprint("reports", __name__, template_folder='templates')


def _render_location_report_page(title, report_generator=None, from_date=None, to_date=None, location_id=None,
category_id=None):
def _render_location_report_page(title, y_label=None, report_generator=None, from_date=None, to_date=None,
location_id=None, category_id=None):
"""
Helper to show a location-based reporting page
:param title: Title of the report
:param y_lable: Y-axis label of the report barchart
:param report_generator: Report generator method
:param from_date: From date for the reporting period
:param to_date: To date for the reporting period
Expand All @@ -29,20 +30,34 @@ def _render_location_report_page(title, report_generator=None, from_date=None, t
to_date_string = to_date.strftime(Sighting.DATE_DISPLAY_FORMAT) if to_date else ""

if report_generator and from_date and location_id and category_id:
report = report_generator(from_date=from_date, to_date=to_date, location_id=location_id,
category_id=category_id)
# Generate the report
report_df = report_generator(from_date=from_date, to_date=to_date, location_id=location_id,
category_id=category_id)

# Create a barchart from the report
barchart_base64 = get_report_barchart_base64(report_df, "Count", "Species", y_label, title, None)

# Get the location and category details
location = get_location(location_id)
category = get_category(category_id)
else:
report = None
report_df = None
barchart_base64 = None
location = None
category = None

return render_template("reports/location_report.html",
title=title,
locations=list_locations(),
categories=list_categories(),
category_id=category_id,
category=category,
location_id=location_id,
location=location,
from_date=from_date_string,
to_date=to_date_string,
report=report)
report=report_df,
chart=barchart_base64)


@reports_bp.route("/location/individuals", methods=["GET", "POST"])
Expand All @@ -59,8 +74,8 @@ def individuals_by_species_and_location():
to_date = get_posted_date("to_date")
location_id = get_posted_int("location")
category_id = get_posted_int("category")
return _render_location_report_page(title, location_individuals_report, from_date, to_date, location_id,
category_id)
return _render_location_report_page(title, "Individuals", location_individuals_report, from_date, to_date,
location_id, category_id)
else:
return _render_location_report_page(title)

Expand All @@ -78,6 +93,7 @@ def sightings_by_species_and_location():
to_date = get_posted_date("to_date")
location_id = get_posted_int("location")
category_id = get_posted_int("category")
return _render_location_report_page(title, location_days_report, from_date, to_date, location_id, category_id)
return _render_location_report_page(title, "Sightings", location_days_report, from_date, to_date, location_id,
category_id)
else:
return _render_location_report_page(title)
30 changes: 16 additions & 14 deletions src/naturerec_web/reports/templates/reports/location_report.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@
{% set index_column_name = "Species" %}

{% block content %}
<form method="post">
<h1>{{ title }}</h1>
<div class="filter-criteria">
{% include "location_selector.html" with context %}
{% include "category_selector.html" with context %}
{% include "date_range_selector.html" with context %}
<div class="button-bar">
<button type="submit" value="generate" class="btn btn-primary">Generate Report</button>
</div>
</div>
{% if report.index | length > 0 %}
<p>{{ report.index | length }} record{% if report.index | length > 1 %}s{% endif %} found</p>
<img src="data:image/png;base64,{{chart}}" alt="{{ title }}" width="100%" />
{% include "reports/report.html" with context %}
{% elif report %}
<span>The report contains no results</span>
{% else %}
<form method="post">
<h1>{{ title }}</h1>
<div class="filter-criteria">
{% include "location_selector.html" with context %}
{% include "category_selector.html" with context %}
{% include "date_range_selector.html" with context %}
<div class="button-bar">
<button type="submit" value="generate" class="btn btn-primary">Generate Report</button>
</div>
</div>
{% if report.empty %}
<span>The report contains no results</span>
{% endif %}
</form>
{% endif %}
</form>
{% endblock %}
5 changes: 5 additions & 0 deletions src/naturerec_web/reports/templates/reports/report.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{% if report.index | length > 0 %}
<div align="center">
Location = {{ location.name }}, Category = {{ category.name }}<br/>
{{ report.index | length }} record{% if report.index | length > 1 %}s{% endif %} found<br/><br/>

<table class="striped">
<thead>
<tr>
Expand All @@ -19,4 +23,5 @@
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
11 changes: 9 additions & 2 deletions tests/naturerec_model/logic/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import os
from src.naturerec_model.model import create_database, Gender, get_data_path
from src.naturerec_model.logic import create_category, create_species, create_location, create_sighting
from src.naturerec_model.logic import location_individuals_report, location_days_report, save_report_barchart
from src.naturerec_model.logic import location_individuals_report, location_days_report, save_report_barchart, \
get_report_barchart_base64


class TestLocations(unittest.TestCase):
Expand All @@ -27,7 +28,7 @@ def test_can_get_location_sightings_report(self):
self.assertTrue("Black-Headed Gull" in report_df.index)
self.assertEqual(1, report_df.loc["Black-Headed Gull", "Count"])

def test_can_export_report_image(self):
def test_can_export_report_barchart(self):
report_df = location_individuals_report(datetime.date(2021, 12, 1), self._location.id, self._category.id)
image_name = f"{str(uuid.uuid4())}.png"
image_path = os.path.join(get_data_path(), "exports", image_name)
Expand All @@ -36,3 +37,9 @@ def test_can_export_report_image(self):
image_path, None)
self.assertTrue(os.path.exists(image_path))
os.unlink(image_path)

def test_can_get_report_barchart_base64(self):
report_df = location_individuals_report(datetime.date(2021, 12, 1), self._location.id, self._category.id)
base64 = get_report_barchart_base64(report_df, "Count", "Species", "Individuals",
"Individuals by Species and Location", None)
self.assertTrue(len(base64) > 0)

0 comments on commit fca18a2

Please sign in to comment.