diff --git a/docker/Dockerfile b/docker/Dockerfile index f237870..760b53c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,15 +1,15 @@ FROM python:3.10-slim-bullseye AS runtime -COPY naturerecorderpy-1.0.23.0 /opt/naturerecorderpy-1.0.23.0 +COPY naturerecorderpy-1.0.24.0 /opt/naturerecorderpy-1.0.24.0 -WORKDIR /opt/naturerecorderpy-1.0.23.0 +WORKDIR /opt/naturerecorderpy-1.0.24.0 RUN apt-get update -y RUN pip install -r requirements.txt -RUN pip install nature_recorder-1.0.23-py3-none-any.whl +RUN pip install nature_recorder-1.0.24-py3-none-any.whl -ENV NATURE_RECORDER_DATA_FOLDER=/var/opt/naturerecorderpy-1.0.23.0 -ENV NATURE_RECORDER_DB=/var/opt/naturerecorderpy-1.0.23.0/naturerecorder.db +ENV NATURE_RECORDER_DATA_FOLDER=/var/opt/naturerecorderpy-1.0.24.0 +ENV NATURE_RECORDER_DB=/var/opt/naturerecorderpy-1.0.24.0/naturerecorder.db ENTRYPOINT [ "python" ] CMD [ "-m", "naturerec_web", "production" ] diff --git a/features/steps/common.py b/features/steps/common.py index ab4621d..f084184 100644 --- a/features/steps/common.py +++ b/features/steps/common.py @@ -69,7 +69,7 @@ def _(context): species = create_test_species(row["Species"], category.id) gender = [key for key, value in Gender.gender_map().items() if value == row["Gender"]][0] with_young = 1 if row["WithYoung"] == "Yes" else 0 - _ = create_sighting(location.id, species.id, sighting_date, int(row["Number"]), gender, with_young) + _ = create_sighting(location.id, species.id, sighting_date, int(row["Number"]), gender, with_young, None) @given("A set of conservation status schemes") diff --git a/features/steps/jobs.py b/features/steps/jobs.py index 8b43f79..8bf61f1 100644 --- a/features/steps/jobs.py +++ b/features/steps/jobs.py @@ -21,7 +21,7 @@ def _(context): category = create_test_category("Birds") species = create_test_species("Cormorant", category.id) gender = [key for key, value in Gender.gender_map().items() if value == "Unknown"][0] - _ = create_sighting(location.id, species.id, sighting_date, None, gender, 0) + _ = create_sighting(location.id, species.id, sighting_date, None, gender, 0, None) # Kick off the export exporter = SightingsExportHelper("sightings.csv", None, None, None, None) diff --git a/setup.py b/setup.py index 6620aaf..3052966 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.23", + version="1.0.24", description="Wildlife sightings database", packages=setuptools.find_packages("src"), include_package_data=True, diff --git a/sql/add_notes.sql b/sql/add_notes.sql new file mode 100644 index 0000000..1455bc9 --- /dev/null +++ b/sql/add_notes.sql @@ -0,0 +1,2 @@ +ALTER TABLE Sightings + ADD Notes TEXT NULL; diff --git a/src/naturerec_model/data_exchange/sightings_data_exchange_helper_base.py b/src/naturerec_model/data_exchange/sightings_data_exchange_helper_base.py index 7842c84..533b1bd 100644 --- a/src/naturerec_model/data_exchange/sightings_data_exchange_helper_base.py +++ b/src/naturerec_model/data_exchange/sightings_data_exchange_helper_base.py @@ -31,6 +31,8 @@ +-----------+-----------------------------------------------------------------------------+ | Longitude | Longitude for the location in decimal format | +-----------+-----------------------------------------------------------------------------+ +| Notes | Sighting notes | ++-----------+-----------------------------------------------------------------------------+ """ from .data_exchange_helper_base import DataExchangeHelperBase @@ -51,7 +53,8 @@ class SightingsDataExchangeHelperBase(DataExchangeHelperBase): 'Postcode', 'Country', 'Latitude', - 'Longitude' + 'Longitude', + 'Notes' ] def __init__(self, action): diff --git a/src/naturerec_model/data_exchange/sightings_import_helper.py b/src/naturerec_model/data_exchange/sightings_import_helper.py index d225098..51ea50f 100644 --- a/src/naturerec_model/data_exchange/sightings_import_helper.py +++ b/src/naturerec_model/data_exchange/sightings_import_helper.py @@ -39,7 +39,8 @@ def import_sightings(self): number = int(row[2]) if row[2].strip() else None gender = [key for key, value in Gender.gender_map().items() if value == row[3].strip().title()][0] with_young = 1 if row[4].strip().title() == "Yes" else 0 - _ = create_sighting(location_id, species_id, date, number, gender, with_young) + notes = row[14] if row[14] else None + _ = create_sighting(location_id, species_id, date, number, gender, with_young, notes) def _read_csv_rows(self): """ @@ -74,7 +75,7 @@ def _validate_row(cls, row, row_number): :param row: CSV row (collection of fields) :param row_number: Row number for error reporting """ - if len(row) != 14: + if len(row) != 15: raise ValueError(f"Malformed data at row {row_number}") cls._check_not_empty(row, 0, row_number) # Species diff --git a/src/naturerec_model/logic/sightings.py b/src/naturerec_model/logic/sightings.py index 8edd62a..041eacb 100644 --- a/src/naturerec_model/logic/sightings.py +++ b/src/naturerec_model/logic/sightings.py @@ -27,7 +27,7 @@ def _check_for_existing_records(session, location_id, species_id, date): return [sighting.id for sighting in sightings] -def create_sighting(location_id, species_id, date, number, gender, with_young): +def create_sighting(location_id, species_id, date, number, gender, with_young, notes): """ Create a new sighting @@ -37,6 +37,7 @@ def create_sighting(location_id, species_id, date, number, gender, with_young): :param number: Number of individuals seen :param gender: Gender of the individuals seen :param with_young: Whether or not young were seen + :param notes: Sighting notes :return: An instance of the Sighting class for the created record """ try: @@ -51,7 +52,8 @@ def create_sighting(location_id, species_id, date, number, gender, with_young): sighting_date=date, number=number, gender=gender, - withYoung=with_young) + withYoung=with_young, + notes=notes) session.add(sighting) except IntegrityError as e: raise ValueError("Invalid sighting properties") from e @@ -59,7 +61,7 @@ def create_sighting(location_id, species_id, date, number, gender, with_young): return sighting -def update_sighting(sighting_id, location_id, species_id, date, number, gender, with_young): +def update_sighting(sighting_id, location_id, species_id, date, number, gender, with_young, notes): """ Update an existing sighting @@ -70,6 +72,7 @@ def update_sighting(sighting_id, location_id, species_id, date, number, gender, :param number: Number of individuals seen :param gender: Gender of the individuals seen :param with_young: Whether or not young were seen + :param notes: Sighting notes :return: An instance of the Sighting class for the updated record """ try: @@ -97,6 +100,7 @@ def update_sighting(sighting_id, location_id, species_id, date, number, gender, sighting.number = number sighting.gender = gender sighting.withYoung = with_young + sighting.notes = notes session.add(sighting) except IntegrityError as e: raise ValueError("Invalid sighting properties") from e diff --git a/src/naturerec_model/model/sighting.py b/src/naturerec_model/model/sighting.py index bbbaa7b..242f6db 100644 --- a/src/naturerec_model/model/sighting.py +++ b/src/naturerec_model/model/sighting.py @@ -32,6 +32,8 @@ class Sighting(Base): withYoung = Column(Integer, default=0, nullable=False) #: Number of individuals seen gender = Column(Integer, default=Gender.UNKNOWN, nullable=False) + #: Sighting notes + notes = Column(String, default=None, nullable=True) #: Related location instance location = relationship("Location", lazy="joined") @@ -50,7 +52,8 @@ def __repr__(self): f"date={self.date!r}, " \ f"number={self.number!r}, " \ f"withYoung={self.withYoung!r}, " \ - f"gender={self.gender!r})" + f"gender={self.gender!r}, " \ + f"notes={self.notes!r})" @property def sighting_date(self): @@ -88,5 +91,6 @@ def csv_columns(self): self.location.postcode, self.location.country, self.location.latitude, - self.location.longitude + self.location.longitude, + self.notes ] diff --git a/src/naturerec_web/sightings/sightings_blueprint.py b/src/naturerec_web/sightings/sightings_blueprint.py index 3062b24..78ac444 100644 --- a/src/naturerec_web/sightings/sightings_blueprint.py +++ b/src/naturerec_web/sightings/sightings_blueprint.py @@ -3,6 +3,7 @@ """ import datetime +import html from flask import Blueprint, render_template, request, session, redirect from flask_login import login_required from naturerec_model.logic import list_sightings, get_sighting, create_sighting, update_sighting @@ -186,6 +187,9 @@ def edit(sighting_id): category_id = get_posted_int("category") session["category_id"] = category_id + # Get the notes and escape them + notes = html.escape(request.form["notes"]) + if sighting_id: _ = update_sighting(sighting_id, location_id, @@ -193,7 +197,8 @@ def edit(sighting_id): sighting_date, get_posted_int("number"), get_posted_int("gender"), - get_posted_bool("with_young")) + get_posted_bool("with_young"), + notes) sighting = get_sighting(sighting_id) else: created_id = create_sighting(location_id, @@ -201,7 +206,8 @@ def edit(sighting_id): sighting_date, get_posted_int("number"), get_posted_int("gender"), - get_posted_bool("with_young")).id + get_posted_bool("with_young"), + notes).id sighting = get_sighting(created_id) # Construct the confirmation message @@ -210,7 +216,12 @@ def edit(sighting_id): f"at {sighting.location.name} " \ f"on {sighting.display_date}" - return _render_sighting_editing_page(sighting_id, message, None) + # If we're editing an existing sighting, return to the sightings list page, so the + # change can be seen in the sightings list. Otherwise, return to the editing page + if sighting_id: + return redirect("/sightings/list") + else: + return _render_sighting_editing_page(sighting_id, message, None) except ValueError as e: return _render_sighting_editing_page(sighting_id, None, e) else: diff --git a/src/naturerec_web/sightings/templates/sightings/edit.html b/src/naturerec_web/sightings/templates/sightings/edit.html index 35bd486..d3afdc6 100644 --- a/src/naturerec_web/sightings/templates/sightings/edit.html +++ b/src/naturerec_web/sightings/templates/sightings/edit.html @@ -5,6 +5,7 @@ {% set number = sighting.number if sighting.number else "" %} {% set current_gender = sighting.gender %} {% set current_with_young = sighting.withYoung %} + {% set current_notes = sighting.notes if sighting.notes else "" %} {% else %} {% set title = "Add Sighting" %} {% set submit_title = "Add Sighting" %} @@ -12,6 +13,7 @@ {% set number = "" %} {% set current_gender = "" %} {% set current_with_young = "" %} + {% set current_notes = "" %} {% endif %} {% set location_required = "required" %} {% set species_required = "required" %} @@ -58,6 +60,10 @@