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, }
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
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)
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
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)
class OsfStorageFileTree(BaseFileObject): _id = oid_primary_key children = fields.AbstractForeignField(list=True, backref='_parent') @classmethod def parent_class(cls): return OsfStorageFileTree
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)
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)
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
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()
class MailRecord(StoredObject): _id = fields.StringField(primary=True, default=lambda: str(bson.ObjectId())) data = fields.DictionaryField() records = fields.AbstractForeignField(list=True)
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)
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." )
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
class Foo(TestObject): _id = fields.IntegerField() bars = fields.AbstractForeignField(list=True)
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
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')
class Foo(TestObject): _id = fields.IntegerField() my_abstract = fields.AbstractForeignField(backref='my_foo')