Repository: Kinto/kinto

Issue:
Prune history entries from a certain timestamp
` DELETE /v1/buckets/history?_to=X`  ?
Prune history entries from a certain timestamp
` DELETE /v1/buckets/history?_to=X`  ?


Discussion:

Resolving patch:
diff --git a/kinto/__init__.py b/kinto/__init__.py
--- a/kinto/__init__.py
+++ b/kinto/__init__.py
@@ -13,7 +13,7 @@
 __version__ = pkg_resources.get_distribution(__package__).version
 
 # Implemented HTTP API Version
-HTTP_API_VERSION = '1.12'
+HTTP_API_VERSION = '1.13'
 
 # Main kinto logger
 logger = logging.getLogger(__name__)
diff --git a/kinto/core/resource/model.py b/kinto/core/resource/model.py
--- a/kinto/core/resource/model.py
+++ b/kinto/core/resource/model.py
@@ -270,8 +270,12 @@ def delete_records(self, filters=None, parent_id=None):
         """
         deleted = super(ShareableModel, self).delete_records(filters,
                                                              parent_id)
-        perm_ids = [self.get_permission_object_id(object_id=r[self.id_field])
-                    for r in deleted]
+        # Take a huge shortcut in case we want to delete everything.
+        if not filters:
+            perm_ids = [self.get_permission_object_id(object_id='*')]
+        else:
+            perm_ids = [self.get_permission_object_id(object_id=r[self.id_field])
+                        for r in deleted]
         self.permission.delete_object_permissions(*perm_ids)
         return deleted
 
diff --git a/kinto/plugins/history/views.py b/kinto/plugins/history/views.py
--- a/kinto/plugins/history/views.py
+++ b/kinto/plugins/history/views.py
@@ -24,7 +24,7 @@ class Options:
 @resource.register(name='history',
                    collection_path='/buckets/{{bucket_id}}/history',
                    record_path=None,
-                   collection_methods=('GET',))
+                   collection_methods=('GET', 'DELETE'))
 class History(resource.ShareableResource):
 
     schema = HistorySchema


Test patch:
diff --git a/tests/plugins/test_history.py b/tests/plugins/test_history.py
--- a/tests/plugins/test_history.py
+++ b/tests/plugins/test_history.py
@@ -62,10 +62,9 @@ def setUp(self):
 
         self.history_uri = '/buckets/test/history'
 
-    def test_only_get_on_collection_is_allowed(self):
+    def test_only_get_and_delete_on_collection_are_allowed(self):
         self.app.put(self.history_uri, headers=self.headers, status=405)
         self.app.patch(self.history_uri, headers=self.headers, status=405)
-        self.app.delete(self.history_uri, headers=self.headers, status=405)
 
     def test_only_collection_endpoint_is_available(self):
         resp = self.app.get(self.history_uri, headers=self.headers)
@@ -316,6 +315,35 @@ def test_does_not_track_records_during_massive_deletion(self):
         assert len(deletion_entries) == 1
 
 
+class HistoryDeletionTest(HistoryWebTest):
+
+    def setUp(self):
+        self.app.put('/buckets/bid', headers=self.headers)
+        self.app.put('/buckets/bid/collections/cid',
+                     headers=self.headers)
+        body = {'data': {'foo': 42}}
+        self.app.put_json('/buckets/bid/collections/cid/records/rid',
+                          body,
+                          headers=self.headers)
+
+    def test_full_deletion(self):
+        self.app.delete('/buckets/bid/history', headers=self.headers)
+        resp = self.app.get('/buckets/bid/history', headers=self.headers)
+        assert len(resp.json['data']) == 0
+
+    def test_partial_deletion(self):
+        resp = self.app.get('/buckets/bid/history', headers=self.headers)
+        before = resp.headers['ETag']
+        self.app.put('/buckets/bid/collections/cid2', headers=self.headers)
+
+        # Delete everything before the last entry (exclusive)
+        self.app.delete('/buckets/bid/history?_before=%s' % before,
+                        headers=self.headers)
+
+        resp = self.app.get('/buckets/bid/history', headers=self.headers)
+        assert len(resp.json['data']) == 2  # record + new collection
+
+
 class FilteringTest(HistoryWebTest):
 
     def setUp(self):