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')