# Buggy code
def on_resource_changed(event):
    """
    Everytime an object is created/changed/deleted, we create an entry in the
    ``history`` resource. The entries are served as read-only in the
    :mod:`kinto.plugins.history.views` module.
    """
    payload = copy.deepcopy(event.payload)
    resource_name = payload["resource_name"]
    event_uri = payload["uri"]

    bucket_id = payload.pop("bucket_id")
    bucket_uri = instance_uri(event.request, "bucket", id=bucket_id)
    collection_id = None
    collection_uri = None
    if "collection_id" in payload:
        collection_id = payload["collection_id"]
        collection_uri = instance_uri(
            event.request, "collection", bucket_id=bucket_id, id=collection_id
        )

    storage = event.request.registry.storage
    permission = event.request.registry.permission

    targets = []
    for impacted in event.impacted_records:
        # On POST .../records, the URI does not contain the newly created
        # record id.
        target = impacted["new"]
        obj_id = target["id"]
        parts = event_uri.split("/")
        if resource_name in parts[-1]:
            parts.append(obj_id)
        else:
            # Make sure the id is correct on grouped events.
            parts[-1] = obj_id
        uri = "/".join(parts)
        targets.append((uri, target))

    # Prepare a list of object ids to be fetched from permission backend,
    # and fetch them all at once. Use a mapping for later convenience.
    all_perms_objects_ids = [oid for (oid, _) in targets]
    all_perms_objects_ids.append(bucket_uri)
    if collection_uri is not None:
        all_perms_objects_ids.append(collection_uri)
    all_perms_objects_ids = list(set(all_perms_objects_ids))
    all_permissions = permission.get_objects_permissions(all_perms_objects_ids)
    perms_by_object_id = dict(zip(all_perms_objects_ids, all_permissions))

    bucket_perms = perms_by_object_id[bucket_uri]
    collection_perms = {}
    if collection_uri is not None:
        collection_perms = perms_by_object_id[collection_uri]

    # The principals allowed to read the bucket and collection.
    # (Note: ``write`` means ``read``)
    read_principals = set(bucket_perms.get("read", []))
    read_principals.update(bucket_perms.get("write", []))
    read_principals.update(collection_perms.get("read", []))
    read_principals.update(collection_perms.get("write", []))

    # Create a history entry for each impacted record.
    for uri, target in targets:
        obj_id = target["id"]
        # Prepare the history entry attributes.
        perms = {k: list(v) for k, v in perms_by_object_id[uri].items()}
        eventattrs = dict(**payload)
        eventattrs.setdefault("%s_id" % resource_name, obj_id)
        eventattrs["uri"] = uri
        attrs = dict(
            date=datetime.now().isoformat(),
            target={"data": target, "permissions": perms},
            **eventattrs,
        )

        # Create a record for the 'history' resource, whose parent_id is
        # the bucket URI (c.f. views.py).
        # Note: this will be rolledback if the transaction is rolledback.
        entry = storage.create(
            parent_id=bucket_uri, collection_id="history", record=attrs
        )

        # The read permission on the newly created history entry is the union
        # of the record permissions with the one from bucket and collection.
        entry_principals = set(read_principals)
        entry_principals.update(perms.get("read", []))
        entry_principals.update(perms.get("write", []))
        entry_perms = {"read": list(entry_principals)}
        # /buckets/{id}/history is the URI for the list of history entries.
        entry_perm_id = "/buckets/%s/history/%s" % (bucket_id, entry["id"])
        permission.replace_object_permissions(entry_perm_id, entry_perms)


