Files
superset2/superset/views/base.py
Grace Guo 9af34ba51c js translation -- performance improvment (#3390)
* Chinese page

* Using react-intl-universal to improve multi language in react page

* Using react-intl-universal to improve multi language in react page

* react_intl_universal

* change

* change

* change

* change

* change

* change

* change

* merge

* multiple page in js

* merge

* merge

* merge

* merge

* Js Translations

* JS Translation

* JS Translations

* Js translation

* JS translations

* JS translations

* Js translaion

* JS en Translation

* JS Translation

* upgrade document

Fixing the damn build (#3179)

* Fixing the build

* Going deeper

[bugfix] only filterable columns should show up in FilterBox list (#3105)

* [bugfix] only filterable columns should show up in FilterBox list

* Touchups

Datasource cannot be empty (#3035)

add title description to model view (#3045)

* add title description to model view

* add missing import

Add 'show/hide totals' option to pivot table vis (#3101)

[bugfix] numeric value for date fields in table viz (#3036)

Bug was present only when using the NOT GROUPED BY option

fixes https://github.com/ApacheInfra/superset/issues/3027

fix hive.fetch_logs (#2968)

add Zalando to the list of organizations (#3171)

docs: fixup installation examples code indentation (#3169)

[bugfix] fix bar order (#3180)

[bugfix] visualize flow error: 'Metric x is not valid' (#3181)

The metric name in the frontend doesn't match the one generated on the
backend. It turns out the explore view will default to the first
metric so specifying one isn't needed.

Fix the segment interval for pulling metadata (#3174)

The end of the interval would be on the truncated today date, which
means that you will exclude today. If your realtime ingestion job
runs shorter than a day, the metadata cannot be pulled from the
druid cluster.

Bump cryptography to 1.9 (#3065)

As 1.7.2 doesn't compile here with openssl 1.1.0f

Escaping the user's SQL in the explore view (#3186)

* Escaping the user's SQL in the explore view

When executing SQL from SQL Lab, we use a lower level API to the
database which doesn't require escaping the SQL. When going through
the explore view, the stack chain leading to the same method may need
escaping depending on how the DBAPI driver is written, and that is the
case for Presto (and perhaps other drivers).

* Using regex to avoid doubling doubles

[sqllab] improve Hive support (#3187)

* [sqllab] improve Hive support

* Fix "Transport not open" bug
* Getting progress bar to show
* Bump pyhive to 0.4.0
* Getting [Track Job] button to show

* Fix testzz

Add BigQuery engine specifications (#3193)

As contributed by @mxmzdlv on issue #945

[bugfix] fix merge conflict that broke Hive support (#3196)

Adding 'apache' to docs (#3194)

[druid] Allow custom druid postaggregators (#3146)

* [druid] Allow custom druid postaggregators

Also, fix the postaggregation for approxHistogram quantiles so it adds
the dependent field and that can show up in the graphs/tables.

In general, postAggregators add significant power, we should probably
support including custom postAggregators. Plywood has standard
postAggregators here, and a customAggregator escape hatch that allows
you to define custom postAggregators.

This commit adds a similar capability for Superset and a additional
field/fields/fieldName breakdown of the typical naming for dependent
aggregations, which should make it significantly easier to develop
approxHistogram and custom postAggregation-required dashboards.

* [druid] Minor style cleanup in tests file.

* [druid] Apply code review suggestions

* break out CustomPostAggregator into separate class. This just cleans
  up the creation of the postaggregator a little bit.
* minor style issues.
* move the function around so the git diff is more readable

add combine config for metrics in pivot table (#3086)

* add combine config for metrics in pivot table

* change method to stack/unstack

* update backendSync

Autofocus search input in VizTypeControl modal onEnter (#2929)

Speed up JS build time (#3203)

Also bumping a few related libs

JS Translation

JS translations

js translation

fix issue 3204 (#3205)

[bugfix] capture Hive job_id pre-url transformation (#3213)

js translation

fix issue 3204 (#3205)

[bugfix] capture Hive job_id pre-url transformation (#3213)

[docs] update url in CONTRIBUTING.md (#3212)

[sqllab/cosmetics] add margin-top for labels in query history (#3222)

[explore] nvd3 sort values in rich tooltip (#3197)

[sqllab] fix UI shows 'The query returned no results' momentarily (#3214)

this is visible when running async queries between the fetching and
success state as the rows are getting cached in the component

[explore] DatasourceControl to pick datasource in modal (#3210)

* [explore] DatasourceControl to pick datasource in modal

Makes it easier to change datasource, also makes it such that the list
of all datasources doesn't need to be loaded upfront.

* Adding more metadata

* Js translation

* js tran

* js trans

* js trans

* js tran

* js trans

* js trans

* js tran

* js translation

* js trans

* js translation

* try load language pack async

* Backend translations things

* create language pack inside common data

* performance improvement for js i18n.

- js bundle should not contain localized content
- we populate translation content from server-side, in boostrap.common.language_pack
- in client-side, use promise to wrap around translation content. text will be translated after translation content arrived/parsed.
- fix linting

* fix Timer unit test

* 1. add global hook for all tests, to make translation pack avaialble before each test starts.
2. fix unit test for Timer component
3. remove noused method get_locale, and modules
4. fix page reload after user change page language

* parse and build i18n dictionary as a module

* fix sync-backend task, which should run without DOM
2017-09-20 12:37:33 -07:00

351 lines
12 KiB
Python

import functools
import json
import logging
import traceback
from flask import g, redirect, Response, flash, abort, get_flashed_messages
from flask_babel import gettext as __
from flask_babel import lazy_gettext as _
from flask_babel import get_locale
from flask_appbuilder import BaseView
from flask_appbuilder import ModelView
from flask_appbuilder.widgets import ListWidget
from flask_appbuilder.actions import action
from flask_appbuilder.models.sqla.filters import BaseFilter
from superset import appbuilder, conf, db, utils, sm, sql_parse
from superset.connectors.connector_registry import ConnectorRegistry
from superset.connectors.sqla.models import SqlaTable
from superset.translations.utils import get_language_pack
FRONTEND_CONF_KEYS = ('SUPERSET_WEBSERVER_TIMEOUT',)
def get_error_msg():
if conf.get("SHOW_STACKTRACE"):
error_msg = traceback.format_exc()
else:
error_msg = "FATAL ERROR \n"
error_msg += (
"Stacktrace is hidden. Change the SHOW_STACKTRACE "
"configuration setting to enable it")
return error_msg
def json_error_response(msg=None, status=500, stacktrace=None, payload=None):
if not payload:
payload = {'error': str(msg)}
if stacktrace:
payload['stacktrace'] = stacktrace
return Response(
json.dumps(payload, default=utils.json_iso_dttm_ser),
status=status, mimetype="application/json")
def api(f):
"""
A decorator to label an endpoint as an API. Catches uncaught exceptions and
return the response in the JSON format
"""
def wraps(self, *args, **kwargs):
try:
return f(self, *args, **kwargs)
except Exception as e:
logging.exception(e)
return json_error_response(get_error_msg())
return functools.update_wrapper(wraps, f)
def get_datasource_exist_error_mgs(full_name):
return __("Datasource %(name)s already exists", name=full_name)
def get_user_roles():
if g.user.is_anonymous():
public_role = conf.get('AUTH_ROLE_PUBLIC')
return [appbuilder.sm.find_role(public_role)] if public_role else []
return g.user.roles
class BaseSupersetView(BaseView):
def can_access(self, permission_name, view_name, user=None):
if not user:
user = g.user
return utils.can_access(
appbuilder.sm, permission_name, view_name, user)
def all_datasource_access(self, user=None):
return self.can_access(
"all_datasource_access", "all_datasource_access", user=user)
def database_access(self, database, user=None):
return (
self.can_access(
"all_database_access", "all_database_access", user=user) or
self.can_access("database_access", database.perm, user=user)
)
def schema_access(self, datasource, user=None):
return (
self.database_access(datasource.database, user=user) or
self.all_datasource_access(user=user) or
self.can_access("schema_access", datasource.schema_perm, user=user)
)
def datasource_access(self, datasource, user=None):
return (
self.schema_access(datasource, user=user) or
self.can_access("datasource_access", datasource.perm, user=user)
)
def datasource_access_by_name(
self, database, datasource_name, schema=None):
if self.database_access(database) or self.all_datasource_access():
return True
schema_perm = utils.get_schema_perm(database, schema)
if schema and self.can_access('schema_access', schema_perm):
return True
datasources = ConnectorRegistry.query_datasources_by_name(
db.session, database, datasource_name, schema=schema)
for datasource in datasources:
if self.can_access("datasource_access", datasource.perm):
return True
return False
def datasource_access_by_fullname(
self, database, full_table_name, schema):
table_name_pieces = full_table_name.split(".")
if len(table_name_pieces) == 2:
table_schema = table_name_pieces[0]
table_name = table_name_pieces[1]
else:
table_schema = schema
table_name = table_name_pieces[0]
return self.datasource_access_by_name(
database, table_name, schema=table_schema)
def rejected_datasources(self, sql, database, schema):
superset_query = sql_parse.SupersetQuery(sql)
return [
t for t in superset_query.tables if not
self.datasource_access_by_fullname(database, t, schema)]
def user_datasource_perms(self):
datasource_perms = set()
for r in g.user.roles:
for perm in r.permissions:
if (
perm.permission and
'datasource_access' == perm.permission.name):
datasource_perms.add(perm.view_menu.name)
return datasource_perms
def schemas_accessible_by_user(self, database, schemas):
if self.database_access(database) or self.all_datasource_access():
return schemas
subset = set()
for schema in schemas:
schema_perm = utils.get_schema_perm(database, schema)
if self.can_access('schema_access', schema_perm):
subset.add(schema)
perms = self.user_datasource_perms()
if perms:
tables = (
db.session.query(SqlaTable)
.filter(
SqlaTable.perm.in_(perms),
SqlaTable.database_id == database.id,
)
.all()
)
for t in tables:
if t.schema:
subset.add(t.schema)
return sorted(list(subset))
def accessible_by_user(self, database, datasource_names, schema=None):
if self.database_access(database) or self.all_datasource_access():
return datasource_names
if schema:
schema_perm = utils.get_schema_perm(database, schema)
if self.can_access('schema_access', schema_perm):
return datasource_names
user_perms = self.user_datasource_perms()
user_datasources = ConnectorRegistry.query_datasources_by_permissions(
db.session, database, user_perms)
if schema:
names = {
d.table_name
for d in user_datasources if d.schema == schema}
return [d for d in datasource_names if d in names]
else:
full_names = {d.full_name for d in user_datasources}
return [d for d in datasource_names if d in full_names]
def common_bootsrap_payload(self):
"""Common data always sent to the client"""
messages = get_flashed_messages(with_categories=True)
locale = str(get_locale())
return {
'flash_messages': messages,
'conf': {k: conf.get(k) for k in FRONTEND_CONF_KEYS},
'locale': locale,
'language_pack': get_language_pack(locale),
}
class SupersetModelView(ModelView):
page_size = 100
class ListWidgetWithCheckboxes(ListWidget):
"""An alternative to list view that renders Boolean fields as checkboxes
Works in conjunction with the `checkbox` view."""
template = 'superset/fab_overrides/list_with_checkboxes.html'
def validate_json(form, field): # noqa
try:
json.loads(field.data)
except Exception as e:
logging.exception(e)
raise Exception(_("json isn't valid"))
class DeleteMixin(object):
def _delete(self, pk):
"""
Delete function logic, override to implement diferent logic
deletes the record with primary_key = pk
:param pk:
record primary key to delete
"""
item = self.datamodel.get(pk, self._base_filters)
if not item:
abort(404)
try:
self.pre_delete(item)
except Exception as e:
flash(str(e), "danger")
else:
view_menu = sm.find_view_menu(item.get_perm())
pvs = sm.get_session.query(sm.permissionview_model).filter_by(
view_menu=view_menu).all()
schema_view_menu = None
if hasattr(item, 'schema_perm'):
schema_view_menu = sm.find_view_menu(item.schema_perm)
pvs.extend(sm.get_session.query(
sm.permissionview_model).filter_by(
view_menu=schema_view_menu).all())
if self.datamodel.delete(item):
self.post_delete(item)
for pv in pvs:
sm.get_session.delete(pv)
if view_menu:
sm.get_session.delete(view_menu)
if schema_view_menu:
sm.get_session.delete(schema_view_menu)
sm.get_session.commit()
flash(*self.datamodel.message)
self.update_redirect()
@action(
"muldelete",
__("Delete"),
__("Delete all Really?"),
"fa-trash",
single=False
)
def muldelete(self, items):
if not items:
abort(404)
for item in items:
try:
self.pre_delete(item)
except Exception as e:
flash(str(e), "danger")
else:
self._delete(item.id)
self.update_redirect()
return redirect(self.get_redirect())
class SupersetFilter(BaseFilter):
"""Add utility function to make BaseFilter easy and fast
These utility function exist in the SecurityManager, but would do
a database round trip at every check. Here we cache the role objects
to be able to make multiple checks but query the db only once
"""
def get_user_roles(self):
return get_user_roles()
def get_all_permissions(self):
"""Returns a set of tuples with the perm name and view menu name"""
perms = set()
for role in self.get_user_roles():
for perm_view in role.permissions:
t = (perm_view.permission.name, perm_view.view_menu.name)
perms.add(t)
return perms
def has_role(self, role_name_or_list):
"""Whether the user has this role name"""
if not isinstance(role_name_or_list, list):
role_name_or_list = [role_name_or_list]
return any(
[r.name in role_name_or_list for r in self.get_user_roles()])
def has_perm(self, permission_name, view_menu_name):
"""Whether the user has this perm"""
return (permission_name, view_menu_name) in self.get_all_permissions()
def get_view_menus(self, permission_name):
"""Returns the details of view_menus for a perm name"""
vm = set()
for perm_name, vm_name in self.get_all_permissions():
if perm_name == permission_name:
vm.add(vm_name)
return vm
def has_all_datasource_access(self):
return (
self.has_role(['Admin', 'Alpha']) or
self.has_perm('all_datasource_access', 'all_datasource_access'))
class DatasourceFilter(SupersetFilter):
def apply(self, query, func): # noqa
if self.has_all_datasource_access():
return query
perms = self.get_view_menus('datasource_access')
# TODO(bogdan): add `schema_access` support here
return query.filter(self.model.perm.in_(perms))
class CsvResponse(Response):
"""
Override Response to take into account csv encoding from config.py
"""
charset = conf.get('CSV_EXPORT').get('encoding', 'utf-8')