class Bar(StoredObject):
     _id = fields.StringField()
     ref = fields.ForeignField('foo', backref='my_ref')
     abs_ref = fields.AbstractForeignField(backref='my_abs_ref')
     ref_list = fields.ForeignField('foo', backref='my_ref_list', list=True)
     abs_ref_list = fields.AbstractForeignField(backref='my_abs_ref_list', list=True)
     _meta = {
         'optimistic': True,
     }
Beispiel #2
0
class TrashedFileNode(StoredObject):
    """The graveyard for all deleted FileNodes"""
    _id = fields.StringField(primary=True)

    last_touched = fields.DateTimeField()
    history = fields.DictionaryField(list=True)
    versions = fields.ForeignField('FileVersion', list=True)

    node = fields.ForeignField('node', required=True)
    parent = fields.AbstractForeignField(default=None)

    is_file = fields.BooleanField(default=True)
    provider = fields.StringField(required=True)

    name = fields.StringField(required=True)
    path = fields.StringField(required=True)
    materialized_path = fields.StringField(required=True)

    checkout = fields.AbstractForeignField('User')
    deleted_by = fields.AbstractForeignField('User')
    deleted_on = fields.DateTimeField(auto_now_add=True)
    tags = fields.ForeignField('Tag', list=True)

    @property
    def deep_url(self):
        """Allows deleted files to resolve to a view
        that will provide a nice error message and http.GONE
        """
        return self.node.web_url_for('addon_deleted_file', trashed_id=self._id)

    def restore(self, recursive=True, parent=None):
        """Recreate a StoredFileNode from the data in this object
        Will re-point all guids and finally remove itself
        :raises KeyExistsException:
        """
        data = self.to_storage()
        data.pop('deleted_on')
        data.pop('deleted_by')
        if parent:
            data['parent'] = parent._id
        elif data['parent']:
            # parent is an AbstractForeignField, so it gets stored as tuple
            data['parent'] = data['parent'][0]
        restored = FileNode.resolve_class(self.provider,
                                          int(self.is_file))(**data)
        if not restored.parent:
            raise ValueError('No parent to restore to')
        restored.save()

        if recursive:
            for child in TrashedFileNode.find(Q('parent', 'eq', self)):
                child.restore(recursive=recursive, parent=restored)

        TrashedFileNode.remove_one(self)
        return restored
Beispiel #3
0
class Guid(StoredObject):

    __indices__ = [{
        'unique': False,
        'key_or_list': [('referent.$', pymongo.ASCENDING)
                        ]  # Forces a mulitkey index h/t @icereval
    }]

    _id = fields.StringField(primary=True)
    referent = fields.AbstractForeignField()

    @classmethod
    def generate(self, referent=None, min_length=5):
        while True:
            # Create GUID
            guid_id = ''.join(random.sample(ALPHABET, min_length))

            # Check GUID against blacklist
            blacklist_guid = BlacklistGuid.load(guid_id)
            if not blacklist_guid:
                try:
                    guid = Guid(_id=guid_id)
                    guid.save()
                    break
                except KeyExistsException:
                    pass
        if referent:
            guid.referent = referent
            guid.save()
        return guid

    def __repr__(self):
        return '<id:{0}, referent:({1}, {2})>'.format(
            self._id, self.referent._primary_key, self.referent._name)
Beispiel #4
0
class OsfStorageFileTree(BaseFileObject):

    _id = oid_primary_key
    children = fields.AbstractForeignField(list=True)

    @classmethod
    def parent_class(cls):
        return OsfStorageFileTree

    def append_child(self, child):
        """Appending children through ODM introduces a race condition such that
        concurrent requests can overwrite previously added items; use the native
        `addToSet` operation instead.
        """
        collection = self._storage[0].store
        collection.update(
            {'_id': self._id},
            {'$addToSet': {
                'children': (child._id, child._name)
            }})
        # Updating MongoDB directly means the cache is wrong; reload manually
        self.reload()

    @property
    def is_deleted(self):
        return False
