Skip to content

Commit

Permalink
Merge pull request #428 from Kinto/delete-buckets-collections
Browse files Browse the repository at this point in the history
Delete buckets and collections
  • Loading branch information
leplatrem committed Mar 7, 2016
2 parents 24a1100 + 9cdedbf commit 59ab6f5
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 66 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This document describes changes between each past release.
**Protocol**

- Allow buckets to store arbitrary properties. (#462)
- Delete every (writable) buckets using ``DELETE /v1/buckets``
- Delete every (writable) collections using ``DELETE /v1/buckets/<bucket-id>/collections``
- Clients are redirected to URLs without trailing slash only if the current URL
does not exist (#656)
- Partial responses can now be specified for nested objects (#445)
Expand All @@ -18,6 +20,8 @@ This document describes changes between each past release.
- Server now returns 415 error response if client cannot accept JSON response (#461, mozilla-services/cliquet#667)
- Server now returns 415 error response if client does not send JSON request (#461, mozilla-services/cliquet#667)

Protocol is now version 1.4. See `API changelog <http://kinto.readthedocs.org/en/latest/api/>`_.

**Breaking changes**

- ``kinto start`` must be explicitly run with ``--reload`` in order to
Expand Down
50 changes: 50 additions & 0 deletions docs/api/1.x/buckets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,56 @@ Retrieving all buckets
}


.. _buckets-delete:

Delete all buckets
=======================

.. http:delete:: /buckets
:synopsis: Delete every writable buckets for this user

**Requires authentication**

**Example Request**

.. sourcecode:: bash

$ http delete http://localhost:8888/v1/buckets --auth="token:bob-token" --verbose

.. sourcecode:: http

DELETE /v1/buckets HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Basic YWxpY2U6
Connection: keep-alive
Content-Length: 0
Host: localhost:8888
User-Agent: HTTPie/0.9.2

**Example Response**

.. sourcecode:: http

HTTP/1.1 200 OK
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Content-Length: 101
Content-Type: application/json; charset=UTF-8
Date: Fri, 26 Feb 2016 14:12:22 GMT
Server: waitress

{
"data": [
{
"deleted": true,
"id": "e64db3f9-6a60-1acf-fc3a-7d1ba7e823aa",
"last_modified": 1456495942515
}
]
}


.. _buckets-default-id:

Personal bucket «default»
Expand Down
121 changes: 120 additions & 1 deletion docs/api/1.x/collections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,126 @@ A collection is a mapping with the following attribute:
data from their personnal bucket, by sharing :ref:`its URL using the full ID <buckets-default-id>`.


.. _collection-post:

.. _collections-get:

List bucket collections
=======================

.. http:post:: /buckets/(bucket_id)/collections
:synopsis: List bucket's readable collections

**Requires authentication**

**Example Request**

.. sourcecode:: bash

$ http GET http://localhost:8888/v1/buckets/blog/collections --auth="token:bob-token" --verbose

.. sourcecode:: http

GET /v1/buckets/blog/collections HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Basic YWxpY2U6
Connection: keep-alive
Host: localhost:8888
User-Agent: HTTPie/0.9.2

.. sourcecode:: http

HTTP/1.1 200 OK
Access-Control-Expose-Headers: Content-Length, Expires, Alert, Retry-After, Last-Modified, Total-Records, ETag, Pragma, Cache-Control, Backoff, Next-Page
Cache-Control: no-cache
Content-Length: 144
Content-Type: application/json; charset=UTF-8
Date: Fri, 26 Feb 2016 14:14:40 GMT
Etag: "1456496072475"
Last-Modified: Fri, 26 Feb 2016 14:14:32 GMT
Server: waitress
Total-Records: 3

{
"data": [
{
"id": "scores",
"last_modified": 1456496072475
},
{
"id": "game",
"last_modified": 1456496060675
},
{
"id": "articles",
"last_modified": 1456496056908
}
]
}


.. _collections-delete:

Delete bucket collections
=======================

.. http:delete:: /buckets/(bucket_id)/collections
:synopsis: Delete every writable collections in this bucket

**Requires authentication**

**Example Request**

.. sourcecode:: bash

$ http delete http://localhost:8888/v1/buckets/blog/collections --auth="token:bob-token" --verbose

.. sourcecode:: http

DELETE /v1/buckets/blog/collections HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Basic YWxpY2U6
Connection: keep-alive
Content-Length: 0
Host: localhost:8888
User-Agent: HTTPie/0.9.2

**Example Response**

.. sourcecode:: http

HTTP/1.1 200 OK
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Content-Length: 189
Content-Type: application/json; charset=UTF-8
Date: Fri, 26 Feb 2016 14:19:21 GMT
Server: waitress

{
"data": [
{
"deleted": true,
"id": "articles",
"last_modified": 1456496361303
},
{
"deleted": true,
"id": "game",
"last_modified": 1456496361304
},
{
"deleted": true,
"id": "scores",
"last_modified": 1456496361305
}
]
}


.. _collections-post:

Creating a collection
=====================
Expand Down
8 changes: 7 additions & 1 deletion docs/api/1.x/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Cheatsheet
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `GET` | :ref:`/buckets <buckets-get>` | :ref:`List buckets <buckets-get>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `DELETE` | :ref:`/buckets <buckets-delete>` | :ref:`Delete every writable buckets <buckets-delete>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `PUT` | :ref:`/buckets/(bucket_id) <bucket-put>` | :ref:`Create or replace a bucket <bucket-put>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `GET` | :ref:`/buckets/(bucket_id) <bucket-get>` | :ref:`Retrieve an existing bucket <bucket-get>` |
Expand All @@ -43,7 +45,11 @@ Cheatsheet
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| **Collections** |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `POST` | :ref:`/buckets/(bucket_id)/collections <collection-post>` | :ref:`Create a collection <collection-put>` |
| `GET` | :ref:`/buckets/(bucket_id)/collections <collections-get>` | :ref:`List bucket's collections <collections-get>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `DELETE` | :ref:`/buckets/(bucket_id)/collections <collections-delete>` | :ref:`Delete writable collections <collections-delete>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `POST` | :ref:`/buckets/(bucket_id)/collections <collections-post>` | :ref:`Create a collection <collections-post>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
| `PUT` | :ref:`/buckets/(bucket_id)/collections/(collection_id) <collection-put>` | :ref:`Create or replace a collection <collection-put>` |
+----------+----------------------------------------------------------------------------------------------+---------------------------------------------------------+
Expand Down
2 changes: 2 additions & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Changelog
''''''''''''''''

- Allow bucket to get arbitrary attributes.
- Delete every (writable) buckets using ``DELETE /v1/buckets``
- Delete every (writable) collections using ``DELETE /v1/buckets/<bucket-id>/collections``
- URLs with trailing slash are redirected only if the current URL does not exist
- Partial responses can now be specified for nested objects.
For example, ``/records?_fields=address.street``.
Expand Down
12 changes: 12 additions & 0 deletions kinto/tests/test_views_buckets.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ def get_app_settings(self, extra=None):
settings['kinto.bucket_read_principals'] = self.principal
return settings

def test_buckets_can_be_deleted_in_bulk(self):
self.app.put_json('/buckets/1', MINIMALIST_BUCKET,
headers=get_user_headers('alice'))
self.app.put_json('/buckets/2', MINIMALIST_BUCKET,
headers=self.headers)
self.app.put_json('/buckets/3', MINIMALIST_BUCKET,
headers=self.headers)
self.app.delete('/buckets', headers=self.headers)
self.app.get('/buckets/1', headers=self.headers, status=200)
self.app.get('/buckets/2', headers=self.headers, status=404)
self.app.get('/buckets/3', headers=self.headers, status=404)

def test_buckets_can_be_deleted(self):
self.app.get(self.bucket_url, headers=self.headers,
status=404)
Expand Down
21 changes: 19 additions & 2 deletions kinto/tests/test_views_collections.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .support import (BaseWebTest, unittest, MINIMALIST_BUCKET,
MINIMALIST_COLLECTION, MINIMALIST_RECORD)
MINIMALIST_COLLECTION, MINIMALIST_RECORD,
get_user_headers)


class CollectionViewTest(BaseWebTest, unittest.TestCase):
Expand Down Expand Up @@ -79,7 +80,10 @@ class CollectionDeletionTest(BaseWebTest, unittest.TestCase):

def setUp(self):
super(CollectionDeletionTest, self).setUp()
self.app.put_json('/buckets/beers', MINIMALIST_BUCKET,
bucket = MINIMALIST_BUCKET.copy()
bucket['permissions'] = {'collection:create': ['system.Everyone'],
'read': ['system.Everyone']}
self.app.put_json('/buckets/beers', bucket,
headers=self.headers)
self.app.put_json(self.collection_url, MINIMALIST_COLLECTION,
headers=self.headers)
Expand All @@ -94,6 +98,19 @@ def test_collections_can_be_deleted(self):
self.app.get(self.collection_url, headers=self.headers,
status=404)

def test_collections_can_be_deleted_in_bulk(self):
alice_headers = get_user_headers('alice')
self.app.put_json('/buckets/beers/collections/1',
MINIMALIST_COLLECTION, headers=self.headers)
self.app.put_json('/buckets/beers/collections/2',
MINIMALIST_COLLECTION, headers=alice_headers)
self.app.put_json('/buckets/beers/collections/3',
MINIMALIST_COLLECTION, headers=alice_headers)
self.app.delete('/buckets/beers/collections',
headers=alice_headers)
resp = self.app.get('/buckets/beers/collections', headers=self.headers)
self.assertEqual(len(resp.json['data']), 1)

def test_records_of_collection_are_deleted_too(self):
self.app.put_json(self.collection_url, MINIMALIST_COLLECTION,
headers=self.headers)
Expand Down
39 changes: 24 additions & 15 deletions kinto/views/buckets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from cliquet import resource
from cliquet.events import ResourceChanged, ACTIONS
from pyramid.events import subscriber

from kinto.views import NameGenerator


Expand All @@ -8,7 +11,7 @@ class Options:


@resource.register(name='bucket',
collection_methods=('GET', 'POST'),
collection_methods=('GET', 'POST', 'DELETE'),
collection_path='/buckets',
record_path='/buckets/{{id}}')
class Bucket(resource.ProtectedResource):
Expand All @@ -23,33 +26,39 @@ def get_parent_id(self, request):
# Buckets are not isolated by user, unlike Cliquet resources.
return ''

def delete(self):
result = super(Bucket, self).delete()

@subscriber(ResourceChanged,
for_resources=('bucket',),
for_actions=(ACTIONS.DELETE,))
def on_buckets_deleted(event):
"""Some buckets were deleted, delete sub-resources.
"""
storage = event.request.registry.storage

for change in event.impacted_records:
bucket = change['old']
parent_id = '/buckets/%s' % bucket['id']

# Delete groups.
storage = self.model.storage
parent_id = '/buckets/%s' % self.record_id
storage.delete_all(collection_id='group',
parent_id=parent_id,
with_deleted=False)
storage.purge_deleted(collection_id='group',
parent_id=parent_id)

# Delete collections.
deleted = storage.delete_all(collection_id='collection',
parent_id=parent_id,
with_deleted=False)
deleted_collections = storage.delete_all(collection_id='collection',
parent_id=parent_id,
with_deleted=False)
storage.purge_deleted(collection_id='collection',
parent_id=parent_id)

# Delete records.
id_field = self.model.id_field
for collection in deleted:
parent_id = '/buckets/%s/collections/%s' % (self.record_id,
collection[id_field])
for collection in deleted_collections:
parent_id = '/buckets/%s/collections/%s' % (bucket['id'],
collection['id'])
storage.delete_all(collection_id='record',
parent_id=parent_id,
with_deleted=False)
storage.purge_deleted(collection_id='record', parent_id=parent_id)

return result
storage.purge_deleted(collection_id='record',
parent_id=parent_id)
Loading

0 comments on commit 59ab6f5

Please sign in to comment.