Skip to content

Commit

Permalink
Merge pull request #29 from davewalker5/location-report
Browse files Browse the repository at this point in the history
Location report
  • Loading branch information
davewalker5 authored Feb 4, 2022
2 parents 3baff8f + 9a8f83a commit 244bd16
Show file tree
Hide file tree
Showing 82 changed files with 59,136 additions and 2,167 deletions.
10 changes: 5 additions & 5 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
FROM python:3.10-slim-bullseye AS runtime

COPY naturerecorderpy-1.0.16.0 /opt/naturerecorderpy-1.0.16.0
COPY naturerecorderpy-1.0.17.0 /opt/naturerecorderpy-1.0.17.0

WORKDIR /opt/naturerecorderpy-1.0.16.0
WORKDIR /opt/naturerecorderpy-1.0.17.0

RUN apt-get update -y
RUN pip install -r requirements.txt
RUN pip install nature_recorder-1.0.16-py3-none-any.whl
RUN pip install nature_recorder-1.0.17-py3-none-any.whl

ENV NATURE_RECORDER_DATA_FOLDER=/var/opt/naturerecorderpy-1.0.16.0
ENV NATURE_RECORDER_DB=/var/opt/naturerecorderpy-1.0.16.0/naturerecorder.db
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

ENTRYPOINT [ "python" ]
CMD [ "-m", "naturerec_web" ]
1 change: 1 addition & 0 deletions docs/source/naturerec_model/logic/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ The logic Package
status_schemes
status_ratings
species_status_ratings
reports
job_statuses
5 changes: 5 additions & 0 deletions docs/source/naturerec_model/logic/reports.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
reports.py
==========

.. automodule:: naturerec_model.logic.reports
:members:
2 changes: 2 additions & 0 deletions docs/source/naturerec_web/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ The naturerec_web Package
species_ratings_blueprint
export_blueprint
jobs_blueprint
reports_blueprint
request_utils
5 changes: 5 additions & 0 deletions docs/source/naturerec_web/reports_blueprint.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
reports_blueprint.py
====================

.. automodule:: naturerec_web.reports.reports_blueprint
:members:
5 changes: 5 additions & 0 deletions docs/source/naturerec_web/request_utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
request_utils.py
================

.. automodule:: naturerec_web.request_utils
:members:
30 changes: 30 additions & 0 deletions features/reports.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Feature: Reporting
Scenario: Report on numbers of individuals by location
Given A set of sightings
| Date | Location | Category | Species | Number | Gender | WithYoung |
| TODAY | Test Location | Birds | Woodpigeon | 1 | Unknown | No |
| TODAY | Test Location | Birds | Blackbird | 1 | Male | No |
| TODAY | Test Location | Birds | Robin | 1 | Unknown | No |
| TODAY | Test Location | Mammals | Grey Squirrel | 1 | Unknown | No |

When I navigate to the individuals by location report page
And I fill in the individuals by location report details
| Location | Category | From |
| Test Location | Birds | TODAY |

And I click on the "Generate Report" button
Then There will be 3 results in the report table

Scenario: Report on numbers of individuals by location
Given A set of sightings
| Date | Location | Category | Species | Number | Gender | WithYoung |
| TODAY | Test Location | Birds | Woodpigeon | 1 | Unknown | No |
| TODAY | Test Location | Mammals | Grey Squirrel | 1 | Unknown | No |

When I navigate to the sightings by location report page
And I fill in the sightings by location report details
| Location | Category | From |
| Test Location | Birds | TODAY |

And I click on the "Generate Report" button
Then There will be 1 result in the report table
35 changes: 35 additions & 0 deletions features/steps/reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from behave import when, then
from selenium.webdriver.common.by import By

from features.steps.helpers import select_option, get_date_from_string, confirm_table_row_count
from naturerec_model.model import Sighting


@when("I navigate to the individuals by location report page")
def _(context):
url = context.flask_runner.make_url("reports/location/individuals")
context.browser.get(url)
assert "Individuals by Species & Location" in context.browser.title


@when("I navigate to the sightings by location report page")
def _(context):
url = context.flask_runner.make_url("reports/location/sightings")
context.browser.get(url)
assert "Sightings by Species & Location" in context.browser.title


@when("I fill in the individuals by location report details")
@when("I fill in the sightings by location report details")
def _(context):
row = context.table.rows[0]
select_option(context, "location", row["Location"], None)
select_option(context, "category", row["Category"], None)
from_date = get_date_from_string(row["From"]).strftime(Sighting.DATE_DISPLAY_FORMAT)
context.browser.find_element(By.NAME, "from_date").send_keys(from_date)


@then("There will be {number} results in the report table")
@then("There will be {number} result in the report table")
def _(context, number):
confirm_table_row_count(context, number, 1)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def find_package_files(directory, remove_root):

