diff --git a/production_tests.sh b/production_tests.sh index b0d7d83ecc39ced5bb9b65c8f57ea6d80bd92290..e2934438b24c98a41da54e90d6f0e45898bacbfc 100755 --- a/production_tests.sh +++ b/production_tests.sh @@ -25,6 +25,11 @@ curl -X POST -H "Content-Type:application/json" -d '{"stationmeta_core": {"codes curl http://127.0.0.1:8000/timeseries/ curl http://127.0.0.1:8000/timeseries/2 +# timeseries upload without nested fields curl -X POST -H "Content-Type:application/json" -d '{"timeseries": {"label": "CMA2", "order": 1, "access_rights": 0, "sampling_frequency": 0, "aggregation": 0, "data_start_date": "2003-09-07T15:30:00+02:00", "data_end_date": "2016-12-31T14:30:00+01:00", "measurement_method": "UV absorption", "sampling_height": 7.0, "date_added": "2020-05-15T15:30:00+02:00", "date_modified": "2020-05-16T09:30:00+02:00", "station_id": 2, "variable_id": 7, "additional_metadata":"{}"}}' http://127.0.0.1:8000/timeseries/ +# nested upload with enum fields +curl -X POST -H "Content-Type:application/json" -d '{"timeseries": {"label": "CMA5", "order": 1, "access_rights": 0, "sampling_frequency": 0, "aggregation": 0, "data_start_date": "2003-09-07T15:30:00+02:00", "data_end_date": "2016-12-31T14:30:00+01:00", "measurement_method": "UV absorption", "sampling_height": 7.0, "date_added": "2020-05-15T15:30:00+02:00", "date_modified": "2020-05-16T09:30:00+02:00", "station_id": 2, "variable_id": 7, "additional_metadata":"{}", "roles": [{"role": 0, "person_id": 3, "status": 0},{"role": 1, "person_id": 3, "status": 0}]}}' http://127.0.0.1:8000/timeseries/ +# nested upload with human readable fields +curl -X POST -H "Content-Type:application/json" -d '{"timeseries15:30:00+02:00", "data_end_date": "2016-12-31T14:30:00+01:00", "measurement_method": "UV absorption", "sampling_height": 7.0, "date_added": "2020-05-15T15:30:00+02:00", "date_modified": "2020-05-16T09:30:00+02:00", "station_id": 2, "variable_id": 7, "additional_metadata":"{}", "roles": [{"role": "PointOfContact", "person": "s.schroeder@fz-juelich.de", "status": "active"},{"role": "Originator", "person": "Stefan.Feigenspan@uba.de", "status": "active"}]}}' http://127.0.0.1:8000/timeseries/ curl -X POST -H 'Content-Type: multipart/form-data; charset=utf-8; boundary=__X_PAW_BOUNDARY__' -F "file=@o3_CO002_2012_2017_v1-0.dat" "http://127.0.0.1:8000/data/" diff --git a/toardb/timeseries/crud.py b/toardb/timeseries/crud.py index 7abde3481ef1108e29f592650953c15dcc1d93e2..f8dc8369fc2f2316621df35d02444bbfcf695d31 100644 --- a/toardb/timeseries/crud.py +++ b/toardb/timeseries/crud.py @@ -7,6 +7,7 @@ Create, Read, Update, Delete functionality from sqlalchemy.orm import Session from fastapi.responses import JSONResponse from . import models +from .models import RS_enum, RC_enum from .schemas import TimeseriesCreate @@ -23,6 +24,11 @@ def get_all_timeseries(db: Session, skip : int = 0, limit: int = None): for db_object in db_objects: # there is a mismatch with additional_metadata db_object.additional_metadata = str(db_object.additional_metadata) + # add roles (model does not have them!) + # do I need a second model?! (not related to the real database?!) + roles = get_role_ids_of_timeseries(db, int(db_object.id)) + print("====================roles ==================", db_object.id, roles) + # now return for schema TimeseriesCore (model.Timeseries has no roles!) return db_objects @@ -37,8 +43,37 @@ def get_timeseries_by_unique_constraints(db: Session, station_id: int, variable_ return db_object +# is this internal, or should this also go to public REST api? +def get_role_ids_of_timeseries(db: Session, timeseries_id: int): + db_objects = db.query(models.TimeseriesTimeseriesRoles) \ + .filter(models.TimeseriesTimeseriesRoles.timeseries_id == timeseries_id) \ + .all() + return db_objects + + +# is this internal, or should this also go to public REST api? +def get_unique_timeseries_role(db: Session, role: int, person_id: int, status: int): + db_object = db.query(models.TimeseriesRole).filter(models.TimeseriesRole.role == role) \ + .filter(models.TimeseriesRole.person_id == person_id) \ + .filter(models.TimeseriesRole.status == status) \ + .first() + return db_object + + +def __get_status_enum(db: Session): + return db.query(RS_enum).all() + + +def __get_role_enum(db: Session): + return db.query(RC_enum).all() + + def create_timeseries(db: Session, timeseries: TimeseriesCreate): - db_timeseries = models.Timeseries (**timeseries.dict()) + enum_RS = __get_status_enum(db) + enum_RC = __get_role_enum(db) + timeseries_dict = timeseries.dict() + roles_data = timeseries_dict.pop('roles', None) + db_timeseries = models.Timeseries(**timeseries_dict) # there's also a mismatch with additional_metadata --> BUT: this should not be switched back! # in upload command, we have now: "additional_metadata": "{}" # but return from this method gives: "additional_metadata": {} @@ -47,4 +82,29 @@ def create_timeseries(db: Session, timeseries: TimeseriesCreate): db.add(db_timeseries) result = db.commit() db.refresh(db_timeseries) + # get timeseries_id + timeseries_id = db_timeseries.id + # store roles and update associaton table + for r in roles_data: + # try which format is used + try: + db_role = models.TimeseriesRole(**r) + except: + print ("human readable format: ", roles_data) + # check whether role is already present in database + db_object = get_unique_timeseries_role(db, db_role.role, db_role.person_id, db_role.status) + if db_object: + role_id = db_object.id + else: + db.add(db_role) + db.commit() + db.refresh(db_role) + role_id = db_role.id + r['id'] = role_id + # now into associaton table! + db_assoc = models.TimeseriesTimeseriesRoles(timeseries_id=timeseries_id, role_id=role_id) + db.add(db_assoc) + db.commit() + db.refresh(db_assoc) + setattr(db_timeseries,'roles',roles_data) return db_timeseries diff --git a/toardb/timeseries/models.py b/toardb/timeseries/models.py index 475ca4805a09c5c01ac96104544e7d06e6a722f4..400dbd8a02c85a70313169d8fd8c891a12ad7170 100644 --- a/toardb/timeseries/models.py +++ b/toardb/timeseries/models.py @@ -3,6 +3,8 @@ from sqlalchemy import MetaData from sqlalchemy.ext.declarative import declarative_base from .models_core import ( Timeseries, + TimeseriesRole, + TimeseriesTimeseriesRoles, Base as TimeseriesBase) from .models_annotation import ( TimeseriesAnnotation, @@ -14,7 +16,7 @@ from .models_programme import ( Base = declarative_base() metadata = MetaData() -from sqlalchemy import Column, ForeignKey, Integer +from sqlalchemy import Table, String, Column, ForeignKey, Integer from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() @@ -92,3 +94,17 @@ Foreign-key constraints: ## role_id = Column(Integer, ForeignKey(TimeseriesRole.id), primary_key=True, nullable=False) +# controlled vocabulary +RS_enum = Table("rs_vocabulary", + metadata, + Column("enum_val", Integer, primary_key=True), + Column("enum_str", String), + Column("enum_display_str", String) + ) + +RC_enum = Table("rc_vocabulary", + metadata, + Column("enum_val", Integer, primary_key=True), + Column("enum_str", String), + Column("enum_display_str", String) + ) diff --git a/toardb/timeseries/models_core.py b/toardb/timeseries/models_core.py index f0d9556255e56b4da54f7aae18987f33beda5457..1ac610f18169e7407d7e2a75202e95ce01ba2d25 100644 --- a/toardb/timeseries/models_core.py +++ b/toardb/timeseries/models_core.py @@ -63,7 +63,7 @@ class TimeseriesTimeseriesRoles(Base): # role_id = Column(ForeignKey(TimeseriesRole.id, deferrable=True, initially='DEFERRED')) timeseries_id = Column(ForeignKey('timeseries.id', deferrable=True, initially='DEFERRED')) role_id = Column(ForeignKey('timeseries_roles.id', deferrable=True, initially='DEFERRED')) -## timeseries = relationship(Timeseries, back_populates = 'role') +## timeseries = relationship(Timeseries, back_populates = 'roles') ## roles = relationship(TimeseriesRole, back_populates = 'timeseries') @@ -108,10 +108,10 @@ class TimeseriesRole(Base): # see: https://groups.google.com/forum/#!topic/sqlalchemy/YjGhE4d6K4U person_id = Column(ForeignKey(Person.id, deferrable=True, initially='DEFERRED'), nullable=False, index=True) # how to reactivate the following line -# person = relationship('Person') + person = relationship(Person) # how to reactivate the following line?! - timeseries_id = Column(Integer, ForeignKey(Timeseries.id), nullable=False) +# timeseries_id = Column(Integer, ForeignKey(Timeseries.id), nullable=False) # timeseries = relationship(Timeseries, secondary=timeseries_timeseries_roles, backref="timeseries") timeseries = relationship(Timeseries, secondary=TimeseriesTimeseriesRoles.__table__, backref="timeseries") diff --git a/toardb/timeseries/schemas.py b/toardb/timeseries/schemas.py index 8cf229e3b5261dc65ba21224055684094741ed59..ec24f36b529a6dd2b85727b5250912b9f3e26e76 100644 --- a/toardb/timeseries/schemas.py +++ b/toardb/timeseries/schemas.py @@ -11,7 +11,7 @@ import datetime as dt # ======== Timeseries ========= -class TimeseriesBase(BaseModel): +class TimeseriesCoreBase(BaseModel): id: int = None label: str order: int @@ -29,11 +29,11 @@ class TimeseriesBase(BaseModel): additional_metadata: Json -class TimeseriesCreate(TimeseriesBase): +class TimeseriesCoreCreate(TimeseriesCoreBase): pass -class Timeseries(TimeseriesBase): +class TimeseriesCore(TimeseriesCoreBase): id: int class Config: @@ -46,8 +46,6 @@ class TimeseriesRoleBase(BaseModel): role: int status: int person_id: int - timeseries_id: int - class TimeseriesRoleCreate(TimeseriesRoleBase): pass @@ -101,3 +99,24 @@ class TimeseriesProgramme(TimeseriesProgrammeBase): class Config: orm_mode = True +# ======== for nested view/upload ========= + +class TimeseriesBase(TimeseriesCoreBase): + roles: List[TimeseriesRole] + + class Config: + orm_mode = True + +class TimeseriesCreate(TimeseriesCoreBase): + roles: List[TimeseriesRoleBase] + + class Config: + orm_mode = True + + +class Timeseries(TimeseriesBase): + id: int + + class Config: + orm_mode = True + diff --git a/toardb/timeseries/timeseries.py b/toardb/timeseries/timeseries.py index 8c0fa82db69367c7fd4cd96b86545eb82f0ed1ba..52d6b1cc6cba49545836ae25cb92c4279739f565 100644 --- a/toardb/timeseries/timeseries.py +++ b/toardb/timeseries/timeseries.py @@ -23,7 +23,7 @@ def get_db(): # plain views to post and get timeseries #get all entries of table timeseries -@router.get('/timeseries/', response_model=List[schemas.Timeseries]) +@router.get('/timeseries/', response_model=List[schemas.TimeseriesCore]) def get_all_timeseries(skip: int = 0, limit: int = None, db: Session = Depends(get_db)): timeseries = crud.get_all_timeseries(db, skip=skip, limit=limit) return timeseries