Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
jacquesfize committed May 28, 2024
2 parents a8b5e79 + 864d7e9 commit 8fae727
Show file tree
Hide file tree
Showing 39 changed files with 3,955 additions and 5,039 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/sphinx.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Build Sphinx Documentation"
name: 'Build Sphinx Documentation'

on:
release:
Expand Down Expand Up @@ -30,12 +30,11 @@ jobs:
- uses: actions/checkout@master
with:
fetch-depth: 0
ref: update-doc

- name: Setup node
uses: actions/setup-node@v3
with:
node-version: lts/gallium
node-version: lts/iron

- name: Install JS librairies
working-directory: ./frontend
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.14.1
2.14.2
2 changes: 1 addition & 1 deletion backend/dependencies/UsersHub
2 changes: 1 addition & 1 deletion backend/dependencies/Utils-Flask-SQLAlchemy-Geo
1 change: 1 addition & 0 deletions backend/geonature/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def create_app(with_external_mods=True):
if "CELERY" in app.config:
from geonature.utils.celery import celery_app

celery_app.init_app(app)
celery_app.conf.update(app.config["CELERY"])

# Emails configuration
Expand Down
17 changes: 15 additions & 2 deletions backend/geonature/core/auth/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
session,
Response,
)
from flask_login import login_user
import sqlalchemy as sa
from sqlalchemy import select
from utils_flask_sqla.response import json_resp

from pypnusershub.db import models
from pypnusershub.db.models import User, Organisme, Application
from pypnusershub.db.tools import encode_token
from pypnusershub.routes import insert_or_update_organism, insert_or_update_role
Expand Down Expand Up @@ -94,7 +97,9 @@ def loginCas():
.id_application
)
token = encode_token(data)
response.set_cookie("token", token, expires=cookie_exp)

token_exp = datetime.datetime.now(datetime.timezone.utc)
token_exp += datetime.timedelta(seconds=current_app.config["COOKIE_EXPIRATION"])

# User cookie
organism_id = info_user["codeOrganisme"]
Expand All @@ -111,7 +116,15 @@ def loginCas():
"id_role": data["id_role"],
"id_organisme": organism_id,
}
response.set_cookie("current_user", str(current_user), expires=cookie_exp)

# Log the user in
user = db.session.execute(
sa.select(models.User)
.where(models.User.identifiant == current_user["user_login"])
.where(models.User.filter_by_app())
).scalar_one()
login_user(user)

return response
else:
log.info("Erreur d'authentification lié au CAS, voir log du CAS")
Expand Down
12 changes: 5 additions & 7 deletions backend/geonature/core/gn_commons/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from geonature.core.gn_permissions import decorators as permissions
from geonature.core.gn_permissions.decorators import login_required
from geonature.core.gn_permissions.tools import get_scope
from geonature.core.gn_commons.schemas import TAdditionalFieldsSchema
import geonature.core.gn_commons.tasks # noqa: F401

from shapely.geometry import shape
Expand Down Expand Up @@ -192,14 +193,11 @@ def get_additional_fields():
else:
query = query.where(TAdditionalFields.objects.any(code_object=object_code))

