8000 674909 create nested project by jimbodriven · Pull Request #208 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

674909 create nested project #208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/docs/samples.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ The following list describes the samples available in the repository:

* `create_group.py`. Create a user group.

* `create_project.py`. Create new projects at the top level as well as nested projects.

* `create_schedules.py`. Create schedules for extract refreshes and subscriptions.

* `explore_datasource.py`. Queries datasources, selects a datasource, populates connections for the datasource, then updates the datasource.
Expand All @@ -54,4 +56,3 @@ The following list describes the samples available in the repository:

**Note**: For all of the samples, ensure that your Tableau Server user account has permission to access the resources
requested by the samples.

68 changes: 68 additions & 0 deletions samples/create_project.py
8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
####
# This script demonstrates how to use the Tableau Server Client
# to create new projects, both at the root level and how to nest them using
# parent_id.
#
#
# To run the script, you must have installed Python 2.7.X or 3.3 and later.
####

import argparse
import getpass
import logging
import sys

import tableauserverclient as TSC


def create_project(server, project_item):
try:
project_item = server.projects.create(project_item)
print('Created a new project called: %s' % project_item.name)
return project_item
except TSC.ServerResponseError:
print('We have already created this project: %s' % project_item.name)
sys.exit(1)

def main():
parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server')
parser.add_argument('--server', '-s', required=True, help='server address')
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
parser.add_argument('--site', '-S', default=None)
parser.add_argument('-p', default=None, help='password')

parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
help='desired logging level (set to error by default)')

args = parser.parse_args()

if args.p is None:
password = getpass.getpass("Password: ")
else:
password = args.p

# Set logging level based on user input, or error by default
logging_level = getattr(logging, args.logging_level.upper())
logging.basicConfig(level=logging_level)

tableau_auth = TSC.TableauAuth(args.username, password)
server = TSC.Server(args.server)

with server.auth.sign_in(tableau_auth):
# Use highest Server REST API version available
server.use_server_version()

# Without parent_id specified, projects are created at the top level.
top_level_project = TSC.ProjectItem(name='Top Level Project')
top_level_project = create_project(server, top_level_project)

# Specifying parent_id creates a nested projects.
child_project = TSC.ProjectItem(name='Child Project', parent_id=top_level_project.id)
child_project = create_project(server, child_project)

# Projects can be nested at any level.
grand_child_project = TSC.ProjectItem(name='Grand Child Project', parent_id=child_project.id)
grand_child_project = create_project(server, grand_child_project)

if __name__ == '__main__':
main()
18 changes: 11 additions & 7 deletions tableauserverclient/models/project_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ class ContentPermissions:
LockedToProject = 'LockedToProject'
ManagedByOwner = 'ManagedByOwner'

def __init__(self, name, description=None, content_permissions=None):
def __init__(self, name, description=None, content_permissions=None, parent_id=None):
self._content_permissions = None
self._id = None
self.description = description
self.name = name
self.content_permissions = content_permissions
self.parent_id = parent_id

@property
def content_permissions(self):
Expand Down Expand Up @@ -45,11 +46,11 @@ def _parse_common_tags(self, project_xml):
project_xml = ET.fromstring(project_xml).find('.//t:project', namespaces=NAMESPACE)

if project_xml is not None:
(_, name, description, content_permissions) = self._parse_element(project_xml)
self._set_values(None, name, description, content_permissions)
(_, name, description, content_permissions, parent_id) = self._parse_element(project_xml)
self._set_values(None, name, description, content_permissions, parent_id)
return self

def _set_values(self, project_id, name, description, content_permissions):
def _set_values(self, project_id, name, description, content_permissions, parent_id):
if project_id is not None:
self._id = project_id
if name:
Expand All @@ -58,6 +59,8 @@ def _set_values(self, project_id, name, description, content_permissions):
self.description = description
if content_permissions:
self._content_permissions = content_permissions
if parent_id:
self.parent_id = parent_id

