Skip to content

Commit

Permalink
Feat/dynamic form/site (PnX-SI#42)
Browse files Browse the repository at this point in the history
* refactor: object.service with observable obj

Change the objectType string to objectType obj
in order to subscribe to multilple properties link to this object

Reviewed-by: andriacap
[Refs_ticket]: PnX-SI#40

* feat: adapt service to get config json

Get fieldsName and fieldsLabel for display properties
(properties component)

Add logic to setItem in LocalStorage to keepLast Value of observable on reload page

TODO:
- [ ]  see if Localstorage is really necessary with the configService used inside api-geom.service )
- [ ] adapt monitoring-form-g.component with the new service

Reviewed-by: andriacap
[Refs ticket]: PnX-SI#4

* feat: column datatable from config json

Add logic into sitegroups service in order to use config json to diplay
column datatable (called "display_list")

Review-by: andriac
[Refs ticket]: PnX-SI#4

* feat: adjust backend to load config json

Adjust backend code to use existing code in order to load config from
file json and by starting with "sites_group"

Fix Media load and upload for site_group

TODO:
- [ ] check if config should be find from file or backend
- [ ] Optimize logic backend (use generic model with class method ?)

Reviewed-by: andriacap
[Refs ticket]: PnX-SI#4

* feat: add button multiselect with filter on backend

filter with params backend (new route -> see if it's possible to change
that)

Add button multiselect above form type site

TODO:
- improve Input / condition of use case of btn multiselect

Reviewed-by: andriac

* feat: btn multiselect option

-Add @input fn , placeholder, title, paramsToFilt
-Remove empty option
-prevent adding from keyboard input or not including in list
-Store config json into object

Reviewed-by: @AndriaC
[Refs-ticket]: PnX-SI#4

* refactor: change form-service and object-service

refactor form-service
add observable into object-service (WIP: futur use for refresh page ?)

Rieviewed-by: andriac

[Refs_ticket]: PnX-SI#40

* refactor: test refresh page and comportment obs

refresh page seems to works with localstorage of different obj in
object_service

Reviewed-by: andriac

* feat: dynamic form

- Add:
- pass config json to form.component-g.ts
- add config json to this.obj and refresh form into form.component-g.ts
- add css for form component to deal when long form selected

-fix :
- refresh page form component (this._configService.init is necessary
  ...)
- comportment different between add or edit into form component and
  form service

Reviewed-by: andriac
[Refs_ticket]: PnX-SI#4

* feat: dynamic form - Correction PR

Remove unused console.log
Rxjs : use concatMap to avoid subscribe inside subscribe
Apply: prettier and sort prettier for import ts file

Reviewed-by: @AndriaC
[Refs_PR]: PnX-SI#42

* feat: dynamic create site

- Add change current-object when click "Add <object>" from datatable
  component
- api-geom.service: change the way to init the api-geom
- WIP:  two subscribes in one .. (the subscription is call only once ..)
- WIP : get config json from backend

Reviewed-by: andriac

* feat: add relationship id into sendData form site

Add type_site and id_site_group to the form site

Reviewed-by: andriac
[Refs_ticket]: PnX-SI#5 et PnX-SI#6

* feat(back): add custom config when post site

Change backend files in order to choose specific config when site is
created.

Reviewed-by: andriac
[Refs_ticket]: PnX-SI#5 , PnX-SI#6

* fix: problem of type object when loading form

Add endPoint and objectType to the observable using by the formservice
in order to use _apiGeomService with good context (endPoint)

[Refs_ticket]: PnX-SI#5 et PnX-SI#6

* fix: todo comments about refactoring code

Add todo comment in order to don't forget what
is to improve inside the code

* chore(front): add comment and remove console.log

* chore(api): put back old formatting

* chore(api): remove useless comment

* refactor(api): move and rename create_update fct

* fix: put back inventor/digitiser & fix typo

* chore: remove useless files

Since columns are now loaded from json, they are not needed
anymore in separate typescript files

* chore: remove definition from sites_group.json

* fix: column problem on sites

* chore: remove useless comment

* refactor(front): remove comment and use rxjs

* chore(front): remove useless file

Since class BtnMultiSelectChipClass is not used anymore

* chore(front): remove useless comments/imports

* style(front): applied prettier

* chore(front): remove useless code

* refactor(front): regroup types into an interface

* style(front): applied prettier

* refactor(front): inherit config-json from config

---------

Co-authored-by: Andria Capai <[email protected]>
  • Loading branch information
mvergez and andriacap authored Apr 14, 2023
1 parent e93f8f2 commit ab5fd47
Show file tree
Hide file tree
Showing 45 changed files with 1,234 additions and 588 deletions.
27 changes: 21 additions & 6 deletions backend/gn_module_monitoring/config/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
config_cache_name = 'MONITORINGS_CONFIG'


