diff --git a/.gitignore b/.gitignore index 5c7c742..090fa60 100644 --- a/.gitignore +++ b/.gitignore @@ -142,8 +142,9 @@ cython_debug/ .idea/ reports/exported/ -naturerecorder.db +naturerecorder_test.db naturerecorder_dev.db +naturerecorder.db clean.sh clean.bat diff --git a/README.md b/README.md index f99567d..bf2a99a 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,7 @@ Nature Recorder is an application for recording wildlife sightings, maintaining - Conservation status ratings, values for the conservation status within a scheme - Species conservation status ratings, status ratings for a species with effective start and end dates - Reports - - Simple reports built into the application - - Flexible reporting using Jupyter Notebooks, outside the application + - Flexible reporting using Jupyter Notebooks # Getting Started diff --git a/docker/Dockerfile b/docker/Dockerfile index 6ce5da3..a646bbd 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,12 @@ FROM python:3.10-slim-bullseye AS runtime -COPY naturerecorderpy-1.14.0.0 /opt/naturerecorderpy +COPY naturerecorderpy-1.15.0.0 /opt/naturerecorderpy WORKDIR /opt/naturerecorderpy RUN apt-get update -y RUN pip install -r requirements.txt -RUN pip install nature_recorder-1.14.0-py3-none-any.whl +RUN pip install nature_recorder-1.15.0-py3-none-any.whl ENV NATURE_RECORDER_DATA_FOLDER=/var/opt/naturerecorderpy ENV NATURE_RECORDER_DB=/var/opt/naturerecorderpy/naturerecorder.db diff --git a/docs/source/naturerec_model/data_exchange/index.rst b/docs/source/naturerec_model/data_exchange/index.rst index a7d8917..47bdbe4 100644 --- a/docs/source/naturerec_model/data_exchange/index.rst +++ b/docs/source/naturerec_model/data_exchange/index.rst @@ -10,4 +10,3 @@ The data_exchange Package status_import_helper sightings_import_helper sightings_export_helper - life_list_export_helper diff --git a/docs/source/naturerec_model/data_exchange/life_list_export_helper.rst b/docs/source/naturerec_model/data_exchange/life_list_export_helper.rst deleted file mode 100644 index 173bd33..0000000 --- a/docs/source/naturerec_model/data_exchange/life_list_export_helper.rst +++ /dev/null @@ -1,5 +0,0 @@ -life_list_export_helper.py -========================== - -.. automodule:: naturerec_model.data_exchange.life_list_export_helper - :members: diff --git a/docs/source/naturerec_model/logic/index.rst b/docs/source/naturerec_model/logic/index.rst index 298a2d9..79ef7f0 100644 --- a/docs/source/naturerec_model/logic/index.rst +++ b/docs/source/naturerec_model/logic/index.rst @@ -12,5 +12,4 @@ The logic Package status_schemes status_ratings species_status_ratings - reports job_statuses diff --git a/docs/source/naturerec_model/logic/reports.rst b/docs/source/naturerec_model/logic/reports.rst deleted file mode 100644 index 9dfd806..0000000 --- a/docs/source/naturerec_model/logic/reports.rst +++ /dev/null @@ -1,5 +0,0 @@ -reports.py -========== - -.. automodule:: naturerec_model.logic.reports - :members: diff --git a/docs/source/naturerec_web/index.rst b/docs/source/naturerec_web/index.rst index 986dfc0..7316a80 100644 --- a/docs/source/naturerec_web/index.rst +++ b/docs/source/naturerec_web/index.rst @@ -10,10 +10,8 @@ The naturerec_web Package categories_blueprint species_blueprint sightings_blueprint - life_list_blueprint status_blueprint species_ratings_blueprint export_blueprint jobs_blueprint - reports_blueprint request_utils diff --git a/docs/source/naturerec_web/life_list_blueprint.rst b/docs/source/naturerec_web/life_list_blueprint.rst deleted file mode 100644 index c55cbb3..0000000 --- a/docs/source/naturerec_web/life_list_blueprint.rst +++ /dev/null @@ -1,5 +0,0 @@ -life_list_blueprint.py -====================== - -.. automodule:: naturerec_web.life_list.life_list_blueprint - :members: diff --git a/docs/source/naturerec_web/reports_blueprint.rst b/docs/source/naturerec_web/reports_blueprint.rst deleted file mode 100644 index 97bd74e..0000000 --- a/docs/source/naturerec_web/reports_blueprint.rst +++ /dev/null @@ -1,5 +0,0 @@ -reports_blueprint.py -==================== - -.. automodule:: naturerec_web.reports.reports_blueprint - :members: diff --git a/make_venv.bat b/make_venv.bat new file mode 100644 index 0000000..0d55132 --- /dev/null +++ b/make_venv.bat @@ -0,0 +1,19 @@ +@ECHO OFF + +REM Deactivate and remove the old virtual environment, if present +ECHO Removing existing Virtual Environment, if present ... +deactivate > nul 2>&1 +RMDIR /S /Q venv + +REM Create a new environment and activate it +ECHO Creating new Virtual Environment ... +python -m venv venv +CALL venv\Scripts\activate.bat + +REM Make sure pip is up to date +python -m pip install --upgrade pip + +REM Install the requirements +python -m pip install -r requirements.txt + +ECHO ON diff --git a/make_venv.sh b/make_venv.sh new file mode 100755 index 0000000..36efaba --- /dev/null +++ b/make_venv.sh @@ -0,0 +1,17 @@ +#!/bin/bash -f + +# Deactivate and remove the old virtual environment, if present +echo "Removing existing Virtual Environment, if present ..." +deactivate 2> /dev/null || true +rm -fr venv + +# Create a new environment and activate it +echo "Creating new Virtual Environment ..." +python -m venv venv +. venv/bin/activate + +# Make sure pip is up to date +pip install --upgrade pip + +# Install the requirements +pip install -r requirements.txt diff --git a/requirements.txt b/requirements.txt index fa9fbc0..05967bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,34 +10,27 @@ click==8.0.3 colorama==0.4.6 coverage==7.8.0 cryptography==44.0.1 -cycler==0.11.0 docutils==0.17.1 Flask==2.2.5 Flask-Login==0.6.3 Flask-WTF==1.2.1 -fonttools==4.43.0 greenlet==3.0.3 h11==0.12.0 idna==3.7 imagesize==1.3.0 itsdangerous==2.0.1 Jinja2==3.1.6 -kiwisolver==1.3.2 Mako==1.2.4 MarkupSafe==2.1.2 -matplotlib==3.5.1 numpy==1.23.1 outcome==1.1.0 -packaging==21.3 pandas==1.3.5 pdfkit==1.0.0 pgeocode==0.3.0 -pillow==10.3.0 pycountry==20.7.3 pycparser==2.21 Pygments==2.15.1 pyOpenSSL==21.0.0 -pyparsing==3.0.6 python-dateutil==2.8.2 python-dotenv==0.19.2 pytz==2021.3 diff --git a/run_coverage.bat b/run_coverage.bat index 209e7a2..958f5f2 100644 --- a/run_coverage.bat +++ b/run_coverage.bat @@ -6,6 +6,7 @@ SET NATURE_RECORDER_DB=%PROJECT_ROOT%\data\naturerecorder_test.db ECHO Project root = %PROJECT_ROOT% ECHO Python Path = %PYTHONPATH% +ECHO Database Path = %NATURE_RECORDER_DB% coverage run --branch --source src -m unittest discover coverage html -d cov_html diff --git a/run_coverage.sh b/run_coverage.sh index 96bba05..515e8a7 100755 --- a/run_coverage.sh +++ b/run_coverage.sh @@ -7,7 +7,7 @@ export NATURE_RECORDER_DB="$PROJECT_ROOT/data/naturerecorder_test.db" echo "Project root = $PROJECT_ROOT" echo "Python Path = $PYTHONPATH" -echo "Test Database = $NATURE_RECORDER_DB" +echo "Database Path = $NATURE_RECORDER_DB" coverage run --branch --source src -m unittest discover coverage html -d cov_html diff --git a/run_tests.bat b/run_tests.bat index b27bee9..35ae50d 100644 --- a/run_tests.bat +++ b/run_tests.bat @@ -2,9 +2,11 @@ SET PROJECT_ROOT=%~p0 CALL %PROJECT_ROOT%\venv\Scripts\activate.bat SET PYTHONPATH=%PROJECT_ROOT%src +SET NATURE_RECORDER_DB=%PROJECT_ROOT%data\naturerecorder_test.db ECHO Project root = %PROJECT_ROOT% ECHO Python Path = %PYTHONPATH% +ECHO Database Path = %NATURE_RECORDER_DB% python -m unittest ECHO ON diff --git a/run_tests.sh b/run_tests.sh index 2d1ceb3..7e40657 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -3,8 +3,10 @@ export PROJECT_ROOT=$( cd "$( dirname "$0" )" && pwd ) . $PROJECT_ROOT/venv/bin/activate export PYTHONPATH=$PROJECT_ROOT/src +export NATURE_RECORDER_DB="$PROJECT_ROOT/data/naturerecorder_test.db" echo "Project root = $PROJECT_ROOT" echo "Python Path = $PYTHONPATH" +echo "Database Path = $NATURE_RECORDER_DB" python -m unittest diff --git a/run_web.bat b/run_web.bat index 503928a..61fd80c 100644 --- a/run_web.bat +++ b/run_web.bat @@ -1,8 +1,8 @@ @ECHO OFF -SET PROJECT_ROOT=%~p0 +SET PROJECT_ROOT=C:%~p0 CALL %PROJECT_ROOT%\venv\Scripts\activate.bat SET PYTHONPATH=%PROJECT_ROOT%src -SET NATURE_RECORDER_DB=%PROJECT_ROOT%data\naturerecorder.db +SET NATURE_RECORDER_DB=%PROJECT_ROOT%data\naturerecorder_dev.db SET FLASK_ENV=development ECHO Project root = %PROJECT_ROOT% diff --git a/run_web.sh b/run_web.sh index b429ca6..7242e8d 100755 --- a/run_web.sh +++ b/run_web.sh @@ -3,7 +3,7 @@ export PROJECT_ROOT=$( cd "$( dirname "$0" )" && pwd ) . $PROJECT_ROOT/venv/bin/activate export PYTHONPATH=$PROJECT_ROOT/src -export NATURE_RECORDER_DB=$PROJECT_ROOT/data/naturerecorder.db +export NATURE_RECORDER_DB="$PROJECT_ROOT/data/naturerecorder_dev.db" export FLASK_ENV=development echo "Project root = $PROJECT_ROOT" diff --git a/setup.py b/setup.py index b98d254..bd8306e 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.14.0", + version="1.15.0", description="Wildlife sightings database", packages=setuptools.find_packages("src"), include_package_data=True, @@ -42,9 +42,7 @@ def find_package_files(directory, remove_root): "naturerec_web.categories": ["templates/categories/*.html"], "naturerec_web.export": ["templates/export/*.html"], "naturerec_web.jobs": ["templates/jobs/*.html"], - "naturerec_web.life_list": ["templates/life_list/*.html"], "naturerec_web.locations": ["templates/locations/*.html"], - "naturerec_web.reports": ["templates/reports/*.html"], "naturerec_web.sightings": ["templates/sightings/*.html"], "naturerec_web.species": ["templates/species/*.html"], "naturerec_web.species_ratings": ["templates/species_ratings/*.html"], diff --git a/src/naturerec_model/data_exchange/__init__.py b/src/naturerec_model/data_exchange/__init__.py index 2344658..acafc2e 100644 --- a/src/naturerec_model/data_exchange/__init__.py +++ b/src/naturerec_model/data_exchange/__init__.py @@ -1,12 +1,10 @@ from .status_import_helper import StatusImportHelper from .sightings_import_helper import SightingsImportHelper from .sightings_export_helper import SightingsExportHelper -from .life_list_export_helper import LifeListExportHelper __all__ = [ "StatusImportHelper", "SightingsImportHelper", - "SightingsExportHelper", - "LifeListExportHelper" + "SightingsExportHelper" ] diff --git a/src/naturerec_model/data_exchange/life_list_export_helper.py b/src/naturerec_model/data_exchange/life_list_export_helper.py deleted file mode 100644 index bafee86..0000000 --- a/src/naturerec_model/data_exchange/life_list_export_helper.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -This module implements a helper that will export a life list to a CSV format file on a background thread -""" - -import csv -import os -from .data_exchange_helper_base import DataExchangeHelperBase -from ..model import get_data_path -from ..logic.sightings import life_list - - -class LifeListExportHelper(DataExchangeHelperBase): - JOB_NAME = "Life List export" - COLUMN_NAMES = ["Category", "Species"] - - def __init__(self, filename, category_id, user): - super().__init__(self.export, user) - self._filename = filename - self._category_id = category_id - self.create_job_status() - - def __repr__(self): - return f"{type(self).__name__}(" \ - f"filename={self._filename!r}, " \ - f"category_id={self._category_id!r})" - - def export(self): - """ - Retrieve the life list matching the criteria passed to the init method and write it to file in CSV format - """ - with open(self.get_file_export_path(), mode='wt', newline='', encoding="UTF-8") as f: - writer = csv.writer(f) - writer.writerow(self.COLUMN_NAMES) - - species = life_list(self._category_id) - for species in species: - writer.writerow([species.category.name, species.name]) - - def get_file_export_path(self): - """ - Construct and return the full path to the export file - - :return: Full path to the export file - """ - export_folder = os.path.join(get_data_path(), "exports") - if not os.path.exists(export_folder): - os.makedirs(export_folder) - - return os.path.join(export_folder, self._filename) diff --git a/src/naturerec_model/logic/__init__.py b/src/naturerec_model/logic/__init__.py index a5bef1d..8ed1e4c 100644 --- a/src/naturerec_model/logic/__init__.py +++ b/src/naturerec_model/logic/__init__.py @@ -1,14 +1,13 @@ from .categories import create_category, get_category, list_categories, update_category, delete_category from .species import create_species, get_species, list_species, update_species, delete_species from .locations import create_location, get_location, list_locations, update_location, geocode_postcode, delete_location -from .sightings import create_sighting, get_sighting, list_sightings, update_sighting, life_list, delete_sighting +from .sightings import create_sighting, get_sighting, list_sightings, update_sighting, delete_sighting from .status_schemes import create_status_scheme, get_status_scheme, list_status_schemes, update_status_scheme, \ delete_status_scheme from .status_ratings import create_status_rating, update_status_rating, delete_status_rating from .species_status_ratings import create_species_status_rating, get_species_status_rating, \ list_species_status_ratings, close_species_status_rating, delete_species_status_rating from .job_statuses import create_job_status, complete_job_status, list_job_status -from .reports import location_species_report, species_by_date_report, get_report_barchart from .users import create_user, authenticate, get_user @@ -32,7 +31,6 @@ "create_sighting", "get_sighting", "list_sightings", - "life_list", "delete_sighting", "update_sighting", "create_status_scheme", @@ -51,9 +49,6 @@ "create_job_status", "complete_job_status", "list_job_status", - "location_species_report", - "species_by_date_report", - "get_report_barchart", "create_user", "authenticate", "get_user" diff --git a/src/naturerec_model/logic/reports.py b/src/naturerec_model/logic/reports.py deleted file mode 100644 index 31df915..0000000 --- a/src/naturerec_model/logic/reports.py +++ /dev/null @@ -1,147 +0,0 @@ -""" -This module contains the business logic for the pre-defined reports -""" - -import base64 -import io -import datetime -import pandas as pd -from ..model import Engine, Sighting -import matplotlib - -# Specify Agg as a non-interactive back-end -matplotlib.use('Agg') - -import matplotlib.pyplot as plt - - -def get_report_barchart(report_df, y_column_name, x_label, y_label, title, subtitle, x_column_name=None): - """ - Get the base-64 representation of an 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 subtitle: Subtitle - :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) - - # Set the chart titles - plt.suptitle(title, fontsize=14) - plt.title(subtitle, fontsize=8) - # plt.title(title) - - # This prevents the X-labels from going over the edge of the plot - plt.tight_layout() - - # Rather than saving to a file and loading that to get its base64 representation, save to a memory buffer and - # then get the base-64 representation from that buffer - # plt.savefig(image_path, format='png', dpi=300) - buffer = io.BytesIO() - plt.savefig(buffer, format='png', dpi=300) - barchart_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') - - # And clear the plot - plt.clf() - plt.cla() - plt.close() - - return barchart_base64 - - -def location_species_report(from_date, to_date, location_id, category_id): - """ - Report on the species and sightings at a location in the specified date range - - :param from_date: Start date for reporting - :param to_date: End-date for reporting - :param location_id: Location id - :param category_id: Category id - :return: A Pandas Dataframe containing the results - """ - - # Format the dates in the format required in a SQL query - from_date_string = from_date.strftime(Sighting.DATE_FORMAT) - to_date_string = to_date.strftime(Sighting.DATE_FORMAT) - - # Construct the query - sql_query = f"SELECT sp.Name AS 'Species', " \ - f"IFNULL( sp.Scientific_Name, '' ) AS 'Scientific Name', " \ - f"COUNT( sp.Id ) AS 'Sightings', " \ - f"SUM( IFNULL( s.Number, 1 ) ) AS 'Total Individuals', " \ - f"MIN( IFNULL( s.Number, 1 ) ) AS 'Minimum Seen', " \ - f"MAX( IFNULL( s.Number, 1 ) ) AS 'Maximum Seen', " \ - f"ROUND(AVG( IFNULL( s.Number, 1 ) ), 2) AS 'Average Seen' " \ - f"FROM SIGHTINGS s " \ - f"INNER JOIN LOCATIONS l ON l.Id = s.LocationId " \ - f"INNER JOIN SPECIES sp ON sp.Id = s.SpeciesId " \ - f"INNER JOIN CATEGORIES c ON c.Id = sp.CategoryId " \ - f"WHERE s.Date BETWEEN '{from_date_string}' AND '{to_date_string}' " \ - f"AND l.Id = {location_id} " \ - f"AND c.Id = {category_id} " \ - f"GROUP BY sp.Name" - - return pd.read_sql(sql_query, Engine).set_index("Species") - - -def species_by_date_report(from_date, to_date, location_id, species_id, by_week): - """ - Return the species sightings report for a location, species and date range - - :param from_date: Start date for reporting - :param to_date: End-date for reporting - :param location_id: Location id - :param species_id: Species id - :param by_week: True to report by week number, False to report by month - :return: A Pandas Dataframe containing the results - """ - # Format the dates in the format required in a SQL query - from_date_string = from_date.strftime(Sighting.DATE_FORMAT) - to_date_string = to_date.strftime(Sighting.DATE_FORMAT) - - # Construct and execute the query - format_specifier = "W" if by_week else "m" - interval_column_name = "Week" if by_week else "Month_Number" - sql_query = f"SELECT STRFTIME( '%{format_specifier}', Date ) AS {interval_column_name}, " \ - f"COUNT( sp.Id ) AS 'Sightings', " \ - f"MIN( IFNULL( s.Number, 1 ) ) AS 'Minimum Seen'," \ - f"MAX( IFNULL( s.Number, 1 ) ) AS 'Maximum Seen', " \ - f"ROUND(AVG( IFNULL( s.Number, 1 ) ), 2) AS 'Average Seen' " \ - f"FROM SIGHTINGS s " \ - f"INNER JOIN LOCATIONS l ON l.Id = s.LocationId " \ - f"INNER JOIN SPECIES sp ON sp.Id = s.SpeciesId " \ - f"WHERE s.Date BETWEEN '{from_date_string}' AND '{to_date_string}' " \ - f"AND l.Id = {location_id} " \ - f"AND sp.Id = {species_id} " \ - f"GROUP BY STRFTIME( '%{format_specifier}', Date ), sp.Name" - report_df = pd.read_sql(sql_query, Engine) - - # If we're reporting by month, add a month name column, remove the month number column and make the month - # name the index. Otherwise, just make the week number the index - if by_week: - report_df.set_index(interval_column_name, inplace=True) - else: - report_df["Month"] = [datetime.datetime.strptime(month_number, "%m").strftime("%b") - for month_number - in report_df[interval_column_name]] - report_df.set_index("Month", inplace=True) - report_df.drop(columns=[interval_column_name], inplace=True) - - return report_df diff --git a/src/naturerec_model/logic/sightings.py b/src/naturerec_model/logic/sightings.py index c791d9e..31c746f 100644 --- a/src/naturerec_model/logic/sightings.py +++ b/src/naturerec_model/logic/sightings.py @@ -165,32 +165,6 @@ def list_sightings(from_date=None, to_date=None, location_id=None, species_id=No return sightings -def life_list(category_id): - """ - Return the "life list" of sightings for a given species category - - :param category_id: ID for the category for which to return the life list - :return: List of Species instances constituting the life list - """ - with Session.begin() as session: - # Get a distinct list of species IDs for the specified category - species_ids = [ - row.speciesId - for row in session.query(Sighting.speciesId) - .filter(Sighting.species.has(Species.categoryId == category_id)) - .distinct() - .all() - ] - - # Now query the species - species = session.query(Species) \ - .filter(Species.id.in_(species_ids)) \ - .order_by(Species.name) \ - .all() - - return species - - def delete_sighting(sighting_id): """ Delete a sighting diff --git a/src/naturerec_web/__init__.py b/src/naturerec_web/__init__.py index 52501c5..6a6ae25 100644 --- a/src/naturerec_web/__init__.py +++ b/src/naturerec_web/__init__.py @@ -10,9 +10,7 @@ from .species import species_bp from .status import status_bp from .species_ratings import species_ratings_bp -from .life_list import life_list_bp from .jobs import jobs_bp -from .reports import reports_bp from .auth import auth_bp, unauthorised, has_roles from naturerec_model.logic import get_user @@ -48,9 +46,7 @@ def create_app(environment="production"): app.register_blueprint(species_bp, url_prefix='/species') app.register_blueprint(status_bp, url_prefix='/status') app.register_blueprint(species_ratings_bp, url_prefix='/species_ratings') - app.register_blueprint(life_list_bp, url_prefix='/life_list') app.register_blueprint(jobs_bp, url_prefix='/jobs') - app.register_blueprint(reports_bp, url_prefix='/reports') app.register_blueprint(auth_bp, url_prefix='/auth') # Register the 401 Unathorised error handler diff --git a/src/naturerec_web/export/export_blueprint.py b/src/naturerec_web/export/export_blueprint.py index 7c526bf..3c4d714 100644 --- a/src/naturerec_web/export/export_blueprint.py +++ b/src/naturerec_web/export/export_blueprint.py @@ -6,7 +6,7 @@ from flask_login import login_required, current_user from naturerec_model.logic import list_locations from naturerec_model.logic import list_categories -from naturerec_model.data_exchange import SightingsExportHelper, LifeListExportHelper +from naturerec_model.data_exchange import SightingsExportHelper from naturerec_model.model import Sighting from naturerec_web.request_utils import get_posted_date, get_posted_int from naturerec_web.auth import requires_roles @@ -45,19 +45,6 @@ def _render_export_filters_page(from_date=None, edit_enabled=True) -def _render_life_list_export_filters_page(category_id=None, message=None): - """ - - :param category_id: Species category for the selected category - :param message: Message to display on the page - :return: The HTML for the rendered life list export page - """ - return render_template("export/life_list.html", - message=message, - categories=list_categories(), - category_id=category_id) - - @export_bp.route("/filters", methods=["GET", "POST"]) @login_required @requires_roles(["Administrator"]) @@ -85,28 +72,3 @@ def export(): return _render_export_filters_page(from_date, to_date, location_id, category_id, species_id, message) else: return _render_export_filters_page() - - -@export_bp.route("/life_list", methods=["GET", "POST"]) -@login_required -@requires_roles(["Administrator", "Reporter"]) -def export_life_list(): - """ - Show the page that presents filters for exporting life lists - - :return: The HTML for the life list export page - """ - if request.method == "POST": - # Get the export parameters - filename = request.form["filename"] - category_id = get_posted_int("category") - - # Kick off the export - exporter = LifeListExportHelper(filename, category_id, current_user) - exporter.start() - - # Go to the life list export page - message = "The selected life list is exporting in the background" - return _render_life_list_export_filters_page(category_id, message) - else: - return _render_life_list_export_filters_page() diff --git a/src/naturerec_web/export/templates/export/life_list.html b/src/naturerec_web/export/templates/export/life_list.html deleted file mode 100644 index 587eddf..0000000 --- a/src/naturerec_web/export/templates/export/life_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "layout.html" %} -{% block title %}Export Life List{% endblock %} - -{% block content %} -
- -

