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:
Arik Fraimovich 2016-03-13 14:40:54 +02:00
parent f8a8928a90
commit 9ae503289c
24 changed files with 230 additions and 217 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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
View 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')

View File

@ -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())

View File

@ -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

View File

@ -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')

View File

@ -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')

View File

@ -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?

View File

@ -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')

View File

@ -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')

View File

@ -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')

View File

@ -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')

View File

@ -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>',

View File

@ -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')

View File

@ -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')

View File

@ -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')

View File

@ -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

View File

@ -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()