"""
Define all base classes. We define database schemes for
- Groups
- Roles
- User
- Locations
"""
from dataclasses import dataclass
from datetime import datetime
# from typing import List, Set, Any # Needs to be here for compatibility with python 3.8
from ResearchNotes.database import db
# =============================================================================
# Roles database scheme
# =============================================================================
[docs]@dataclass(init=False, eq=False)
class Role(db.Model): # type: ignore
"""
Define the different roles that Users can have.
Currently, we define 4 different roles:
Admin, Student, Supervisor, Ex-Student.
The role is required for certain administrative tasks and used in the
role_required decorator of the auth.py module (used mainly in the conf.py module).
Creates user.role_member as back reference.
"""
# id: int = field(init=False)
# name: str
# description: str
__tablename__ = "role"
id: int = db.Column(db.Integer, primary_key=True)
name: str = db.Column(db.String(50), unique=True, nullable=False)
description: str = db.Column(db.String(250), unique=True)
# one-to-many relationships
# User that are member (All should b). Creates User.role_member field.
members = db.relationship("User", backref="role_member", lazy="dynamic")
def __repr__(self) -> str:
"""Define a representation function for the Role db Model."""
return f"Group ID: {self.id}, Name: {str(self.name)}, description: {self.description}"
# =============================================================================
# Groups database scheme
# =============================================================================
[docs]@dataclass(init=False, eq=False)
class Groups(db.Model): # type: ignore
"""
Database scheme to define the groups that a user will be member of.
The group has several one-to-many relationships that will make finding stuff easier.
members relating to Users (hence group. members should contain all group ids of the members)
Creates field user.group_member
locations for samples locations
Creates samples.group_locations
mtypes are the measurement types of the group
Creates MeasurementType.group_mtype
"""
# id: int = field(init=False)
# name: str
# members: User
# locations: Locations
# mtyps: MeasurementType
__tablename__ = "groups"
id: int = db.Column(db.Integer, primary_key=True)
name: str = db.Column(db.String(50), unique=True, nullable=False)
# One-to-many back relations on the many side
# Members, Samples
members = db.relationship("User", backref="group_member", lazy="dynamic", order_by="User.UserName")
samples = db.relationship("Samples", backref="group_sample", lazy="dynamic")
owned_instruments = db.relationship("Instrument", backref="owner_group", lazy="dynamic")
# Locations
locations = db.relationship("Locations", backref="group_location", lazy="dynamic")
# Instrumentation journal entries created by user. Creates "InstrumentationJournalEntry.group"
journal_entries = db.relationship(
"InstrumentationJournalEntry",
backref="group",
lazy="dynamic",
)
# Instrument templates.
# Templates for instruments owned by the group. Creates TemplateInstrument.group
instrument_templates = db.relationship("TemplateInstrument", backref="group", lazy="dynamic")
# Instrumentation journal templates.
# Templates for instrumentation journal entries owned by the group. Creates TemplateInstrumentationJournalEntry.group
journal_entry_templates = db.relationship(
"TemplateInstrumentationJournalEntry", backref="group", lazy="dynamic"
)
# MeasurementType
#
mtypes = db.relationship("MeasurementType", backref="group_mtype", lazy="dynamic")
# Documents
#
documents = db.relationship("Documents", backref="doc_group", lazy="dynamic")
itypes = db.relationship("ItemType", backref="group_itype", lazy="dynamic")
items = db.relationship("Items", backref="item_group", lazy="dynamic")
def __repr__(self) -> str:
"""Define a representation function for the Group db model."""
return f"Group ID: {self.id}, Name: {str(self.name)}"
# =============================================================================
# User database scheme
# =============================================================================
[docs]@dataclass(init=False, eq=False)
class User(db.Model): # type: ignore
"""
Defines the user db Scheme.
User have an ID, name, a full name (UserName), password as password hash,
marking if active or not. The field confirmed has any use up to now?
We also track login dates (last login, current login, login IP and login Count).
Several one-to-many relationships as well as a many-to-many relationship are defined
here
We get a group_id and a role_id
We do backref to samples, measurements and reports
Creates Sample.sample_owner, Measurements.measurement_owner, Report.report_owner
Back-refers to many-to-many relationship
samples.sharedsamples_owner
We have one function defined as properties that gives the id of the sharedsamples
"""
# id: int = field(init=False)
# name: str
# UserName: str
# email: str
# password: str
# active: bool = field(init=False)
#
# confirmed_at: datetime = field(init=False)
# last_login_at: datetime = field(init=False)
# current_login_at: datetime = field(init=False)
# last_login_ip: str = field(init=False)
# current_login_ip: str = field(init=False)
# login_count: int = field(init=False)
#
# group_id: int
# role_id: int
__tablename__ = "users"
id: int = db.Column(db.Integer, primary_key=True)
# Login Handle
name: str = db.Column(db.String(50), unique=True, nullable=False) # This should be renamed login
# Full username, e.g. first name, last name
UserName: str = db.Column(db.String(80), nullable=False) # This should be renamed username
email: str = db.Column(db.String(80), nullable=False)
password: str = db.Column(db.String(500), nullable=False)
active: bool = db.Column(db.Boolean, nullable=False, default=True)
confirmed_at: datetime = db.Column(db.DateTime, nullable=False, index=True, default=datetime.utcnow)
last_login_at: datetime = db.Column(db.DateTime, nullable=False, index=True, default=datetime.utcnow)
current_login_at: datetime = db.Column(
db.DateTime, nullable=False, index=True, default=datetime.utcnow
)
last_login_ip: str = db.Column(db.String(80), nullable=True)
current_login_ip: str = db.Column(db.String(80), nullable=True)
login_count: int = db.Column(db.Integer, nullable=False, default=0)
# One-to-many inside
# Group membership
group_id: int = db.Column(db.Integer, db.ForeignKey("groups.id"))
# Role of user as defined in Roles
role_id: int = db.Column(db.Integer, db.ForeignKey("role.id"))
# Back-referring
# Samples by user. Creates Sample.sample_owner
samples = db.relationship(
"Samples",
backref="sample_owner",
lazy="dynamic",
order_by="desc(Samples.updated)",
)
# measurements by user. Creates Measurements.measurement_owner
measurements = db.relationship(
"Measurements",
backref="measurement_owner",
lazy="dynamic",
order_by="desc(Measurements.updated)",
)
# Reports by user. Creates "Reports.report_owner"
reports = db.relationship(
"Reports",
backref="reports_owner",
lazy="dynamic",
order_by="desc(Reports.updated)",
)
# Reports by user. Creates "Reports.report_owner"
created_instruments = db.relationship(
"Instrument",
backref="creator",
lazy="dynamic",
order_by="desc(Instrument.updated)",
)
# Reports by user. Creates "Reports.report_owner"
created_entries = db.relationship(
"InstrumentationJournalEntry",
backref="creator",
lazy="dynamic",
order_by="desc(InstrumentationJournalEntry.updated)",
)
# Shared samples
#
# Many-to-many relationship to for shared samples between user.
# These guy here should be an object containing all shared samples with the user
#
# It seems our backref is wrong and introduces issue #14 here
#
sharedsamples = db.relationship(
"Samples",
secondary="sharedsamples",
back_populates="sharedsamples",
order_by="desc(Samples.updated)",
)
# Shared instruments
#
# Many-to-many relationship to for shared samples between user.
# These guy here should be an object containing all shared samples with the user
shared_instruments = db.relationship(
"Instrument",
secondary="shared_instruments_to_guest_users",
back_populates="guest_users",
)
checked_out_items = db.relationship("ItemsCheckOut", back_populates="user")
def __repr__(self) -> str:
"""
Return representation of User Class.
Returns
-------
str
Representation string.
"""
return f"User ID: {self.id} - User name: {self.name}"
@property
def is_active(self) -> bool:
"""
Return True, if user is active.
Use database entry to set a property, if user is active or not.
Returns
-------
bool
True if user is active.
"""
return self.active
@property
def sharedsamplelist(self) -> set[int]:
"""
Give a set of ids in user.sharedsamples.
This should be the sample
records shared to the user.
Returns
-------
set
Set of samples IDs with shared user.
"""
return {s.id for s in self.sharedsamples}
@property
def samplelist(self) -> set[int]:
"""
Give a set of ids in user.samples.
This should be the sample owned by the user.
Returns
-------
set
Set of samples IDs with shared user.
"""
return {s.id for s in self.samples}
# =============================================================================
# Locations database scheme
# =============================================================================
[docs]@dataclass(init=False, eq=False)
class ParentLocations(db.Model): # type: ignore
"""
Database scheme for Locations.
We have id and name of location
One-to-many relation
- Groups
Backref
- Locations
"""
# id: int = field(init=False)
# name: str
# group_id: int
__tablename__ = "parent_locations"
pl_id: int = db.Column(db.Integer, primary_key=True)
name: str = db.Column(db.String(50), nullable=False)
# One-to-many relation for groups
group_id: int = db.Column(db.Integer, db.ForeignKey("groups.id"))
# Backrefs to other places.
sublocations = db.relationship(
"Locations", backref="parent_location", lazy="dynamic", order_by="asc(Locations.name)"
)
def __repr__(self) -> str:
"""
Return a representation string of the sample database scheme.
Returns
-------
str
Representation string.
"""
return f"Top location ID: {self.pl_id}, Location name: {self.name}"
[docs]@dataclass(init=False, eq=False)
class Locations(db.Model): # type: ignore
"""
Database scheme for Locations.
We have id and name of location
One-to-many relation
- Groups
Backref
- Samples
- Instruments
"""
# id: int = field(init=False)
# name: str
# group_id: int
__tablename__ = "locations"
id: int = db.Column(db.Integer, primary_key=True)
name: str = db.Column(db.String(50), nullable=False)
#
# One-to-many relation for groups
group_id: int = db.Column(db.Integer, db.ForeignKey("groups.id"))
parent_location_id: int = db.Column(db.Integer, db.ForeignKey("parent_locations.pl_id"))
#
# Samples at locations (Backref to sample)
# Creates Samples.sample_location
samples = db.relationship("Samples", backref="sample_location", lazy="dynamic")
#
# Instruments at locations (Backref to sample)
# Creates Instrument.instrument_location
instruments = db.relationship("Instrument", backref="instrument_location", lazy="dynamic")
items = db.relationship("Items", backref="item_location", lazy="dynamic")
def __repr__(self) -> str:
"""
Return a representation string of the sample database scheme.
Returns
-------
str
Representation string.
"""
return f"Sublocation ID: {self.id}, Sublocation name: {self.name}, Parent Location {self.parent_location.name}"