def _merge_users(target, source, **kwargs): from indico.modules.attachments.models.attachments import Attachment, AttachmentFile from indico.modules.attachments.models.principals import AttachmentPrincipal, AttachmentFolderPrincipal Attachment.find(user_id=source.id).update({Attachment.user_id: target.id}) AttachmentFile.find(user_id=source.id).update({AttachmentFile.user_id: target.id}) AttachmentPrincipal.merge_users(target, source, 'attachment') AttachmentFolderPrincipal.merge_users(target, source, 'folder')
def update_merged_ids(self): self.print_step('updating merged users in attachment acls') for p in AttachmentPrincipal.find(User.merged_into_id != None, _join=AttachmentPrincipal.user): # noqa user = p.user while p.user.merged_into_user: p.user = p.user.merged_into_user self.print_success(cformat('%{cyan}{}%{reset} -> %{cyan}{}%{reset}').format(user, p.user), always=True) self.print_step('updating merged users in folder acls') for p in AttachmentFolderPrincipal.find(User.merged_into_id != None, _join=AttachmentFolderPrincipal.user): # noqa while p.user.merged_into_user: p.user = p.user.merged_into_user self.print_success(cformat('%{cyan}{}%{reset} -> %{cyan}{}%{reset}').format(user, p.user), always=True) db.session.commit()
class AttachmentFolder(LinkMixin, ProtectionMixin, db.Model): __tablename__ = 'folders' allowed_link_types = LinkMixin.allowed_link_types - { LinkType.session_block } unique_links = 'is_default' events_backref_name = 'all_attachment_folders' link_backref_name = 'attachment_folders' link_backref_lazy = 'dynamic' @strict_classproperty @staticmethod def __auto_table_args(): default_inheriting = 'not (is_default and protection_mode != {})'.format( ProtectionMode.inheriting.value) return (db.CheckConstraint(default_inheriting, 'default_inheriting'), db.CheckConstraint('is_default = (title IS NULL)', 'default_or_title'), db.CheckConstraint('not (is_default and is_deleted)', 'default_not_deleted'), db.CheckConstraint('not (is_hidden and is_always_visible)', 'is_hidden_not_is_always_visible'), { 'schema': 'attachments' }) @declared_attr def __table_args__(cls): return auto_table_args(cls) #: The ID of the folder id = db.Column(db.Integer, primary_key=True) #: The name of the folder (``None`` for the default folder) title = db.Column(db.String, nullable=True) #: The description of the folder description = db.Column(db.Text, nullable=False, default='') #: If the folder has been deleted is_deleted = db.Column(db.Boolean, nullable=False, default=False) #: If the folder is the default folder (used for "folder-less" files) is_default = db.Column(db.Boolean, nullable=False, default=False) #: If the folder is always visible (even if you cannot access it) is_always_visible = db.Column(db.Boolean, nullable=False, default=True) #: If the folder is never shown in the frontend (even if you can access it) is_hidden = db.Column(db.Boolean, nullable=False, default=False) acl_entries = db.relationship('AttachmentFolderPrincipal', backref='folder', cascade='all, delete-orphan', collection_class=set) #: The ACL of the folder (used for ProtectionMode.protected) acl = association_proxy( 'acl_entries', 'principal', creator=lambda v: AttachmentFolderPrincipal(principal=v)) #: The list of attachments that are not deleted, ordered by name attachments = db.relationship( 'Attachment', primaryjoin=lambda: (Attachment.folder_id == AttachmentFolder.id) & ~Attachment.is_deleted, order_by=lambda: db.func.lower(Attachment.title), viewonly=True, lazy=True) # relationship backrefs: # - all_attachments (Attachment.folder) # - legacy_mapping (LegacyAttachmentFolderMapping.folder) @property def protection_parent(self): return self.object @classmethod def get_or_create_default(cls, linked_object): """Gets the default folder for the given object or creates it.""" folder = cls.find_first(is_default=True, object=linked_object) if folder is None: folder = cls(is_default=True, object=linked_object) return folder @classmethod def get_or_create(cls, linked_object, title=None): """Gets a folder for the given object or creates it. If no folder title is specified, the default folder will be used. It is the caller's responsibility to add the folder or an object (such as an attachment) associated with it to the SQLAlchemy session using ``db.session.add(...)``. """ if title is None: return AttachmentFolder.get_or_create_default(linked_object) else: folder = AttachmentFolder.find_first(object=linked_object, is_default=False, is_deleted=False, title=title) return folder or AttachmentFolder(object=linked_object, title=title) @locator_property def locator(self): return dict(self.object.locator, folder_id=self.id) def can_access(self, user, *args, **kwargs): """Checks if the user is allowed to access the folder. This is the case if the user has access the folder or if the user can manage attachments for the linked object. """ return (super(AttachmentFolder, self).can_access( user, *args, **kwargs) or can_manage_attachments(self.object, user)) def can_view(self, user): """Checks if the user can see the folder. This does not mean the user can actually access its contents. It just determines if it is visible to him or not. """ if self.is_hidden: return False if not self.object.can_access(user): return False return self.is_always_visible or super(AttachmentFolder, self).can_access(user) @classmethod def get_for_linked_object(cls, linked_object, preload_event=False): """Gets the attachments for the given object. This only returns attachments that haven't been deleted. :param linked_object: A category, event, session, contribution or subcontribution. :param preload_event: If all attachments for the same event should be pre-loaded and cached in the app context. This must not be used when ``linked_object`` is a category. """ from indico.modules.attachments.api.util import get_event event = get_event(linked_object) if event and event in g.get('event_attachments', {}): return g.event_attachments[event].get(linked_object, []) elif not preload_event or not event: return (linked_object.attachment_folders.filter_by( is_deleted=False).order_by( AttachmentFolder.is_default.desc(), db.func.lower(AttachmentFolder.title)).options( joinedload(AttachmentFolder.attachments)).all()) else: if 'event_attachments' not in g: g.event_attachments = {} g.event_attachments[event] = defaultdict(list) query = (event.all_attachment_folders.filter_by( is_deleted=False).order_by( AttachmentFolder.is_default.desc(), db.func.lower(AttachmentFolder.title)).options( joinedload(AttachmentFolder.attachments), joinedload(AttachmentFolder.linked_event), joinedload(AttachmentFolder.session), joinedload(AttachmentFolder.contribution), joinedload(AttachmentFolder.subcontribution))) # populate cache for obj in query: g.event_attachments[event][obj.object].append(obj) return g.event_attachments[event].get(linked_object, []) @return_ascii def __repr__(self): return '<AttachmentFolder({}, {}{}{}{}, {}, {})>'.format( self.id, self.title, ', is_default=True' if self.is_default else '', ', is_always_visible=False' if not self.is_always_visible else '', ', is_hidden=True' if self.is_hidden else '', ', is_deleted=True' if self.is_deleted else '', self.protection_repr)