Beispiel #5
0
class Guid(StoredObject):

    _id = fields.StringField(primary=True)
    referent = fields.AbstractForeignField()

    @classmethod
    def generate(self, referent=None):
        while True:
            # Create GUID
            guid_id = ''.join(random.sample(ALPHABET, 5))

            # Check GUID against blacklist
            blacklist_guid = BlacklistGuid.load(guid_id)
            if not blacklist_guid:
                try:
                    guid = Guid(_id=guid_id)
                    guid.save()
                    break
                except KeyExistsException:
                    pass
        if referent:
            guid.referent = referent
            guid.save()
        return guid

    def __repr__(self):
        return '<id:{0}, referent:({1}, {2})>'.format(
            self._id, self.referent._primary_key, self.referent._name)
Beispiel #6
0
class OsfStorageFileTree(BaseFileObject):

    _id = oid_primary_key
    children = fields.AbstractForeignField(list=True, backref='_parent')

    @classmethod
    def parent_class(cls):
        return OsfStorageFileTree
Beispiel #7
0
class Guid(StoredObject):

    _id = fields.StringField(primary=True)
    referent = fields.AbstractForeignField()

    def __repr__(self):
        return '<id:{0}, referent:({1}, {2})>'.format(
            self._id, self.referent._primary_key, self.referent._name)
Beispiel #8
0
class Identifier(StoredObject):
    """A persistent identifier model for DOIs, ARKs, and the like."""
    _id = fields.StringField(default=lambda: str(ObjectId()))
    # object to which the identifier points
    referent = fields.AbstractForeignField(required=True)
    # category: e.g. 'ark', 'doi'
    category = fields.StringField(required=True)
    # value: e.g. 'FK424601'
    value = fields.StringField(required=True)
Beispiel #9
0
class TrashedFileNode(StoredObject):
    """The graveyard for all deleted FileNodes"""
    _id = fields.StringField(primary=True)

    last_touched = fields.DateTimeField()
    history = fields.DictionaryField(list=True)
    versions = fields.ForeignField('FileVersion', list=True)

    node = fields.ForeignField('node', required=True)
    parent = fields.AbstractForeignField(default=None)

    is_file = fields.BooleanField(default=True)
    provider = fields.StringField(required=True)

    name = fields.StringField(required=True)
    path = fields.StringField(required=True)
    materialized_path = fields.StringField(required=True)

    checkout = fields.AbstractForeignField('User')
    deleted_by = fields.AbstractForeignField('User')
    deleted_on = fields.DateTimeField(auto_now_add=True)

    @property
    def deep_url(self):
        """Allows deleted files to resolve to a view
        that will provide a nice error message and http.GONE
        """
        return self.node.web_url_for('addon_deleted_file', trashed_id=self._id)

    def restore(self):
        """Recreate a StoredFileNode from the data in this object
        Will re-point all guids and finally remove itself
        :raises KeyExistsException:
        """
        data = self.to_storage()
        data.pop('deleted_on')
        data.pop('deleted_by')
        restored = FileNode.resolve_class(self.provider,
                                          int(self.is_file))(**data)
        restored.save()
        TrashedFileNode.remove_one(self)
        return restored
