Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
47 changes: 47 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.3.0
1.3.2
10 changes: 3 additions & 7 deletions backend/gncitizen/core/observations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"comment",
"timestamp_create",
"json_data",
"name"
"name",
)

TAXREF_KEYS = ["nom_vern", "cd_nom", "cd_ref", "lb_nom"]
Expand Down Expand Up @@ -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
)
Expand All @@ -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"] = [
{
Expand Down
33 changes: 18 additions & 15 deletions backend/gncitizen/core/taxonomy/routes.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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():
Expand Down Expand Up @@ -57,7 +58,7 @@ def get_lists():
@taxo_api.route("/taxonomy/lists/<int:id>/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/<id>/species
Expand Down Expand Up @@ -252,20 +253,21 @@ 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)
return reformatted_taxa
except Exception as e:
return {"message": str(e)}, 400


@taxo_api.route("/taxonomy/taxon/<int:cd_nom>", methods=["GET"])
@json_resp
def get_taxon_from_cd_nom(cd_nom):
Expand All @@ -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
Expand Down Expand Up @@ -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.
---
Expand Down Expand Up @@ -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
return {"message": str(e)}, 400
26 changes: 18 additions & 8 deletions backend/gncitizen/utils/taxonomy.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,19 @@ 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))
return res.json().get("data", [])
return None



def get_specie_from_cd_nom(cd_nom) -> Dict:
"""get specie datas from taxref id (cd_nom)

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -189,7 +194,7 @@ def reformat_taxa(taxa):
"regne",
"sous_famille",
"tribu",
"url"
"url",
]

for item in items:
Expand All @@ -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", []):
Expand All @@ -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)
Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "gncitizen-backend"
version = "1.2.0"
version = "1.3.2"
description = "Citizen nature inventories (Backend"
authors = [
"lpofredc",
Expand Down
2 changes: 1 addition & 1 deletion backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion data/custom_forms_samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| `type_contact.json`| Observation | Type de contact faune |
2 changes: 1 addition & 1 deletion data/custom_forms_samples/arbres_remarquables.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion data/custom_forms_samples/avosmares.json
Original file line number Diff line number Diff line change
Expand Up @@ -831,4 +831,4 @@
},
"description": "Données associées à une mare"
}
}
}
2 changes: 1 addition & 1 deletion data/custom_forms_samples/is_dead.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion data/custom_forms_samples/nids_hirondelles.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion data/custom_forms_samples/stade_vie_papillon.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion data/custom_forms_samples/trogne.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion data/custom_forms_samples/type_contact.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ <h5 class="modal-title" id="exampleModalLabel" i18n="Modal title for clicked ph
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
<div class="modal-body">
<img [src]="
API_ENDPOINT+'/media/' +
Expand Down
Loading