We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DELETE /v1/accounts raises a 500
```
$ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin
HTTP/1.1 500 Internal Server Error
Access-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length
Content-Length: 177
Content-Type: application/json
Date: Mon, 28 Jan 2019 20:45:56 GMT
Via: 1.1 alproxy
X-Content-Type-Options: nosniff
```

```
  File "/home/natim/kinto/kinto/kinto/plugins/accounts/views.py", line 221, in on_account_changed
    username = request.matchdict["id"]
KeyError: 'id'
```
DELETE /v1/accounts raises a 500
```
$ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin
HTTP/1.1 500 Internal Server Error
Access-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length
Content-Length: 177
Content-Type: application/json
Date: Mon, 28 Jan 2019 20:45:56 GMT
Via: 1.1 alproxy
X-Content-Type-Options: nosniff
```

```
  File "/home/natim/kinto/kinto/kinto/plugins/accounts/views.py", line 221, in on_account_changed
    username = request.matchdict["id"]
KeyError: 'id'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kinto/plugins/accounts/views.py`
Content:
```
1 import colander
2 from pyramid import httpexceptions
3 from pyramid.decorator import reify
4 from pyramid.security import Authenticated, Everyone
5 from pyramid.settings import aslist
6 from pyramid.events import subscriber
7 
8 from kinto.views import NameGenerator
9 from kinto.core import resource, utils
10 from kinto.core.errors import raise_invalid, http_error
11 from kinto.core.events import ResourceChanged, ACTIONS
12 
13 from .utils import hash_password, ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME
14 
15 
16 def _extract_posted_body_id(request):
17     try:
18         # Anonymous creation with POST.
19         return request.json["data"]["id"]
20     except (ValueError, KeyError):
21         # Bad POST data.
22         if request.method.lower() == "post":
23             error_details = {"name": "data.id", "description": "data.id in body: Required"}
24             raise_invalid(request, **error_details)
25         # Anonymous GET
26         error_msg = "Cannot read accounts."
27         raise http_error(httpexceptions.HTTPUnauthorized(), error=error_msg)
28 
29 
30 class AccountIdGenerator(NameGenerator):
31     """Allow @ signs in account IDs."""
32 
33     regexp = r"^[a-zA-Z0-9][.@a-zA-Z0-9_-]*$"
34 
35 
36 class AccountSchema(resource.ResourceSchema):
37     password = colander.SchemaNode(colander.String())
38 
39 
40 @resource.register()
41 class Account(resource.Resource):
42 
43     schema = AccountSchema
44 
45     def __init__(self, request, context):
46         # Store if current user is administrator (before accessing get_parent_id())
47         allowed_from_settings = request.registry.settings.get("account_write_principals", [])
48         context.is_administrator = (
49             len(set(aslist(allowed_from_settings)) & set(request.prefixed_principals)) > 0
50         )
51         # Shortcut to check if current is anonymous (before get_parent_id()).
52         context.is_anonymous = Authenticated not in request.effective_principals
53 
54         super().__init__(request, context)
55 
56         # Overwrite the current principal set by Resource.
57         if self.model.current_principal == Everyone or context.is_administrator:
58             # Creation is anonymous, but author with write perm is this:
59             self.model.current_principal = f"{ACCOUNT_POLICY_NAME}:{self.model.parent_id}"
60 
61     @reify
62     def id_generator(self):
63         # This generator is used for ID validation.
64         return AccountIdGenerator()
65 
66     def get_parent_id(self, request):
67         # The whole challenge here is that we want to isolate what
68         # authenticated users can list, but give access to everything to
69         # administrators.
70         # Plus when anonymous create accounts, we have to set their parent id
71         # to the same value they would obtain when authenticated.
72         if self.context.is_administrator:
73             if self.context.on_plural_endpoint:
74                 # Accounts created by admin should have userid as parent.
75                 if request.method.lower() == "post":
76                     return _extract_posted_body_id(request)
77                 else:
78                     # Admin see all accounts.
79                     return "*"
80             else:
81                 # No pattern matching for admin on single record.
82                 return request.matchdict["id"]
83 
84         if not self.context.is_anonymous:
85             # Authenticated users see their own account only.
86             return request.selected_userid
87 
88         # Anonymous creation with PUT.
89         if "id" in request.matchdict:
90             return request.matchdict["id"]
91 
92         return _extract_posted_body_id(request)
93 
94     def plural_post(self):
95         result = super(Account, self).plural_post()
96         if self.context.is_anonymous and self.request.response.status_code == 200:
97             error_details = {"message": "Account ID %r already exists" % result["data"]["id"]}
98             raise http_error(httpexceptions.HTTPForbidden(), **error_details)
99         return result
100 
101     def process_object(self, new, old=None):
102         new = super(Account, self).process_object(new, old)
103 
104         new["password"] = hash_password(new["password"])
105 
106         # Administrators can reach other accounts and anonymous have no
107         # selected_userid. So do not try to enforce.
108         if self.context.is_administrator or self.context.is_anonymous:
109             return new
110 
111         # Do not let accounts be created without usernames.
112         if self.model.id_field not in new:
113             error_details = {"name": "data.id", "description": "Accounts must have an ID."}
114             raise_invalid(self.request, **error_details)
115 
116         # Otherwise, we force the id to match the authenticated username.
117         if new[self.model.id_field] != self.request.selected_userid:
118             error_details = {
119                 "name": "data.id",
120                 "description": "Username and account ID do not match.",
121             }
122             raise_invalid(self.request, **error_details)
123 
124         return new
125 
126 
127 # Clear cache on account change
128 @subscriber(
129     ResourceChanged, for_resources=("account",), for_actions=(ACTIONS.UPDATE, ACTIONS.DELETE)
130 )
131 def on_account_changed(event):
132     request = event.request
133     cache = request.registry.cache
134     settings = request.registry.settings
135     # Extract username and password from current user
136     username = request.matchdict["id"]
137     hmac_secret = settings["userid_hmac_secret"]
138     cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
139     # Delete cache
140     cache.delete(cache_key)
141
```
--- 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/kinto/plugins/accounts/views.py b/kinto/plugins/accounts/views.py
--- a/kinto/plugins/accounts/views.py
+++ b/kinto/plugins/accounts/views.py
@@ -132,9 +132,11 @@
     request = event.request
     cache = request.registry.cache
     settings = request.registry.settings
