We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Goal page is not accessible once connected
"405 Method Not Allowed"
on this page 
https://liberapay.com/unisson/goal.html when i want to change my goal
I'm connected on my account.

Goal page is not accessible once connected
"405 Method Not Allowed"
on this page 
https://liberapay.com/unisson/goal.html when i want to change my goal
I'm connected on my account.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `liberapay/utils/__init__.py`
Content:
```
1 # encoding: utf8
2 
3 from __future__ import absolute_import, division, print_function, unicode_literals
4 
5 from base64 import b64decode, b64encode
6 from datetime import date, datetime, timedelta
7 import re
8 
9 from aspen import Response, json
10 from aspen.utils import to_rfc822, utcnow
11 from markupsafe import Markup
12 from postgres.cursors import SimpleCursorBase
13 
14 import liberapay
15 from liberapay.exceptions import AuthRequired
16 from liberapay.utils.i18n import Money
17 
18 
19 BEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')
20 
21 
22 def get_participant(state, restrict=True, redirect_stub=True, allow_member=False):
23     """Given a Request, raise Response or return Participant.
24 
25     If restrict is True then we'll restrict access to owners and admins.
26 
27     """
28     request = state['request']
29     user = state['user']
30     slug = request.line.uri.path['username']
31     _ = state['_']
32 
33     if restrict and user.ANON:
34         raise AuthRequired
35 
36     if slug.startswith('~'):
37         thing = 'id'
38         value = slug[1:]
39         participant = user if user and str(user.id) == value else None
40     else:
41         thing = 'lower(username)'
42         value = slug.lower()
43         participant = user if user and user.username.lower() == value else None
44 
45     if participant is None:
46         from liberapay.models.participant import Participant  # avoid circular import
47         participant = Participant._from_thing(thing, value) if value else None
48         if participant is None or participant.kind == 'community':
49             raise Response(404)
50 
51     if request.method in ('GET', 'HEAD'):
52         if slug != participant.username:
53             canon = '/' + participant.username + request.line.uri[len(slug)+1:]
54             raise Response(302, headers={'Location': canon})
55 
56     status = participant.status
57     if status == 'closed':
58         if user.is_admin:
59             return participant
60         raise Response(410)
61     elif status == 'stub':
62         if redirect_stub:
63             to = participant.resolve_stub()
64             assert to
65             raise Response(302, headers={'Location': to})
66 
67     if restrict:
68         if participant != user:
69             if allow_member and participant.kind == 'group' and user.member_of(participant):
70                 pass
71             elif not user.is_admin:
72                 raise Response(403, _("You are not authorized to access this page."))
73 
74     return participant
75 
76 
77 def b64decode_s(s, **kw):
78     udecode = lambda a: a.decode('utf8')
79     if s[:1] == b'.':
80         udecode = lambda a: a
81         s = s[1:]
82     s = s.replace(b'~', b'=')
83     try:
84         return udecode(b64decode(s, '-_'))
85     except Exception:
86         try:
87             # For retrocompatibility
88             return udecode(b64decode(s))
89         except Exception:
90             pass
91         if 'default' in kw:
92             return kw['default']
93         raise Response(400, "invalid base64 input")
94 
95 
96 def b64encode_s(s):
97     prefix = b''
98     if not isinstance(s, bytes):
99         s = s.encode('utf8')
100     else:
101         # Check whether the string is binary or already utf8
102         try:
103             s.decode('utf8')
104         except UnicodeError:
105             prefix = b'.'
106     return prefix + b64encode(s, b'-_').replace(b'=', b'~')
107 
108 
109 def update_global_stats(website):
110     website.gnusers = website.db.one("""
111         SELECT count(*)
112           FROM participants
113          WHERE status = 'active'
114            AND kind <> 'community';
115     """)
116     transfer_volume = website.db.one("""
117         SELECT coalesce(sum(amount), 0)
118           FROM current_tips
119          WHERE is_funded
120     """)
121     website.gmonthly_volume = Money(transfer_volume * 52 / 12, 'EUR')
122 
123 
124 def _execute(this, sql, params=[]):
125     print(sql.strip(), params)
126     super(SimpleCursorBase, this).execute(sql, params)
127 
128 def log_cursor(f):
129     "Prints sql and params to stdout. Works globaly so watch for threaded use."
130     def wrapper(*a, **kw):
131         try:
132             SimpleCursorBase.execute = _execute
133             ret = f(*a, **kw)
134         finally:
135             del SimpleCursorBase.execute
136         return ret
137     return wrapper
138 
139 
140 def excerpt_intro(text, length=175, append='…'):
141     if not text:
142         return ''
143     if len(text) > length:
144         return text[:length] + append
145     return text
146 
147 
148 def is_card_expired(exp_year, exp_month):
149     today = date.today()
150     cur_year, cur_month = today.year, today.month
151     return exp_year < cur_year or exp_year == cur_year and exp_month < cur_month
152 
153 
154 def set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):
155     cookies[key] = value
156     cookie = cookies[key]
157     if expires:
158         if isinstance(expires, timedelta):
159             expires += utcnow()
160         if isinstance(expires, datetime):
161             expires = to_rfc822(expires).encode('ascii')
162         cookie[b'expires'] = expires
163     if httponly:
164         cookie[b'httponly'] = True
165     if path:
166         cookie[b'path'] = path
167     if liberapay.canonical_scheme == 'https':
168         cookie[b'secure'] = True
169 
170 
171 def erase_cookie(cookies, key, **kw):
172     set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)
173 
174 
175 def filter_profile_subnav(user, participant, pages):
176     out = []
177     for foo, bar, show_them, show_others in pages:
178         if (user == participant and show_them) \
179         or (user != participant and show_others) \
180         or user.is_admin:
181             out.append((foo, bar))
182     return out
183 
184 
185 def to_javascript(obj):
186     """For when you want to inject an object into a <script> tag.
187     """
188     return json.dumps(obj).replace('</', '<\\/')
189 
190 
191 svg_attrs_re = re.compile(r'\s+(?:height|width|x|y|xmlns)=(["\']).*?\1')
192 
193 def include_svg(svg, height, width, x=None, y=None):
194     """For when you want to include an SVG in an HTML page or in another SVG.
195     """
196     assert svg.startswith('<svg')
197     i = svg.find('>')
198     assert i != -1
199     d = locals()
200     attrs = svg_attrs_re.sub('', svg[4:i])
201     for a in ('height', 'width', 'x', 'y'):
202         v = d[a]
203         if v is None:
204             continue
205         attrs += ' %s="%s"' % (a, v)
206     return Markup(svg[:4] + attrs + svg[i:])
207 
208 
209 def group_by(iterable, key):
210     r = {}
211     for obj in iterable:
212         try:
213             k = obj[key]
214         except KeyError:
215             continue
216         r.setdefault(k, []).append(obj)
217     return r
218
```
--- 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/liberapay/utils/__init__.py b/liberapay/utils/__init__.py
--- a/liberapay/utils/__init__.py
+++ b/liberapay/utils/__init__.py
@@ -6,6 +6,8 @@
 from datetime import date, datetime, timedelta
 import re
 
