We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
AttributeError: 'ApiUser' object has no attribute 'email'
### Issue Summary

API POST calls to /api/queries/###/refresh throw this error on 1.0.3+b2850

### Steps to Reproduce

1. Make a POST call to /api/queries/###/refresh
2. See error in api_error.log and 500 result

Full error with trace:

```
Exception on /api/queries/###/refresh [POST]
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1639, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1625, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/local/lib/python2.7/dist-packages/flask_restful/__init__.py", line 477, in wrapper
    resp = resource(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flask_login/utils.py", line 228, in decorated_view
    return func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "/opt/redash/redash.1.0.3.b2850/redash/handlers/base.py", line 28, in dispatch_request
    return super(BaseResource, self).dispatch_request(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/flask_restful/__init__.py", line 587, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/opt/redash/redash.1.0.3.b2850/redash/handlers/queries.py", line 266, in post
    return run_query(query.data_source, parameter_values, query.query_text, query.id)
  File "/opt/redash/redash.1.0.3.b2850/redash/handlers/query_results.py", line 45, in run_query
    job = enqueue_query(query_text, data_source, current_user.id, metadata={"Username": current_user.email, "Query ID": query_id})
  File "/usr/local/lib/python2.7/dist-packages/werkzeug/local.py", line 343, in __getattr__
    return getattr(self._get_current_object(), name)
AttributeError: 'ApiUser' object has no attribute 'email'
```

Same request worked in some previous version of Redash. Looking at the current code on master, it seems to still make the same reference to `current_user.email` so assuming this bug still exists, but haven't confirmed that yet.

### Technical details:

