Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/crud/gp sites components #38

Merged
merged 44 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a093d9a
feat(front): wip sites_groups component and svc
Dec 23, 2022
ce64039
WIP feat(front): DataTable sites_groups
andriacap Dec 26, 2022
3ee07bc
feat(front): Datatable format and selected row
andriacap Dec 27, 2022
1ec9c79
feat(front): Filtering table (OK)
andriacap Dec 27, 2022
673c180
feat(front): wip server pagination & filtering
Jan 2, 2023
c3194cc
feat(front): add sorting capability and fixes
Jan 4, 2023
4e0d1ad
fix(db): change trigger to constraint (migrations)
andriacap Jan 16, 2023
15a7a10
refactor: Custom type Geojson and group properties
andriacap Jan 17, 2023
f10e201
refactor: site component with site-service
andriacap Jan 17, 2023
acf9ff2
refactor: create datatable component and service
andriacap Jan 18, 2023
868348c
fix: change offset to page
andriacap Jan 19, 2023
7b6340a
merge: merge interface and type
andriacap Jan 19, 2023
49a9bd5
feat: details properties sites groups
andriacap Jan 19, 2023
8a4e434
feat: display groups sites's child
andriacap Jan 20, 2023
f965a6a
refactor(front): rename interfaces, remove classes
Jan 19, 2023
c5e1917
feat(front): get all geometries
Jan 23, 2023
7580095
feat(front): WIP: geojson service to create layers
Jan 24, 2023
d203a11
feat(front): implemented select capability
Jan 24, 2023
dc09191
feat(api): add route to get one site_group by id
Jan 27, 2023
f6489ad
fix(front): too much /
Jan 27, 2023
314584a
feat(front): add get sites_group from id
Jan 27, 2023
73a6153
fix(front): add possibility to provide Geometry
Jan 27, 2023
8127226
refactor(front): sites and sites_groups component
Jan 27, 2023
409d81f
fix(front): fix filters by adding baseFilters
Jan 27, 2023
2748431
feat: edit sitegroups
andriacap Jan 31, 2023
6efe45f
feat: edit sitegroups
andriacap Feb 1, 2023
36e9434
feat: improve edit
andriacap Feb 1, 2023
de2118a
feat: improve rendering front "edit" and "add"
andriacap Feb 1, 2023
e72ee0a
feat: improving patch method object
andriacap Feb 2, 2023
2b3632a
feat: create site group method with form
andriacap Feb 20, 2023
d234330
feat: delete site_group component
andriacap Feb 20, 2023
c4a82bf
chore(api): removed unused code
Feb 21, 2023
47c825c
style(config): apply formatter
Feb 21, 2023
601cb2c
chore(front): removed unused code & console.log
Feb 21, 2023
1fc5dfc
feat(front): removed display map button
Feb 21, 2023
36ba4e3
refactor(front): remove Object for keys
Feb 21, 2023
05e3fb0
style(front): reformat routes
Feb 21, 2023
6d1ebb7
refactor(front): add create component
Feb 22, 2023
ce30a88
chore(front): remove unused services
Feb 22, 2023
3c04ec6
chore(front): removed usused code
Feb 22, 2023
fca2f5a
chore(api): remove string package
Feb 22, 2023
7456009
chore(api): removed unused comment
Feb 22, 2023
c3b5b7b
chore(front): removed console.log and comments
Feb 22, 2023
fb0c29a
chore(api): removed unused code and log
Feb 24, 2023
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
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 @@ -247,7 +284,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):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mettre MA.SQLAlchemyAutoSchema partout ?


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)
14 changes: 8 additions & 6 deletions config/monitoring/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 config/monitoring/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
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