import json import typing as T from pycs import db from pycs.database.base import BaseModel from pycs.database.util import commit_on_return class ResultConfirmation(BaseModel): """ DB Model for user confirmations of results """ result_id = db.Column( db.Integer, db.ForeignKey("result.id", ondelete="CASCADE"), nullable=False) confirming_user = db.Column(db.String, nullable=False) serialize_only = BaseModel.serialize_only + ( "result_id", "confirming_user", ) class Result(BaseModel): """ DB Model for projects """ file_id = db.Column( db.Integer, db.ForeignKey("file.id", ondelete="CASCADE"), nullable=False) origin = db.Column(db.String, nullable=False) origin_user = db.Column(db.String, nullable=True) type = db.Column(db.String, nullable=False) label_id = db.Column( db.Integer, db.ForeignKey("label.id", ondelete="SET NULL"), nullable=True) data_encoded = db.Column(db.String) result_confirmations = db.relationship("ResultConfirmation", backref="result", lazy="dynamic", passive_deletes=True, ) serialize_only = BaseModel.serialize_only + ( "file_id", "origin", "origin_user", "type", "label_id", "data", "confirmations" ) def serialize(self): """ extends the default serialize with the decoded data attribute """ result = super().serialize() result["data"] = self.data result["confirmations"] = self.confirmations return result @property def data(self) -> T.Optional[dict]: """ getter for the decoded data attribute """ return None if self.data_encoded is None else json.loads(self.data_encoded) @data.setter def data(self, value): """ setter for the decoded data attribute The attribute is encoded property before assigned to the object. """ if isinstance(value, str) or value is None: self.data_encoded = value elif isinstance(value, (dict, list)): self.data_encoded = json.dumps(value) else: raise ValueError(f"Not supported type: {type(value)}") @commit_on_return def set_origin(self, origin: str, origin_user: str = None): """ set this results origin :param origin: either 'user' or 'pipeline' :param origin_user: None if origin is 'pipeline' else name of the user :return: """ if origin == "pipeline" and not origin_user is None: raise ValueError("If an annotation was made by the pipeline no user"\ "can be specified!") self.origin = origin self.origin_user = origin_user self.reset_confirmations(commit=False) @commit_on_return def set_label(self, label: int): """ set this results origin :param label: label ID :return: """ if self.label_id != label: self.reset_confirmations(commit=False) self.label_id = label @property def confirmations(self) -> T.List[ResultConfirmation]: """ Returns all confirmations for this results :return: list of result confirmations """ confirmations = db.session.query(ResultConfirmation).filter( ResultConfirmation.result.has(Result.id==self.id)) _confirmations = [c.serialize() for c in confirmations.all()] _confirmations = [{k:v for k, v in c.items() if k in ('id', 'confirming_user')} for c in _confirmations] return _confirmations @commit_on_return def reset_confirmations(self) -> T.List[ResultConfirmation]: """ Resets all confirmations :return: list of result confirmation objects """ confirmations = ResultConfirmation.query.filter( ResultConfirmation.result_id == self.id) # delete returns the serialized object _confirmations = [c.delete(commit=False) for c in confirmations.all()] return _confirmations @commit_on_return def confirm(self, user: str): """ Result is confirmed by the given user. This sets the origin to "user". If no username was specified before, the given username is used. A confirmation is only added if it does not already exist. The result has be labeled to be confirmed. :param user: username """ if user is None: raise ValueError("When confirming a result the username has to" \ "be specified.") if self.origin == "pipeline": self.set_origin(origin="user", origin_user=user) # Get current confirmations by given user. confirmations_by_user = ResultConfirmation.query.filter( ResultConfirmation.result_id == self.id, ResultConfirmation.confirming_user == user) _confirmations_by_user = [c.serialize() for c in confirmations_by_user.all()] # Results can only be confirmed if the result is labeled. # Also, the original annotator cannot confirm the result and we want # to avoid duplicates. if self.label_id is not None and self.origin_user != user and not len(_confirmations_by_user) > 0: ResultConfirmation.new(commit=False, result_id=self.id, confirming_user=user)