prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Share access permissions for add/remove widgets
  Hi @arikfr ,
  
  in our PR #1113 we focused primarily on sharing access permissions for queries, and for dashboards we currently only allow to modify basic info of the dashboard. What is still missing is ability to allow other users to add and remove widgets. Is that something you are planning to add, or should we give it a shot?
  
  Another thing that came up - we're currently enforcing `require_admin_or_owner(...)` for managing access permissions (e.g., https://github.com/getredash/redash/blob/master/redash/handlers/permissions.py#L42). This is actually a very restrictive limitation, and we believe that anybody with permissions (owner, admin, other permitted users) should be able to add/remove users. For instance, if you consider person A creating a dashboard, then giving access to persons B and C who are actively maintaining that dashboard. Then, if person A leaves the company, B and C would not be able to make the required changes to add another person D. What do you think?
  
  /cc @rohanpd
  
  </issue>
  <code>
  [start of redash/handlers/widgets.py]
  1 import json
  2 
  3 from flask import request
  4 
  5 from redash import models
  6 from redash.permissions import require_permission, require_admin_or_owner, require_access, view_only
  7 from redash.handlers.base import BaseResource
  8 
  9 
  10 class WidgetListResource(BaseResource):
  11     @require_permission('edit_dashboard')
  12     def post(self):
  13         widget_properties = request.get_json(force=True)
  14         dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)
  15         require_admin_or_owner(dashboard.user_id)
  16 
  17         widget_properties['options'] = json.dumps(widget_properties['options'])
  18         widget_properties.pop('id', None)
  19         widget_properties['dashboard'] = dashboard
  20 
  21         visualization_id = widget_properties.pop('visualization_id')
  22         if visualization_id:
  23             visualization = models.Visualization.get_by_id_and_org(visualization_id, self.current_org)
  24             require_access(visualization.query.groups, self.current_user, view_only)
  25         else:
  26             visualization = None
  27 
  28         widget_properties['visualization'] = visualization
  29 
  30         widget = models.Widget.create(**widget_properties)
  31 
  32         layout = json.loads(widget.dashboard.layout)
  33         new_row = True
  34 
  35         if len(layout) == 0 or widget.width == 2:
  36             layout.append([widget.id])
  37         elif len(layout[-1]) == 1:
  38             neighbour_widget = models.Widget.get(models.Widget.id == layout[-1][0])
  39             if neighbour_widget.width == 1:
  40                 layout[-1].append(widget.id)
  41                 new_row = False
  42             else:
  43                 layout.append([widget.id])
  44         else:
  45             layout.append([widget.id])
  46 
  47         widget.dashboard.layout = json.dumps(layout)
  48         widget.dashboard.save()
  49 
  50         return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}
  51 
  52 
  53 class WidgetResource(BaseResource):
  54     @require_permission('edit_dashboard')
  55     def post(self, widget_id):
  56         # This method currently handles Text Box widgets only.
  57         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
  58         require_admin_or_owner(widget.dashboard.user_id)
  59         widget_properties = request.get_json(force=True)
  60         widget.text = widget_properties['text']
  61         widget.save()
  62 
  63         return widget.to_dict()
  64 
  65     @require_permission('edit_dashboard')
  66     def delete(self, widget_id):
  67         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
  68         require_admin_or_owner(widget.dashboard.user_id)
  69         widget.delete_instance()
  70 
  71         return {'layout': widget.dashboard.layout}
  72
  [end of redash/handlers/widgets.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/redash/handlers/widgets.py b/redash/handlers/widgets.py
  --- a/redash/handlers/widgets.py
  +++ b/redash/handlers/widgets.py
  @@ -1,10 +1,11 @@
   import json
   
   from flask import request
  -
   from redash import models
  -from redash.permissions import require_permission, require_admin_or_owner, require_access, view_only
   from redash.handlers.base import BaseResource
  +from redash.permissions import (require_access,
  +                                require_object_modify_permission,
  +                                require_permission, view_only)
   
   
   class WidgetListResource(BaseResource):
  @@ -12,7 +13,7 @@
       def post(self):
           widget_properties = request.get_json(force=True)
           dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)
  -        require_admin_or_owner(dashboard.user_id)
  +        require_object_modify_permission(dashboard, self.current_user)
   
           widget_properties['options'] = json.dumps(widget_properties['options'])
           widget_properties.pop('id', None)
  @@ -47,7 +48,7 @@
           widget.dashboard.layout = json.dumps(layout)
           widget.dashboard.save()
   
  -        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}
  +        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row, 'version': dashboard.version}
   
   
   class WidgetResource(BaseResource):
  @@ -55,7 +56,7 @@
       def post(self, widget_id):
           # This method currently handles Text Box widgets only.
           widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
  -        require_admin_or_owner(widget.dashboard.user_id)
  +        require_object_modify_permission(widget.dashboard, self.current_user)
           widget_properties = request.get_json(force=True)
           widget.text = widget_properties['text']
           widget.save()
  @@ -65,7 +66,7 @@
       @require_permission('edit_dashboard')
       def delete(self, widget_id):
           widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
  -        require_admin_or_owner(widget.dashboard.user_id)
  +        require_object_modify_permission(widget.dashboard, self.current_user)
           widget.delete_instance()
   
  -        return {'layout': widget.dashboard.layout}
  +        return {'layout': widget.dashboard.layout, 'version': widget.dashboard.version}
  