setuptools.setup(
name="nature_recorder",
version="1.0.16",
version="1.0.17",
description="Wildlife sightings database",
packages=setuptools.find_packages("src"),
include_package_data=True,
Expand Down
5 changes: 4 additions & 1 deletion src/naturerec_model/logic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +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


__all__ = [
Expand Down Expand Up @@ -40,5 +41,7 @@
"list_species_status_ratings",
"create_job_status",
"complete_job_status",
"list_job_status"
"list_job_status",
"location_individuals_report",
"location_days_report"
]
71 changes: 71 additions & 0 deletions src/naturerec_model/logic/reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
This module contains the business logic for the pre-defined reports
"""

import datetime
import pandas as pd
from ..model import Engine, Sighting


def location_individuals_report(from_date, location_id, category_id, to_date=None):
"""
Report on the total number of individuals seen, filtering by location, category and date range
:param from_date: Start date for reporting
:param location_id: Location id
:param category_id: Category id
:param to_date: End-date for reporting or None for today
:return: A Pandas Dataframe containing the results
"""
# If the "to" date isn't set, make it today
if not to_date:
to_date = datetime.datetime.today()

# 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', SUM( IFNULL( s.Number, 1 ) ) AS 'Count' " \
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 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 location_days_report(from_date, location_id, category_id, to_date=None):
"""
Report on the number of days on which a given species was seen, filtering by location, category and date range
:param from_date: Start date for reporting
:param location_id: Location id
:param category_id: Category id
:param to_date: End-date for reporting or None for today
:return: A Pandas Dataframe containing the results
"""
# If the "to" date isn't set, make it today
if not to_date:
to_date = datetime.datetime.today()

# 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', COUNT( s.Id ) AS 'Count' " \
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 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")
37 changes: 7 additions & 30 deletions src/naturerec_web/export/export_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,16 @@
The export blueprint supplies view functions and templates for exporting sightings
"""

import datetime
from flask import Blueprint, render_template, request
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.model import Sighting

from naturerec_web.request_utils import get_posted_date, get_posted_int

export_bp = Blueprint("export", __name__, template_folder='templates')


def _get_filter_int(key):
"""
Retrieve a named integer value from the POSTed filtering form
:param key: Value key
:return: Value or None if not specified
"""
value = request.form[key] if key in request.form else None
return int(value) if value else None


def _get_filter_date(key):
"""
Retrieve a named date value from the POSTed filtering form
:param key: Value key
:return: Value or None if not specified
"""
date_string = request.form[key] if key in request.form else None
return datetime.datetime.strptime(date_string, Sighting.DATE_DISPLAY_FORMAT).date() if date_string else None


def _render_export_filters_page(from_date=None,
to_date=None,
location_id=None,
Expand Down Expand Up @@ -89,11 +66,11 @@ def export():
if request.method == "POST":
# Get the export parameters
filename = request.form["filename"]
from_date = _get_filter_date("from_date")
to_date = _get_filter_date("to_date")
location_id = _get_filter_int("location")
category_id = _get_filter_int("category")
species_id = _get_filter_int("species")
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")

# Kick off the export
exporter = SightingsExportHelper(filename, from_date, to_date, location_id, species_id)
Expand All @@ -116,7 +93,7 @@ def export_life_list():
if request.method == "POST":
# Get the export parameters
filename = request.form["filename"]
category_id = _get_filter_int("category")
category_id = get_posted_int("category")

# Kick off the export
exporter = LifeListExportHelper(filename, category_id)
Expand Down
1 change: 0 additions & 1 deletion src/naturerec_web/jobs/templates/jobs/list.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% extends "layout.html" %}
{% block title %}Background Job Status{% endblock %}
{% set import_enabled = True %}

{% block content %}
<form method="post">
Expand Down
18 changes: 3 additions & 15 deletions src/naturerec_web/life_list/life_list_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from flask import Blueprint, render_template, request
from naturerec_model.logic import list_categories, get_category
from naturerec_model.logic import life_list

from naturerec_web.request_utils import get_posted_int

life_list_bp = Blueprint("life_list", __name__, template_folder='templates')

Expand All @@ -23,19 +23,7 @@ def _render_life_list_page(category_id=None):
categories=list_categories(),
category=category,
category_id=category_id,
species=species,
edit_enabled=True)


def _get_posted_int(key):
"""
Retrieve a named integer value from the POSTed form
:param key: Value key
:return: Value or None if not specified
"""
value = request.form[key]
return int(value) if value else None
species=species)


@life_list_bp.route("/list", methods=["GET", "POST"])
Expand All @@ -46,6 +34,6 @@ def life_list_for_category():
:return: The HTML for the life list page
"""
if request.method == "POST":
return _render_life_list_page(_get_posted_int("category"))
return _render_life_list_page(get_posted_int("category"))
else:
return _render_life_list_page()
3 changes: 0 additions & 3 deletions src/naturerec_web/life_list/templates/life_list/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ <h1>Life List</h1>
<div class="filter-criteria">
{% include "category_selector.html" with context %}
<div class="button-bar">
<button type="button" value="import" class="btn btn-light">
<a href="{{ url_for('export.export_life_list') }}">Export Life List</a>
</button>
<button type="submit" value="filter" class="btn btn-primary">List Species</button>
</div>
</div>
Expand Down
21 changes: 5 additions & 16 deletions src/naturerec_web/locations/locations_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from flask import Blueprint, render_template, request, redirect
from naturerec_model.logic import list_locations, get_location, create_location, update_location, geocode_postcode

from naturerec_web.request_utils import get_posted_float

locations_bp = Blueprint("locations", __name__, template_folder='templates')

Expand All @@ -23,17 +23,6 @@ def _render_location_editing_page(location_id, error):
error=error)


def _get_posted_float(key):
"""
Retrieve a named float value from a POSTed form
:param key: Value key
:return: Value or None if not specified
"""
value = request.form[key]
return float(value) if value else None


@locations_bp.route("/list")
def list_all():
"""
Expand Down Expand Up @@ -66,17 +55,17 @@ def edit(location_id):
request.form["address"],
request.form["city"],
request.form["postcode"],
_get_posted_float("latitude"),
_get_posted_float("longitude"))
get_posted_float("latitude"),
get_posted_float("longitude"))
else:
_ = create_location(request.form["name"],
request.form["county"],
request.form["country"],
request.form["address"],
request.form["city"],
request.form["postcode"],
_get_posted_float("latitude"),
_get_posted_float("longitude"))
get_posted_float("latitude"),
get_posted_float("longitude"))
return redirect("/locations/list")
except ValueError as e:
return _render_location_editing_page(location_id, e)
Expand Down
Loading

0 comments on commit 244bd16

Please sign in to comment.