Repository: box/box-python-sdk
Pull request #323 (2018-08-20 18:23:55)

Device pins v2


Patch:
diff --git a/boxsdk/client/client.py b/boxsdk/client/client.py
index c96ca3b..e95534e 100644
--- a/boxsdk/client/client.py
+++ b/boxsdk/client/client.py
@@ -582,3 +582,60 @@ def get_url(self, endpoint, *args):
         """
         # pylint:disable=no-self-use
         return self._session.get_url(endpoint, *args)
+
+    def device_pinner(self, device_pin_id):
+        """
+        Initialize a :class:`DevicePinner` object, whose box id is device_pin_id.
+
+        :param device_pin_id:
+            The assignment ID of the :class:`DevicePin` object.
+        :type device_pin_id:
+            `unicode`
+        :return:
+            A :class:`DevicePinner` object with the given entry ID.
+        :rtype:
+            :class:`DevicePinner`
+        """
+        return self.translator.translate('device_pinner')(session=self._session, object_id=device_pin_id)
+
+    def device_pinners(self, enterprise_id, direction=None, limit=None, marker=None, fields=None):
+        """
+        Returns all of the device pins for the given enterprise.
+
+        :param enterprise_id:
+            The id of the enterprise to retrieve device pinners for.
+        :type enterprise_id:
+            `unicode`
+        :param direction:
+            The sorting direction. Set to `ASC` or `DESC`
+        :type direction:
+            `unicode` or None
+        :param limit:
+            The maximum number of entries to return per page. If not specified, then will use the server-side default.
+        :type limit:
+            `int` or None
+        :param marker:
+            The paging marker to start paging from.
+        :type marker:
+            `unicode` or None
+        :param fields:
+            List of fields to request.
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the entries in the device pins.
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        additional_params = {}
+        if direction is not None:
+            additional_params['direction'] = direction
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self.get_url('enterprises', enterprise_id, 'device_pinners'),
+            additional_params=additional_params,
+            limit=limit,
+            marker=marker,
+            fields=fields,
+            return_full_pages=False,
+        )
diff --git a/boxsdk/object/__init__.py b/boxsdk/object/__init__.py
index 3d83fbe..c0e58e5 100644
--- a/boxsdk/object/__init__.py
+++ b/boxsdk/object/__init__.py
@@ -9,6 +9,7 @@
     'collaboration',
     'collection',
     'comment',
+    'device_pinner',
     'events',
     'event',
     'file',
