From 8fbb8ffc31430b45308dbeb6b9766c06f2c1ba28 Mon Sep 17 00:00:00 2001
From: schroeder5 <s.schroeder@fz-juelich.de>
Date: Sat, 4 Jul 2020 22:52:20 +0200
Subject: [PATCH] #5: model organisation is integrated into contact; TBD:
 nested download not yet working as desired

---
 toardb/contacts/contacts.py       | 21 +++++++--------
 toardb/contacts/crud.py           | 12 +++++++++
 toardb/contacts/models.py         | 43 ++++++++++++++++++++++++++++++-
 toardb/contacts/schemas.py        | 10 ++++++-
 toardb/stationmeta/crud.py        |  6 ++---
 toardb/stationmeta/models_role.py | 12 ++++-----
 toardb/stationmeta/schemas.py     |  7 ++++-
 toardb/timeseries/crud.py         |  6 ++---
 toardb/timeseries/models_role.py  | 16 ++++++------
 toardb/timeseries/schemas.py      |  5 +++-
 10 files changed, 101 insertions(+), 37 deletions(-)

diff --git a/toardb/contacts/contacts.py b/toardb/contacts/contacts.py
index 171a549..210c090 100644
--- a/toardb/contacts/contacts.py
+++ b/toardb/contacts/contacts.py
@@ -12,6 +12,8 @@ router = APIRouter()
 
 # plain views to post and get contacts
 
+# 1. persons
+
 #get all entries of table persons
 @router.get('/contacts/persons/', response_model=List[schemas.Person])
 def get_all_persons(skip: int = 0, limit: int = None, db: Session = Depends(get_db)):
@@ -33,11 +35,6 @@ def get_person(name: str, db: Session = Depends(get_db)):
         raise HTTPException(status_code=404, detail="Person not found.")
     return db_person
 
-#some more gets to be inserted:
-#
-#
-#
-
 @router.post('/contacts/persons/', response_model=schemas.Person)
 def create_person(person: schemas.PersonCreate = Body(..., embed = True), db: Session = Depends(get_db)):
     db_person = crud.get_person_by_name(db, name=person.name)
