Skip to content

Commit eb18359

Browse files
jorwoodsWoods
andauthored
User favorites endpoint (#638)
* Create FavoriteRequest factory * Create favorites_endpoint * Enable addition of favorites * Enabled deletion of favorites * Fix XML response calls * Genericize descriptor * Fix typo * Remove outdated content * Use more descriptive variable names * Adjust API version * Create Favorite "enum" * Factor response parsing logic to model The favorites item is now a dictionary. The user_item has been altered to reflect this. * Test favorites.get * Test adding a favorite workbook * Test adding favorite view * Test adding favorite data source * Test adding favorite project * Test favorite deletion * Expand favorites test_get * Unpack list of views in class method response * Add Favorites back to import * Replace deprecated assertEquals with assertEqual * Rename Favorite FavoriteItem and encapsulate Co-authored-by: Woods <jordan.woods@mkcorp.com>
1 parent ccd5a4f commit eb18359

15 files changed

+414
-9
lines changed

tableauserverclient/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .datasource_item import DatasourceItem
66
from .database_item import DatabaseItem
77
from .exceptions import UnpopulatedPropertyError
8+
from .favorites_item import FavoriteItem
89
from .group_item import GroupItem
910
from .flow_item import FlowItem
1011
from .interval_item import IntervalItem, DailyInterval, WeeklyInterval, MonthlyInterval, HourlyInterval
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import xml.etree.ElementTree as ET
2+
import logging
3+
from .workbook_item import WorkbookItem
4+
from .view_item import ViewItem
5+
from .project_item import ProjectItem
6+
from .datasource_item import DatasourceItem
7+
8+
logger = logging.getLogger('tableau.models.favorites_item')
9+
10+
11+
class FavoriteItem:
12+
class Type:
13+
Workbook = 'workbook'
14+
Datasource = 'datasource'
15+
View = 'view'
16+
Project = 'project'
17+
18+
@classmethod
19+
def from_response(cls, xml, namespace):
20+
favorites = {
21+
'datasources': [],
22+
'projects': [],
23+
'views': [],
24+
'workbooks': [],
25+
}
26+
27+
parsed_response = ET.fromstring(xml)
28+
for workbook in parsed_response.findall('.//t:favorite/t:workbook', namespace):
29+
fav_workbook = WorkbookItem('')
30+
fav_workbook._set_values(*fav_workbook._parse_element(workbook, namespace))
31+
if fav_workbook:
32+
favorites['workbooks'].append(fav_workbook)
33+
for view in parsed_response.findall('.//t:favorite[t:view]', namespace):
34+
fav_views = ViewItem.from_xml_element(view, namespace)
35+
if fav_views:
36+
for fav_view in fav_views:
37+
favorites['views'].append(fav_view)
38+
for datasource in parsed_response.findall('.//t:favorite/t:datasource', namespace):
39+
fav_datasource = DatasourceItem('')
40+
fav_datasource._set_values(*fav_datasource._parse_element(datasource, namespace))
41+
if fav_datasource:
42+
favorites['datasources'].append(fav_datasource)
43+
for project in parsed_response.findall('.//t:favorite/t:project', namespace):
44+
fav_project = ProjectItem('p')
45+
fav_project._set_values(*fav_project._parse_element(project))
46+
if fav_project:
47+
favorites['projects'].append(fav_project)
48+
49+
return favorites

tableauserverclient/models/user_item.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(self, name=None, site_role=None, auth_setting=None):
4242
self._id = None
4343
self._last_login = None
4444
self._workbooks = None
45+
self._favorites = None
4546
self.email = None
4647
self.fullname = None
4748
self.name = name
@@ -99,6 +100,13 @@ def workbooks(self):
99100
raise UnpopulatedPropertyError(error)
100101
return self._workbooks()
101102

103+
@property
104+
def favorites(self):
105+
if self._favorites is None:
106+
error = "User item must be populated with favorites first."
107+
raise UnpopulatedPropertyError(error)
108+
return self._favorites
109+
102110
def to_reference(self):
103111
return ResourceReference(id_=self.id, tag_name=self.tag_name)
104112

tableauserverclient/server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
PermissionsRule, Permission, ColumnItem, FlowItem, WebhookItem
99
from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \
1010
Sites, Tables, Users, Views, Workbooks, Subscriptions, ServerResponseError, \
11-
MissingRequiredFieldError, Flows
11+
MissingRequiredFieldError, Flows, Favorites
1212
from .server import Server
1313
from .pager import Pager
1414
from .exceptions import NotSignedInError

tableauserverclient/server/endpoint/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .datasources_endpoint import Datasources
44
from .databases_endpoint import Databases
55
from .endpoint import Endpoint
6+
from .favorites_endpoint import Favorites
67
from .flows_endpoint import Flows
78
from .exceptions import ServerResponseError, MissingRequiredFieldError, ServerInfoEndpointNotFoundError
89
from .groups_endpoint import Groups
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from .endpoint import Endpoint, api
2+
from .exceptions import MissingRequiredFieldError
3+
from .. import RequestFactory
4+
from ...models import FavoriteItem
5+
from ..pager import Pager
6+
import xml.etree.ElementTree as ET
7+
import logging
8+
import copy
9+
10+
logger = logging.getLogger('tableau.endpoint.favorites')
11+
12+
13+
class Favorites(Endpoint):
14+
@property
15+
def baseurl(self):
16+
return "{0}/sites/{1}/favorites".format(self.parent_srv.baseurl, self.parent_srv.site_id)
17+
18+
# Gets all favorites
19+
@api(version="2.5")
20+
def get(self, user_item, req_options=None):
21+
logger.info('Querying all favorites for user {0}'.format(user_item.name))
22+
url = '{0}/{1}'.format(self.baseurl, user_item.id)
23+
server_response = self.get_request(url, req_options)
24+
25+
user_item._favorites = FavoriteItem.from_response(server_response.content,
26+
self.parent_srv.namespace)
27+
28+
@api(version="2.0")
29+
def add_favorite_workbook(self, user_item, workbook_item):
30+
url = '{0}/{1}'.format(self.baseurl, user_item.id)
31+
add_req = RequestFactory.Favorite.add_workbook_req(workbook_item.id, workbook_item.name)
32+
server_response = self.put_request(url, add_req)
33+
logger.info('Favorited {0} for user (ID: {1})'.format(workbook_item.name, user_item.id))
34+
35+
@api(version="2.0")
36+
def add_favorite_view(self, user_item, view_item):
37+
url = '{0}/{1}'.format(self.baseurl, user_item.id)
38+
add_req = RequestFactory.Favorite.add_view_req(view_item.id, view_item.name)
39+
server_response = self.put_request(url, add_req)
40+
logger.info('Favorited {0} for user (ID: {1})'.format(view_item.name, user_item.id))
41+
42+
@api(version="2.3")
43+
def add_favorite_datasource(self, user_item, datasource_item):
44+
url = '{0}/{1}'.format(self.baseurl, user_item.id)
45+
add_req = RequestFactory.Favorite.add_datasource_req(datasource_item.id, datasource_item.name)
46+
server_response = self.put_request(url, add_req)
47+
logger.info('Favorited {0} for user (ID: {1})'.format(datasource_item.name, user_item.id))
48+
49+
@api(version="3.1")
50+
def add_favorite_project(self, user_item, project_item):
51+
url = '{0}/{1}'.format(self.baseurl, user_item.id)
52+
add_req = RequestFactory.Favorite.add_project_req(project_item.id, project_item.name)
53+
server_response = self.put_request(url, add_req)
54+
logger.info('Favorited {0} for user (ID: {1})'.format(project_item.name, user_item.id))
55+
56+
@api(version="2.0")
57+
def delete_favorite_workbook(self, user_item, workbook_item):
58+
url = '{0}/{1}/workbooks/{2}'.format(self.baseurl, user_item.id, workbook_item.id)
59+
logger.info('Removing favorite {0} for user (ID: {1})'.format(workbook_item.id, user_item.id))
60+
self.delete_request(url)
61+
62+
@api(version="2.0")
63+
def delete_favorite_view(self, user_item, view_item):
64+
url = '{0}/{1}/views/{2}'.format(self.baseurl, user_item.id, view_item.id)
65+
logger.info('Removing favorite {0} for user (ID: {1})'.format(view_item.id, user_item.id))
66+
self.delete_request(url)
67+
68+
@api(version="2.3")
69+
def delete_favorite_datasource(self, user_item, datasource_item):
70+
url = '{0}/{1}/datasources/{2}'.format(self.baseurl, user_item.id, datasource_item.id)
71+
logger.info('Removing favorite {0} for user (ID: {1})'.format(datasource_item.id, user_item.id))
72+
self.delete_request(url)
73+
74+
@api(version="3.1")
75+
def delete_favorite_project(self, user_item, project_item):
76+
url = '{0}/{1}/projects/{2}'.format(self.baseurl, user_item.id, project_item.id)
77+
logger.info('Removing favorite {0} for user (ID: {1})'.format(project_item.id, user_item.id))
78+
self.delete_request(url)

tableauserverclient/server/request_factory.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from requests.packages.urllib3.fields import RequestField
44
from requests.packages.urllib3.filepost import encode_multipart_formdata
55

6-
from ..models import TaskItem
6+
from ..models import TaskItem, UserItem, GroupItem, PermissionsRule, FavoriteItem
77

88

99
def _add_multipart(parts):
@@ -149,6 +149,34 @@ def publish_req_chunked(self, datasource_item, connection_credentials=None, conn
149149
return _add_multipart(parts)
150150

151151

152+
class FavoriteRequest(object):
153+
def _add_to_req(self, id_, target_type, label):
154+
'''
155+
<favorite label="...">
156+
<target_type id="..." />
157+
</favorite>
158+
'''
159+
xml_request = ET.Element('tsRequest')
160+
favorite_element = ET.SubElement(xml_request, 'favorite')
161+
target = ET.SubElement(favorite_element, target_type)
162+
favorite_element.attrib['label'] = label
163+
target.attrib['id'] = id_
164+
165+
return ET.tostring(xml_request)
166+
167+
def add_datasource_req(self, id_, name):
168+
return self._add_to_req(id_, FavoriteItem.Type.Datasource, name)
169+
170+
def add_project_req(self, id_, name):
171+
return self._add_to_req(id_, FavoriteItem.Type.Project, name)
172+
173+
def add_view_req(self, id_, name):
174+
return self._add_to_req(id_, FavoriteItem.Type.View, name)
175+
176+
def add_workbook_req(self, id_, name):
177+
return self._add_to_req(id_, FavoriteItem.Type.Workbook, name)
178+
179+
152180
class FileuploadRequest(object):
153181
def chunk_req(self, chunk):
154182
parts = {'request_payload': ('', '', 'text/xml'),
@@ -605,6 +633,7 @@ class RequestFactory(object):
605633
Datasource = DatasourceRequest()
606634
Database = DatabaseRequest()
607635
Empty = EmptyRequest()
636+
Favorite = FavoriteRequest()
608637
Fileupload = FileuploadRequest()
609638
Flow = FlowRequest()
610639
Group = GroupRequest()

tableauserverclient/server/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ..namespace import Namespace
55
from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, \
66
Schedules, ServerInfo, Tasks, Subscriptions, Jobs, Metadata,\
7-
Databases, Tables, Flows, Webhooks, DataAccelerationReport
7+
Databases, Tables, Flows, Webhooks, DataAccelerationReport, Favorites
88
from .endpoint.exceptions import EndpointUnavailableError, ServerInfoEndpointNotFoundError
99

1010
import requests
@@ -46,6 +46,7 @@ def __init__(self, server_address, use_server_version=False):
4646
self.jobs = Jobs(self)
4747
self.workbooks = Workbooks(self)
4848
self.datasources = Datasources(self)
49+
self.favorites = Favorites(self)
4950
self.flows = Flows(self)
5051
self.projects = Projects(self)
5152
self.schedules = Schedules(self)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
3+
<favorites>
4+
<favorite>
5+
<datasource id="e76a1461-3b1d-4588-bf1b-17551a879ad9"
6+
name="SampleDS"
7+
contentUrl="SampleDS"
8+
type="dataengine"
9+
createdAt="2016-08-11T21:22:40Z"
10+
updatedAt="2016-08-11T21:34:17Z">
11+
<project id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" name="default" />
12+
<owner id="5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" />
13+
<tags />
14+
</datasource>
15+
</favorite>
16+
</favorites>
17+
</tsResponse>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
3+
<favorites>
4+
<favorite>
5+
<project id="1d0304cd-3796-429f-b815-7258370b9b74"
6+
name="Tableau"
7+
description=""
8+
contentPermissions="ManagedByOwner" />
9+
</favorite>
10+
</favorites>
11+
</tsResponse>

0 commit comments

Comments
 (0)