-    # Extract username and password from current user
-    username = request.matchdict["id"]
     hmac_secret = settings["userid_hmac_secret"]
-    cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
-    # Delete cache
-    cache.delete(cache_key)
+
+    for obj in event.impacted_objects:
+        # Extract username and password from current user
+        username = obj["old"]["id"]
+        cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))
+        # Delete cache
+        cache.delete(cache_key)

{"golden_diff": "diff --git a/kinto/plugins/accounts/views.py b/kinto/plugins/accounts/views.py\n--- a/kinto/plugins/accounts/views.py\n+++ b/kinto/plugins/accounts/views.py\n@@ -132,9 +132,11 @@\n     request = event.request\n     cache = request.registry.cache\n     settings = request.registry.settings\n-    # Extract username and password from current user\n-    username = request.matchdict[\"id\"]\n     hmac_secret = settings[\"userid_hmac_secret\"]\n-    cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n-    # Delete cache\n-    cache.delete(cache_key)\n+\n+    for obj in event.impacted_objects:\n+        # Extract username and password from current user\n+        username = obj[\"old\"][\"id\"]\n+        cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n+        # Delete cache\n+        cache.delete(cache_key)\n", "issue": "DELETE /v1/accounts raises a 500\n```\r\n$ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin\r\nHTTP/1.1 500 Internal Server Error\r\nAccess-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length\r\nContent-Length: 177\r\nContent-Type: application/json\r\nDate: Mon, 28 Jan 2019 20:45:56 GMT\r\nVia: 1.1 alproxy\r\nX-Content-Type-Options: nosniff\r\n```\r\n\r\n```\r\n  File \"/home/natim/kinto/kinto/kinto/plugins/accounts/views.py\", line 221, in on_account_changed\r\n    username = request.matchdict[\"id\"]\r\nKeyError: 'id'\r\n```\nDELETE /v1/accounts raises a 500\n```\r\n$ http DELETE https://natim.alwaysdata.net/v1/accounts --auth admin:admin\r\nHTTP/1.1 500 Internal Server Error\r\nAccess-Control-Expose-Headers: Retry-After, Alert, Backoff, Content-Length\r\nContent-Length: 177\r\nContent-Type: application/json\r\nDate: Mon, 28 Jan 2019 20:45:56 GMT\r\nVia: 1.1 alproxy\r\nX-Content-Type-Options: nosniff\r\n```\r\n\r\n```\r\n  File \"/home/natim/kinto/kinto/kinto/plugins/accounts/views.py\", line 221, in on_account_changed\r\n    username = request.matchdict[\"id\"]\r\nKeyError: 'id'\r\n```\n", "before_files": [{"content": "import colander\nfrom pyramid import httpexceptions\nfrom pyramid.decorator import reify\nfrom pyramid.security import Authenticated, Everyone\nfrom pyramid.settings import aslist\nfrom pyramid.events import subscriber\n\nfrom kinto.views import NameGenerator\nfrom kinto.core import resource, utils\nfrom kinto.core.errors import raise_invalid, http_error\nfrom kinto.core.events import ResourceChanged, ACTIONS\n\nfrom .utils import hash_password, ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME\n\n\ndef _extract_posted_body_id(request):\n    try:\n        # Anonymous creation with POST.\n        return request.json[\"data\"][\"id\"]\n    except (ValueError, KeyError):\n        # Bad POST data.\n        if request.method.lower() == \"post\":\n            error_details = {\"name\": \"data.id\", \"description\": \"data.id in body: Required\"}\n            raise_invalid(request, **error_details)\n        # Anonymous GET\n        error_msg = \"Cannot read accounts.\"\n        raise http_error(httpexceptions.HTTPUnauthorized(), error=error_msg)\n\n\nclass AccountIdGenerator(NameGenerator):\n    \"\"\"Allow @ signs in account IDs.\"\"\"\n\n    regexp = r\"^[a-zA-Z0-9][.@a-zA-Z0-9_-]*$\"\n\n\nclass AccountSchema(resource.ResourceSchema):\n    password = colander.SchemaNode(colander.String())\n\n\n@resource.register()\nclass Account(resource.Resource):\n\n    schema = AccountSchema\n\n    def __init__(self, request, context):\n        # Store if current user is administrator (before accessing get_parent_id())\n        allowed_from_settings = request.registry.settings.get(\"account_write_principals\", [])\n        context.is_administrator = (\n            len(set(aslist(allowed_from_settings)) & set(request.prefixed_principals)) > 0\n        )\n        # Shortcut to check if current is anonymous (before get_parent_id()).\n        context.is_anonymous = Authenticated not in request.effective_principals\n\n        super().__init__(request, context)\n\n        # Overwrite the current principal set by Resource.\n        if self.model.current_principal == Everyone or context.is_administrator:\n            # Creation is anonymous, but author with write perm is this:\n            self.model.current_principal = f\"{ACCOUNT_POLICY_NAME}:{self.model.parent_id}\"\n\n    @reify\n    def id_generator(self):\n        # This generator is used for ID validation.\n        return AccountIdGenerator()\n\n    def get_parent_id(self, request):\n        # The whole challenge here is that we want to isolate what\n        # authenticated users can list, but give access to everything to\n        # administrators.\n        # Plus when anonymous create accounts, we have to set their parent id\n        # to the same value they would obtain when authenticated.\n        if self.context.is_administrator:\n            if self.context.on_plural_endpoint:\n                # Accounts created by admin should have userid as parent.\n                if request.method.lower() == \"post\":\n                    return _extract_posted_body_id(request)\n                else:\n                    # Admin see all accounts.\n                    return \"*\"\n            else:\n                # No pattern matching for admin on single record.\n                return request.matchdict[\"id\"]\n\n        if not self.context.is_anonymous:\n            # Authenticated users see their own account only.\n            return request.selected_userid\n\n        # Anonymous creation with PUT.\n        if \"id\" in request.matchdict:\n            return request.matchdict[\"id\"]\n\n        return _extract_posted_body_id(request)\n\n    def plural_post(self):\n        result = super(Account, self).plural_post()\n        if self.context.is_anonymous and self.request.response.status_code == 200:\n            error_details = {\"message\": \"Account ID %r already exists\" % result[\"data\"][\"id\"]}\n            raise http_error(httpexceptions.HTTPForbidden(), **error_details)\n        return result\n\n    def process_object(self, new, old=None):\n        new = super(Account, self).process_object(new, old)\n\n        new[\"password\"] = hash_password(new[\"password\"])\n\n        # Administrators can reach other accounts and anonymous have no\n        # selected_userid. So do not try to enforce.\n        if self.context.is_administrator or self.context.is_anonymous:\n            return new\n\n        # Do not let accounts be created without usernames.\n        if self.model.id_field not in new:\n            error_details = {\"name\": \"data.id\", \"description\": \"Accounts must have an ID.\"}\n            raise_invalid(self.request, **error_details)\n\n        # Otherwise, we force the id to match the authenticated username.\n        if new[self.model.id_field] != self.request.selected_userid:\n            error_details = {\n                \"name\": \"data.id\",\n                \"description\": \"Username and account ID do not match.\",\n            }\n            raise_invalid(self.request, **error_details)\n\n        return new\n\n\n# Clear cache on account change\n@subscriber(\n    ResourceChanged, for_resources=(\"account\",), for_actions=(ACTIONS.UPDATE, ACTIONS.DELETE)\n)\ndef on_account_changed(event):\n    request = event.request\n    cache = request.registry.cache\n    settings = request.registry.settings\n    # Extract username and password from current user\n    username = request.matchdict[\"id\"]\n    hmac_secret = settings[\"userid_hmac_secret\"]\n    cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n    # Delete cache\n    cache.delete(cache_key)\n", "path": "kinto/plugins/accounts/views.py"}], "after_files": [{"content": "import colander\nfrom pyramid import httpexceptions\nfrom pyramid.decorator import reify\nfrom pyramid.security import Authenticated, Everyone\nfrom pyramid.settings import aslist\nfrom pyramid.events import subscriber\n\nfrom kinto.views import NameGenerator\nfrom kinto.core import resource, utils\nfrom kinto.core.errors import raise_invalid, http_error\nfrom kinto.core.events import ResourceChanged, ACTIONS\n\nfrom .utils import hash_password, ACCOUNT_CACHE_KEY, ACCOUNT_POLICY_NAME\n\n\ndef _extract_posted_body_id(request):\n    try:\n        # Anonymous creation with POST.\n        return request.json[\"data\"][\"id\"]\n    except (ValueError, KeyError):\n        # Bad POST data.\n        if request.method.lower() == \"post\":\n            error_details = {\"name\": \"data.id\", \"description\": \"data.id in body: Required\"}\n            raise_invalid(request, **error_details)\n        # Anonymous GET\n        error_msg = \"Cannot read accounts.\"\n        raise http_error(httpexceptions.HTTPUnauthorized(), error=error_msg)\n\n\nclass AccountIdGenerator(NameGenerator):\n    \"\"\"Allow @ signs in account IDs.\"\"\"\n\n    regexp = r\"^[a-zA-Z0-9][.@a-zA-Z0-9_-]*$\"\n\n\nclass AccountSchema(resource.ResourceSchema):\n    password = colander.SchemaNode(colander.String())\n\n\n@resource.register()\nclass Account(resource.Resource):\n\n    schema = AccountSchema\n\n    def __init__(self, request, context):\n        # Store if current user is administrator (before accessing get_parent_id())\n        allowed_from_settings = request.registry.settings.get(\"account_write_principals\", [])\n        context.is_administrator = (\n            len(set(aslist(allowed_from_settings)) & set(request.prefixed_principals)) > 0\n        )\n        # Shortcut to check if current is anonymous (before get_parent_id()).\n        context.is_anonymous = Authenticated not in request.effective_principals\n\n        super().__init__(request, context)\n\n        # Overwrite the current principal set by Resource.\n        if self.model.current_principal == Everyone or context.is_administrator:\n            # Creation is anonymous, but author with write perm is this:\n            self.model.current_principal = f\"{ACCOUNT_POLICY_NAME}:{self.model.parent_id}\"\n\n    @reify\n    def id_generator(self):\n        # This generator is used for ID validation.\n        return AccountIdGenerator()\n\n    def get_parent_id(self, request):\n        # The whole challenge here is that we want to isolate what\n        # authenticated users can list, but give access to everything to\n        # administrators.\n        # Plus when anonymous create accounts, we have to set their parent id\n        # to the same value they would obtain when authenticated.\n        if self.context.is_administrator:\n            if self.context.on_plural_endpoint:\n                # Accounts created by admin should have userid as parent.\n                if request.method.lower() == \"post\":\n                    return _extract_posted_body_id(request)\n                else:\n                    # Admin see all accounts.\n                    return \"*\"\n            else:\n                # No pattern matching for admin on single record.\n                return request.matchdict[\"id\"]\n\n        if not self.context.is_anonymous:\n            # Authenticated users see their own account only.\n            return request.selected_userid\n\n        # Anonymous creation with PUT.\n        if \"id\" in request.matchdict:\n            return request.matchdict[\"id\"]\n\n        return _extract_posted_body_id(request)\n\n    def plural_post(self):\n        result = super(Account, self).plural_post()\n        if self.context.is_anonymous and self.request.response.status_code == 200:\n            error_details = {\"message\": \"Account ID %r already exists\" % result[\"data\"][\"id\"]}\n            raise http_error(httpexceptions.HTTPForbidden(), **error_details)\n        return result\n\n    def process_object(self, new, old=None):\n        new = super(Account, self).process_object(new, old)\n\n        new[\"password\"] = hash_password(new[\"password\"])\n\n        # Administrators can reach other accounts and anonymous have no\n        # selected_userid. So do not try to enforce.\n        if self.context.is_administrator or self.context.is_anonymous:\n            return new\n\n        # Do not let accounts be created without usernames.\n        if self.model.id_field not in new:\n            error_details = {\"name\": \"data.id\", \"description\": \"Accounts must have an ID.\"}\n            raise_invalid(self.request, **error_details)\n\n        # Otherwise, we force the id to match the authenticated username.\n        if new[self.model.id_field] != self.request.selected_userid:\n            error_details = {\n                \"name\": \"data.id\",\n                \"description\": \"Username and account ID do not match.\",\n            }\n            raise_invalid(self.request, **error_details)\n\n        return new\n\n\n# Clear cache on account change\n@subscriber(\n    ResourceChanged, for_resources=(\"account\",), for_actions=(ACTIONS.UPDATE, ACTIONS.DELETE)\n)\ndef on_account_changed(event):\n    request = event.request\n    cache = request.registry.cache\n    settings = request.registry.settings\n    hmac_secret = settings[\"userid_hmac_secret\"]\n\n    for obj in event.impacted_objects:\n        # Extract username and password from current user\n        username = obj[\"old\"][\"id\"]\n        cache_key = utils.hmac_digest(hmac_secret, ACCOUNT_CACHE_KEY.format(username))\n        # Delete cache\n        cache.delete(cache_key)\n", "path": "kinto/plugins/accounts/views.py"}]}