Beispiel #10
0
class NotificationSubscription(StoredObject):
    _id = fields.StringField(
        primary=True)  # pxyz_wiki_updated, uabc_comment_replies

    event_name = fields.StringField()  # wiki_updated, comment_replies
    owner = fields.AbstractForeignField()

    # Notification types
    none = fields.ForeignField('user', list=True)
    email_digest = fields.ForeignField('user', list=True)
    email_transactional = fields.ForeignField('user', list=True)

    def add_user_to_subscription(self, user, notification_type, save=True):
        for nt in NOTIFICATION_TYPES:
            if user in getattr(self, nt):
                if nt != notification_type:
                    getattr(self, nt).remove(user)
            else:
                if nt == notification_type:
                    getattr(self, nt).append(user)

        if notification_type != 'none' and isinstance(
                self.owner, Node) and self.owner.parent_node:
            user_subs = self.owner.parent_node.child_node_subscriptions
            if self.owner._id not in user_subs.setdefault(user._id, []):
                user_subs[user._id].append(self.owner._id)
                self.owner.parent_node.save()

        if save:
            self.save()

    def remove_user_from_subscription(self, user, save=True):
        for notification_type in NOTIFICATION_TYPES:
            try:
                getattr(self, notification_type, []).remove(user)
            except ValueError:
                pass

        if isinstance(self.owner, Node) and self.owner.parent_node:
            try:
                self.owner.parent_node.child_node_subscriptions.get(
                    user._id, []).remove(self.owner._id)
                self.owner.parent_node.save()
            except ValueError:
                pass

        if save:
            self.save()
Beispiel #11
0
class MailRecord(StoredObject):
    _id = fields.StringField(primary=True, default=lambda: str(bson.ObjectId()))
    data = fields.DictionaryField()
    records = fields.AbstractForeignField(list=True)
Beispiel #12
0
class StoredFileNode(StoredObject):
    """The storage backend for FileNode objects.
    This class should generally not be used or created manually as FileNode
    contains all the helpers required.
    A FileNode wraps a StoredFileNode to provider usable abstraction layer
    """

    __indices__ = [{
        'unique':
        False,
        'key_or_list': [('path', pymongo.ASCENDING),
                        ('node', pymongo.ASCENDING),
                        ('is_file', pymongo.ASCENDING),
                        ('provider', pymongo.ASCENDING)]
    }, {
        'unique':
        False,
        'key_or_list': [('node', pymongo.ASCENDING),
                        ('is_file', pymongo.ASCENDING),
                        ('provider', pymongo.ASCENDING)]
    }]

    _id = fields.StringField(primary=True,
                             default=lambda: str(bson.ObjectId()))

    # The last time the touch method was called on this FileNode
    last_touched = fields.DateTimeField()
    # A list of dictionaries sorted by the 'modified' key
    # The raw output of the metadata request deduped by etag
    # Add regardless it can be pinned to a version or not
    history = fields.DictionaryField(list=True)
    # A concrete version of a FileNode, must have an identifier
    versions = fields.ForeignField('FileVersion', list=True)

    node = fields.ForeignField('Node', required=True)
    parent = fields.ForeignField('StoredFileNode', default=None)

    is_file = fields.BooleanField(default=True)
    provider = fields.StringField(required=True)

    name = fields.StringField(required=True)
    path = fields.StringField(required=True)
    materialized_path = fields.StringField(required=True)

    # The User that has this file "checked out"
    # Should only be used for OsfStorage
    checkout = fields.AbstractForeignField('User')

    #Tags for a file, currently only used for osfStorage
    tags = fields.ForeignField('Tag', list=True)

    # For Django compatibility
    @property
    def pk(self):
        return self._id

    # For Django compatibility
    # TODO Find a better way
    @property
    def node_id(self):
        return self.node._id

    @property
    def deep_url(self):
        return self.wrapped().deep_url

    def wrapped(self):
        """Wrap self in a FileNode subclass
        """
        return FileNode.resolve_class(self.provider, int(self.is_file))(self)

    def get_guid(self, create=False):
        """Attempt to find a Guid that points to this object.
        One will be created if requested.
        :rtype: Guid
        """
        try:
            # Note sometimes multiple GUIDs can exist for
            # a single object. Just go with the first one
            return Guid.find(Q('referent', 'eq', self))[0]
        except IndexError:
            if not create:
                return None
        return Guid.generate(self)
