Skip to content

Commit efe03dd

Browse files
authored
Merge pull request #479 from PnX-SI/dev
Merge pull request #476 from PnX-SI/master
2 parents 2e46f9d + 52f3c03 commit efe03dd

28 files changed

+415
-334
lines changed

.pre-commit-config.yaml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,19 @@ repos:
3939
"--config=setup.cfg",
4040
"--select=E9,F63,F7,F82,QGS101,QGS102,QGS103,QGS104,QGS106",
4141
]
42+
- repo: local
43+
hooks:
44+
- id: generate-requirements
45+
name: Generate Python Requirements
46+
entry: |
47+
bash -c "cd backend && \
48+
poetry export -f requirements.txt -o requirements.txt --without-hashes && \
49+
poetry export -f requirements.txt --only=dev -o requirements-dev.txt --without-hashes && \
50+
poetry export -f requirements.txt --only=docs -o requirements-docs.txt --without-hashes"
51+
language: system
52+
types: [python]
4253

4354
ci:
44-
autoupdate_schedule: quarterly
45-
skip: []
46-
submodules: false
55+
autoupdate_schedule: quarterly
56+
skip: []
57+
submodules: false

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
# CHANGELOG
22

3+
## 1.3.2 - 2025-12-01
4+
5+
> [!WARNING]
6+
> **Version 1.3.x will be the latest release compatible with python 3.9**
7+
8+
### :bug: Fixes
9+
10+
* Refix servor error when creating or updating programs (missing requirements up to date, temporary patch to fix #477).
11+
12+
### :technologist: Development
13+
14+
* New Makefile to facilitate certain maintenance operations
15+
* Requirements are now generated automatically during commit validation using pre-commit.
16+
317
## 1.3.1 - 2025-11-30
418

519
> [!WARNING]
6-
> **Last release compatible with python 3.9**
20+
> **Version 1.3.x will be the latest release compatible with python 3.9**
721
822
### :bug: Fixes
923

Makefile

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Define variables
2+
APP_NAME := gncitizen
3+
BACKEND_DIR := backend
4+
FRONTEND_DIR := frontend
5+
DOCKER_COMPOSE_FILE := docker-compose.yml
6+
7+
include .env
8+
9+
DOCKER_IMAGE_BACKEND := $(APP_NAME)-backend
10+
DOCKER_IMAGE_FRONTEND := $(APP_NAME)-frontend
11+
12+
# Phony targets
13+
.PHONY: all install-dependencies generate-requirements build docker-start clean
14+
15+
# Default target
16+
all: install-dependencies generate-requirements build docker-start
17+
18+
# Install dependencies for frontend and backend
19+
install-dependencies:
20+
cd $(BACKEND_DIR) && \
21+
if [ -f poetry.lock ]; then \
22+
poetry install; \
23+
elif [ -f requirements.txt ]; then \
24+
pip install -r requirements.txt; \
25+
fi
26+
cd $(FRONTEND_DIR) && npm install
27+
28+
# Generate Python requirements files
29+
generate-requirements:
30+
cd $(BACKEND_DIR) && \
31+
poetry export -f requirements.txt -o requirements.txt --without-hashes && \
32+
poetry export -f requirements.txt --only=dev -o requirements-dev.txt --without-hashes && \
33+
poetry export -f requirements.txt --only=docs -o requirements-docs.txt --without-hashes
34+
35+
# Build Docker images for backend and frontend
36+
build:
37+
docker build -t $(DOCKER_IMAGE_BACKEND) -f $(BACKEND_DIR)/Dockerfile $(BACKEND_DIR)
38+
docker build -t $(DOCKER_IMAGE_FRONTEND) -f $(FRONTEND_DIR)/Dockerfile $(FRONTEND_DIR)
39+
40+
# Start project using docker-compose
41+
docker-start:
42+
docker-compose -f $(DOCKER_COMPOSE_FILE) up -d
43+
44+
# Clean up Docker containers and images
45+
clean:
46+
docker-compose -f $(DOCKER_COMPOSE_FILE) down
47+
docker rmi $(DOCKER_IMAGE_BACKEND) $(DOCKER_IMAGE_FRONTEND)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.3.1
1+
1.3.2

backend/gncitizen/core/observations/models.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"comment",
2828
"timestamp_create",
2929
"json_data",
30-
"name"
30+
"name",
3131
)
3232

3333
TAXREF_KEYS = ["nom_vern", "cd_nom", "cd_ref", "lb_nom"]
@@ -155,9 +155,7 @@ def get_feature(self):
155155
"""get obs data as geojson feature"""
156156