return jsonify(
[
d.as_dict(
fields=["bib_nomenclature_type", "modules", "objects", "datasets", "type_widget"]
)
for d in db.session.scalars(query).all()
]
#
schema = TAdditionalFieldsSchema(
only=["bib_nomenclature_type", "modules", "objects", "datasets", "type_widget"], many=True
)
return jsonify(schema.dump(db.session.scalars(query).all()))


@routes.route("/t_mobile_apps", methods=["GET"])
Expand Down
39 changes: 37 additions & 2 deletions backend/geonature/core/gn_commons/schemas.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import logging
from marshmallow import Schema, pre_load, fields, EXCLUDE

from pypnnomenclature.schemas import NomenclatureSchema
from utils_flask_sqla.schema import SmartRelationshipsMixin

from pypnnomenclature.schemas import NomenclatureSchema, BibNomenclaturesTypesSchema

from pypnusershub.schemas import UserSchema
from geonature.utils.env import MA
from geonature.core.gn_commons.models import (
Expand All @@ -10,6 +14,10 @@
TAdditionalFields,
BibWidgets,
)
from geonature.core.gn_permissions.schemas import PermObjectSchema


log = logging.getLogger()


class ModuleSchema(MA.SQLAlchemyAutoSchema):
Expand Down Expand Up @@ -68,11 +76,38 @@ class LabelValueDict(Schema):
value = fields.Raw()


class TAdditionalFieldsSchema(MA.SQLAlchemyAutoSchema):
class CastableField(fields.Field):
"""
A field which tries to cast the value to int or float before returning it.
If the value is not castable, the default value is returned.
"""

def _serialize(self, value, attr, obj, **kwargs):
if value:
try:
value = float(value)
except ValueError:
log.warning("default value not castable to float")
try:
value = int(value)
except ValueError:
log.warning("default value not castable to int")
return value


class TAdditionalFieldsSchema(SmartRelationshipsMixin, MA.SQLAlchemyAutoSchema):
class Meta:
model = TAdditionalFields
load_instance = True

default_value = CastableField(allow_none=True)

modules = fields.Nested(ModuleSchema, many=True, dump_only=True)
objects = fields.Nested(PermObjectSchema, many=True, dump_only=True)
type_widget = fields.Nested(BibWidgetSchema, dump_only=True)
datasets = fields.Nested("DatasetSchema", many=True, dump_only=True)
bib_nomenclature_type = fields.Nested(BibNomenclaturesTypesSchema, dump_only=True)

def load(self, data, *, many=None, **kwargs):

if data["type_widget"].widget_name in (
Expand Down
8 changes: 4 additions & 4 deletions backend/geonature/core/gn_meta/mtd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ def get_ds_user_list(self):
url = url.format(ID_ROLE=self.id_role)
try:
xml = self._get_xml_by_url(url)
except requests.HttpError as http_error:
except requests.HTTPError as http_error:
error_code = http_error.response.status_code
warning_message = f"""[HttpError : {error_code}] for URL "{url}"."""
warning_message = f"""[HTTPError : {error_code}] for URL "{url}"."""
if error_code == 404:
warning_message = f"""{warning_message} > Probably no dataset found for the user with ID '{self.id_role}'"""
logger.warning(warning_message)
Expand All @@ -112,9 +112,9 @@ def get_list_af_for_user(self):
url = urljoin(self.api_endpoint, self.af_user_path).format(ID_ROLE=self.id_role)
try:
xml = self._get_xml_by_url(url)
except requests.HttpError as http_error:
except requests.HTTPError as http_error:
error_code = http_error.response.status_code
warning_message = f"""[HttpError : {error_code}] for URL "{url}"."""
warning_message = f"""[HTTPError : {error_code}] for URL "{url}"."""
if error_code == 404:
warning_message = f"""{warning_message} > Probably no acquisition framework found for the user with ID '{self.id_role}'"""
logger.warning(warning_message)
Expand Down
6 changes: 5 additions & 1 deletion backend/geonature/core/gn_meta/mtd/mtd_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ def sync_af(af):
.on_conflict_do_nothing(index_elements=["unique_acquisition_framework_id"])
.returning(TAcquisitionFramework)
)
DB.session.execute(statement)

return DB.session.scalar(statement)
acquisition_framework = DB.session.scalars(
select(TAcquisitionFramework).filter_by(unique_acquisition_framework_id=af_uuid)
)
return acquisition_framework


def add_or_update_organism(uuid, nom, email):
Expand Down
2 changes: 1 addition & 1 deletion backend/geonature/core/gn_monitoring/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def get_site_areas(id_site):
corSiteModule.c.id_module == params["id_module"]
)

data = DB.session.scalars(query).all()
data = DB.session.execute(query).all()
features = []
for d in data:
feature = get_geojson_feature(d[2])
Expand Down
10 changes: 10 additions & 0 deletions backend/geonature/core/gn_permissions/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from marshmallow import fields, validates_schema, EXCLUDE

from geonature.utils.env import db, ma
from geonature.core.gn_permissions.models import PermObject


class PermObjectSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = PermObject
include_fk = True
5 changes: 0 additions & 5 deletions backend/geonature/tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,3 @@

from geonature.utils.env import db
from geonature.utils.celery import celery_app


@task_postrun.connect
def close_session(*args, **kwargs):
db.session.remove()
4 changes: 2 additions & 2 deletions backend/geonature/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
from click.testing import CliRunner
from geonature.utils.config import config
from geonature.utils.env import db
from munch import Munch
from pypnusershub.db.models import User
import pytest

