diff --git a/docker/Dockerfile b/docker/Dockerfile index 005667f..71c1bb5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,15 +1,15 @@ FROM python:3.10-slim-bullseye AS runtime -COPY naturerecorderpy-1.0.17.0 /opt/naturerecorderpy-1.0.17.0 +COPY naturerecorderpy-1.0.18.0 /opt/naturerecorderpy-1.0.18.0 -WORKDIR /opt/naturerecorderpy-1.0.17.0 +WORKDIR /opt/naturerecorderpy-1.0.18.0 RUN apt-get update -y RUN pip install -r requirements.txt -RUN pip install nature_recorder-1.0.17-py3-none-any.whl +RUN pip install nature_recorder-1.0.18-py3-none-any.whl -ENV NATURE_RECORDER_DATA_FOLDER=/var/opt/naturerecorderpy-1.0.17.0 -ENV NATURE_RECORDER_DB=/var/opt/naturerecorderpy-1.0.17.0/naturerecorder.db +ENV NATURE_RECORDER_DATA_FOLDER=/var/opt/naturerecorderpy-1.0.18.0 +ENV NATURE_RECORDER_DB=/var/opt/naturerecorderpy-1.0.18.0/naturerecorder.db ENTRYPOINT [ "python" ] CMD [ "-m", "naturerec_web" ] diff --git a/requirements.txt b/requirements.txt index c443907..501b978 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,10 +11,12 @@ click==8.0.3 ConfigArgParse==1.5.3 coverage==6.2 cryptography==36.0.1 +cycler==0.11.0 docutils==0.17.1 Flask==2.0.2 Flask-BasicAuth==0.2.0 Flask-Cors==3.0.10 +fonttools==4.29.1 gevent==21.12.0 geventhttpclient==1.5.3 greenlet==1.1.2 @@ -23,8 +25,10 @@ idna==3.3 imagesize==1.3.0 itsdangerous==2.0.1 Jinja2==3.0.3 +kiwisolver==1.3.2 locust==2.5.1 MarkupSafe==2.0.1 +matplotlib==3.5.1 msgpack==1.0.3 numpy==1.21.5 outcome==1.1.0 @@ -34,6 +38,7 @@ parse==1.19.0 parse-type==0.5.2 pdfkit==1.0.0 pgeocode==0.3.0 +Pillow==9.0.1 psutil==5.9.0 pycountry==20.7.3 pycparser==2.21 diff --git a/setup.py b/setup.py index 86d5d5a..a862428 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def find_package_files(directory, remove_root): setuptools.setup( name="nature_recorder", - version="1.0.17", + version="1.0.18", description="Wildlife sightings database", packages=setuptools.find_packages("src"), include_package_data=True, diff --git a/src/naturerec_model/logic/__init__.py b/src/naturerec_model/logic/__init__.py index ce16898..332291f 100644 --- a/src/naturerec_model/logic/__init__.py +++ b/src/naturerec_model/logic/__init__.py @@ -7,7 +7,7 @@ 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 +from .reports import location_individuals_report, location_days_report, save_report_barchart __all__ = [ @@ -43,5 +43,6 @@ "complete_job_status", "list_job_status", "location_individuals_report", - "location_days_report" + "location_days_report", + "save_report_barchart" ] diff --git a/src/naturerec_model/logic/reports.py b/src/naturerec_model/logic/reports.py index 4a8fc9d..7b48e20 100644 --- a/src/naturerec_model/logic/reports.py +++ b/src/naturerec_model/logic/reports.py @@ -4,6 +4,7 @@ import datetime import pandas as pd +import matplotlib.pyplot as plt from ..model import Engine, Sighting @@ -69,3 +70,39 @@ def location_days_report(from_date, location_id, category_id, to_date=None): f"GROUP BY sp.Name" return pd.read_sql(sql_query, Engine).set_index("Species") + + +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 + + :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 image_path: Output image path + :param x_column_name: Name of the column containing the X-axis labels or None to use the index + """ + + # Set up the X and Y axes + x = report_df[x_column_name] if x_column_name else report_df.index + y = report_df[y_column_name] + x_pos = [i for i, _ in enumerate(x)] + + # Configure the style and plot type + plt.style.use('ggplot') + plt.bar(x_pos, y, color='green') + + # Set up the axes and title + plt.xlabel(x_label) + plt.xticks(x_pos, x) + plt.xticks(rotation=90) + plt.ylabel(y_label) + plt.title(title) + + # This prevents the X-labels from going over the edge of the plot + plt.tight_layout() + + # Save to the specified file in PNG format + plt.savefig(image_path, format='png', dpi=300) diff --git a/tests/naturerec_model/logic/test_reports.py b/tests/naturerec_model/logic/test_reports.py index 41483ff..478ef41 100644 --- a/tests/naturerec_model/logic/test_reports.py +++ b/tests/naturerec_model/logic/test_reports.py @@ -1,8 +1,10 @@ import unittest import datetime -from src.naturerec_model.model import create_database, Gender +import uuid +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 +from src.naturerec_model.logic import location_individuals_report, location_days_report, save_report_barchart class TestLocations(unittest.TestCase): @@ -24,3 +26,13 @@ def test_can_get_location_sightings_report(self): self.assertEqual(1, len(report_df.index)) 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): + 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) + self.assertFalse(os.path.exists(image_path)) + save_report_barchart(report_df, "Count", "Species", "Individuals", "Individuals by Species and Location", + image_path, None) + self.assertTrue(os.path.exists(image_path)) + os.unlink(image_path)