Beispiel #13
0
class AddonOAuthNodeSettingsBase(AddonNodeSettingsBase):
    _meta = {
        'abstract': True,
    }

    # TODO: Validate this field to be sure it matches the provider's short_name
    # NOTE: Do not set this field directly. Use ``set_auth()``
    external_account = fields.ForeignField('externalaccount')

    # NOTE: Do not set this field directly. Use ``set_auth()``
    user_settings = fields.AbstractForeignField()

    # The existence of this property is used to determine whether or not
    #   an addon instance is an "OAuth addon" in
    #   AddonModelMixin.get_oauth_addons().
    oauth_provider = None

    @property
    def folder_id(self):
        raise NotImplementedError(
            "AddonOAuthNodeSettingsBase subclasses must expose a 'folder_id' property."
        )

    @property
    def folder_name(self):
        raise NotImplementedError(
            "AddonOAuthNodeSettingsBase subclasses must expose a 'folder_name' property."
        )

    @property
    def folder_path(self):
        raise NotImplementedError(
            "AddonOAuthNodeSettingsBase subclasses must expose a 'folder_path' property."
        )

    @property
    def nodelogger(self):
        auth = None
        if self.user_settings:
            auth = Auth(self.user_settings.owner)
        self._logger_class = getattr(
            self,
            '_logger_class',
            type(
                '{0}NodeLogger'.format(self.config.short_name.capitalize()),
                (logger.AddonNodeLogger, ),
                {'addon_short_name': self.config.short_name}
            )
        )
        return self._logger_class(
            node=self.owner,
            auth=auth
        )

    @property
    def complete(self):
        return bool(
            self.has_auth and
            self.external_account and
            self.user_settings.verify_oauth_access(
                node=self.owner,
                external_account=self.external_account,
            )
        )

    @property
    def configured(self):
        return bool(
            self.complete and
            (self.folder_id or self.folder_name or self.folder_path)
        )

    @property
    def has_auth(self):
        """Instance has an external account and *active* permission to use it"""
        return bool(
            self.user_settings and self.user_settings.has_auth
        ) and bool(
            self.external_account and self.user_settings.verify_oauth_access(
                node=self.owner,
                external_account=self.external_account
            )
        )

    def clear_settings(self):
        raise NotImplementedError(
            "AddonOAuthNodeSettingsBase subclasses must expose a 'clear_settings' method."
        )

    def set_auth(self, external_account, user, metadata=None, log=True):
        """Connect the node addon to a user's external account.

        This method also adds the permission to use the account in the user's
        addon settings.
        """
        # tell the user's addon settings that this node is connected to it
        user_settings = user.get_or_add_addon(self.oauth_provider.short_name)
        user_settings.grant_oauth_access(
            node=self.owner,
            external_account=external_account,
            metadata=metadata  # metadata can be passed in when forking
        )
        user_settings.save()

        # update this instance
        self.user_settings = user_settings
        self.external_account = external_account

        if log:
            self.nodelogger.log(action='node_authorized', save=True)
        self.save()

    def deauthorize(self, auth=None, add_log=False):
        """Remove authorization from this node.

        This method should be overridden for addon-specific behavior,
        such as logging and clearing non-generalizable settings.
        """
        self.clear_auth()

    def clear_auth(self):
        """Disconnect the node settings from the user settings.

        This method does not remove the node's permission in the user's addon
        settings.
        """
        self.external_account = None
        self.user_settings = None
        self.save()

    def before_remove_contributor_message(self, node, removed):
        """If contributor to be removed authorized this addon, warn that removing
        will remove addon authorization.
        """
        if self.has_auth and self.user_settings.owner == removed:
            return (
                u'The {addon} add-on for this {category} is authenticated by {name}. '
                u'Removing this user will also remove write access to {addon} '
                u'unless another contributor re-authenticates the add-on.'
            ).format(
                addon=self.config.full_name,
                category=node.project_or_component,
                name=removed.fullname,
            )

    # backwards compatibility
    before_remove_contributor = before_remove_contributor_message

    def after_remove_contributor(self, node, removed, auth=None):
        """If removed contributor authorized this addon, remove addon authorization
        from owner.
        """
        if self.user_settings and self.user_settings.owner == removed:

            # Delete OAuth tokens
            self.user_settings.oauth_grants[self.owner._id].pop(self.external_account._id)
            self.clear_auth()
            message = (
                u'Because the {addon} add-on for {category} "{title}" was authenticated '
                u'by {user}, authentication information has been deleted.'
            ).format(
                addon=self.config.full_name,
                category=markupsafe.escape(node.category_display),
                title=markupsafe.escape(node.title),
                user=markupsafe.escape(removed.fullname)
            )

            if not auth or auth.user != removed:
                url = node.web_url_for('node_setting')
                message += (
                    u' You can re-authenticate on the <u><a href="{url}">Settings</a></u> page.'
                ).format(url=url)
            #
            return message

    def after_fork(self, node, fork, user, save=True):
        """After forking, copy user settings if the user is the one who authorized
        the addon.

        :return: A tuple of the form (cloned_settings, message)
        """
        clone, _ = super(AddonOAuthNodeSettingsBase, self).after_fork(
            node=node,
            fork=fork,
            user=user,
            save=False,
        )
        if self.has_auth and self.user_settings.owner == user:
            metadata = None
            if self.complete:
                try:
                    metadata = self.user_settings.oauth_grants[node._id][self.external_account._id]
                except (KeyError, AttributeError):
                    pass
            clone.set_auth(self.external_account, user, metadata=metadata, log=False)
            message = '{addon} authorization copied to forked {category}.'.format(
                addon=self.config.full_name,
                category=fork.project_or_component,
            )
        else:
            message = (
                u'{addon} authorization not copied to forked {category}. You may '
                u'authorize this fork on the <u><a href="{url}">Settings</a></u> '
                u'page.'
            ).format(
                addon=self.config.full_name,
                url=fork.web_url_for('node_setting'),
                category=fork.project_or_component,
            )
        if save:
            clone.save()
        return clone, message

    def before_register_message(self, node, user):
        """Return warning text to display if user auth will be copied to a
        registration.
        """
        if self.has_auth:
            return (
                u'The contents of {addon} add-ons cannot be registered at this time; '
                u'the {addon} add-on linked to this {category} will not be included '
                u'as part of this registration.'
            ).format(
                addon=self.config.full_name,
                category=node.project_or_component,
            )

    # backwards compatibility
    before_register = before_register_message

    def serialize_waterbutler_credentials(self):
        raise NotImplementedError(
            "AddonOAuthNodeSettingsBase subclasses must implement a 'serialize_waterbutler_credentials' method."
        )

    def serialize_waterbutler_settings(self):
        raise NotImplementedError(
            "AddonOAuthNodeSettingsBase subclasses must implement a 'serialize_waterbutler_settings' method."
        )