157157
result_dict = self.as_dict(True)
158-
result_dict["observer"] = (
159-
self.observer.as_simple_dict() if self.observer else None
160-
)
158+
result_dict["observer"] = self.observer.as_simple_dict() if self.observer else None
161159
result_dict["validator"] = (
162160
self.validator_ref.as_simple_dict() if self.validator_ref else None
163161
)
@@ -171,9 +169,7 @@ def get_feature(self):
171169
for k in result_dict:
172170
if k in OBS_KEYS:
173171
feature["properties"][k] = (
174-
result_dict[k].name
175-
if isinstance(result_dict[k], Enum)
176-
else result_dict[k]
172+
result_dict[k].name if isinstance(result_dict[k], Enum) else result_dict[k]
177173
)
178174
feature["properties"]["photos"] = [
179175
{

backend/gncitizen/core/taxonomy/routes.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from flask import Blueprint, request
1+
from flask import Blueprint, request
22
from typing import List, Dict, Any, Union
33
from utils_flask_sqla.response import json_resp
44

@@ -9,11 +9,12 @@
99
get_taxa_by_cd_nom,
1010
get_all_medias_types,
1111
get_all_attributes,
12-
refresh_taxonlist
12+
refresh_taxonlist,
1313
)
1414

1515
taxo_api = Blueprint("taxonomy", __name__)
1616

17+
1718
@taxo_api.route("/taxonomy/refresh", methods=["GET"])
1819
@json_resp
1920
def refresh():
@@ -57,7 +58,7 @@ def get_lists():
5758
@taxo_api.route("/taxonomy/lists/<int:id>/species", methods=["GET"])
5859
@json_resp
5960
# @lru_cache()
60-
def get_list(id)-> Union[List[Dict[str, Any]], Dict[str, str]]:
61+
def get_list(id) -> Union[List[Dict[str, Any]], Dict[str, str]]:
6162
"""Renvoie l'ensemble des espèces de la liste demandée.
6263
6364
GET /taxonomy/lists/<id>/species
@@ -252,20 +253,21 @@ def get_list(id)-> Union[List[Dict[str, Any]], Dict[str, str]]:
252253
message: "Invalid list ID"
253254
raises:
254255
Exception: En cas d'erreur inattendue pendant le traitement.
255-
"""
256+
"""
256257

257258
try:
258259
params = request.args.to_dict()
259260
res = taxhub_rest_get_taxon_list(id, params)
260261
if isinstance(res, dict) and "items" in res:
261-
reformatted_taxa = reformat_taxa(res)
262+
reformatted_taxa = reformat_taxa(res)
262263
else:
263264
reformatted_taxa = []
264265
print(reformatted_taxa)
265266
return reformatted_taxa
266267
except Exception as e:
267268
return {"message": str(e)}, 400
268269

270+
269271
@taxo_api.route("/taxonomy/taxon/<int:cd_nom>", methods=["GET"])
270272
@json_resp
271273
def get_taxon_from_cd_nom(cd_nom):
@@ -292,12 +294,12 @@ def get_taxon_from_cd_nom(cd_nom):
292294
return get_taxa_by_cd_nom(cd_nom=cd_nom)
293295
except Exception as e:
294296
return {"message": str(e)}, 400
295-
297+
296298

297299
@taxo_api.route("/taxonomy/tmedias/types", methods=["GET"])
298300
@json_resp
299-
def get_media_types()-> List[Dict[str, Union[int, str]]]:
300-
"""Get all media types.
301+
def get_media_types() -> List[Dict[str, Union[int, str]]]:
302+
"""Get all media types.
301303
---
302304
tags:
303305
- Taxon
@@ -330,15 +332,16 @@ def get_media_types()-> List[Dict[str, Union[int, str]]]:
330332
message:
331333
type: string
332334
description: Error message.
333-
"""
334-
try:
335-
return get_all_medias_types()
336-
except Exception as e:
337-
return {"message": str(e)}, 400
335+
"""
336+
try:
337+
return get_all_medias_types()
338+
except Exception as e:
339+
return {"message": str(e)}, 400
340+
338341

339342
@taxo_api.route("/taxonomy/bibattributs", methods=["GET"])
340343
@json_resp
341-
def get_attributes()-> List[Dict[str, Union[int, str]]]:
344+
def get_attributes() -> List[Dict[str, Union[int, str]]]:
342345
"""
343346
Get all attributes.
344347
---
@@ -404,4 +407,4 @@ def get_attributes()-> List[Dict[str, Union[int, str]]]:
404407
try:
405408
return get_all_attributes()
406409
except Exception as e:
407-
return {"message": str(e)}, 400
410+
return {"message": str(e)}, 400

backend/gncitizen/utils/taxonomy.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,19 @@ def taxhub_rest_get_all_lists() -> Optional[Dict]:
7272
taxa_lists = res.json()["data"]
7373
taxa_lists = [taxa for taxa in taxa_lists if not taxa["id_liste"] in excluded_list_ids]
7474
for taxa_list in taxa_lists:
75-
taxonomy_lists.append((taxa_list["id_liste"], f'[{taxa_list["code_liste"]}] {taxa_list["nom_liste"]} ({taxa_list["nb_taxons"]} taxon(s))'))
75+
taxonomy_lists.append(
76+
(
77+
taxa_list["id_liste"],
78+
f'[{taxa_list["code_liste"]}] {taxa_list["nom_liste"]} ({taxa_list["nb_taxons"]} taxon(s))',
79+
)
80+
)
7681
print(f"taxonomy_lists {taxonomy_lists}")
7782
except Exception as e:
7883
logger.critical(str(e))
7984
return res.json().get("data", [])
8085
return None
8186

8287

83-
8488
def get_specie_from_cd_nom(cd_nom) -> Dict:
8589
"""get specie datas from taxref id (cd_nom)
8690
@@ -121,6 +125,7 @@ def refresh_taxonlist() -> Dict:
121125
logger.warning("ERROR: No taxhub lists available")
122126
return taxhub_lists
123127

128+
124129
def get_all_medias_types() -> Dict:
125130
"""get all medias types"""
126131
url = f"{TAXHUB_API}tmedias/types"
@@ -189,7 +194,7 @@ def reformat_taxa(taxa):
189194
"regne",
190195
"sous_famille",
191196
"tribu",
192-
"url"
197+
"url",
193198
]
194199

195200
for item in items:
@@ -198,7 +203,7 @@ def reformat_taxa(taxa):
198203
"attributs": [],
199204
"cd_nom": item.get("cd_nom"),
200205
"nom_francais": None,
201-
"taxref": { field: item.get(field) for field in TAXREF_FIELDS }
206+
"taxref": {field: item.get(field) for field in TAXREF_FIELDS},
202207
}
203208
# Récupérer tous les médias sans condition de types
204209
for media in item.get("medias", []):
@@ -213,7 +218,7 @@ def reformat_taxa(taxa):
213218
return result
214219

215220

216-
def get_taxa_by_cd_nom(cd_nom, params_to_update: Dict = {}) -> Dict:
221+
def get_taxa_by_cd_nom(cd_nom, params_to_update: Dict = {}) -> Dict:
217222
"""get taxa datas from taxref id (cd_nom)
218223
219224
:param cd_nom: taxref unique id (cd_nom)
@@ -238,9 +243,14 @@ def set_taxa_info_from_taxhub(taxhub_data, features):
238243
for feature in features: # Parcours des features
239244
if feature["properties"]["cd_nom"] == taxon["cd_nom"]:
240245
excluded_keys = {"medias", "attributs"}
241-
filtered_data = {key: value for key, value in taxon.items() if key not in excluded_keys}
242-
243-
if "taxref" not in feature["properties"] or feature["properties"]["taxref"] is None:
246+
filtered_data = {
247+
key: value for key, value in taxon.items() if key not in excluded_keys
248+
}
249+
250+
if (
251+
"taxref" not in feature["properties"]
252+
or feature["properties"]["taxref"] is None
253+
):
244254
feature["properties"]["taxref"] = {}
245255
feature["properties"]["taxref"].update(filtered_data)
246256

backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "gncitizen-backend"
3-
version = "1.2.0"
3+
version = "1.3.2"
44
description = "Citizen nature inventories (Backend"
55
authors = [
66
"lpofredc",

backend/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ urllib3==2.4.0 ; python_version >= "3.9" and python_version < "4.0"
6060
utils-flask-sqlalchemy-geo==0.3.3 ; python_version >= "3.9" and python_version < "4.0"
6161
utils-flask-sqlalchemy==0.4.2 ; python_version >= "3.9" and python_version < "4.0"
6262
werkzeug==3.1.3 ; python_version >= "3.9" and python_version < "4.0"
63-
wtforms==3.2.1 ; python_version >= "3.9" and python_version < "4.0"
63+
wtforms==3.1.2 ; python_version >= "3.9" and python_version < "4.0"
6464
xlwt==1.3.0 ; python_version >= "3.9" and python_version < "4.0"
6565
zipp==3.22.0 ; python_version == "3.9"

data/custom_forms_samples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
| `nids_hirondelles.json`|Observation | Inventaire des nids d'hirondelles en bati |
99
| `stade_vie_papillon.json`|Observation | Stade de vie des papillons |
1010
| `trogne.json`|Site | Inventaire des arbres remarquables |
11-
| `type_contact.json`| Observation | Type de contact faune |
11+
| `type_contact.json`| Observation | Type de contact faune |

0 commit comments

Comments
 (0)