Merge branch 'CURA-8138_check_user_access_DF' of github.com:Ultimaker/Cura

This commit is contained in:
Jaime van Kessel 2021-07-12 13:21:08 +02:00
commit 5aa4a6f713
No known key found for this signature in database
GPG key ID: 3710727397403C91
5 changed files with 85 additions and 6 deletions

View file

@ -22,6 +22,7 @@ from .DFFileUploader import DFFileUploader
from .DFLibraryFileUploadRequest import DFLibraryFileUploadRequest
from .DFLibraryFileUploadResponse import DFLibraryFileUploadResponse
from .DFPrintJobUploadRequest import DFPrintJobUploadRequest
from .DigitalFactoryFeatureBudgetResponse import DigitalFactoryFeatureBudgetResponse
from .DigitalFactoryFileResponse import DigitalFactoryFileResponse
from .DigitalFactoryProjectResponse import DigitalFactoryProjectResponse
from .PaginationLinks import PaginationLinks
@ -57,6 +58,27 @@ class DigitalFactoryApiClient:
self._projects_pagination_mgr = PaginationManager(limit = projects_limit_per_page) if projects_limit_per_page else None # type: Optional[PaginationManager]
def checkUserHasAccess(self, callback: Callable) -> None:
"""Checks if the user has any sort of access to the digital library.
A user is considered to have access if the max-# of private projects is greater then 0 (or -1 for unlimited).
"""
def callbackWrap(response: Optional[Any] = None, *args, **kwargs) -> None:
if (response is not None and isinstance(response, DigitalFactoryFeatureBudgetResponse) and
response.library_max_private_projects is not None):
callback(
response.library_max_private_projects == -1 or # Note: -1 is unlimited
response.library_max_private_projects > 0)
else:
Logger.warning(f"Digital Factory: Response is not a feature budget, likely an error: {str(response)}")
callback(False)
self._http.get(f"{self.CURA_API_ROOT}/feature_budgets",
scope = self._scope,
callback = self._parseCallback(callbackWrap, DigitalFactoryFeatureBudgetResponse, callbackWrap),
error_callback = callbackWrap,
timeout = self.DEFAULT_REQUEST_TIMEOUT)
def getProject(self, library_project_id: str, on_finished: Callable[[DigitalFactoryProjectResponse], Any], failed: Callable) -> None:
"""
Retrieves a digital factory project by its library project id.

View file

@ -89,6 +89,9 @@ class DigitalFactoryController(QObject):
uploadFileError = Signal()
uploadFileFinished = Signal()
"""Signal to inform about the state of user access."""
userAccessStateChanged = pyqtSignal(bool)
def __init__(self, application: CuraApplication) -> None:
super().__init__(parent = None)
@ -106,6 +109,7 @@ class DigitalFactoryController(QObject):
self._has_more_projects_to_load = False
self._account = self._application.getInstance().getCuraAPI().account # type: Account
self._account.loginStateChanged.connect(self._onLoginStateChanged)
self._current_workspace_information = CuraApplication.getInstance().getCurrentWorkspaceInformation()
# Initialize the project model
@ -131,6 +135,8 @@ class DigitalFactoryController(QObject):
self._application.engineCreatedSignal.connect(self._onEngineCreated)
self._application.initializationFinished.connect(self._applicationInitializationFinished)
self._user_has_access = False
def clear(self) -> None:
self._project_model.clearProjects()
self._api.clear()
@ -143,16 +149,24 @@ class DigitalFactoryController(QObject):
self.setSelectedProjectIndex(-1)
def _onLoginStateChanged(self, logged_in: bool) -> None:
def callback(has_access, **kwargs):
self._user_has_access = has_access
self.userAccessStateChanged.emit(logged_in)
self._api.checkUserHasAccess(callback)
def userAccountHasLibraryAccess(self) -> bool:
"""
Checks whether the currently logged in user account has access to the Digital Library
:return: True if the user account has Digital Library access, else False
"""
subscriptions = [] # type: List[Dict[str, Any]]
if self._account.userProfile:
subscriptions = self._account.userProfile.get("subscriptions", [])
return len(subscriptions) > 0
if len(subscriptions) > 0:
return True
return self._user_has_access
def initialize(self, preselected_project_id: Optional[str] = None) -> None:
self.clear()

View file

@ -0,0 +1,43 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .BaseModel import BaseModel
from typing import Optional
class DigitalFactoryFeatureBudgetResponse(BaseModel):
"""Class representing the capabilities of a user account for Digital Library.
NOTE: For each max_..._projects fields, '-1' means unlimited!
"""
def __init__(self,
library_can_use_business_value: Optional[bool] = False,
library_can_use_comments: Optional[bool] = False,
library_can_use_status: Optional[bool] = False,
library_can_use_tags: Optional[bool] = False,
library_can_use_technical_requirements: Optional[bool] = False,
library_max_organization_shared_projects: Optional[int] = False, # -1 means unlimited
library_max_private_projects: Optional[int] = False, # -1 means unlimited
library_max_team_shared_projects: Optional[int] = False, # -1 means unlimited
**kwargs) -> None:
self.library_can_use_business_value = library_can_use_business_value
self.library_can_use_comments = library_can_use_comments
self.library_can_use_status = library_can_use_status
self.library_can_use_tags = library_can_use_tags
self.library_can_use_technical_requirements = library_can_use_technical_requirements
self.library_max_organization_shared_projects = library_max_organization_shared_projects # -1 means unlimited
self.library_max_private_projects = library_max_private_projects # -1 means unlimited
self.library_max_team_shared_projects = library_max_team_shared_projects # -1 means unlimited
super().__init__(**kwargs)
def __repr__(self) -> str:
return "max private: {}, max org: {}, max team: {}".format(
self.library_max_private_projects,
self.library_max_organization_shared_projects,
self.library_max_team_shared_projects)
# Validates the model, raising an exception if the model is invalid.
def validate(self) -> None:
super().validate()
# No validation for now, as the response can be "data: []", which should be interpreted as all False and 0's

View file

@ -22,7 +22,7 @@ class DigitalFactoryFileProvider(FileProvider):
self._dialog = None
self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account
self._account.loginStateChanged.connect(self._onLoginStateChanged)
self._controller.userAccessStateChanged.connect(self._onUserAccessStateChanged)
self.enabled = self._account.isLoggedIn and self._controller.userAccountHasLibraryAccess()
self.priority = 10
@ -53,7 +53,7 @@ class DigitalFactoryFileProvider(FileProvider):
if not self._dialog:
Logger.log("e", "Unable to create the Digital Library Open dialog.")
def _onLoginStateChanged(self, logged_in: bool) -> None:
def _onUserAccessStateChanged(self, logged_in: bool) -> None:
"""
Sets the enabled status of the DigitalFactoryFileProvider according to the account's login status
:param logged_in: The new login status

View file

@ -45,7 +45,7 @@ class DigitalFactoryOutputDevice(ProjectOutputDevice):
self._writing = False
self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account
self._account.loginStateChanged.connect(self._onLoginStateChanged)
self._controller.userAccessStateChanged.connect(self._onUserAccessStateChanged)
self.enabled = self._account.isLoggedIn and self._controller.userAccountHasLibraryAccess()
self._current_workspace_information = CuraApplication.getInstance().getCurrentWorkspaceInformation()
@ -97,7 +97,7 @@ class DigitalFactoryOutputDevice(ProjectOutputDevice):
if not self._dialog:
Logger.log("e", "Unable to create the Digital Library Save dialog.")
def _onLoginStateChanged(self, logged_in: bool) -> None:
def _onUserAccessStateChanged(self, logged_in: bool) -> None:
"""
Sets the enabled status of the DigitalFactoryOutputDevice according to the account's login status
:param logged_in: The new login status