This is an automated email from the ASF dual-hosted git repository.
maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push:
new fd03386 Make owner a m2m relation on datasources (#6544)
fd03386 is described below
commit fd0338614a95b4d13a85b0728c487bea35a295d7
Author: leakingoxide <[email protected]>
AuthorDate: Fri Dec 21 05:35:32 2018 +0100
Make owner a m2m relation on datasources (#6544)
* Make owner a m2m relation on datasources
* Fix pylint
* Make migration work in mysql & sqlite
---
.../assets/src/datasource/DatasourceEditor.jsx | 8 +-
superset/connectors/base/models.py | 7 +-
superset/connectors/druid/models.py | 21 +++--
superset/connectors/druid/views.py | 6 +-
superset/connectors/sqla/models.py | 18 ++--
superset/connectors/sqla/views.py | 6 +-
...e1b21cd94a4_change_owner_to_m2m_relation_on_.py | 105 +++++++++++++++++++++
superset/views/datasource.py | 4 +
8 files changed, 149 insertions(+), 26 deletions(-)
diff --git a/superset/assets/src/datasource/DatasourceEditor.jsx
b/superset/assets/src/datasource/DatasourceEditor.jsx
index f16c1e5..8df3fd9 100644
--- a/superset/assets/src/datasource/DatasourceEditor.jsx
+++ b/superset/assets/src/datasource/DatasourceEditor.jsx
@@ -349,13 +349,13 @@ export class DatasourceEditor extends React.PureComponent
{
control={<TextControl />}
/>}
<Field
- fieldKey="owner"
- label={t('Owner')}
- descr={t('Owner of the datasource')}
+ fieldKey="owners"
+ label={t('Owners')}
+ descr={t('Owners of the datasource')}
control={
<SelectAsyncControl
dataEndpoint="/users/api/read"
- multi={false}
+ multi
mutator={data => data.pks.map((pk, i) => ({
value: pk,
label: `${data.result[i].first_name}
${data.result[i].last_name}`,
diff --git a/superset/connectors/base/models.py
b/superset/connectors/base/models.py
index 216ed9e..6876fa0 100644
--- a/superset/connectors/base/models.py
+++ b/superset/connectors/base/models.py
@@ -25,6 +25,7 @@ class BaseDatasource(AuditMixinNullable, ImportMixin):
baselink = None # url portion pointing to ModelView endpoint
column_class = None # link to derivative of BaseColumn
metric_class = None # link to derivative of BaseMetric
+ owner_class = None
# Used to do code highlighting when displaying the query in the UI
query_language = None
@@ -45,7 +46,7 @@ class BaseDatasource(AuditMixinNullable, ImportMixin):
perm = Column(String(1000))
sql = None
- owner = None
+ owners = None
update_from_object_fields = None
@declared_attr
@@ -205,7 +206,7 @@ class BaseDatasource(AuditMixinNullable, ImportMixin):
'metrics': [o.data for o in self.metrics],
'metrics_combo': self.metrics_combo,
'order_by_choices': order_by_choices,
- 'owner': self.owner.id if self.owner else None,
+ 'owners': [owner.id for owner in self.owners],
'verbose_map': verbose_map,
'select_star': self.select_star,
}
@@ -325,7 +326,7 @@ class BaseDatasource(AuditMixinNullable, ImportMixin):
for attr in self.update_from_object_fields:
setattr(self, attr, obj.get(attr))
- self.user_id = obj.get('owner')
+ self.owners = obj.get('owners', [])
# Syncing metrics
metrics = self.get_fk_many_from_list(
diff --git a/superset/connectors/druid/models.py
b/superset/connectors/druid/models.py
index 937c8d8..ae53246 100644
--- a/superset/connectors/druid/models.py
+++ b/superset/connectors/druid/models.py
@@ -26,7 +26,7 @@ from pydruid.utils.postaggregator import (
import requests
import sqlalchemy as sa
from sqlalchemy import (
- Boolean, Column, DateTime, ForeignKey, Integer, String, Text,
UniqueConstraint,
+ Boolean, Column, DateTime, ForeignKey, Integer, String, Table, Text,
UniqueConstraint,
)
from sqlalchemy.orm import backref, relationship
@@ -43,6 +43,7 @@ from superset.utils.core import (
DRUID_TZ = conf.get('DRUID_TZ')
POST_AGG_TYPE = 'postagg'
+metadata = Model.metadata # pylint: disable=no-member
# Function wrapper because bound methods cannot
@@ -446,6 +447,14 @@ class DruidMetric(Model, BaseMetric):
return import_datasource.import_simple_obj(db.session, i_metric,
lookup_obj)
+druiddatasource_user = Table(
+ 'druiddatasource_user', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('user_id', Integer, ForeignKey('ab_user.id')),
+ Column('datasource_id', Integer, ForeignKey('datasources.id')),
+)
+
+
class DruidDatasource(Model, BaseDatasource):
"""ORM object referencing Druid datasources (tables)"""
@@ -458,6 +467,7 @@ class DruidDatasource(Model, BaseDatasource):
cluster_class = DruidCluster
metric_class = DruidMetric
column_class = DruidColumn
+ owner_class = security_manager.user_model
baselink = 'druiddatasourcemodelview'
@@ -470,11 +480,8 @@ class DruidDatasource(Model, BaseDatasource):
String(250), ForeignKey('clusters.cluster_name'))
cluster = relationship(
'DruidCluster', backref='datasources', foreign_keys=[cluster_name])
- user_id = Column(Integer, ForeignKey('ab_user.id'))
- owner = relationship(
- security_manager.user_model,
- backref=backref('datasources', cascade='all, delete-orphan'),
- foreign_keys=[user_id])
+ owners = relationship(owner_class, secondary=druiddatasource_user,
+ backref='druiddatasources')
UniqueConstraint('cluster_name', 'datasource_name')
export_fields = (
@@ -657,7 +664,7 @@ class DruidDatasource(Model, BaseDatasource):
datasource = cls(
datasource_name=druid_config['name'],
cluster=cluster,
- owner=user,
+ owners=[user],
changed_by_fk=user.id,
created_by_fk=user.id,
)
diff --git a/superset/connectors/druid/views.py
b/superset/connectors/druid/views.py
index 18c1aef..eda7ce4 100644
--- a/superset/connectors/druid/views.py
+++ b/superset/connectors/druid/views.py
@@ -214,12 +214,12 @@ class DruidDatasourceModelView(DatasourceModelView,
DeleteMixin, YamlExportMixin
order_columns = ['datasource_link', 'modified']
related_views = [DruidColumnInlineView, DruidMetricInlineView]
edit_columns = [
- 'datasource_name', 'cluster', 'description', 'owner',
+ 'datasource_name', 'cluster', 'description', 'owners',
'is_hidden',
'filter_select_enabled', 'fetch_values_from',
'default_endpoint', 'offset', 'cache_timeout']
search_columns = (
- 'datasource_name', 'cluster', 'description', 'owner',
+ 'datasource_name', 'cluster', 'description', 'owners',
)
add_columns = edit_columns
show_columns = add_columns + ['perm', 'slices']
@@ -263,7 +263,7 @@ class DruidDatasourceModelView(DatasourceModelView,
DeleteMixin, YamlExportMixin
'datasource_link': _('Data Source'),
'cluster': _('Cluster'),
'description': _('Description'),
- 'owner': _('Owner'),
+ 'owners': _('Owners'),
'is_hidden': _('Is Hidden'),
'filter_select_enabled': _('Enable Filter Select'),
'default_endpoint': _('Default Endpoint'),
diff --git a/superset/connectors/sqla/models.py
b/superset/connectors/sqla/models.py
index cf22add..779e4a3 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -9,7 +9,7 @@ import pandas as pd
import sqlalchemy as sa
from sqlalchemy import (
and_, asc, Boolean, Column, DateTime, desc, ForeignKey, Integer, or_,
- select, String, Text,
+ select, String, Table, Text,
)
from sqlalchemy.exc import CompileError
from sqlalchemy.orm import backref, relationship
@@ -27,6 +27,7 @@ from superset.models.helpers import QueryResult
from superset.utils import core as utils, import_datasource
config = app.config
+metadata = Model.metadata # pylint: disable=no-member
class AnnotationDatasource(BaseDatasource):
@@ -250,6 +251,14 @@ class SqlMetric(Model, BaseMetric):
return import_datasource.import_simple_obj(db.session, i_metric,
lookup_obj)
+sqlatable_user = Table(
+ 'sqlatable_user', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('user_id', Integer, ForeignKey('ab_user.id')),
+ Column('table_id', Integer, ForeignKey('tables.id')),
+)
+
+
class SqlaTable(Model, BaseDatasource):
"""An ORM object for SqlAlchemy table references"""
@@ -258,6 +267,7 @@ class SqlaTable(Model, BaseDatasource):
query_language = 'sql'
metric_class = SqlMetric
column_class = TableColumn
+ owner_class = security_manager.user_model
__tablename__ = 'tables'
__table_args__ = (UniqueConstraint('database_id', 'table_name'),)
@@ -266,11 +276,7 @@ class SqlaTable(Model, BaseDatasource):
main_dttm_col = Column(String(250))
database_id = Column(Integer, ForeignKey('dbs.id'), nullable=False)
fetch_values_predicate = Column(String(1000))
- user_id = Column(Integer, ForeignKey('ab_user.id'))
- owner = relationship(
- security_manager.user_model,
- backref='tables',
- foreign_keys=[user_id])
+ owners = relationship(owner_class, secondary=sqlatable_user,
backref='tables')
database = relationship(
'Database',
backref=backref('tables', cascade='all, delete-orphan'),
diff --git a/superset/connectors/sqla/views.py
b/superset/connectors/sqla/views.py
index c085958..734a2c0 100644
--- a/superset/connectors/sqla/views.py
+++ b/superset/connectors/sqla/views.py
@@ -162,7 +162,7 @@ class TableModelView(DatasourceModelView, DeleteMixin,
YamlExportMixin): # noqa
edit_columns = [
'table_name', 'sql', 'filter_select_enabled',
'fetch_values_predicate', 'database', 'schema',
- 'description', 'owner',
+ 'description', 'owners',
'main_dttm_col', 'default_endpoint', 'offset', 'cache_timeout',
'is_sqllab_view', 'template_params',
]
@@ -171,7 +171,7 @@ class TableModelView(DatasourceModelView, DeleteMixin,
YamlExportMixin): # noqa
related_views = [TableColumnInlineView, SqlMetricInlineView]
base_order = ('changed_on', 'desc')
search_columns = (
- 'database', 'schema', 'table_name', 'owner', 'is_sqllab_view',
+ 'database', 'schema', 'table_name', 'owners', 'is_sqllab_view',
)
description_columns = {
'slices': _(
@@ -233,7 +233,7 @@ class TableModelView(DatasourceModelView, DeleteMixin,
YamlExportMixin): # noqa
'cache_timeout': _('Cache Timeout'),
'table_name': _('Table Name'),
'fetch_values_predicate': _('Fetch Values Predicate'),
- 'owner': _('Owner'),
+ 'owners': _('Owners'),
'main_dttm_col': _('Main Datetime Column'),
'description': _('Description'),
'is_sqllab_view': _('SQL Lab View'),
diff --git
a/superset/migrations/versions/3e1b21cd94a4_change_owner_to_m2m_relation_on_.py
b/superset/migrations/versions/3e1b21cd94a4_change_owner_to_m2m_relation_on_.py
new file mode 100644
index 0000000..e087b35
--- /dev/null
+++
b/superset/migrations/versions/3e1b21cd94a4_change_owner_to_m2m_relation_on_.py
@@ -0,0 +1,105 @@
+"""change_owner_to_m2m_relation_on_datasources.py
+
+Revision ID: 3e1b21cd94a4
+Revises: 4ce8df208545
+Create Date: 2018-12-15 12:34:47.228756
+
+"""
+
+# revision identifiers, used by Alembic.
+from superset import db
+from superset.utils.core import generic_find_fk_constraint_name
+
+revision = '3e1b21cd94a4'
+down_revision = '6c7537a6004a'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+sqlatable_user = sa.Table(
+ 'sqlatable_user', sa.MetaData(),
+ sa.Column('id', sa.Integer, primary_key=True),
+ sa.Column('user_id', sa.Integer, sa.ForeignKey('ab_user.id')),
+ sa.Column('table_id', sa.Integer, sa.ForeignKey('tables.id')),
+)
+
+SqlaTable = sa.Table(
+ 'tables', sa.MetaData(),
+ sa.Column('id', sa.Integer, primary_key=True),
+ sa.Column('user_id', sa.Integer, sa.ForeignKey('ab_user.id')),
+)
+
+druiddatasource_user = sa.Table(
+ 'druiddatasource_user', sa.MetaData(),
+ sa.Column('id', sa.Integer, primary_key=True),
+ sa.Column('user_id', sa.Integer, sa.ForeignKey('ab_user.id')),
+ sa.Column('datasource_id', sa.Integer, sa.ForeignKey('datasources.id')),
+)
+
+DruidDatasource = sa.Table(
+ 'datasources', sa.MetaData(),
+ sa.Column('id', sa.Integer, primary_key=True),
+ sa.Column('user_id', sa.Integer, sa.ForeignKey('ab_user.id')),
+)
+
+
+def upgrade():
+ op.create_table('sqlatable_user',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('user_id', sa.Integer(), nullable=True),
+ sa.Column('table_id', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['table_id'], ['tables.id'], ),
+ sa.ForeignKeyConstraint(['user_id'], ['ab_user.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('druiddatasource_user',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('user_id', sa.Integer(), nullable=True),
+ sa.Column('datasource_id', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['datasource_id'],
['datasources.id'], ),
+ sa.ForeignKeyConstraint(['user_id'], ['ab_user.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+
+ bind = op.get_bind()
+ insp = sa.engine.reflection.Inspector.from_engine(bind)
+ session = db.Session(bind=bind)
+
+ tables = session.query(SqlaTable).all()
+ for table in tables:
+ if table.user_id is not None:
+ session.execute(
+ sqlatable_user.insert().values(user_id=table.user_id,
table_id=table.id)
+ )
+
+ druiddatasources = session.query(DruidDatasource).all()
+ for druiddatasource in druiddatasources:
+ if druiddatasource.user_id is not None:
+ session.execute(
+
druiddatasource_user.insert().values(user_id=druiddatasource.user_id,
datasource_id=druiddatasource.id)
+ )
+
+ session.close()
+ with op.batch_alter_table('tables') as batch_op:
+ batch_op.drop_constraint('user_id', type_='foreignkey')
+ batch_op.drop_column('user_id')
+ with op.batch_alter_table('datasources') as batch_op:
+ batch_op.drop_constraint(generic_find_fk_constraint_name(
+ 'datasources',
+ {'id'},
+ 'ab_user',
+ insp,
+ ), type_='foreignkey')
+ batch_op.drop_column('user_id')
+
+
+def downgrade():
+ op.drop_table('sqlatable_user')
+ op.drop_table('druiddatasource_user')
+ with op.batch_alter_table('tables') as batch_op:
+ batch_op.add_column(sa.Column('user_id', sa.INTEGER(), nullable=True))
+ batch_op.create_foreign_key('user_id', 'ab_user', ['user_id'], ['id'])
+ with op.batch_alter_table('datasources') as batch_op:
+ batch_op.add_column(sa.Column('user_id', sa.INTEGER(), nullable=True))
+ batch_op.create_foreign_key('fk_datasources_user_id_ab_user',
'ab_user', ['user_id'], ['id'])
diff --git a/superset/views/datasource.py b/superset/views/datasource.py
index 5df20a2..9d3d341 100644
--- a/superset/views/datasource.py
+++ b/superset/views/datasource.py
@@ -29,6 +29,10 @@ class Datasource(BaseSupersetView):
'this data source configuration'),
status='401',
)
+
+ if 'owners' in datasource:
+ datasource['owners'] =
db.session.query(orm_datasource.owner_class).filter(
+ orm_datasource.owner_class.id.in_(datasource['owners'])).all()
orm_datasource.update_from_object(datasource)
data = orm_datasource.data
db.session.commit()