+from six.moves.urllib.parse import quote as urlquote
+
 from aspen import Response, json
 from aspen.utils import to_rfc822, utcnow
 from markupsafe import Markup
@@ -13,6 +15,7 @@
 
 import liberapay
 from liberapay.exceptions import AuthRequired
+from liberapay.models.community import Community
 from liberapay.utils.i18n import Money
 
 
@@ -74,6 +77,30 @@
     return participant
 
 
+def get_community(state, restrict=False):
+    request, response = state['request'], state['response']
+    user = state['user']
+    name = request.path['name']
+
+    c = Community.from_name(name)
+    if request.method in ('GET', 'HEAD'):
+        if not c:
+            response.redirect('/for/new?name=' + urlquote(name))
+        if c.name != name:
+            response.redirect('/for/' + c.name + request.line.uri[5+len(name):])
+    elif not c:
+        raise Response(404)
+
+    if restrict:
+        if user.ANON:
+            raise AuthRequired
+        if user.id != c.creator and not user.is_admin:
+            _ = state['_']
+            raise Response(403, _("You are not authorized to access this page."))
+
+    return c
+
+
 def b64decode_s(s, **kw):
     udecode = lambda a: a.decode('utf8')
     if s[:1] == b'.':

{"golden_diff": "diff --git a/liberapay/utils/__init__.py b/liberapay/utils/__init__.py\n--- a/liberapay/utils/__init__.py\n+++ b/liberapay/utils/__init__.py\n@@ -6,6 +6,8 @@\n from datetime import date, datetime, timedelta\n import re\n \n+from six.moves.urllib.parse import quote as urlquote\n+\n from aspen import Response, json\n from aspen.utils import to_rfc822, utcnow\n from markupsafe import Markup\n@@ -13,6 +15,7 @@\n \n import liberapay\n from liberapay.exceptions import AuthRequired\n+from liberapay.models.community import Community\n from liberapay.utils.i18n import Money\n \n \n@@ -74,6 +77,30 @@\n     return participant\n \n \n+def get_community(state, restrict=False):\n+    request, response = state['request'], state['response']\n+    user = state['user']\n+    name = request.path['name']\n+\n+    c = Community.from_name(name)\n+    if request.method in ('GET', 'HEAD'):\n+        if not c:\n+            response.redirect('/for/new?name=' + urlquote(name))\n+        if c.name != name:\n+            response.redirect('/for/' + c.name + request.line.uri[5+len(name):])\n+    elif not c:\n+        raise Response(404)\n+\n+    if restrict:\n+        if user.ANON:\n+            raise AuthRequired\n+        if user.id != c.creator and not user.is_admin:\n+            _ = state['_']\n+            raise Response(403, _(\"You are not authorized to access this page.\"))\n+\n+    return c\n+\n+\n def b64decode_s(s, **kw):\n     udecode = lambda a: a.decode('utf8')\n     if s[:1] == b'.':\n", "issue": "Goal page is not accessible once connected\n\"405 Method Not Allowed\"\non this page \nhttps://liberapay.com/unisson/goal.html when i want to change my goal\nI'm connected on my account.\n\nGoal page is not accessible once connected\n\"405 Method Not Allowed\"\non this page \nhttps://liberapay.com/unisson/goal.html when i want to change my goal\nI'm connected on my account.\n\n", "before_files": [{"content": "# encoding: utf8\n\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom base64 import b64decode, b64encode\nfrom datetime import date, datetime, timedelta\nimport re\n\nfrom aspen import Response, json\nfrom aspen.utils import to_rfc822, utcnow\nfrom markupsafe import Markup\nfrom postgres.cursors import SimpleCursorBase\n\nimport liberapay\nfrom liberapay.exceptions import AuthRequired\nfrom liberapay.utils.i18n import Money\n\n\nBEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')\n\n\ndef get_participant(state, restrict=True, redirect_stub=True, allow_member=False):\n    \"\"\"Given a Request, raise Response or return Participant.\n\n    If restrict is True then we'll restrict access to owners and admins.\n\n    \"\"\"\n    request = state['request']\n    user = state['user']\n    slug = request.line.uri.path['username']\n    _ = state['_']\n\n    if restrict and user.ANON:\n        raise AuthRequired\n\n    if slug.startswith('~'):\n        thing = 'id'\n        value = slug[1:]\n        participant = user if user and str(user.id) == value else None\n    else:\n        thing = 'lower(username)'\n        value = slug.lower()\n        participant = user if user and user.username.lower() == value else None\n\n    if participant is None:\n        from liberapay.models.participant import Participant  # avoid circular import\n        participant = Participant._from_thing(thing, value) if value else None\n        if participant is None or participant.kind == 'community':\n            raise Response(404)\n\n    if request.method in ('GET', 'HEAD'):\n        if slug != participant.username:\n            canon = '/' + participant.username + request.line.uri[len(slug)+1:]\n            raise Response(302, headers={'Location': canon})\n\n    status = participant.status\n    if status == 'closed':\n        if user.is_admin:\n            return participant\n        raise Response(410)\n    elif status == 'stub':\n        if redirect_stub:\n            to = participant.resolve_stub()\n            assert to\n            raise Response(302, headers={'Location': to})\n\n    if restrict:\n        if participant != user:\n            if allow_member and participant.kind == 'group' and user.member_of(participant):\n                pass\n            elif not user.is_admin:\n                raise Response(403, _(\"You are not authorized to access this page.\"))\n\n    return participant\n\n\ndef b64decode_s(s, **kw):\n    udecode = lambda a: a.decode('utf8')\n    if s[:1] == b'.':\n        udecode = lambda a: a\n        s = s[1:]\n    s = s.replace(b'~', b'=')\n    try:\n        return udecode(b64decode(s, '-_'))\n    except Exception:\n        try:\n            # For retrocompatibility\n            return udecode(b64decode(s))\n        except Exception:\n            pass\n        if 'default' in kw:\n            return kw['default']\n        raise Response(400, \"invalid base64 input\")\n\n\ndef b64encode_s(s):\n    prefix = b''\n    if not isinstance(s, bytes):\n        s = s.encode('utf8')\n    else:\n        # Check whether the string is binary or already utf8\n        try:\n            s.decode('utf8')\n        except UnicodeError:\n            prefix = b'.'\n    return prefix + b64encode(s, b'-_').replace(b'=', b'~')\n\n\ndef update_global_stats(website):\n    website.gnusers = website.db.one(\"\"\"\n        SELECT count(*)\n          FROM participants\n         WHERE status = 'active'\n           AND kind <> 'community';\n    \"\"\")\n    transfer_volume = website.db.one(\"\"\"\n        SELECT coalesce(sum(amount), 0)\n          FROM current_tips\n         WHERE is_funded\n    \"\"\")\n    website.gmonthly_volume = Money(transfer_volume * 52 / 12, 'EUR')\n\n\ndef _execute(this, sql, params=[]):\n    print(sql.strip(), params)\n    super(SimpleCursorBase, this).execute(sql, params)\n\ndef log_cursor(f):\n    \"Prints sql and params to stdout. Works globaly so watch for threaded use.\"\n    def wrapper(*a, **kw):\n        try:\n            SimpleCursorBase.execute = _execute\n            ret = f(*a, **kw)\n        finally:\n            del SimpleCursorBase.execute\n        return ret\n    return wrapper\n\n\ndef excerpt_intro(text, length=175, append='\u2026'):\n    if not text:\n        return ''\n    if len(text) > length:\n        return text[:length] + append\n    return text\n\n\ndef is_card_expired(exp_year, exp_month):\n    today = date.today()\n    cur_year, cur_month = today.year, today.month\n    return exp_year < cur_year or exp_year == cur_year and exp_month < cur_month\n\n\ndef set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):\n    cookies[key] = value\n    cookie = cookies[key]\n    if expires:\n        if isinstance(expires, timedelta):\n            expires += utcnow()\n        if isinstance(expires, datetime):\n            expires = to_rfc822(expires).encode('ascii')\n        cookie[b'expires'] = expires\n    if httponly:\n        cookie[b'httponly'] = True\n    if path:\n        cookie[b'path'] = path\n    if liberapay.canonical_scheme == 'https':\n        cookie[b'secure'] = True\n\n\ndef erase_cookie(cookies, key, **kw):\n    set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)\n\n\ndef filter_profile_subnav(user, participant, pages):\n    out = []\n    for foo, bar, show_them, show_others in pages:\n        if (user == participant and show_them) \\\n        or (user != participant and show_others) \\\n        or user.is_admin:\n            out.append((foo, bar))\n    return out\n\n\ndef to_javascript(obj):\n    \"\"\"For when you want to inject an object into a <script> tag.\n    \"\"\"\n    return json.dumps(obj).replace('</', '<\\\\/')\n\n\nsvg_attrs_re = re.compile(r'\\s+(?:height|width|x|y|xmlns)=([\"\\']).*?\\1')\n\ndef include_svg(svg, height, width, x=None, y=None):\n    \"\"\"For when you want to include an SVG in an HTML page or in another SVG.\n    \"\"\"\n    assert svg.startswith('<svg')\n    i = svg.find('>')\n    assert i != -1\n    d = locals()\n    attrs = svg_attrs_re.sub('', svg[4:i])\n    for a in ('height', 'width', 'x', 'y'):\n        v = d[a]\n        if v is None:\n            continue\n        attrs += ' %s=\"%s\"' % (a, v)\n    return Markup(svg[:4] + attrs + svg[i:])\n\n\ndef group_by(iterable, key):\n    r = {}\n    for obj in iterable:\n        try:\n            k = obj[key]\n        except KeyError:\n            continue\n        r.setdefault(k, []).append(obj)\n    return r\n", "path": "liberapay/utils/__init__.py"}], "after_files": [{"content": "# encoding: utf8\n\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom base64 import b64decode, b64encode\nfrom datetime import date, datetime, timedelta\nimport re\n\nfrom six.moves.urllib.parse import quote as urlquote\n\nfrom aspen import Response, json\nfrom aspen.utils import to_rfc822, utcnow\nfrom markupsafe import Markup\nfrom postgres.cursors import SimpleCursorBase\n\nimport liberapay\nfrom liberapay.exceptions import AuthRequired\nfrom liberapay.models.community import Community\nfrom liberapay.utils.i18n import Money\n\n\nBEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')\n\n\ndef get_participant(state, restrict=True, redirect_stub=True, allow_member=False):\n    \"\"\"Given a Request, raise Response or return Participant.\n\n    If restrict is True then we'll restrict access to owners and admins.\n\n    \"\"\"\n    request = state['request']\n    user = state['user']\n    slug = request.line.uri.path['username']\n    _ = state['_']\n\n    if restrict and user.ANON:\n        raise AuthRequired\n\n    if slug.startswith('~'):\n        thing = 'id'\n        value = slug[1:]\n        participant = user if user and str(user.id) == value else None\n    else:\n        thing = 'lower(username)'\n        value = slug.lower()\n        participant = user if user and user.username.lower() == value else None\n\n    if participant is None:\n        from liberapay.models.participant import Participant  # avoid circular import\n        participant = Participant._from_thing(thing, value) if value else None\n        if participant is None or participant.kind == 'community':\n            raise Response(404)\n\n    if request.method in ('GET', 'HEAD'):\n        if slug != participant.username:\n            canon = '/' + participant.username + request.line.uri[len(slug)+1:]\n            raise Response(302, headers={'Location': canon})\n\n    status = participant.status\n    if status == 'closed':\n        if user.is_admin:\n            return participant\n        raise Response(410)\n    elif status == 'stub':\n        if redirect_stub:\n            to = participant.resolve_stub()\n            assert to\n            raise Response(302, headers={'Location': to})\n\n    if restrict:\n        if participant != user:\n            if allow_member and participant.kind == 'group' and user.member_of(participant):\n                pass\n            elif not user.is_admin:\n                raise Response(403, _(\"You are not authorized to access this page.\"))\n\n    return participant\n\n\ndef get_community(state, restrict=False):\n    request, response = state['request'], state['response']\n    user = state['user']\n    name = request.path['name']\n\n    c = Community.from_name(name)\n    if request.method in ('GET', 'HEAD'):\n        if not c:\n            response.redirect('/for/new?name=' + urlquote(name))\n        if c.name != name:\n            response.redirect('/for/' + c.name + request.line.uri[5+len(name):])\n    elif not c:\n        raise Response(404)\n\n    if restrict:\n        if user.ANON:\n            raise AuthRequired\n        if user.id != c.creator and not user.is_admin:\n            _ = state['_']\n            raise Response(403, _(\"You are not authorized to access this page.\"))\n\n    return c\n\n\ndef b64decode_s(s, **kw):\n    udecode = lambda a: a.decode('utf8')\n    if s[:1] == b'.':\n        udecode = lambda a: a\n        s = s[1:]\n    s = s.replace(b'~', b'=')\n    try:\n        return udecode(b64decode(s, '-_'))\n    except Exception:\n        try:\n            # For retrocompatibility\n            return udecode(b64decode(s))\n        except Exception:\n            pass\n        if 'default' in kw:\n            return kw['default']\n        raise Response(400, \"invalid base64 input\")\n\n\ndef b64encode_s(s):\n    prefix = b''\n    if not isinstance(s, bytes):\n        s = s.encode('utf8')\n    else:\n        # Check whether the string is binary or already utf8\n        try:\n            s.decode('utf8')\n        except UnicodeError:\n            prefix = b'.'\n    return prefix + b64encode(s, b'-_').replace(b'=', b'~')\n\n\ndef update_global_stats(website):\n    website.gnusers = website.db.one(\"\"\"\n        SELECT count(*)\n          FROM participants\n         WHERE status = 'active'\n           AND kind <> 'community';\n    \"\"\")\n    transfer_volume = website.db.one(\"\"\"\n        SELECT coalesce(sum(amount), 0)\n          FROM current_tips\n         WHERE is_funded\n    \"\"\")\n    website.gmonthly_volume = Money(transfer_volume * 52 / 12, 'EUR')\n\n\ndef _execute(this, sql, params=[]):\n    print(sql.strip(), params)\n    super(SimpleCursorBase, this).execute(sql, params)\n\ndef log_cursor(f):\n    \"Prints sql and params to stdout. Works globaly so watch for threaded use.\"\n    def wrapper(*a, **kw):\n        try:\n            SimpleCursorBase.execute = _execute\n            ret = f(*a, **kw)\n        finally:\n            del SimpleCursorBase.execute\n        return ret\n    return wrapper\n\n\ndef excerpt_intro(text, length=175, append='\u2026'):\n    if not text:\n        return ''\n    if len(text) > length:\n        return text[:length] + append\n    return text\n\n\ndef is_card_expired(exp_year, exp_month):\n    today = date.today()\n    cur_year, cur_month = today.year, today.month\n    return exp_year < cur_year or exp_year == cur_year and exp_month < cur_month\n\n\ndef set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):\n    cookies[key] = value\n    cookie = cookies[key]\n    if expires:\n        if isinstance(expires, timedelta):\n            expires += utcnow()\n        if isinstance(expires, datetime):\n            expires = to_rfc822(expires).encode('ascii')\n        cookie[b'expires'] = expires\n    if httponly:\n        cookie[b'httponly'] = True\n    if path:\n        cookie[b'path'] = path\n    if liberapay.canonical_scheme == 'https':\n        cookie[b'secure'] = True\n\n\ndef erase_cookie(cookies, key, **kw):\n    set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)\n\n\ndef filter_profile_subnav(user, participant, pages):\n    out = []\n    for foo, bar, show_them, show_others in pages:\n        if (user == participant and show_them) \\\n        or (user != participant and show_others) \\\n        or user.is_admin:\n            out.append((foo, bar))\n    return out\n\n\ndef to_javascript(obj):\n    \"\"\"For when you want to inject an object into a <script> tag.\n    \"\"\"\n    return json.dumps(obj).replace('</', '<\\\\/')\n\n\nsvg_attrs_re = re.compile(r'\\s+(?:height|width|x|y|xmlns)=([\"\\']).*?\\1')\n\ndef include_svg(svg, height, width, x=None, y=None):\n    \"\"\"For when you want to include an SVG in an HTML page or in another SVG.\n    \"\"\"\n    assert svg.startswith('<svg')\n    i = svg.find('>')\n    assert i != -1\n    d = locals()\n    attrs = svg_attrs_re.sub('', svg[4:i])\n    for a in ('height', 'width', 'x', 'y'):\n        v = d[a]\n        if v is None:\n            continue\n        attrs += ' %s=\"%s\"' % (a, v)\n    return Markup(svg[:4] + attrs + svg[i:])\n\n\ndef group_by(iterable, key):\n    r = {}\n    for obj in iterable:\n        try:\n            k = obj[key]\n        except KeyError:\n            continue\n        r.setdefault(k, []).append(obj)\n    return r\n", "path": "liberapay/utils/__init__.py"}]}