From 8640a43c6828ca37e7259991239055eb577a7da9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sabine=20Schr=C3=B6der?= <s.schroeder@fz-juelich.de>
Date: Sat, 3 Oct 2020 13:59:58 +0200
Subject: [PATCH] #17 and #9: stationmeta patch API writes (one) entry to
 changelog table; schema shows changelog table when requesting stationmeta
 from database

---
 toardb/stationmeta/crud.py             | 12 ++++++++++--
 toardb/stationmeta/models_changelog.py | 10 +++++-----
 toardb/stationmeta/models_core.py      |  1 +
 toardb/stationmeta/schemas.py          | 22 ++++++++++++++++++++++
 toardb/stationmeta/stationmeta.py      |  4 ++--
 toardb/timeseries/models_changelog.py  |  6 +++---
 6 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/toardb/stationmeta/crud.py b/toardb/stationmeta/crud.py
index 0a74642..a2842f1 100644
--- a/toardb/stationmeta/crud.py
+++ b/toardb/stationmeta/crud.py
@@ -12,7 +12,8 @@ from sqlalchemy.dialects.postgresql import JSONB, ARRAY
 from fastapi import File, UploadFile
 from fastapi.responses import JSONResponse
 from . import models
-from .models import StationmetaCore, stationmeta_core_stationmeta_roles_table, stationmeta_core_stationmeta_annotations_table, \
+from .models import StationmetaCore, StationmetaChangelog, stationmeta_core_stationmeta_roles_table, \
+                    stationmeta_core_stationmeta_annotations_table, \
                     CZ_enum, CV_enum, ST_enum, TA_enum
 from toardb.generic.models import RS_enum, RC_enum
 from .schemas import get_coordinates_from_geom, get_geom_from_coordinates, StationmetaCreate, StationmetaPatch, Coordinates
@@ -174,7 +175,7 @@ def create_stationmeta(db: Session, stationmeta: StationmetaCreate):
     db_stationmeta.coordinates = tmp_coordinates
     return db_stationmeta
 
-def patch_stationmeta(db: Session, station_id: int, stationmeta: StationmetaPatch):
+def patch_stationmeta(db: Session, description: str, station_id: int, stationmeta: StationmetaPatch):
     stationmeta_dict = stationmeta.dict()
     roles_data         = stationmeta_dict.pop('roles', None)
     annotations_data   = stationmeta_dict.pop('annotations', None)
