We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Make dashboard level filters a feature available to non-admins
### Issue Summary

Currently to enable dashboard level filters you have to be an administrator and you have to change a flag manually in the dashboards table. It would be good if this was just on by default or an option that users could change through the front end.

### Technical details:

* Redash Version: 1.0.3
* Browser/OS: Chrome
* How did you install Redash: Amazon via the AMI


--- 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/dashboards.py`
Content:
```
1 from itertools import chain
2 
3 from flask import request, url_for
4 from funcy import distinct, project, take
5 
6 from flask_restful import abort
7 from redash import models, serializers, settings
8 from redash.handlers.base import BaseResource, get_object_or_404
9 from redash.permissions import (can_modify, require_admin_or_owner,
10                                 require_object_modify_permission,
11                                 require_permission)
12 from sqlalchemy.orm.exc import StaleDataError
13 
14 
15 class RecentDashboardsResource(BaseResource):
16     @require_permission('list_dashboards')
17     def get(self):
18         """
19         Lists dashboards modified in the last 7 days.
20         """
21         if settings.FEATURE_DUMB_RECENTS:
22             dashboards = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id).order_by(models.Dashboard.updated_at.desc()).limit(10)
23             dashboards = [d.to_dict() for d in dashboards]
24         else:
25             recent = [d.to_dict() for d in models.Dashboard.recent(self.current_org, self.current_user.group_ids, self.current_user.id, for_user=True)]
26 
27             global_recent = []
28             if len(recent) < 10:
29                 global_recent = [d.to_dict() for d in models.Dashboard.recent(self.current_org, self.current_user.group_ids, self.current_user.id)]
30 
31             dashboards = take(20, distinct(chain(recent, global_recent), key=lambda d: d['id']))
32 
33         return dashboards
34 
35 
36 class DashboardListResource(BaseResource):
37     @require_permission('list_dashboards')
38     def get(self):
39         """
40         Lists all accessible dashboards.
41         """
42         results = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id)
43         return [q.to_dict() for q in results]
44 
45     @require_permission('create_dashboard')
46     def post(self):
47         """
48         Creates a new dashboard.
49 
50         :<json string name: Dashboard name
51 
52         Responds with a :ref:`dashboard <dashboard-response-label>`.
53         """
54         dashboard_properties = request.get_json(force=True)
55         dashboard = models.Dashboard(name=dashboard_properties['name'],
56                                      org=self.current_org,
57                                      user=self.current_user,
58                                      is_draft=True,
59                                      layout='[]')
60         models.db.session.add(dashboard)
61         models.db.session.commit()
62         return dashboard.to_dict()
63 
64 
65 class DashboardResource(BaseResource):
66     @require_permission('list_dashboards')
67     def get(self, dashboard_slug=None):
68         """
69         Retrieves a dashboard.
70 
71         :qparam string slug: Slug of dashboard to retrieve.
72 
73         .. _dashboard-response-label:
74 
75         :>json number id: Dashboard ID
76         :>json string name:
77         :>json string slug:
78         :>json number user_id: ID of the dashboard creator
79         :>json string created_at: ISO format timestamp for dashboard creation
80         :>json string updated_at: ISO format timestamp for last dashboard modification
81         :>json number version: Revision number of dashboard
82         :>json boolean dashboard_filters_enabled: Whether filters are enabled or not
83         :>json boolean is_archived: Whether this dashboard has been removed from the index or not
84         :>json boolean is_draft: Whether this dashboard is a draft or not.
85         :>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in
86         :>json array widgets: Array of arrays containing :ref:`widget <widget-response-label>` data
87 
88         .. _widget-response-label:
89 
90         Widget structure:
91 
92         :>json number widget.id: Widget ID
93         :>json number widget.width: Widget size
94         :>json object widget.options: Widget options
95         :>json number widget.dashboard_id: ID of dashboard containing this widget
96         :>json string widget.text: Widget contents, if this is a text-box widget
97         :>json object widget.visualization: Widget contents, if this is a visualization widget
98         :>json string widget.created_at: ISO format timestamp for widget creation
99         :>json string widget.updated_at: ISO format timestamp for last widget modification
100         """
101         dashboard = get_object_or_404(models.Dashboard.get_by_slug_and_org, dashboard_slug, self.current_org)
102         response = dashboard.to_dict(with_widgets=True, user=self.current_user)
103 
104         api_key = models.ApiKey.get_by_object(dashboard)
105         if api_key:
106             response['public_url'] = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)
107             response['api_key'] = api_key.api_key
108 
109         response['can_edit'] = can_modify(dashboard, self.current_user)
110 
111         return response
112 
113     @require_permission('edit_dashboard')
114     def post(self, dashboard_slug):
115         """
116         Modifies a dashboard.
117 
118         :qparam string slug: Slug of dashboard to retrieve.
119 
120         Responds with the updated :ref:`dashboard <dashboard-response-label>`.
121 
122         :status 200: success
123         :status 409: Version conflict -- dashboard modified since last read
124         """
125         dashboard_properties = request.get_json(force=True)
126         # TODO: either convert all requests to use slugs or ids
127         dashboard = models.Dashboard.get_by_id_and_org(dashboard_slug, self.current_org)
128 
129         require_object_modify_permission(dashboard, self.current_user)
130 
131         updates = project(dashboard_properties, ('name', 'layout', 'version',
132                                                  'is_draft'))
133 
134         # SQLAlchemy handles the case where a concurrent transaction beats us
135         # to the update. But we still have to make sure that we're not starting
136         # out behind.
137         if 'version' in updates and updates['version'] != dashboard.version:
138             abort(409)
139 
140         updates['changed_by'] = self.current_user
141 
142         self.update_model(dashboard, updates)
143         models.db.session.add(dashboard)
144         try:
145             models.db.session.commit()
146         except StaleDataError:
147             abort(409)
148 
149         result = dashboard.to_dict(with_widgets=True, user=self.current_user)
150         return result
151 
152     @require_permission('edit_dashboard')
153     def delete(self, dashboard_slug):
154         """
155         Archives a dashboard.
156 
157         :qparam string slug: Slug of dashboard to retrieve.
158 
159         Responds with the archived :ref:`dashboard <dashboard-response-label>`.
160         """
161         dashboard = models.Dashboard.get_by_slug_and_org(dashboard_slug, self.current_org)
162         dashboard.is_archived = True
163         dashboard.record_changes(changed_by=self.current_user)
164         models.db.session.add(dashboard)
165         d = dashboard.to_dict(with_widgets=True, user=self.current_user)
166         models.db.session.commit()
167         return d
168 
169 
170 class PublicDashboardResource(BaseResource):
171     def get(self, token):
172         """
173         Retrieve a public dashboard.
174 
175         :param token: An API key for a public dashboard.
176         :>json array widgets: An array of arrays of :ref:`public widgets <public-widget-label>`, corresponding to the rows and columns the widgets are displayed in
177         """
178         if not isinstance(self.current_user, models.ApiUser):
179             api_key = get_object_or_404(models.ApiKey.get_by_api_key, token)
180             dashboard = api_key.object
181         else:
182             dashboard = self.current_user.object
183 
184         return serializers.public_dashboard(dashboard)
185 
186 
187 class DashboardShareResource(BaseResource):
188     def post(self, dashboard_id):
189         """
190         Allow anonymous access to a dashboard.
191 
192         :param dashboard_id: The numeric ID of the dashboard to share.
193         :>json string public_url: The URL for anonymous access to the dashboard.
194         :>json api_key: The API key to use when accessing it.
195         """
196         dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)
197         require_admin_or_owner(dashboard.user_id)
198         api_key = models.ApiKey.create_for_object(dashboard, self.current_user)
199         models.db.session.flush()
200         models.db.session.commit()
201 
202         public_url = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)
203 
204         self.record_event({
205             'action': 'activate_api_key',
206             'object_id': dashboard.id,
207             'object_type': 'dashboard',
208         })
209 
210         return {'public_url': public_url, 'api_key': api_key.api_key}
211 
212     def delete(self, dashboard_id):
213         """
214         Disable anonymous access to a dashboard.
215 
216         :param dashboard_id: The numeric ID of the dashboard to unshare.
217         """
218         dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)
219         require_admin_or_owner(dashboard.user_id)
220         api_key = models.ApiKey.get_by_object(dashboard)
221 
222         if api_key:
223             api_key.active = False
224             models.db.session.add(api_key)
225             models.db.session.commit()
226 
227         self.record_event({
228             'action': 'deactivate_api_key',
229             'object_id': dashboard.id,
230             'object_type': 'dashboard',
231         })
232
```
--- 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/dashboards.py b/redash/handlers/dashboards.py
--- a/redash/handlers/dashboards.py
+++ b/redash/handlers/dashboards.py
@@ -129,7 +129,7 @@
         require_object_modify_permission(dashboard, self.current_user)
 
         updates = project(dashboard_properties, ('name', 'layout', 'version',
-                                                 'is_draft'))
+                                                 'is_draft', 'dashboard_filters_enabled'))
 
         # SQLAlchemy handles the case where a concurrent transaction beats us
         # to the update. But we still have to make sure that we're not starting

{"golden_diff": "diff --git a/redash/handlers/dashboards.py b/redash/handlers/dashboards.py\n--- a/redash/handlers/dashboards.py\n+++ b/redash/handlers/dashboards.py\n@@ -129,7 +129,7 @@\n         require_object_modify_permission(dashboard, self.current_user)\n \n         updates = project(dashboard_properties, ('name', 'layout', 'version',\n-                                                 'is_draft'))\n+                                                 'is_draft', 'dashboard_filters_enabled'))\n \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", "issue": "Make dashboard level filters a feature available to non-admins\n### Issue Summary\r\n\r\nCurrently to enable dashboard level filters you have to be an administrator and you have to change a flag manually in the dashboards table. It would be good if this was just on by default or an option that users could change through the front end.\r\n\r\n### Technical details:\r\n\r\n* Redash Version: 1.0.3\r\n* Browser/OS: Chrome\r\n* How did you install Redash: Amazon via the AMI\r\n\n", "before_files": [{"content": "from itertools import chain\n\nfrom flask import request, url_for\nfrom funcy import distinct, project, take\n\nfrom flask_restful import abort\nfrom redash import models, serializers, settings\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.permissions import (can_modify, require_admin_or_owner,\n                                require_object_modify_permission,\n                                require_permission)\nfrom sqlalchemy.orm.exc import StaleDataError\n\n\nclass RecentDashboardsResource(BaseResource):\n    @require_permission('list_dashboards')\n    def get(self):\n        \"\"\"\n        Lists dashboards modified in the last 7 days.\n        \"\"\"\n        if settings.FEATURE_DUMB_RECENTS:\n            dashboards = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id).order_by(models.Dashboard.updated_at.desc()).limit(10)\n            dashboards = [d.to_dict() for d in dashboards]\n        else:\n            recent = [d.to_dict() for d in models.Dashboard.recent(self.current_org, self.current_user.group_ids, self.current_user.id, for_user=True)]\n\n            global_recent = []\n            if len(recent) < 10:\n                global_recent = [d.to_dict() for d in models.Dashboard.recent(self.current_org, self.current_user.group_ids, self.current_user.id)]\n\n            dashboards = take(20, distinct(chain(recent, global_recent), key=lambda d: d['id']))\n\n        return dashboards\n\n\nclass DashboardListResource(BaseResource):\n    @require_permission('list_dashboards')\n    def get(self):\n        \"\"\"\n        Lists all accessible dashboards.\n        \"\"\"\n        results = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id)\n        return [q.to_dict() for q in results]\n\n    @require_permission('create_dashboard')\n    def post(self):\n        \"\"\"\n        Creates a new dashboard.\n\n        :<json string name: Dashboard name\n\n        Responds with a :ref:`dashboard <dashboard-response-label>`.\n        \"\"\"\n        dashboard_properties = request.get_json(force=True)\n        dashboard = models.Dashboard(name=dashboard_properties['name'],\n                                     org=self.current_org,\n                                     user=self.current_user,\n                                     is_draft=True,\n                                     layout='[]')\n        models.db.session.add(dashboard)\n        models.db.session.commit()\n        return dashboard.to_dict()\n\n\nclass DashboardResource(BaseResource):\n    @require_permission('list_dashboards')\n    def get(self, dashboard_slug=None):\n        \"\"\"\n        Retrieves a dashboard.\n\n        :qparam string slug: Slug of dashboard to retrieve.\n\n        .. _dashboard-response-label:\n\n        :>json number id: Dashboard ID\n        :>json string name:\n        :>json string slug:\n        :>json number user_id: ID of the dashboard creator\n        :>json string created_at: ISO format timestamp for dashboard creation\n        :>json string updated_at: ISO format timestamp for last dashboard modification\n        :>json number version: Revision number of dashboard\n        :>json boolean dashboard_filters_enabled: Whether filters are enabled or not\n        :>json boolean is_archived: Whether this dashboard has been removed from the index or not\n        :>json boolean is_draft: Whether this dashboard is a draft or not.\n        :>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in\n        :>json array widgets: Array of arrays containing :ref:`widget <widget-response-label>` data\n\n        .. _widget-response-label:\n\n        Widget structure:\n\n        :>json number widget.id: Widget ID\n        :>json number widget.width: Widget size\n        :>json object widget.options: Widget options\n        :>json number widget.dashboard_id: ID of dashboard containing this widget\n        :>json string widget.text: Widget contents, if this is a text-box widget\n        :>json object widget.visualization: Widget contents, if this is a visualization widget\n        :>json string widget.created_at: ISO format timestamp for widget creation\n        :>json string widget.updated_at: ISO format timestamp for last widget modification\n        \"\"\"\n        dashboard = get_object_or_404(models.Dashboard.get_by_slug_and_org, dashboard_slug, self.current_org)\n        response = dashboard.to_dict(with_widgets=True, user=self.current_user)\n\n        api_key = models.ApiKey.get_by_object(dashboard)\n        if api_key:\n            response['public_url'] = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)\n            response['api_key'] = api_key.api_key\n\n        response['can_edit'] = can_modify(dashboard, self.current_user)\n\n        return response\n\n    @require_permission('edit_dashboard')\n    def post(self, dashboard_slug):\n        \"\"\"\n        Modifies a dashboard.\n\n        :qparam string slug: Slug of dashboard to retrieve.\n\n        Responds with the updated :ref:`dashboard <dashboard-response-label>`.\n\n        :status 200: success\n        :status 409: Version conflict -- dashboard modified since last read\n        \"\"\"\n        dashboard_properties = request.get_json(force=True)\n        # TODO: either convert all requests to use slugs or ids\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_slug, self.current_org)\n\n        require_object_modify_permission(dashboard, self.current_user)\n\n        updates = project(dashboard_properties, ('name', 'layout', 'version',\n                                                 'is_draft'))\n\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 updates and updates['version'] != dashboard.version:\n            abort(409)\n\n        updates['changed_by'] = self.current_user\n\n        self.update_model(dashboard, updates)\n        models.db.session.add(dashboard)\n        try:\n            models.db.session.commit()\n        except StaleDataError:\n            abort(409)\n\n        result = dashboard.to_dict(with_widgets=True, user=self.current_user)\n        return result\n\n    @require_permission('edit_dashboard')\n    def delete(self, dashboard_slug):\n        \"\"\"\n        Archives a dashboard.\n\n        :qparam string slug: Slug of dashboard to retrieve.\n\n        Responds with the archived :ref:`dashboard <dashboard-response-label>`.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_slug_and_org(dashboard_slug, self.current_org)\n        dashboard.is_archived = True\n        dashboard.record_changes(changed_by=self.current_user)\n        models.db.session.add(dashboard)\n        d = dashboard.to_dict(with_widgets=True, user=self.current_user)\n        models.db.session.commit()\n        return d\n\n\nclass PublicDashboardResource(BaseResource):\n    def get(self, token):\n        \"\"\"\n        Retrieve a public dashboard.\n\n        :param token: An API key for a public dashboard.\n        :>json array widgets: An array of arrays of :ref:`public widgets <public-widget-label>`, corresponding to the rows and columns the widgets are displayed in\n        \"\"\"\n        if not isinstance(self.current_user, models.ApiUser):\n            api_key = get_object_or_404(models.ApiKey.get_by_api_key, token)\n            dashboard = api_key.object\n        else:\n            dashboard = self.current_user.object\n\n        return serializers.public_dashboard(dashboard)\n\n\nclass DashboardShareResource(BaseResource):\n    def post(self, dashboard_id):\n        \"\"\"\n        Allow anonymous access to a dashboard.\n\n        :param dashboard_id: The numeric ID of the dashboard to share.\n        :>json string public_url: The URL for anonymous access to the dashboard.\n        :>json api_key: The API key to use when accessing it.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n        api_key = models.ApiKey.create_for_object(dashboard, self.current_user)\n        models.db.session.flush()\n        models.db.session.commit()\n\n        public_url = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)\n\n        self.record_event({\n            'action': 'activate_api_key',\n            'object_id': dashboard.id,\n            'object_type': 'dashboard',\n        })\n\n        return {'public_url': public_url, 'api_key': api_key.api_key}\n\n    def delete(self, dashboard_id):\n        \"\"\"\n        Disable anonymous access to a dashboard.\n\n        :param dashboard_id: The numeric ID of the dashboard to unshare.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n        api_key = models.ApiKey.get_by_object(dashboard)\n\n        if api_key:\n            api_key.active = False\n            models.db.session.add(api_key)\n            models.db.session.commit()\n\n        self.record_event({\n            'action': 'deactivate_api_key',\n            'object_id': dashboard.id,\n            'object_type': 'dashboard',\n        })\n", "path": "redash/handlers/dashboards.py"}], "after_files": [{"content": "from itertools import chain\n\nfrom flask import request, url_for\nfrom funcy import distinct, project, take\n\nfrom flask_restful import abort\nfrom redash import models, serializers, settings\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.permissions import (can_modify, require_admin_or_owner,\n                                require_object_modify_permission,\n                                require_permission)\nfrom sqlalchemy.orm.exc import StaleDataError\n\n\nclass RecentDashboardsResource(BaseResource):\n    @require_permission('list_dashboards')\n    def get(self):\n        \"\"\"\n        Lists dashboards modified in the last 7 days.\n        \"\"\"\n        if settings.FEATURE_DUMB_RECENTS:\n            dashboards = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id).order_by(models.Dashboard.updated_at.desc()).limit(10)\n            dashboards = [d.to_dict() for d in dashboards]\n        else:\n            recent = [d.to_dict() for d in models.Dashboard.recent(self.current_org, self.current_user.group_ids, self.current_user.id, for_user=True)]\n\n            global_recent = []\n            if len(recent) < 10:\n                global_recent = [d.to_dict() for d in models.Dashboard.recent(self.current_org, self.current_user.group_ids, self.current_user.id)]\n\n            dashboards = take(20, distinct(chain(recent, global_recent), key=lambda d: d['id']))\n\n        return dashboards\n\n\nclass DashboardListResource(BaseResource):\n    @require_permission('list_dashboards')\n    def get(self):\n        \"\"\"\n        Lists all accessible dashboards.\n        \"\"\"\n        results = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id)\n        return [q.to_dict() for q in results]\n\n    @require_permission('create_dashboard')\n    def post(self):\n        \"\"\"\n        Creates a new dashboard.\n\n        :<json string name: Dashboard name\n\n        Responds with a :ref:`dashboard <dashboard-response-label>`.\n        \"\"\"\n        dashboard_properties = request.get_json(force=True)\n        dashboard = models.Dashboard(name=dashboard_properties['name'],\n                                     org=self.current_org,\n                                     user=self.current_user,\n                                     is_draft=True,\n                                     layout='[]')\n        models.db.session.add(dashboard)\n        models.db.session.commit()\n        return dashboard.to_dict()\n\n\nclass DashboardResource(BaseResource):\n    @require_permission('list_dashboards')\n    def get(self, dashboard_slug=None):\n        \"\"\"\n        Retrieves a dashboard.\n\n        :qparam string slug: Slug of dashboard to retrieve.\n\n        .. _dashboard-response-label:\n\n        :>json number id: Dashboard ID\n        :>json string name:\n        :>json string slug:\n        :>json number user_id: ID of the dashboard creator\n        :>json string created_at: ISO format timestamp for dashboard creation\n        :>json string updated_at: ISO format timestamp for last dashboard modification\n        :>json number version: Revision number of dashboard\n        :>json boolean dashboard_filters_enabled: Whether filters are enabled or not\n        :>json boolean is_archived: Whether this dashboard has been removed from the index or not\n        :>json boolean is_draft: Whether this dashboard is a draft or not.\n        :>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in\n        :>json array widgets: Array of arrays containing :ref:`widget <widget-response-label>` data\n\n        .. _widget-response-label:\n\n        Widget structure:\n\n        :>json number widget.id: Widget ID\n        :>json number widget.width: Widget size\n        :>json object widget.options: Widget options\n        :>json number widget.dashboard_id: ID of dashboard containing this widget\n        :>json string widget.text: Widget contents, if this is a text-box widget\n        :>json object widget.visualization: Widget contents, if this is a visualization widget\n        :>json string widget.created_at: ISO format timestamp for widget creation\n        :>json string widget.updated_at: ISO format timestamp for last widget modification\n        \"\"\"\n        dashboard = get_object_or_404(models.Dashboard.get_by_slug_and_org, dashboard_slug, self.current_org)\n        response = dashboard.to_dict(with_widgets=True, user=self.current_user)\n\n        api_key = models.ApiKey.get_by_object(dashboard)\n        if api_key:\n            response['public_url'] = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)\n            response['api_key'] = api_key.api_key\n\n        response['can_edit'] = can_modify(dashboard, self.current_user)\n\n        return response\n\n    @require_permission('edit_dashboard')\n    def post(self, dashboard_slug):\n        \"\"\"\n        Modifies a dashboard.\n\n        :qparam string slug: Slug of dashboard to retrieve.\n\n        Responds with the updated :ref:`dashboard <dashboard-response-label>`.\n\n        :status 200: success\n        :status 409: Version conflict -- dashboard modified since last read\n        \"\"\"\n        dashboard_properties = request.get_json(force=True)\n        # TODO: either convert all requests to use slugs or ids\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_slug, self.current_org)\n\n        require_object_modify_permission(dashboard, self.current_user)\n\n        updates = project(dashboard_properties, ('name', 'layout', 'version',\n                                                 'is_draft', 'dashboard_filters_enabled'))\n\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 updates and updates['version'] != dashboard.version:\n            abort(409)\n\n        updates['changed_by'] = self.current_user\n\n        self.update_model(dashboard, updates)\n        models.db.session.add(dashboard)\n        try:\n            models.db.session.commit()\n        except StaleDataError:\n            abort(409)\n\n        result = dashboard.to_dict(with_widgets=True, user=self.current_user)\n        return result\n\n    @require_permission('edit_dashboard')\n    def delete(self, dashboard_slug):\n        \"\"\"\n        Archives a dashboard.\n\n        :qparam string slug: Slug of dashboard to retrieve.\n\n        Responds with the archived :ref:`dashboard <dashboard-response-label>`.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_slug_and_org(dashboard_slug, self.current_org)\n        dashboard.is_archived = True\n        dashboard.record_changes(changed_by=self.current_user)\n        models.db.session.add(dashboard)\n        d = dashboard.to_dict(with_widgets=True, user=self.current_user)\n        models.db.session.commit()\n        return d\n\n\nclass PublicDashboardResource(BaseResource):\n    def get(self, token):\n        \"\"\"\n        Retrieve a public dashboard.\n\n        :param token: An API key for a public dashboard.\n        :>json array widgets: An array of arrays of :ref:`public widgets <public-widget-label>`, corresponding to the rows and columns the widgets are displayed in\n        \"\"\"\n        if not isinstance(self.current_user, models.ApiUser):\n            api_key = get_object_or_404(models.ApiKey.get_by_api_key, token)\n            dashboard = api_key.object\n        else:\n            dashboard = self.current_user.object\n\n        return serializers.public_dashboard(dashboard)\n\n\nclass DashboardShareResource(BaseResource):\n    def post(self, dashboard_id):\n        \"\"\"\n        Allow anonymous access to a dashboard.\n\n        :param dashboard_id: The numeric ID of the dashboard to share.\n        :>json string public_url: The URL for anonymous access to the dashboard.\n        :>json api_key: The API key to use when accessing it.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n        api_key = models.ApiKey.create_for_object(dashboard, self.current_user)\n        models.db.session.flush()\n        models.db.session.commit()\n\n        public_url = url_for('redash.public_dashboard', token=api_key.api_key, org_slug=self.current_org.slug, _external=True)\n\n        self.record_event({\n            'action': 'activate_api_key',\n            'object_id': dashboard.id,\n            'object_type': 'dashboard',\n        })\n\n        return {'public_url': public_url, 'api_key': api_key.api_key}\n\n    def delete(self, dashboard_id):\n        \"\"\"\n        Disable anonymous access to a dashboard.\n\n        :param dashboard_id: The numeric ID of the dashboard to unshare.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n        api_key = models.ApiKey.get_by_object(dashboard)\n\n        if api_key:\n            api_key.active = False\n            models.db.session.add(api_key)\n            models.db.session.commit()\n\n        self.record_event({\n            'action': 'deactivate_api_key',\n            'object_id': dashboard.id,\n            'object_type': 'dashboard',\n        })\n", "path": "redash/handlers/dashboards.py"}]}