def get_config_objects(module_code, config, tree=None, parent_type=None):
def get_config_objects(module_code, config, tree=None, parent_type=None, customSpecConfig=None):
'''
recupere la config de chaque object present dans tree pour le module <module_code>
'''
Expand All @@ -36,7 +36,10 @@ def get_config_objects(module_code, config, tree=None, parent_type=None):
for object_type in tree:
# config object
if not object_type in config:
config[object_type] = config_object_from_files(module_code, object_type)
if (object_type=="site"):
config[object_type] = config_object_from_files(module_code, object_type,customSpecConfig)
else:
config[object_type] = config_object_from_files(module_code, object_type)

# tree
children_types = tree[object_type] and list(tree[object_type].keys()) or []
Expand Down Expand Up @@ -74,23 +77,25 @@ def get_config_objects(module_code, config, tree=None, parent_type=None):

# recursif
if tree[object_type]:
get_config_objects(module_code, config, tree[object_type], object_type)
get_config_objects(module_code, config, tree[object_type], object_type,customSpecConfig)


def config_object_from_files(module_code, object_type):
def config_object_from_files(module_code, object_type,custom=None):
'''
recupere la configuration d'un object de type <object_type> pour le module <module_code>
'''
generic_config_object = json_config_from_file('generic', object_type)
specific_config_object = {} if module_code == 'generic' else json_config_from_file(module_code, object_type)

if module_code == 'generic' and object_type == 'site' and custom is not None:
specific_config_object = custom
config_object = generic_config_object
config_object.update(specific_config_object)

return config_object


def get_config(module_code=None, force=False):
def get_config(module_code=None, force=False,customSpecConfig=None):
'''
recupere la configuration pour le module monitoring
Expand Down Expand Up @@ -130,7 +135,7 @@ def get_config(module_code=None, force=False):
# return config

config = config_from_files('config', module_code)
get_config_objects(module_code, config)
get_config_objects(module_code, config,customSpecConfig=customSpecConfig)

# customize config
if module:
Expand Down Expand Up @@ -240,3 +245,13 @@ def get_config_frontend(module_code=None, force=True):

config = dict(get_config(module_code, force))
return config



# def get_config_from_backend(module_code=None, force=False):

# module_code = 'generic'
# #TODO: voir la sortie de cette fonction
# config = config_from_backend('config', module_code)
# #TODO: voir également à quoi sert cette fonction
# get_config_objects(module_code, config)
2 changes: 1 addition & 1 deletion backend/gn_module_monitoring/monitoring/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def MonitoringModel(self, object_type):


monitoring_definitions = MonitoringDefinitions()

monitoring_g_definitions = MonitoringDefinitions()

class MonitoringObjectBase():

Expand Down
15 changes: 13 additions & 2 deletions backend/gn_module_monitoring/monitoring/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
MonitoringSite
)

from .base import monitoring_definitions
from .base import monitoring_definitions, monitoring_g_definitions
from .repositories import MonitoringObject
from .geom import MonitoringObjectGeom

Expand All @@ -28,7 +28,7 @@
'visit': TMonitoringVisits,
'observation': TMonitoringObservations,
'observation_detail': TMonitoringObservationDetails,
'sites_group': TMonitoringSitesGroups
'sites_group': TMonitoringSitesGroups,
}

MonitoringObjects_dict = {
Expand All @@ -41,3 +41,14 @@
}

monitoring_definitions.set(MonitoringObjects_dict, MonitoringModels_dict)

# #####################""
MonitoringModelsG_dict = {
x: MonitoringModels_dict[x] for x in MonitoringModels_dict if x not in "module"
}

MonitoringObjectsG_dict = {
x: MonitoringObjects_dict[x] for x in MonitoringObjects_dict if x not in "module"
}

monitoring_g_definitions.set(MonitoringObjectsG_dict, MonitoringModelsG_dict)
14 changes: 8 additions & 6 deletions backend/gn_module_monitoring/monitoring/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ class MonitoringSite(MonitoringObjectGeom):
'''

def preprocess_data(self, data):
type_site_ids = [type_site.id_nomenclature_type_site for type_site in self._model.types_site]
if len(data['types_site']) >0 :
for id_type_site in data['types_site']:
if int(id_type_site) not in type_site_ids:
type_site_ids.append(id_type_site)
#TODO: A enlever une fois qu'on aura enelever le champ "id_nomenclature_type_site" du model et de la bdd
data["id_nomenclature_type_site"]=data["types_site"][0]

module_ids = [module.id_module for module in self._model.modules]
id_module = int(data['id_module'])
if id_module not in module_ids:
module_ids.append(id_module)

data['modules'] = module_ids
data['types_site'] = type_site_ids
2 changes: 1 addition & 1 deletion backend/gn_module_monitoring/monitoring/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Meta:
exclude = ("geom_geojson",)
load_instance = True

