Source code for ResearchNotes.database.basics

"""
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}"