Beispiel #14
0
class AddonOAuthNodeSettingsBase(AddonNodeSettingsBase):
    _meta = {
        'abstract': True,
    }

    # TODO: Validate this field to be sure it matches the provider's short_name
    # NOTE: Do not set this field directly. Use ``set_auth()``
    external_account = fields.ForeignField('externalaccount',
                                           backref='connected')

    # NOTE: Do not set this field directly. Use ``set_auth()``
    user_settings = fields.AbstractForeignField()

    # The existence of this property is used to determine whether or not
    #   an addon instance is an "OAuth addon" in
    #   AddonModelMixin.get_oauth_addons().
    oauth_provider = None

    @property
    def has_auth(self):
        """Instance has an external account and *active* permission to use it"""
        if not (self.user_settings and self.external_account):
            return False

        return self.user_settings.verify_oauth_access(
            node=self.owner, external_account=self.external_account)

    def set_auth(self, external_account, user):
        """Connect the node addon to a user's external account.

        This method also adds the permission to use the account in the user's
        addon settings.
        """
        # tell the user's addon settings that this node is connected to it
        user_settings = user.get_or_add_addon(self.oauth_provider.short_name)
        user_settings.grant_oauth_access(
            node=self.owner,
            external_account=external_account
            # no metadata, because the node has access to no folders
        )
        user_settings.save()

        # update this instance
        self.user_settings = user_settings
        self.external_account = external_account

        self.save()

    def clear_auth(self):
        """Disconnect the node settings from the user settings.

        This method does not remove the node's permission in the user's addon
        settings.
        """
        self.external_account = None
        self.user_settings = None
        self.save()

    def before_remove_contributor_message(self, node, removed):
        """If contributor to be removed authorized this addon, warn that removing
        will remove addon authorization.
        """
        if self.has_auth and self.user_settings.owner == removed:
            return (
                u'The {addon} add-on for this {category} is authenticated by {name}. '
                u'Removing this user will also remove write access to {addon} '
                u'unless another contributor re-authenticates the add-on.'
            ).format(
                addon=self.config.full_name,
                category=node.project_or_component,
                name=removed.fullname,
            )

    # backwards compatibility
    before_remove_contributor = before_remove_contributor_message

    def after_remove_contributor(self, node, removed, auth=None):
        """If removed contributor authorized this addon, remove addon authorization
        from owner.
        """
        if self.has_auth and self.user_settings.owner == removed:

            # Delete OAuth tokens
            self.user_settings.oauth_grants[self.owner._id].pop(
                self.external_account._id)
            self.clear_auth()
            message = (
                u'Because the {addon} add-on for {category} "{title}" was authenticated '
                u'by {user}, authentication information has been deleted.'
            ).format(addon=self.config.full_name,
                     category=node.category_display,
                     title=node.title,
                     user=removed.fullname)

            if not auth or auth.user != removed:
                url = node.web_url_for('node_setting')
                message += (
                    u' You can re-authenticate on the <a href="{url}">Settings</a> page.'
                ).format(url=url)
            #
            return message

    def before_fork_message(self, node, user):
        """Return warning text to display if user auth will be copied to a
        fork.
        """
        if self.user_settings and self.user_settings.owner == user:
            return (
                u'Because you have authorized the {addon} add-on for this '
                u'{category}, forking it will also transfer your authentication token to '
                u'the forked {category}.').format(
                    addon=self.config.full_name,
                    category=node.project_or_component,
                )
        return (
            u'Because the {addon} add-on has been authorized by a different '
            u'user, forking it will not transfer authentication token to the forked '
            u'{category}.').format(
                addon=self.config.full_name,
                category=node.project_or_component,
            )

    # backwards compatibility
    before_fork = before_fork_message

    def after_fork(self, node, fork, user, save=True):
        """After forking, copy user settings if the user is the one who authorized
        the addon.

        :return: A tuple of the form (cloned_settings, message)
        """
        clone, _ = super(AddonOAuthNodeSettingsBase, self).after_fork(
            node=node,
            fork=fork,
            user=user,
            save=False,
        )
        if self.has_auth and self.user_settings.owner == user:
            clone.set_auth(self.external_account, user)
            message = '{addon} authorization copied to forked {category}.'.format(
                addon=self.config.full_name,
                category=fork.project_or_component,
            )
        else:
            message = (
                u'{addon} authorization not copied to forked {category}. You may '
                u'authorize this fork on the <a href="{url}">Settings</a> '
                u'page.').format(
                    addon=self.config.full_name,
                    url=fork.web_url_for('node_setting'),
                    category=fork.project_or_component,
                )
        if save:
            clone.save()
        return clone, message

    def before_register_message(self, node, user):
        """Return warning text to display if user auth will be copied to a
        registration.
        """
        if self.has_auth:
            return (
                u'The contents of {addon} add-ons cannot be registered at this time; '
                u'the {addon} add-on linked to this {category} will not be included '
                u'as part of this registration.').format(
                    addon=self.config.full_name,
                    category=node.project_or_component,
                )

    # backwards compatibility
    before_register = before_register_message
