diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index 48f27c60c..0f5ddb113 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -1,5 +1,7 @@ import logging import xml.etree.ElementTree as ET +from .property_decorators import property_is_enum, property_not_empty, property_not_nullable +from ..datetime_helpers import parse_datetime from typing import Optional from defusedxml.ElementTree import fromstring @@ -38,6 +40,11 @@ def __init__( self._samples: Optional[bool] = samples self._owner_id: Optional[str] = None + self._created_at = None + self._owner_name: Optional[str] = None + self._top_level_project: Optional[bool] = None + self._updated_at = None + self._permissions = None self._default_workbook_permissions = None self._default_datasource_permissions = None @@ -122,6 +129,18 @@ def default_table_permissions(self): def id(self) -> Optional[str]: return self._id + @property + def created_at(self): + return self._created_at + + @property + def description(self): + return self._description + + @description.setter + def description(self, value): + self._description = value + @property def name(self) -> Optional[str]: return self._name @@ -130,6 +149,26 @@ def name(self) -> Optional[str]: def name(self, value: str) -> None: self._name = value + @property + def owner_name(self): + return self._owner_name + + @property + def parent_id(self): + return self._parent_id + + @parent_id.setter + def parent_id(self, value): + self._parent_id = value + + @property + def top_level_project(self): + return self._top_level_project + + @property + def updated_at(self): + return self._updated_at + @property def owner_id(self) -> Optional[str]: return self._owner_id @@ -142,33 +181,7 @@ def is_default(self): return self.name.lower() == "default" def _parse_common_tags(self, project_xml, ns): - if not isinstance(project_xml, ET.Element): - project_xml = fromstring(project_xml).find(".//t:project", namespaces=ns) - - if project_xml is not None: - ( - _, - 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, parent_id, owner_id): - if project_id is not None: - self._id = project_id - if name: - self._name = name - if description: - self.description = description - if content_permissions: - self._content_permissions = content_permissions - if parent_id: - self.parent_id = parent_id - if owner_id: - self._owner_id = owner_id + return ProjectItem.from_xml(ProjectItem, project_xml, ns) def _set_permissions(self, permissions): self._permissions = permissions @@ -188,25 +201,40 @@ def from_response(cls, resp, ns) -> list["ProjectItem"]: all_project_xml = parsed_response.findall(".//t:project", namespaces=ns) for project_xml in all_project_xml: - project_item = cls.from_xml(project_xml) + project_item = cls.from_xml(project_xml, ns) all_project_items.append(project_item) return all_project_items @classmethod - def from_xml(cls, project_xml, namespace=None) -> "ProjectItem": + def from_xml(cls, project_xml, ns) -> "ProjectItem": project_item = cls() - project_item._set_values(*cls._parse_element(project_xml)) + if project_xml.get("contentPermissions", None): + project_item._content_permissions = project_xml.get("contentPermissions") + if project_xml.get("createdAt", None): + project_item._created_at = parse_datetime(project_xml.get("createdAt")) + if project_xml.get("description", None): + project_item._description = project_xml.get("description") + if project_xml.get("id", None): + project_item._id = project_xml.get("id") + if project_xml.get("name", None): + project_item._name = project_xml.get("name") + if project_xml.get("parentProjectId", None): + project_item._parent_id = project_xml.get("parentProjectId") + if project_xml.get("topLevelProject", None): + project_item._top_level_project = ProjectItem.string_to_bool(project_xml.get("topLevelProject")) + if project_xml.get("updatedAt", None): + project_item._updated_at = parse_datetime(project_xml.get("updatedAt")) + + if project_item.parent_id is not None: + project_item._top_level_project = False + + owner_elem = project_xml.find(".//t:owner", ns) + if owner_elem is not None: + project_item._owner_id = owner_elem.get("id", None) + project_item._owner_name = owner_elem.get("name", None) + return project_item - @staticmethod - def _parse_element(project_xml): - id = project_xml.get("id", None) - name = project_xml.get("name", None) - description = project_xml.get("description", None) - content_permissions = project_xml.get("contentPermissions", None) - parent_id = project_xml.get("parentProjectId", None) - owner_id = None - for owner in project_xml: - owner_id = owner.get("id", None) - - return id, name, description, content_permissions, parent_id, owner_id + # Used to convert string represented boolean to a boolean type + def string_to_bool(s): + return s.lower() == "true" diff --git a/tableauserverclient/server/endpoint/projects_endpoint.py b/tableauserverclient/server/endpoint/projects_endpoint.py index 74bb865c7..ffda4ea7f 100644 --- a/tableauserverclient/server/endpoint/projects_endpoint.py +++ b/tableauserverclient/server/endpoint/projects_endpoint.py @@ -1,3 +1,6 @@ +from .endpoint import Endpoint, api, parameter_added_in +from .exceptions import MissingRequiredFieldError + import logging from tableauserverclient.server.endpoint.default_permissions_endpoint import _DefaultPermissionsEndpoint @@ -5,8 +8,7 @@ from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint from tableauserverclient.server import RequestFactory, RequestOptions -from tableauserverclient.models.permissions_item import PermissionsRule -from tableauserverclient.models import ProjectItem, PaginationItem, Resource +from tableauserverclient.models import PaginationItem, PermissionsRule, ProjectItem, Resource from typing import Optional, TYPE_CHECKING @@ -33,7 +35,7 @@ def baseurl(self) -> str: @api(version="2.0") def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[ProjectItem], PaginationItem]: logger.info("Querying all projects on site") - url = self.baseurl + url = self.baseurl + "?fields=_all_" server_response = self.get_request(url, req_options) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) all_project_items = ProjectItem.from_response(server_response.content, self.parent_srv.namespace) @@ -49,13 +51,14 @@ def delete(self, project_id: str) -> None: logger.info(f"Deleted single project (ID: {project_id})") @api(version="2.0") + @parameter_added_in(publish_samples="2.5") def update(self, project_item: ProjectItem, samples: bool = False) -> ProjectItem: if not project_item.id: error = "Project item missing ID." raise MissingRequiredFieldError(error) - params = {"params": {RequestOptions.Field.PublishSamples: samples}} url = f"{self.baseurl}/{project_item.id}" + params = {"params": {RequestOptions.Field.PublishSamples: samples}} update_req = RequestFactory.Project.update_req(project_item) server_response = self.put_request(url, update_req, XML_CONTENT_TYPE, params) logger.info(f"Updated project item (ID: {project_item.id})") diff --git a/test/assets/project_get.xml b/test/assets/project_get.xml index 7898c8c13..38e64da50 100644 --- a/test/assets/project_get.xml +++ b/test/assets/project_get.xml @@ -1,9 +1,11 @@ - - + + - + + - + + - + \ No newline at end of file diff --git a/test/test_project.py b/test/test_project.py index 430db84b2..8ca662855 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -4,6 +4,7 @@ import requests_mock import tableauserverclient as TSC +from tableauserverclient.datetime_helpers import format_datetime from tableauserverclient import GroupItem from ._utils import read_xml_asset, asset @@ -42,6 +43,7 @@ def test_get(self) -> None: self.assertEqual("ManagedByOwner", all_projects[0].content_permissions) self.assertEqual(None, all_projects[0].parent_id) self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", all_projects[0].owner_id) + self.assertEqual("_system", all_projects[0].owner_name) self.assertEqual("1d0304cd-3796-429f-b815-7258370b9b74", all_projects[1].id) self.assertEqual("Tableau", all_projects[1].name) @@ -54,6 +56,7 @@ def test_get(self) -> None: self.assertEqual("ManagedByOwner", all_projects[2].content_permissions) self.assertEqual("1d0304cd-3796-429f-b815-7258370b9b74", all_projects[2].parent_id) self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", all_projects[2].owner_id) + self.assertEqual("testadmin", all_projects[2].owner_name) def test_get_before_signin(self) -> None: self.server._auth_token = None