from .fixtures import *
from .utils import dict2obj

# Reuse Lambda function in the following tests
abs_function = lambda *args, **kwargs: None
Expand Down Expand Up @@ -46,7 +46,7 @@ def module_code():

def _():
return [
Munch.fromDict(
dict2obj(
{
"entry_points": {
"code": {"module": module_name, "load": module_code},
Expand Down
20 changes: 19 additions & 1 deletion backend/geonature/tests/test_gn_commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from flask import url_for, current_app
from geoalchemy2.elements import WKTElement
from PIL import Image
from marshmallow import Schema
from pypnnomenclature.models import BibNomenclaturesTypes, TNomenclatures
from sqlalchemy import func, select, exists
from werkzeug.exceptions import Conflict, Forbidden, NotFound, Unauthorized
Expand All @@ -19,11 +20,16 @@
from geonature.core.gn_permissions.models import PermObject
from geonature.utils.env import db
from geonature.utils.errors import GeoNatureError
from geonature.core.gn_commons.schemas import CastableField

from .fixtures import *
from .utils import set_logged_user


class DummySchema(Schema):
test = CastableField()


@pytest.fixture(scope="function")
def place(users):
place = TPlaces(place_name="test", role=users["user"])
Expand All @@ -46,7 +52,8 @@ def additional_field(app, datasets):
description="une descrption",
quantitative=False,
unity="degré C",
field_values=["la", "li"],
field_values=[100, 200, 300],
default_value="100",
id_widget=1,
modules=[module],
objects=[obj],
Expand Down Expand Up @@ -500,6 +507,9 @@ def test_get_additional_fields(self, datasets, additional_field):
assert "type_widget" in addi_one
assert "bib_nomenclature_type" in addi_one

# test default value has been casted
assert type(addi_one["default_value"]) is int

def test_get_additional_fields_multi_module(self, datasets, additional_field):
response = self.client.get(
url_for("gn_commons.get_additional_fields"),
Expand Down Expand Up @@ -561,6 +571,14 @@ def test_additional_field_admin(self, app, users, module, perm_object):
exists().where(TAdditionalFields.field_name == "pytest_invvalid").select()
)

@pytest.mark.parametrize(
"value, expected_type", [("1", int), ("1.0", float), ("1 ans", str), ("1,0", str)]
)
def test_castable_field(self, value, expected_type):
# test the serialization of a model using CastableField
result = DummySchema().dump({"test": value})
assert type(result["test"] == expected_type)

def test_get_t_mobile_apps(self, mobile_app):
import os, shutil, time
from pathlib import Path
Expand Down
26 changes: 26 additions & 0 deletions backend/geonature/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,29 @@ def get_id_nomenclature(nomenclature_type_mnemonique, cd_nomenclature):
)
)
)


def dict2obj(dict_data):

# checking whether object d is a
# instance of class list
if isinstance(dict_data, list):
dict_data = [dict2obj(x) for x in dict_data]

# if d is not a instance of dict then
# directly object is returned
if not isinstance(dict_data, dict):
return dict_data

# declaring a class
class C:
def __getitem__(self, item):
return getattr(self, item)

# constructor of the class passed to obj
obj = C()

for k in dict_data:
obj.__dict__[k] = dict2obj(dict_data[k])

return obj
37 changes: 36 additions & 1 deletion backend/geonature/utils/celery.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
from celery import Celery
import flask
from geonature.utils.env import db


celery_app = Celery("geonature")
class FlaskCelery(Celery):

def __init__(self, *args, **kwargs):

super(FlaskCelery, self).__init__(*args, **kwargs)
self.patch_task()

if "app" in kwargs:
self.init_app(kwargs["app"])

def patch_task(self):
TaskBase = self.Task
_celery = self

class ContextTask(TaskBase):
abstract = True

def __call__(self, *args, **kwargs):
if hasattr(_celery, "app"):
with _celery.app.app_context():
# No need for db.session.remove() since it is automatically closed
# by flask-sqlalchemy when exit the app context created
return TaskBase.__call__(self, *args, **kwargs)
else:
return TaskBase.__call__(self, *args, **kwargs)

self.Task = ContextTask

def init_app(self, app):
self.app = app
self.config_from_object(app.config)


celery_app = FlaskCelery("geonature")
Loading

0 comments on commit 8fae727

Please sign in to comment.