Beispiel #15
0
 class Foo(TestObject):
     _id = fields.IntegerField()
     bars = fields.AbstractForeignField(list=True)
Beispiel #16
0
class TrashedFileNode(StoredObject, Commentable):
    """The graveyard for all deleted FileNodes"""

    __indices__ = [{
        'unique': False,
        'key_or_list': [
            ('node', pymongo.ASCENDING),
            ('is_file', pymongo.ASCENDING),
            ('provider', pymongo.ASCENDING),
        ]
    }]

    _id = fields.StringField(primary=True)

    last_touched = fields.DateTimeField()
    history = fields.DictionaryField(list=True)
    versions = fields.ForeignField('FileVersion', list=True)

    node = fields.ForeignField('node', required=True)
    parent = fields.AbstractForeignField(default=None)

    is_file = fields.BooleanField(default=True)
    provider = fields.StringField(required=True)

    name = fields.StringField(required=True)
    path = fields.StringField(required=True)
    materialized_path = fields.StringField(required=True)

    checkout = fields.AbstractForeignField('User')
    deleted_by = fields.AbstractForeignField('User')
    deleted_on = fields.DateTimeField(auto_now_add=True)
    tags = fields.ForeignField('Tag', list=True)
    suspended = fields.BooleanField(default=False)

    copied_from = fields.ForeignField('StoredFileNode', default=None)

    @property
    def deep_url(self):
        """Allows deleted files to resolve to a view
        that will provide a nice error message and http.GONE
        """
        return self.node.web_url_for('addon_deleted_file', trashed_id=self._id)

    # For Comment API compatibility
    @property
    def target_type(self):
        """The object "type" used in the OSF v2 API."""
        return 'files'

    @property
    def root_target_page(self):
        """The comment page type associated with TrashedFileNodes."""
        return 'files'

    @property
    def is_deleted(self):
        return True

    def belongs_to_node(self, node_id):
        """Check whether the file is attached to the specified node."""
        return self.node._id == node_id

    def get_extra_log_params(self, comment):
        return {'file': {'name': self.name, 'url': comment.get_comment_page_url()}}

    def restore(self, recursive=True, parent=None):
        """Recreate a StoredFileNode from the data in this object
        Will re-point all guids and finally remove itself
        :raises KeyExistsException:
        """
        data = self.to_storage()
        data.pop('deleted_on')
        data.pop('deleted_by')
        data.pop('suspended')
        if parent:
            data['parent'] = parent._id
        elif data['parent']:
            # parent is an AbstractForeignField, so it gets stored as tuple
            data['parent'] = data['parent'][0]
        restored = FileNode.resolve_class(self.provider, int(self.is_file))(**data)
        if not restored.parent:
            raise ValueError('No parent to restore to')
        restored.save()

        # repoint guid
        for guid in Guid.find(Q('referent', 'eq', self)):
            guid.referent = restored
            guid.save()

        if recursive:
            for child in TrashedFileNode.find(Q('parent', 'eq', self)):
                child.restore(recursive=recursive, parent=restored)

        TrashedFileNode.remove_one(self)
        return restored

    def get_guid(self):
        """Attempt to find a Guid that points to this object.

        :rtype: Guid or None
        """
        try:
            # Note sometimes multiple GUIDs can exist for
            # a single object. Just go with the first one
            return Guid.find(Q('referent', 'eq', self))[0]
        except IndexError:
            return None
