diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 710f0b86983..c0efd8b145e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,11 @@ repos: rev: v2.13.6 hooks: - id: jshint + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.7 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format + args: ["--line-length=79"] diff --git a/Dockerfile b/Dockerfile index 189b0e998b9..15429b0980a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ RUN apk add --no-cache --virtual .run-deps \ libpq-dev \ libffi \ libev \ + libev-dev \ libevent \ && yarn global add bower \ && mkdir -p /var/www \ diff --git a/addons/base/__init__.py b/addons/base/__init__.py index 26219a8fee2..30fd8751cda 100644 --- a/addons/base/__init__.py +++ b/addons/base/__init__.py @@ -1,15 +1,16 @@ from django.db.models import options -default_app_config = 'addons.base.apps.BaseAddonAppConfig' + +default_app_config = "addons.base.apps.BaseAddonAppConfig" # Patch to make abstractproperties overridable by djangofields -if 'add_field' not in options.DEFAULT_NAMES: - options.DEFAULT_NAMES += ('add_field', ) +if "add_field" not in options.DEFAULT_NAMES: + options.DEFAULT_NAMES += ("add_field",) original_add_field = options.Options.add_field def add_field_patched(self, field, **kwargs): prop = getattr(field.model, field.name, None) - if prop and getattr(prop, '__isabstractmethod__', None): + if prop and getattr(prop, "__isabstractmethod__", None): setattr(field.model, field.name, None) return original_add_field(field.model._meta, field, **kwargs) diff --git a/addons/base/apps.py b/addons/base/apps.py index 66038377f9a..256fd27287a 100644 --- a/addons/base/apps.py +++ b/addons/base/apps.py @@ -13,19 +13,20 @@ def _is_image(filename): mtype, _ = mimetypes.guess_type(filename) - return mtype and mtype.startswith('image') + return mtype and mtype.startswith("image") + NODE_SETTINGS_TEMPLATE_DEFAULT = os.path.join( settings.TEMPLATES_PATH, - 'project', - 'addon', - 'node_settings_default.mako', + "project", + "addon", + "node_settings_default.mako", ) USER_SETTINGS_TEMPLATE_DEFAULT = os.path.join( settings.TEMPLATES_PATH, - 'profile', - 'user_settings_default.mako', + "profile", + "user_settings_default.mako", ) @@ -42,16 +43,17 @@ def _root_folder(node_settings, auth, **kwargs): permissions=auth, nodeUrl=node.url, nodeApiUrl=node.api_url, - private_key=kwargs.get('view_only', None), + private_key=kwargs.get("view_only", None), ) return [root] - _root_folder.__name__ = f'{addon_short_name}_root_folder' + + _root_folder.__name__ = f"{addon_short_name}_root_folder" return _root_folder class BaseAddonAppConfig(AppConfig): - name = 'addons.base' - label = 'addons_base' + name = "addons.base" + label = "addons_base" actions = tuple() user_settings = None @@ -81,26 +83,20 @@ def __init__(self, *args, **kwargs): paths.append(os.path.dirname(self.user_settings_template)) if self.node_settings_template: paths.append(os.path.dirname(self.node_settings_template)) - template_dirs = list( - { - path - for path in paths - if os.path.exists(path) - } - ) + template_dirs = list({path for path in paths if os.path.exists(path)}) if template_dirs: self.template_lookup = TemplateLookup( directories=template_dirs, default_filters=[ - 'unicode', # default filter; must set explicitly when overriding - 'temp_ampersand_fixer', + "unicode", # default filter; must set explicitly when overriding + "temp_ampersand_fixer", # FIXME: Temporary workaround for data stored in wrong format in DB. Unescape it before it gets re-escaped by Markupsafe. See [#OSF-4432] - 'h', + "h", ], imports=[ - 'from website.util.sanitize import temp_ampersand_fixer', + "from website.util.sanitize import temp_ampersand_fixer", # FIXME: Temporary workaround for data stored in wrong format in DB. Unescape it before it gets re-escaped by Markupsafe. See [#OSF-4432] - ] + ], ) else: self.template_lookup = None @@ -119,8 +115,8 @@ def icon(self): try: return self._icon except Exception: - static_path = os.path.join('addons', self.short_name, 'static') - static_files = glob.glob(os.path.join(static_path, 'comicon.*')) + static_path = os.path.join("addons", self.short_name, "static") + static_files = glob.glob(os.path.join(static_path, "comicon.*")) image_files = [ os.path.split(filename)[1] for filename in static_files @@ -144,22 +140,24 @@ def _static_url(self, filename): :return str: Static URL for file """ - if filename.startswith('/'): + if filename.startswith("/"): return filename - return '/static/addons/{addon}/{filename}'.format( + return "/static/addons/{addon}/{filename}".format( addon=self.short_name, filename=filename, ) def to_json(self): return { - 'short_name': self.short_name, - 'full_name': self.full_name, - 'capabilities': self.short_name in settings.ADDON_CAPABILITIES, - 'addon_capabilities': settings.ADDON_CAPABILITIES.get(self.short_name), - 'icon': self.icon_url, - 'has_page': 'page' in self.views, - 'has_widget': 'widget' in self.views, + "short_name": self.short_name, + "full_name": self.full_name, + "capabilities": self.short_name in settings.ADDON_CAPABILITIES, + "addon_capabilities": settings.ADDON_CAPABILITIES.get( + self.short_name + ), + "icon": self.icon_url, + "has_page": "page" in self.views, + "has_widget": "widget" in self.views, } # Override Appconfig diff --git a/addons/base/exceptions.py b/addons/base/exceptions.py index 3edda785149..37a85a37d83 100644 --- a/addons/base/exceptions.py +++ b/addons/base/exceptions.py @@ -24,4 +24,5 @@ class DoesNotExist(AddonError): class NotApplicableError(AddonError): """This exception is used by non-storage and/or non-oauth add-ons when they don't need or have certain features.""" + pass diff --git a/addons/base/generic_views.py b/addons/base/generic_views.py index d7eb4da6ced..edaac454ea7 100644 --- a/addons/base/generic_views.py +++ b/addons/base/generic_views.py @@ -15,17 +15,18 @@ def import_auth(addon_short_name, Serializer): - @must_have_addon(addon_short_name, 'user') - @must_have_addon(addon_short_name, 'node') + @must_have_addon(addon_short_name, "user") + @must_have_addon(addon_short_name, "node") @must_have_permission(permissions.WRITE) def _import_auth(auth, node_addon, user_addon, **kwargs): - """Import add-on credentials from the currently logged-in user to a node. - """ + """Import add-on credentials from the currently logged-in user to a node.""" external_account = ExternalAccount.load( - request.json['external_account_id'] + request.json["external_account_id"] ) - if not user_addon.external_accounts.filter(id=external_account.id).exists(): + if not user_addon.external_accounts.filter( + id=external_account.id + ).exists(): raise HTTPError(http_status.HTTP_403_FORBIDDEN) try: @@ -36,10 +37,11 @@ def _import_auth(auth, node_addon, user_addon, **kwargs): node_addon.save() return { - 'result': Serializer().serialize_settings(node_addon, auth.user), - 'message': 'Successfully imported credentials from profile.', + "result": Serializer().serialize_settings(node_addon, auth.user), + "message": "Successfully imported credentials from profile.", } - _import_auth.__name__ = f'{addon_short_name}_import_auth' + + _import_auth.__name__ = f"{addon_short_name}_import_auth" return _import_auth @@ -49,74 +51,80 @@ def _account_list(auth): user_settings = auth.user.get_addon(addon_short_name) serializer = Serializer(user_settings=user_settings) return serializer.serialized_user_settings - _account_list.__name__ = f'{addon_short_name}_account_list' + + _account_list.__name__ = f"{addon_short_name}_account_list" return _account_list def folder_list(addon_short_name, addon_full_name, get_folders): # TODO [OSF-6678]: Generalize this for API use after node settings have been refactored - @must_have_addon(addon_short_name, 'node') + @must_have_addon(addon_short_name, "node") @must_be_addon_authorizer(addon_short_name) def _folder_list(node_addon, **kwargs): """Returns a list of folders""" if not node_addon.has_auth: raise HTTPError(http_status.HTTP_403_FORBIDDEN) - folder_id = request.args.get('folderId') + folder_id = request.args.get("folderId") return get_folders(node_addon, folder_id) - _folder_list.__name__ = f'{addon_short_name}_folder_list' + + _folder_list.__name__ = f"{addon_short_name}_folder_list" return _folder_list def get_config(addon_short_name, Serializer): @must_be_logged_in - @must_have_addon(addon_short_name, 'node') + @must_have_addon(addon_short_name, "node") @must_be_valid_project @must_have_permission(permissions.WRITE) def _get_config(node_addon, auth, **kwargs): """API that returns the serialized node settings.""" return { - 'result': Serializer().serialize_settings( - node_addon, - auth.user - ) + "result": Serializer().serialize_settings(node_addon, auth.user) } - _get_config.__name__ = f'{addon_short_name}_get_config' + + _get_config.__name__ = f"{addon_short_name}_get_config" return _get_config def set_config(addon_short_name, addon_full_name, Serializer, set_folder): @must_not_be_registration - @must_have_addon(addon_short_name, 'user') - @must_have_addon(addon_short_name, 'node') + @must_have_addon(addon_short_name, "user") + @must_have_addon(addon_short_name, "node") @must_be_addon_authorizer(addon_short_name) @must_have_permission(permissions.WRITE) def _set_config(node_addon, user_addon, auth, **kwargs): """View for changing a node's linked folder.""" - folder = request.json.get('selected') + folder = request.json.get("selected") set_folder(node_addon, folder, auth) path = node_addon.folder_path return { - 'result': { - 'folder': { - 'name': path.replace('All Files', '') if path != '/' else f'/ (Full {addon_full_name})', - 'path': path, + "result": { + "folder": { + "name": path.replace("All Files", "") + if path != "/" + else f"/ (Full {addon_full_name})", + "path": path, }, - 'urls': Serializer(node_settings=node_addon).addon_serialized_urls, + "urls": Serializer( + node_settings=node_addon + ).addon_serialized_urls, }, - 'message': 'Successfully updated settings.', + "message": "Successfully updated settings.", } - _set_config.__name__ = f'{addon_short_name}_set_config' + + _set_config.__name__ = f"{addon_short_name}_set_config" return _set_config def deauthorize_node(addon_short_name): @must_not_be_registration - @must_have_addon(addon_short_name, 'node') + @must_have_addon(addon_short_name, "node") @must_have_permission(permissions.WRITE) def _deauthorize_node(auth, node_addon, **kwargs): node_addon.deauthorize(auth=auth) node_addon.save() - _deauthorize_node.__name__ = f'{addon_short_name}_deauthorize_node' + + _deauthorize_node.__name__ = f"{addon_short_name}_deauthorize_node" return _deauthorize_node diff --git a/addons/base/logger.py b/addons/base/logger.py index edb80e25098..cb27265a7f9 100644 --- a/addons/base/logger.py +++ b/addons/base/logger.py @@ -1,11 +1,13 @@ import abc + class AddonNodeLogger: """Helper class for adding correctly-formatted addon logs to nodes. :param Node node: The node to add logs to :param Auth auth: Authorization of the person who did the action. """ + __metaclass__ = abc.ABCMeta @property @@ -14,13 +16,15 @@ def addon_short_name(self): pass def _log_params(self): - node_settings = self.node.get_addon(self.addon_short_name, is_deleted=True) + node_settings = self.node.get_addon( + self.addon_short_name, is_deleted=True + ) return { - 'project': self.node.parent_id, - 'node': self.node._primary_key, - 'folder_id': node_settings.folder_id, - 'folder_name': node_settings.folder_name, - 'folder': node_settings.folder_path + "project": self.node.parent_id, + "node": self.node._primary_key, + "folder_id": node_settings.folder_id, + "folder_name": node_settings.folder_name, + "folder": node_settings.folder_path, } def __init__(self, node, auth, path=None): @@ -39,24 +43,30 @@ def log(self, action, extra=None, save=False): params = self._log_params() # If logging a file-related action, add the file's view and download URLs if self.path: - params.update({ - 'urls': { - 'view': self.node.web_url_for('addon_view_or_download_file', path=self.path, provider=self.addon_short_name), - 'download': self.node.web_url_for( - 'addon_view_or_download_file', - path=self.path, - provider=self.addon_short_name - ) - }, - 'path': self.path, - }) + params.update( + { + "urls": { + "view": self.node.web_url_for( + "addon_view_or_download_file", + path=self.path, + provider=self.addon_short_name, + ), + "download": self.node.web_url_for( + "addon_view_or_download_file", + path=self.path, + provider=self.addon_short_name, + ), + }, + "path": self.path, + } + ) if extra: params.update(extra) self.node.add_log( - action=f'{self.addon_short_name}_{action}', + action=f"{self.addon_short_name}_{action}", params=params, - auth=self.auth + auth=self.auth, ) if save: self.node.save() diff --git a/addons/base/models.py b/addons/base/models.py index 46b2203cbb6..32f0b2a5c50 100644 --- a/addons/base/models.py +++ b/addons/base/models.py @@ -21,21 +21,19 @@ from website.oauth.signals import oauth_complete lookup = TemplateLookup( - directories=[ - settings.TEMPLATES_PATH - ], + directories=[settings.TEMPLATES_PATH], default_filters=[ - 'unicode', # default filter; must set explicitly when overriding + "unicode", # default filter; must set explicitly when overriding # FIXME: Temporary workaround for data stored in wrong format in DB. Unescape it before it # gets re-escaped by Markupsafe. See [#OSF-4432] - 'temp_ampersand_fixer', - 'h', + "temp_ampersand_fixer", + "h", ], imports=[ # FIXME: Temporary workaround for data stored in wrong format in DB. Unescape it before it # gets re-escaped by Markupsafe. See [#OSF-4432] - 'from website.util.sanitize import temp_ampersand_fixer', - ] + "from website.util.sanitize import temp_ampersand_fixer", + ], ) @@ -70,8 +68,8 @@ def undelete(self, save=True): def to_json(self, user): return { - 'addon_short_name': self.config.short_name, - 'addon_full_name': self.config.full_name, + "addon_short_name": self.config.short_name, + "addon_full_name": self.config.full_name, } ############# @@ -88,8 +86,13 @@ def on_delete(self): class BaseUserSettings(BaseAddonSettings): - owner = models.OneToOneField(OSFUser, related_name='%(app_label)s_user_settings', - blank=True, null=True, on_delete=models.CASCADE) + owner = models.OneToOneField( + OSFUser, + related_name="%(app_label)s_user_settings", + blank=True, + null=True, + on_delete=models.CASCADE, + ) class Meta: abstract = True @@ -112,33 +115,42 @@ def nodes_authorized(self): model = self.config.node_settings if not model: return [] - return [obj.owner for obj in model.objects.filter(user_settings=self, owner__is_deleted=False).select_related('owner')] + return [ + obj.owner + for obj in model.objects.filter( + user_settings=self, owner__is_deleted=False + ).select_related("owner") + ] @property def can_be_merged(self): - return hasattr(self, 'merge') + return hasattr(self, "merge") def to_json(self, user): ret = super().to_json(user) - ret['has_auth'] = self.has_auth - ret.update({ - 'nodes': [ - { - '_id': node._id, - 'url': node.url, - 'title': node.title, - 'registered': node.is_registration, - 'api_url': node.api_url - } - for node in self.nodes_authorized - ] - }) + ret["has_auth"] = self.has_auth + ret.update( + { + "nodes": [ + { + "_id": node._id, + "url": node.url, + "title": node.title, + "registered": node.is_registration, + "api_url": node.api_url, + } + for node in self.nodes_authorized + ] + } + ) return ret def __repr__(self): if self.owner: - return f'<{self.__class__.__name__} owned by user {self.owner._id}>' - return f'<{self.__class__.__name__} with no owner>' + return ( + f"<{self.__class__.__name__} owned by user {self.owner._id}>" + ) + return f"<{self.__class__.__name__} with no owner>" @oauth_complete.connect @@ -181,17 +193,23 @@ def has_auth(self): @property def external_accounts(self): """The user's list of ``ExternalAccount`` instances for this provider""" - return self.owner.external_accounts.filter(provider=self.oauth_provider.short_name) + return self.owner.external_accounts.filter( + provider=self.oauth_provider.short_name + ) def delete(self, save=True): - for account in self.external_accounts.filter(provider=self.config.short_name): + for account in self.external_accounts.filter( + provider=self.config.short_name + ): self.revoke_oauth_access(account, save=False) super().delete(save=save) def grant_oauth_access(self, node, external_account, metadata=None): """Give a node permission to use an ``ExternalAccount`` instance.""" # ensure the user owns the external_account - if not self.owner.external_accounts.filter(id=external_account.id).exists(): + if not self.owner.external_accounts.filter( + id=external_account.id + ).exists(): raise PermissionsError() metadata = metadata or {} @@ -220,13 +238,17 @@ def revoke_oauth_access(self, external_account, auth, save=True): """ for node in self.get_nodes_with_oauth_grants(external_account): try: - node.get_addon(external_account.provider, is_deleted=True).deauthorize(auth=auth) + node.get_addon( + external_account.provider, is_deleted=True + ).deauthorize(auth=auth) except AttributeError: # No associated addon settings despite oauth grant pass - if external_account.osfuser_set.count() == 1 and \ - external_account.osfuser_set.filter(id=auth.user.id).exists(): + if ( + external_account.osfuser_set.count() == 1 + and external_account.osfuser_set.filter(id=auth.user.id).exists() + ): # Only this user is using the account, so revoke remote access as well. self.revoke_remote_oauth_access(external_account) @@ -236,7 +258,7 @@ def revoke_oauth_access(self, external_account, auth, save=True): self.save() def revoke_remote_oauth_access(self, external_account): - """ Makes outgoing request to remove the remote oauth grant + """Makes outgoing request to remove the remote oauth grant stored by third-party provider. Individual addons must override this method, as it is addon-specific behavior. @@ -297,7 +319,7 @@ def get_attached_nodes(self, external_account): def merge(self, user_settings): """Merge `user_settings` into this instance""" if user_settings.__class__ is not self.__class__: - raise TypeError('Cannot merge different addons') + raise TypeError("Cannot merge different addons") for node_id, data in user_settings.oauth_grants.items(): if node_id not in self.oauth_grants: @@ -319,18 +341,20 @@ def merge(self, user_settings): config = settings.ADDONS_AVAILABLE_DICT[ self.oauth_provider.short_name ] - Model = config.models['nodesettings'] + Model = config.models["nodesettings"] except KeyError: pass else: - Model.objects.filter(user_settings=user_settings).update(user_settings=self) + Model.objects.filter(user_settings=user_settings).update( + user_settings=self + ) self.save() def to_json(self, user): ret = super().to_json(user) - ret['accounts'] = self.serializer( + ret["accounts"] = self.serializer( user_settings=self ).serialized_accounts @@ -341,10 +365,11 @@ def to_json(self, user): ############# def on_delete(self): - """When the user deactivates the addon, clear auth for connected nodes. - """ + """When the user deactivates the addon, clear auth for connected nodes.""" super().on_delete() - nodes = [AbstractNode.load(node_id) for node_id in self.oauth_grants.keys()] + nodes = [ + AbstractNode.load(node_id) for node_id in self.oauth_grants.keys() + ] for node in nodes: node_addon = node.get_addon(self.oauth_provider.short_name) if node_addon and node_addon.user_settings == self: @@ -352,8 +377,13 @@ def on_delete(self): class BaseNodeSettings(BaseAddonSettings): - owner = models.OneToOneField(AbstractNode, related_name='%(app_label)s_node_settings', - null=True, blank=True, on_delete=models.CASCADE) + owner = models.OneToOneField( + AbstractNode, + related_name="%(app_label)s_node_settings", + null=True, + blank=True, + on_delete=models.CASCADE, + ) class Meta: abstract = True @@ -379,18 +409,20 @@ def has_auth(self): def to_json(self, user): ret = super().to_json(user) - ret.update({ - 'user': { - 'permissions': self.owner.get_permissions(user) - }, - 'node': { - 'id': self.owner._id, - 'api_url': self.owner.api_url, - 'url': self.owner.url, - 'is_registration': self.owner.is_registration, - }, - 'node_settings_template': os.path.basename(self.config.node_settings_template), - }) + ret.update( + { + "user": {"permissions": self.owner.get_permissions(user)}, + "node": { + "id": self.owner._id, + "api_url": self.owner.api_url, + "url": self.owner.url, + "is_registration": self.owner.is_registration, + }, + "node_settings_template": os.path.basename( + self.config.node_settings_template + ), + } + ) return ret ############# @@ -455,30 +487,30 @@ def before_fork(self, node, user): :returns Alert message """ - if hasattr(self, 'user_settings'): + if hasattr(self, "user_settings"): if self.user_settings is None: return ( - f'Because you have not configured the {self.config.full_name} ' - 'add-on, your authentication will not be transferred to the forked ' - f'{node.project_or_component}. You may authorize and configure the ' - f'{self.config.full_name} add-on in the new fork on the settings ' - 'page.' + f"Because you have not configured the {self.config.full_name} " + "add-on, your authentication will not be transferred to the forked " + f"{node.project_or_component}. You may authorize and configure the " + f"{self.config.full_name} add-on in the new fork on the settings " + "page." ) elif self.user_settings and self.user_settings.owner == user: return ( - f'Because you have authorized the {self.config.full_name} add-on ' - f'for this {node.project_or_component}, forking it will also ' - 'transfer your authentication to the forked ' - f'{node.project_or_component}.' + f"Because you have authorized the {self.config.full_name} add-on " + f"for this {node.project_or_component}, forking it will also " + "transfer your authentication to the forked " + f"{node.project_or_component}." ) else: return ( - f'Because the {self.config.full_name} add-on has been authorized ' - 'by a different user, forking it will not transfer authentication ' - f'to the forked {node.project_or_component}. You may authorize and ' - f'configure the {self.config.full_name} add-on in the new fork on ' - 'the settings page.' + f"Because the {self.config.full_name} add-on has been authorized " + "by a different user, forking it will not transfer authentication " + f"to the forked {node.project_or_component}. You may authorize and " + f"configure the {self.config.full_name} add-on in the new fork on " + "the settings page." ) def after_fork(self, node, fork, user, save=True): @@ -536,9 +568,10 @@ def after_delete(self, user): # Archiver # ############ + class GenericRootNode: - path = '/' - name = '' + path = "/" + name = "" class BaseStorageAddon: @@ -553,66 +586,72 @@ class Meta: @property def archive_folder_name(self): - name = f'Archive of {self.config.full_name}' - folder_name = getattr(self, 'folder_name', '').lstrip('/').strip() + name = f"Archive of {self.config.full_name}" + folder_name = getattr(self, "folder_name", "").lstrip("/").strip() if folder_name: - name = name + f': {folder_name}' + name = name + f": {folder_name}" return name - def _get_fileobj_child_metadata(self, filenode, user, cookie=None, version=None): + def _get_fileobj_child_metadata( + self, filenode, user, cookie=None, version=None + ): from api.base.utils import waterbutler_api_url_for kwargs = {} if version: - kwargs['version'] = version + kwargs["version"] = version if cookie: - kwargs['cookie'] = cookie + kwargs["cookie"] = cookie elif user: - kwargs['cookie'] = user.get_or_create_cookie().decode() + kwargs["cookie"] = user.get_or_create_cookie().decode() metadata_url = waterbutler_api_url_for( self.owner._id, self.config.short_name, - path=filenode.get('path', '/'), + path=filenode.get("path", "/"), user=user, view_only=True, _internal=True, base_url=self.owner.osfstorage_region.waterbutler_url, - **kwargs + **kwargs, ) res = requests.get(metadata_url) if res.status_code != 200: - raise HTTPError(res.status_code, data={'error': res.json()}) + raise HTTPError(res.status_code, data={"error": res.json()}) # TODO: better throttling? time.sleep(1.0 / 5.0) - data = res.json().get('data', None) + data = res.json().get("data", None) if data: - return [child['attributes'] for child in data] + return [child["attributes"] for child in data] return [] - def _get_file_tree(self, filenode=None, user=None, cookie=None, version=None): + def _get_file_tree( + self, filenode=None, user=None, cookie=None, version=None + ): """ Recursively get file metadata """ filenode = filenode or { - 'path': '/', - 'kind': 'folder', - 'name': self.root_node.name, + "path": "/", + "kind": "folder", + "name": self.root_node.name, } - if filenode.get('kind') == 'file': + if filenode.get("kind") == "file": return filenode kwargs = { - 'version': version, - 'cookie': cookie, + "version": version, + "cookie": cookie, } - filenode['children'] = [ + filenode["children"] = [ self._get_file_tree(child, user, cookie=cookie) - for child in self._get_fileobj_child_metadata(filenode, user, **kwargs) + for child in self._get_fileobj_child_metadata( + filenode, user, **kwargs + ) ] return filenode @@ -620,9 +659,13 @@ def _get_file_tree(self, filenode=None, user=None, cookie=None, version=None): class BaseOAuthNodeSettings(BaseNodeSettings): # TODO: Validate this field to be sure it matches the provider's short_name # NOTE: Do not set this field directly. Use ``set_auth()`` - external_account = models.ForeignKey(ExternalAccount, null=True, blank=True, - related_name='%(app_label)s_node_settings', - on_delete=models.CASCADE) + external_account = models.ForeignKey( + ExternalAccount, + null=True, + blank=True, + related_name="%(app_label)s_node_settings", + on_delete=models.CASCADE, + ) # NOTE: Do not set this field directly. Use ``set_auth()`` # user_settings = fields.AbstractForeignField() @@ -666,24 +709,21 @@ def nodelogger(self): auth = Auth(self.user_settings.owner) self._logger_class = getattr( self, - '_logger_class', + "_logger_class", type( - f'{self.config.short_name.capitalize()}NodeLogger', + f"{self.config.short_name.capitalize()}NodeLogger", (logger.AddonNodeLogger,), - {'addon_short_name': self.config.short_name} - ) - ) - return self._logger_class( - node=self.owner, - auth=auth + {"addon_short_name": self.config.short_name}, + ), ) + return self._logger_class(node=self.owner, auth=auth) @property def complete(self): return bool( - self.has_auth and - self.external_account and - self.user_settings.verify_oauth_access( + self.has_auth + and self.external_account + and self.user_settings.verify_oauth_access( node=self.owner, external_account=self.external_account, ) @@ -692,8 +732,8 @@ def complete(self): @property def configured(self): return bool( - self.complete and - (self.folder_id or self.folder_name or self.folder_path) + self.complete + and (self.folder_id or self.folder_name or self.folder_path) ) @property @@ -702,9 +742,9 @@ def has_auth(self): return bool( self.user_settings and self.user_settings.has_auth ) and bool( - self.external_account and self.user_settings.verify_oauth_access( - node=self.owner, - external_account=self.external_account + self.external_account + and self.user_settings.verify_oauth_access( + node=self.owner, external_account=self.external_account ) ) @@ -724,7 +764,7 @@ def set_auth(self, external_account, user, metadata=None, log=True): user_settings.grant_oauth_access( node=self.owner, external_account=external_account, - metadata=metadata # metadata can be passed in when forking + metadata=metadata, # metadata can be passed in when forking ) user_settings.save() @@ -733,7 +773,7 @@ def set_auth(self, external_account, user, metadata=None, log=True): self.external_account = external_account if log: - self.nodelogger.log(action='node_authorized', save=True) + self.nodelogger.log(action="node_authorized", save=True) self.save() def deauthorize(self, auth=None, add_log=False): @@ -760,9 +800,9 @@ def before_remove_contributor_message(self, node, removed): """ if self.has_auth and self.user_settings.owner == removed: return ( - 'The {addon} add-on for this {category} is authenticated by {name}. ' - 'Removing this user will also remove write access to {addon} ' - 'unless another contributor re-authenticates the add-on.' + "The {addon} add-on for this {category} is authenticated by {name}. " + "Removing this user will also remove write access to {addon} " + "unless another contributor re-authenticates the add-on." ).format( addon=self.config.full_name, category=node.project_or_component, @@ -777,23 +817,24 @@ def after_remove_contributor(self, node, removed, auth=None): from owner. """ if self.user_settings and self.user_settings.owner == removed: - # Delete OAuth tokens - self.user_settings.oauth_grants[self.owner._id].pop(self.external_account._id) + self.user_settings.oauth_grants[self.owner._id].pop( + self.external_account._id + ) self.user_settings.save() self.clear_auth() message = ( 'Because the {addon} add-on for {category} "{title}" was authenticated ' - 'by {user}, authentication information has been deleted.' + "by {user}, authentication information has been deleted." ).format( addon=self.config.full_name, category=markupsafe.escape(node.category_display), title=markupsafe.escape(node.title), - user=markupsafe.escape(removed.fullname) + user=markupsafe.escape(removed.fullname), ) if not auth or auth.user != removed: - url = node.web_url_for('node_addons') + url = node.web_url_for("node_addons") message += ( ' You can re-authenticate on the add-ons page.' ).format(url=url) @@ -816,10 +857,14 @@ def after_fork(self, node, fork, user, save=True): metadata = None if self.complete: try: - metadata = self.user_settings.oauth_grants[node._id][self.external_account._id] + metadata = self.user_settings.oauth_grants[node._id][ + self.external_account._id + ] except (KeyError, AttributeError): pass - clone.set_auth(self.external_account, user, metadata=metadata, log=False) + clone.set_auth( + self.external_account, user, metadata=metadata, log=False + ) else: clone.clear_settings() if save: @@ -832,9 +877,9 @@ def before_register_message(self, node, user): """ if self.has_auth: return ( - 'The contents of {addon} add-ons cannot be registered at this time; ' - 'the {addon} add-on linked to this {category} will not be included ' - 'as part of this registration.' + "The contents of {addon} add-ons cannot be registered at this time; " + "the {addon} add-on linked to this {category} will not be included " + "as part of this registration." ).format( addon=self.config.full_name, category=node.project_or_component, @@ -844,12 +889,16 @@ def before_register_message(self, node, user): before_register = before_register_message def serialize_waterbutler_credentials(self): - raise NotImplementedError("BaseOAuthNodeSettings subclasses must implement a \ - 'serialize_waterbutler_credentials' method.") + raise NotImplementedError( + "BaseOAuthNodeSettings subclasses must implement a \ + 'serialize_waterbutler_credentials' method." + ) def serialize_waterbutler_settings(self): - raise NotImplementedError("BaseOAuthNodeSettings subclasses must implement a \ - 'serialize_waterbutler_settings' method.") + raise NotImplementedError( + "BaseOAuthNodeSettings subclasses must implement a \ + 'serialize_waterbutler_settings' method." + ) class BaseCitationsNodeSettings(BaseOAuthNodeSettings): @@ -878,11 +927,14 @@ def api(self): @property def complete(self): """Boolean indication of addon completeness""" - return bool(self.has_auth and self.user_settings.verify_oauth_access( - node=self.owner, - external_account=self.external_account, - metadata={'folder': self.list_id}, - )) + return bool( + self.has_auth + and self.user_settings.verify_oauth_access( + node=self.owner, + external_account=self.external_account, + metadata={"folder": self.list_id}, + ) + ) @property def root_folder(self): @@ -905,9 +957,9 @@ def folder_path(self): def fetch_folder_name(self): """Returns a displayable folder name""" if self.list_id is None: - return '' - elif self.list_id == 'ROOT': - return 'All Documents' + return "" + elif self.list_id == "ROOT": + return "All Documents" else: return self._fetch_folder_name @@ -930,10 +982,10 @@ def deauthorize(self, auth=None, add_log=True): """Remove user authorization from this node and log the event.""" if add_log: self.owner.add_log( - f'{self.provider_name}_node_deauthorized', + f"{self.provider_name}_node_deauthorized", params={ - 'project': self.owner.parent_id, - 'node': self.owner._id, + "project": self.owner.parent_id, + "node": self.owner._id, }, auth=auth, ) diff --git a/addons/base/serializer.py b/addons/base/serializer.py index e10fc5766d2..50cac053749 100644 --- a/addons/base/serializer.py +++ b/addons/base/serializer.py @@ -40,22 +40,23 @@ def credentials_owner(self): @property def serialized_node_settings(self): result = { - 'nodeHasAuth': self.node_settings.has_auth, - 'userIsOwner': self.user_is_owner, - 'urls': self.serialized_urls, + "nodeHasAuth": self.node_settings.has_auth, + "userIsOwner": self.user_is_owner, + "urls": self.serialized_urls, } if self.user_settings: - result['userHasAuth'] = self.user_settings.has_auth + result["userHasAuth"] = self.user_settings.has_auth else: - result['userHasAuth'] = False + result["userHasAuth"] = False if self.node_settings.has_auth: owner = self.credentials_owner if owner: - result['urls']['owner'] = web_url_for('profile_view_id', - uid=owner._primary_key) - result['ownerName'] = owner.fullname + result["urls"]["owner"] = web_url_for( + "profile_view_id", uid=owner._primary_key + ) + result["ownerName"] = owner.fullname return result @property @@ -64,7 +65,6 @@ def serialized_user_settings(self): class OAuthAddonSerializer(AddonSerializer): - @property def credentials_owner(self): return self.user_settings.owner if self.user_settings else None @@ -76,8 +76,8 @@ def user_is_owner(self): user_accounts = self.user_settings.external_accounts.all() return bool( - self.node_settings.has_auth and - self.node_settings.external_account in user_accounts + self.node_settings.has_auth + and self.node_settings.external_account in user_accounts ) @property @@ -87,7 +87,7 @@ def serialized_urls(self): for url in self.REQUIRED_URLS: msg = f"addon_serialized_urls must include key '{url}'" assert url in ret, msg - ret.update({'settings': web_url_for('user_addons')}) + ret.update({"settings": web_url_for("user_addons")}) return ret @property @@ -100,9 +100,9 @@ def serialized_accounts(self): @property def serialized_user_settings(self): retval = super().serialized_user_settings - retval['accounts'] = [] + retval["accounts"] = [] if self.user_settings: - retval['accounts'] = self.serialized_accounts + retval["accounts"] = self.serialized_accounts return retval @@ -110,13 +110,13 @@ def serialize_account(self, external_account): if external_account is None: return None return { - 'id': external_account._id, - 'provider_id': external_account.provider_id, - 'provider_name': external_account.provider_name, - 'provider_short_name': external_account.provider, - 'display_name': external_account.display_name, - 'profile_url': external_account.profile_url, - 'nodes': [ + "id": external_account._id, + "provider_id": external_account.provider_id, + "provider_name": external_account.provider_name, + "provider_short_name": external_account.provider, + "display_name": external_account.display_name, + "profile_url": external_account.profile_url, + "nodes": [ self.serialize_granted_node(node) for node in self.user_settings.get_attached_nodes( external_account=external_account @@ -126,24 +126,30 @@ def serialize_account(self, external_account): @collect_auth def serialize_granted_node(self, node, auth): - node_settings = node.get_addon( self.user_settings.oauth_provider.short_name ) serializer = node_settings.serializer(node_settings=node_settings) urls = serializer.addon_serialized_urls - urls['view'] = node.url + urls["view"] = node.url return { - 'id': node._id, - 'title': node.title if node.can_view(auth) else None, - 'urls': urls, + "id": node._id, + "title": node.title if node.can_view(auth) else None, + "urls": urls, } class StorageAddonSerializer(OAuthAddonSerializer): - - REQUIRED_URLS = ('auth', 'importAuth', 'folders', 'files', 'config', 'deauthorize', 'accounts') + REQUIRED_URLS = ( + "auth", + "importAuth", + "folders", + "files", + "config", + "deauthorize", + "accounts", + ) @abc.abstractmethod def credentials_are_valid(self, user_settings, client=None): @@ -157,55 +163,65 @@ def serialize_settings(self, node_settings, current_user, client=None): user_settings = node_settings.user_settings self.node_settings = node_settings current_user_settings = current_user.get_addon(self.addon_short_name) - user_is_owner = user_settings is not None and user_settings.owner == current_user + user_is_owner = ( + user_settings is not None and user_settings.owner == current_user + ) - valid_credentials = self.credentials_are_valid(user_settings, client=client) + valid_credentials = self.credentials_are_valid( + user_settings, client=client + ) result = { - 'userIsOwner': user_is_owner, - 'nodeHasAuth': node_settings.has_auth, - 'urls': self.serialized_urls, - 'validCredentials': valid_credentials, - 'userHasAuth': current_user_settings is not None and current_user_settings.has_auth, + "userIsOwner": user_is_owner, + "nodeHasAuth": node_settings.has_auth, + "urls": self.serialized_urls, + "validCredentials": valid_credentials, + "userHasAuth": current_user_settings is not None + and current_user_settings.has_auth, } if node_settings.has_auth: # Add owner's profile URL - result['urls']['owner'] = web_url_for( - 'profile_view_id', - uid=user_settings.owner._id + result["urls"]["owner"] = web_url_for( + "profile_view_id", uid=user_settings.owner._id ) - result['ownerName'] = user_settings.owner.fullname + result["ownerName"] = user_settings.owner.fullname # Show available folders if node_settings.folder_id is None: - result['folder'] = {'name': None, 'path': None} + result["folder"] = {"name": None, "path": None} elif valid_credentials: - result['folder'] = self.serialized_folder(node_settings) + result["folder"] = self.serialized_folder(node_settings) return result class CitationsAddonSerializer(OAuthAddonSerializer): - - REQUIRED_URLS = ('importAuth', 'folders', 'config', 'deauthorize', 'accounts') + REQUIRED_URLS = ( + "importAuth", + "folders", + "config", + "deauthorize", + "accounts", + ) serialized_root_folder = { - 'name': 'All Documents', - 'provider_list_id': None, - 'id': 'ROOT', - 'parent_list_id': '__', - 'kind': 'folder', + "name": "All Documents", + "provider_list_id": None, + "id": "ROOT", + "parent_list_id": "__", + "kind": "folder", } @property def serialized_urls(self): external_account = self.node_settings.external_account ret = { - 'auth': api_url_for('oauth_connect', - service_name=self.addon_short_name), - 'files': self.node_settings.owner.url, + "auth": api_url_for( + "oauth_connect", service_name=self.addon_short_name + ), + "files": self.node_settings.owner.url, } if external_account and external_account.profile_url: - ret['owner'] = external_account.profile_url + ret["owner"] = external_account.profile_url ret.update(super().serialized_urls) return ret @@ -213,9 +229,7 @@ def serialized_urls(self): @property def serialized_node_settings(self): result = super().serialized_node_settings - result['folder'] = { - 'name': self.node_settings.fetch_folder_name - } + result["folder"] = {"name": self.node_settings.fetch_folder_name} return result @property @@ -224,14 +238,14 @@ def credentials_owner(self): def serialize_folder(self, folder): return { - 'data': folder, - 'kind': 'folder', - 'name': folder['name'], - 'id': folder['id'], - 'urls': { - 'fetch': self.node_settings.owner.api_url_for( - f'{self.addon_short_name}_citation_list', - list_id=folder['id'] + "data": folder, + "kind": "folder", + "name": folder["name"], + "id": folder["id"], + "urls": { + "fetch": self.node_settings.owner.api_url_for( + f"{self.addon_short_name}_citation_list", + list_id=folder["id"], ), }, } @@ -240,16 +254,24 @@ def serialize_folder(self, folder): def addon_serialized_urls(self): node = self.node_settings.owner return { - 'importAuth': node.api_url_for(f'{self.addon_short_name}_import_auth'), - 'folders': node.api_url_for(f'{self.addon_short_name}_citation_list'), - 'config': node.api_url_for(f'{self.addon_short_name}_set_config'), - 'deauthorize': node.api_url_for(f'{self.addon_short_name}_deauthorize_node'), - 'accounts': node.api_url_for(f'{self.addon_short_name}_account_list'), + "importAuth": node.api_url_for( + f"{self.addon_short_name}_import_auth" + ), + "folders": node.api_url_for( + f"{self.addon_short_name}_citation_list" + ), + "config": node.api_url_for(f"{self.addon_short_name}_set_config"), + "deauthorize": node.api_url_for( + f"{self.addon_short_name}_deauthorize_node" + ), + "accounts": node.api_url_for( + f"{self.addon_short_name}_account_list" + ), } def serialize_citation(self, citation): return { - 'csl': citation, - 'kind': 'file', - 'id': citation['id'], + "csl": citation, + "kind": "file", + "id": citation["id"], } diff --git a/addons/base/signals.py b/addons/base/signals.py index 2cb86aba118..4ae5eb6b150 100644 --- a/addons/base/signals.py +++ b/addons/base/signals.py @@ -2,6 +2,6 @@ import blinker signals = blinker.Namespace() -file_updated = signals.signal('file_updated') -file_viewed = signals.signal('file_viewed') -file_downloaded = signals.signal('file_downloaded') +file_updated = signals.signal("file_updated") +file_viewed = signals.signal("file_viewed") +file_downloaded = signals.signal("file_downloaded") diff --git a/addons/base/tests/base.py b/addons/base/tests/base.py index 406cbe0a8a5..6b93b0b108e 100644 --- a/addons/base/tests/base.py +++ b/addons/base/tests/base.py @@ -24,11 +24,12 @@ class AddonTestCase: - self.node_settings: AddonNodeSettings object for the addon """ + DISABLE_OUTGOING_CONNECTIONS = True - DB_NAME = getattr(django_settings, 'TEST_DB_ADDON_NAME', 'osf_addon') + DB_NAME = getattr(django_settings, "TEST_DB_ADDON_NAME", "osf_addon") ADDON_SHORT_NAME = None - OWNERS = ['user', 'node'] - NODE_USER_FIELD = 'user_settings' + OWNERS = ["user", "node"] + NODE_USER_FIELD = "user_settings" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -46,18 +47,23 @@ def create_project(self): return ProjectFactory(creator=self.user) def set_user_settings(self, settings): - raise NotImplementedError('Must define set_user_settings(self, settings) method') + raise NotImplementedError( + "Must define set_user_settings(self, settings) method" + ) def set_node_settings(self, settings): - raise NotImplementedError('Must define set_node_settings(self, settings) method') + raise NotImplementedError( + "Must define set_node_settings(self, settings) method" + ) def create_user_settings(self): - """Initialize user settings object if requested by `self.OWNERS`. - """ - if 'user' not in self.OWNERS: + """Initialize user settings object if requested by `self.OWNERS`.""" + if "user" not in self.OWNERS: return self.user.add_addon(self.ADDON_SHORT_NAME) - assert self.user.has_addon(self.ADDON_SHORT_NAME), f'{self.ADDON_SHORT_NAME} is not enabled' + assert self.user.has_addon( + self.ADDON_SHORT_NAME + ), f"{self.ADDON_SHORT_NAME} is not enabled" self.user_settings = self.user.get_addon(self.ADDON_SHORT_NAME) self.set_user_settings(self.user_settings) self.user_settings.save() @@ -66,23 +72,24 @@ def create_node_settings(self): """Initialize node settings object if requested by `self.OWNERS`, additionally linking to user settings if requested by `self.NODE_USER_FIELD`. """ - if 'node' not in self.OWNERS: + if "node" not in self.OWNERS: return self.project.add_addon(self.ADDON_SHORT_NAME, auth=Auth(self.user)) self.node_settings = self.project.get_addon(self.ADDON_SHORT_NAME) # User has imported their addon settings to this node if self.NODE_USER_FIELD: - setattr(self.node_settings, self.NODE_USER_FIELD, self.user_settings) + setattr( + self.node_settings, self.NODE_USER_FIELD, self.user_settings + ) self.set_node_settings(self.node_settings) self.node_settings.save() def setUp(self): - super().setUp() self.user = self.create_user() if not self.ADDON_SHORT_NAME: - raise ValueError('Must define ADDON_SHORT_NAME in the test class.') + raise ValueError("Must define ADDON_SHORT_NAME in the test class.") self.user.save() self.project = self.create_project() self.project.save() @@ -91,7 +98,6 @@ def setUp(self): class OAuthAddonTestCaseMixin: - @property def ExternalAccountFactory(self): raise NotImplementedError() @@ -112,4 +118,6 @@ def set_user_settings(self, settings): self.auth = Auth(self.user) def set_node_settings(self, settings): - self.user_settings.grant_oauth_access(self.project, self.external_account) + self.user_settings.grant_oauth_access( + self.project, self.external_account + ) diff --git a/addons/base/tests/logger.py b/addons/base/tests/logger.py index 9ec65021b1a..bef27a8d4cb 100644 --- a/addons/base/tests/logger.py +++ b/addons/base/tests/logger.py @@ -3,8 +3,8 @@ from framework.auth import Auth from osf_tests.factories import AuthUserFactory, ProjectFactory -class AddonNodeLoggerTestSuiteMixinBase: +class AddonNodeLoggerTestSuiteMixinBase: __metaclass__ = abc.ABCMeta @property @@ -27,21 +27,24 @@ def setUp(self): class StorageAddonNodeLoggerTestSuiteMixin(AddonNodeLoggerTestSuiteMixinBase): - def setUp(self): super().setUp() def test_log_file_added(self): - self.logger.log('file_added', save=True) + self.logger.log("file_added", save=True) last_log = self.node.logs.latest() - assert last_log.action == '{}_{}'.format(self.addon_short_name, 'file_added') + assert last_log.action == "{}_{}".format( + self.addon_short_name, "file_added" + ) def test_log_file_removed(self): - self.logger.log('file_removed', save=True) + self.logger.log("file_removed", save=True) last_log = self.node.logs.latest() - assert last_log.action == '{}_{}'.format(self.addon_short_name, 'file_removed') + assert last_log.action == "{}_{}".format( + self.addon_short_name, "file_removed" + ) def test_log_deauthorized_when_node_settings_are_deleted(self): node_settings = self.node.get_addon(self.addon_short_name) @@ -49,7 +52,7 @@ def test_log_deauthorized_when_node_settings_are_deleted(self): # sanity check assert node_settings.deleted - self.logger.log(action='node_deauthorized', save=True) + self.logger.log(action="node_deauthorized", save=True) last_log = self.node.logs.latest() - assert last_log.action == f'{self.addon_short_name}_node_deauthorized' + assert last_log.action == f"{self.addon_short_name}_node_deauthorized" diff --git a/addons/base/tests/models.py b/addons/base/tests/models.py index c20f834cf24..752eca784ca 100644 --- a/addons/base/tests/models.py +++ b/addons/base/tests/models.py @@ -12,13 +12,11 @@ from osf_tests.factories import ProjectFactory, UserFactory from tests.utils import mock_auth from addons.base import exceptions -from osf_tests.conftest import request_context pytestmark = pytest.mark.django_db class OAuthAddonModelTestSuiteMixinBase: - ___metaclass__ = abc.ABCMeta @property @@ -38,7 +36,6 @@ def ExternalAccountFactory(self): class OAuthAddonUserSettingTestSuiteMixin(OAuthAddonModelTestSuiteMixinBase): - def setUp(self): self.node = ProjectFactory() self.user = self.node.creator @@ -59,10 +56,11 @@ def test_merge_user_settings(self): other_account = self.ExternalAccountFactory() other_user.external_accounts.add(other_account) other_user_settings = other_user.get_or_add_addon(self.short_name) - other_node_settings = other_node.get_or_add_addon(self.short_name, auth=Auth(other_user)) + other_node_settings = other_node.get_or_add_addon( + self.short_name, auth=Auth(other_user) + ) other_node_settings.set_auth( - user=other_user, - external_account=other_account + user=other_user, external_account=other_account ) assert other_node_settings.has_auth @@ -86,19 +84,21 @@ def test_grant_oauth_access_no_metadata(self): ) self.user_settings.save() - assert self.user_settings.oauth_grants == {self.node._id: {self.external_account._id: {}}} + assert self.user_settings.oauth_grants == { + self.node._id: {self.external_account._id: {}} + } def test_grant_oauth_access_metadata(self): self.user_settings.grant_oauth_access( node=self.node, external_account=self.external_account, - metadata={'folder': 'fake_folder_id'} + metadata={"folder": "fake_folder_id"}, ) self.user_settings.save() assert self.user_settings.oauth_grants == { self.node._id: { - self.external_account._id: {'folder': 'fake_folder_id'} + self.external_account._id: {"folder": "fake_folder_id"} }, } @@ -110,43 +110,43 @@ def test_verify_oauth_access_no_metadata(self): self.user_settings.save() assert self.user_settings.verify_oauth_access( - node=self.node, - external_account=self.external_account - ) + node=self.node, external_account=self.external_account + ) assert not self.user_settings.verify_oauth_access( - node=self.node, - external_account=self.ExternalAccountFactory() - ) + node=self.node, external_account=self.ExternalAccountFactory() + ) def test_verify_oauth_access_metadata(self): self.user_settings.grant_oauth_access( node=self.node, external_account=self.external_account, - metadata={'folder': 'fake_folder_id'} + metadata={"folder": "fake_folder_id"}, ) self.user_settings.save() assert self.user_settings.verify_oauth_access( - node=self.node, - external_account=self.external_account, - metadata={'folder': 'fake_folder_id'} - ) + node=self.node, + external_account=self.external_account, + metadata={"folder": "fake_folder_id"}, + ) assert not self.user_settings.verify_oauth_access( - node=self.node, - external_account=self.external_account, - metadata={'folder': 'another_folder_id'} - ) + node=self.node, + external_account=self.external_account, + metadata={"folder": "another_folder_id"}, + ) -class OAuthAddonNodeSettingsTestSuiteMixin(OAuthAddonModelTestSuiteMixinBase): +class OAuthAddonNodeSettingsTestSuiteMixin(OAuthAddonModelTestSuiteMixinBase): @pytest.fixture(autouse=True) def _request_context(self, app): - context = app.test_request_context(headers={ - 'Remote-Addr': '146.9.219.56', - 'User-Agent': 'Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:0.9.4.1) Gecko/20020518 Netscape6/6.2.3' - }) + context = app.test_request_context( + headers={ + "Remote-Addr": "146.9.219.56", + "User-Agent": "Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:0.9.4.1) Gecko/20020518 Netscape6/6.2.3", + } + ) context.push() yield context context.pop() @@ -168,9 +168,9 @@ def UserSettingsFactory(self): def _node_settings_class_kwargs(self, node, user_settings): return { - 'user_settings': self.user_settings, - 'folder_id': '1234567890', - 'owner': self.node + "user_settings": self.user_settings, + "folder_id": "1234567890", + "owner": self.node, } def setUp(self): @@ -186,13 +186,13 @@ def setUp(self): self.user_settings.grant_oauth_access( node=self.node, external_account=self.external_account, - metadata={'folder': '1234567890'} + metadata={"folder": "1234567890"}, ) self.user_settings.save() self.node_settings = self.NodeSettingsFactory( external_account=self.external_account, - **self._node_settings_class_kwargs(self.node, self.user_settings) + **self._node_settings_class_kwargs(self.node, self.user_settings), ) @pytest.mark.django_db @@ -220,8 +220,9 @@ def test_complete_has_auth_not_verified(self): assert self.user_settings.oauth_grants == {self.node._id: {}} def test_revoke_remote_access_called(self): - - with mock.patch.object(self.user_settings, 'revoke_remote_oauth_access') as mock_revoke: + with mock.patch.object( + self.user_settings, "revoke_remote_oauth_access" + ) as mock_revoke: with mock_auth(self.user): self.user_settings.revoke_oauth_access(self.external_account) assert mock_revoke.call_count == 1 @@ -230,7 +231,9 @@ def test_revoke_remote_access_not_called(self): user2 = UserFactory() user2.external_accounts.add(self.external_account) user2.save() - with mock.patch.object(self.user_settings, 'revoke_remote_oauth_access') as mock_revoke: + with mock.patch.object( + self.user_settings, "revoke_remote_oauth_access" + ) as mock_revoke: with mock_auth(self.user): self.user_settings.revoke_oauth_access(self.external_account) assert mock_revoke.call_count == 0 @@ -242,15 +245,19 @@ def test_complete_auth_false(self): assert not self.node_settings.complete def test_fields(self): - node_settings = self.NodeSettingsClass(owner=ProjectFactory(), user_settings=self.user_settings) + node_settings = self.NodeSettingsClass( + owner=ProjectFactory(), user_settings=self.user_settings + ) node_settings.save() assert node_settings.user_settings assert node_settings.user_settings.owner == self.user - assert hasattr(node_settings, 'folder_id') - assert hasattr(node_settings, 'user_settings') + assert hasattr(node_settings, "folder_id") + assert hasattr(node_settings, "user_settings") def test_folder_defaults_to_none(self): - node_settings = self.NodeSettingsClass(user_settings=self.user_settings) + node_settings = self.NodeSettingsClass( + user_settings=self.user_settings + ) node_settings.save() assert node_settings.folder_id is None @@ -258,7 +265,9 @@ def test_has_auth(self): self.user.external_accounts.clear() self.user_settings.reload() node = ProjectFactory() - settings = self.NodeSettingsClass(user_settings=self.user_settings, owner=node) + settings = self.NodeSettingsClass( + user_settings=self.user_settings, owner=node + ) settings.save() assert not settings.has_auth @@ -291,14 +300,14 @@ def test_to_json(self): settings = self.node_settings user = UserFactory() result = settings.to_json(user) - assert result['addon_short_name'] == self.short_name + assert result["addon_short_name"] == self.short_name def test_delete(self): assert self.node_settings.user_settings assert self.node_settings.folder_id old_logs = list(self.node.logs.all()) mock_now = datetime.datetime(2017, 3, 16, 11, 00, tzinfo=pytz.utc) - with mock.patch.object(timezone, 'now', return_value=mock_now): + with mock.patch.object(timezone, "now", return_value=mock_now): self.node_settings.delete() self.node_settings.save() assert self.node_settings.user_settings is None @@ -308,9 +317,7 @@ def test_delete(self): assert list(self.node.logs.all()) == list(old_logs) def test_on_delete(self): - self.user.delete_addon( - self.user_settings.oauth_provider.short_name - ) + self.user.delete_addon(self.user_settings.oauth_provider.short_name) self.node_settings.reload() @@ -326,20 +333,20 @@ def test_deauthorize(self): assert self.node_settings.folder_id is None last_log = self.node.logs.first() - assert last_log.action == f'{self.short_name}_node_deauthorized' + assert last_log.action == f"{self.short_name}_node_deauthorized" params = last_log.params - assert 'node' in params - assert 'project' in params + assert "node" in params + assert "project" in params def test_set_folder(self): - folder_id = '1234567890' + folder_id = "1234567890" self.node_settings.set_folder(folder_id, auth=Auth(self.user)) self.node_settings.save() # Folder was set assert self.node_settings.folder_id == folder_id # Log was saved last_log = self.node.logs.first() - assert last_log.action == f'{self.short_name}_folder_selected' + assert last_log.action == f"{self.short_name}_folder_selected" def test_set_user_auth(self): node_settings = self.NodeSettingsFactory() @@ -357,17 +364,17 @@ def test_set_user_auth(self): assert node_settings.user_settings == user_settings # A log was saved last_log = node_settings.owner.logs.first() - assert last_log.action == f'{self.short_name}_node_authorized' + assert last_log.action == f"{self.short_name}_node_authorized" log_params = last_log.params - assert log_params['node'] == node_settings.owner._id + assert log_params["node"] == node_settings.owner._id assert last_log.user == user_settings.owner def test_serialize_credentials(self): - self.user_settings.external_accounts[0].oauth_key = 'key-11' + self.user_settings.external_accounts[0].oauth_key = "key-11" self.user_settings.save() credentials = self.node_settings.serialize_waterbutler_credentials() - expected = {'token': self.node_settings.external_account.oauth_key} + expected = {"token": self.node_settings.external_account.oauth_key} assert credentials == expected def test_serialize_credentials_not_authorized(self): @@ -378,7 +385,7 @@ def test_serialize_credentials_not_authorized(self): def test_serialize_settings(self): settings = self.node_settings.serialize_waterbutler_settings() - expected = {'folder': self.node_settings.folder_id} + expected = {"folder": self.node_settings.folder_id} assert settings == expected def test_serialize_settings_not_configured(self): @@ -388,18 +395,18 @@ def test_serialize_settings_not_configured(self): self.node_settings.serialize_waterbutler_settings() def test_create_log(self): - action = 'file_added' - path = 'pizza.nii' + action = "file_added" + path = "pizza.nii" nlog = self.node.logs.count() self.node_settings.create_waterbutler_log( auth=Auth(user=self.user), action=action, - metadata={'path': path, 'materialized': path}, + metadata={"path": path, "materialized": path}, ) self.node.reload() assert self.node.logs.count() == nlog + 1 - assert self.node.logs.latest().action == f'{self.short_name}_{action}' - assert self.node.logs.latest().params['path'] == path + assert self.node.logs.latest().action == f"{self.short_name}_{action}" + assert self.node.logs.latest().params["path"] == path def test_after_fork_by_authorized_user(self): fork = ProjectFactory() @@ -412,34 +419,36 @@ def test_after_fork_by_unauthorized_user(self): fork = ProjectFactory() user = UserFactory() clone = self.node_settings.after_fork( - node=self.node, fork=fork, user=user, - save=True + node=self.node, fork=fork, user=user, save=True ) assert clone.user_settings is None def test_before_remove_contributor_message(self): message = self.node_settings.before_remove_contributor( - self.node, self.user) + self.node, self.user + ) assert message assert self.user.fullname in message assert self.node.project_or_component in message def test_after_remove_authorized_user_not_self(self): message = self.node_settings.after_remove_contributor( - self.node, self.user_settings.owner) + self.node, self.user_settings.owner + ) self.node_settings.save() assert self.node_settings.user_settings is None assert message - assert 'You can re-authenticate' in message + assert "You can re-authenticate" in message def test_after_remove_authorized_user_self(self): auth = Auth(user=self.user_settings.owner) message = self.node_settings.after_remove_contributor( - self.node, self.user_settings.owner, auth) + self.node, self.user_settings.owner, auth + ) self.node_settings.save() assert self.node_settings.user_settings is None assert message - assert 'You can re-authenticate' not in message + assert "You can re-authenticate" not in message def test_after_delete(self): self.node.remove_node(Auth(user=self.node.creator)) @@ -462,52 +471,59 @@ def OAuthProviderClass(self): class OAuthCitationsNodeSettingsTestSuiteMixin( - OAuthAddonNodeSettingsTestSuiteMixin, - OAuthCitationsTestSuiteMixinBase): - + OAuthAddonNodeSettingsTestSuiteMixin, OAuthCitationsTestSuiteMixinBase +): def setUp(self): super().setUp() self.user_settings.grant_oauth_access( node=self.node, external_account=self.external_account, - metadata={'folder': 'fake_folder_id'} + metadata={"folder": "fake_folder_id"}, ) self.user_settings.save() def test_fetch_folder_name_root(self): - self.node_settings.list_id = 'ROOT' + self.node_settings.list_id = "ROOT" - assert self.node_settings.fetch_folder_name == 'All Documents' + assert self.node_settings.fetch_folder_name == "All Documents" def test_selected_folder_name_empty(self): self.node_settings.list_id = None - assert self.node_settings.fetch_folder_name == '' + assert self.node_settings.fetch_folder_name == "" def test_selected_folder_name(self): # Mock the return from api call to get the folder's name mock_folder = MockFolder() name = None - with mock.patch.object(self.OAuthProviderClass, '_folder_metadata', return_value=mock_folder): + with mock.patch.object( + self.OAuthProviderClass, + "_folder_metadata", + return_value=mock_folder, + ): name = self.node_settings.fetch_folder_name - assert name == 'Fake Folder' + assert name == "Fake Folder" def test_api_not_cached(self): # The first call to .api returns a new object - with mock.patch.object(self.NodeSettingsClass, 'oauth_provider') as mock_api: + with mock.patch.object( + self.NodeSettingsClass, "oauth_provider" + ) as mock_api: api = self.node_settings.api mock_api.assert_called_once_with(account=self.external_account) assert api == mock_api() def test_api_cached(self): # Repeated calls to .api returns the same object - with mock.patch.object(self.NodeSettingsClass, 'oauth_provider') as mock_api: - self.node_settings._api = 'testapi' + with mock.patch.object( + self.NodeSettingsClass, "oauth_provider" + ) as mock_api: + self.node_settings._api = "testapi" api = self.node_settings.api assert not mock_api.called - assert api == 'testapi' + assert api == "testapi" ############# Overrides ############## # `pass` due to lack of waterbutler- # @@ -516,9 +532,9 @@ def test_api_cached(self): def _node_settings_class_kwargs(self, node, user_settings): return { - 'user_settings': self.user_settings, - 'list_id': 'fake_folder_id', - 'owner': self.node + "user_settings": self.user_settings, + "list_id": "fake_folder_id", + "owner": self.node, } def test_serialize_credentials(self): @@ -537,8 +553,8 @@ def test_create_log(self): pass def test_set_folder(self): - folder_id = 'fake-folder-id' - folder_name = 'fake-folder-name' + folder_id = "fake-folder-id" + folder_name = "fake-folder-name" self.node_settings.clear_settings() self.node_settings.save() @@ -555,58 +571,70 @@ def test_set_folder(self): ) # instance was updated - assert self.node_settings.list_id == 'fake-folder-id' + assert self.node_settings.list_id == "fake-folder-id" # user_settings was updated # TODO: the call to grant_oauth_access should be mocked assert self.user_settings.verify_oauth_access( - node=self.node, - external_account=self.external_account, - metadata={'folder': 'fake-folder-id'} - ) + node=self.node, + external_account=self.external_account, + metadata={"folder": "fake-folder-id"}, + ) log = self.node.logs.latest() - assert log.action == f'{self.short_name}_folder_selected' - assert log.params['folder_id'] == folder_id - assert log.params['folder_name'] == folder_name + assert log.action == f"{self.short_name}_folder_selected" + assert log.params["folder_id"] == folder_id + assert log.params["folder_name"] == folder_name - @mock.patch('framework.status.push_status_message') + @mock.patch("framework.status.push_status_message") def test_remove_contributor_authorizer(self, mock_push_status): contributor = UserFactory() self.node.add_contributor(contributor, permissions=ADMIN) - self.node.remove_contributor(self.node.creator, auth=Auth(user=contributor)) + self.node.remove_contributor( + self.node.creator, auth=Auth(user=contributor) + ) self.node_settings.reload() self.user_settings.reload() assert not self.node_settings.has_auth - assert not self.user_settings.verify_oauth_access(self.node, self.external_account) + assert not self.user_settings.verify_oauth_access( + self.node, self.external_account + ) def test_remove_contributor_not_authorizer(self): contributor = UserFactory() self.node.add_contributor(contributor) - self.node.remove_contributor(contributor, auth=Auth(user=self.node.creator)) + self.node.remove_contributor( + contributor, auth=Auth(user=self.node.creator) + ) assert self.node_settings.has_auth - assert self.user_settings.verify_oauth_access(self.node, self.external_account) + assert self.user_settings.verify_oauth_access( + self.node, self.external_account + ) - @mock.patch('framework.status.push_status_message') + @mock.patch("framework.status.push_status_message") def test_fork_by_authorizer(self, mock_push_status): fork = self.node.fork_node(auth=Auth(user=self.node.creator)) self.user_settings.reload() assert fork.get_addon(self.short_name).has_auth - assert self.user_settings.verify_oauth_access(fork, self.external_account) + assert self.user_settings.verify_oauth_access( + fork, self.external_account + ) - @mock.patch('framework.status.push_status_message') + @mock.patch("framework.status.push_status_message") def test_fork_not_by_authorizer(self, mock_push_status): contributor = UserFactory() self.node.add_contributor(contributor) fork = self.node.fork_node(auth=Auth(user=contributor)) assert not fork.get_addon(self.short_name).has_auth - assert not self.user_settings.verify_oauth_access(fork, self.external_account) + assert not self.user_settings.verify_oauth_access( + fork, self.external_account + ) -class CitationAddonProviderTestSuiteMixin(OAuthCitationsTestSuiteMixinBase): +class CitationAddonProviderTestSuiteMixin(OAuthCitationsTestSuiteMixinBase): @property @abc.abstractmethod def ApiExceptionClass(self): @@ -630,13 +658,17 @@ def test_citation_lists(self): self.provider._client = mock_client mock_account = mock.Mock() self.provider.account = mock_account - res = self.provider.citation_lists(self.ProviderClass()._extract_folder) - assert res[1]['name'] == mock_folders[0].name - assert res[1]['id'] == mock_folders[0].json['id'] + res = self.provider.citation_lists( + self.ProviderClass()._extract_folder + ) + assert res[1]["name"] == mock_folders[0].name + assert res[1]["id"] == mock_folders[0].json["id"] def test_client_not_cached(self): # The first call to .client returns a new client - with mock.patch.object(self.OAuthProviderClass, '_get_client') as mock_get_client: + with mock.patch.object( + self.OAuthProviderClass, "_get_client" + ) as mock_get_client: mock_account = mock.Mock() mock_account.expires_at = timezone.now() self.provider.account = mock_account @@ -646,20 +678,28 @@ def test_client_not_cached(self): def test_client_cached(self): # Repeated calls to .client returns the same client - with mock.patch.object(self.OAuthProviderClass, '_get_client') as mock_get_client: + with mock.patch.object( + self.OAuthProviderClass, "_get_client" + ) as mock_get_client: self.provider._client = mock.Mock() res = self.provider.client assert res == self.provider._client assert not mock_get_client.called def test_has_access(self): - with mock.patch.object(self.OAuthProviderClass, '_get_client') as mock_get_client: + with mock.patch.object( + self.OAuthProviderClass, "_get_client" + ) as mock_get_client: mock_client = mock.Mock() mock_error = mock.PropertyMock() mock_error.status_code = 403 - mock_error.text = 'Mocked 403 ApiException' - mock_client.folders.list.side_effect = self.ApiExceptionClass(mock_error) - mock_client.collections.side_effect = self.ApiExceptionClass(mock_error) + mock_error.text = "Mocked 403 ApiException" + mock_client.folders.list.side_effect = self.ApiExceptionClass( + mock_error + ) + mock_client.collections.side_effect = self.ApiExceptionClass( + mock_error + ) mock_get_client.return_value = mock_client with pytest.raises(HTTPError) as exc_info: self.provider.client diff --git a/addons/base/tests/serializers.py b/addons/base/tests/serializers.py index 545761dcf12..7b7f7b16c0c 100644 --- a/addons/base/tests/serializers.py +++ b/addons/base/tests/serializers.py @@ -10,7 +10,6 @@ class AddonSerializerTestSuiteMixin: - __metaclass__ = abc.ABCMeta @property @@ -46,26 +45,37 @@ def setUp(self): self.user = AuthUserFactory() self.node = ProjectFactory(creator=self.user) self.set_user_settings(self.user) - assert getattr(self, 'user_settings') is not None, "'set_user_settings' should set the 'user_settings' attribute of the instance to an instance of \ + assert ( + getattr(self, "user_settings") is not None + ), ( + "'set_user_settings' should set the 'user_settings' attribute of the instance to an instance of \ the appropriate user settings model." + ) self.set_node_settings(self.user_settings) - assert getattr(self, 'node_settings') is not None, "'set_node_settings' should set the 'user_settings' attribute of the instance to an instance of \ + assert ( + getattr(self, "node_settings") is not None + ), ( + "'set_node_settings' should set the 'user_settings' attribute of the instance to an instance of \ the appropriate node settings model." + ) self.ser = self.Serializer( - user_settings=self.user_settings, - node_settings=self.node_settings + user_settings=self.user_settings, node_settings=self.node_settings ) def test_serialized_node_settings_unauthorized(self): - with mock.patch.object(type(self.node_settings), 'has_auth', return_value=False): + with mock.patch.object( + type(self.node_settings), "has_auth", return_value=False + ): serialized = self.ser.serialized_node_settings for setting in self.required_settings: assert setting in serialized def test_serialized_node_settings_authorized(self): - with mock.patch.object(type(self.node_settings), 'has_auth', return_value=True): + with mock.patch.object( + type(self.node_settings), "has_auth", return_value=True + ): serialized = self.ser.serialized_node_settings for setting in self.required_settings: assert setting in serialized @@ -74,7 +84,6 @@ def test_serialized_node_settings_authorized(self): class OAuthAddonSerializerTestSuiteMixin(AddonSerializerTestSuiteMixin): - def set_user_settings(self, user): self.user_settings = user.get_or_add_addon(self.addon_short_name) self.external_account = self.ExternalAccountFactory() @@ -82,8 +91,12 @@ def set_user_settings(self, user): self.user.save() def set_node_settings(self, user_settings): - self.node_settings = self.node.get_or_add_addon(self.addon_short_name, auth=Auth(user_settings.owner)) - self.node_settings.set_auth(self.user_settings.external_accounts[0], self.user) + self.node_settings = self.node.get_or_add_addon( + self.addon_short_name, auth=Auth(user_settings.owner) + ) + self.node_settings.set_auth( + self.user_settings.external_accounts[0], self.user + ) def test_credentials_owner(self): owner = self.ser.credentials_owner @@ -108,8 +121,10 @@ def test_user_is_owner_node_not_authorized_user_has_accounts(self): def test_user_is_owner_node_authorized_user_is_not_owner(self): self.node_settings.external_account = self.ExternalAccountFactory() - with mock.patch('addons.base.models.BaseOAuthUserSettings.verify_oauth_access', - return_value=True): + with mock.patch( + "addons.base.models.BaseOAuthUserSettings.verify_oauth_access", + return_value=True, + ): self.user.external_accounts.clear() assert not self.ser.user_is_owner @@ -117,7 +132,7 @@ def test_user_is_owner_node_authorized_user_is_owner(self): assert self.ser.user_is_owner def test_serialized_urls_checks_required(self): - with mock.patch.object(self.ser, 'REQUIRED_URLS', ('foobar', )): + with mock.patch.object(self.ser, "REQUIRED_URLS", ("foobar",)): with pytest.raises(AssertionError): self.ser.serialized_urls @@ -125,7 +140,9 @@ def test_serialized_acccounts(self): ea = self.ExternalAccountFactory() self.user.external_accounts.add(ea) - with mock.patch.object(type(self.ser), 'serialize_account') as mock_serialize_account: + with mock.patch.object( + type(self.ser), "serialize_account" + ) as mock_serialize_account: mock_serialize_account.return_value = {} serialized = self.ser.serialized_accounts assert len(serialized) == self.user.external_accounts.count() @@ -134,36 +151,39 @@ def test_serialized_acccounts(self): def test_serialize_acccount(self): ea = self.ExternalAccountFactory() expected = { - 'id': ea._id, - 'provider_id': ea.provider_id, - 'provider_name': ea.provider_name, - 'provider_short_name': ea.provider, - 'display_name': ea.display_name, - 'profile_url': ea.profile_url, - 'nodes': [], + "id": ea._id, + "provider_id": ea.provider_id, + "provider_name": ea.provider_name, + "provider_short_name": ea.provider, + "display_name": ea.display_name, + "profile_url": ea.profile_url, + "nodes": [], } assert self.ser.serialize_account(ea) == expected def test_serialized_user_settings(self): - with mock.patch.object(self.Serializer, 'serialized_accounts', return_value=[]): + with mock.patch.object( + self.Serializer, "serialized_accounts", return_value=[] + ): serialized = self.ser.serialized_user_settings - assert 'accounts' in serialized + assert "accounts" in serialized def test_serialize_granted_node(self): with mock_auth(self.user): - serialized = self.ser.serialize_granted_node(self.node, auth=Auth(self.user)) - for key in ('id', 'title', 'urls'): + serialized = self.ser.serialize_granted_node( + self.node, auth=Auth(self.user) + ) + for key in ("id", "title", "urls"): assert key in serialized - assert self.node._id == serialized['id'] - assert self.node.title == serialized['title'] - assert 'view' in serialized['urls'] - assert serialized['urls']['view'] == self.node.url + assert self.node._id == serialized["id"] + assert self.node.title == serialized["title"] + assert "view" in serialized["urls"] + assert serialized["urls"]["view"] == self.node.url class StorageAddonSerializerTestSuiteMixin(OAuthAddonSerializerTestSuiteMixin): - - required_settings = ('userIsOwner', 'nodeHasAuth', 'urls', 'userHasAuth') - required_settings_authorized = ('ownerName', ) + required_settings = ("userIsOwner", "nodeHasAuth", "urls", "userHasAuth") + required_settings_authorized = ("ownerName",) @property @abc.abstractmethod @@ -178,44 +198,63 @@ def set_provider_id(self): pass def test_serialize_settings_unauthorized(self): - with mock.patch.object(type(self.node_settings), 'has_auth', return_value=False): - serialized = self.ser.serialize_settings(self.node_settings, self.user, self.client) + with mock.patch.object( + type(self.node_settings), "has_auth", return_value=False + ): + serialized = self.ser.serialize_settings( + self.node_settings, self.user, self.client + ) for key in self.required_settings: assert key in serialized def test_serialize_settings_authorized(self): - with mock.patch.object(type(self.node_settings), 'has_auth', return_value=True): - serialized = self.ser.serialize_settings(self.node_settings, self.user, self.client) + with mock.patch.object( + type(self.node_settings), "has_auth", return_value=True + ): + serialized = self.ser.serialize_settings( + self.node_settings, self.user, self.client + ) for key in self.required_settings: assert key in serialized - assert 'owner' in serialized['urls'] - assert serialized['urls']['owner'] == web_url_for( - 'profile_view_id', - uid=self.user_settings.owner._id + assert "owner" in serialized["urls"] + assert serialized["urls"]["owner"] == web_url_for( + "profile_view_id", uid=self.user_settings.owner._id ) - assert 'ownerName' in serialized - assert serialized['ownerName'] == self.user_settings.owner.fullname - assert 'folder' in serialized + assert "ownerName" in serialized + assert serialized["ownerName"] == self.user_settings.owner.fullname + assert "folder" in serialized def test_serialize_settings_authorized_no_folder(self): - with mock.patch.object(type(self.node_settings), 'has_auth', return_value=True): - serialized = self.ser.serialize_settings(self.node_settings, self.user, self.client) - assert 'folder' in serialized - assert serialized['folder'] == {'name': None, 'path': None} + with mock.patch.object( + type(self.node_settings), "has_auth", return_value=True + ): + serialized = self.ser.serialize_settings( + self.node_settings, self.user, self.client + ) + assert "folder" in serialized + assert serialized["folder"] == {"name": None, "path": None} def test_serialize_settings_authorized_folder_is_set(self): - self.set_provider_id('foo') - with mock.patch.object(type(self.node_settings), 'has_auth', return_value=True): - with mock.patch.object(self.ser, 'serialized_folder') as mock_serialized_folder: + self.set_provider_id("foo") + with mock.patch.object( + type(self.node_settings), "has_auth", return_value=True + ): + with mock.patch.object( + self.ser, "serialized_folder" + ) as mock_serialized_folder: mock_serialized_folder.return_value = {} - serialized = self.ser.serialize_settings(self.node_settings, self.user, self.client) - assert 'folder' in serialized + serialized = self.ser.serialize_settings( + self.node_settings, self.user, self.client + ) + assert "folder" in serialized assert mock_serialized_folder.called -class CitationAddonSerializerTestSuiteMixin(OAuthAddonSerializerTestSuiteMixin): - required_settings = ('userIsOwner', 'nodeHasAuth', 'urls', 'userHasAuth') - required_settings_authorized = ('ownerName', ) +class CitationAddonSerializerTestSuiteMixin( + OAuthAddonSerializerTestSuiteMixin +): + required_settings = ("userIsOwner", "nodeHasAuth", "urls", "userHasAuth") + required_settings_authorized = ("ownerName",) @property @abc.abstractmethod @@ -224,12 +263,12 @@ def folder(self): def test_serialize_folder(self): serialized_folder = self.ser.serialize_folder(self.folder) - assert serialized_folder['id'] == self.folder['id'] - assert serialized_folder['name'] == self.folder.name - assert serialized_folder['kind'] == 'folder' + assert serialized_folder["id"] == self.folder["id"] + assert serialized_folder["name"] == self.folder.name + assert serialized_folder["kind"] == "folder" def test_serialize_citation(self): serialized_citation = self.ser.serialize_citation(self.folder) - assert serialized_citation['csl'] == self.folder - assert serialized_citation['id'] == self.folder['id'] - assert serialized_citation['kind'] == 'file' + assert serialized_citation["csl"] == self.folder + assert serialized_citation["id"] == self.folder["id"] + assert serialized_citation["kind"] == "file" diff --git a/addons/base/tests/utils.py b/addons/base/tests/utils.py index 5ab0bbbf45c..90cd79220f6 100644 --- a/addons/base/tests/utils.py +++ b/addons/base/tests/utils.py @@ -1,31 +1,40 @@ import pytest from addons.base.utils import get_mfr_url -from addons.osfstorage.tests.utils import StorageTestCase from tests.base import OsfTestCase -from osf_tests.factories import ProjectFactory, UserFactory, RegionFactory, CommentFactory +from osf_tests.factories import ProjectFactory, UserFactory, CommentFactory from website.settings import MFR_SERVER_URL class MockFolder(dict): - def __init__(self): - self.name = 'Fake Folder' - self.json = {'id': 'Fake Key', 'parent_id': 'cba321', 'name': 'Fake Folder'} - self['data'] = {'name': 'Fake Folder', 'key': 'Fake Key', 'parentCollection': False} - self['library'] = {'type': 'personal', 'id': '34241'} - self['name'] = 'Fake Folder' - self['id'] = 'Fake Key' + self.name = "Fake Folder" + self.json = { + "id": "Fake Key", + "parent_id": "cba321", + "name": "Fake Folder", + } + self["data"] = { + "name": "Fake Folder", + "key": "Fake Key", + "parentCollection": False, + } + self["library"] = {"type": "personal", "id": "34241"} + self["name"] = "Fake Folder" + self["id"] = "Fake Key" class MockLibrary(dict): - def __init__(self): - self.name = 'Fake Library' - self.json = {'id': 'Fake Library Key', 'parent_id': 'cba321'} - self['data'] = {'name': 'Fake Library', 'key': 'Fake Key', 'id': '12345' } - self['name'] = 'Fake Library' - self['id'] = 'Fake Library Key' + self.name = "Fake Library" + self.json = {"id": "Fake Library Key", "parent_id": "cba321"} + self["data"] = { + "name": "Fake Library", + "key": "Fake Key", + "id": "12345", + } + self["name"] = "Fake Library" + self["id"] = "Fake Library Key" @pytest.mark.django_db @@ -34,6 +43,9 @@ def test_mfr_url(self): user = UserFactory() project = ProjectFactory(creator=user) comment = CommentFactory() - assert get_mfr_url(project, 'github') == MFR_SERVER_URL - assert get_mfr_url(project, 'osfstorage') == project.osfstorage_region.mfr_url - assert get_mfr_url(comment, 'osfstorage') == MFR_SERVER_URL + assert get_mfr_url(project, "github") == MFR_SERVER_URL + assert ( + get_mfr_url(project, "osfstorage") + == project.osfstorage_region.mfr_url + ) + assert get_mfr_url(comment, "osfstorage") == MFR_SERVER_URL diff --git a/addons/base/tests/views.py b/addons/base/tests/views.py index 33675736754..02b97080069 100644 --- a/addons/base/tests/views.py +++ b/addons/base/tests/views.py @@ -21,7 +21,6 @@ class OAuthAddonAuthViewsTestCaseMixin(OAuthAddonTestCaseMixin): - @property def ADDON_SHORT_NAME(self): raise NotImplementedError() @@ -35,10 +34,7 @@ def Provider(self): raise NotImplementedError() def test_oauth_start(self): - url = api_url_for( - 'oauth_connect', - service_name=self.ADDON_SHORT_NAME - ) + url = api_url_for("oauth_connect", service_name=self.ADDON_SHORT_NAME) res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_302_FOUND redirect_url = urlparse(res.location) @@ -46,45 +42,42 @@ def test_oauth_start(self): provider_url = urlparse(self.Provider().auth_url) provider_params = parse_qs(provider_url.query) for param, value in redirect_params.items(): - if param == 'state': # state may change between calls + if param == "state": # state may change between calls continue assert value == provider_params[param] def test_oauth_finish(self): - url = web_url_for( - 'oauth_callback', - service_name=self.ADDON_SHORT_NAME - ) - with mock.patch.object(self.Provider, 'auth_callback') as mock_callback: + url = web_url_for("oauth_callback", service_name=self.ADDON_SHORT_NAME) + with mock.patch.object( + self.Provider, "auth_callback" + ) as mock_callback: mock_callback.return_value = True res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK name, args, kwargs = mock_callback.mock_calls[0] - assert kwargs['user']._id == self.user._id + assert kwargs["user"]._id == self.user._id - @mock.patch('website.oauth.views.requests.get') + @mock.patch("website.oauth.views.requests.get") def test_oauth_finish_enable_gv(self, mock_requests_get): - url = web_url_for( - 'oauth_callback', - service_name=self.ADDON_SHORT_NAME - ) + url = web_url_for("oauth_callback", service_name=self.ADDON_SHORT_NAME) query_params = { - 'code': 'somecode', - 'state': 'somestatetoken', + "code": "somecode", + "state": "somestatetoken", } with override_flag(ENABLE_GV, active=True): - request_url = urlunparse(urlparse(url)._replace(query=urlencode(query_params))) + request_url = urlunparse( + urlparse(url)._replace(query=urlencode(query_params)) + ) res = self.app.get(request_url, auth=self.user.auth) gv_callback_url = mock_requests_get.call_args[0][0] parsed_callback_url = urlparse(gv_callback_url) assert parsed_callback_url.netloc == urlparse(GRAVYVALET_URL).netloc - assert parsed_callback_url.path == '/v1/oauth/callback' + assert parsed_callback_url.path == "/v1/oauth/callback" assert dict(parse_qsl(parsed_callback_url.query)) == query_params def test_delete_external_account(self): url = api_url_for( - 'oauth_disconnect', - external_account_id=self.external_account._id + "oauth_disconnect", external_account_id=self.external_account._id ) res = self.app.delete(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK @@ -96,15 +89,13 @@ def test_delete_external_account(self): def test_delete_external_account_not_owner(self): other_user = AuthUserFactory() url = api_url_for( - 'oauth_disconnect', - external_account_id=self.external_account._id + "oauth_disconnect", external_account_id=self.external_account._id ) res = self.app.delete(url, auth=other_user.auth) assert res.status_code == http_status.HTTP_403_FORBIDDEN class OAuthAddonConfigViewsTestCaseMixin(OAuthAddonTestCaseMixin): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.node_settings = None @@ -119,7 +110,9 @@ def ExternalAccountFactory(self): @property def folder(self): - raise NotImplementedError("This test suite must expose a 'folder' property.") + raise NotImplementedError( + "This test suite must expose a 'folder' property." + ) @property def Serializer(self): @@ -135,20 +128,22 @@ def test_import_auth(self): self.user.save() node = ProjectFactory(creator=self.user) - node_settings = node.get_or_add_addon(self.ADDON_SHORT_NAME, auth=Auth(self.user)) + node_settings = node.get_or_add_addon( + self.ADDON_SHORT_NAME, auth=Auth(self.user) + ) node.save() - url = node.api_url_for(f'{self.ADDON_SHORT_NAME}_import_auth') - res = self.app.put(url, json={ - 'external_account_id': ea._id - }, auth=self.user.auth) + url = node.api_url_for(f"{self.ADDON_SHORT_NAME}_import_auth") + res = self.app.put( + url, json={"external_account_id": ea._id}, auth=self.user.auth + ) assert res.status_code == http_status.HTTP_200_OK - assert 'result' in res.json + assert "result" in res.json node_settings.reload() assert node_settings.external_account._id == ea._id node.reload() last_log = node.logs.latest() - assert last_log.action == f'{self.ADDON_SHORT_NAME}_node_authorized' + assert last_log.action == f"{self.ADDON_SHORT_NAME}_node_authorized" def test_import_auth_invalid_account(self): ea = self.ExternalAccountFactory() @@ -156,10 +151,12 @@ def test_import_auth_invalid_account(self): node = ProjectFactory(creator=self.user) node.add_addon(self.ADDON_SHORT_NAME, auth=self.auth) node.save() - url = node.api_url_for(f'{self.ADDON_SHORT_NAME}_import_auth') - res = self.app.put(url, json={ - 'external_account_id': ea._id - }, auth=self.user.auth, ) + url = node.api_url_for(f"{self.ADDON_SHORT_NAME}_import_auth") + res = self.app.put( + url, + json={"external_account_id": ea._id}, + auth=self.user.auth, + ) assert res.status_code == http_status.HTTP_403_FORBIDDEN def test_import_auth_cant_write_node(self): @@ -170,71 +167,83 @@ def test_import_auth_cant_write_node(self): user.save() node = ProjectFactory(creator=self.user) - node.add_contributor(user, permissions=permissions.READ, auth=self.auth, save=True) + node.add_contributor( + user, permissions=permissions.READ, auth=self.auth, save=True + ) node.add_addon(self.ADDON_SHORT_NAME, auth=self.auth) node.save() - url = node.api_url_for(f'{self.ADDON_SHORT_NAME}_import_auth') - res = self.app.put(url, json={ - 'external_account_id': ea._id - }, auth=user.auth, ) + url = node.api_url_for(f"{self.ADDON_SHORT_NAME}_import_auth") + res = self.app.put( + url, + json={"external_account_id": ea._id}, + auth=user.auth, + ) assert res.status_code == http_status.HTTP_403_FORBIDDEN def test_set_config(self): self.node_settings.set_auth(self.external_account, self.user) - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_set_config') - res = self.app.put(url, json={ - 'selected': self.folder - }, auth=self.user.auth) + url = self.project.api_url_for(f"{self.ADDON_SHORT_NAME}_set_config") + res = self.app.put( + url, json={"selected": self.folder}, auth=self.user.auth + ) assert res.status_code == http_status.HTTP_200_OK self.project.reload() - assert self.project.logs.latest().action == f'{self.ADDON_SHORT_NAME}_folder_selected' - assert res.json['result']['folder']['path'] == self.folder['path'] + assert ( + self.project.logs.latest().action + == f"{self.ADDON_SHORT_NAME}_folder_selected" + ) + assert res.json["result"]["folder"]["path"] == self.folder["path"] def test_get_config(self): - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_get_config') - with mock.patch.object(type(self.Serializer()), 'credentials_are_valid', return_value=True): + url = self.project.api_url_for(f"{self.ADDON_SHORT_NAME}_get_config") + with mock.patch.object( + type(self.Serializer()), "credentials_are_valid", return_value=True + ): res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK - assert 'result' in res.json + assert "result" in res.json serialized = self.Serializer().serialize_settings( - self.node_settings, - self.user, - self.client + self.node_settings, self.user, self.client ) - assert serialized == res.json['result'] + assert serialized == res.json["result"] def test_get_config_unauthorized(self): - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_get_config') + url = self.project.api_url_for(f"{self.ADDON_SHORT_NAME}_get_config") user = AuthUserFactory() - self.project.add_contributor(user, permissions=permissions.READ, auth=self.auth, save=True) - res = self.app.get(url, auth=user.auth, ) + self.project.add_contributor( + user, permissions=permissions.READ, auth=self.auth, save=True + ) + res = self.app.get( + url, + auth=user.auth, + ) assert res.status_code == http_status.HTTP_403_FORBIDDEN def test_get_config_not_logged_in(self): - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_get_config') + url = self.project.api_url_for(f"{self.ADDON_SHORT_NAME}_get_config") res = self.app.get(url, auth=None) assert res.status_code == http_status.HTTP_302_FOUND def test_account_list_single(self): - url = api_url_for(f'{self.ADDON_SHORT_NAME}_account_list') + url = api_url_for(f"{self.ADDON_SHORT_NAME}_account_list") res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK - assert 'accounts' in res.json - assert len(res.json['accounts']) == 1 + assert "accounts" in res.json + assert len(res.json["accounts"]) == 1 def test_account_list_multiple(self): ea = self.ExternalAccountFactory() self.user.external_accounts.add(ea) self.user.save() - url = api_url_for(f'{self.ADDON_SHORT_NAME}_account_list') + url = api_url_for(f"{self.ADDON_SHORT_NAME}_account_list") res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK - assert 'accounts' in res.json - assert len(res.json['accounts']) == 2 + assert "accounts" in res.json + assert len(res.json["accounts"]) == 2 def test_account_list_not_authorized(self): - url = api_url_for(f'{self.ADDON_SHORT_NAME}_account_list') + url = api_url_for(f"{self.ADDON_SHORT_NAME}_account_list") res = self.app.get(url, auth=None) assert res.status_code == http_status.HTTP_302_FOUND @@ -244,13 +253,15 @@ def test_folder_list(self): # subclass, mock any API calls, and call super. self.node_settings.set_auth(self.external_account, self.user) self.node_settings.save() - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_folder_list') + url = self.project.api_url_for(f"{self.ADDON_SHORT_NAME}_folder_list") res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK # TODO test result serialization? def test_deauthorize_node(self): - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_deauthorize_node') + url = self.project.api_url_for( + f"{self.ADDON_SHORT_NAME}_deauthorize_node" + ) res = self.app.delete(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK self.node_settings.reload() @@ -260,13 +271,16 @@ def test_deauthorize_node(self): # A log event was saved self.project.reload() last_log = self.project.logs.latest() - assert last_log.action == f'{self.ADDON_SHORT_NAME}_node_deauthorized' + assert last_log.action == f"{self.ADDON_SHORT_NAME}_node_deauthorized" -class OAuthCitationAddonConfigViewsTestCaseMixin(OAuthAddonConfigViewsTestCaseMixin): - +class OAuthCitationAddonConfigViewsTestCaseMixin( + OAuthAddonConfigViewsTestCaseMixin +): def __init__(self, *args, **kwargs): - super(OAuthAddonConfigViewsTestCaseMixin,self).__init__(*args, **kwargs) + super(OAuthAddonConfigViewsTestCaseMixin, self).__init__( + *args, **kwargs + ) self.mock_verify = None self.node_settings = None self.provider = None @@ -310,8 +324,7 @@ def mockResponses(self): def setUp(self): super().setUp() self.mock_verify = mock.patch.object( - self.client, - '_verify_client_validity' + self.client, "_verify_client_validity" ) self.mock_verify.start() @@ -320,45 +333,66 @@ def tearDown(self): super().tearDown() def test_set_config(self): - with mock.patch.object(self.client, '_folder_metadata') as mock_metadata: + with mock.patch.object( + self.client, "_folder_metadata" + ) as mock_metadata: mock_metadata.return_value = self.folder - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_set_config') - res = self.app.put(url, json={ - 'external_list_id': self.folder.json['id'], - 'external_list_name': self.folder.name, - }, auth=self.user.auth) + url = self.project.api_url_for( + f"{self.ADDON_SHORT_NAME}_set_config" + ) + res = self.app.put( + url, + json={ + "external_list_id": self.folder.json["id"], + "external_list_name": self.folder.name, + }, + auth=self.user.auth, + ) assert res.status_code == http_status.HTTP_200_OK self.project.reload() - assert self.project.logs.latest().action == f'{self.ADDON_SHORT_NAME}_folder_selected' - assert res.json['result']['folder']['name'] == self.folder.name + assert ( + self.project.logs.latest().action + == f"{self.ADDON_SHORT_NAME}_folder_selected" + ) + assert res.json["result"]["folder"]["name"] == self.folder.name def test_get_config(self): - with mock.patch.object(self.client, '_folder_metadata') as mock_metadata: + with mock.patch.object( + self.client, "_folder_metadata" + ) as mock_metadata: mock_metadata.return_value = self.folder - self.node_settings.api._client = 'client' + self.node_settings.api._client = "client" self.node_settings.save() - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_get_config') + url = self.project.api_url_for( + f"{self.ADDON_SHORT_NAME}_get_config" + ) res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK - assert 'result' in res.json - result = res.json['result'] + assert "result" in res.json + result = res.json["result"] serialized = self.Serializer( node_settings=self.node_settings, - user_settings=self.node_settings.user_settings + user_settings=self.node_settings.user_settings, ).serialized_node_settings - serialized['validCredentials'] = self.citationsProvider().check_credentials(self.node_settings) + serialized["validCredentials"] = ( + self.citationsProvider().check_credentials(self.node_settings) + ) assert serialized == result def test_folder_list(self): - with mock.patch.object(self.client, '_get_folders'): + with mock.patch.object(self.client, "_get_folders"): self.node_settings.set_auth(self.external_account, self.user) self.node_settings.save() - url = self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_citation_list') + url = self.project.api_url_for( + f"{self.ADDON_SHORT_NAME}_citation_list" + ) res = self.app.get(url, auth=self.user.auth) assert res.status_code == http_status.HTTP_200_OK def test_check_credentials(self): - with mock.patch.object(self.client, 'client', new_callable=mock.PropertyMock) as mock_client: + with mock.patch.object( + self.client, "client", new_callable=mock.PropertyMock + ) as mock_client: self.provider = self.citationsProvider() mock_client.side_effect = HTTPError(403) assert not self.provider.check_credentials(self.node_settings) @@ -372,17 +406,19 @@ def test_widget_view_complete(self): self.citationsProvider().set_config( self.node_settings, self.user, - self.folder.json['id'], + self.folder.json["id"], self.folder.name, - Auth(self.user) + Auth(self.user), ) assert self.node_settings.complete - assert self.node_settings.list_id == 'Fake Key' + assert self.node_settings.list_id == "Fake Key" - res = self.citationsProvider().widget(self.project.get_addon(self.ADDON_SHORT_NAME)) + res = self.citationsProvider().widget( + self.project.get_addon(self.ADDON_SHORT_NAME) + ) - assert res['complete'] - assert res['list_id'] == 'Fake Key' + assert res["complete"] + assert res["list_id"] == "Fake Key" def test_widget_view_incomplete(self): # JSON: tell the widget when it hasn't been configured @@ -391,41 +427,41 @@ def test_widget_view_incomplete(self): assert not self.node_settings.complete assert self.node_settings.list_id is None - res = self.citationsProvider().widget(self.project.get_addon(self.ADDON_SHORT_NAME)) + res = self.citationsProvider().widget( + self.project.get_addon(self.ADDON_SHORT_NAME) + ) - assert not res['complete'] - assert res['list_id'] is None + assert not res["complete"] + assert res["list_id"] is None @responses.activate def test_citation_list_root(self): - responses.add( responses.Response( responses.GET, self.foldersApiUrl, - body=self.mockResponses['folders'], - content_type='application/json' + body=self.mockResponses["folders"], + content_type="application/json", ) ) res = self.app.get( - self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_citation_list'), - auth=self.user.auth + self.project.api_url_for(f"{self.ADDON_SHORT_NAME}_citation_list"), + auth=self.user.auth, ) - root = res.json['contents'][0] - assert root['kind'] == 'folder' - assert root['id'] == 'ROOT' - assert root['parent_list_id'] == '__' + root = res.json["contents"][0] + assert root["kind"] == "folder" + assert root["id"] == "ROOT" + assert root["parent_list_id"] == "__" @responses.activate def test_citation_list_non_root(self): - responses.add( responses.Response( responses.GET, self.foldersApiUrl, - body=self.mockResponses['folders'], - content_type='application/json' + body=self.mockResponses["folders"], + content_type="application/json", ) ) @@ -433,36 +469,38 @@ def test_citation_list_non_root(self): responses.Response( responses.GET, self.documentsApiUrl, - body=self.mockResponses['documents'], - content_type='application/json' + body=self.mockResponses["documents"], + content_type="application/json", ) ) res = self.app.get( - self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_citation_list', list_id='ROOT'), - auth=self.user.auth + self.project.api_url_for( + f"{self.ADDON_SHORT_NAME}_citation_list", list_id="ROOT" + ), + auth=self.user.auth, ) - children = res.json['contents'] + children = res.json["contents"] assert len(children) == 7 - assert children[0]['kind'] == 'folder' - assert children[1]['kind'] == 'file' - assert children[1].get('csl') is not None + assert children[0]["kind"] == "folder" + assert children[1]["kind"] == "file" + assert children[1].get("csl") is not None @responses.activate def test_citation_list_non_linked_or_child_non_authorizer(self): non_authorizing_user = AuthUserFactory() self.project.add_contributor(non_authorizing_user, save=True) - self.node_settings.list_id = 'e843da05-8818-47c2-8c37-41eebfc4fe3f' + self.node_settings.list_id = "e843da05-8818-47c2-8c37-41eebfc4fe3f" self.node_settings.save() responses.add( responses.Response( responses.GET, self.foldersApiUrl, - body=self.mockResponses['folders'], - content_type='application/json' + body=self.mockResponses["folders"], + content_type="application/json", ) ) @@ -470,13 +508,15 @@ def test_citation_list_non_linked_or_child_non_authorizer(self): responses.Response( responses.GET, self.documentsApiUrl, - body=self.mockResponses['documents'], - content_type='application/json' + body=self.mockResponses["documents"], + content_type="application/json", ) ) res = self.app.get( - self.project.api_url_for(f'{self.ADDON_SHORT_NAME}_citation_list', list_id='ROOT'), + self.project.api_url_for( + f"{self.ADDON_SHORT_NAME}_citation_list", list_id="ROOT" + ), auth=non_authorizing_user.auth, ) assert res.status_code == http_status.HTTP_403_FORBIDDEN diff --git a/addons/base/utils.py b/addons/base/utils.py index c86f790fecd..9a64fc38625 100644 --- a/addons/base/utils.py +++ b/addons/base/utils.py @@ -6,7 +6,7 @@ def get_mfr_url(target, provider_name): - if hasattr(target, 'osfstorage_region') and provider_name == 'osfstorage': + if hasattr(target, "osfstorage_region") and provider_name == "osfstorage": return target.osfstorage_region.mfr_url return MFR_SERVER_URL @@ -16,42 +16,76 @@ def serialize_addon_config(config, user): user_addon = user.get_addon(config.short_name) ret = { - 'addon_short_name': config.short_name, - 'addon_full_name': config.full_name, - 'node_settings_template': lookup.get_template(basename(config.node_settings_template)), - 'user_settings_template': lookup.get_template(basename(config.user_settings_template)), - 'is_enabled': user_addon is not None, - 'addon_icon_url': config.icon_url, + "addon_short_name": config.short_name, + "addon_full_name": config.full_name, + "node_settings_template": lookup.get_template( + basename(config.node_settings_template) + ), + "user_settings_template": lookup.get_template( + basename(config.user_settings_template) + ), + "is_enabled": user_addon is not None, + "addon_icon_url": config.icon_url, } ret.update(user_addon.to_json(user) if user_addon else {}) return ret + def get_addons_by_config_type(config_type, user): - addons = [addon for addon in settings.ADDONS_AVAILABLE if config_type in addon.configs] - return [serialize_addon_config(addon_config, user) for addon_config in sorted(addons, key=lambda cfg: cfg.full_name.lower())] + addons = [ + addon + for addon in settings.ADDONS_AVAILABLE + if config_type in addon.configs + ] + return [ + serialize_addon_config(addon_config, user) + for addon_config in sorted( + addons, key=lambda cfg: cfg.full_name.lower() + ) + ] + def format_last_known_metadata(auth, node, file, error_type): msg = """ """ # None is default - if error_type != 'FILE_SUSPENDED' and ((auth.user and node.is_contributor_or_group_member(auth.user)) or - (auth.private_key and auth.private_key in node.private_link_keys_active)): + if error_type != "FILE_SUSPENDED" and ( + (auth.user and node.is_contributor_or_group_member(auth.user)) + or ( + auth.private_key + and auth.private_key in node.private_link_keys_active + ) + ): last_meta = file.last_known_metadata - last_seen = last_meta.get('last_seen', None) - hashes = last_meta.get('hashes', None) - path = last_meta.get('path', None) - size = last_meta.get('size', None) + last_seen = last_meta.get("last_seen", None) + hashes = last_meta.get("hashes", None) + path = last_meta.get("path", None) + size = last_meta.get("size", None) parts = [ - """
This file was """ if last_seen or hashes or path or size else '', - """last seen on {} UTC """.format(last_seen.strftime('%c')) if last_seen else '', - f"""and found at path {markupsafe.escape(path)} """ if last_seen and path else '', - f"""last found at path {markupsafe.escape(path)} """ if not last_seen and path else '', - f"""with a file size of {size} bytes""" if size and (last_seen or path) else '', - f"""last seen with a file size of {size} bytes""" if size and not (last_seen or path) else '', - """.

