diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8872a570..4ad8f60d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,8 +39,19 @@ repos: "--config=setup.cfg", "--select=E9,F63,F7,F82,QGS101,QGS102,QGS103,QGS104,QGS106", ] + - repo: local + hooks: + - id: generate-requirements + name: Generate Python Requirements + entry: | + bash -c "cd backend && \ + poetry export -f requirements.txt -o requirements.txt --without-hashes && \ + poetry export -f requirements.txt --only=dev -o requirements-dev.txt --without-hashes && \ + poetry export -f requirements.txt --only=docs -o requirements-docs.txt --without-hashes" + language: system + types: [python] ci: - autoupdate_schedule: quarterly - skip: [] - submodules: false + autoupdate_schedule: quarterly + skip: [] + submodules: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 10262a11..333b0e08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,27 @@ # CHANGELOG +## 1.3.2 - 2025-12-01 + +> [!WARNING] +> **Version 1.3.x will be the latest release compatible with python 3.9** + +### :bug: Fixes + +* Refix servor error when creating or updating programs (missing requirements up to date, temporary patch to fix #477). + +### :technologist: Development + +* New Makefile to facilitate certain maintenance operations +* Requirements are now generated automatically during commit validation using pre-commit. + ## 1.3.1 - 2025-11-30 > [!WARNING] -> **Last release compatible with python 3.9** +> **Version 1.3.x will be the latest release compatible with python 3.9** ### :bug: Fixes -* Fix servor error when create or update programs (temporary patch to fix #477). +* Fix servor error when creating or updating programs (temporary patch to fix #477). ## 1.3.0 - 2025-11-26 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..4feea709 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +# Define variables +APP_NAME := gncitizen +BACKEND_DIR := backend +FRONTEND_DIR := frontend +DOCKER_COMPOSE_FILE := docker-compose.yml + +include .env + +DOCKER_IMAGE_BACKEND := $(APP_NAME)-backend +DOCKER_IMAGE_FRONTEND := $(APP_NAME)-frontend + +# Phony targets +.PHONY: all install-dependencies generate-requirements build docker-start clean + +# Default target +all: install-dependencies generate-requirements build docker-start + +# Install dependencies for frontend and backend +install-dependencies: + cd $(BACKEND_DIR) && \ + if [ -f poetry.lock ]; then \ + poetry install; \ + elif [ -f requirements.txt ]; then \ + pip install -r requirements.txt; \ + fi + cd $(FRONTEND_DIR) && npm install + +# Generate Python requirements files +generate-requirements: + cd $(BACKEND_DIR) && \ + poetry export -f requirements.txt -o requirements.txt --without-hashes && \ + poetry export -f requirements.txt --only=dev -o requirements-dev.txt --without-hashes && \ + poetry export -f requirements.txt --only=docs -o requirements-docs.txt --without-hashes + +# Build Docker images for backend and frontend +build: + docker build -t $(DOCKER_IMAGE_BACKEND) -f $(BACKEND_DIR)/Dockerfile $(BACKEND_DIR) + docker build -t $(DOCKER_IMAGE_FRONTEND) -f $(FRONTEND_DIR)/Dockerfile $(FRONTEND_DIR) + +# Start project using docker-compose +docker-start: + docker-compose -f $(DOCKER_COMPOSE_FILE) up -d + +# Clean up Docker containers and images +clean: + docker-compose -f $(DOCKER_COMPOSE_FILE) down + docker rmi $(DOCKER_IMAGE_BACKEND) $(DOCKER_IMAGE_FRONTEND) diff --git a/VERSION b/VERSION index f0bb29e7..1892b926 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.0 +1.3.2 diff --git a/backend/gncitizen/core/observations/models.py b/backend/gncitizen/core/observations/models.py index 8e1277bb..666157c1 100644 --- a/backend/gncitizen/core/observations/models.py +++ b/backend/gncitizen/core/observations/models.py @@ -27,7 +27,7 @@ "comment", "timestamp_create", "json_data", - "name" + "name", ) TAXREF_KEYS = ["nom_vern", "cd_nom", "cd_ref", "lb_nom"] @@ -155,9 +155,7 @@ def get_feature(self): """get obs data as geojson feature""" result_dict = self.as_dict(True) - result_dict["observer"] = ( - self.observer.as_simple_dict() if self.observer else None - ) + result_dict["observer"] = self.observer.as_simple_dict() if self.observer else None result_dict["validator"] = ( self.validator_ref.as_simple_dict() if self.validator_ref else None ) @@ -171,9 +169,7 @@ def get_feature(self): for k in result_dict: if k in OBS_KEYS: feature["properties"][k] = ( - result_dict[k].name - if isinstance(result_dict[k], Enum) - else result_dict[k] + result_dict[k].name if isinstance(result_dict[k], Enum) else result_dict[k] ) feature["properties"]["photos"] = [ { diff --git a/backend/gncitizen/core/taxonomy/routes.py b/backend/gncitizen/core/taxonomy/routes.py index ebc01cba..3fb347bc 100644 --- a/backend/gncitizen/core/taxonomy/routes.py +++ b/backend/gncitizen/core/taxonomy/routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint, request +from flask import Blueprint, request from typing import List, Dict, Any, Union from utils_flask_sqla.response import json_resp @@ -9,11 +9,12 @@ get_taxa_by_cd_nom, get_all_medias_types, get_all_attributes, - refresh_taxonlist + refresh_taxonlist, ) taxo_api = Blueprint("taxonomy", __name__) + @taxo_api.route("/taxonomy/refresh", methods=["GET"]) @json_resp def refresh(): @@ -57,7 +58,7 @@ def get_lists(): @taxo_api.route("/taxonomy/lists//species", methods=["GET"]) @json_resp # @lru_cache() -def get_list(id)-> Union[List[Dict[str, Any]], Dict[str, str]]: +def get_list(id) -> Union[List[Dict[str, Any]], Dict[str, str]]: """Renvoie l'ensemble des espèces de la liste demandée. GET /taxonomy/lists//species @@ -252,13 +253,13 @@ def get_list(id)-> Union[List[Dict[str, Any]], Dict[str, str]]: message: "Invalid list ID" raises: Exception: En cas d'erreur inattendue pendant le traitement. - """ + """ try: params = request.args.to_dict() res = taxhub_rest_get_taxon_list(id, params) if isinstance(res, dict) and "items" in res: - reformatted_taxa = reformat_taxa(res) + reformatted_taxa = reformat_taxa(res) else: reformatted_taxa = [] print(reformatted_taxa) @@ -266,6 +267,7 @@ def get_list(id)-> Union[List[Dict[str, Any]], Dict[str, str]]: except Exception as e: return {"message": str(e)}, 400 + @taxo_api.route("/taxonomy/taxon/", methods=["GET"]) @json_resp def get_taxon_from_cd_nom(cd_nom): @@ -292,12 +294,12 @@ def get_taxon_from_cd_nom(cd_nom): return get_taxa_by_cd_nom(cd_nom=cd_nom) except Exception as e: return {"message": str(e)}, 400 - + @taxo_api.route("/taxonomy/tmedias/types", methods=["GET"]) @json_resp -def get_media_types()-> List[Dict[str, Union[int, str]]]: - """Get all media types. +def get_media_types() -> List[Dict[str, Union[int, str]]]: + """Get all media types. --- tags: - Taxon @@ -330,15 +332,16 @@ def get_media_types()-> List[Dict[str, Union[int, str]]]: message: type: string description: Error message. - """ - try: - return get_all_medias_types() - except Exception as e: - return {"message": str(e)}, 400 + """ + try: + return get_all_medias_types() + except Exception as e: + return {"message": str(e)}, 400 + @taxo_api.route("/taxonomy/bibattributs", methods=["GET"]) @json_resp -def get_attributes()-> List[Dict[str, Union[int, str]]]: +def get_attributes() -> List[Dict[str, Union[int, str]]]: """ Get all attributes. --- @@ -404,4 +407,4 @@ def get_attributes()-> List[Dict[str, Union[int, str]]]: try: return get_all_attributes() except Exception as e: - return {"message": str(e)}, 400 \ No newline at end of file + return {"message": str(e)}, 400 diff --git a/backend/gncitizen/utils/taxonomy.py b/backend/gncitizen/utils/taxonomy.py index cfbcf4d8..a3eed92c 100644 --- a/backend/gncitizen/utils/taxonomy.py +++ b/backend/gncitizen/utils/taxonomy.py @@ -72,7 +72,12 @@ def taxhub_rest_get_all_lists() -> Optional[Dict]: taxa_lists = res.json()["data"] taxa_lists = [taxa for taxa in taxa_lists if not taxa["id_liste"] in excluded_list_ids] for taxa_list in taxa_lists: - taxonomy_lists.append((taxa_list["id_liste"], f'[{taxa_list["code_liste"]}] {taxa_list["nom_liste"]} ({taxa_list["nb_taxons"]} taxon(s))')) + taxonomy_lists.append( + ( + taxa_list["id_liste"], + f'[{taxa_list["code_liste"]}] {taxa_list["nom_liste"]} ({taxa_list["nb_taxons"]} taxon(s))', + ) + ) print(f"taxonomy_lists {taxonomy_lists}") except Exception as e: logger.critical(str(e)) @@ -80,7 +85,6 @@ def taxhub_rest_get_all_lists() -> Optional[Dict]: return None - def get_specie_from_cd_nom(cd_nom) -> Dict: """get specie datas from taxref id (cd_nom) @@ -121,6 +125,7 @@ def refresh_taxonlist() -> Dict: logger.warning("ERROR: No taxhub lists available") return taxhub_lists + def get_all_medias_types() -> Dict: """get all medias types""" url = f"{TAXHUB_API}tmedias/types" @@ -189,7 +194,7 @@ def reformat_taxa(taxa): "regne", "sous_famille", "tribu", - "url" + "url", ] for item in items: @@ -198,7 +203,7 @@ def reformat_taxa(taxa): "attributs": [], "cd_nom": item.get("cd_nom"), "nom_francais": None, - "taxref": { field: item.get(field) for field in TAXREF_FIELDS } + "taxref": {field: item.get(field) for field in TAXREF_FIELDS}, } # Récupérer tous les médias sans condition de types for media in item.get("medias", []): @@ -213,7 +218,7 @@ def reformat_taxa(taxa): return result -def get_taxa_by_cd_nom(cd_nom, params_to_update: Dict = {}) -> Dict: +def get_taxa_by_cd_nom(cd_nom, params_to_update: Dict = {}) -> Dict: """get taxa datas from taxref id (cd_nom) :param cd_nom: taxref unique id (cd_nom) @@ -238,9 +243,14 @@ def set_taxa_info_from_taxhub(taxhub_data, features): for feature in features: # Parcours des features if feature["properties"]["cd_nom"] == taxon["cd_nom"]: excluded_keys = {"medias", "attributs"} - filtered_data = {key: value for key, value in taxon.items() if key not in excluded_keys} - - if "taxref" not in feature["properties"] or feature["properties"]["taxref"] is None: + filtered_data = { + key: value for key, value in taxon.items() if key not in excluded_keys + } + + if ( + "taxref" not in feature["properties"] + or feature["properties"]["taxref"] is None + ): feature["properties"]["taxref"] = {} feature["properties"]["taxref"].update(filtered_data) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index b5295184..db2e5ace 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "gncitizen-backend" -version = "1.2.0" +version = "1.3.2" description = "Citizen nature inventories (Backend" authors = [ "lpofredc", diff --git a/backend/requirements.txt b/backend/requirements.txt index 2ef91fa8..ca4d5461 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -60,6 +60,6 @@ urllib3==2.4.0 ; python_version >= "3.9" and python_version < "4.0" utils-flask-sqlalchemy-geo==0.3.3 ; python_version >= "3.9" and python_version < "4.0" utils-flask-sqlalchemy==0.4.2 ; python_version >= "3.9" and python_version < "4.0" werkzeug==3.1.3 ; python_version >= "3.9" and python_version < "4.0" -wtforms==3.2.1 ; python_version >= "3.9" and python_version < "4.0" +wtforms==3.1.2 ; python_version >= "3.9" and python_version < "4.0" xlwt==1.3.0 ; python_version >= "3.9" and python_version < "4.0" zipp==3.22.0 ; python_version == "3.9" diff --git a/data/custom_forms_samples/README.md b/data/custom_forms_samples/README.md index f6a8e698..d122f286 100644 --- a/data/custom_forms_samples/README.md +++ b/data/custom_forms_samples/README.md @@ -8,4 +8,4 @@ | `nids_hirondelles.json`|Observation | Inventaire des nids d'hirondelles en bati | | `stade_vie_papillon.json`|Observation | Stade de vie des papillons | | `trogne.json`|Site | Inventaire des arbres remarquables | -| `type_contact.json`| Observation | Type de contact faune | \ No newline at end of file +| `type_contact.json`| Observation | Type de contact faune | diff --git a/data/custom_forms_samples/arbres_remarquables.json b/data/custom_forms_samples/arbres_remarquables.json index e6d8b87b..7217eb32 100644 --- a/data/custom_forms_samples/arbres_remarquables.json +++ b/data/custom_forms_samples/arbres_remarquables.json @@ -32,4 +32,4 @@ } } } -} \ No newline at end of file +} diff --git a/data/custom_forms_samples/avosmares.json b/data/custom_forms_samples/avosmares.json index 91c0e4df..318aa8ad 100644 --- a/data/custom_forms_samples/avosmares.json +++ b/data/custom_forms_samples/avosmares.json @@ -831,4 +831,4 @@ }, "description": "Données associées à une mare" } -} \ No newline at end of file +} diff --git a/data/custom_forms_samples/is_dead.json b/data/custom_forms_samples/is_dead.json index bae0a889..a6d8980e 100644 --- a/data/custom_forms_samples/is_dead.json +++ b/data/custom_forms_samples/is_dead.json @@ -11,4 +11,4 @@ } } } -} \ No newline at end of file +} diff --git a/data/custom_forms_samples/nids_hirondelles.json b/data/custom_forms_samples/nids_hirondelles.json index d2ce1b07..a8e7839a 100644 --- a/data/custom_forms_samples/nids_hirondelles.json +++ b/data/custom_forms_samples/nids_hirondelles.json @@ -111,4 +111,4 @@ } } } -} \ No newline at end of file +} diff --git a/data/custom_forms_samples/stade_vie_papillon.json b/data/custom_forms_samples/stade_vie_papillon.json index f5c72ba8..2f88650c 100644 --- a/data/custom_forms_samples/stade_vie_papillon.json +++ b/data/custom_forms_samples/stade_vie_papillon.json @@ -18,4 +18,4 @@ } } } -} \ No newline at end of file +} diff --git a/data/custom_forms_samples/trogne.json b/data/custom_forms_samples/trogne.json index 83601f74..014b5730 100644 --- a/data/custom_forms_samples/trogne.json +++ b/data/custom_forms_samples/trogne.json @@ -59,4 +59,4 @@ } } } -} \ No newline at end of file +} diff --git a/data/custom_forms_samples/type_contact.json b/data/custom_forms_samples/type_contact.json index 5099e363..87bfd1b3 100644 --- a/data/custom_forms_samples/type_contact.json +++ b/data/custom_forms_samples/type_contact.json @@ -18,4 +18,4 @@ } } } -} \ No newline at end of file +} diff --git a/frontend/package.json b/frontend/package.json index 581396c2..bee74106 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "1.2.0", + "version": "1.3.2", "scripts": { "ng": "ng", "start": "ng serve --poll 2000 --host 0.0.0.0 --port 4000", diff --git a/frontend/src/app/programs/media-galery/media-galery.component.html b/frontend/src/app/programs/media-galery/media-galery.component.html index 7513d1d5..83ae8b22 100644 --- a/frontend/src/app/programs/media-galery/media-galery.component.html +++ b/frontend/src/app/programs/media-galery/media-galery.component.html @@ -34,7 +34,7 @@