Beispiel #17
0
class StoredFileNode(StoredObject, Commentable):
    """The storage backend for FileNode objects.
    This class should generally not be used or created manually as FileNode
    contains all the helpers required.
    A FileNode wraps a StoredFileNode to provider usable abstraction layer
    """

    __indices__ = [{
        'unique': False,
        'key_or_list': [
            ('path', pymongo.ASCENDING),
            ('node', pymongo.ASCENDING),
            ('is_file', pymongo.ASCENDING),
            ('provider', pymongo.ASCENDING),
        ]
    }, {
        'unique': False,
        'key_or_list': [
            ('node', pymongo.ASCENDING),
            ('is_file', pymongo.ASCENDING),
            ('provider', pymongo.ASCENDING),
        ]
    }, {
        'unique': False,
        'key_or_list': [
            ('parent', pymongo.ASCENDING),
        ]
    }]

    _id = fields.StringField(primary=True, default=lambda: str(bson.ObjectId()))

    # The last time the touch method was called on this FileNode
    last_touched = fields.DateTimeField()
    # A list of dictionaries sorted by the 'modified' key
    # The raw output of the metadata request deduped by etag
    # Add regardless it can be pinned to a version or not
    history = fields.DictionaryField(list=True)
    # A concrete version of a FileNode, must have an identifier
    versions = fields.ForeignField('FileVersion', list=True)

    node = fields.ForeignField('Node', required=True)
    parent = fields.ForeignField('StoredFileNode', default=None)
    copied_from = fields.ForeignField('StoredFileNode', default=None)

    is_file = fields.BooleanField(default=True)
    provider = fields.StringField(required=True)

    name = fields.StringField(required=True)
    path = fields.StringField(required=True)
    materialized_path = fields.StringField(required=True)

    # The User that has this file "checked out"
    # Should only be used for OsfStorage
    checkout = fields.AbstractForeignField('User')

    #Tags for a file, currently only used for osfStorage
    tags = fields.ForeignField('Tag', list=True)

    # For Django compatibility
    @property
    def pk(self):
        return self._id

    # For Django compatibility
    # TODO Find a better way
    @property
    def node_id(self):
        return self.node._id

    @property
    def deep_url(self):
        return self.wrapped().deep_url

    @property
    def absolute_api_v2_url(self):
        path = '/files/{}/'.format(self._id)
        return util.api_v2_url(path)

    # For Comment API compatibility
    @property
    def target_type(self):
        """The object "type" used in the OSF v2 API."""
        return 'files'

    @property
    def root_target_page(self):
        """The comment page type associated with StoredFileNodes."""
        return 'files'

    @property
    def is_deleted(self):
        if self.provider == 'osfstorage':
            return False

    def belongs_to_node(self, node_id):
        """Check whether the file is attached to the specified node."""
        return self.node._id == node_id

    def get_extra_log_params(self, comment):
        return {'file': {'name': self.name, 'url': comment.get_comment_page_url()}}

    # used by django and DRF
    def get_absolute_url(self):
        return self.absolute_api_v2_url

    def wrapped(self):
        """Wrap self in a FileNode subclass
        """
        return FileNode.resolve_class(self.provider, int(self.is_file))(self)

    def get_guid(self, create=False):
        """Attempt to find a Guid that points to this object.
        One will be created if requested.

        :param Boolean create: Should we generate a GUID if there isn't one?  Default: False
        :rtype: Guid or None
        """
        try:
            # Note sometimes multiple GUIDs can exist for
            # a single object. Just go with the first one
            return Guid.find(Q('referent', 'eq', self))[0]
        except IndexError:
            if not create:
                return None
        return Guid.generate(self)
 class Foo(StoredObject):
     _meta = {'optimistic': True}
     _id = fields.StringField()
     my_abstract = fields.AbstractForeignField(backref='my_foos')
     my_other_abstract = fields.AbstractForeignField(backref='my_foos')
Beispiel #19
0
 class Foo(TestObject):
     _id = fields.IntegerField()
     my_abstract = fields.AbstractForeignField(backref='my_foo')