@classmethod
def from_response(cls, resp):
Expand All @@ -66,9 +69,9 @@ def from_response(cls, resp):
all_project_xml = parsed_response.findall('.//t:project', namespaces=NAMESPACE)

for project_xml in all_project_xml:
(id, name, description, content_permissions) = cls._parse_element(project_xml)
(id, name, description, content_permissions, parent_id) = cls._parse_element(project_xml)
project_item = cls(name)
project_item._set_values(id, name, description, content_permissions)
project_item._set_values(id, name, description, content_permissions, parent_id)
all_project_items.append(project_item)
return all_project_items

Expand All @@ -78,5 +81,6 @@ def _parse_element(project_xml):
name = project_xml.get('name', None)
description = project_xml.get('description', None)
content_permissions = project_xml.get('contentPermissions', None)
parent_id = project_xml.get('parentId', None)

return id, name, description, content_permissions
return id, name, description, content_permissions, parent_id
5 changes: 2 additions & 3 deletions tableauserverclient/server/endpoint/projects_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from .exceptions import MissingRequiredFieldError
from .. import RequestFactory, ProjectItem, PaginationItem
import logging
import copy

logger = logging.getLogger('tableau.endpoint.projects')

Expand Down Expand Up @@ -40,8 +39,8 @@ def update(self, project_item):
update_req = RequestFactory.Project.update_req(project_item)
server_response = self.put_request(url, update_req)
logger.info('Updated project item (ID: {0})'.format(project_item.id))
updated_project = copy.copy(project_item)
return updated_project._parse_common_tags(server_response.content)
updated_project = ProjectItem.from_response(server_response.content)[0]
return updated_project

@api(version="2.0")
def create(self, project_item):
Expand Down
2 changes: 2 additions & 0 deletions tableauserverclient/server/request_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ def create_req(self, project_item):
project_element.attrib['description'] = project_item.description
if project_item.content_permissions:
project_element.attrib['contentPermissions'] = project_item.content_permissions
if project_item.parent_id:
project_element.attrib['parentId'] = project_item.parent_id
return ET.tostring(xml_request)


Expand Down
4 changes: 2 additions & 2 deletions test/assets/project_create.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<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">
<project id="ccbea03f-77c4-4209-8774-f67bc59c3cef" name="Test Project" description="Project created for testing" contentPermissions="ManagedByOwner" />
</tsResponse>
<project id="ccbea03f-77c4-4209-8774-f67bc59c3cef" name="Test Project" description="Project created for testing" contentPermissions="ManagedByOwner" parentId="9a8f2265-70f3-4494-96c5-e5949d7a1120" />
</tsResponse>
5 changes: 3 additions & 2 deletions test/assets/project_get.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<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">
<pagination pageNumber="1" pageSize="100" totalAvailable="2" />
<pagination pageNumber="1" pageSize="100" totalAvailable="3" />
<projects>
<project id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" name="default" description="The default project that was automatically created by Tableau." contentPermissions="ManagedByOwner" />
<project id="1d0304cd-3796-429f-b815-7258370b9b74" name="Tableau" description="" contentPermissions="ManagedByOwner" />
<project id="4cc52973-5e3a-4d1f-a4fb-5b5f73796edf" name="Tableau > Child 1" description="" contentPermissions="ManagedByOwner" parentId="1d0304cd-3796-429f-b815-7258370b9b74" />
</projects>
</tsResponse>
</tsResponse>
4 changes: 2 additions & 2 deletions test/assets/project_update.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<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">
<project id="1d0304cd-3796-429f-b815-7258370b9b74" name="Test Project" description="Project created for testing" contentPermissions="LockedToProject" />
</tsResponse>
<project id="1d0304cd-3796-429f-b815-7258370b9b74" name="Test Project" description="Project created for testing" contentPermissions="LockedToProject" parentId="9a8f2265-70f3-4494-96c5-e5949d7a1120" />
</tsResponse>
30 changes: 15 additions & 15 deletions test/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,23 @@ def test_get(self):
m.get(self.baseurl, text=response_xml)
all_projects, pagination_item = self.server.projects.get()

