Skip to content

Commit

Permalink
Feat/crud/gp sites components (#38)
Browse files Browse the repository at this point in the history
* feat(front): wip sites_groups component and svc

* WIP feat(front): DataTable sites_groups

- Table with specific data value (OK)
- Table with sort column (OK)
- Datatable , select row event and change
color (wip).

Reviewed-by: andriac
[Refs ticket]: #4

* feat(front): Datatable format and selected row

- Get and display data from group_site database (OK)
- Selecting row and get id of row table (OK)

Improve :
- improve assign colname table outside the component (maybe into the
  class folder ?)

Todo/next:
- Filtering table
- Add action column to table
- refactor code by creating component for the ngx-datable in order to
  reuse component for other data

Reviewed-by: andriac
[Refs ticket]: #4

* feat(front): Filtering table (OK)

Function filtering is working on each column
Button filtering hide/display filter inputs (OK)

TODO:
- check for "help" icon and description for each column if description
  present
- Check if multiple rows and pages if it's working

Reviewed-by: andriac
[Refs ticket]: #4

* feat(front): wip server pagination & filtering

* feat(front): add sorting capability and fixes

Fix: keep filters (sort and filters) on when changing pages
Feat: remove useless id column
Feat: add sorting

* fix(db): change trigger to constraint (migrations)

Delete the trigger and create check constraint on id_nomenclature column

Reviewed-by: andriacap
[Refs ticket]: #3

* refactor: Custom type Geojson and group properties

Create custom type geojson and build properties into marshmallow schema
according to this type of Geojson

[Refs ticket]: #3

* refactor: site component with site-service

add function to site.services.ts

[Refs ticket]: #4

* refactor: create datatable component and service

Separate of concern for all about table (service and component)
and all about sites (service and component)

reviewed_by: andriacap
[Refs ticket]: #3

* fix: change offset to page

change offset name to page
for paginated

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

* merge: merge interface and type

merge site_group.service.ts
delete site.service.ts
add interfaces
re arrange code from branch front-site-interface

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

* feat: details properties sites groups

Create properties components to display properties of sites_groups
Adding route with id according to the site group selected

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

* feat: display groups sites's child

Display group site child into table and uder properties
Use routing and id params to display property of groups site

Create service site
Add logic to check routing and child route to display reactive component
properties and table

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

* refactor(front): rename interfaces, remove classes

Better types

* feat(front): get all geometries

For sites and sites_groups

* feat(front): WIP: geojson service to create layers

And features groups since geojson component accumulated layers
without cleaning them...

* feat(front): implemented select capability

Need to refact a lot!

* feat(api): add route to get one site_group by id

* fix(front): too much /

* feat(front): add get sites_group from id

* fix(front): add possibility to provide Geometry

To setMapData

* refactor(front): sites and sites_groups component

To extend a base component to gather the same methods at one place
To use route children and router-outlet properly
To add some rxjs operators
To move some common interfaces/functions

* fix(front): fix filters by adding baseFilters

* feat: edit sitegroups

Create edit-service
Create observable to update object to edit according to form component
Adapt function service of monitoring object to sites and sitesgroups

WIP:
- adapt remaining function of monitoring object inside
form-component-g
- create an object which inherit of patch/post/delete method with
- think about object with ObjectType of sites_group , sites, observation
  ? , visit ?
- reload properties or update data of properties table after update data

Reviewed-by:andriacap
[Refs_ticket]: #4

* feat: edit sitegroups

Improving route 'patch'
Adding reload properties when sitegroups is updated
Refactoring EditService for more readability

Reviewed-by: andriacap
[Ref_ticket]: #4

* feat: improve edit

Add method to define objectType according to service used (site or
gpsite)

Rewied-by: andriacap
[Refs_ticket]: #4

* feat: improve rendering front "edit" and "add"

Adding global object service to set object type and
variable "add" or "edit" button

Reviewed-by: andriacap

[Refs_ticket]: #4

* feat: improving patch method object

Adding service to share request patch,get in order to to re-use the
service in form component according to the type pass inside the
formcomponent
Adding decorator errorhandler for blueprint routes

Reviewed-by:andriacap
[Refs_ticket]: #4

* feat: create site group method with form

Route create with form component
Using location pacakage to choose between edit form or init sitegroup
(see to improve that)

Rewiewed-by: andriacap
[Refs ticket]: #4

* feat: delete site_group component

Add route back
Add site_group from front form component

Reviewed-by:andriac
[Refs_ticket]: #4

* chore(api): removed unused code

* style(config): apply formatter

* chore(front): removed unused code & console.log

* feat(front): removed display map button

As it was ugly

* refactor(front): remove Object for keys

As there is an Angular pipe to get keys and values
Also removed console.log
Removed unused code

* style(front): reformat routes

* refactor(front): add create component

To isolate functionnalities

* chore(front): remove unused services

* chore(front): removed usused code

* chore(api): remove string package

And fix if that cannot be reached

* chore(api): removed unused comment

* chore(front): removed console.log and comments

* chore(api): removed unused code and log

---------

Co-authored-by: Andria Capai <[email protected]>
  • Loading branch information
mvergez and andriacap committed Aug 31, 2023
1 parent 5f94db7 commit 75fc8cd
Show file tree
Hide file tree
Showing 55 changed files with 2,413 additions and 69 deletions.
14 changes: 8 additions & 6 deletions backend/gn_module_monitoring/config/generic/config.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
{
"tree": {
"module": {
"site": {
"visit": {
"observation": null
"sites_group": {
"site": {
"visit": {
"observation": null
}
}
}
}
},
"synthese" : "__MODULE.B_SYNTHESE",
"synthese": "__MODULE.B_SYNTHESE",
"default_display_field_names": {
"user": "nom_complet",
"nomenclature": "label_fr",
"dataset": "dataset_name",
"observer_list": "nom_liste",
"taxonomy" : "__MODULE.TAXONOMY_DISPLAY_FIELD_NAME",
"taxonomy": "__MODULE.TAXONOMY_DISPLAY_FIELD_NAME",
"taxonomy_list": "nom_liste",
"sites_group": "sites_group_name",
"habitat": "lb_hab_fr",
"area": "area_name",
"municipality": "nom_com_dept",
"site": "base_site_name"
}
}
}
15 changes: 0 additions & 15 deletions backend/gn_module_monitoring/config/generic/sites_group.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@
{"prop": "sites_group_code"}
],
"generic": {
"id_sites_group": {
"type_widget": "text",
"attribut_label": "Id site",
"hidden": true
},
"id_module": {
"type_widget": "text",
"attribut_label": "ID Module",
"hidden": true
},
"sites_group_name": {
"type_widget": "text",
"attribut_label": "Nom",
Expand All @@ -54,11 +44,6 @@
},
"nb_visits": {
"attribut_label": "Nombre de visites"
},
"medias": {
"type_widget": "medias",
"attribut_label": "Médias",
"schema_dot_table": "gn_monitoring.t_sites_groups"
}
}
}
Expand Down
43 changes: 40 additions & 3 deletions backend/gn_module_monitoring/monitoring/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
Modèles SQLAlchemy pour les modules de suivi
"""
from sqlalchemy import select, func, and_
from sqlalchemy.orm import column_property
from sqlalchemy.orm import column_property, ColumnProperty, RelationshipProperty, class_mapper
from sqlalchemy.dialects.postgresql import JSONB, UUID
from uuid import uuid4

from utils_flask_sqla.serializers import serializable
from utils_flask_sqla_geo.serializers import geoserializable

from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.ext.declarative import declared_attr

from pypnnomenclature.models import TNomenclatures, BibNomenclaturesTypes
from geonature.core.gn_commons.models import TMedias
Expand All @@ -21,6 +22,42 @@
from geonature.core.gn_monitoring.models import corVisitObserver
from gn_module_monitoring.monitoring.queries import Query as MonitoringQuery


class GenericModel:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()

@classmethod
def set_id(cls) -> None:
pk_string = class_mapper(cls).primary_key[0].name
if hasattr(cls,"id_g") ==False:
pk_value= getattr(cls,pk_string)
setattr(cls,"id_g",pk_value)

@classmethod
def get_id(cls) -> None:
pk_string = class_mapper(cls).primary_key[0].name
# print('======= ==>', pk_string)
if hasattr(cls,"id_g") ==False:
pk_value= getattr(cls,pk_string)
setattr(cls,"id_g",pk_value)
return pk_string

@classmethod
def find_by_id(cls, _id: int) -> "GenericModel":
cls.set_id()
return cls.query.get_or_404(_id)

@classmethod
def attribute_names(cls):
return [
prop.key
for prop in class_mapper(cls).iterate_properties
if isinstance(prop, ColumnProperty)
]


cor_module_type = DB.Table(
"cor_module_type",
DB.Column(
Expand Down Expand Up @@ -53,7 +90,7 @@


@serializable
class BibTypeSite(DB.Model):
class BibTypeSite(DB.Model, GenericModel):
__tablename__ = "bib_type_site"
__table_args__ = {"schema": "gn_monitoring"}
query_class = MonitoringQuery
Expand Down Expand Up @@ -238,7 +275,7 @@ class TMonitoringSites(TBaseSites):


@serializable
class TMonitoringSitesGroups(DB.Model):
class TMonitoringSitesGroups(DB.Model, GenericModel):
__tablename__ = 't_sites_groups'
__table_args__ = {'schema': 'gn_monitoring'}
query_class = MonitoringQuery
Expand Down
23 changes: 17 additions & 6 deletions backend/gn_module_monitoring/monitoring/schemas.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import json

import geojson
from marshmallow import Schema, fields
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from pypnnomenclature.schemas import NomenclatureSchema
from geonature.utils.env import MA
from marshmallow import Schema, fields, validate
from geonature.core.gn_commons.schemas import MediaSchema

from gn_module_monitoring.monitoring.models import (
BibTypeSite,
Expand All @@ -22,19 +22,30 @@ class PaginationSchema(Schema):
return PaginationSchema


class MonitoringSitesGroupsSchema(SQLAlchemyAutoSchema):
class MonitoringSitesGroupsSchema(MA.SQLAlchemyAutoSchema):

sites_group_name = fields.String(
validate=validate.Length(min=3,error="Length must be greater than 3"),)

class Meta:
model = TMonitoringSitesGroups
exclude = ("geom_geojson",)
load_instance = True

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

def set_pk(self,obj):
return self.Meta.model.get_id()

def serialize_geojson(self, obj):
if obj.geom_geojson is not None:
return json.loads(obj.geom_geojson)



class MonitoringSitesSchema(SQLAlchemyAutoSchema):
class MonitoringSitesSchema(MA.SQLAlchemyAutoSchema):
class Meta:
model = TMonitoringSites
exclude = ("geom_geojson", "geom")
Expand All @@ -46,7 +57,7 @@ def serialize_geojson(self, obj):
return geojson.dumps(obj.as_geofeature().get("geometry"))


class BibTypeSiteSchema(SQLAlchemyAutoSchema):
class BibTypeSiteSchema(MA.SQLAlchemyAutoSchema):
label = fields.Method("get_label_from_type_site")
# See if useful in the future:
# type_site = fields.Nested(NomenclatureSchema(only=("label_fr",)), dump_only=True)
Expand Down
3 changes: 1 addition & 2 deletions backend/gn_module_monitoring/routes/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ def get_types_site():

@blueprint.route("/sites/types/<int:id_type_site>", methods=["GET"])
def get_type_site_by_id(id_type_site):
query = BibTypeSite.query.filter_by(id_nomenclature_type_site=id_type_site)
res = query.first()
res = BibTypeSite.find_by_id(id_type_site)
schema = BibTypeSiteSchema()
return schema.dump(res)

Expand Down
51 changes: 51 additions & 0 deletions backend/gn_module_monitoring/routes/sites_groups.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from flask import jsonify, request
from geonature.utils.env import db
from marshmallow import ValidationError
from sqlalchemy import func
from werkzeug.datastructures import MultiDict

Expand All @@ -14,6 +15,7 @@
paginate,
sort,
)
from gn_module_monitoring.utils.errors.errorHandler import InvalidUsage


@blueprint.route("/sites_groups", methods=["GET"])
Expand All @@ -34,6 +36,13 @@ def get_sites_groups():
)


@blueprint.route("/sites_groups/<int:id_sites_group>", methods=["GET"])
def get_sites_group_by_id(id_sites_group: int):
schema = MonitoringSitesGroupsSchema()
result = TMonitoringSitesGroups.find_by_id(id_sites_group)
return jsonify(schema.dump(result))


@blueprint.route("/sites_groups/geometries", methods=["GET"])
def get_sites_group_geometries():
subquery = (
Expand All @@ -53,3 +62,45 @@ def get_sites_group_geometries():
result = geojson_query(subquery)

return jsonify(result)


@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


@blueprint.route("/sites_groups/<int:_id>", methods=["DELETE"])
def delete(_id):
item_schema = MonitoringSitesGroupsSchema()
item = TMonitoringSitesGroups.find_by_id(_id)
TMonitoringSitesGroups.query.filter_by(id_g=_id).delete()
db.session.commit()
return item_schema.dump(item), 201


@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


@blueprint.errorhandler(ValidationError)
def handle_validation_error(error):
return InvalidUsage(
"Fields cannot be validated, message : {}".format(error.messages), status_code=422, payload=error.data
).to_dict()
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@

@pytest.mark.usefixtures("client_class", "temporary_transaction")
class TestSitesGroups:
def test_get_sites_group_by_id(self, sites_groups):
sites_group = list(sites_groups.values())[0]
id_sites_group = sites_group.id_sites_group
r = self.client.get(
url_for("monitorings.get_sites_group_by_id", id_sites_group=id_sites_group)
)

assert r.json["id_sites_group"] == id_sites_group
assert r.json["sites_group_name"] == sites_group.sites_group_name

def test_get_sites_groups(self, sites_groups):
r = self.client.get(url_for("monitorings.get_sites_groups"))

Expand Down
Empty file.
19 changes: 19 additions & 0 deletions backend/gn_module_monitoring/utils/errors/errorHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from geonature.utils.errors import GeonatureApiError


class InvalidUsage(GeonatureApiError):
status_code = 400

def __init__(self, message, status_code=None, payload=None):
GeonatureApiError.__init__(self, message, status_code)
self.message = message
if status_code is not None:
self.status_code = status_code
self.payload = payload

def to_dict(self):
rv = {}
rv["payload"] = self.payload
rv["message"] = self.message
rv["status_code"] = self.status_code
return (rv, self.status_code)
30 changes: 30 additions & 0 deletions frontend/app/class/monitoring-geom-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PageInfo } from "../interfaces/page";
import { JsonData } from "../types/jsondata";

const LIMIT = 10;

type callbackFunction = (pageNumber: number, filters: JsonData) => void;

export class MonitoringGeomComponent {
protected getAllItemsCallback: callbackFunction;
protected limit = LIMIT;
public filters = {};
public baseFilters = {};

constructor() {}

setPage(page: PageInfo) {
this.getAllItemsCallback(page.offset + 1, this.filters);
}

setSort(filters: JsonData) {
this.filters = { ...this.baseFilters, ...filters };
const pageNumber = 1;
this.getAllItemsCallback(pageNumber, this.filters);
}

setFilter(filters) {
this.filters = { ...this.baseFilters, ...filters };
this.getAllItemsCallback(1, this.filters);
}
}
13 changes: 13 additions & 0 deletions frontend/app/class/monitoring-site.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export enum columnNameSite {
base_site_name = "Nom",
last_visit = "Dernière visite",
nb_visits = "Nb. visites",
base_site_code = "Code",
altitude_max = "Alt.max",
altitude_min = "Alt.min",
}

export const extendedDetailsSite = {
...columnNameSite,
base_site_description: "Description",
};
12 changes: 12 additions & 0 deletions frontend/app/class/monitoring-sites-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export enum columnNameSiteGroup {
sites_group_name = "Nom",
nb_sites = "Nb. sites",
nb_visits = "Nb. visites",
sites_group_code = "Code",
}

export const extendedDetailsSiteGroup = {
...columnNameSiteGroup,
comments: "Commentaires",
sites_group_description: "Description",
};
2 changes: 1 addition & 1 deletion frontend/app/components/modules/modules.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2 class="title-module mb-4">{{titleModule}}</h2>
<div class="col-md-4 col-sm-12 des-module"> {{description}}</div>
<!-- <div class="w-100 d-none d-sm-block"></div> -->
<div class="btn-module col-md-8 col-sm-12 text-center">
<button class="btn btn-success mr-2" (click)="onAccessSitesClick(modules)" *ngIf="currentUser">
<button class="btn btn-success mr-2" [routerLink]="'sites_group'" *ngIf="currentUser">
Accès aux sites
</button>
<div class="container py-5 border rounded m-2">
Expand Down
Loading

0 comments on commit 75fc8cd

Please sign in to comment.