mirror of
https://github.com/valitydev/redash.git
synced 2024-11-06 09:05:17 +00:00
Restructure the Flask app:
Tried to untangle the cyclic dependencies, although there are still some of them left. The main reason for this restructuring/refactoring is to make the application a bit more extensible, so it's possible to add more routes to it without touching the core.
This commit is contained in:
parent
f8a8928a90
commit
9ae503289c
@ -1,12 +1,17 @@
|
||||
import logging
|
||||
import urlparse
|
||||
import redis
|
||||
from flask import Flask
|
||||
from flask_sslify import SSLify
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
from werkzeug.routing import BaseConverter, ValidationError
|
||||
from statsd import StatsClient
|
||||
from flask_mail import Mail
|
||||
|
||||
from redash import settings
|
||||
from redash.query_runner import import_query_runners
|
||||
|
||||
|
||||
__version__ = '0.10.0'
|
||||
|
||||
|
||||
@ -49,3 +54,54 @@ import_query_runners(settings.QUERY_RUNNERS)
|
||||
|
||||
from redash.version_check import reset_new_version_status
|
||||
reset_new_version_status()
|
||||
|
||||
|
||||
class SlugConverter(BaseConverter):
|
||||
def to_python(self, value):
|
||||
# This is an ugly workaround for when we enable multi-org and some files are being called by the index rule:
|
||||
if value in ('google_login.png', 'favicon.ico', 'robots.txt', 'views'):
|
||||
raise ValidationError()
|
||||
|
||||
return value
|
||||
|
||||
def to_url(self, value):
|
||||
return value
|
||||
|
||||
|
||||
def create_app():
|
||||
from redash import handlers
|
||||
from redash.admin import init_admin
|
||||
from redash.models import db
|
||||
from redash.authentication import setup_authentication
|
||||
from redash.metrics.request import provision_app
|
||||
|
||||
app = Flask(__name__,
|
||||
template_folder=settings.STATIC_ASSETS_PATH,
|
||||
static_folder=settings.STATIC_ASSETS_PATH,
|
||||
static_path='/static')
|
||||
|
||||
# Make sure we get the right referral address even behind proxies like nginx.
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, settings.PROXIES_COUNT)
|
||||
app.url_map.converters['org_slug'] = SlugConverter
|
||||
|
||||
if settings.ENFORCE_HTTPS:
|
||||
SSLify(app, skips=['ping'])
|
||||
|
||||
if settings.SENTRY_DSN:
|
||||
from raven.contrib.flask import Sentry
|
||||
sentry = Sentry(app, dsn=settings.SENTRY_DSN)
|
||||
sentry.client.release = __version__
|
||||
|
||||
# configure our database
|
||||
settings.DATABASE_CONFIG.update({'threadlocals': True})
|
||||
app.config['DATABASE'] = settings.DATABASE_CONFIG
|
||||
app.config.update(settings.all_settings())
|
||||
|
||||
provision_app(app)
|
||||
init_admin(app)
|
||||
db.init_app(app)
|
||||
mail.init_app(app)
|
||||
setup_authentication(app)
|
||||
handlers.init_app(app)
|
||||
|
||||
return app
|
||||
|
@ -4,18 +4,26 @@ import hmac
|
||||
import time
|
||||
import logging
|
||||
|
||||
from flask import redirect, request, jsonify
|
||||
from flask import redirect, request, jsonify, url_for
|
||||
|
||||
from redash import models, settings
|
||||
from redash.authentication import google_oauth, saml_auth
|
||||
from redash.authentication.org_resolving import current_org
|
||||
from redash.authentication.helper import get_login_url
|
||||
from redash.authentication import google_oauth, saml_auth
|
||||
from redash.tasks import record_event
|
||||
|
||||
login_manager = LoginManager()
|
||||
logger = logging.getLogger('authentication')
|
||||
|
||||
|
||||
def get_login_url(external=False, next="/"):
|
||||
if settings.MULTI_ORG:
|
||||
login_url = url_for('redash.login', org_slug=current_org.slug, next=next, _external=external)
|
||||
else:
|
||||
login_url = url_for('redash.login', next=next, _external=external)
|
||||
|
||||
return login_url
|
||||
|
||||
|
||||
def sign(key, path, expires):
|
||||
if not key:
|
||||
return None
|
||||
@ -145,3 +153,5 @@ def setup_authentication(app):
|
||||
else:
|
||||
logger.warning("Unknown authentication type ({}). Using default (HMAC).".format(settings.AUTH_TYPE))
|
||||
login_manager.request_loader(hmac_load_user_from_request)
|
||||
|
||||
|
||||
|
@ -79,7 +79,7 @@ def org_login(org_slug):
|
||||
@blueprint.route('/oauth/google', endpoint="authorize")
|
||||
def login():
|
||||
callback = url_for('.callback', _external=True)
|
||||
next = request.args.get('next', url_for("index", org_slug=session.get('org_slug')))
|
||||
next = request.args.get('next', url_for("redash.index", org_slug=session.get('org_slug')))
|
||||
logger.debug("Callback url: %s", callback)
|
||||
logger.debug("Next is: %s", next)
|
||||
return google_remote_app().authorize(callback=callback, state=next)
|
||||
@ -93,12 +93,12 @@ def authorized():
|
||||
if access_token is None:
|
||||
logger.warning("Access token missing in call back request.")
|
||||
flash("Validation error. Please retry.")
|
||||
return redirect(url_for('login'))
|
||||
return redirect(url_for('redash.login'))
|
||||
|
||||
profile = get_user_profile(access_token)
|
||||
if profile is None:
|
||||
flash("Validation error. Please retry.")
|
||||
return redirect(url_for('login'))
|
||||
return redirect(url_for('redash.login'))
|
||||
|
||||
if 'org_slug' in session:
|
||||
org = models.Organization.get_by_slug(session.pop('org_slug'))
|
||||
@ -108,10 +108,10 @@ def authorized():
|
||||
if not verify_profile(org, profile):
|
||||
logger.warning("User tried to login with unauthorized domain name: %s (org: %s)", profile['email'], org)
|
||||
flash("Your Google Apps domain name isn't allowed.")
|
||||
return redirect(url_for('login', org_slug=org.slug))
|
||||
return redirect(url_for('redash.login', org_slug=org.slug))
|
||||
|
||||
create_and_login_user(org, profile['name'], profile['email'])
|
||||
|
||||
next = request.args.get('state') or url_for("index", org_slug=org.slug)
|
||||
next = request.args.get('state') or url_for("redash.index", org_slug=org.slug)
|
||||
|
||||
return redirect(next)
|
||||
|
@ -1,13 +0,0 @@
|
||||
from redash import settings
|
||||
from redash.authentication.org_resolving import current_org
|
||||
from flask import url_for, request
|
||||
|
||||
|
||||
# TODO: move this back to authentication/__init__.py after resolving circular depdency between redash.wsgi and redash.handler
|
||||
def get_login_url(external=False, next="/"):
|
||||
if settings.MULTI_ORG:
|
||||
login_url = url_for('login', org_slug=current_org.slug, next=next, _external=external)
|
||||
else:
|
||||
login_url = url_for('login', next=next, _external=external)
|
||||
|
||||
return login_url
|
@ -6,9 +6,11 @@ single Organization in your installation.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from redash.models import Organization
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from flask import request
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from redash.models import Organization
|
||||
|
||||
|
||||
def _get_current_org():
|
||||
@ -17,6 +19,5 @@ def _get_current_org():
|
||||
logging.debug("Current organization: %s (slug: %s)", org, slug)
|
||||
return org
|
||||
|
||||
|
||||
# TODO: move to authentication
|
||||
current_org = LocalProxy(_get_current_org)
|
||||
|
@ -86,7 +86,7 @@ def idp_initiated():
|
||||
# What that means is that, if a user in a SAML assertion
|
||||
# isn't in the user store, we create that user first, then log them in
|
||||
create_and_login_user(current_org, name, email)
|
||||
url = url_for('index')
|
||||
url = url_for('redash.index')
|
||||
|
||||
return redirect(url)
|
||||
|
||||
@ -95,7 +95,7 @@ def idp_initiated():
|
||||
def sp_initiated():
|
||||
if not settings.SAML_METADATA_URL:
|
||||
logger.error("Cannot invoke saml endpoint without metadata url in settings.")
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('redash.index'))
|
||||
|
||||
saml_client = get_saml_client()
|
||||
reqid, info = saml_client.prepare_for_authenticate()
|
||||
|
@ -1,35 +1,29 @@
|
||||
from flask import jsonify, url_for
|
||||
from flask_login import login_required
|
||||
|
||||
from redash.authentication import current_org
|
||||
from redash import settings
|
||||
from redash.wsgi import app
|
||||
from redash.permissions import require_super_admin
|
||||
from redash.authentication.org_resolving import current_org
|
||||
from redash.handlers.api import api
|
||||
from redash.handlers.base import routes
|
||||
from redash.monitor import get_status
|
||||
|
||||
|
||||
def org_scoped_rule(rule):
|
||||
if settings.MULTI_ORG:
|
||||
return "/<org_slug:org_slug>{}".format(rule)
|
||||
|
||||
return rule
|
||||
from redash.permissions import require_super_admin
|
||||
|
||||
|
||||
def base_href():
|
||||
if settings.MULTI_ORG:
|
||||
base_href = url_for('index', _external=True, org_slug=current_org.slug)
|
||||
base_href = url_for('redash.index', _external=True, org_slug=current_org.slug)
|
||||
else:
|
||||
base_href = url_for('index', _external=True)
|
||||
base_href = url_for('redash.index', _external=True)
|
||||
|
||||
return base_href
|
||||
|
||||
|
||||
@app.route('/ping', methods=['GET'])
|
||||
@routes.route('/ping', methods=['GET'])
|
||||
def ping():
|
||||
return 'PONG.'
|
||||
|
||||
|
||||
@app.route('/status.json')
|
||||
@routes.route('/status.json')
|
||||
@login_required
|
||||
@require_super_admin
|
||||
def status_api():
|
||||
@ -38,5 +32,7 @@ def status_api():
|
||||
return jsonify(status)
|
||||
|
||||
|
||||
from redash.handlers import alerts, authentication, base, dashboards, data_sources, events, queries, query_results, \
|
||||
static, users, visualizations, widgets, embed, groups
|
||||
def init_app(app):
|
||||
from redash.handlers import embed, queries, static, authentication
|
||||
app.register_blueprint(routes)
|
||||
api.init_app(app)
|
||||
|
@ -4,7 +4,6 @@ from flask import request
|
||||
from funcy import project
|
||||
|
||||
from redash import models
|
||||
from redash.wsgi import api
|
||||
from redash.permissions import require_access, require_admin_or_owner, view_only, require_permission
|
||||
from redash.handlers.base import BaseResource, require_fields, get_object_or_404
|
||||
|
||||
@ -110,7 +109,3 @@ class AlertSubscriptionResource(BaseResource):
|
||||
'object_type': 'alert'
|
||||
})
|
||||
|
||||
api.add_org_resource(AlertResource, '/api/alerts/<alert_id>', endpoint='alert')
|
||||
api.add_org_resource(AlertSubscriptionListResource, '/api/alerts/<alert_id>/subscriptions', endpoint='alert_subscriptions')
|
||||
api.add_org_resource(AlertSubscriptionResource, '/api/alerts/<alert_id>/subscriptions/<subscriber_id>', endpoint='alert_subscription')
|
||||
api.add_org_resource(AlertListResource, '/api/alerts', endpoint='alerts')
|
||||
|
86
redash/handlers/api.py
Normal file
86
redash/handlers/api.py
Normal file
@ -0,0 +1,86 @@
|
||||
from flask_restful import Api
|
||||
from werkzeug.wrappers import Response
|
||||
from flask import make_response
|
||||
|
||||
from redash.utils import json_dumps
|
||||
from redash.handlers.base import org_scoped_rule
|
||||
from redash.handlers.alerts import AlertResource, AlertListResource, AlertSubscriptionListResource, AlertSubscriptionResource
|
||||
from redash.handlers.dashboards import DashboardListResource, RecentDashboardsResource, DashboardResource, DashboardShareResource
|
||||
from redash.handlers.data_sources import DataSourceTypeListResource, DataSourceListResource, DataSourceSchemaResource, DataSourceResource
|
||||
from redash.handlers.events import EventResource
|
||||
from redash.handlers.queries import QueryRefreshResource, QueryListResource, QueryRecentResource, QuerySearchResource, QueryResource
|
||||
from redash.handlers.query_results import QueryResultListResource, QueryResultResource, JobResource
|
||||
from redash.handlers.users import UserResource, UserListResource
|
||||
from redash.handlers.visualizations import VisualizationListResource
|
||||
from redash.handlers.visualizations import VisualizationResource
|
||||
from redash.handlers.widgets import WidgetResource, WidgetListResource
|
||||
from redash.handlers.groups import GroupListResource, GroupResource, GroupMemberListResource, GroupMemberResource, \
|
||||
GroupDataSourceListResource, GroupDataSourceResource
|
||||
|
||||
|
||||
class ApiExt(Api):
|
||||
def add_org_resource(self, resource, *urls, **kwargs):
|
||||
urls = [org_scoped_rule(url) for url in urls]
|
||||
return self.add_resource(resource, *urls, **kwargs)
|
||||
|
||||
api = ApiExt()
|
||||
|
||||
|
||||
@api.representation('application/json')
|
||||
def json_representation(data, code, headers=None):
|
||||
# Flask-Restful checks only for flask.Response but flask-login uses werkzeug.wrappers.Response
|
||||
if isinstance(data, Response):
|
||||
return data
|
||||
resp = make_response(json_dumps(data), code)
|
||||
resp.headers.extend(headers or {})
|
||||
return resp
|
||||
|
||||
|
||||
api.add_org_resource(AlertResource, '/api/alerts/<alert_id>', endpoint='alert')
|
||||
api.add_org_resource(AlertSubscriptionListResource, '/api/alerts/<alert_id>/subscriptions', endpoint='alert_subscriptions')
|
||||
api.add_org_resource(AlertSubscriptionResource, '/api/alerts/<alert_id>/subscriptions/<subscriber_id>', endpoint='alert_subscription')
|
||||
api.add_org_resource(AlertListResource, '/api/alerts', endpoint='alerts')
|
||||
|
||||
api.add_org_resource(DashboardListResource, '/api/dashboards', endpoint='dashboards')
|
||||
api.add_org_resource(RecentDashboardsResource, '/api/dashboards/recent', endpoint='recent_dashboards')
|
||||
api.add_org_resource(DashboardResource, '/api/dashboards/<dashboard_slug>', endpoint='dashboard')
|
||||
api.add_org_resource(DashboardShareResource, '/api/dashboards/<dashboard_id>/share', endpoint='dashboard_share')
|
||||
|
||||
api.add_org_resource(DataSourceTypeListResource, '/api/data_sources/types', endpoint='data_source_types')
|
||||
api.add_org_resource(DataSourceListResource, '/api/data_sources', endpoint='data_sources')
|
||||
api.add_org_resource(DataSourceSchemaResource, '/api/data_sources/<data_source_id>/schema')
|
||||
api.add_org_resource(DataSourceResource, '/api/data_sources/<data_source_id>', endpoint='data_source')
|
||||
|
||||
api.add_org_resource(GroupListResource, '/api/groups', endpoint='groups')
|
||||
api.add_org_resource(GroupResource, '/api/groups/<group_id>', endpoint='group')
|
||||
api.add_org_resource(GroupMemberListResource, '/api/groups/<group_id>/members', endpoint='group_members')
|
||||
api.add_org_resource(GroupMemberResource, '/api/groups/<group_id>/members/<user_id>', endpoint='group_member')
|
||||
api.add_org_resource(GroupDataSourceListResource, '/api/groups/<group_id>/data_sources', endpoint='group_data_sources')
|
||||
api.add_org_resource(GroupDataSourceResource, '/api/groups/<group_id>/data_sources/<data_source_id>', endpoint='group_data_source')
|
||||
|
||||
api.add_org_resource(EventResource, '/api/events', endpoint='events')
|
||||
|
||||
api.add_org_resource(QuerySearchResource, '/api/queries/search', endpoint='queries_search')
|
||||
api.add_org_resource(QueryRecentResource, '/api/queries/recent', endpoint='recent_queries')
|
||||
api.add_org_resource(QueryListResource, '/api/queries', endpoint='queries')
|
||||
api.add_org_resource(QueryRefreshResource, '/api/queries/<query_id>/refresh', endpoint='query_refresh')
|
||||
api.add_org_resource(QueryResource, '/api/queries/<query_id>', endpoint='query')
|
||||
|
||||
api.add_org_resource(QueryResultListResource, '/api/query_results', endpoint='query_results')
|
||||
api.add_org_resource(QueryResultResource,
|
||||
'/api/query_results/<query_result_id>',
|
||||
'/api/queries/<query_id>/results.<filetype>',
|
||||
'/api/queries/<query_id>/results/<query_result_id>.<filetype>',
|
||||
endpoint='query_result')
|
||||
api.add_org_resource(JobResource, '/api/jobs/<job_id>', endpoint='job')
|
||||
|
||||
api.add_org_resource(UserListResource, '/api/users', endpoint='users')
|
||||
api.add_org_resource(UserResource, '/api/users/<user_id>', endpoint='user')
|
||||
|
||||
api.add_org_resource(VisualizationListResource, '/api/visualizations', endpoint='visualizations')
|
||||
api.add_org_resource(VisualizationResource, '/api/visualizations/<visualization_id>', endpoint='visualization')
|
||||
|
||||
api.add_org_resource(WidgetListResource, '/api/widgets', endpoint='widgets')
|
||||
api.add_org_resource(WidgetResource, '/api/widgets/<int:widget_id>', endpoint='widget')
|
||||
|
||||
|
@ -2,15 +2,14 @@ from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import current_user, login_user, logout_user
|
||||
|
||||
from redash import models, settings
|
||||
from redash.wsgi import app
|
||||
from redash.handlers import org_scoped_rule
|
||||
from redash.authentication.org_resolving import current_org
|
||||
from redash.authentication.helper import get_login_url
|
||||
from redash.handlers import routes
|
||||
from redash.handlers.base import org_scoped_rule
|
||||
from redash.authentication import current_org, get_login_url
|
||||
|
||||
|
||||
@app.route(org_scoped_rule('/login'), methods=['GET', 'POST'])
|
||||
@routes.route(org_scoped_rule('/login'), methods=['GET', 'POST'])
|
||||
def login(org_slug=None):
|
||||
index_url = url_for("index", org_slug=org_slug)
|
||||
index_url = url_for("redash.index", org_slug=org_slug)
|
||||
next_path = request.args.get('next', index_url)
|
||||
|
||||
if current_user.is_authenticated:
|
||||
@ -49,7 +48,7 @@ def login(org_slug=None):
|
||||
show_saml_login=settings.SAML_LOGIN_ENABLED)
|
||||
|
||||
|
||||
@app.route(org_scoped_rule('/logout'))
|
||||
@routes.route(org_scoped_rule('/logout'))
|
||||
def logout(org_slug=None):
|
||||
logout_user()
|
||||
return redirect(get_login_url())
|
||||
|
@ -1,13 +1,16 @@
|
||||
import time
|
||||
from flask import request
|
||||
from flask import request, Blueprint
|
||||
from flask_restful import Resource, abort
|
||||
from flask_login import current_user, login_required
|
||||
from peewee import DoesNotExist
|
||||
|
||||
from redash.authentication.org_resolving import current_org
|
||||
from redash import settings
|
||||
from redash.authentication import current_org
|
||||
from redash.tasks import record_event
|
||||
from redash.models import ApiUser
|
||||
|
||||
routes = Blueprint('redash', __name__)
|
||||
|
||||
|
||||
class BaseResource(Resource):
|
||||
decorators = [login_required]
|
||||
@ -63,3 +66,10 @@ def get_object_or_404(fn, *args, **kwargs):
|
||||
return fn(*args, **kwargs)
|
||||
except DoesNotExist:
|
||||
abort(404)
|
||||
|
||||
|
||||
def org_scoped_rule(rule):
|
||||
if settings.MULTI_ORG:
|
||||
return "/<org_slug:org_slug>{}".format(rule)
|
||||
|
||||
return rule
|
||||
|
@ -4,7 +4,6 @@ from funcy import distinct, take
|
||||
from itertools import chain
|
||||
|
||||
from redash import models
|
||||
from redash.wsgi import api
|
||||
from redash.permissions import require_permission, require_admin_or_owner
|
||||
from redash.handlers.base import BaseResource, get_object_or_404
|
||||
|
||||
@ -47,7 +46,7 @@ class DashboardResource(BaseResource):
|
||||
|
||||
api_key = models.ApiKey.get_by_object(dashboard)
|
||||
if api_key:
|
||||
response['public_url'] = url_for('public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)
|
||||
response['public_url'] = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)
|
||||
response['api_key'] = api_key.api_key
|
||||
|
||||
return response
|
||||
@ -77,7 +76,7 @@ class DashboardShareResource(BaseResource):
|
||||
dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)
|
||||
require_admin_or_owner(dashboard.user_id)
|
||||
api_key = models.ApiKey.create_for_object(dashboard, self.current_user)
|
||||
public_url = url_for('public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)
|
||||
public_url = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)
|
||||
|
||||
return {'public_url': public_url, 'api_key': api_key.api_key}
|
||||
|
||||
@ -91,7 +90,3 @@ class DashboardShareResource(BaseResource):
|
||||
api_key.save()
|
||||
|
||||
|
||||
api.add_org_resource(DashboardListResource, '/api/dashboards', endpoint='dashboards')
|
||||
api.add_org_resource(RecentDashboardsResource, '/api/dashboards/recent', endpoint='recent_dashboards')
|
||||
api.add_org_resource(DashboardResource, '/api/dashboards/<dashboard_slug>', endpoint='dashboard')
|
||||
api.add_org_resource(DashboardShareResource, '/api/dashboards/<dashboard_id>/share', endpoint='dashboard_share')
|
||||
|
@ -3,22 +3,19 @@ from flask_restful import abort
|
||||
from funcy import project
|
||||
|
||||
from redash import models
|
||||
from redash.wsgi import api
|
||||
from redash.utils.configuration import ConfigurationContainer, ValidationError
|
||||
from redash.permissions import require_admin, require_permission, require_access, view_only
|
||||
from redash.query_runner import query_runners, get_configuration_schema_for_type
|
||||
from redash.handlers.base import BaseResource, get_object_or_404
|
||||
|
||||
|
||||
class DataSourceTypeListAPI(BaseResource):
|
||||
class DataSourceTypeListResource(BaseResource):
|
||||
@require_admin
|
||||
def get(self):
|
||||
return [q.to_dict() for q in sorted(query_runners.values(), key=lambda q: q.name())]
|
||||
|
||||
api.add_org_resource(DataSourceTypeListAPI, '/api/data_sources/types', endpoint='data_source_types')
|
||||
|
||||
|
||||
class DataSourceAPI(BaseResource):
|
||||
class DataSourceResource(BaseResource):
|
||||
@require_admin
|
||||
def get(self, data_source_id):
|
||||
data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)
|
||||
@ -53,7 +50,7 @@ class DataSourceAPI(BaseResource):
|
||||
return make_response('', 204)
|
||||
|
||||
|
||||
class DataSourceListAPI(BaseResource):
|
||||
class DataSourceListResource(BaseResource):
|
||||
@require_permission('list_data_sources')
|
||||
def get(self):
|
||||
if self.current_user.has_permission('admin'):
|
||||
@ -100,11 +97,8 @@ class DataSourceListAPI(BaseResource):
|
||||
|
||||
return datasource.to_dict(all=True)
|
||||
|
||||
api.add_org_resource(DataSourceListAPI, '/api/data_sources', endpoint='data_sources')
|
||||
api.add_org_resource(DataSourceAPI, '/api/data_sources/<data_source_id>', endpoint='data_source')
|
||||
|
||||
|
||||
class DataSourceSchemaAPI(BaseResource):
|
||||
class DataSourceSchemaResource(BaseResource):
|
||||
def get(self, data_source_id):
|
||||
data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)
|
||||
require_access(data_source.groups, self.current_user, view_only)
|
||||
@ -112,4 +106,3 @@ class DataSourceSchemaAPI(BaseResource):
|
||||
|
||||
return schema
|
||||
|
||||
api.add_org_resource(DataSourceSchemaAPI, '/api/data_sources/<data_source_id>/schema')
|
||||
|
@ -1,20 +1,20 @@
|
||||
import json
|
||||
|
||||
from funcy import project
|
||||
from flask import render_template, url_for, request
|
||||
from flask import render_template, request
|
||||
from flask_login import login_required, current_user
|
||||
from flask_restful import abort
|
||||
|
||||
from redash import models, settings
|
||||
from redash import serializers
|
||||
from redash.wsgi import app
|
||||
from redash.utils import json_dumps
|
||||
from redash.handlers import org_scoped_rule, base_href
|
||||
from redash.handlers import base_href, routes
|
||||
from redash.handlers.base import org_scoped_rule
|
||||
from redash.permissions import require_access, view_only
|
||||
from redash.authentication.org_resolving import current_org
|
||||
from authentication import current_org
|
||||
|
||||
|
||||
@app.route(org_scoped_rule('/embed/query/<query_id>/visualization/<visualization_id>'), methods=['GET'])
|
||||
@routes.route(org_scoped_rule('/embed/query/<query_id>/visualization/<visualization_id>'), methods=['GET'])
|
||||
@login_required
|
||||
def embed(query_id, visualization_id, org_slug=None):
|
||||
# TODO: add event for embed access
|
||||
@ -49,7 +49,7 @@ def embed(query_id, visualization_id, org_slug=None):
|
||||
analytics=settings.ANALYTICS)
|
||||
|
||||
|
||||
@app.route(org_scoped_rule('/public/dashboards/<token>'), methods=['GET'])
|
||||
@routes.route(org_scoped_rule('/public/dashboards/<token>'), methods=['GET'])
|
||||
@login_required
|
||||
def public_dashboard(token, org_slug=None):
|
||||
# TODO: verify object is a dashboard?
|
||||
|
@ -1,15 +1,12 @@
|
||||
from flask import request
|
||||
|
||||
from redash import statsd_client
|
||||
from redash.wsgi import api
|
||||
from redash.handlers.base import BaseResource
|
||||
|
||||
|
||||
class EventAPI(BaseResource):
|
||||
class EventResource(BaseResource):
|
||||
def post(self):
|
||||
events_list = request.get_json(force=True)
|
||||
for event in events_list:
|
||||
self.record_event(event)
|
||||
|
||||
|
||||
api.add_org_resource(EventAPI, '/api/events', endpoint='events')
|
||||
|
@ -2,7 +2,6 @@ import time
|
||||
from flask import request
|
||||
from flask_restful import abort
|
||||
from redash import models
|
||||
from redash.wsgi import api
|
||||
from redash.permissions import require_admin, require_permission
|
||||
from redash.handlers.base import BaseResource, get_object_or_404
|
||||
|
||||
@ -179,9 +178,3 @@ class GroupDataSourceResource(BaseResource):
|
||||
})
|
||||
|
||||
|
||||
api.add_org_resource(GroupListResource, '/api/groups', endpoint='groups')
|
||||
api.add_org_resource(GroupResource, '/api/groups/<group_id>', endpoint='group')
|
||||
api.add_org_resource(GroupMemberListResource, '/api/groups/<group_id>/members', endpoint='group_members')
|
||||
api.add_org_resource(GroupMemberResource, '/api/groups/<group_id>/members/<user_id>', endpoint='group_member')
|
||||
api.add_org_resource(GroupDataSourceListResource, '/api/groups/<group_id>/data_sources', endpoint='group_data_sources')
|
||||
api.add_org_resource(GroupDataSourceResource, '/api/groups/<group_id>/data_sources/<data_source_id>', endpoint='group_data_source')
|
||||
|
@ -6,15 +6,15 @@ import sqlparse
|
||||
from funcy import distinct, take
|
||||
from itertools import chain
|
||||
|
||||
from redash.handlers.base import routes
|
||||
from redash.handlers.query_results import run_query
|
||||
from redash import models
|
||||
from redash.wsgi import app, api
|
||||
from redash.permissions import require_permission, require_access, require_admin_or_owner, not_view_only, view_only
|
||||
from redash.handlers.base import BaseResource, get_object_or_404
|
||||
from redash.utils import collect_parameters_from_request
|
||||
|
||||
|
||||
@app.route('/api/queries/format', methods=['POST'])
|
||||
@routes.route('/api/queries/format', methods=['POST'])
|
||||
@login_required
|
||||
def format_sql_query():
|
||||
arguments = request.get_json(force=True)
|
||||
@ -23,7 +23,7 @@ def format_sql_query():
|
||||
return sqlparse.format(query, reindent=True, keyword_case='upper')
|
||||
|
||||
|
||||
class QuerySearchAPI(BaseResource):
|
||||
class QuerySearchResource(BaseResource):
|
||||
@require_permission('view_query')
|
||||
def get(self):
|
||||
term = request.args.get('q', '')
|
||||
@ -31,7 +31,7 @@ class QuerySearchAPI(BaseResource):
|
||||
return [q.to_dict() for q in models.Query.search(term, self.current_user.groups)]
|
||||
|
||||
|
||||
class QueryRecentAPI(BaseResource):
|
||||
class QueryRecentResource(BaseResource):
|
||||
@require_permission('view_query')
|
||||
def get(self):
|
||||
queries = models.Query.recent(self.current_user.groups, self.current_user.id)
|
||||
@ -44,7 +44,7 @@ class QueryRecentAPI(BaseResource):
|
||||
return take(20, distinct(chain(recent, global_recent), key=lambda d: d['id']))
|
||||
|
||||
|
||||
class QueryListAPI(BaseResource):
|
||||
class QueryListResource(BaseResource):
|
||||
@require_permission('create_query')
|
||||
def post(self):
|
||||
query_def = request.get_json(force=True)
|
||||
@ -72,7 +72,7 @@ class QueryListAPI(BaseResource):
|
||||
return [q.to_dict(with_stats=True) for q in models.Query.all_queries(self.current_user.groups)]
|
||||
|
||||
|
||||
class QueryAPI(BaseResource):
|
||||
class QueryResource(BaseResource):
|
||||
@require_permission('edit_query')
|
||||
def post(self, query_id):
|
||||
query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
|
||||
@ -123,8 +123,3 @@ class QueryRefreshResource(BaseResource):
|
||||
return run_query(query.data_source, parameter_values, query.query, query.id)
|
||||
|
||||
|
||||
api.add_org_resource(QuerySearchAPI, '/api/queries/search', endpoint='queries_search')
|
||||
api.add_org_resource(QueryRecentAPI, '/api/queries/recent', endpoint='recent_queries')
|
||||
api.add_org_resource(QueryListAPI, '/api/queries', endpoint='queries')
|
||||
api.add_org_resource(QueryRefreshResource, '/api/queries/<query_id>/refresh', endpoint='query_refresh')
|
||||
api.add_org_resource(QueryAPI, '/api/queries/<query_id>', endpoint='query')
|
||||
|
@ -9,7 +9,6 @@ from flask_login import current_user
|
||||
from flask_restful import abort
|
||||
import xlsxwriter
|
||||
from redash import models, settings, utils
|
||||
from redash.wsgi import api
|
||||
from redash.tasks import QueryTask, record_event
|
||||
from redash.permissions import require_permission, not_view_only, has_access, require_access, view_only
|
||||
from redash.handlers.base import BaseResource, get_object_or_404
|
||||
@ -39,7 +38,7 @@ def run_query(data_source, parameter_values, query_text, query_id, max_age=0):
|
||||
return {'job': job.to_dict()}
|
||||
|
||||
|
||||
class QueryResultListAPI(BaseResource):
|
||||
class QueryResultListResource(BaseResource):
|
||||
@require_permission('execute_query')
|
||||
def post(self):
|
||||
params = request.get_json(force=True)
|
||||
@ -68,7 +67,7 @@ class QueryResultListAPI(BaseResource):
|
||||
ONE_YEAR = 60 * 60 * 24 * 365.25
|
||||
|
||||
|
||||
class QueryResultAPI(BaseResource):
|
||||
class QueryResultResource(BaseResource):
|
||||
@staticmethod
|
||||
def add_cors_headers(headers):
|
||||
if 'Origin' in request.headers:
|
||||
@ -189,15 +188,7 @@ class QueryResultAPI(BaseResource):
|
||||
return make_response(s.getvalue(), 200, headers)
|
||||
|
||||
|
||||
api.add_org_resource(QueryResultListAPI, '/api/query_results', endpoint='query_results')
|
||||
api.add_org_resource(QueryResultAPI,
|
||||
'/api/query_results/<query_result_id>',
|
||||
'/api/queries/<query_id>/results.<filetype>',
|
||||
'/api/queries/<query_id>/results/<query_result_id>.<filetype>',
|
||||
endpoint='query_result')
|
||||
|
||||
|
||||
class JobAPI(BaseResource):
|
||||
class JobResource(BaseResource):
|
||||
def get(self, job_id):
|
||||
# TODO: if finished, include the query result
|
||||
job = QueryTask(job_id=job_id)
|
||||
@ -207,4 +198,3 @@ class JobAPI(BaseResource):
|
||||
job = QueryTask(job_id=job_id)
|
||||
job.cancel()
|
||||
|
||||
api.add_org_resource(JobAPI, '/api/jobs/<job_id>', endpoint='job')
|
||||
|
@ -1,17 +1,17 @@
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from flask import render_template, send_from_directory, current_app, url_for, request
|
||||
from flask import render_template, send_from_directory, current_app
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from redash import settings, __version__
|
||||
from redash.handlers import org_scoped_rule, base_href
|
||||
from redash.wsgi import app
|
||||
from redash.handlers import base_href, routes
|
||||
from redash.handlers.base import org_scoped_rule
|
||||
from redash.version_check import get_latest_version
|
||||
from redash.authentication.org_resolving import current_org
|
||||
from authentication import current_org
|
||||
|
||||
|
||||
@app.route('/<path:filename>')
|
||||
@routes.route('/<path:filename>')
|
||||
def send_static(filename):
|
||||
if current_app.debug:
|
||||
cache_timeout = 0
|
||||
@ -59,10 +59,10 @@ def index(**kwargs):
|
||||
|
||||
def register_static_routes(rules):
|
||||
# Make sure that / is the first route considered as index.
|
||||
app.add_url_rule(org_scoped_rule("/"), "index", index)
|
||||
routes.add_url_rule(org_scoped_rule("/"), "index", index)
|
||||
|
||||
for rule in rules:
|
||||
app.add_url_rule(org_scoped_rule(rule), None, index)
|
||||
routes.add_url_rule(org_scoped_rule(rule), None, index)
|
||||
|
||||
rules = ['/admin/<anything>/<whatever>',
|
||||
'/admin/<anything>',
|
||||
|
@ -5,7 +5,6 @@ from funcy import project
|
||||
from peewee import IntegrityError
|
||||
|
||||
from redash import models
|
||||
from redash.wsgi import api
|
||||
from redash.permissions import require_permission, require_admin_or_owner, is_admin_or_owner, \
|
||||
require_permission_or_owner, require_admin
|
||||
from redash.handlers.base import BaseResource, require_fields, get_object_or_404
|
||||
@ -93,5 +92,3 @@ class UserResource(BaseResource):
|
||||
return user.to_dict(with_api_key=is_admin_or_owner(user_id))
|
||||
|
||||
|
||||
api.add_org_resource(UserListResource, '/api/users', endpoint='users')
|
||||
api.add_org_resource(UserResource, '/api/users/<user_id>', endpoint='user')
|
||||
|
@ -2,7 +2,6 @@ import json
|
||||
from flask import request
|
||||
|
||||
from redash import models
|
||||
from redash.wsgi import api
|
||||
from redash.permissions import require_permission, require_admin_or_owner
|
||||
from redash.handlers.base import BaseResource, get_object_or_404
|
||||
|
||||
@ -46,6 +45,3 @@ class VisualizationResource(BaseResource):
|
||||
require_admin_or_owner(vis.query.user_id)
|
||||
|
||||
vis.delete_instance()
|
||||
|
||||
api.add_org_resource(VisualizationListResource, '/api/visualizations', endpoint='visualizations')
|
||||
api.add_org_resource(VisualizationResource, '/api/visualizations/<visualization_id>', endpoint='visualization')
|
||||
|
@ -3,12 +3,11 @@ import json
|
||||
from flask import request
|
||||
|
||||
from redash import models
|
||||
from redash.wsgi import api
|
||||
from redash.permissions import require_permission, require_admin_or_owner, require_access, view_only
|
||||
from redash.handlers.base import BaseResource
|
||||
|
||||
|
||||
class WidgetListAPI(BaseResource):
|
||||
class WidgetListResource(BaseResource):
|
||||
@require_permission('edit_dashboard')
|
||||
def post(self):
|
||||
widget_properties = request.get_json(force=True)
|
||||
@ -51,7 +50,7 @@ class WidgetListAPI(BaseResource):
|
||||
return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}
|
||||
|
||||
|
||||
class WidgetAPI(BaseResource):
|
||||
class WidgetResource(BaseResource):
|
||||
@require_permission('edit_dashboard')
|
||||
def delete(self, widget_id):
|
||||
widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
|
||||
@ -60,5 +59,3 @@ class WidgetAPI(BaseResource):
|
||||
|
||||
return {'layout': widget.dashboard.layout}
|
||||
|
||||
api.add_org_resource(WidgetListAPI, '/api/widgets', endpoint='widgets')
|
||||
api.add_org_resource(WidgetAPI, '/api/widgets/<int:widget_id>', endpoint='widget')
|
||||
|
@ -152,6 +152,7 @@ class ApiUser(UserMixin, PermissionsCheckMixin):
|
||||
else:
|
||||
self.id = api_key.api_key
|
||||
self.object = api_key.object
|
||||
# self.name =
|
||||
self.groups = groups
|
||||
self.org = org
|
||||
|
||||
|
@ -1,84 +1,3 @@
|
||||
import json
|
||||
from flask import Flask, make_response
|
||||
from werkzeug.wrappers import Response
|
||||
from flask_restful import Api
|
||||
from flask_sslify import SSLify
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
from redash import create_app
|
||||
|
||||
from redash import settings, utils, mail, __version__
|
||||
from redash.models import db
|
||||
from redash.metrics.request import provision_app
|
||||
from redash.admin import init_admin
|
||||
from werkzeug.routing import BaseConverter, ValidationError
|
||||
|
||||
|
||||
class SlugConverter(BaseConverter):
|
||||
def to_python(self, value):
|
||||
# This is an ugly workaround for when we enable multi-org and some files are being called by the index rule:
|
||||
if value in ('google_login.png', 'favicon.ico', 'robots.txt', 'views'):
|
||||
raise ValidationError()
|
||||
|
||||
return value
|
||||
|
||||
def to_url(self, value):
|
||||
return value
|
||||
|
||||
|
||||
app = Flask(__name__,
|
||||
template_folder=settings.STATIC_ASSETS_PATH,
|
||||
static_folder=settings.STATIC_ASSETS_PATH,
|
||||
static_path='/static')
|
||||
|
||||
# Make sure we get the right referral address even behind proxies like nginx.
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, settings.PROXIES_COUNT)
|
||||
app.url_map.converters['org_slug'] = SlugConverter
|
||||
provision_app(app)
|
||||
|
||||
|
||||
# TODO: remove duplication
|
||||
def org_scoped_rule(rule):
|
||||
if settings.MULTI_ORG:
|
||||
return "/<org_slug>{}".format(rule)
|
||||
|
||||
return rule
|
||||
|
||||
|
||||
class ApiExt(Api):
|
||||
def add_org_resource(self, resource, *urls, **kwargs):
|
||||
urls = [org_scoped_rule(url) for url in urls]
|
||||
return self.add_resource(resource, *urls, **kwargs)
|
||||
|
||||
api = ApiExt(app)
|
||||
init_admin(app)
|
||||
|
||||
|
||||
if settings.ENFORCE_HTTPS:
|
||||
SSLify(app, skips=['ping'])
|
||||
|
||||
|
||||
if settings.SENTRY_DSN:
|
||||
from raven.contrib.flask import Sentry
|
||||
sentry = Sentry(app, dsn=settings.SENTRY_DSN)
|
||||
sentry.client.release = __version__
|
||||
|
||||
# configure our database
|
||||
settings.DATABASE_CONFIG.update({'threadlocals': True})
|
||||
app.config['DATABASE'] = settings.DATABASE_CONFIG
|
||||
app.config.update(settings.all_settings())
|
||||
db.init_app(app)
|
||||
mail.init_app(app)
|
||||
|
||||
from redash.authentication import setup_authentication
|
||||
setup_authentication(app)
|
||||
|
||||
|
||||
@api.representation('application/json')
|
||||
def json_representation(data, code, headers=None):
|
||||
# Flask-Restful checks only for flask.Response but flask-login uses werkzeug.wrappers.Response
|
||||
if isinstance(data, Response):
|
||||
return data
|
||||
resp = make_response(json.dumps(data, cls=utils.JSONEncoder), code)
|
||||
resp.headers.extend(headers or {})
|
||||
return resp
|
||||
|
||||
from redash import handlers
|
||||
app = create_app()
|
||||
|
Loading…
Reference in New Issue
Block a user