Life List

-
-
- - -
- {% include "category_selector.html" with context %} -
- -
-
- {% include "message.html" with context %} -
-{% endblock %} diff --git a/src/naturerec_web/life_list/__init__.py b/src/naturerec_web/life_list/__init__.py deleted file mode 100644 index a6237cf..0000000 --- a/src/naturerec_web/life_list/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from naturerec_web.life_list.life_list_blueprint import life_list_bp - -__all__ = [ - "life_list_bp" -] diff --git a/src/naturerec_web/life_list/life_list_blueprint.py b/src/naturerec_web/life_list/life_list_blueprint.py deleted file mode 100644 index f586114..0000000 --- a/src/naturerec_web/life_list/life_list_blueprint.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -The life list blueprint supplies view functions and templates for generating species sighting "life lists" -""" - -from flask import Blueprint, render_template, request -from flask_login import login_required -from naturerec_model.logic import list_categories, get_category -from naturerec_model.logic import life_list -from naturerec_web.auth import requires_roles -from naturerec_web.request_utils import get_posted_int - -life_list_bp = Blueprint("life_list", __name__, template_folder='templates') - - -def _render_life_list_page(category_id=None): - """ - Helper to render the life list page - - :param category_id: ID of the category for which to generate a life list - :return: Rendered life list template - """ - category = get_category(category_id) if category_id else None - species = life_list(category_id) if category_id else [] - return render_template("life_list/list.html", - categories=list_categories(), - category=category, - category_id=category_id, - species=species) - - -@life_list_bp.route("/list", methods=["GET", "POST"]) -@login_required -@requires_roles(["Administrator", "Reporter", "Reader"]) -def life_list_for_category(): - """ - Show the page that generates life lists for a given category, with category selection option - - :return: The HTML for the life list page - """ - if request.method == "POST": - return _render_life_list_page(get_posted_int("category")) - else: - return _render_life_list_page() diff --git a/src/naturerec_web/life_list/templates/life_list/list.html b/src/naturerec_web/life_list/templates/life_list/list.html deleted file mode 100644 index 27c8999..0000000 --- a/src/naturerec_web/life_list/templates/life_list/list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "layout.html" %} -{% block title %}Life List{% endblock %} - -{% block content %} -
- -

Life List

-
- {% include "category_selector.html" with context %} -
- -
-
- {% if species | length > 0 %} -

There are {{ species | length }} species in the life list for "{{ category.name }}"

- {% include "species.html" with context %} - {% elif category_id %} - There are no species in the database for the specified category - {% endif %} -
-{% endblock %} diff --git a/src/naturerec_web/reports/__init__.py b/src/naturerec_web/reports/__init__.py deleted file mode 100644 index 6a82e39..0000000 --- a/src/naturerec_web/reports/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from naturerec_web.reports.reports_blueprint import reports_bp - -__all__ = [ - "reports_bp" -] diff --git a/src/naturerec_web/reports/reports_blueprint.py b/src/naturerec_web/reports/reports_blueprint.py deleted file mode 100644 index 1e55f73..0000000 --- a/src/naturerec_web/reports/reports_blueprint.py +++ /dev/null @@ -1,208 +0,0 @@ -""" -The reports blueprint supplies view functions and templates for reporting on sightings -""" - -import datetime -import os -from flask import Blueprint, render_template, request -from flask_login import login_required -from naturerec_model.logic import list_locations, get_location -from naturerec_model.logic import list_categories, get_category -from naturerec_model.logic import get_species -from naturerec_model.logic import location_species_report, get_report_barchart, species_by_date_report -from naturerec_model.model import Sighting -from naturerec_web.auth import requires_roles -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(from_date=None, to_date=None, location_id=None, category_id=None): - """ - Helper to show a location-based reporting page - - :param from_date: From date for the reporting period - :param to_date: To date for the reporting period - :param location_id: ID for the location to report on - :param category_id: ID for the category to report on - :return: HTML for the rendered reporting page - """ - # This is done just to make the Behave tests work when the reports page opens a new tab - form_target = "target='_blank'" if "NATUREREC_SAME_PAGE_REPORT" not in os.environ else "" - - # If the to date isn't set, make to today - if not to_date: - to_date = datetime.datetime.today() - - from_date_string = from_date.strftime(Sighting.DATE_DISPLAY_FORMAT) if from_date else "" - to_date_string = to_date.strftime(Sighting.DATE_DISPLAY_FORMAT) if to_date else "" - - if from_date and location_id and category_id: - # Generate the report - report_df = location_species_report(from_date, to_date, location_id, category_id) - - # Get the location and category details and use them to construct a sub-title - location = get_location(location_id) - category = get_category(category_id) - subtitle = f"Location: {location.name}\n" \ - f"Category: {category.name} " \ - f"From: {from_date_string} " \ - f"To: {to_date_string}" - - # Create the bar charts from the report - chart_config = [ - { - "title": "Number of Sightings by Location and Species", - "y-column": "Sightings", - "y-label": "Number of Sightings" - }, - { - "title": "Total Individuals by Location and Species", - "y-column": "Total Individuals", - "y-label": "Total Individuals Seen" - }, - { - "title": "Average Individuals per Sighting", - "y-column": "Average Seen", - "y-label": "Average Individuals Seen" - } - ] - - charts = [] - for config in chart_config: - barchart_base64 = get_report_barchart(report_df, config["y-column"], "Species", config["y-label"], - config["title"], subtitle, None) - charts.append(barchart_base64) - - else: - report_df = None - charts = None - - return render_template("reports/location_report.html", - title="Location Report", - locations=list_locations(), - categories=list_categories(), - category_id=category_id, - location_id=location_id, - from_date=from_date_string, - to_date=to_date_string, - report=report_df, - charts=charts, - form_target=form_target) - - -def _render_species_report_page(from_date=None, to_date=None, location_id=None, category_id=None, species_id=None, - interval=None): - """ - Helper to show a location-based reporting page - - :param from_date: From date for the reporting period - :param to_date: To date for the reporting period - :param location_id: ID for the location to report on - :param category_id: ID for the category used to select the species - :param species_id: ID for the species to report on - :param interval: Reporting interval - :return: HTML for the rendered reporting page - """ - # This is done just to make the Behave tests work when the reports page opens a new tab - form_target = "target='_blank'" if "NATUREREC_SAME_PAGE_REPORT" not in os.environ else "" - - # If the to date isn't set, make to today - if not to_date: - to_date = datetime.datetime.today() - - from_date_string = from_date.strftime(Sighting.DATE_DISPLAY_FORMAT) if from_date else "" - to_date_string = to_date.strftime(Sighting.DATE_DISPLAY_FORMAT) if to_date else "" - - if from_date and location_id and category_id and species_id: - # Generate the report - by_week = interval.casefold() == "week" if interval else False - report_df = species_by_date_report(from_date, to_date, location_id, species_id, by_week) - - # Get the location and species details and use them to construct a sub-title - location = get_location(location_id) - species = get_species(species_id) - print(f"LATIN = {species.scientific_name}") - scientific_name = f" ({species.scientific_name})" if species.scientific_name != None else "" - subtitle = f"Location: {location.name}\n" \ - f"Species: {species.name}{scientific_name}\n" \ - f"From: {from_date_string} " \ - f"To: {to_date_string}" - - # Create the bar charts from the report - chart_config = [ - { - "title": "Number of Sightings by Location and Date", - "y-column": "Sightings", - "y-label": "Number of Sightings" - }, - { - "title": "Average Individuals per Sighting", - "y-column": "Average Seen", - "y-label": "Average Individuals Seen" - } - ] - - charts = [] - for config in chart_config: - barchart_base64 = get_report_barchart(report_df, config["y-column"], "Month", config["y-label"], - config["title"], subtitle, None) - charts.append(barchart_base64) - - else: - report_df = None - charts = None - - return render_template("reports/species_by_date_report.html", - title="Species by Date Report", - locations=list_locations(), - categories=list_categories(), - category_id=category_id, - species_id=species_id, - location_id=location_id, - from_date=from_date_string, - to_date=to_date_string, - interval=interval, - report=report_df, - charts=charts, - form_target=form_target) - - -@reports_bp.route("/location", methods=["GET", "POST"]) -@login_required -@requires_roles(["Administrator", "Reporter", "Reader"]) -def location_report(): - """ - Show the page that generates a location, species and sighting report for a given date range - - :return: The HTML for the reporting page - """ - if request.method == "POST": - from_date = get_posted_date("from_date") - to_date = get_posted_date("to_date") - location_id = get_posted_int("location") - category_id = get_posted_int("category") - return _render_location_report_page(from_date, to_date, location_id, category_id) - else: - return _render_location_report_page() - - -@reports_bp.route("/species", methods=["GET", "POST"]) -@login_required -@requires_roles(["Administrator", "Reporter", "Reader"]) -def species_report(): - """ - Show the page that generates a species sighting report for a given date range and location - - :return: The HTML for the reporting page - """ - if request.method == "POST": - from_date = get_posted_date("from_date") - to_date = get_posted_date("to_date") - location_id = get_posted_int("location") - category_id = get_posted_int("category") - species_id = get_posted_int("species") - interval = request.form["interval"] - return _render_species_report_page(from_date, to_date, location_id, category_id, species_id, interval) - else: - return _render_species_report_page() diff --git a/src/naturerec_web/reports/templates/reports/charts.html b/src/naturerec_web/reports/templates/reports/charts.html deleted file mode 100644 index d9a73d0..0000000 --- a/src/naturerec_web/reports/templates/reports/charts.html +++ /dev/null @@ -1,5 +0,0 @@ -{% for chart in charts %} -

- Chart -

-{% endfor %} diff --git a/src/naturerec_web/reports/templates/reports/location_report.html b/src/naturerec_web/reports/templates/reports/location_report.html deleted file mode 100644 index 1c3678d..0000000 --- a/src/naturerec_web/reports/templates/reports/location_report.html +++ /dev/null @@ -1,33 +0,0 @@ -{% if report.index | length > 0 %} - {% set suppress_menu = True %} -{% endif %} -{% extends "layout.html" %} -{% block title %}{{ title }}{% endblock %} -{% set location_required = "required" %} -{% set from_date_required = "required" %} -{% set index_column_name = "Species" %} - -{% block content %} - {% if report.index | length > 0 %} - {% include "reports/charts.html" with context %} - {% include "reports/report.html" with context %} - {% else %} -
- -

{{ title }}

-
- {% include "location_selector.html" with context %} - {% include "category_selector.html" with context %} - {% include "date_range_selector.html" with context %} -
- -
-
- {% if report.empty %} - The report contains no results - {% endif %} -
- {% endif %} -{% endblock %} diff --git a/src/naturerec_web/reports/templates/reports/report.html b/src/naturerec_web/reports/templates/reports/report.html deleted file mode 100644 index 0232b83..0000000 --- a/src/naturerec_web/reports/templates/reports/report.html +++ /dev/null @@ -1,24 +0,0 @@ -{% if report.index | length > 0 %} -
- - - - - {% for column in report.columns %} - - {% endfor %} - - - - {% for index in report.index %} - - - {% for column in report.columns %} - - {% endfor %} - - {% endfor %} - -
{{ index_column_name }}{{ column }}
{{ index }} {{ report.loc[index, column] }}
-
-{% endif %} diff --git a/src/naturerec_web/reports/templates/reports/species_by_date_report.html b/src/naturerec_web/reports/templates/reports/species_by_date_report.html deleted file mode 100644 index a4c37f8..0000000 --- a/src/naturerec_web/reports/templates/reports/species_by_date_report.html +++ /dev/null @@ -1,49 +0,0 @@ -{% if report.index | length > 0 %} - {% set suppress_menu = True %} -{% endif %} -{% extends "layout.html" %} -{% block title %}{{ title }}{% endblock %} -{% set location_required = "required" %} -{% set from_date_required = "required" %} - -{% block content %} - {% if report.index | length > 0 %} - {% include "reports/charts.html" with context %} - {% include "reports/report.html" with context %} - {% else %} -
- -

{{ title }}

-
- {% include "location_selector.html" with context %} - {% include "species_selector.html" with context %} - {% include "date_range_selector.html" with context %} -
- - -
-
- -
-
- {% if report.empty %} - The report contains no results - {% endif %} -
- {% endif %} -{% endblock %} - -{% block scripts %} - - -{% endblock %} \ No newline at end of file diff --git a/src/naturerec_web/templates/menu.html b/src/naturerec_web/templates/menu.html index a4e8d93..297978c 100644 --- a/src/naturerec_web/templates/menu.html +++ b/src/naturerec_web/templates/menu.html @@ -7,18 +7,13 @@