@@ -183,6 +184,8 @@ def patch_stationmeta(db: Session, station_id: int, stationmeta: StationmetaPatc
     aux_urls_data      = stationmeta_dict.pop('aux_urls', None)
     globalmeta_data    = stationmeta_dict.pop('globalmeta', None)
     globalservice_data = stationmeta_dict.pop('globalservice', None)
+    # prepare changelog entry/entries
+    db_changelog = StationmetaChangelog(description=description, station_id=station_id, author_id=1, type_of_change=1)
     # there's a mismatch with coordinates --> how to automatically switch back and forth?!
     # ==> the following two commands are not working
     # ==> workaround
@@ -194,7 +197,9 @@ def patch_stationmeta(db: Session, station_id: int, stationmeta: StationmetaPatc
     db_stationmeta.coordinates = get_geom_from_coordinates(db_stationmeta.coordinates)
     for k, v in stationmeta_dict.items():
         if v is not None:
+            db_changelog.old_value=str(getattr(db_stationmeta,k))
             setattr(db_stationmeta,k,stationmeta_dict[k])
+            db_changelog.new_value=str(getattr(db_stationmeta,k))
     result = db.commit()
     db.refresh(db_stationmeta)
     # store roles and update association table
@@ -269,6 +274,9 @@ def patch_stationmeta(db: Session, station_id: int, stationmeta: StationmetaPatc
         db.add(db_globalservice)
         db.commit()
         db.refresh(db_globalservice)
+    # add patch to changelog table
+    db.add(db_changelog)
+    db.commit()
     # there's a mismatch with coordinates --> how to automatically switch back and forth?!
     db_stationmeta.coordinates = tmp_coordinates
     return db_stationmeta
diff --git a/toardb/stationmeta/models_changelog.py b/toardb/stationmeta/models_changelog.py
index 3298370..3848c97 100644
--- a/toardb/stationmeta/models_changelog.py
+++ b/toardb/stationmeta/models_changelog.py
@@ -3,7 +3,7 @@
 class StationmetaChangelog(Base)
 ================================
 """
-from sqlalchemy import Column, DateTime, BigInteger, ForeignKey, String, \
+from sqlalchemy import Column, DateTime, BigInteger, ForeignKey, String, text, \
                        Text, CheckConstraint, Table, Sequence
 from sqlalchemy.orm import relationship
 from .models_core import StationmetaCore
@@ -19,7 +19,7 @@ class StationmetaChangelog(Base):
     +================+==========================+===========+==========+========================================================+
     | id             | bigint                   |           | not null | nextval('stationmeta_changelog_id_seq'::regclass)      |
     +----------------+--------------------------+-----------+----------+--------------------------------------------------------+
-    | datetime       | timestamp with time zone |           | not null |                                                        |
+    | datetime       | timestamp with time zone |           | not null | now()                                                  |
     +----------------+--------------------------+-----------+----------+--------------------------------------------------------+
     | station_id     | bigint                   |           | not null |                                                        |
     +----------------+--------------------------+-----------+----------+--------------------------------------------------------+
@@ -43,17 +43,17 @@ class StationmetaChangelog(Base):
     __tablename__ = 'stationmeta_changelog'
 
     id = Column(BigInteger, STATIONMETA_CHANGELOG_ID_SEQ, primary_key=True, server_default=STATIONMETA_CHANGELOG_ID_SEQ.next_value())
-    datetime = Column(DateTime(True), nullable=False)
+    datetime = Column(DateTime(True), nullable=False, server_default=text("now()"))
     description = Column(Text, nullable=False)
     old_value = Column(String(256), nullable=False)
     new_value = Column(String(256), nullable=False)
 # do not use string declaration here (not working for pytest)
 # use the explicit class name here,
 # see: https://groups.google.com/forum/#!topic/sqlalchemy/YjGhE4d6K4U
-    station_id = Column(ForeignKey(StationmetaCore.id), nullable=False)
+    station_id = Column(ForeignKey(StationmetaCore.id, deferrable=True, initially='DEFERRED'), nullable=False)
     author_id = Column(ForeignKey(AuthUser.id), nullable=False)
 # still to check for some pytest solution for controlled vocabulary
-    type_of_change = Column(ForeignKey('cl_Vocabulary.enum_val'), nullable=False)
+    type_of_change = Column(ForeignKey('cl_vocabulary.enum_val'), nullable=False)
 
     author = relationship('AuthUser')
     station = relationship('StationmetaCore')
diff --git a/toardb/stationmeta/models_core.py b/toardb/stationmeta/models_core.py
index 7e26330..92262f5 100644
--- a/toardb/stationmeta/models_core.py
+++ b/toardb/stationmeta/models_core.py
@@ -87,6 +87,7 @@ class StationmetaCore_WithoutCoords(Base):
     aux_urls = relationship('StationmetaAuxUrl', back_populates='station')
     globalmeta = relationship('StationmetaGlobal', uselist=False, back_populates='station')
     globalservice = relationship('StationmetaGlobalService', uselist=False, back_populates='station')
+    changelog = relationship('StationmetaChangelog', back_populates='station')
 
     # for nested view
     timeseries = relationship("Timeseries", back_populates="station")
diff --git a/toardb/stationmeta/schemas.py b/toardb/stationmeta/schemas.py
index 67266c6..520d3fc 100644
--- a/toardb/stationmeta/schemas.py
+++ b/toardb/stationmeta/schemas.py
@@ -448,6 +448,27 @@ class StationmetaRole(StationmetaRoleBase):
     class Config:
         orm_mode = True
 
+
+# ======== StationmetaChangelog =========
+
+class StationmetaChangelogBase(BaseModel):
+    id: int
+    datetime: dt.datetime
+    description: str
+    old_value: str
+    new_value: str
+    station_id: int
+    author_id: int
+    type_of_change: int
+
+
+class StationmetaChangelog(StationmetaChangelogBase):
+    id: int
+
+    class Config:
+        orm_mode = True
+
+
 # ======== for nested view/upload =========
 
 class StationmetaBase(StationmetaCoreBase):
@@ -457,6 +478,7 @@ class StationmetaBase(StationmetaCoreBase):
     aux_docs: List[StationmetaAuxDoc] = None
     aux_urls: List[StationmetaAuxUrl] = None
     globalmeta: StationmetaGlobal = None
+    changelog: List[StationmetaChangelog] = None
 
     class Config:
        orm_mode = True
diff --git a/toardb/stationmeta/stationmeta.py b/toardb/stationmeta/stationmeta.py
index 240a3a9..ddc5261 100644
--- a/toardb/stationmeta/stationmeta.py
+++ b/toardb/stationmeta/stationmeta.py
@@ -60,11 +60,11 @@ def create_stationmeta_core(stationmeta: schemas.StationmetaCreate = Body(..., e
     return crud.create_stationmeta(db=db, stationmeta=stationmeta)
 
 @router.patch('/stationmeta/{station_code}', response_model=schemas.StationmetaPatch)
-def patch_stationmeta_core(station_code: str, stationmeta: schemas.StationmetaPatch = Body(..., embed = True), db: Session = Depends(get_db)):
+def patch_stationmeta_core(station_code: str, description: str, stationmeta: schemas.StationmetaPatch = Body(..., embed = True), db: Session = Depends(get_db)):
     db_stationmeta = crud.get_stationmeta(db, station_code=station_code)
     if db_stationmeta is None:
         raise HTTPException(status_code=404, detail="Station for patching not found.")
-    return crud.patch_stationmeta(db=db, station_id=db_stationmeta.id, stationmeta=stationmeta)
+    return crud.patch_stationmeta(db=db, description=description, station_id=db_stationmeta.id, stationmeta=stationmeta)
 
 @router.patch('/stationmeta/delete_field/{station_code}', response_model=schemas.StationmetaPatch)
 def patch_stationmeta_core(station_code: str, field: str, db: Session = Depends(get_db)):
diff --git a/toardb/timeseries/models_changelog.py b/toardb/timeseries/models_changelog.py
index c4ac170..3eb58ff 100644
--- a/toardb/timeseries/models_changelog.py
+++ b/toardb/timeseries/models_changelog.py
@@ -3,7 +3,7 @@
 class TimeseriesChangelog(Base)
 ===============================
 """
-from sqlalchemy import Column, DateTime, ForeignKey, BigInteger, String, CHAR, \
+from sqlalchemy import Column, DateTime, ForeignKey, BigInteger, String, CHAR, text, \
                        Text, Table, Sequence
 from sqlalchemy.orm import relationship
 from .models_core import Timeseries
@@ -19,7 +19,7 @@ class TimeseriesChangelog(Base):
     +================+==========================+===========+==========+==================================================+
     | id             | bigint                   |           | not null | nextval('timeseries_changelog_id_seq'::regclass) |
     +----------------+--------------------------+-----------+----------+--------------------------------------------------+
-    | datetime       | timestamp with time zone |           | not null |                                                  |
+    | datetime       | timestamp with time zone |           | not null | now()                                            |
     +----------------+--------------------------+-----------+----------+--------------------------------------------------+
     | timeseries_id  | bigint                   |           | not null |                                                  |
     +----------------+--------------------------+-----------+----------+--------------------------------------------------+
@@ -50,7 +50,7 @@ class TimeseriesChangelog(Base):
     __tablename__ = 'timeseries_changelog'
 
     id = Column(BigInteger, TIMESERIES_CHANGELOG_ID_SEQ, primary_key=True, server_default=TIMESERIES_CHANGELOG_ID_SEQ.next_value())
-    datetime = Column(DateTime(True), nullable=False)
+    datetime = Column(DateTime(True), nullable=False, server_default=text("now()"))
     description = Column(Text, nullable=False)
     old_value = Column(String(256), nullable=False)
     new_value = Column(String(256), nullable=False)
-- 
GitLab