# Traceback (KeyError)
ERROR  "DELETE /v1/buckets" ? (? ms) 'bucket_id' agent=node-fetch/1.0 (+https://github.com/bitinn/node-fetch) authn_type=basicauth collection_id=bucket collection_timestamp=1471536086687 errno=None exception=Traceback (most recent call last):
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py", line 22, in excview_tween
response = handler(request)
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 101, in tm_tween
reraise(*exc_info)
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 90, in tm_tween
manager.commit()
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_manager.py", line 111, in commit
return self.get().commit()
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py", line 265, in commit
self._callBeforeCommitHooks()
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/transaction/_transaction.py", line 337, in _callBeforeCommitHooks
hook(*args, **kws)
File "/home/mathieu/Code/Mozilla/kinto/kinto/core/events.py", line 76, in _notify_resource_events_before
request.registry.notify(event)
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/registry.py", line 91, in notify
[ _ for _ in self.subscribers(events, None) ]
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/registry.py", line 328, in subscribers
return self.adapters.subscribers(objects, provided)
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/zope/interface/adapter.py", line 596, in subscribers
subscription(*objects)
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py", line 130, in subscriber_wrapper
return derived_subscriber(*arg)
File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/adapters.py", line 103, in derived_subscriber
return subscriber(arg[0])
File "/home/mathieu/Code/Mozilla/kinto/kinto/plugins/history/listener.py", line 16, in on_resource_changed
bucket_id = payload.pop('bucket_id')
KeyError: 'bucket_id' lang=None uid=a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a

# Fixed code
def on_resource_changed(event):
    """
    Everytime an object is created/changed/deleted, we create an entry in the
    ``history`` resource. The entries are served as read-only in the
    :mod:`kinto.plugins.history.views` module.
    """
    payload = event.payload
    resource_name = payload["resource_name"]
    event_uri = payload["uri"]

    bucket_id = None
    bucket_uri = None
    collection_uri = None

    storage = event.request.registry.storage
    permission = event.request.registry.permission

    targets = []
    for impacted in event.impacted_records:
        target = impacted["new"]
        obj_id = target["id"]

        try:
            bucket_id = payload["bucket_id"]
        except KeyError:
            # e.g. DELETE /buckets
            bucket_id = obj_id
        bucket_uri = instance_uri(event.request, "bucket", id=bucket_id)

        if "collection_id" in payload:
            collection_id = payload["collection_id"]
            collection_uri = instance_uri(
                event.request, "collection", bucket_id=bucket_id, id=collection_id
            )

        # On POST .../records, the URI does not contain the newly created
        # record id.
        parts = event_uri.split("/")
        if resource_name in parts[-1]:
            parts.append(obj_id)
        else:
            # Make sure the id is correct on grouped events.
            parts[-1] = obj_id
        uri = "/".join(parts)
        targets.append((uri, target))

    # Prepare a list of object ids to be fetched from permission backend,
    # and fetch them all at once. Use a mapping for later convenience.
    all_perms_objects_ids = [oid for (oid, _) in targets]
    all_perms_objects_ids.append(bucket_uri)
    if collection_uri is not None:
        all_perms_objects_ids.append(collection_uri)
    all_perms_objects_ids = list(set(all_perms_objects_ids))
    all_permissions = permission.get_objects_permissions(all_perms_objects_ids)
    perms_by_object_id = dict(zip(all_perms_objects_ids, all_permissions))

    bucket_perms = perms_by_object_id[bucket_uri]
    collection_perms = {}
    if collection_uri is not None:
        collection_perms = perms_by_object_id[collection_uri]

    # The principals allowed to read the bucket and collection.
    # (Note: ``write`` means ``read``)
    read_principals = set(bucket_perms.get("read", []))
    read_principals.update(bucket_perms.get("write", []))
    read_principals.update(collection_perms.get("read", []))
    read_principals.update(collection_perms.get("write", []))

    # Create a history entry for each impacted record.
    for uri, target in targets:
        obj_id = target["id"]
        # Prepare the history entry attributes.
        perms = {k: list(v) for k, v in perms_by_object_id[uri].items()}
        eventattrs = dict(**payload)
        eventattrs.pop("bucket_id", None)
        eventattrs.setdefault("%s_id" % resource_name, obj_id)
        eventattrs["uri"] = uri
        attrs = dict(
            date=datetime.now().isoformat(),
            target={"data": target, "permissions": perms},
            **eventattrs,
        )

        # Create a record for the 'history' resource, whose parent_id is
        # the bucket URI (c.f. views.py).
        # Note: this will be rolledback if the transaction is rolledback.
        entry = storage.create(
            parent_id=bucket_uri, collection_id="history", record=attrs
        )

        # The read permission on the newly created history entry is the union
        # of the record permissions with the one from bucket and collection.
        entry_principals = set(read_principals)
        entry_principals.update(perms.get("read", []))
        entry_principals.update(perms.get("write", []))
        entry_perms = {"read": list(entry_principals)}
        # /buckets/{id}/history is the URI for the list of history entries.
        entry_perm_id = "/buckets/%s/history/%s" % (bucket_id, entry["id"])
        permission.replace_object_permissions(entry_perm_id, entry_perms)