from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
from sqlalchemy import func
from sqlalchemy.orm.collections import attribute_mapped_collection
from timApp.auth.accesstype import AccessType
from timApp.auth.auth_models import BlockAccess
from timApp.item.blockassociation import BlockAssociation
from timApp.item.tag import Tag
from timApp.messaging.messagelist.messagelist_models import MessageListModel
from timApp.messaging.timMessage.internalmessage_models import (
InternalMessage,
InternalMessageDisplay,
)
from timApp.timdb.sqa import db
from timApp.user.usergroup import UserGroup
from timApp.user.usergroupdoc import UserGroupDoc
from timApp.util.utils import get_current_time
if TYPE_CHECKING:
from timApp.folder.folder import Folder
[docs]class Block(db.Model):
"""The "base class" for all database objects that are part of the permission system."""
__tablename__ = "block"
id = db.Column(db.Integer, primary_key=True)
"""A unique identifier for the Block."""
latest_revision_id = db.Column(db.Integer)
"""Old field that is not used anymore."""
type_id = db.Column(db.Integer, nullable=False)
"""Type of the Block, see BlockType enum for possible types."""
description = db.Column(db.Text)
"""Additional information about the Block. This is used for different purposes by different BlockTypes,
so it isn't merely a "description".
"""
created = db.Column(db.DateTime(timezone=True), nullable=False, default=func.now())
"""When this Block was created."""
modified = db.Column(db.DateTime(timezone=True), default=func.now())
"""When this Block was last modified."""
docentries = db.relationship("DocEntry", back_populates="_block")
folder = db.relationship("Folder", back_populates="_block", uselist=False)
translation = db.relationship(
"Translation",
back_populates="_block",
uselist=False,
foreign_keys="Translation.doc_id",
)
answerupload = db.relationship(
"AnswerUpload", back_populates="block", lazy="dynamic"
)
accesses = db.relationship(
"BlockAccess",
back_populates="block",
lazy="joined",
cascade="all, delete-orphan",
collection_class=attribute_mapped_collection("block_collection_key"),
)
tags: list[Tag] = db.relationship("Tag", back_populates="block", lazy="select")
children = db.relationship(
"Block",
secondary=BlockAssociation.__table__,
primaryjoin=id == BlockAssociation.__table__.c.parent,
secondaryjoin=id == BlockAssociation.__table__.c.child,
lazy="select",
)
parents = db.relationship(
"Block",
secondary=BlockAssociation.__table__,
primaryjoin=id == BlockAssociation.__table__.c.child,
secondaryjoin=id == BlockAssociation.__table__.c.parent,
lazy="select",
)
notifications = db.relationship(
"Notification", back_populates="block", lazy="dynamic"
)
relevance = db.relationship(
"BlockRelevance", back_populates="_block", uselist=False
)
# If this Block corresponds to a group's manage document, indicates the group being managed.
managed_usergroup: UserGroup | None = db.relationship(
"UserGroup",
secondary=UserGroupDoc.__table__,
lazy="select",
uselist=False,
)
# If this Block corresponds to a message list's manage document, indicates the message list
# being managed.
managed_messagelist: MessageListModel | None = db.relationship(
"MessageListModel", back_populates="block", lazy="select"
)
internalmessage: InternalMessage | None = db.relationship(
"InternalMessage", back_populates="block"
)
internalmessage_display: InternalMessageDisplay | None = db.relationship(
"InternalMessageDisplay", back_populates="display_block"
)
def __json__(self):
return ["id", "type_id", "description", "created", "modified"]
@property
def owners(self) -> list[UserGroup]:
return [o.usergroup for o in self.owner_accesses]
@property
def parent(self) -> Folder:
if self.type_id == BlockType.Document.value:
from timApp.document.docentry import DocEntry
return DocEntry.find_by_id(self.id).parent
elif self.type_id == BlockType.Folder.value:
from timApp.folder.folder import Folder
folder = Folder.get_by_id(self.id)
return folder.parent
[docs] def is_unpublished(self):
from timApp.auth.sessioninfo import get_current_user_object
u = get_current_user_object()
return (
u.has_ownership(self) is not None
and all(not o.is_large() for o in self.owners)
and len(self.accesses) <= 1
)
@property
def owner_accesses(self):
return [a for a in self.accesses.values() if a.type == AccessType.owner.value]
[docs] def set_owner(self, usergroup: UserGroup):
"""Changes the owner group for a block.
:param usergroup: The new usergroup.
"""
self.accesses = {
(usergroup.id, AccessType.owner.value): BlockAccess(
usergroup_id=usergroup.id,
type=AccessType.owner.value,
accessible_from=get_current_time(),
)
}
[docs] def add_rights(self, groups, access_type: AccessType):
for gr in groups:
key = (gr.id, access_type.value)
self.accesses[key] = BlockAccess(
usergroup_id=gr.id,
type=access_type.value,
accessible_from=get_current_time(),
)
[docs]class BlockType(Enum):
Document = 0
Comment = 1
Note = 2
Answer = 3
Image = 4
Reading = 5
Folder = 6
File = 7
Upload = 8
ScheduledFunction = 9
Task = 10
[docs] @staticmethod
def from_str(type_name: str) -> BlockType:
return BlockType[type_name.title()]
[docs]def insert_block(
block_type: BlockType,
description: str | None,
owner_groups: list[UserGroup] | None = None,
) -> Block:
"""Inserts a block to database.
:param description: The name (description) of the block.
:param owner_groups: The owner groups of the block.
:param block_type: The type of the block.
:returns: The id of the block.
"""
b = Block(description=description, type_id=block_type.value)
db.session.add(b)
if owner_groups:
for owner_group in owner_groups:
db.session.flush()
access = BlockAccess(
block=b,
usergroup=owner_group,
type=AccessType.owner.value,
accessible_from=get_current_time(),
)
b.accesses[(owner_group.id, AccessType.owner.value)] = access
# Also register to accesses_alt because it may be used by other methods in the same session
owner_group.accesses_alt[(b.id, AccessType.owner.value)] = access
db.session.flush()
return b
[docs]def copy_default_rights(
item, item_type: BlockType, owners_to_skip: list[UserGroup] | None = None
):
from timApp.user.userutils import grant_access
from timApp.user.users import get_default_rights_holders
default_rights: list[BlockAccess] = []
folder = item.parent
while folder is not None:
default_rights += get_default_rights_holders(folder, item_type)
folder = folder.parent
refreshed_groups = set()
for d in default_rights:
if (
owners_to_skip
and d.usergroup in owners_to_skip
and d.access_type == AccessType.owner
):
continue
# Because copy_default_rights is usually applied to a new item with new permissions, the usergroup.accesses_alt
# might not be updated yet. Because of this, we need to force the update of the accesses_alt at least once.
if d.usergroup.id not in refreshed_groups:
db.session.expire(d.usergroup, ["accesses_alt"])
refreshed_groups.add(d.usergroup.id)
grant_access(
d.usergroup,
item,
d.atype.to_enum(),
accessible_from=d.accessible_from,
accessible_to=d.accessible_to,
duration_from=d.duration_from,
duration_to=d.duration_to,
duration=d.duration,
)