Lots of changes, I guess
This commit is contained in:
parent
ff17665cb0
commit
a7589e604c
8 changed files with 156 additions and 67 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,7 +5,7 @@ settings.ini
|
||||||
completionmail.ini
|
completionmail.ini
|
||||||
venv/
|
venv/
|
||||||
output/
|
output/
|
||||||
reports/*.py
|
reports/*
|
||||||
!reports/__init__.py
|
!reports/__init__.py
|
||||||
.vscode
|
.vscode
|
||||||
*.old
|
*.old
|
||||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "reportmonster"
|
name = "reportmonster"
|
||||||
version = "0.9.6"
|
version = "0.9.7"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Kumi Systems e.U.", email="office@kumi.systems" },
|
{ name="Kumi Systems e.U.", email="office@kumi.systems" },
|
||||||
]
|
]
|
||||||
|
|
0
reports/__init__.py
Normal file
0
reports/__init__.py
Normal file
|
@ -1,7 +1,7 @@
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Union, List
|
||||||
|
|
||||||
from .vessel import Vessel
|
from .vessel import Vessel
|
||||||
from .user import User
|
from .user import User
|
||||||
|
@ -37,16 +37,17 @@ class MonsterConfig:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
try:
|
try:
|
||||||
import pyadonis
|
import pyadonis
|
||||||
|
|
||||||
self.pyadonis = Path(pyadonis.__path__[0])
|
self.pyadonis = Path(pyadonis.__path__[0])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(f"PyAdonis is not defined in the MONSTER section of {path}, some features may be missing.")
|
print(
|
||||||
|
f"PyAdonis is not defined in the MONSTER section of {path}, some features may be missing."
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, path: Union[str, Path]) -> None:
|
def __init__(self, path: Union[str, Path]) -> None:
|
||||||
"""Initialize a new (empty) MonsterConfig object
|
"""Initialize a new (empty) MonsterConfig object"""
|
||||||
"""
|
self.vessels: List[Vessel] = []
|
||||||
self.vessels = []
|
self.users: List[User] = []
|
||||||
self.users = []
|
|
||||||
self.pyadonis = None
|
self.pyadonis = None
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
|
|
|
@ -10,8 +10,7 @@ import socket
|
||||||
|
|
||||||
|
|
||||||
class Connection:
|
class Connection:
|
||||||
"""Class representing an SSH/SFTP connection to a Vessel
|
"""Class representing an SSH/SFTP connection to a Vessel"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, vessel):
|
def __init__(self, vessel):
|
||||||
"""Initialize a new Connection to a Vessel
|
"""Initialize a new Connection to a Vessel
|
||||||
|
@ -24,9 +23,14 @@ class Connection:
|
||||||
self._client = SSHClient()
|
self._client = SSHClient()
|
||||||
self._client.load_system_host_keys()
|
self._client.load_system_host_keys()
|
||||||
self._client.set_missing_host_key_policy(WarningPolicy)
|
self._client.set_missing_host_key_policy(WarningPolicy)
|
||||||
self._client.connect(vessel.host, 22, vessel.ssh_username,
|
self._client.connect(
|
||||||
vessel.ssh_password, timeout=vessel.ssh_timeout,
|
vessel.host,
|
||||||
passphrase=vessel.ssh_passphrase)
|
22,
|
||||||
|
vessel.ssh_username,
|
||||||
|
vessel.ssh_password,
|
||||||
|
timeout=vessel.ssh_timeout,
|
||||||
|
passphrase=vessel.ssh_passphrase,
|
||||||
|
)
|
||||||
self._transport = self._client.get_transport()
|
self._transport = self._client.get_transport()
|
||||||
self._transport.set_keepalive(10)
|
self._transport.set_keepalive(10)
|
||||||
self._sftp = self._client.open_sftp()
|
self._sftp = self._client.open_sftp()
|
||||||
|
@ -36,14 +40,14 @@ class Connection:
|
||||||
(self._vessel.host, 22),
|
(self._vessel.host, 22),
|
||||||
ssh_username=self._vessel.ssh_username,
|
ssh_username=self._vessel.ssh_username,
|
||||||
ssh_private_key_password=self._vessel.ssh_passphrase,
|
ssh_private_key_password=self._vessel.ssh_passphrase,
|
||||||
remote_bind_address=("127.0.0.1", remote))
|
remote_bind_address=("127.0.0.1", remote),
|
||||||
|
)
|
||||||
|
|
||||||
self._process.start()
|
self._process.start()
|
||||||
return self._process.local_bind_port
|
return self._process.local_bind_port
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""Close SSH connection when ending Connection
|
"""Close SSH connection when ending Connection"""
|
||||||
"""
|
|
||||||
self._client.close()
|
self._client.close()
|
||||||
|
|
||||||
if self._process:
|
if self._process:
|
||||||
|
|
|
@ -9,18 +9,23 @@ from .logger import Logger
|
||||||
|
|
||||||
logger = Logger()
|
logger = Logger()
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
"""Class wrapping MySQL database connection
|
"""Class wrapping MySQL database connection"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, vessel):
|
def __init__(self, vessel):
|
||||||
"""Initialize a new Database object
|
"""Initialize a new Database object"""
|
||||||
"""
|
|
||||||
self.vessel = vessel
|
self.vessel = vessel
|
||||||
self._con = None
|
self._con = None
|
||||||
self._ssh = None
|
self._ssh = None
|
||||||
|
|
||||||
def _execute(self, query: str, parameters: Optional[tuple] = None, ctype: Optional[MySQLdb.cursors.BaseCursor] = None, retry: bool = True):
|
def _execute(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
parameters: Optional[tuple] = None,
|
||||||
|
ctype: Optional[MySQLdb.cursors.BaseCursor] = None,
|
||||||
|
retry: bool = True,
|
||||||
|
):
|
||||||
"""Execute a query on the database
|
"""Execute a query on the database
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -52,8 +57,13 @@ class Database:
|
||||||
port = 3306
|
port = 3306
|
||||||
host = self.vessel.host
|
host = self.vessel.host
|
||||||
|
|
||||||
self._con = MySQLdb.connect(host=host, user=self.vessel.username,
|
self._con = MySQLdb.connect(
|
||||||
passwd=self.vessel.password, db=self.vessel.database, port=port)
|
host=host,
|
||||||
|
user=self.vessel.username,
|
||||||
|
passwd=self.vessel.password,
|
||||||
|
db=self.vessel.database,
|
||||||
|
port=port,
|
||||||
|
)
|
||||||
|
|
||||||
def commit(self) -> None:
|
def commit(self) -> None:
|
||||||
"""Commit the current database transaction
|
"""Commit the current database transaction
|
||||||
|
@ -64,7 +74,9 @@ class Database:
|
||||||
"""
|
"""
|
||||||
self._con.commit()
|
self._con.commit()
|
||||||
|
|
||||||
def getCursor(self, ctype: Optional[MySQLdb.cursors.BaseCursor] = None) -> MySQLdb.cursors.BaseCursor:
|
def getCursor(
|
||||||
|
self, ctype: Optional[MySQLdb.cursors.BaseCursor] = None
|
||||||
|
) -> MySQLdb.cursors.BaseCursor:
|
||||||
"""Return a cursor to operate on the MySQL database
|
"""Return a cursor to operate on the MySQL database
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -73,7 +85,6 @@ class Database:
|
||||||
return self._con.cursor(ctype)
|
return self._con.cursor(ctype)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""Close database connection on removal of the Database object
|
"""Close database connection on removal of the Database object"""
|
||||||
"""
|
|
||||||
if self._con:
|
if self._con:
|
||||||
self._con.close()
|
self._con.close()
|
|
@ -10,8 +10,8 @@ from bcrypt import hashpw, gensalt
|
||||||
|
|
||||||
|
|
||||||
class User:
|
class User:
|
||||||
"""Class describing a User
|
"""Class describing a User"""
|
||||||
"""
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromConfig(cls, config: SectionProxy):
|
def fromConfig(cls, config: SectionProxy):
|
||||||
"""Create User object from a User section in the Config file
|
"""Create User object from a User section in the Config file
|
||||||
|
@ -29,7 +29,6 @@ class User:
|
||||||
|
|
||||||
return cls(config.name.split()[1], config["Password"])
|
return cls(config.name.split()[1], config["Password"])
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, username: str, password: str) -> None:
|
def __init__(self, username: str, password: str) -> None:
|
||||||
"""Initialize new Vessel object
|
"""Initialize new Vessel object
|
||||||
|
|
||||||
|
@ -39,6 +38,5 @@ class User:
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
|
|
||||||
def validatePasword(self, password) -> bool:
|
def validatePasword(self, password) -> bool:
|
||||||
return password == self.password
|
return password == self.password
|
|
@ -1,4 +1,4 @@
|
||||||
from classes.database import Database
|
from .database import Database
|
||||||
|
|
||||||
from configparser import SectionProxy
|
from configparser import SectionProxy
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
@ -8,12 +8,12 @@ from MySQLdb.cursors import DictCursor
|
||||||
from MySQLdb._exceptions import IntegrityError
|
from MySQLdb._exceptions import IntegrityError
|
||||||
from bcrypt import hashpw, gensalt
|
from bcrypt import hashpw, gensalt
|
||||||
|
|
||||||
from const import *
|
from ..const import *
|
||||||
|
|
||||||
|
|
||||||
class Vessel:
|
class Vessel:
|
||||||
"""Class describing a Vessel
|
"""Class describing a Vessel"""
|
||||||
"""
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromConfig(cls, config: SectionProxy):
|
def fromConfig(cls, config: SectionProxy):
|
||||||
"""Create Vessel object from a Vessel section in the Config file
|
"""Create Vessel object from a Vessel section in the Config file
|
||||||
|
@ -50,14 +50,37 @@ class Vessel:
|
||||||
database = config["Database"]
|
database = config["Database"]
|
||||||
|
|
||||||
if "SSH" in config.keys():
|
if "SSH" in config.keys():
|
||||||
if int(config["SSH"]) == 1:
|
if config.getint("SSH") == 1:
|
||||||
ssh = True
|
ssh = True
|
||||||
|
|
||||||
return cls(config.name.split()[1], config["Host"], username, password, database, ssh, ssh_username, ssh_password, ssh_timeout, ssh_passphrase)
|
ssh_username = config.get("SSHUser")
|
||||||
|
|
||||||
def __init__(self, name: str, host: str, username: Optional[str] = None,
|
return cls(
|
||||||
password: Optional[str] = None, database: Optional[str] = None,
|
config.name.split()[1],
|
||||||
ssh = False, ssh_username = None, ssh_password = None, ssh_timeout = None, ssh_passphrase = None) -> None:
|
config["Host"],
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
database,
|
||||||
|
ssh,
|
||||||
|
ssh_username,
|
||||||
|
ssh_password,
|
||||||
|
ssh_timeout,
|
||||||
|
ssh_passphrase,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
host: str,
|
||||||
|
username: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
database: Optional[str] = None,
|
||||||
|
ssh=False,
|
||||||
|
ssh_username=None,
|
||||||
|
ssh_password=None,
|
||||||
|
ssh_timeout=None,
|
||||||
|
ssh_passphrase=None,
|
||||||
|
) -> None:
|
||||||
"""Initialize new Vessel object
|
"""Initialize new Vessel object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -76,7 +99,7 @@ class Vessel:
|
||||||
|
|
||||||
self.db = self.connect()
|
self.db = self.connect()
|
||||||
|
|
||||||
def connect(self):
|
def connect(self) -> Database:
|
||||||
return Database(self)
|
return Database(self)
|
||||||
|
|
||||||
def reconnect(self):
|
def reconnect(self):
|
||||||
|
@ -98,7 +121,9 @@ class Vessel:
|
||||||
results = self.db._execute(QUERY_USER_INFO_FIELD, ctype=DictCursor)
|
results = self.db._execute(QUERY_USER_INFO_FIELD, ctype=DictCursor)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def getUserInfoData(self, field: Optional[int] = None, user: Optional[int] = None) -> list:
|
def getUserInfoData(
|
||||||
|
self, field: Optional[int] = None, user: Optional[int] = None
|
||||||
|
) -> list:
|
||||||
query = QUERY_USER_INFO_DATA
|
query = QUERY_USER_INFO_DATA
|
||||||
parameters = []
|
parameters = []
|
||||||
|
|
||||||
|
@ -119,7 +144,9 @@ class Vessel:
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def getUsers(self, username: Optional[str] = None, id: Optional[int] = None) -> dict:
|
def getUsers(
|
||||||
|
self, username: Optional[str] = None, id: Optional[int] = None
|
||||||
|
) -> dict:
|
||||||
query = QUERY_USER
|
query = QUERY_USER
|
||||||
parameters = tuple()
|
parameters = tuple()
|
||||||
|
|
||||||
|
@ -147,7 +174,9 @@ class Vessel:
|
||||||
|
|
||||||
for value in odata:
|
for value in odata:
|
||||||
try:
|
try:
|
||||||
users[value["userid"]]["custom_fields"][ofield["shortname"]] = value["data"]
|
users[value["userid"]]["custom_fields"][
|
||||||
|
ofield["shortname"]
|
||||||
|
] = value["data"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -155,7 +184,10 @@ class Vessel:
|
||||||
|
|
||||||
def getHTMLCerts(self, after: int = 0, before: Optional[int] = None):
|
def getHTMLCerts(self, after: int = 0, before: Optional[int] = None):
|
||||||
before = before or Vessel.getTimestamp()
|
before = before or Vessel.getTimestamp()
|
||||||
results = self.db._execute(f"{QUERY_HTML_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}", ctype=DictCursor)
|
results = self.db._execute(
|
||||||
|
f"{QUERY_HTML_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}",
|
||||||
|
ctype=DictCursor,
|
||||||
|
)
|
||||||
ocerts = self.db._execute(QUERY_HTML_CERT, ctype=DictCursor)
|
ocerts = self.db._execute(QUERY_HTML_CERT, ctype=DictCursor)
|
||||||
|
|
||||||
certs = dict()
|
certs = dict()
|
||||||
|
@ -175,7 +207,10 @@ class Vessel:
|
||||||
|
|
||||||
def getCustomCerts(self, after: int = 0, before: Optional[int] = None):
|
def getCustomCerts(self, after: int = 0, before: Optional[int] = None):
|
||||||
before = before or Vessel.getTimestamp()
|
before = before or Vessel.getTimestamp()
|
||||||
results = self.db._execute(f"{QUERY_CUSTOM_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}", ctype=DictCursor)
|
results = self.db._execute(
|
||||||
|
f"{QUERY_CUSTOM_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}",
|
||||||
|
ctype=DictCursor,
|
||||||
|
)
|
||||||
ocerts = self.db._execute(QUERY_CUSTOM_CERT, ctype=DictCursor)
|
ocerts = self.db._execute(QUERY_CUSTOM_CERT, ctype=DictCursor)
|
||||||
|
|
||||||
certs = dict()
|
certs = dict()
|
||||||
|
@ -195,7 +230,10 @@ class Vessel:
|
||||||
|
|
||||||
def getCerts(self, after: int = 0, before: Optional[int] = None):
|
def getCerts(self, after: int = 0, before: Optional[int] = None):
|
||||||
before = before or Vessel.getTimestamp()
|
before = before or Vessel.getTimestamp()
|
||||||
return sorted(self.getHTMLCerts(after, before) + self.getCustomCerts(after, before), key=lambda d: d["timecreated"])
|
return sorted(
|
||||||
|
self.getHTMLCerts(after, before) + self.getCustomCerts(after, before),
|
||||||
|
key=lambda d: d["timecreated"],
|
||||||
|
)
|
||||||
|
|
||||||
def setPassword(self, username: str, password: str):
|
def setPassword(self, username: str, password: str):
|
||||||
hashed = hashpw(password.encode(), gensalt(prefix=b"2b"))
|
hashed = hashpw(password.encode(), gensalt(prefix=b"2b"))
|
||||||
|
@ -218,11 +256,16 @@ class Vessel:
|
||||||
|
|
||||||
if not enrol:
|
if not enrol:
|
||||||
self.createEnrol(courseid, enrol)
|
self.createEnrol(courseid, enrol)
|
||||||
enrol = list(filter(lambda x: x["courseid"] == courseid , self.getEnrols(enrol)))
|
enrol = list(
|
||||||
|
filter(lambda x: x["courseid"] == courseid, self.getEnrols(enrol))
|
||||||
|
)
|
||||||
|
|
||||||
assert enrol
|
assert enrol
|
||||||
|
|
||||||
self.db._execute(QUERY_ENROL_USER, (enrol[0]["id"], userid, Vessel.getTimestamp(), Vessel.getTimestamp()))
|
self.db._execute(
|
||||||
|
QUERY_ENROL_USER,
|
||||||
|
(enrol[0]["id"], userid, Vessel.getTimestamp(), Vessel.getTimestamp()),
|
||||||
|
)
|
||||||
|
|
||||||
def getEnrolments(self):
|
def getEnrolments(self):
|
||||||
results = list(self.db._execute(QUERY_ENROLMENTS, ctype=DictCursor))
|
results = list(self.db._execute(QUERY_ENROLMENTS, ctype=DictCursor))
|
||||||
|
@ -230,16 +273,30 @@ class Vessel:
|
||||||
|
|
||||||
def createUser(self, username, password, email, firstname, lastname):
|
def createUser(self, username, password, email, firstname, lastname):
|
||||||
email = email or f"{username}@pin.seachefsacademy.com"
|
email = email or f"{username}@pin.seachefsacademy.com"
|
||||||
self.db._execute(QUERY_USER_CREATE, (username, email, firstname, lastname, Vessel.getTimestamp(), Vessel.getTimestamp()))
|
self.db._execute(
|
||||||
|
QUERY_USER_CREATE,
|
||||||
|
(
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
|
Vessel.getTimestamp(),
|
||||||
|
Vessel.getTimestamp(),
|
||||||
|
),
|
||||||
|
)
|
||||||
self.setPassword(username, password)
|
self.setPassword(username, password)
|
||||||
|
|
||||||
def assignRole(self, userid: int, courseid: int, roleid: int = 5):
|
def assignRole(self, userid: int, courseid: int, roleid: int = 5):
|
||||||
contextid = self.getCourseContext(courseid)[0]["id"]
|
contextid = self.getCourseContext(courseid)[0]["id"]
|
||||||
self.db._execute(QUERY_ASSIGN_ROLE, (roleid, contextid, userid, Vessel.getTimestamp()))
|
self.db._execute(
|
||||||
|
QUERY_ASSIGN_ROLE, (roleid, contextid, userid, Vessel.getTimestamp())
|
||||||
|
)
|
||||||
|
|
||||||
def getRole(self, userid: int, courseid: int) -> Optional[int]:
|
def getRole(self, userid: int, courseid: int) -> Optional[int]:
|
||||||
contextid = self.getCourseContext(courseid)[0]["id"]
|
contextid = self.getCourseContext(courseid)[0]["id"]
|
||||||
results = self.db._execute(QUERY_GET_ROLE, (contextid, userid), ctype=DictCursor)
|
results = self.db._execute(
|
||||||
|
QUERY_GET_ROLE, (contextid, userid), ctype=DictCursor
|
||||||
|
)
|
||||||
if results:
|
if results:
|
||||||
return results[0]["roleid"]
|
return results[0]["roleid"]
|
||||||
|
|
||||||
|
@ -249,7 +306,10 @@ class Vessel:
|
||||||
return results[0]["id"]
|
return results[0]["id"]
|
||||||
|
|
||||||
def setEmail(self, userid: int, email: str):
|
def setEmail(self, userid: int, email: str):
|
||||||
email = email or f"{self.getUsers(id=userid)[userid]['username']}@pin.seachefsacademy.com"
|
email = (
|
||||||
|
email
|
||||||
|
or f"{self.getUsers(id=userid)[userid]['username']}@pin.seachefsacademy.com"
|
||||||
|
)
|
||||||
self.db._execute(QUERY_USER_SET_EMAIL, (email, userid))
|
self.db._execute(QUERY_USER_SET_EMAIL, (email, userid))
|
||||||
|
|
||||||
def setName(self, userid: int, first: str, last: str):
|
def setName(self, userid: int, first: str, last: str):
|
||||||
|
@ -260,21 +320,30 @@ class Vessel:
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def getCourseByContext(self, contextid: int) -> Optional[int]:
|
def getCourseByContext(self, contextid: int) -> Optional[int]:
|
||||||
results = self.db._execute(QUERY_COURSE_CONTEXT_REVERSE, (contextid,), ctype=DictCursor)
|
results = self.db._execute(
|
||||||
|
QUERY_COURSE_CONTEXT_REVERSE, (contextid,), ctype=DictCursor
|
||||||
|
)
|
||||||
if results:
|
if results:
|
||||||
return results[0]["instanceid"]
|
return results[0]["instanceid"]
|
||||||
|
|
||||||
def getCourseModules(self, courseid: int):
|
def getCourseModules(self, courseid: int):
|
||||||
results = list(self.db._execute(QUERY_COURSE_MODULES, (courseid,), ctype=DictCursor))
|
results = list(
|
||||||
|
self.db._execute(QUERY_COURSE_MODULES, (courseid,), ctype=DictCursor)
|
||||||
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def getCourseModuleCompletion(self, moduleid: int):
|
def getCourseModuleCompletion(self, moduleid: int):
|
||||||
results = list(self.db._execute(QUERY_MODULE_COMPLETION, (moduleid,), ctype=DictCursor))
|
results = list(
|
||||||
|
self.db._execute(QUERY_MODULE_COMPLETION, (moduleid,), ctype=DictCursor)
|
||||||
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def setCourseModuleCompletion(self, moduleid: int, userid: int):
|
def setCourseModuleCompletion(self, moduleid: int, userid: int):
|
||||||
try:
|
try:
|
||||||
self.db._execute(QUERY_INSERT_MODULE_COMPLETION, (moduleid, userid, Vessel.getTimestamp()))
|
self.db._execute(
|
||||||
|
QUERY_INSERT_MODULE_COMPLETION,
|
||||||
|
(moduleid, userid, Vessel.getTimestamp()),
|
||||||
|
)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
pass # Module completion record already exists
|
pass # Module completion record already exists
|
||||||
self.db._execute(QUERY_UPDATE_MODULE_COMPLETION, (moduleid, userid))
|
self.db._execute(QUERY_UPDATE_MODULE_COMPLETION, (moduleid, userid))
|
||||||
|
@ -287,3 +356,9 @@ class Vessel:
|
||||||
|
|
||||||
def writeLog(self, event, data):
|
def writeLog(self, event, data):
|
||||||
self.db._execute(QUERY_LOG_INSERT, (event, data))
|
self.db._execute(QUERY_LOG_INSERT, (event, data))
|
||||||
|
|
||||||
|
def getCourseCompletions(self, courseid: int):
|
||||||
|
results = list(
|
||||||
|
self.db._execute(QUERY_COURSE_COMPLETION, (courseid,), ctype=DictCursor)
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
|
Loading…
Reference in a new issue