self.assertEqual(2, pagination_item.total_available)
self.assertEqual(3, pagination_item.total_available)
self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', all_projects[0].id)
self.assertEqual('default', all_projects[0].name)
self.assertEqual('The default project that was automatically created by Tableau.',
all_projects[0].description)
self.assertEqual('ManagedByOwner', all_projects[0].content_permissions)
self.assertEqual(None, all_projects[0].parent_id)

self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', all_projects[1].id)
self.assertEqual('Tableau', all_projects[1].name)
self.assertEqual('ManagedByOwner', all_projects[1].content_permissions)
self.assertEqual(None, all_projects[1].parent_id)

self.assertEqual('4cc52973-5e3a-4d1f-a4fb-5b5f73796edf', all_projects[2].id)
self.assertEqual('Tableau > Child 1', all_projects[2].name)
self.assertEqual('ManagedByOwner', all_projects[2].content_permissions)
self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', all_projects[2].parent_id)

def test_get_before_signin(self):
self.server._auth_token = None
Expand All @@ -55,27 +62,18 @@ def test_update(self):
response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.put(self.baseurl + '/1d0304cd-3796-429f-b815-7258370b9b74', text=response_xml)
single_project = TSC.ProjectItem(name='Test Project', content_permissions='LockedToProject',
description='Project created for testing')
single_project = TSC.ProjectItem(name='Test Project',
content_permissions='LockedToProject',
description='Project created for testing',
parent_id='9a8f2265-70f3-4494-96c5-e5949d7a1120')
single_project._id = '1d0304cd-3796-429f-b815-7258370b9b74'
single_project = self.server.projects.update(single_project)

self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', single_project.id)
self.assertEqual('Test Project', single_project.name)
self.assertEqual('Project created for testing', single_project.description)
self.assertEqual('LockedToProject', single_project.content_permissions)

def test_update_copy_fields(self):
with open(UPDATE_XML, 'rb') as f:
response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.put(self.baseurl + '/1d0304cd-3796-429f-b815-7258370b9b74', text=response_xml)
single_project = TSC.ProjectItem('test')
single_project._id = '1d0304cd-3796-429f-b815-7258370b9b74'
single_project._permissions = 'Test to check if permissions copied over.'
updated_project = self.server.projects.update(single_project)

self.assertEqual(single_project._permissions, updated_project._permissions)
self.assertEqual('9a8f2265-70f3-4494-96c5-e5949d7a1120', single_project.parent_id)

def test_update_missing_id(self):
single_project = TSC.ProjectItem('test')
Expand All @@ -88,12 +86,14 @@ def test_create(self):
m.post(self.baseurl, text=response_xml)
new_project = TSC.ProjectItem(name='Test Project', description='Project created for testing')
new_project.content_permissions = 'ManagedByOwner'
new_project.parent_id = '9a8f2265-70f3-4494-96c5-e5949d7a1120'
new_project = self.server.projects.create(new_project)

self.assertEqual('ccbea03f-77c4-4209-8774-f67bc59c3cef', new_project.id)
self.assertEqual('Test Project', new_project.name)
self.assertEqual('Project created for testing', new_project.description)
self.assertEqual('ManagedByOwner', new_project.content_permissions)
self.assertEqual('9a8f2265-70f3-4494-96c5-e5949d7a1120', new_project.parent_id)

def test_create_missing_name(self):
self.assertRaises(ValueError, TSC.ProjectItem, '')
5 changes: 5 additions & 0 deletions test/test_project_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ def test_invalid_content_permissions(self):
project = TSC.ProjectItem("proj")
with self.assertRaises(ValueError):
project.content_permissions = "Hello"

def test_parent_id(self):
project = TSC.ProjectItem("proj")
project.parent_id = "foo"
self.assertEqual(project.parent_id, "foo")
0