keystone/keystone/resource/backends/sql_model.py

137 lines
5.3 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from six import text_type
from sqlalchemy import orm
from sqlalchemy.orm import collections
from keystone.common import resource_options
from keystone.common import sql
from keystone.resource.backends import base
from keystone.resource.backends import resource_options as ro
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
class ProjectTag(sql.ModelBase, sql.ModelDictMixin):
def to_dict(self):
d = super(ProjectTag, self).to_dict()
return d
__tablename__ = 'project_tag'
attributes = ['project_id', 'name']
project_id = sql.Column(
sql.String(64), sql.ForeignKey('project.id', ondelete='CASCADE'),
nullable=False, primary_key=True)
name = sql.Column(sql.Unicode(255), nullable=False, primary_key=True)
__table_args__ = (sql.UniqueConstraint('project_id', 'name'),)
class ProjectOption(sql.ModelBase):
__tablename__ = 'project_option'
project_id = sql.Column(sql.String(64),
sql.ForeignKey('project.id', ondelete='CASCADE'),
nullable=False, primary_key=True)
option_id = sql.Column(sql.String(4), nullable=False,
primary_key=True)
option_value = sql.Column(sql.JsonBlob, nullable=True)
def __init__(self, option_id, option_value):
self.option_id = option_id
self.option_value = option_value