verification_info: {"golden_diff": "diff --git a/redash/handlers/widgets.py b/redash/handlers/widgets.py\n--- a/redash/handlers/widgets.py\n+++ b/redash/handlers/widgets.py\n@@ -1,10 +1,11 @@\n import json\n \n from flask import request\n-\n from redash import models\n-from redash.permissions import require_permission, require_admin_or_owner, require_access, view_only\n from redash.handlers.base import BaseResource\n+from redash.permissions import (require_access,\n+                                require_object_modify_permission,\n+                                require_permission, view_only)\n \n \n class WidgetListResource(BaseResource):\n@@ -12,7 +13,7 @@\n     def post(self):\n         widget_properties = request.get_json(force=True)\n         dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)\n-        require_admin_or_owner(dashboard.user_id)\n+        require_object_modify_permission(dashboard, self.current_user)\n \n         widget_properties['options'] = json.dumps(widget_properties['options'])\n         widget_properties.pop('id', None)\n@@ -47,7 +48,7 @@\n         widget.dashboard.layout = json.dumps(layout)\n         widget.dashboard.save()\n \n-        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}\n+        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row, 'version': dashboard.version}\n \n \n class WidgetResource(BaseResource):\n@@ -55,7 +56,7 @@\n     def post(self, widget_id):\n         # This method currently handles Text Box widgets only.\n         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n-        require_admin_or_owner(widget.dashboard.user_id)\n+        require_object_modify_permission(widget.dashboard, self.current_user)\n         widget_properties = request.get_json(force=True)\n         widget.text = widget_properties['text']\n         widget.save()\n@@ -65,7 +66,7 @@\n     @require_permission('edit_dashboard')\n     def delete(self, widget_id):\n         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n-        require_admin_or_owner(widget.dashboard.user_id)\n+        require_object_modify_permission(widget.dashboard, self.current_user)\n         widget.delete_instance()\n \n-        return {'layout': widget.dashboard.layout}\n+        return {'layout': widget.dashboard.layout, 'version': widget.dashboard.version}\n", "issue": "Share access permissions for add/remove widgets\nHi @arikfr ,\r\n\r\nin our PR #1113 we focused primarily on sharing access permissions for queries, and for dashboards we currently only allow to modify basic info of the dashboard. What is still missing is ability to allow other users to add and remove widgets. Is that something you are planning to add, or should we give it a shot?\r\n\r\nAnother thing that came up - we're currently enforcing `require_admin_or_owner(...)` for managing access permissions (e.g., https://github.com/getredash/redash/blob/master/redash/handlers/permissions.py#L42). This is actually a very restrictive limitation, and we believe that anybody with permissions (owner, admin, other permitted users) should be able to add/remove users. For instance, if you consider person A creating a dashboard, then giving access to persons B and C who are actively maintaining that dashboard. Then, if person A leaves the company, B and C would not be able to make the required changes to add another person D. What do you think?\r\n\r\n/cc @rohanpd\n", "before_files": [{"content": "import json\n\nfrom flask import request\n\nfrom redash import models\nfrom redash.permissions import require_permission, require_admin_or_owner, require_access, view_only\nfrom redash.handlers.base import BaseResource\n\n\nclass WidgetListResource(BaseResource):\n    @require_permission('edit_dashboard')\n    def post(self):\n        widget_properties = request.get_json(force=True)\n        dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n\n        widget_properties['options'] = json.dumps(widget_properties['options'])\n        widget_properties.pop('id', None)\n        widget_properties['dashboard'] = dashboard\n\n        visualization_id = widget_properties.pop('visualization_id')\n        if visualization_id:\n            visualization = models.Visualization.get_by_id_and_org(visualization_id, self.current_org)\n            require_access(visualization.query.groups, self.current_user, view_only)\n        else:\n            visualization = None\n\n        widget_properties['visualization'] = visualization\n\n        widget = models.Widget.create(**widget_properties)\n\n        layout = json.loads(widget.dashboard.layout)\n        new_row = True\n\n        if len(layout) == 0 or widget.width == 2:\n            layout.append([widget.id])\n        elif len(layout[-1]) == 1:\n            neighbour_widget = models.Widget.get(models.Widget.id == layout[-1][0])\n            if neighbour_widget.width == 1:\n                layout[-1].append(widget.id)\n                new_row = False\n            else:\n                layout.append([widget.id])\n        else:\n            layout.append([widget.id])\n\n        widget.dashboard.layout = json.dumps(layout)\n        widget.dashboard.save()\n\n        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}\n\n\nclass WidgetResource(BaseResource):\n    @require_permission('edit_dashboard')\n    def post(self, widget_id):\n        # This method currently handles Text Box widgets only.\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_admin_or_owner(widget.dashboard.user_id)\n        widget_properties = request.get_json(force=True)\n        widget.text = widget_properties['text']\n        widget.save()\n\n        return widget.to_dict()\n\n    @require_permission('edit_dashboard')\n    def delete(self, widget_id):\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_admin_or_owner(widget.dashboard.user_id)\n        widget.delete_instance()\n\n        return {'layout': widget.dashboard.layout}\n", "path": "redash/handlers/widgets.py"}]}