redash/handlers/base.py (93 lines of code) (raw):
import time
from inspect import isclass
from flask import Blueprint, current_app, request
from flask_login import current_user, login_required
from flask_restful import Resource, abort
from redash import settings
from redash.authentication import current_org
from redash.models import db
from redash.tasks import record_event as record_event_task
from redash.utils import json_dumps
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy import cast
from sqlalchemy.dialects import postgresql
from sqlalchemy_utils import sort_query
routes = Blueprint(
"redash", __name__, template_folder=settings.fix_assets_path("templates"),
url_prefix=settings.ROUTE_PREFIX,
)
class BaseResource(Resource):
decorators = [login_required]
def __init__(self, *args, **kwargs):
super(BaseResource, self).__init__(*args, **kwargs)
self._user = None
def dispatch_request(self, *args, **kwargs):
kwargs.pop("org_slug", None)
return super(BaseResource, self).dispatch_request(*args, **kwargs)
@property
def current_user(self):
return current_user._get_current_object()
@property
def current_org(self):
return current_org._get_current_object()
def record_event(self, options):
record_event(self.current_org, self.current_user, options)
# TODO: this should probably be somewhere else
def update_model(self, model, updates):
for k, v in updates.items():
setattr(model, k, v)
def record_event(org, user, options):
if user.is_api_user():
options.update({"api_key": user.name, "org_id": org.id})
else:
options.update({"user_id": user.id, "user_name": user.name, "org_id": org.id})
options.update({"user_agent": request.user_agent.string, "ip": request.remote_addr})
if "timestamp" not in options:
options["timestamp"] = int(time.time())
record_event_task.delay(options)
def require_fields(req, fields):
for f in fields:
if f not in req:
abort(400)
def get_object_or_404(fn, *args, **kwargs):
try:
rv = fn(*args, **kwargs)
if rv is None:
abort(404)
except NoResultFound:
abort(404)
return rv
def paginate(query_set, page, page_size, serializer, **kwargs):
count = query_set.count()
if page < 1:
abort(400, message="Page must be positive integer.")
if (page - 1) * page_size + 1 > count > 0:
abort(400, message="Page is out of range.")
if page_size > 250 or page_size < 1:
abort(400, message="Page size is out of range (1-250).")
results = query_set.paginate(page, page_size)
# support for old function based serializers
if isclass(serializer):
items = serializer(results.items, **kwargs).serialize()
else:
items = [serializer(result) for result in results.items]
return {"count": count, "page": page, "page_size": page_size, "results": items}
def org_scoped_rule(rule):
if settings.MULTI_ORG:
return "/<org_slug>{}".format(rule)
return rule
def json_response(response):
return current_app.response_class(json_dumps(response), mimetype="application/json")
def filter_by_tags(result_set, column):
if request.args.getlist("tags"):
tags = request.args.getlist("tags")
result_set = result_set.filter(
cast(column, postgresql.ARRAY(db.Text)).contains(tags)
)
return result_set
def order_results(results, default_order, allowed_orders, fallback=True):
"""
Orders the given results with the sort order as requested in the
"order" request query parameter or the given default order.
"""
# See if a particular order has been requested
requested_order = request.args.get("order", "").strip()
# and if not (and no fallback is wanted) return results as is
if not requested_order and not fallback:
return results
# and if it matches a long-form for related fields, falling
# back to the default order
selected_order = allowed_orders.get(requested_order, None)
if selected_order is None and fallback:
selected_order = default_order
# The query may already have an ORDER BY statement attached
# so we clear it here and apply the selected order
return sort_query(results.order_by(None), selected_order)