@@ -46,7 +43,7 @@ def create_person(person: schemas.PersonCreate = Body(..., embed = True), db: Se
     return crud.create_person(db=db, person=person)
 
 
-# plain views to post and get organisations
+# 2. organisations
 
 #get all entries of table organisations
 @router.get('/contacts/organisations/', response_model=List[schemas.Organisation])
@@ -69,12 +66,6 @@ def get_organisation(name: str, db: Session = Depends(get_db)):
         raise HTTPException(status_code=404, detail="Organisation not found.")
     return db_organisation
 
-
-#some more gets to be inserted:
-#
-#
-#
-
 @router.post('/contacts/organisations/', response_model=schemas.Organisation)
 def create_organisation(organisation: schemas.OrganisationCreate = Body(..., embed = True), db: Session = Depends(get_db)):
     db_organisation = crud.get_organisation_by_name(db, name=organisation.name)
@@ -82,3 +73,9 @@ def create_organisation(organisation: schemas.OrganisationCreate = Body(..., emb
         raise HTTPException(status_code=400, detail="Organisation already registered.")
     return crud.create_organisation(db=db, organisation=organisation)
 
+# 3. combined (person + organisation)
+#get all entries of table contacts
+@router.get('/contacts/', response_model=List[schemas.Contact])
+def get_all_contacts(skip: int = 0, limit: int = None, db: Session = Depends(get_db)):
+    contacts = crud.get_all_contacts(db, skip=skip, limit=limit)
+    return contacts
diff --git a/toardb/contacts/crud.py b/toardb/contacts/crud.py
index 5a3460d..9ad7c03 100644
--- a/toardb/contacts/crud.py
+++ b/toardb/contacts/crud.py
@@ -30,6 +30,10 @@ def create_organisation(db: Session, organisation: OrganisationCreate):
     db.add(db_organisation)
     result = db.commit()
     db.refresh(db_organisation)
+    db_contact = models.Contact(person_id=0,organisation_id=db_organisation)
+    db.add(db_contact)
+    result = db.commit()
+    db.refresh(db_contact)
     return db_organisation
 
 
@@ -49,4 +53,12 @@ def create_person(db: Session, person: PersonCreate):
     db.add(db_person)
     result = db.commit()
     db.refresh(db_person)
+    db_contact = models.Contact(person_id=db_person.id,organisation_id=0)
+    db.add(db_contact)
+    result = db.commit()
+    db.refresh(db_contact)
     return db_person
+
+
+def get_all_contacts(db: Session, skip : int = 0, limit: int = None):
+    return db.query(models.Contact).offset(skip).limit(limit).all()
diff --git a/toardb/contacts/models.py b/toardb/contacts/models.py
index c4cbe6e..0c33a6e 100644
--- a/toardb/contacts/models.py
+++ b/toardb/contacts/models.py
@@ -1,11 +1,18 @@
 # coding: utf-8
+"""
+class Contact(Base)
+===================
+"""
+
 from toardb.base import Base
 
 from .models_person import Person
 from .models_organisation import Organisation
 
-from sqlalchemy import Table, Column, Integer, String
+from sqlalchemy import Table, Column, Integer, String, \
+                       ForeignKey, text, CheckConstraint, Sequence
 from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship
 
 # controlled vocabulary
 
@@ -29,3 +36,37 @@ OK_enum = (
     Enumdict(7, 'Other', 'other')
     )
 
+CONTACTS_ID_SEQ = Sequence('contacts_id_seq')  # define sequence explicitly
+class Contact(Base):
+    """ Table "public.contacts"
+
+    +-----------------+---------+-----------+----------+----------------------------------------+
+    |     Column      |  Type   | Collation | Nullable |                Default                 |
+    +=================+=========+===========+==========+========================================+
+    | id              | integer |           | not null | nextval('contacts_id_seq'::regclass)   |
+    +-----------------+---------+-----------+----------+----------------------------------------+
+    | person_id       | integer |           |          |                                        |
+    +-----------------+---------+-----------+----------+----------------------------------------+
+    | organisation_id | integer |           |          |                                        |
+    +-----------------+---------+-----------+----------+----------------------------------------+
+    Indexes:
+        "contacts_pkey" PRIMARY KEY, btree (id)
+    Check constraints:
+        "ck_contact" CHECK (person_id > 0 AND organisation_id = 0 OR person_id = 0 AND organisation_id > 0)
+    Foreign-key constraints:
+        "contacts_organisation_id_fkey" FOREIGN KEY (organisation_id) REFERENCES organisations(id)
+        "contacts_person_id_fkey" FOREIGN KEY (person_id) REFERENCES persons(id)
+    """
+
+    __tablename__ = 'contacts'
+    __table_args__ = (
+        CheckConstraint('((person_id > 0) AND (organisation_id = 0)) OR ((person_id = 0) AND (organisation_id > 0))'),
+    )
+
+    id = Column(Integer, primary_key=True, server_default=text("nextval('contacts_id_seq'::regclass)"))
+    person_id = Column(ForeignKey('persons.id'))
+    organisation_id = Column(ForeignKey('organisations.id'))
+
+    organisation = relationship('Organisation')
+    person = relationship('Person')
+
diff --git a/toardb/contacts/schemas.py b/toardb/contacts/schemas.py
index 25cd340..296b88a 100644
--- a/toardb/contacts/schemas.py
+++ b/toardb/contacts/schemas.py
@@ -3,7 +3,7 @@ Pydantic schemas for TOAR database
 
 """
 
-from typing import List
+from typing import List, Dict, Any
 
 from pydantic import BaseModel, validator
 from .models import OK_enum
@@ -61,4 +61,12 @@ class Person(PersonBase):
     class Config:
         orm_mode = True
 
+# ======== for nested view =========
+
+class Contact(BaseModel):
+    person: Person
+    organisation: Organisation
+
+    class Config:
+        orm_mode = True
 
diff --git a/toardb/stationmeta/crud.py b/toardb/stationmeta/crud.py
index 47cce8b..24a21c4 100644
--- a/toardb/stationmeta/crud.py
+++ b/toardb/stationmeta/crud.py
@@ -58,9 +58,9 @@ def get_all_stationmeta(db: Session, skip : int = 0, limit: int = None):
 
 
 # is this internal, or should this also go to public REST api?
-def get_unique_stationmeta_role(db: Session, role: int, person_id: int, status: int):
+def get_unique_stationmeta_role(db: Session, role: int, contact_id: int, status: int):
     db_object = db.query(models.StationmetaRole).filter(models.StationmetaRole.role == role) \
-                                                .filter(models.StationmetaRole.person_id == person_id) \
+                                                .filter(models.StationmetaRole.contact_id == contact_id) \
                                                 .filter(models.StationmetaRole.status == status) \
                                                 .first()
     return db_object
@@ -107,7 +107,7 @@ def create_stationmeta(db: Session, stationmeta: StationmetaCreate):
             db_role.role = get_value_from_str(RC_enum,db_role.role)
             db_role.status = get_value_from_str(RS_enum,db_role.status)
             # check whether role is already present in database
-            db_object = get_unique_stationmeta_role(db, db_role.role, db_role.person_id, db_role.status)
+            db_object = get_unique_stationmeta_role(db, db_role.role, db_role.contact_id, db_role.status)
             if db_object:
                 role_id = db_object.id
             else:
diff --git a/toardb/stationmeta/models_role.py b/toardb/stationmeta/models_role.py
index e052591..ced78b8 100644
--- a/toardb/stationmeta/models_role.py
+++ b/toardb/stationmeta/models_role.py
@@ -6,7 +6,7 @@ class StationmetaRole (Base)
 from sqlalchemy import Column, ForeignKey, Integer, \
                        CheckConstraint, UniqueConstraint, Table, Sequence
 from sqlalchemy.orm import relationship
-from toardb.contacts.models import Person
+from toardb.contacts.models import Contact
 from toardb.base import Base
 
 
@@ -30,16 +30,16 @@ class StationmetaRole(Base):
     +------------+---------+-----------+----------+-----------------------------------------------+
     | status     | integer |           | not null |                                               |
     +------------+---------+-----------+----------+-----------------------------------------------+
-    | person_id  | integer |           | not null |                                               |
+    | contact_id | integer |           | not null |                                               |
     +------------+---------+-----------+----------+-----------------------------------------------+
     Indexes:
      "stationmeta_roles_pkey" PRIMARY KEY, btree (id)
-     "stationmeta_roles_person_id_3bd9c160" btree (person_id)
+     "stationmeta_roles_contact_id_3bd9c160" btree (contact_id)
     Check constraints:
      "stationmeta_roles_role_check" CHECK (role >= 0)
      "stationmeta_roles_status_check" CHECK (status >= 0)
     Foreign-key constraints:
-     "stationmeta_roles_person_id_3bd9c160_fk_persons_id" FOREIGN KEY (person_id) REFERENCES persons(id) DEFERRABLE INITIALLY DEFERRED
+     "stationmeta_roles_contact_id_3bd9c160_fk_contacts_id" FOREIGN KEY (contact_id) REFERENCES contacts(id) DEFERRABLE INITIALLY DEFERRED
      "stationmeta_roles_role_fk_rc_vocabulary_enum_val" FOREIGN KEY (role) REFERENCES rc_vocabulary(enum_val)
      "stationmeta_roles_status_fk_rs_vocabulary_enum_val" FOREIGN KEY (status) REFERENCES rs_vocabulary(enum_val)
     """
@@ -56,9 +56,7 @@ class StationmetaRole(Base):
 # 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
-    person_id = Column(ForeignKey(Person.id, deferrable=True, initially='DEFERRED'), nullable=False, index=True)
-# how to reactivate the following two lines?!
-#   person = relationship('Person')
+    contact_id = Column(ForeignKey(Contact.id, deferrable=True, initially='DEFERRED'), nullable=False, index=True)
 
     station = relationship("StationmetaCore",
                            secondary=stationmeta_core_stationmeta_roles_table,
diff --git a/toardb/stationmeta/schemas.py b/toardb/stationmeta/schemas.py
index db22981..85e9e54 100644
--- a/toardb/stationmeta/schemas.py
+++ b/toardb/stationmeta/schemas.py
@@ -12,6 +12,7 @@ import datetime as dt
 from .models import CZ_enum, CV_enum, ST_enum, TA_enum, TC_enum, \
                     TR_enum, DL_enum
 from toardb.generic.models import RC_enum, RS_enum
+from toardb.contacts.models import Contact
 
 
 # the following class was taken from:
@@ -320,7 +321,8 @@ class StationmetaRoleBase(BaseModel):
     id: int = None
     role: str
     status: str
-    person_id: int
+#   contact: Contact
+    contact_id: int
 
     @validator('role')
     def check_role(cls, v):
@@ -330,6 +332,9 @@ class StationmetaRoleBase(BaseModel):
     def check_status(cls, v):
         return tuple(filter(lambda x: x.value == int(v), RS_enum))[0].string
 
+    class Config:
+        orm_mode = True
+
 
 class StationmetaRoleCreate(StationmetaRoleBase):
     pass
diff --git a/toardb/timeseries/crud.py b/toardb/timeseries/crud.py
index 4fec5d0..03480e8 100644
--- a/toardb/timeseries/crud.py
+++ b/toardb/timeseries/crud.py
@@ -53,9 +53,9 @@ def get_role_ids_of_timeseries(db: Session, timeseries_id: int):
 
 
 # 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):
+def get_unique_timeseries_role(db: Session, role: int, contact_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.contact_id == contact_id) \
                                       .filter(models.TimeseriesRole.status == status) \
                                       .first()
     return db_object
@@ -97,7 +97,7 @@ def create_timeseries(db: Session, timeseries: TimeseriesCreate):
             db_role.role = get_value_from_str(RC_enum,db_role.role)
             db_role.status = get_value_from_str(RS_enum,db_role.status)
             # 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)
+            db_object = get_unique_timeseries_role(db, db_role.role, db_role.contact_id, db_role.status)
             if db_object:
                 role_id = db_object.id
             else:
diff --git a/toardb/timeseries/models_role.py b/toardb/timeseries/models_role.py
index bf88739..2db40ea 100644
--- a/toardb/timeseries/models_role.py
+++ b/toardb/timeseries/models_role.py
@@ -7,7 +7,7 @@ from sqlalchemy import Column, ForeignKey, Integer, text, \
                        CheckConstraint, UniqueConstraint, \
                        Table, Sequence
 from sqlalchemy.orm import relationship
-from toardb.contacts.models import Person
+from toardb.contacts.models import Contact
 from toardb.base import Base
 
 
@@ -30,24 +30,24 @@ class TimeseriesRole(Base):
     +---------------+---------+-----------+----------+----------------------------------------------+
     | status        | integer |           | not null |                                              |
     +---------------+---------+-----------+----------+----------------------------------------------+
-    | person_id     | integer |           | not null |                                              |
+    | contact_id    | integer |           | not null |                                              |
     +---------------+---------+-----------+----------+----------------------------------------------+
     Indexes:
         "timeseries_roles_pkey" PRIMARY KEY, btree (id)
-        "timeseries_roles_role_person_id_uniq" UNIQUE CONSTRAINT, btree (role, person_id)
-        "timeseries_roles_person_id" btree (person_id)
+        "timeseries_roles_role_contact_id_uniq" UNIQUE CONSTRAINT, btree (role, contact_id)
+        "timeseries_roles_contact_id" btree (contact_id)
     Check constraints:
         "timeseries_roles_role_check" CHECK (role >= 0)
         "timeseries_roles_status_check" CHECK (status >= 0)
     Foreign-key constraints:
-        "timeseries_roles_person_id_3e26200e_fk_persons_id" FOREIGN KEY (person_id) REFERENCES persons(id) DEFERRABLE INITIALLY DEFERRED
+        "timeseries_roles_contact_id_fk_contacts_id" FOREIGN KEY (contact_id) REFERENCES contacts(id) DEFERRABLE INITIALLY DEFERRED
     """
 
     __tablename__ = 'timeseries_roles'
     __table_args__ = (
         CheckConstraint('role >= 0'),
         CheckConstraint('status >= 0'),
-        UniqueConstraint('role', 'person_id')
+        UniqueConstraint('role', 'contact_id')
     )
 
     id = Column(Integer, TIMESERIES_ROLES_ID_SEQ, primary_key=True, server_default=TIMESERIES_ROLES_ID_SEQ.next_value())
@@ -56,8 +56,8 @@ class TimeseriesRole(Base):
 # 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
-    person_id = Column(ForeignKey(Person.id, deferrable=True, initially='DEFERRED'), nullable=False, index=True)
-#   person = relationship(Person)
+    contact_id = Column(ForeignKey(Contact.id, deferrable=True, initially='DEFERRED'), nullable=False, index=True)
+    contact = relationship(Contact)
 #   rc_vocabulary = relationship('RcVocabulary')
 #   rs_vocabulary = relationship('RsVocabulary')
 
diff --git a/toardb/timeseries/schemas.py b/toardb/timeseries/schemas.py
index 46d2498..75d0637 100644
--- a/toardb/timeseries/schemas.py
+++ b/toardb/timeseries/schemas.py
@@ -9,6 +9,7 @@ from typing import List, Any
 from pydantic import BaseModel, Json, validator
 import datetime as dt
 from toardb.generic.models import RS_enum, RC_enum
+from toardb.contacts.models import Contact
 from .models import DA_enum, SF_enum, AT_enum, DS_enum, MM_enum
 from toardb.variables.schemas import Variable 
 #from toardb.stationmeta.schemas import StationmetaCoreCreate
@@ -115,7 +116,9 @@ class TimeseriesRoleBase(BaseModel):
     id: int = None
     role: str
     status: str
-    person_id: int
+#   contact_id: int  # works
+#   contact: Contact # does not work!
+    contact: Any
 
     @validator('role')
     def check_role(cls, v):
-- 
GitLab