""" if last_seen or hashes or path or size else '', + """
This file was """ + if last_seen or hashes or path or size + else "", + """last seen on {} UTC """.format(last_seen.strftime("%c")) + if last_seen + else "", + f"""and found at path {markupsafe.escape(path)} """ + if last_seen and path + else "", + f"""last found at path {markupsafe.escape(path)} """ + if not last_seen and path + else "", + f"""with a file size of {size} bytes""" + if size and (last_seen or path) + else "", + f"""last seen with a file size of {size} bytes""" + if size and not (last_seen or path) + else "", + """.

""" if last_seen or hashes or path or size else "", """Hashes of last seen version:

{}

""".format( - '
'.join([f'{k}: {v}' for k, v in hashes.items()]) - ) if hashes else '', # TODO: Format better for UI - msg + "
".join([f"{k}: {v}" for k, v in hashes.items()]) + ) + if hashes + else "", # TODO: Format better for UI + msg, ] - return ''.join(parts) + return "".join(parts) return msg diff --git a/addons/base/views.py b/addons/base/views.py index 6fea2444421..a63ea2d6960 100644 --- a/addons/base/views.py +++ b/addons/base/views.py @@ -28,7 +28,11 @@ from framework.auth import Auth from framework.auth import cas from framework.auth import oauth_scopes -from framework.auth.decorators import collect_auth, must_be_logged_in, must_be_signed +from framework.auth.decorators import ( + collect_auth, + must_be_logged_in, + must_be_signed, +) from framework.exceptions import HTTPError from framework.flask import redirect from framework.sentry import log_exception @@ -53,21 +57,26 @@ DraftRegistration, Guid, FileVersionUserMetadata, - FileVersion + FileVersion, ) from osf.metrics import PreprintView, PreprintDownload from osf.utils import permissions from osf.utils.requests import requests_retry_session from website.profile.utils import get_profile_image_url from website.project import decorators -from website.project.decorators import must_be_contributor_or_public, must_be_valid_project, check_contributor_auth +from website.project.decorators import ( + must_be_contributor_or_public, + must_be_valid_project, + check_contributor_auth, +) from website.project.utils import serialize_node from website.util import rubeus # import so that associated listener is instantiated and gets emails from website.notifications.events.files import FileEvent # noqa -ERROR_MESSAGES = {'FILE_GONE': """ +ERROR_MESSAGES = { + "FILE_GONE": """ @@ -78,7 +87,7 @@

It was deleted by {deleted_by} on {deleted_on}.

""", - 'FILE_GONE_ACTOR_UNKNOWN': """ + "FILE_GONE_ACTOR_UNKNOWN": """ @@ -89,7 +98,7 @@

It was deleted on {deleted_on}.

""", - 'DONT_KNOW': """ + "DONT_KNOW": """ @@ -97,7 +106,7 @@

File not found at {provider}.

""", - 'BLAME_PROVIDER': """ + "BLAME_PROVIDER": """ @@ -109,36 +118,39 @@

You may wish to verify this through {provider}'s website.

""", - 'FILE_SUSPENDED': """ + "FILE_SUSPENDED": """