* Redash Version: 1.0.3+b2850
* Browser/OS: N/A
* How did you install Redash: N/A


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redash/handlers/queries.py`
Content:
```
1 from itertools import chain
2 
3 import sqlparse
4 from flask import jsonify, request
5 from flask_login import login_required
6 from flask_restful import abort
7 from funcy import distinct, take
8 from sqlalchemy.orm.exc import StaleDataError
9 
10 from redash import models, settings
11 from redash.handlers.base import (BaseResource, get_object_or_404,
12                                   org_scoped_rule, paginate, routes)
13 from redash.handlers.query_results import run_query
14 from redash.permissions import (can_modify, not_view_only, require_access,
15                                 require_admin_or_owner,
16                                 require_object_modify_permission,
17                                 require_permission, view_only)
18 from redash.utils import collect_parameters_from_request
19 
20 
21 @routes.route(org_scoped_rule('/api/queries/format'), methods=['POST'])
22 @login_required
23 def format_sql_query(org_slug=None):
24     """
25     Formats an SQL query using the Python ``sqlparse`` formatter.
26 
27     :<json string query: The SQL text to format
28     :>json string query: Formatted SQL text
29     """
30     arguments = request.get_json(force=True)
31     query = arguments.get("query", "")
32 
33     return jsonify({'query': sqlparse.format(query, reindent=True, keyword_case='upper')})
34 
35 
36 class QuerySearchResource(BaseResource):
37     @require_permission('view_query')
38     def get(self):
39         """
40         Search query text, titles, and descriptions.
41 
42         :qparam string q: Search term
43 
44         Responds with a list of :ref:`query <query-response-label>` objects.
45         """
46         term = request.args.get('q', '')
47         include_drafts = request.args.get('include_drafts') is not None
48 
49         return [q.to_dict(with_last_modified_by=False) for q in models.Query.search(term, self.current_user.group_ids, include_drafts=include_drafts)]
50 
51 
52 class QueryRecentResource(BaseResource):
53     @require_permission('view_query')
54     def get(self):
55         """
56         Retrieve up to 20 queries modified in the last 7 days.
57 
58         Responds with a list of :ref:`query <query-response-label>` objects.
59         """
60 
61         if settings.FEATURE_DUMB_RECENTS:
62             results = models.Query.by_user(self.current_user).order_by(models.Query.updated_at.desc()).limit(10)
63             queries = [q.to_dict(with_last_modified_by=False, with_user=False) for q in results]
64         else:
65             queries = models.Query.recent(self.current_user.group_ids, self.current_user.id)
66             recent = [d.to_dict(with_last_modified_by=False, with_user=False) for d in queries]
67 
68             global_recent = []
69             if len(recent) < 10:
70                 global_recent = [d.to_dict(with_last_modified_by=False, with_user=False) for d in models.Query.recent(self.current_user.group_ids)]
71 
72             queries = take(20, distinct(chain(recent, global_recent), key=lambda d: d['id']))
73 
74         return queries
75 
76 
77 class QueryListResource(BaseResource):
78     @require_permission('create_query')
79     def post(self):
80         """
81         Create a new query.
82 
83         :<json number data_source_id: The ID of the data source this query will run on
84         :<json string query: Query text
85         :<json string name:
86         :<json string description:
87         :<json string schedule: Schedule interval, in seconds, for repeated execution of this query
88         :<json object options: Query options
89 
90         .. _query-response-label:
91 
92         :>json number id: Query ID
93         :>json number latest_query_data_id: ID for latest output data from this query
94         :>json string name:
95         :>json string description:
96         :>json string query: Query text
97         :>json string query_hash: Hash of query text
98         :>json string schedule: Schedule interval, in seconds, for repeated execution of this query
99         :>json string api_key: Key for public access to this query's results.
100         :>json boolean is_archived: Whether this query is displayed in indexes and search results or not.
101         :>json boolean is_draft: Whether this query is a draft or not
102         :>json string updated_at: Time of last modification, in ISO format
103         :>json string created_at: Time of creation, in ISO format
104         :>json number data_source_id: ID of the data source this query will run on
105         :>json object options: Query options
106         :>json number version: Revision version (for update conflict avoidance)
107         :>json number user_id: ID of query creator
108         :>json number last_modified_by_id: ID of user who last modified this query
109         :>json string retrieved_at: Time when query results were last retrieved, in ISO format (may be null)
110         :>json number runtime: Runtime of last query execution, in seconds (may be null)
111         """
112         query_def = request.get_json(force=True)
113         data_source = models.DataSource.get_by_id_and_org(query_def.pop('data_source_id'), self.current_org)
114         require_access(data_source.groups, self.current_user, not_view_only)
115 
116         for field in ['id', 'created_at', 'api_key', 'visualizations', 'latest_query_data', 'last_modified_by']:
117             query_def.pop(field, None)
118 
119         query_def['query_text'] = query_def.pop('query')
120         query_def['user'] = self.current_user
121         query_def['data_source'] = data_source
122         query_def['org'] = self.current_org
123         query_def['is_draft'] = True
124         query = models.Query.create(**query_def)
125         models.db.session.add(query)
126         models.db.session.commit()
127 
128         self.record_event({
129             'action': 'create',
130             'object_id': query.id,
131             'object_type': 'query'
132         })
133 
134         return query.to_dict()
135 
136     @require_permission('view_query')
137     def get(self):
138         """
139         Retrieve a list of queries.
140 
141         :qparam number page_size: Number of queries to return
142         :qparam number page: Page number to retrieve
143 
144         Responds with an array of :ref:`query <query-response-label>` objects.
145         """
146 
147         results = models.Query.all_queries(self.current_user.group_ids, self.current_user.id)
148         page = request.args.get('page', 1, type=int)
149         page_size = request.args.get('page_size', 25, type=int)
150         return paginate(results, page, page_size, lambda q: q.to_dict(with_stats=True, with_last_modified_by=False))
151 
152 
153 class MyQueriesResource(BaseResource):
154     @require_permission('view_query')
155     def get(self):
156         """
157         Retrieve a list of queries created by the current user.
158 
159         :qparam number page_size: Number of queries to return
160         :qparam number page: Page number to retrieve
161 
162         Responds with an array of :ref:`query <query-response-label>` objects.
163         """
164         drafts = request.args.get('drafts') is not None
165         results = models.Query.by_user(self.current_user)
166         page = request.args.get('page', 1, type=int)
167         page_size = request.args.get('page_size', 25, type=int)
168         return paginate(results, page, page_size, lambda q: q.to_dict(with_stats=True, with_last_modified_by=False))
169 
170 
171 class QueryResource(BaseResource):
172     @require_permission('edit_query')
173     def post(self, query_id):
174         """
175         Modify a query.
176 
177         :param query_id: ID of query to update
178         :<json number data_source_id: The ID of the data source this query will run on
179         :<json string query: Query text
180         :<json string name:
181         :<json string description:
182         :<json string schedule: Schedule interval, in seconds, for repeated execution of this query
183         :<json object options: Query options
184 
185         Responds with the updated :ref:`query <query-response-label>` object.
186         """
187         query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
188         query_def = request.get_json(force=True)
189 
190         require_object_modify_permission(query, self.current_user)
191 
192         for field in ['id', 'created_at', 'api_key', 'visualizations', 'latest_query_data', 'user', 'last_modified_by', 'org']:
193             query_def.pop(field, None)
194 
195         if 'query' in query_def:
196             query_def['query_text'] = query_def.pop('query')
197 
198         query_def['last_modified_by'] = self.current_user
199         query_def['changed_by'] = self.current_user
200         # SQLAlchemy handles the case where a concurrent transaction beats us
201         # to the update. But we still have to make sure that we're not starting
202         # out behind.
203         if 'version' in query_def and query_def['version'] != query.version:
204             abort(409)
205 
206         try:
207             self.update_model(query, query_def)
208             models.db.session.commit()
209         except StaleDataError:
210             abort(409)
211 
212         return query.to_dict(with_visualizations=True)
213 
214     @require_permission('view_query')
215     def get(self, query_id):
216         """
217         Retrieve a query.
218 
219         :param query_id: ID of query to fetch
220 
221         Responds with the :ref:`query <query-response-label>` contents.
222         """
223         q = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
224         require_access(q.groups, self.current_user, view_only)
225 
226         result = q.to_dict(with_visualizations=True)
227         result['can_edit'] = can_modify(q, self.current_user)
228         return result
229 
230     # TODO: move to resource of its own? (POST /queries/{id}/archive)
231     def delete(self, query_id):
232         """
233         Archives a query.
234 
235         :param query_id: ID of query to archive
236         """
237         query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
238         require_admin_or_owner(query.user_id)
239         query.archive(self.current_user)
240         models.db.session.commit()
241 
242 
243 class QueryForkResource(BaseResource):
244     @require_permission('edit_query')
245     def post(self, query_id):
246         """
247         Creates a new query, copying the query text from an existing one.
248 
249         :param query_id: ID of query to fork
250 
251         Responds with created :ref:`query <query-response-label>` object.
252         """
253         query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
254         require_access(query.data_source.groups, self.current_user, not_view_only)
255         forked_query = query.fork(self.current_user)
256         models.db.session.commit()
257         return forked_query.to_dict(with_visualizations=True)
258 
259 
260 class QueryRefreshResource(BaseResource):
261     def post(self, query_id):
262         """
263         Execute a query, updating the query object with the results.
264 
265         :param query_id: ID of query to execute
266 
267         Responds with query task details.
268         """
269         query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
270         require_access(query.groups, self.current_user, not_view_only)
271 
272         parameter_values = collect_parameters_from_request(request.args)
273 
274         return run_query(query.data_source, parameter_values, query.query_text, query.id)
275
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/redash/handlers/queries.py b/redash/handlers/queries.py
--- a/redash/handlers/queries.py
+++ b/redash/handlers/queries.py
@@ -266,6 +266,12 @@
 
         Responds with query task details.
         """
+        # TODO: this should actually check for permissions, but because currently you can only
+        # get here either with a user API key or a query one, we can just check whether it's
+        # an api key (meaning this is a query API key, which only grants read access).
+        if self.current_user.is_api_user():
+            abort(403, message="Please use a user API key.")
+
         query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
         require_access(query.groups, self.current_user, not_view_only)

{"golden_diff": "diff --git a/redash/handlers/queries.py b/redash/handlers/queries.py\n--- a/redash/handlers/queries.py\n+++ b/redash/handlers/queries.py\n@@ -266,6 +266,12 @@\n \n         Responds with query task details.\n         \"\"\"\n+        # TODO: this should actually check for permissions, but because currently you can only\n+        # get here either with a user API key or a query one, we can just check whether it's\n+        # an api key (meaning this is a query API key, which only grants read access).\n+        if self.current_user.is_api_user():\n+            abort(403, message=\"Please use a user API key.\")\n+\n         query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n         require_access(query.groups, self.current_user, not_view_only)\n", "issue": "AttributeError: 'ApiUser' object has no attribute 'email'\n### Issue Summary\r\n\r\nAPI POST calls to /api/queries/###/refresh throw this error on 1.0.3+b2850\r\n\r\n### Steps to Reproduce\r\n\r\n1. Make a POST call to /api/queries/###/refresh\r\n2. See error in api_error.log and 500 result\r\n\r\nFull error with trace:\r\n\r\n```\r\nException on /api/queries/###/refresh [POST]\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python2.7/dist-packages/flask/app.py\", line 1639, in full_dispatch_request\r\n    rv = self.dispatch_request()\r\n  File \"/usr/local/lib/python2.7/dist-packages/flask/app.py\", line 1625, in dispatch_request\r\n    return self.view_functions[rule.endpoint](**req.view_args)\r\n  File \"/usr/local/lib/python2.7/dist-packages/flask_restful/__init__.py\", line 477, in wrapper\r\n    resp = resource(*args, **kwargs)\r\n  File \"/usr/local/lib/python2.7/dist-packages/flask_login/utils.py\", line 228, in decorated_view\r\n    return func(*args, **kwargs)\r\n  File \"/usr/local/lib/python2.7/dist-packages/flask/views.py\", line 84, in view\r\n    return self.dispatch_request(*args, **kwargs)\r\n  File \"/opt/redash/redash.1.0.3.b2850/redash/handlers/base.py\", line 28, in dispatch_request\r\n    return super(BaseResource, self).dispatch_request(*args, **kwargs)\r\n  File \"/usr/local/lib/python2.7/dist-packages/flask_restful/__init__.py\", line 587, in dispatch_request\r\n    resp = meth(*args, **kwargs)\r\n  File \"/opt/redash/redash.1.0.3.b2850/redash/handlers/queries.py\", line 266, in post\r\n    return run_query(query.data_source, parameter_values, query.query_text, query.id)\r\n  File \"/opt/redash/redash.1.0.3.b2850/redash/handlers/query_results.py\", line 45, in run_query\r\n    job = enqueue_query(query_text, data_source, current_user.id, metadata={\"Username\": current_user.email, \"Query ID\": query_id})\r\n  File \"/usr/local/lib/python2.7/dist-packages/werkzeug/local.py\", line 343, in __getattr__\r\n    return getattr(self._get_current_object(), name)\r\nAttributeError: 'ApiUser' object has no attribute 'email'\r\n```\r\n\r\nSame request worked in some previous version of Redash. Looking at the current code on master, it seems to still make the same reference to `current_user.email` so assuming this bug still exists, but haven't confirmed that yet.\r\n\r\n### Technical details:\r\n\r\n* Redash Version: 1.0.3+b2850\r\n* Browser/OS: N/A\r\n* How did you install Redash: N/A\r\n\n", "before_files": [{"content": "from itertools import chain\n\nimport sqlparse\nfrom flask import jsonify, request\nfrom flask_login import login_required\nfrom flask_restful import abort\nfrom funcy import distinct, take\nfrom sqlalchemy.orm.exc import StaleDataError\n\nfrom redash import models, settings\nfrom redash.handlers.base import (BaseResource, get_object_or_404,\n                                  org_scoped_rule, paginate, routes)\nfrom redash.handlers.query_results import run_query\nfrom redash.permissions import (can_modify, not_view_only, require_access,\n                                require_admin_or_owner,\n                                require_object_modify_permission,\n                                require_permission, view_only)\nfrom redash.utils import collect_parameters_from_request\n\n\n@routes.route(org_scoped_rule('/api/queries/format'), methods=['POST'])\n@login_required\ndef format_sql_query(org_slug=None):\n    \"\"\"\n    Formats an SQL query using the Python ``sqlparse`` formatter.\n\n    :<json string query: The SQL text to format\n    :>json string query: Formatted SQL text\n    \"\"\"\n    arguments = request.get_json(force=True)\n    query = arguments.get(\"query\", \"\")\n\n    return jsonify({'query': sqlparse.format(query, reindent=True, keyword_case='upper')})\n\n\nclass QuerySearchResource(BaseResource):\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Search query text, titles, and descriptions.\n\n        :qparam string q: Search term\n\n        Responds with a list of :ref:`query <query-response-label>` objects.\n        \"\"\"\n        term = request.args.get('q', '')\n        include_drafts = request.args.get('include_drafts') is not None\n\n        return [q.to_dict(with_last_modified_by=False) for q in models.Query.search(term, self.current_user.group_ids, include_drafts=include_drafts)]\n\n\nclass QueryRecentResource(BaseResource):\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Retrieve up to 20 queries modified in the last 7 days.\n\n        Responds with a list of :ref:`query <query-response-label>` objects.\n        \"\"\"\n\n        if settings.FEATURE_DUMB_RECENTS:\n            results = models.Query.by_user(self.current_user).order_by(models.Query.updated_at.desc()).limit(10)\n            queries = [q.to_dict(with_last_modified_by=False, with_user=False) for q in results]\n        else:\n            queries = models.Query.recent(self.current_user.group_ids, self.current_user.id)\n            recent = [d.to_dict(with_last_modified_by=False, with_user=False) for d in queries]\n\n            global_recent = []\n            if len(recent) < 10:\n                global_recent = [d.to_dict(with_last_modified_by=False, with_user=False) for d in models.Query.recent(self.current_user.group_ids)]\n\n            queries = take(20, distinct(chain(recent, global_recent), key=lambda d: d['id']))\n\n        return queries\n\n\nclass QueryListResource(BaseResource):\n    @require_permission('create_query')\n    def post(self):\n        \"\"\"\n        Create a new query.\n\n        :<json number data_source_id: The ID of the data source this query will run on\n        :<json string query: Query text\n        :<json string name:\n        :<json string description:\n        :<json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :<json object options: Query options\n\n        .. _query-response-label:\n\n        :>json number id: Query ID\n        :>json number latest_query_data_id: ID for latest output data from this query\n        :>json string name:\n        :>json string description:\n        :>json string query: Query text\n        :>json string query_hash: Hash of query text\n        :>json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :>json string api_key: Key for public access to this query's results.\n        :>json boolean is_archived: Whether this query is displayed in indexes and search results or not.\n        :>json boolean is_draft: Whether this query is a draft or not\n        :>json string updated_at: Time of last modification, in ISO format\n        :>json string created_at: Time of creation, in ISO format\n        :>json number data_source_id: ID of the data source this query will run on\n        :>json object options: Query options\n        :>json number version: Revision version (for update conflict avoidance)\n        :>json number user_id: ID of query creator\n        :>json number last_modified_by_id: ID of user who last modified this query\n        :>json string retrieved_at: Time when query results were last retrieved, in ISO format (may be null)\n        :>json number runtime: Runtime of last query execution, in seconds (may be null)\n        \"\"\"\n        query_def = request.get_json(force=True)\n        data_source = models.DataSource.get_by_id_and_org(query_def.pop('data_source_id'), self.current_org)\n        require_access(data_source.groups, self.current_user, not_view_only)\n\n        for field in ['id', 'created_at', 'api_key', 'visualizations', 'latest_query_data', 'last_modified_by']:\n            query_def.pop(field, None)\n\n        query_def['query_text'] = query_def.pop('query')\n        query_def['user'] = self.current_user\n        query_def['data_source'] = data_source\n        query_def['org'] = self.current_org\n        query_def['is_draft'] = True\n        query = models.Query.create(**query_def)\n        models.db.session.add(query)\n        models.db.session.commit()\n\n        self.record_event({\n            'action': 'create',\n            'object_id': query.id,\n            'object_type': 'query'\n        })\n\n        return query.to_dict()\n\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Retrieve a list of queries.\n\n        :qparam number page_size: Number of queries to return\n        :qparam number page: Page number to retrieve\n\n        Responds with an array of :ref:`query <query-response-label>` objects.\n        \"\"\"\n\n        results = models.Query.all_queries(self.current_user.group_ids, self.current_user.id)\n        page = request.args.get('page', 1, type=int)\n        page_size = request.args.get('page_size', 25, type=int)\n        return paginate(results, page, page_size, lambda q: q.to_dict(with_stats=True, with_last_modified_by=False))\n\n\nclass MyQueriesResource(BaseResource):\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Retrieve a list of queries created by the current user.\n\n        :qparam number page_size: Number of queries to return\n        :qparam number page: Page number to retrieve\n\n        Responds with an array of :ref:`query <query-response-label>` objects.\n        \"\"\"\n        drafts = request.args.get('drafts') is not None\n        results = models.Query.by_user(self.current_user)\n        page = request.args.get('page', 1, type=int)\n        page_size = request.args.get('page_size', 25, type=int)\n        return paginate(results, page, page_size, lambda q: q.to_dict(with_stats=True, with_last_modified_by=False))\n\n\nclass QueryResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self, query_id):\n        \"\"\"\n        Modify a query.\n\n        :param query_id: ID of query to update\n        :<json number data_source_id: The ID of the data source this query will run on\n        :<json string query: Query text\n        :<json string name:\n        :<json string description:\n        :<json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :<json object options: Query options\n\n        Responds with the updated :ref:`query <query-response-label>` object.\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        query_def = request.get_json(force=True)\n\n        require_object_modify_permission(query, self.current_user)\n\n        for field in ['id', 'created_at', 'api_key', 'visualizations', 'latest_query_data', 'user', 'last_modified_by', 'org']:\n            query_def.pop(field, None)\n\n        if 'query' in query_def:\n            query_def['query_text'] = query_def.pop('query')\n\n        query_def['last_modified_by'] = self.current_user\n        query_def['changed_by'] = self.current_user\n        # SQLAlchemy handles the case where a concurrent transaction beats us\n        # to the update. But we still have to make sure that we're not starting\n        # out behind.\n        if 'version' in query_def and query_def['version'] != query.version:\n            abort(409)\n\n        try:\n            self.update_model(query, query_def)\n            models.db.session.commit()\n        except StaleDataError:\n            abort(409)\n\n        return query.to_dict(with_visualizations=True)\n\n    @require_permission('view_query')\n    def get(self, query_id):\n        \"\"\"\n        Retrieve a query.\n\n        :param query_id: ID of query to fetch\n\n        Responds with the :ref:`query <query-response-label>` contents.\n        \"\"\"\n        q = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(q.groups, self.current_user, view_only)\n\n        result = q.to_dict(with_visualizations=True)\n        result['can_edit'] = can_modify(q, self.current_user)\n        return result\n\n    # TODO: move to resource of its own? (POST /queries/{id}/archive)\n    def delete(self, query_id):\n        \"\"\"\n        Archives a query.\n\n        :param query_id: ID of query to archive\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_admin_or_owner(query.user_id)\n        query.archive(self.current_user)\n        models.db.session.commit()\n\n\nclass QueryForkResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self, query_id):\n        \"\"\"\n        Creates a new query, copying the query text from an existing one.\n\n        :param query_id: ID of query to fork\n\n        Responds with created :ref:`query <query-response-label>` object.\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query.data_source.groups, self.current_user, not_view_only)\n        forked_query = query.fork(self.current_user)\n        models.db.session.commit()\n        return forked_query.to_dict(with_visualizations=True)\n\n\nclass QueryRefreshResource(BaseResource):\n    def post(self, query_id):\n        \"\"\"\n        Execute a query, updating the query object with the results.\n\n        :param query_id: ID of query to execute\n\n        Responds with query task details.\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query.groups, self.current_user, not_view_only)\n\n        parameter_values = collect_parameters_from_request(request.args)\n\n        return run_query(query.data_source, parameter_values, query.query_text, query.id)\n", "path": "redash/handlers/queries.py"}], "after_files": [{"content": "from itertools import chain\n\nimport sqlparse\nfrom flask import jsonify, request\nfrom flask_login import login_required\nfrom flask_restful import abort\nfrom funcy import distinct, take\nfrom sqlalchemy.orm.exc import StaleDataError\n\nfrom redash import models, settings\nfrom redash.handlers.base import (BaseResource, get_object_or_404,\n                                  org_scoped_rule, paginate, routes)\nfrom redash.handlers.query_results import run_query\nfrom redash.permissions import (can_modify, not_view_only, require_access,\n                                require_admin_or_owner,\n                                require_object_modify_permission,\n                                require_permission, view_only)\nfrom redash.utils import collect_parameters_from_request\n\n\n@routes.route(org_scoped_rule('/api/queries/format'), methods=['POST'])\n@login_required\ndef format_sql_query(org_slug=None):\n    \"\"\"\n    Formats an SQL query using the Python ``sqlparse`` formatter.\n\n    :<json string query: The SQL text to format\n    :>json string query: Formatted SQL text\n    \"\"\"\n    arguments = request.get_json(force=True)\n    query = arguments.get(\"query\", \"\")\n\n    return jsonify({'query': sqlparse.format(query, reindent=True, keyword_case='upper')})\n\n\nclass QuerySearchResource(BaseResource):\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Search query text, titles, and descriptions.\n\n        :qparam string q: Search term\n\n        Responds with a list of :ref:`query <query-response-label>` objects.\n        \"\"\"\n        term = request.args.get('q', '')\n        include_drafts = request.args.get('include_drafts') is not None\n\n        return [q.to_dict(with_last_modified_by=False) for q in models.Query.search(term, self.current_user.group_ids, include_drafts=include_drafts)]\n\n\nclass QueryRecentResource(BaseResource):\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Retrieve up to 20 queries modified in the last 7 days.\n\n        Responds with a list of :ref:`query <query-response-label>` objects.\n        \"\"\"\n\n        if settings.FEATURE_DUMB_RECENTS:\n            results = models.Query.by_user(self.current_user).order_by(models.Query.updated_at.desc()).limit(10)\n            queries = [q.to_dict(with_last_modified_by=False, with_user=False) for q in results]\n        else:\n            queries = models.Query.recent(self.current_user.group_ids, self.current_user.id)\n            recent = [d.to_dict(with_last_modified_by=False, with_user=False) for d in queries]\n\n            global_recent = []\n            if len(recent) < 10:\n                global_recent = [d.to_dict(with_last_modified_by=False, with_user=False) for d in models.Query.recent(self.current_user.group_ids)]\n\n            queries = take(20, distinct(chain(recent, global_recent), key=lambda d: d['id']))\n\n        return queries\n\n\nclass QueryListResource(BaseResource):\n    @require_permission('create_query')\n    def post(self):\n        \"\"\"\n        Create a new query.\n\n        :<json number data_source_id: The ID of the data source this query will run on\n        :<json string query: Query text\n        :<json string name:\n        :<json string description:\n        :<json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :<json object options: Query options\n\n        .. _query-response-label:\n\n        :>json number id: Query ID\n        :>json number latest_query_data_id: ID for latest output data from this query\n        :>json string name:\n        :>json string description:\n        :>json string query: Query text\n        :>json string query_hash: Hash of query text\n        :>json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :>json string api_key: Key for public access to this query's results.\n        :>json boolean is_archived: Whether this query is displayed in indexes and search results or not.\n        :>json boolean is_draft: Whether this query is a draft or not\n        :>json string updated_at: Time of last modification, in ISO format\n        :>json string created_at: Time of creation, in ISO format\n        :>json number data_source_id: ID of the data source this query will run on\n        :>json object options: Query options\n        :>json number version: Revision version (for update conflict avoidance)\n        :>json number user_id: ID of query creator\n        :>json number last_modified_by_id: ID of user who last modified this query\n        :>json string retrieved_at: Time when query results were last retrieved, in ISO format (may be null)\n        :>json number runtime: Runtime of last query execution, in seconds (may be null)\n        \"\"\"\n        query_def = request.get_json(force=True)\n        data_source = models.DataSource.get_by_id_and_org(query_def.pop('data_source_id'), self.current_org)\n        require_access(data_source.groups, self.current_user, not_view_only)\n\n        for field in ['id', 'created_at', 'api_key', 'visualizations', 'latest_query_data', 'last_modified_by']:\n            query_def.pop(field, None)\n\n        query_def['query_text'] = query_def.pop('query')\n        query_def['user'] = self.current_user\n        query_def['data_source'] = data_source\n        query_def['org'] = self.current_org\n        query_def['is_draft'] = True\n        query = models.Query.create(**query_def)\n        models.db.session.add(query)\n        models.db.session.commit()\n\n        self.record_event({\n            'action': 'create',\n            'object_id': query.id,\n            'object_type': 'query'\n        })\n\n        return query.to_dict()\n\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Retrieve a list of queries.\n\n        :qparam number page_size: Number of queries to return\n        :qparam number page: Page number to retrieve\n\n        Responds with an array of :ref:`query <query-response-label>` objects.\n        \"\"\"\n\n        results = models.Query.all_queries(self.current_user.group_ids, self.current_user.id)\n        page = request.args.get('page', 1, type=int)\n        page_size = request.args.get('page_size', 25, type=int)\n        return paginate(results, page, page_size, lambda q: q.to_dict(with_stats=True, with_last_modified_by=False))\n\n\nclass MyQueriesResource(BaseResource):\n    @require_permission('view_query')\n    def get(self):\n        \"\"\"\n        Retrieve a list of queries created by the current user.\n\n        :qparam number page_size: Number of queries to return\n        :qparam number page: Page number to retrieve\n\n        Responds with an array of :ref:`query <query-response-label>` objects.\n        \"\"\"\n        drafts = request.args.get('drafts') is not None\n        results = models.Query.by_user(self.current_user)\n        page = request.args.get('page', 1, type=int)\n        page_size = request.args.get('page_size', 25, type=int)\n        return paginate(results, page, page_size, lambda q: q.to_dict(with_stats=True, with_last_modified_by=False))\n\n\nclass QueryResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self, query_id):\n        \"\"\"\n        Modify a query.\n\n        :param query_id: ID of query to update\n        :<json number data_source_id: The ID of the data source this query will run on\n        :<json string query: Query text\n        :<json string name:\n        :<json string description:\n        :<json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :<json object options: Query options\n\n        Responds with the updated :ref:`query <query-response-label>` object.\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        query_def = request.get_json(force=True)\n\n        require_object_modify_permission(query, self.current_user)\n\n        for field in ['id', 'created_at', 'api_key', 'visualizations', 'latest_query_data', 'user', 'last_modified_by', 'org']:\n            query_def.pop(field, None)\n\n        if 'query' in query_def:\n            query_def['query_text'] = query_def.pop('query')\n\n        query_def['last_modified_by'] = self.current_user\n        query_def['changed_by'] = self.current_user\n        # SQLAlchemy handles the case where a concurrent transaction beats us\n        # to the update. But we still have to make sure that we're not starting\n        # out behind.\n        if 'version' in query_def and query_def['version'] != query.version:\n            abort(409)\n\n        try:\n            self.update_model(query, query_def)\n            models.db.session.commit()\n        except StaleDataError:\n            abort(409)\n\n        return query.to_dict(with_visualizations=True)\n\n    @require_permission('view_query')\n    def get(self, query_id):\n        \"\"\"\n        Retrieve a query.\n\n        :param query_id: ID of query to fetch\n\n        Responds with the :ref:`query <query-response-label>` contents.\n        \"\"\"\n        q = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(q.groups, self.current_user, view_only)\n\n        result = q.to_dict(with_visualizations=True)\n        result['can_edit'] = can_modify(q, self.current_user)\n        return result\n\n    # TODO: move to resource of its own? (POST /queries/{id}/archive)\n    def delete(self, query_id):\n        \"\"\"\n        Archives a query.\n\n        :param query_id: ID of query to archive\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_admin_or_owner(query.user_id)\n        query.archive(self.current_user)\n        models.db.session.commit()\n\n\nclass QueryForkResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self, query_id):\n        \"\"\"\n        Creates a new query, copying the query text from an existing one.\n\n        :param query_id: ID of query to fork\n\n        Responds with created :ref:`query <query-response-label>` object.\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query.data_source.groups, self.current_user, not_view_only)\n        forked_query = query.fork(self.current_user)\n        models.db.session.commit()\n        return forked_query.to_dict(with_visualizations=True)\n\n\nclass QueryRefreshResource(BaseResource):\n    def post(self, query_id):\n        \"\"\"\n        Execute a query, updating the query object with the results.\n\n        :param query_id: ID of query to execute\n\n        Responds with query task details.\n        \"\"\"\n        # TODO: this should actually check for permissions, but because currently you can only\n        # get here either with a user API key or a query one, we can just check whether it's\n        # an api key (meaning this is a query API key, which only grants read access).\n        if self.current_user.is_api_user():\n            abort(403, message=\"Please use a user API key.\")\n\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query.groups, self.current_user, not_view_only)\n\n        parameter_values = collect_parameters_from_request(request.args)\n\n        return run_query(query.data_source, parameter_values, query.query_text, query.id)\n", "path": "redash/handlers/queries.py"}]}