class CredentialModel(sql.ModelBase, sql.ModelDictMixinWithExtras): __tablename__ = 'credential' attributes = [ 'id', 'user_id', 'project_id', 'encrypted_blob', 'type', 'key_hash' ] id = sql.Column(sql.String(64), primary_key=True) user_id = sql.Column(sql.String(64), nullable=False) project_id = sql.Column(sql.String(64)) _encrypted_blob = sql.Column('encrypted_blob', sql.Text(), nullable=True) type = sql.Column(sql.String(255), nullable=False) key_hash = sql.Column(sql.String(64), nullable=True) extra = sql.Column(sql.JsonBlob()) @hybrid_property def encrypted_blob(self): return self._encrypted_blob @encrypted_blob.setter def encrypted_blob(self, encrypted_blob): # Make sure to hand over the encrypted credential as a string value # to the backend driver to avoid the sql drivers (esp. psycopg2) # treating this as binary data and e.g. hex-escape it. if isinstance(encrypted_blob, bytes): encrypted_blob = encrypted_blob.decode('utf-8') self._encrypted_blob = encrypted_blob
class Endpoint(sql.ModelBase, sql.ModelDictMixinWithExtras): __tablename__ = 'endpoint' attributes = [ 'id', 'interface', 'region_id', 'service_id', 'url', 'legacy_endpoint_id', 'enabled' ] id = sql.Column(sql.String(64), primary_key=True) legacy_endpoint_id = sql.Column(sql.String(64)) interface = sql.Column(sql.String(8), nullable=False) region_id = sql.Column(sql.String(255), sql.ForeignKey('region.id', ondelete='RESTRICT'), nullable=True, default=None) service_id = sql.Column(sql.String(64), sql.ForeignKey('service.id'), nullable=False) url = sql.Column(sql.Text(), nullable=False) enabled = sql.Column(sql.Boolean, nullable=False, default=True, server_default=sqlalchemy.sql.expression.true()) extra = sql.Column(sql.JsonBlob()) @classmethod def from_dict(cls, endpoint_dict): """Override from_dict to set enabled if missing.""" new_dict = endpoint_dict.copy() if new_dict.get('enabled') is None: new_dict['enabled'] = True return super(Endpoint, cls).from_dict(new_dict)
class Project(sql.ModelBase, sql.ModelDictMixinWithExtras): # NOTE(henry-nash): From the manager and above perspective, the domain_id # is nullable. However, to ensure uniqueness in multi-process # configurations, it is better to still use the sql uniqueness constraint. # Since the support for a nullable component of a uniqueness constraint # across different sql databases is mixed, we instead store a special value # to represent null, as defined in NULL_DOMAIN_ID above. def to_dict(self, include_extra_dict=False): d = super(Project, self).to_dict( include_extra_dict=include_extra_dict) if d['domain_id'] == base.NULL_DOMAIN_ID: d['domain_id'] = None return d __tablename__ = 'project' attributes = ['id', 'name', 'domain_id', 'description', 'enabled', 'parent_id', 'is_domain'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), sql.ForeignKey('project.id'), nullable=False) description = sql.Column(sql.Text()) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id')) is_domain = sql.Column(sql.Boolean, default=False, nullable=False, server_default='0') # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'),)
class RegisteredLimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'registered_limit' attributes = [ 'internal_id', 'id', 'service_id', 'region_id', 'resource_name', 'default_limit', 'description' ] internal_id = sql.Column(sql.Integer, primary_key=True, nullable=False) id = sql.Column(sql.String(length=64), nullable=False, unique=True) service_id = sql.Column(sql.String(255), sql.ForeignKey('service.id')) region_id = sql.Column(sql.String(64), sql.ForeignKey('region.id'), nullable=True) resource_name = sql.Column(sql.String(255)) default_limit = sql.Column(sql.Integer, nullable=False) description = sql.Column(sql.Text()) def to_dict(self): ref = super(RegisteredLimitModel, self).to_dict() ref.pop('internal_id') return ref
class ServiceProviderModel(sql.ModelBase, sql.DictBase): __tablename__ = 'service_provider' attributes = [ 'auth_url', 'id', 'enabled', 'description', 'relay_state_prefix', 'sp_url' ] mutable_attributes = frozenset( ['auth_url', 'description', 'enabled', 'relay_state_prefix', 'sp_url']) id = sql.Column(sql.String(64), primary_key=True) enabled = sql.Column(sql.Boolean, nullable=False) description = sql.Column(sql.Text(), nullable=True) auth_url = sql.Column(sql.String(256), nullable=False) sp_url = sql.Column(sql.String(256), nullable=False) relay_state_prefix = sql.Column(sql.String(256), nullable=False) @classmethod def from_dict(cls, dictionary): new_dictionary = dictionary.copy() return cls(**new_dictionary) def to_dict(self): """Return a dictionary with model's attributes.""" d = dict() for attr in self.__class__.attributes: d[attr] = getattr(self, attr) return d
class ApplicationCredentialModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'application_credential' attributes = [ 'internal_id', 'id', 'name', 'secret_hash', 'description', 'user_id', 'project_id', 'system', 'expires_at', 'unrestricted' ] internal_id = sql.Column(sql.Integer, primary_key=True, nullable=False) id = sql.Column(sql.String(64), nullable=False) name = sql.Column(sql.String(255), nullable=False) secret_hash = sql.Column(sql.String(255), nullable=False) description = sql.Column(sql.Text()) user_id = sql.Column(sql.String(64), nullable=False) project_id = sql.Column(sql.String(64), nullable=True) system = sql.Column(sql.String(64), nullable=True) expires_at = sql.Column(sql.DateTimeInt()) unrestricted = sql.Column(sql.Boolean) __table_args__ = (sql.UniqueConstraint( 'name', 'user_id', name='duplicate_app_cred_constraint'), ) roles = sqlalchemy.orm.relationship( 'ApplicationCredentialRoleModel', backref=sqlalchemy.orm.backref('application_credential'), cascade='all, delete-orphan') access_rules = sqlalchemy.orm.relationship( 'ApplicationCredentialAccessRuleModel', backref=sqlalchemy.orm.backref('application_credential'), cascade='all, delete-orphan')
class Project(sql.ModelBase, sql.ModelDictMixinWithExtras): # NOTE(henry-nash): From the manager and above perspective, the domain_id # is nullable. However, to ensure uniqueness in multi-process # configurations, it is better to still use the sql uniqueness constraint. # Since the support for a nullable component of a uniqueness constraint # across different sql databases is mixed, we instead store a special value # to represent null, as defined in NULL_DOMAIN_ID above. def to_dict(self, include_extra_dict=False): d = super(Project, self).to_dict(include_extra_dict=include_extra_dict) if d['domain_id'] == base.NULL_DOMAIN_ID: d['domain_id'] = None return d __tablename__ = 'project' attributes = [ 'id', 'name', 'domain_id', 'description', 'enabled', 'parent_id', 'is_domain', 'tags' ] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), sql.ForeignKey('project.id'), nullable=False) description = sql.Column(sql.Text()) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id')) is_domain = sql.Column(sql.Boolean, default=False, nullable=False, server_default='0') _tags = orm.relationship( 'ProjectTag', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='project', primaryjoin='and_(ProjectTag.project_id==Project.id)') # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), ) @property def tags(self): if self._tags: return [tag.name for tag in self._tags] return [] @tags.setter def tags(self, values): new_tags = [] for tag in values: tag_ref = ProjectTag() tag_ref.project_id = self.id tag_ref.name = text_type(tag) new_tags.append(tag_ref) self._tags = new_tags
class LimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'limit' attributes = [ 'internal_id', 'id', 'project_id', 'domain_id', 'service_id', 'region_id', 'resource_name', 'resource_limit', 'description', 'registered_limit_id' ] internal_id = sql.Column(sql.Integer, primary_key=True, nullable=False) id = sql.Column(sql.String(length=64), nullable=False, unique=True) project_id = sql.Column(sql.String(64)) domain_id = sql.Column(sql.String(64)) resource_limit = sql.Column(sql.Integer, nullable=False) description = sql.Column(sql.Text()) registered_limit_id = sql.Column(sql.String(64), sql.ForeignKey('registered_limit.id')) registered_limit = sqlalchemy.orm.relationship('RegisteredLimitModel') @hybrid_property def service_id(self): if self.registered_limit: return self.registered_limit.service_id return None @service_id.expression def service_id(self): return RegisteredLimitModel.service_id @hybrid_property def region_id(self): if self.registered_limit: return self.registered_limit.region_id return None @region_id.expression def region_id(self): return RegisteredLimitModel.region_id @hybrid_property def resource_name(self): if self.registered_limit: return self.registered_limit.resource_name return self._resource_name @resource_name.expression def resource_name(self): return RegisteredLimitModel.resource_name def to_dict(self): ref = super(LimitModel, self).to_dict() if self.registered_limit: ref['service_id'] = self.registered_limit.service_id ref['region_id'] = self.registered_limit.region_id ref['resource_name'] = self.registered_limit.resource_name ref.pop('internal_id') ref.pop('registered_limit_id') return ref
class Group(sql.ModelBase, sql.DictBase): __tablename__ = 'group' attributes = ['id', 'name', 'domain_id', 'description'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), nullable=False) description = sql.Column(sql.Text()) extra = sql.Column(sql.JsonBlob()) # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class CredentialModel(sql.ModelBase, sql.ModelDictMixinWithExtras): __tablename__ = 'credential' attributes = [ 'id', 'user_id', 'project_id', 'encrypted_blob', 'type', 'key_hash' ] id = sql.Column(sql.String(64), primary_key=True) user_id = sql.Column(sql.String(64), nullable=False) project_id = sql.Column(sql.String(64)) encrypted_blob = sql.Column(sql.Text(), nullable=True) type = sql.Column(sql.String(255), nullable=False) key_hash = sql.Column(sql.String(64), nullable=True) extra = sql.Column(sql.JsonBlob())
class Endpoint(sql.ModelBase, sql.DictBase): __tablename__ = 'endpoint' attributes = ['id', 'interface', 'region', 'service_id', 'url', 'legacy_endpoint_id'] id = sql.Column(sql.String(64), primary_key=True) legacy_endpoint_id = sql.Column(sql.String(64)) interface = sql.Column(sql.String(8), nullable=False) region = sql.Column(sql.String(255)) service_id = sql.Column(sql.String(64), sql.ForeignKey('service.id'), nullable=False) url = sql.Column(sql.Text(), nullable=False) extra = sql.Column(sql.JsonBlob())
class Group(sql.ModelBase, sql.ModelDictMixinWithExtras): __tablename__ = 'group' attributes = ['id', 'name', 'domain_id', 'description'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), nullable=False) description = sql.Column(sql.Text()) extra = sql.Column(sql.JsonBlob()) expiring_user_group_memberships = orm.relationship( 'ExpiringUserGroupMembership', cascade='all, delete-orphan', backref="group") # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), )
class Project(sql.ModelBase, sql.DictBase): __tablename__ = 'project' attributes = ['id', 'name', 'domain_id', 'description', 'enabled', 'parent_id'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'), nullable=False) description = sql.Column(sql.Text()) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id')) # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class IdentityProviderModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'identity_provider' attributes = [ 'id', 'domain_id', 'enabled', 'description', 'remote_ids', 'authorization_ttl' ] mutable_attributes = frozenset( ['description', 'enabled', 'remote_ids', 'authorization_ttl']) id = sql.Column(sql.String(64), primary_key=True) domain_id = sql.Column(sql.String(64), nullable=False) enabled = sql.Column(sql.Boolean, nullable=False) description = sql.Column(sql.Text(), nullable=True) authorization_ttl = sql.Column(sql.Integer, nullable=True) remote_ids = orm.relationship('IdPRemoteIdsModel', order_by='IdPRemoteIdsModel.remote_id', cascade='all, delete-orphan') expiring_user_group_memberships = orm.relationship( 'ExpiringUserGroupMembership', cascade='all, delete-orphan', backref="idp") @classmethod def from_dict(cls, dictionary): new_dictionary = dictionary.copy() remote_ids_list = new_dictionary.pop('remote_ids', None) if not remote_ids_list: remote_ids_list = [] identity_provider = cls(**new_dictionary) remote_ids = [] # NOTE(fmarco76): the remote_ids_list contains only remote ids # associated with the IdP because of the "relationship" established in # sqlalchemy and corresponding to the FK in the idp_remote_ids table for remote in remote_ids_list: remote_ids.append(IdPRemoteIdsModel(remote_id=remote)) identity_provider.remote_ids = remote_ids return identity_provider def to_dict(self): """Return a dictionary with model's attributes.""" d = dict() for attr in self.__class__.attributes: d[attr] = getattr(self, attr) d['remote_ids'] = [] for remote in self.remote_ids: d['remote_ids'].append(remote.remote_id) return d
class Consumer(sql.ModelBase, sql.DictBase): __tablename__ = 'consumer_oauth2' attributes = ['id', 'name', 'description', 'secret', 'client_type', 'redirect_uris', 'grant_type', 'response_type', 'scopes', 'extra'] __table_args__ = {'extend_existing': True} id = sql.Column(sql.String(64), primary_key=True, nullable=False) name = sql.Column(sql.String(64), nullable=False) description = sql.Column(sql.Text(), nullable=True) secret = sql.Column(sql.String(128), nullable=False) client_type = sql.Column(VALID_CLIENT_TYPES, nullable=False) redirect_uris = sql.Column(sql.JsonBlob(), nullable=False) grant_type = sql.Column(VALID_GRANT_TYPES, nullable=False) response_type = sql.Column(VALID_RESPONSE_TYPES, nullable=False) # TODO(garcianavalon) better naming to reflect they are the allowed scopes for the client scopes = sql.Column(sql.JsonBlob(), nullable=True) extra = sql.Column(sql.JsonBlob(), nullable=True)
class Permission(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'permission_fiware' __table_args__ = (sql.UniqueConstraint('name', 'application_id'), {'extend_existing': True}) attributes = ['id', 'name', 'is_internal', 'application_id', 'action' 'resource', 'xml'] id = sql.Column(sql.String(64), primary_key=True, nullable=False) name = sql.Column(sql.String(64), nullable=False) is_internal = sql.Column(sql.Boolean(), default=False, nullable=False) application_id = sql.Column( sql.String(64), sql.ForeignKey('consumer_oauth2.id'), nullable=False, index=True) action = sql.Column(sql.String(10), nullable=True) resource = sql.Column(sql.String(256), nullable=True) xml = sql.Column(sql.Text(), nullable=True)
class RegisteredLimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'registered_limit' attributes = [ 'id', 'service_id', 'region_id', 'resource_name', 'default_limit', 'description' ] id = sql.Column(sql.String(length=64), primary_key=True) service_id = sql.Column(sql.String(255), sql.ForeignKey('service.id')) region_id = sql.Column(sql.String(64), sql.ForeignKey('region.id'), nullable=True) resource_name = sql.Column(sql.String(255)) default_limit = sql.Column(sql.Integer, nullable=False) description = sql.Column(sql.Text()) __table_args__ = (sqlalchemy.UniqueConstraint('service_id', 'region_id', 'resource_name'), )
class Endpoint(sql.ModelBase, sql.DictBase): __tablename__ = 'endpoint' attributes = ['id', 'interface', 'region_id', 'service_id', 'url', 'legacy_endpoint_id', 'enabled'] id = sql.Column(sql.String(64), primary_key=True) legacy_endpoint_id = sql.Column(sql.String(64)) interface = sql.Column(sql.String(8), nullable=False) region_id = sql.Column(sql.String(255), sql.ForeignKey('region.id', ondelete='RESTRICT'), nullable=True, default=None) service_id = sql.Column(sql.String(64), sql.ForeignKey('service.id'), nullable=False) url = sql.Column(sql.Text(), nullable=False) enabled = sql.Column(sql.Boolean, nullable=False, default=True, server_default=sqlalchemy.sql.expression.true()) extra = sql.Column(sql.JsonBlob())
class IdentityProviderModel(sql.ModelBase, sql.DictBase): __tablename__ = 'identity_provider' attributes = ['id', 'enabled', 'description'] mutable_attributes = frozenset(['description', 'enabled']) id = sql.Column(sql.String(64), primary_key=True) enabled = sql.Column(sql.Boolean, nullable=False) description = sql.Column(sql.Text(), nullable=True) @classmethod def from_dict(cls, dictionary): new_dictionary = dictionary.copy() return cls(**new_dictionary) def to_dict(self, include_extra_dict=False): """Return the model's attributes as a dictionary.""" d = dict() for attr in self.__class__.attributes: d[attr] = getattr(self, attr) return d
class AccessToken(sql.ModelBase, sql.DictBase): __tablename__ = 'access_token' attributes = ['id', 'access_secret', 'authorizing_user_id', 'project_id', 'role_ids', 'consumer_id', 'expires_at'] id = sql.Column(sql.String(64), primary_key=True, nullable=False) access_secret = sql.Column(sql.String(64), nullable=False) authorizing_user_id = sql.Column(sql.String(64), nullable=False, index=True) project_id = sql.Column(sql.String(64), nullable=False) role_ids = sql.Column(sql.Text(), nullable=False) consumer_id = sql.Column(sql.String(64), sql.ForeignKey('consumer.id'), nullable=False) expires_at = sql.Column(sql.String(64), nullable=True) @classmethod def from_dict(cls, user_dict): return cls(**user_dict) def to_dict(self): return dict(self.items())
class RequestToken(sql.ModelBase, sql.DictBase): __tablename__ = 'request_token' attributes = [ 'id', 'request_secret', 'verifier', 'authorizing_user_id', 'requested_project_id', 'requested_roles', 'consumer_id', 'expires_at' ] id = sql.Column(sql.String(64), primary_key=True, nullable=False) request_secret = sql.Column(sql.String(64), nullable=False) verifier = sql.Column(sql.String(64), nullable=True) authorizing_user_id = sql.Column(sql.String(64), nullable=True) requested_project_id = sql.Column(sql.String(64), nullable=False) requested_roles = sql.Column(sql.Text(), nullable=False) consumer_id = sql.Column(sql.String(64), nullable=False, index=True) expires_at = sql.Column(sql.String(64), nullable=True) @classmethod def from_dict(cls, user_dict): return cls(**user_dict) def to_dict(self): return dict(self.iteritems())
class LimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'limit' attributes = [ 'internal_id', 'id', 'project_id', 'service_id', 'region_id', 'resource_name', 'resource_limit', 'description', 'registered_limit_id' ] # TODO(wxy): Drop "service_id", "region_id" and "resource_name" columns # in T release. internal_id = sql.Column(sql.Integer, primary_key=True, nullable=False) id = sql.Column(sql.String(length=64), nullable=False, unique=True) project_id = sql.Column(sql.String(64)) _service_id = sql.Column('service_id', sql.String(255)) _region_id = sql.Column('region_id', sql.String(64), nullable=True) _resource_name = sql.Column('resource_name', sql.String(255)) resource_limit = sql.Column(sql.Integer, nullable=False) description = sql.Column(sql.Text()) registered_limit_id = sql.Column(sql.String(64), sql.ForeignKey('registered_limit.id')) registered_limit = sqlalchemy.orm.relationship('RegisteredLimitModel') @hybrid_property def service_id(self): if self.registered_limit: return self.registered_limit.service_id return self._service_id @service_id.setter def service_id(self, value): self._service_id = value @service_id.expression def service_id(self): return LimitModel._service_id @hybrid_property def region_id(self): if self.registered_limit: return self.registered_limit.region_id return self._region_id @region_id.setter def region_id(self, value): self._region_id = value @region_id.expression def region_id(self): return LimitModel._region_id @hybrid_property def resource_name(self): if self.registered_limit: return self.registered_limit.resource_name return self._resource_name @resource_name.setter def resource_name(self, value): self._resource_name = value @resource_name.expression def resource_name(self): return LimitModel._resource_name @classmethod def from_dict(cls, limit): obj = super(LimitModel, cls).from_dict(limit) with sql.session_for_read() as session: query = session.query(RegisteredLimitModel).filter_by( id=obj.registered_limit_id) obj.registered_limit = query.first() return obj def to_dict(self): ref = super(LimitModel, self).to_dict() ref.pop('internal_id') ref.pop('registered_limit_id') return ref
class Project(sql.ModelBase, sql.ModelDictMixinWithExtras): # NOTE(henry-nash): From the manager and above perspective, the domain_id # is nullable. However, to ensure uniqueness in multi-process # configurations, it is better to still use the sql uniqueness constraint. # Since the support for a nullable component of a uniqueness constraint # across different sql databases is mixed, we instead store a special value # to represent null, as defined in NULL_DOMAIN_ID above. def to_dict(self, include_extra_dict=False): d = super(Project, self).to_dict( include_extra_dict=include_extra_dict) if d['domain_id'] == base.NULL_DOMAIN_ID: d['domain_id'] = None # NOTE(notmorgan): Eventually it may make sense to drop the empty # option dict creation to the superclass (if enough models use it) d['options'] = resource_options.ref_mapper_to_dict_options(self) return d @classmethod def from_dict(cls, project_dict): new_dict = project_dict.copy() # TODO(morgan): move this functionality to a common location resource_options = {} options = new_dict.pop('options', {}) for opt in cls.resource_options_registry.options: if opt.option_name in options: opt_value = options[opt.option_name] # NOTE(notmorgan): None is always a valid type if opt_value is not None: opt.validator(opt_value) resource_options[opt.option_id] = opt_value project_obj = super(Project, cls).from_dict(new_dict) setattr(project_obj, '_resource_options', resource_options) return project_obj __tablename__ = 'project' attributes = ['id', 'name', 'domain_id', 'description', 'enabled', 'parent_id', 'is_domain', 'tags'] resource_options_registry = ro.PROJECT_OPTIONS_REGISTRY id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), sql.ForeignKey('project.id'), nullable=False) description = sql.Column(sql.Text()) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id')) is_domain = sql.Column(sql.Boolean, default=False, nullable=False, server_default='0') _tags = orm.relationship( 'ProjectTag', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='project', primaryjoin='and_(ProjectTag.project_id==Project.id)' ) _resource_option_mapper = orm.relationship( 'ProjectOption', single_parent=True, cascade='all,delete,delete-orphan', lazy='subquery', backref='project', collection_class=collections.attribute_mapped_collection('option_id') ) # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'),) @property def tags(self): if self._tags: return [tag.name for tag in self._tags] return [] @tags.setter def tags(self, values): new_tags = [] for tag in values: tag_ref = ProjectTag() tag_ref.project_id = self.id tag_ref.name = text_type(tag) new_tags.append(tag_ref) self._tags = new_tags