medias = MA.Nested(MediaSchema)
medias = MA.Nested(MediaSchema,many=True)
pk = fields.Method("set_pk",dump_only=True)
geometry = fields.Method("serialize_geojson", dump_only=True)

Expand Down
15 changes: 10 additions & 5 deletions backend/gn_module_monitoring/monitoring/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@
import datetime
import uuid
from flask import current_app
from .base import MonitoringObjectBase, monitoring_definitions
from .base import MonitoringObjectBase, monitoring_definitions, monitoring_g_definitions
from ..utils.utils import to_int
from ..routes.data_utils import id_field_name_dict
from geonature.utils.env import DB


class MonitoringObjectSerializer(MonitoringObjectBase):



def get_parent(self):

monitoring_def = monitoring_g_definitions if self._module_code == "generic" else monitoring_definitions
parent_type = self.parent_type()
if not parent_type:
return

if not self._parent:
self._parent = (
monitoring_definitions
monitoring_def
.monitoring_object_instance(
self._module_code,
parent_type,
Expand Down Expand Up @@ -64,6 +65,7 @@ def unflatten_specific_properties(self, properties):
properties['data'] = data

def serialize_children(self, depth):
monitoring_def = monitoring_g_definitions if self._module_code == "generic" else monitoring_definitions
children_types = self.config_param('children_types')

if not children_types:
Expand All @@ -82,7 +84,7 @@ def serialize_children(self, depth):

for child_model in getattr(self._model, relation_name):
child = (
monitoring_definitions
monitoring_def
.monitoring_object_instance(self._module_code, children_type, model=child_model)
)
children_of_type.append(child.serialize(depth))
Expand Down Expand Up @@ -187,6 +189,9 @@ def populate(self, post_data):

# ajout des données en base
if hasattr(self._model, 'from_geofeature'):
for key in list(post_data):
if key not in ("properties","geometry","type"):
post_data.pop(key)
self._model.from_geofeature(post_data, True)
else:
self._model.from_dict(properties, True)
41 changes: 40 additions & 1 deletion backend/gn_module_monitoring/routes/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from werkzeug.datastructures import MultiDict

from gn_module_monitoring.blueprint import blueprint
from gn_module_monitoring.monitoring.models import BibTypeSite, TMonitoringSites
from gn_module_monitoring.config.repositories import get_config
from gn_module_monitoring.monitoring.models import BibTypeSite, TMonitoringSites, TNomenclatures
from gn_module_monitoring.monitoring.schemas import BibTypeSiteSchema, MonitoringSitesSchema
from gn_module_monitoring.utils.routes import (
create_or_update_object_api_sites_sites_group,
filter_params,
geojson_query,
get_limit_page,
Expand Down Expand Up @@ -34,6 +36,30 @@ def get_types_site():
)


@blueprint.route("/sites/types/label", methods=["GET"])
def get_types_site_by_label():
params = MultiDict(request.args)
limit, page = get_limit_page(params=params)
sort_label, sort_dir = get_sort(
params=params, default_sort="label_fr", default_direction="desc"
)
joinquery = BibTypeSite.query.join(BibTypeSite.nomenclature).filter(
TNomenclatures.label_fr.ilike(f"%{params['label_fr']}%")
)
if sort_dir == "asc":
joinquery = joinquery.order_by(TNomenclatures.label_fr.asc())

# See if there are not too much labels since they are used
# in select in the frontend side. And an infinite select is not
# implemented
return paginate(
query=joinquery,
schema=BibTypeSiteSchema,
limit=limit,
page=page,
)


@blueprint.route("/sites/types/<int:id_type_site>", methods=["GET"])
def get_type_site_by_id(id_type_site):
res = BibTypeSite.find_by_id(id_type_site)
Expand Down Expand Up @@ -83,3 +109,16 @@ def get_all_site_geometries():
def get_module_sites(module_code: str):
# TODO: load with site_categories.json API
return jsonify({"module_code": module_code})


@blueprint.route("/sites", methods=["POST"])
def post_sites():
module_code = "generic"
object_type = "site"
customConfig = dict()
post_data = dict(request.get_json())
for keys in post_data["dataComplement"].keys():
if "config" in post_data["dataComplement"][keys]:
customConfig.update(post_data["dataComplement"][keys]["config"])
get_config(module_code, force=True, customSpecConfig=customConfig)
return create_or_update_object_api_sites_sites_group(module_code, object_type), 201
39 changes: 19 additions & 20 deletions backend/gn_module_monitoring/routes/sites_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
from werkzeug.datastructures import MultiDict

from gn_module_monitoring.blueprint import blueprint
from gn_module_monitoring.config.repositories import get_config
from gn_module_monitoring.modules.repositories import get_module
from gn_module_monitoring.monitoring.definitions import monitoring_g_definitions
from gn_module_monitoring.monitoring.models import TMonitoringSites, TMonitoringSitesGroups
from gn_module_monitoring.monitoring.schemas import MonitoringSitesGroupsSchema
from gn_module_monitoring.utils.errors.errorHandler import InvalidUsage
from gn_module_monitoring.utils.routes import (
create_or_update_object_api_sites_sites_group,
filter_params,
geojson_query,
get_limit_page,
get_sort,
paginate,
sort,
)
from gn_module_monitoring.utils.errors.errorHandler import InvalidUsage
from gn_module_monitoring.utils.utils import to_int


@blueprint.route("/sites_groups", methods=["GET"])
Expand Down Expand Up @@ -66,18 +71,12 @@ def get_sites_group_geometries():

@blueprint.route("/sites_groups/<int:_id>", methods=["PATCH"])
def patch(_id):
item_schema = MonitoringSitesGroupsSchema()
item_json = request.get_json()
item = TMonitoringSitesGroups.find_by_id(_id)
fields = TMonitoringSitesGroups.attribute_names()
for field in item_json:
if field in fields:
setattr(item, field, item_json[field])
item_schema.load(item_json)
db.session.add(item)

db.session.commit()
return item_schema.dump(item), 201
# ###############################""
# FROM route/monitorings
module_code = "generic"
object_type = "sites_group"
get_config(module_code, force=True)
return create_or_update_object_api_sites_sites_group(module_code, object_type, _id), 201


@blueprint.route("/sites_groups/<int:_id>", methods=["DELETE"])
Expand All @@ -91,16 +90,16 @@ def delete(_id):

@blueprint.route("/sites_groups", methods=["POST"])
def post():
item_schema = MonitoringSitesGroupsSchema()
item_json = request.get_json()
item = item_schema.load(item_json)
db.session.add(item)
db.session.commit()
return item_schema.dump(item), 201
module_code = "generic"
object_type = "sites_group"
get_config(module_code, force=True)
return create_or_update_object_api_sites_sites_group(module_code, object_type), 201


@blueprint.errorhandler(ValidationError)
def handle_validation_error(error):
return InvalidUsage(
"Fields cannot be validated, message : {}".format(error.messages), status_code=422, payload=error.data
"Fields cannot be validated, message : {}".format(error.messages),
status_code=422,
payload=error.data,
).to_dict()
46 changes: 45 additions & 1 deletion backend/gn_module_monitoring/utils/routes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import Tuple

from flask import Response
from flask import Response, request
from flask.json import jsonify
from geonature.utils.env import DB
from gn_module_monitoring.modules.repositories import get_module
from gn_module_monitoring.utils.utils import to_int
from marshmallow import Schema
from sqlalchemy import cast, func, text
from sqlalchemy.dialects.postgresql import JSON
Expand All @@ -11,6 +13,7 @@

from gn_module_monitoring.monitoring.queries import Query as MonitoringQuery
from gn_module_monitoring.monitoring.schemas import paginate_schema
from gn_module_monitoring.monitoring.definitions import monitoring_g_definitions


def get_limit_page(params: MultiDict) -> Tuple[int]:
Expand Down Expand Up @@ -57,3 +60,44 @@ def geojson_query(subquery) -> bytes:
if len(result) > 0:
return result[0]
return b""


def create_or_update_object_api_sites_sites_group(module_code, object_type, id=None):
"""
route pour la création ou la modification d'un objet
si id est renseigné, c'est une création (PATCH)
sinon c'est une modification (POST)
:param module_code: reference le module concerne
:param object_type: le type d'object (site, visit, obervation)
:param id : l'identifiant de l'object (de id_base_site pour site)
:type module_code: str
:type object_type: str
:type id: int
:return: renvoie l'object crée ou modifié
:rtype: dict
"""
depth = to_int(request.args.get("depth", 1))

# recupération des données post
post_data = dict(request.get_json())
if module_code != "generic":
module = get_module("module_code", module_code)
else:
module = {"id_module": "generic"}
# TODO : A enlever une fois que le post_data contiendra geometry et type depuis le front
if object_type == "site":
post_data["geometry"] = {"type": "Point", "coordinates": [2.5, 50]}
post_data["type"] = "Feature"
# on rajoute id_module s'il n'est pas renseigné par défaut ??
if "id_module" not in post_data["properties"]:
module["id_module"] = "generic"
post_data["properties"]["id_module"] = module["id_module"]
else:
post_data["properties"]["id_module"] = module.id_module

return (
monitoring_g_definitions.monitoring_object_instance(module_code, object_type, id)
.create_or_update(post_data)
.serialize(depth)
)
Loading

0 comments on commit ab5fd47

Please sign in to comment.