diff --git a/docs/_includes/analytics.html b/docs/_includes/analytics.html new file mode 100644 index 000000000..0cdbad25d --- /dev/null +++ b/docs/_includes/analytics.html @@ -0,0 +1,7 @@ + + + diff --git a/docs/_includes/head.html b/docs/_includes/head.html index 80ae0323a..e4d432e12 100644 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -13,3 +13,5 @@ + +{% if jekyll.environment == "production" %}{% include analytics.html %}{% endif %} diff --git a/tableauserverclient/models/access_permissions.py b/tableauserverclient/models/access_permissions.py new file mode 100644 index 000000000..e8865e0cd --- /dev/null +++ b/tableauserverclient/models/access_permissions.py @@ -0,0 +1,33 @@ +class AccessPermissions(object): + class Mode(object): + Allow = 'Allow' + Deny = 'Deny' + + class _Base_Permssions(object): + Read = 'Read' + Write = 'Write' + + class Workbook(_Base_Permssions): + AddComment = 'AddComment' + ChangeHierarchy = 'ChangeHierarchy' + ChangePermissions = 'ChangePermissions' + Delete = 'Delete' + ExportData = 'ExportData' + ExportImage = 'ExportImage' + ExportXml = 'ExportXml' + Filter = 'Filter' + ShareView = 'ShareView' + ViewComments = 'ViewComments' + ViewUnderlyingData = 'ViewUnderlyingData' + WebAuthoring = 'WebAuthoring' + + + class Datasource(_Base_Permssions): + ChangePermissions = 'ChangePermissions' + Connect = 'Connect' + Delete = 'Delete' + ExportXml = 'ExportXml' + + + class Project(_Base_Permssions): + ProjectLeader = 'ProjectLeader' diff --git a/tableauserverclient/models/favorite_item.py b/tableauserverclient/models/favorite_item.py new file mode 100644 index 000000000..542034008 --- /dev/null +++ b/tableauserverclient/models/favorite_item.py @@ -0,0 +1,75 @@ +import xml.etree.ElementTree as ET +import re +from .. import NAMESPACE + + +class FavoriteItem(object): + # Note that name must be the same as string value + class Types: + Workbook = 'Workbook' + View = 'View' + Datasource = 'Datasource' + + def __init__(self, label, id, type): + self._label = None + self._id = None + self._type = None + self.label = label + self.id = id + self.type = type + + @property + def label(self): + return self._label + + @label.setter + def label(self, value): + if not value: + error = 'Label must be defined.' + raise ValueError(error) + else: + self._label = value + + @property + def id(self): + return self._id + + @id.setter + def id(self, value): + if not value: + error = 'ID must be defined.' + raise ValueError(error) + else: + self._id = value + + @property + def type(self): + return self._type + + @type.setter + def type(self, value): + if not value: + error = 'Type must be defined.' + raise ValueError(error) + elif not hasattr(FavoriteItem.Types, value): + error = 'Invalid type for favorite.' + raise ValueError(error) + else: + self._type = value + + @classmethod + def from_response(cls, resp): + all_favorite_items = set() + parsed_response = ET.fromstring(resp) + all_favorite_xml = parsed_response.findall('.//t:favorite', namespaces=NAMESPACE) + for favorite_xml in all_favorite_xml: + child_elem = favorite_xml.getchildren()[0] + leading_namespace = '{{{0}}}'.format(NAMESPACE['t']) + favorite_type = child_elem.tag.replace(leading_namespace, '') + subject_elem = favorite_xml.find('.//t:' + favorite_type, namespaces=NAMESPACE) + id = subject_elem.get('id', None) + label = favorite_xml.get('label', None) + type = favorite_type.title() + favorite_item = cls(label, id, type) + all_favorite_items.add(favorite_item) + return all_favorite_items diff --git a/tableauserverclient/models/permission_item.py b/tableauserverclient/models/permission_item.py new file mode 100644 index 000000000..881d353b9 --- /dev/null +++ b/tableauserverclient/models/permission_item.py @@ -0,0 +1,44 @@ +import xml.etree.ElementTree as ET +from .. import NAMESPACE + + +class PermissionItem(object): + Group = 'group' + User = 'user' + + def __init__(self): + self._grantee_type = None + self._grantee_id = None + self.permissions = {} + + @property + def grantee_type(self): + return self._grantee_type + + @property + def grantee_id(self): + return self._grantee_id + + @classmethod + def from_response(cls, resp): + all_permission_items = list() + parsed_response = ET.fromstring(resp) + all_grantee_xml = parsed_response.findall('.//t:granteeCapabilities', namespaces=NAMESPACE) + for grantee_xml in all_grantee_xml: + permission_item = cls() + all_capability_xml = grantee_xml.findall('.//t:capability', namespaces=NAMESPACE) + for capability_xml in all_capability_xml: + name = capability_xml.get('name', None) + mode = capability_xml.get('mode', None) + permission_item.permissions[name] = mode + + group_elem = grantee_xml.find('.//t:group', namespaces=NAMESPACE) + user_elem = grantee_xml.find('.//t:user', namespaces=NAMESPACE) + if group_elem is not None: + permission_item._grantee_id = group_elem.get('id', None) + permission_item._grantee_type = permission_item.Group + elif user_elem is not None: + permission_item._grantee_id = user_elem.get('id', None) + permission_item._grantee_type = permission_item.User + all_permission_items.append(permission_item) + return all_permission_items diff --git a/tableauserverclient/server/endpoint/favorites_endpoint.py b/tableauserverclient/server/endpoint/favorites_endpoint.py new file mode 100644 index 000000000..134a679da --- /dev/null +++ b/tableauserverclient/server/endpoint/favorites_endpoint.py @@ -0,0 +1,35 @@ +from endpoint import Endpoint +from exceptions import MissingRequiredFieldError +from .. import RequestFactory, FavoriteItem +import logging + +logger = logging.getLogger('tableau.endpoint.favorites') + + +class Favorites(Endpoint): + def __init__(self, parent_srv): + super(Endpoint, self).__init__() + self.baseurl = "{0}/sites/{1}/favorites" + self.parent_srv = parent_srv + + def _construct_url(self): + return self.baseurl.format(self.parent_srv.baseurl, self.parent_srv.site_id) + + def add(self, favorite_item, user_item): + url = "{0}/{1}".format(self._construct_url(), user_item.id) + add_req = RequestFactory().favorite.add_req(favorite_item) + server_response = self.put_request(url, add_req) + logger.info('Added {0} (ID: {1}) to {2}\'s favorites'.format(favorite_item.type, + favorite_item.id, user_item.name)) + return FavoriteItem.from_response(server_response.text) + + def delete(self, favorite_item, user_item): + if not user_item.id: + error = "User item missing ID." + raise MissingRequiredFieldError(error) + + url = "{0}/{1}/{2}s/{3}".format(self._construct_url(), user_item.id, + favorite_item.type.lower(), favorite_item.id) + self.delete_request(url) + logger.info('Deleted {0} (ID: {1}) from {2}\'s favorites'.format(favorite_item.type, + favorite_item.id, user_item.name)) diff --git a/tableauserverclient/server/endpoint/sites_endpoint.py b/tableauserverclient/server/endpoint/sites_endpoint.py index 3977ad0f2..5b448845e 100644 --- a/tableauserverclient/server/endpoint/sites_endpoint.py +++ b/tableauserverclient/server/endpoint/sites_endpoint.py @@ -31,6 +31,16 @@ def get_by_id(self, site_id): server_response = self.get_request(url) return SiteItem.from_response(server_response.content)[0] + # Gets 1 site by name + def get_by_name(self, site_name): + if not site_name: + error = "Site Name undefined." + raise ValueError(error) + logger.info('Querying single site (Name: {0})'.format(site_name)) + url = "{0}/{1}?key=name".format(self.baseurl, site_name) + server_response = self.get_request(url) + return SiteItem.from_response(server_response.content)[0] + # Update site def update(self, site_item): if not site_item.id: diff --git a/test/assets/site_get_by_name.xml b/test/assets/site_get_by_name.xml new file mode 100644 index 000000000..5b3042e61 --- /dev/null +++ b/test/assets/site_get_by_name.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/test/test_site.py b/test/test_site.py index 311f9524f..1d23351a9 100644 --- a/test/test_site.py +++ b/test/test_site.py @@ -7,6 +7,7 @@ GET_XML = os.path.join(TEST_ASSET_DIR, 'site_get.xml') GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, 'site_get_by_id.xml') +GET_BY_NAME_XML = os.path.join(TEST_ASSET_DIR, 'site_get_by_name.xml') UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'site_update.xml') CREATE_XML = os.path.join(TEST_ASSET_DIR, 'site_create.xml') @@ -64,6 +65,24 @@ def test_get_by_id(self): def test_get_by_id_missing_id(self): self.assertRaises(ValueError, self.server.sites.get_by_id, '') + def test_get_by_name(self): + with open(GET_BY_NAME_XML, 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.get(self.baseurl + '/testsite?key=name', text=response_xml) + single_site = self.server.sites.get_by_name('testsite') + + self.assertEqual('dad65087-b08b-4603-af4e-2887b8aafc67', single_site.id) + self.assertEqual('Active', single_site.state) + self.assertEqual('testsite', single_site.name) + self.assertEqual('ContentOnly', single_site.admin_mode) + self.assertEqual(False, single_site.revision_history_enabled) + self.assertEqual(True, single_site.subscribe_others_enabled) + self.assertEqual(False, single_site.disable_subscriptions) + + def test_get_by_name_missing_name(self): + self.assertRaises(ValueError, self.server.sites.get_by_name, '') + def test_update(self): with open(UPDATE_XML, 'rb') as f: response_xml = f.read().decode('utf-8')