diff --git a/boxsdk/object/device_pinner.py b/boxsdk/object/device_pinner.py
new file mode 100644
index 0000000..aa4c20b
--- /dev/null
+++ b/boxsdk/object/device_pinner.py
@@ -0,0 +1,9 @@
+# coding: utf-8
+from __future__ import unicode_literals
+from .base_object import BaseObject
+
+
+class DevicePinner(BaseObject):
+    """Represents the device pinner"""
+
+    _item_type = 'device_pinner'
diff --git a/docs/device_pin.md b/docs/device_pin.md
new file mode 100644
index 0000000..4d2c692
--- /dev/null
+++ b/docs/device_pin.md
@@ -0,0 +1,47 @@
+Device Pins
+===========
+
+Device pinning is a feature that allows enterprise admins to pin their user’s
+corporate-managed Box account to a particular mobile device or Box Sync client.
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [List Enterprise Device Pins](#list-enterprise-device-pins)
+- [Get Device Pin Information](#get-device-pin-information)
+- [Delete Device Pin](#delete-device-pin)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+List Enterprise Device Pins
+---------------------------
+
+To retrieve all device pins for the enterprise, use `client.device_pinners(enterprise_id, limit=None, marker=None, direction=None, fields=None)`.
+
+```python
+enterprise_id = '1234'
+device_pins = client.device_pinners(enterprise_id)
+for device_in in device_pins:
+    # Do something
+```
+
+Get Device Pin Information
+--------------------------
+
+To get information about a specific device pin. use `device_pinner.get()`
+
+```python
+device_pin_id = '1111'
+device_pin_info = client.device_pinner('device_pin_id).get()
+```
+
+Delete Device Pin
+-----------------
+
+To delete a specific device pin, use `device_pinner.delete()`
+
+```python
+device_pin_id = '1111'
+client.device_pin(device_pin_id).delete()
+```


Test patch:
diff --git a/test/unit/client/test_client.py b/test/unit/client/test_client.py
index 4997a76..039715d 100644
--- a/test/unit/client/test_client.py
+++ b/test/unit/client/test_client.py
@@ -181,6 +181,30 @@ def recent_items_response(file_id):
     return mock_network_response
 
 
+@pytest.fixture(scope='module')
+def device_pin_id_1():
+    return 101
+
+
+@pytest.fixture(scope='module')
+def device_pin_id_2():
+    return 202
+
+
+@pytest.fixture(scope='module')
+def device_pins_response(device_pin_id_1, device_pin_id_2):
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'device_pinner', 'id': device_pin_id_1},
+            {'type': 'device_pinner', 'id': device_pin_id_2},
+        ],
+        'limit': 2,
+    }
+    return mock_network_response
+
+
 @pytest.mark.parametrize('test_class, factory_method_name', [
     (Folder, 'folder'),
     (File, 'file'),
@@ -460,3 +484,13 @@ def test_downscope_token_sends_downscope_request_with_additional_data(
         'extra_data_key': 'extra_data_value',
     }
     check_downscope_token_request(File, [TokenScope.ITEM_READWRITE], additional_data, expected_data)
+
+
+def test_device_pins_for_enterprise(mock_client, mock_box_session, device_pins_response, device_pin_id_1, device_pin_id_2):
+    # pylint:disable=redefined-outer-name
+    mock_box_session.get.return_value = device_pins_response
+    pins = mock_client.device_pinners('1234')
+    for pin, expected_id in zip(pins, [device_pin_id_1, device_pin_id_2]):
+        assert pin.object_id == expected_id
+        # pylint:disable=protected-access
+        assert pin._session == mock_box_session
diff --git a/test/unit/object/conftest.py b/test/unit/object/conftest.py
index 021e4d3..a1ec752 100644
--- a/test/unit/object/conftest.py
+++ b/test/unit/object/conftest.py
@@ -9,6 +9,7 @@
 from boxsdk.object.collaboration import Collaboration
 from boxsdk.object.collection import Collection
 from boxsdk.object.comment import Comment
+from boxsdk.object.device_pinner import DevicePinner
 from boxsdk.object.file import File
 from boxsdk.object.folder import Folder
 from boxsdk.object.group import Group
@@ -96,6 +97,11 @@ def test_search(mock_box_session):
     return Search(mock_box_session)
 
 
+@pytest.fixture()
+def test_device_pin(mock_box_session, mock_object_id):
+    return DevicePinner(mock_box_session, mock_object_id)
+
+
 @pytest.fixture(scope='function')
 def mock_collab_response(make_mock_box_request, mock_collaboration_id):
     mock_box_response, _ = make_mock_box_request(
diff --git a/test/unit/object/test_device_pin.py b/test/unit/object/test_device_pin.py
new file mode 100644
index 0000000..54d9d28
--- /dev/null
+++ b/test/unit/object/test_device_pin.py
@@ -0,0 +1,47 @@
+# coding: utf-8
+
+from __future__ import unicode_literals
+
+import pytest
+
+from mock import Mock
+from boxsdk.config import API
+from boxsdk.object.device_pinner import DevicePinner
+from boxsdk.network.default_network import DefaultNetworkResponse
+
+
+@pytest.fixture(scope='module')
+def delete_device_pin_response():
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.ok = True
+    return mock_network_response
+
+
+def test_get(test_device_pin, mock_box_session):
+    created_at = '2016-05-18T17:38:03-07:00',
+    expected_url = '{0}/device_pinners/{1}'.format(API.BASE_API_URL, test_device_pin.object_id)
+    mock_box_session.get.return_value.json.return_value = {
+        'type': 'device_pinner',
+        'id': test_device_pin.object_id,
+        'created_at': created_at
+    }
+    device_pin = test_device_pin.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert isinstance(device_pin, DevicePinner)
+    assert device_pin.created_at == created_at
+
+
+def test_delete_device_pin_return_the_correct_response(
+        test_device_pin,
+        mock_box_session,
+        delete_device_pin_response,
+):
+    # pylint:disable=redefined-outer-name
+    mock_box_session.delete.return_value = delete_device_pin_response
+    response = test_device_pin.delete()
+    # pylint:disable=protected-access
+    expected_url = test_device_pin.get_url()
+    # pylint:enable = protected-access
+    mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None)
+    assert response is True