mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
Template dashboard (#5550)
* Template dashboard * Fix MySQL test * Model for user attributes * Redirect to welcome dash * Fix lint * Add missing file * Add migration script * Fix lint * Fix more lint
This commit is contained in:
parent
39ff9deb57
commit
222b79df7e
@ -412,6 +412,9 @@ HIVE_POLL_INTERVAL = 5
|
|||||||
# an XSS security vulnerability
|
# an XSS security vulnerability
|
||||||
ENABLE_JAVASCRIPT_CONTROLS = False
|
ENABLE_JAVASCRIPT_CONTROLS = False
|
||||||
|
|
||||||
|
# The id of a template dashboard that should be copied to every new user
|
||||||
|
DASHBOARD_TEMPLATE_ID = None
|
||||||
|
|
||||||
# A callable that allows altering the database conneciton URL and params
|
# A callable that allows altering the database conneciton URL and params
|
||||||
# on the fly, at runtime. This allows for things like impersonation or
|
# on the fly, at runtime. This allows for things like impersonation or
|
||||||
# arbitrary logic. For instance you can wire different users to
|
# arbitrary logic. For instance you can wire different users to
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
"""add user attributes table
|
||||||
|
|
||||||
|
Revision ID: 0c5070e96b57
|
||||||
|
Revises: 7fcdcde0761c
|
||||||
|
Create Date: 2018-08-06 14:38:18.965248
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0c5070e96b57'
|
||||||
|
down_revision = '7fcdcde0761c'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('user_attribute',
|
||||||
|
sa.Column('created_on', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('changed_on', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('welcome_dashboard_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('created_by_fk', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('changed_by_fk', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['ab_user.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['welcome_dashboard_id'], ['dashboards.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('user_attribute')
|
@ -1,3 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from . import core # noqa
|
from . import core # noqa
|
||||||
from . import sql_lab # noqa
|
from . import sql_lab # noqa
|
||||||
|
from . import user_attributes # noqa
|
||||||
|
@ -17,6 +17,7 @@ import textwrap
|
|||||||
from flask import escape, g, Markup, request
|
from flask import escape, g, Markup, request
|
||||||
from flask_appbuilder import Model
|
from flask_appbuilder import Model
|
||||||
from flask_appbuilder.models.decorators import renders
|
from flask_appbuilder.models.decorators import renders
|
||||||
|
from flask_appbuilder.security.sqla.models import User
|
||||||
from future.standard_library import install_aliases
|
from future.standard_library import install_aliases
|
||||||
import numpy
|
import numpy
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -28,7 +29,7 @@ from sqlalchemy import (
|
|||||||
)
|
)
|
||||||
from sqlalchemy.engine import url
|
from sqlalchemy.engine import url
|
||||||
from sqlalchemy.engine.url import make_url
|
from sqlalchemy.engine.url import make_url
|
||||||
from sqlalchemy.orm import relationship, subqueryload
|
from sqlalchemy.orm import relationship, sessionmaker, subqueryload
|
||||||
from sqlalchemy.orm.session import make_transient
|
from sqlalchemy.orm.session import make_transient
|
||||||
from sqlalchemy.pool import NullPool
|
from sqlalchemy.pool import NullPool
|
||||||
from sqlalchemy.schema import UniqueConstraint
|
from sqlalchemy.schema import UniqueConstraint
|
||||||
@ -39,6 +40,7 @@ from superset import app, db, db_engine_specs, security_manager, utils
|
|||||||
from superset.connectors.connector_registry import ConnectorRegistry
|
from superset.connectors.connector_registry import ConnectorRegistry
|
||||||
from superset.legacy import update_time_range
|
from superset.legacy import update_time_range
|
||||||
from superset.models.helpers import AuditMixinNullable, ImportMixin, set_perm
|
from superset.models.helpers import AuditMixinNullable, ImportMixin, set_perm
|
||||||
|
from superset.models.user_attributes import UserAttribute
|
||||||
from superset.viz import viz_types
|
from superset.viz import viz_types
|
||||||
install_aliases()
|
install_aliases()
|
||||||
from urllib import parse # noqa
|
from urllib import parse # noqa
|
||||||
@ -59,6 +61,41 @@ def set_related_perm(mapper, connection, target): # noqa
|
|||||||
target.perm = ds.perm
|
target.perm = ds.perm
|
||||||
|
|
||||||
|
|
||||||
|
def copy_dashboard(mapper, connection, target):
|
||||||
|
dashboard_id = config.get('DASHBOARD_TEMPLATE_ID')
|
||||||
|
if dashboard_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
Session = sessionmaker(autoflush=False)
|
||||||
|
session = Session(bind=connection)
|
||||||
|
new_user = session.query(User).filter_by(id=target.id).first()
|
||||||
|
|
||||||
|
# copy template dashboard to user
|
||||||
|
template = session.query(Dashboard).filter_by(id=int(dashboard_id)).first()
|
||||||
|
dashboard = Dashboard(
|
||||||
|
dashboard_title=template.dashboard_title,
|
||||||
|
position_json=template.position_json,
|
||||||
|
description=template.description,
|
||||||
|
css=template.css,
|
||||||
|
json_metadata=template.json_metadata,
|
||||||
|
slices=template.slices,
|
||||||
|
owners=[new_user],
|
||||||
|
)
|
||||||
|
session.add(dashboard)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# set dashboard as the welcome dashboard
|
||||||
|
extra_attributes = UserAttribute(
|
||||||
|
user_id=target.id,
|
||||||
|
welcome_dashboard_id=dashboard.id,
|
||||||
|
)
|
||||||
|
session.add(extra_attributes)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
sqla.event.listen(User, 'after_insert', copy_dashboard)
|
||||||
|
|
||||||
|
|
||||||
class Url(Model, AuditMixinNullable):
|
class Url(Model, AuditMixinNullable):
|
||||||
"""Used for the short url feature"""
|
"""Used for the short url feature"""
|
||||||
|
|
||||||
|
36
superset/models/user_attributes.py
Normal file
36
superset/models/user_attributes.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from flask_appbuilder import Model
|
||||||
|
from sqlalchemy import Column, ForeignKey, Integer
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from superset import security_manager
|
||||||
|
from superset.models.helpers import AuditMixinNullable
|
||||||
|
|
||||||
|
|
||||||
|
class UserAttribute(Model, AuditMixinNullable):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Custom attributes attached to the user.
|
||||||
|
|
||||||
|
Extending the user attribute is tricky due to its dependency on the
|
||||||
|
authentication typew an circular dependencies in Superset. Instead, we use
|
||||||
|
a custom model for adding attributes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = 'user_attribute'
|
||||||
|
id = Column(Integer, primary_key=True) # pylint: disable=invalid-name
|
||||||
|
user_id = Column(Integer, ForeignKey('ab_user.id'))
|
||||||
|
user = relationship(
|
||||||
|
security_manager.user_model,
|
||||||
|
backref='extra_attributes',
|
||||||
|
foreign_keys=[user_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
welcome_dashboard_id = Column(Integer, ForeignKey('dashboards.id'))
|
||||||
|
welcome_dashboard = relationship('Dashboard')
|
@ -45,6 +45,7 @@ from superset.jinja_context import get_template_processor
|
|||||||
from superset.legacy import cast_form_data, update_time_range
|
from superset.legacy import cast_form_data, update_time_range
|
||||||
import superset.models.core as models
|
import superset.models.core as models
|
||||||
from superset.models.sql_lab import Query
|
from superset.models.sql_lab import Query
|
||||||
|
from superset.models.user_attributes import UserAttribute
|
||||||
from superset.sql_parse import SupersetQuery
|
from superset.sql_parse import SupersetQuery
|
||||||
from superset.utils import (
|
from superset.utils import (
|
||||||
merge_extra_filters, merge_request_params, QueryStatus,
|
merge_extra_filters, merge_request_params, QueryStatus,
|
||||||
@ -2648,6 +2649,15 @@ class Superset(BaseSupersetView):
|
|||||||
if not g.user or not g.user.get_id():
|
if not g.user or not g.user.get_id():
|
||||||
return redirect(appbuilder.get_url_for_login)
|
return redirect(appbuilder.get_url_for_login)
|
||||||
|
|
||||||
|
welcome_dashboard_id = (
|
||||||
|
db.session
|
||||||
|
.query(UserAttribute.welcome_dashboard_id)
|
||||||
|
.filter_by(user_id=g.user.get_id())
|
||||||
|
.scalar()
|
||||||
|
)
|
||||||
|
if welcome_dashboard_id:
|
||||||
|
return self.dashboard(str(welcome_dashboard_id))
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'user': bootstrap_user_data(),
|
'user': bootstrap_user_data(),
|
||||||
'common': self.common_bootsrap_payload(),
|
'common': self.common_bootsrap_payload(),
|
||||||
|
Loading…
Reference in New Issue
Block a user