From d0dc1ccebb4e153edd92555a7eb705caee56c12d Mon Sep 17 00:00:00 2001 From: Mariia Lychko Date: Mon, 12 Aug 2024 16:09:29 +0300 Subject: [PATCH 01/18] - poetry fix; --- Dockerfile | 1 + 1 file changed, 1 insertion(+) 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 \ From bc9fae9fe5a7d5162d35f0a868a0f6f110e1fb55 Mon Sep 17 00:00:00 2001 From: Mariia Lychko Date: Mon, 12 Aug 2024 16:10:07 +0300 Subject: [PATCH 02/18] - ruff settings; --- pyproject.toml | 15 +++++++++++++++ requirements.txt | 2 ++ 2 files changed, 17 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b78ec011364..2b223f022ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,6 +114,7 @@ webtest-plus = "1.0.0" Faker = "23.2.1" schema = "0.7.4" responses = "0.25.0" +ruff = "0.5.5" # Syntax checking flake8 = "7.0.0" @@ -169,3 +170,17 @@ uwsgi = "2.0.24" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + + +[tool.ruff] +# Set the maximum line length to 79. +line-length = 79 +exclude = [".venv", "requirements-dev.txt", "requirements.txt", ".txt", ".git", ".yaml", ".toml", "Dockerfile"] +extend-exclude = ["migrations"] + +[tool.ruff.lint] +# Add the `line-too-long` rule to the enforced rule set. By default, Ruff omits rules that +# overlap with the use of a formatter, like Black, but we can override this behavior by +# explicitly adding the rule. +extend-select = ["E501"] + diff --git a/requirements.txt b/requirements.txt index 7fb762e01b0..78f5bf8e44f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -105,3 +105,5 @@ git+https://github.com/CenterForOpenScience/django-elasticsearch-metrics.git@f5b # Impact Metrics CSV Export djangorestframework-csv==3.0.2 gevent==24.2.1 + +ruff == 0.5.5 From 6d2f4ba633616422415cbe1b2319d0c02927f49d Mon Sep 17 00:00:00 2001 From: Mariia Lychko Date: Mon, 12 Aug 2024 16:13:31 +0300 Subject: [PATCH 03/18] - ruff fixed modules; --- addons/base/tests/models.py | 1 - addons/base/tests/utils.py | 3 +-- addons/bitbucket/tests/test_models.py | 2 -- addons/bitbucket/tests/test_serializer.py | 1 - addons/bitbucket/tests/test_views.py | 3 --- addons/box/tests/factories.py | 1 - addons/github/tests/test_models.py | 1 - addons/github/tests/utils.py | 1 - addons/gitlab/tests/utils.py | 2 +- addons/mendeley/tests/test_views.py | 2 +- addons/onedrive/tests/test_client.py | 1 - addons/onedrive/tests/test_serializer.py | 1 - addons/osfstorage/tests/factories.py | 3 +-- addons/osfstorage/tests/test_views.py | 4 ++-- addons/osfstorage/tests/utils.py | 1 - addons/twofactor/tests/test_models.py | 2 +- addons/wiki/tests/test_views.py | 2 +- addons/zotero/tests/test_views.py | 2 +- scripts/add_taxonomies_to_paleoarxiv.py | 5 ----- scripts/embargo_registrations.py | 2 +- scripts/generate_sitemap.py | 6 ++---- scripts/osfstorage/usage_audit.py | 2 +- scripts/populate_new_and_noteworthy_projects.py | 1 - scripts/populate_preprint_providers.py | 2 +- scripts/retract_registrations.py | 2 +- scripts/stuck_registration_audit.py | 6 ++---- scripts/tests/test_fix_registration_unclaimed_records.py | 2 +- scripts/tests/test_send_queued_mails.py | 2 +- scripts/tests/test_user_system_tag_normalization.py | 4 ++-- scripts/unpurge_trashed_files.py | 3 +-- tests/framework_tests/test_email.py | 4 +--- tests/framework_tests/test_modular_templates.py | 4 +--- tests/identifiers/test_crossref.py | 3 --- tests/test_addons.py | 3 --- tests/test_auth.py | 2 +- tests/test_conferences.py | 1 - tests/test_node_licenses.py | 1 - tests/test_notifications.py | 6 +++--- tests/test_oauth.py | 4 +--- tests/test_preprints.py | 4 ++-- tests/test_registrations/test_embargoes.py | 5 ++--- tests/test_registrations/test_registration_approvals.py | 8 +++----- tests/test_registrations/test_retractions.py | 2 +- tests/test_sanitize.py | 1 - tests/test_serializers.py | 3 +-- tests/test_spam_mixin.py | 1 - tests/test_test_utils.py | 4 +--- tests/test_views.py | 3 +-- tests/test_websitefiles.py | 1 - website/settings/local-travis.py | 1 - 50 files changed, 40 insertions(+), 91 deletions(-) diff --git a/addons/base/tests/models.py b/addons/base/tests/models.py index c20f834cf24..e92ed1114e6 100644 --- a/addons/base/tests/models.py +++ b/addons/base/tests/models.py @@ -12,7 +12,6 @@ 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 diff --git a/addons/base/tests/utils.py b/addons/base/tests/utils.py index 5ab0bbbf45c..33fefb999ec 100644 --- a/addons/base/tests/utils.py +++ b/addons/base/tests/utils.py @@ -1,9 +1,8 @@ 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 diff --git a/addons/bitbucket/tests/test_models.py b/addons/bitbucket/tests/test_models.py index bfa759e23e5..2764e43526d 100644 --- a/addons/bitbucket/tests/test_models.py +++ b/addons/bitbucket/tests/test_models.py @@ -4,7 +4,6 @@ from tests.base import OsfTestCase, get_default_metaschema from osf_tests.factories import ( - ExternalAccountFactory, ProjectFactory, UserFactory, DraftRegistrationFactory, @@ -13,7 +12,6 @@ from framework.auth import Auth from addons.bitbucket.exceptions import NotFoundError -from addons.bitbucket import settings as bitbucket_settings from addons.bitbucket.models import NodeSettings from addons.bitbucket.tests.factories import ( BitbucketAccountFactory, diff --git a/addons/bitbucket/tests/test_serializer.py b/addons/bitbucket/tests/test_serializer.py index 008625ab909..7c9000cefd2 100644 --- a/addons/bitbucket/tests/test_serializer.py +++ b/addons/bitbucket/tests/test_serializer.py @@ -1,6 +1,5 @@ """Serializer tests for the Bitbucket addon.""" -from unittest import mock import pytest from tests.base import OsfTestCase diff --git a/addons/bitbucket/tests/test_views.py b/addons/bitbucket/tests/test_views.py index 51cac6012b5..11972791c9a 100644 --- a/addons/bitbucket/tests/test_views.py +++ b/addons/bitbucket/tests/test_views.py @@ -1,7 +1,6 @@ from rest_framework import status as http_status from unittest import mock -import datetime import unittest import pytest @@ -16,13 +15,11 @@ from framework.exceptions import HTTPError from framework.auth import Auth -from website.util import api_url_for from addons.base.tests.views import ( OAuthAddonAuthViewsTestCaseMixin, OAuthAddonConfigViewsTestCaseMixin ) from addons.bitbucket import utils from addons.bitbucket.api import BitbucketClient -from addons.bitbucket.models import BitbucketProvider from addons.bitbucket.serializer import BitbucketSerializer from addons.bitbucket.tests.factories import BitbucketAccountFactory from addons.bitbucket.tests.utils import BitbucketAddonTestCase, create_mock_bitbucket diff --git a/addons/box/tests/factories.py b/addons/box/tests/factories.py index 17b1898e49d..6369eb0cffe 100644 --- a/addons/box/tests/factories.py +++ b/addons/box/tests/factories.py @@ -1,5 +1,4 @@ """Factory boy factories for the Box addon.""" -from datetime import datetime from dateutil.relativedelta import relativedelta from django.utils import timezone diff --git a/addons/github/tests/test_models.py b/addons/github/tests/test_models.py index 6432d818d3c..e2b74eea9f3 100644 --- a/addons/github/tests/test_models.py +++ b/addons/github/tests/test_models.py @@ -20,7 +20,6 @@ from addons.base import exceptions from addons.github.exceptions import NotFoundError -from .utils import create_mock_github pytestmark = pytest.mark.django_db diff --git a/addons/github/tests/utils.py b/addons/github/tests/utils.py index 61ed45d6bbe..0b382923e5a 100644 --- a/addons/github/tests/utils.py +++ b/addons/github/tests/utils.py @@ -1,7 +1,6 @@ from unittest import mock from json import dumps import github3 -from github3.repos import Repository from github3.session import GitHubSession from addons.github.api import GitHubClient diff --git a/addons/gitlab/tests/utils.py b/addons/gitlab/tests/utils.py index ee2e3e132a7..0f20105cee9 100644 --- a/addons/gitlab/tests/utils.py +++ b/addons/gitlab/tests/utils.py @@ -91,7 +91,7 @@ def create_mock_gitlab(user='osfio', private=False): branch = mock.Mock(**{ 'commit': {'author_email': f'{user}@gmail.com', - 'author_name': f'', + 'author_name': '', 'authored_date': '2017-07-05T16:43:04.000+00:00', 'committed_date': '2017-07-05T16:43:04.000+00:00', 'committer_email': f'{user}@gmail.com', diff --git a/addons/mendeley/tests/test_views.py b/addons/mendeley/tests/test_views.py index d9ee6d7af35..8e13c3d494b 100644 --- a/addons/mendeley/tests/test_views.py +++ b/addons/mendeley/tests/test_views.py @@ -1,6 +1,6 @@ from unittest import mock import pytest -from urllib.parse import urlparse, urljoin +from urllib.parse import urljoin from addons.base.tests import views from addons.base.tests.utils import MockFolder diff --git a/addons/onedrive/tests/test_client.py b/addons/onedrive/tests/test_client.py index a6c32f78a11..b24ecd26573 100644 --- a/addons/onedrive/tests/test_client.py +++ b/addons/onedrive/tests/test_client.py @@ -1,4 +1,3 @@ -import pytest from unittest import mock diff --git a/addons/onedrive/tests/test_serializer.py b/addons/onedrive/tests/test_serializer.py index f561154b88c..63451f4745c 100644 --- a/addons/onedrive/tests/test_serializer.py +++ b/addons/onedrive/tests/test_serializer.py @@ -2,7 +2,6 @@ from unittest import mock import pytest -from addons.onedrive.models import OneDriveProvider from addons.onedrive.serializer import OneDriveSerializer from addons.onedrive.tests.factories import OneDriveAccountFactory from addons.onedrive.tests.utils import MockOneDriveClient, dummy_user_info, raw_root_folder_response diff --git a/addons/osfstorage/tests/factories.py b/addons/osfstorage/tests/factories.py index ba2b7c7200d..cf1e3a2a067 100644 --- a/addons/osfstorage/tests/factories.py +++ b/addons/osfstorage/tests/factories.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 from django.apps import apps from django.utils import timezone -from factory import SubFactory, post_generation, Sequence +from factory import SubFactory, post_generation from factory.django import DjangoModelFactory from osf_tests.factories import AuthUserFactory from osf import models -from addons.osfstorage.models import Region settings = apps.get_app_config('addons_osfstorage') diff --git a/addons/osfstorage/tests/test_views.py b/addons/osfstorage/tests/test_views.py index 19940043548..e26e50626e2 100644 --- a/addons/osfstorage/tests/test_views.py +++ b/addons/osfstorage/tests/test_views.py @@ -10,7 +10,7 @@ from addons.github.tests.factories import GitHubAccountFactory from addons.github.models import GithubFile -from addons.osfstorage.models import OsfStorageFileNode, OsfStorageFolder +from addons.osfstorage.models import OsfStorageFileNode from framework.auth.core import Auth from addons.osfstorage.tests.utils import ( StorageTestCase, Delta, AssertDeltas, @@ -24,7 +24,7 @@ from framework.auth import cas from osf import features -from osf.models import Tag, QuickFilesNode +from osf.models import Tag from osf.models import files as models from addons.osfstorage.apps import osf_storage_root from addons.osfstorage import utils diff --git a/addons/osfstorage/tests/utils.py b/addons/osfstorage/tests/utils.py index c9381c3254b..77b6452d45f 100644 --- a/addons/osfstorage/tests/utils.py +++ b/addons/osfstorage/tests/utils.py @@ -4,7 +4,6 @@ from osf_tests.factories import ProjectFactory from addons.osfstorage import settings as storage_settings -import collections from framework.auth import Auth diff --git a/addons/twofactor/tests/test_models.py b/addons/twofactor/tests/test_models.py index 05b6cd219f3..6863a2a83b6 100644 --- a/addons/twofactor/tests/test_models.py +++ b/addons/twofactor/tests/test_models.py @@ -1,5 +1,5 @@ import unittest -from urllib.parse import urlparse, urljoin, parse_qs +from urllib.parse import urlparse, parse_qs import pytest from addons.twofactor.tests.utils import _valid_code diff --git a/addons/wiki/tests/test_views.py b/addons/wiki/tests/test_views.py index 646d03c5e15..ea74e312031 100644 --- a/addons/wiki/tests/test_views.py +++ b/addons/wiki/tests/test_views.py @@ -7,7 +7,7 @@ from addons.wiki.models import WikiPage, WikiVersion from addons.wiki.exceptions import (NameInvalidError, NameMaximumLengthError, - PageCannotRenameError, PageConflictError, PageNotFoundError) + PageCannotRenameError, PageConflictError) from addons.wiki.tests.factories import WikiVersionFactory, WikiFactory from addons.wiki.utils import serialize_wiki_widget from framework.auth import Auth diff --git a/addons/zotero/tests/test_views.py b/addons/zotero/tests/test_views.py index a47234c7717..20f500aff06 100644 --- a/addons/zotero/tests/test_views.py +++ b/addons/zotero/tests/test_views.py @@ -1,6 +1,6 @@ from unittest import mock import pytest -from urllib.parse import urlparse, urljoin +from urllib.parse import urljoin import responses from framework.auth import Auth diff --git a/scripts/add_taxonomies_to_paleoarxiv.py b/scripts/add_taxonomies_to_paleoarxiv.py index f746b55b25b..110fcdbe7fb 100644 --- a/scripts/add_taxonomies_to_paleoarxiv.py +++ b/scripts/add_taxonomies_to_paleoarxiv.py @@ -1,16 +1,11 @@ -import os -import json import logging import sys from django.db import transaction -from django.apps import apps from scripts import utils as script_utils -from scripts.populate_preprint_providers import update_or_create from osf.models import PreprintProvider, Subject from website.app import init_app -from website import settings logger = logging.getLogger(__name__) diff --git a/scripts/embargo_registrations.py b/scripts/embargo_registrations.py index 23d836abb79..a4e57a3adc7 100644 --- a/scripts/embargo_registrations.py +++ b/scripts/embargo_registrations.py @@ -18,7 +18,7 @@ from framework.celery_tasks import app as celery_app from website import settings -from osf.models import Embargo, Registration, NodeLog +from osf.models import Embargo, Registration from scripts import utils as scripts_utils diff --git a/scripts/generate_sitemap.py b/scripts/generate_sitemap.py index 8cce0a40a29..4a32f6929d0 100644 --- a/scripts/generate_sitemap.py +++ b/scripts/generate_sitemap.py @@ -5,7 +5,7 @@ import gzip import os import shutil -from urllib.parse import urlparse, urljoin +from urllib.parse import urljoin import xml import django @@ -15,9 +15,7 @@ from framework import sentry from framework.celery_tasks import app as celery_app -from django.db.models import Q -from osf.models import OSFUser, AbstractNode, Preprint, PreprintProvider -from osf.utils.workflows import DefaultStates +from osf.models import OSFUser, AbstractNode, Preprint from scripts import utils as script_utils from website import settings from website.app import init_app diff --git a/scripts/osfstorage/usage_audit.py b/scripts/osfstorage/usage_audit.py index 8a8ffb6c1f1..d7b84ae63e0 100644 --- a/scripts/osfstorage/usage_audit.py +++ b/scripts/osfstorage/usage_audit.py @@ -112,7 +112,7 @@ def main(send_email=False): logger.info('Sending email...') mails.send_mail('support+scripts@osf.io', mails.EMPTY, body='\n'.join(lines), subject='Script: OsfStorage usage audit', can_change_preferences=False,) else: - logger.info(f'send_email is False, not sending email') + logger.info('send_email is False, not sending email') logger.info(f'{len(lines)} offending project(s) and user(s) found') else: logger.info('No offending projects or users found') diff --git a/scripts/populate_new_and_noteworthy_projects.py b/scripts/populate_new_and_noteworthy_projects.py index 9f12abc17e9..2f2dbb9fc25 100644 --- a/scripts/populate_new_and_noteworthy_projects.py +++ b/scripts/populate_new_and_noteworthy_projects.py @@ -15,7 +15,6 @@ from framework.auth.core import Auth from scripts import utils as script_utils from framework.celery_tasks import app as celery_app -from framework.encryption import ensure_bytes from website.settings import \ POPULAR_LINKS_NODE, NEW_AND_NOTEWORTHY_LINKS_NODE,\ NEW_AND_NOTEWORTHY_CONTRIBUTOR_BLACKLIST diff --git a/scripts/populate_preprint_providers.py b/scripts/populate_preprint_providers.py index 1f354b3d05d..8a0eed8a379 100644 --- a/scripts/populate_preprint_providers.py +++ b/scripts/populate_preprint_providers.py @@ -10,7 +10,7 @@ import django django.setup() -from osf.models import Subject, PreprintProvider, NodeLicense, NotificationSubscription +from osf.models import Subject, PreprintProvider, NodeLicense logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/scripts/retract_registrations.py b/scripts/retract_registrations.py index fcccd5c232d..281c201ff0d 100644 --- a/scripts/retract_registrations.py +++ b/scripts/retract_registrations.py @@ -31,7 +31,7 @@ def main(dry_run=True): parent_registration = retraction.registrations.get() except Exception as err: logger.exception(f'Could not find registration associated with retraction {retraction}') - logger.error(f'Skipping...') + logger.error('Skipping...') sentry.log_message(str(err)) continue diff --git a/scripts/stuck_registration_audit.py b/scripts/stuck_registration_audit.py index b5445873faf..9a77e3073b8 100644 --- a/scripts/stuck_registration_audit.py +++ b/scripts/stuck_registration_audit.py @@ -2,7 +2,6 @@ import os import csv import logging -from datetime import datetime from website.app import setup_django setup_django() @@ -15,9 +14,8 @@ from framework.auth import Auth from framework.celery_tasks import app as celery_app from osf.management.commands import force_archive as fa -from osf.models import ArchiveJob, Registration -from website.archiver import ARCHIVER_INITIATED -from website.settings import ARCHIVE_TIMEOUT_TIMEDELTA, ADDONS_REQUESTED +from osf.models import Registration +from website.settings import ADDONS_REQUESTED from scripts import utils as scripts_utils diff --git a/scripts/tests/test_fix_registration_unclaimed_records.py b/scripts/tests/test_fix_registration_unclaimed_records.py index 8b408760428..5f71534e0c3 100644 --- a/scripts/tests/test_fix_registration_unclaimed_records.py +++ b/scripts/tests/test_fix_registration_unclaimed_records.py @@ -1,7 +1,7 @@ import pytest from framework.auth.core import Auth -from osf_tests.factories import PreprintFactory, UserFactory, ProjectFactory +from osf_tests.factories import UserFactory, ProjectFactory from scripts.fix_registration_unclaimed_records import main as fix_records_script from osf_tests.utils import mock_archive diff --git a/scripts/tests/test_send_queued_mails.py b/scripts/tests/test_send_queued_mails.py index 142eb75c4a6..c5dd5a49db5 100644 --- a/scripts/tests/test_send_queued_mails.py +++ b/scripts/tests/test_send_queued_mails.py @@ -69,7 +69,7 @@ def test_pop_and_verify_mails_for_each_user(self): mails_ = list(pop_and_verify_mails_for_each_user(user_queue)) assert len(mails_) == 2 user_mails = [mail.user for mail in mails_] - assert not (user_with_email_sent in user_mails) + assert user_with_email_sent not in user_mails assert user_with_multiple_emails in user_mails assert user_with_no_emails_sent in user_mails diff --git a/scripts/tests/test_user_system_tag_normalization.py b/scripts/tests/test_user_system_tag_normalization.py index 689bffc72e0..6f43d33a5ba 100644 --- a/scripts/tests/test_user_system_tag_normalization.py +++ b/scripts/tests/test_user_system_tag_normalization.py @@ -2,10 +2,10 @@ import pytz from datetime import datetime -from osf_tests.factories import PreprintFactory, UserFactory, ProjectFactory, TagFactory +from osf_tests.factories import UserFactory from osf.models import Tag from scripts.normalize_user_tags import normalize_source_tags, add_claimed_tags, add_osf_provider_tags, add_prereg_campaign_tags, PROVIDER_SOURCE_TAGS, CAMPAIGN_SOURCE_TAGS, PROVIDER_CLAIMED_TAGS, CAMPAIGN_CLAIMED_TAGS -from website.util.metrics import OsfSourceTags, CampaignSourceTags, OsfClaimedTags, CampaignClaimedTags +from website.util.metrics import OsfSourceTags, CampaignSourceTags, OsfClaimedTags pytestmark = pytest.mark.django_db diff --git a/scripts/unpurge_trashed_files.py b/scripts/unpurge_trashed_files.py index ea4bcd38605..2f5358179d2 100644 --- a/scripts/unpurge_trashed_files.py +++ b/scripts/unpurge_trashed_files.py @@ -4,7 +4,6 @@ setup_django() import argparse -from django.template.defaultfilters import filesizeformat from google.cloud.storage.client import Client from google.oauth2.service_account import Credentials @@ -30,7 +29,7 @@ def unpurge_trash(ids): def main(): parser = argparse.ArgumentParser( - description=f'Unpurges TrashedFiles by id' + description='Unpurges TrashedFiles by id' ) parser.add_argument( 'ids', diff --git a/tests/framework_tests/test_email.py b/tests/framework_tests/test_email.py index 1dde2ada9ad..25802086c9d 100644 --- a/tests/framework_tests/test_email.py +++ b/tests/framework_tests/test_email.py @@ -4,9 +4,7 @@ from unittest import mock from unittest.mock import MagicMock -import sendgrid from sendgrid import SendGridAPIClient -from sendgrid.helpers.mail import Category from framework.email.tasks import send_email, _send_with_sendgrid from website import settings @@ -18,7 +16,7 @@ try: s = smtplib.SMTP(settings.MAIL_SERVER) s.quit() -except Exception as err: +except Exception: SERVER_RUNNING = False diff --git a/tests/framework_tests/test_modular_templates.py b/tests/framework_tests/test_modular_templates.py index cf967facc56..031b6def015 100644 --- a/tests/framework_tests/test_modular_templates.py +++ b/tests/framework_tests/test_modular_templates.py @@ -7,14 +7,12 @@ import os import flask -from lxml.html import fragment_fromstring import werkzeug.wrappers from rest_framework import status as http_status from framework.exceptions import HTTPError from framework.routing import ( - Renderer, JSONRenderer, WebRenderer, - render_mako_string, + Renderer, JSONRenderer, render_mako_string, ) from tests.base import AppTestCase, OsfTestCase diff --git a/tests/identifiers/test_crossref.py b/tests/identifiers/test_crossref.py index a7164b569d5..f6a70120c63 100644 --- a/tests/identifiers/test_crossref.py +++ b/tests/identifiers/test_crossref.py @@ -1,4 +1,3 @@ -import os from unittest import mock import lxml import pytest @@ -9,13 +8,11 @@ from osf.models import NodeLicense from osf_tests.factories import ( - ProjectFactory, PreprintFactory, PreprintProviderFactory, AuthUserFactory, InstitutionFactory ) -from framework.flask import rm_handlers from framework.auth.core import Auth from framework.auth.utils import impute_names diff --git a/tests/test_addons.py b/tests/test_addons.py index 92da61642b5..9c35666f7bb 100644 --- a/tests/test_addons.py +++ b/tests/test_addons.py @@ -1,7 +1,6 @@ import datetime import time import functools -import logging from importlib import import_module from unittest.mock import Mock @@ -43,8 +42,6 @@ from api.caching.utils import storage_usage_cache from dateutil.parser import parse as parse_date from framework import sentry -from api.base.settings.defaults import API_BASE -from tests.json_api_test_app import JSONAPITestApp from website.settings import EXTERNAL_EMBER_APPS from waffle.testutils import override_flag from django.conf import settings as django_conf_settings diff --git a/tests/test_auth.py b/tests/test_auth.py index f59f1498a93..538daad3f10 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -6,7 +6,7 @@ import logging from unittest import mock -from urllib.parse import urlparse, quote +from urllib.parse import urlparse from rest_framework import status as http_status from flask import Flask from werkzeug.wrappers import Response diff --git a/tests/test_conferences.py b/tests/test_conferences.py index 6837ad27edf..4aab15c47f8 100644 --- a/tests/test_conferences.py +++ b/tests/test_conferences.py @@ -9,7 +9,6 @@ from furl import furl from framework.auth import get_or_create_user -from framework.auth.core import Auth from osf.models import OSFUser, AbstractNode from addons.wiki.models import WikiVersion diff --git a/tests/test_node_licenses.py b/tests/test_node_licenses.py index d16cdb500d9..97dcb826805 100644 --- a/tests/test_node_licenses.py +++ b/tests/test_node_licenses.py @@ -11,7 +11,6 @@ from tests.base import OsfTestCase from osf.utils.migrations import ensure_licenses from tests.utils import assert_logs, assert_not_logs -from website import settings from osf.models.licenses import NodeLicense, serialize_node_license_record, serialize_node_license from osf.models import NodeLog from osf.exceptions import NodeStateError diff --git a/tests/test_notifications.py b/tests/test_notifications.py index b52190ca999..e7487f3d253 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -432,7 +432,7 @@ def test_create_new_subscription(self): url = api_url_for('configure_subscription') self.app.post(url, json=new_payload, auth=self.node.creator.auth) s.reload() - assert not self.node.creator in getattr(s, payload['notification_type']).all() + assert self.node.creator not in getattr(s, payload['notification_type']).all() assert self.node.creator in getattr(s, new_payload['notification_type']).all() def test_cannot_create_registration_subscription(self): @@ -483,11 +483,11 @@ def test_change_subscription_to_adopt_parent_subscription_removes_user(self): # assert that user is removed from the subscription entirely for n in constants.NOTIFICATION_TYPES: - assert not self.node.creator in getattr(s, n).all() + assert self.node.creator not in getattr(s, n).all() def test_configure_subscription_adds_node_id_to_notifications_configured(self): project = factories.ProjectFactory(creator=self.user) - assert not project._id in self.user.notifications_configured + assert project._id not in self.user.notifications_configured payload = { 'id': project._id, 'event': 'comments', diff --git a/tests/test_oauth.py b/tests/test_oauth.py index ef732e9e5d0..8c761ad3586 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -2,13 +2,11 @@ import logging import pytest -from oauthlib.oauth2.rfc6749.errors import CustomOAuth2Error from rest_framework import status as http_status import json -import logging from unittest import mock import time -from urllib.parse import urlparse, urljoin, parse_qs +from urllib.parse import urlparse, parse_qs import responses import pytz diff --git a/tests/test_preprints.py b/tests/test_preprints.py index 22d4167d764..537c69bb851 100644 --- a/tests/test_preprints.py +++ b/tests/test_preprints.py @@ -27,11 +27,11 @@ from framework.auth import signing from framework.celery_tasks import handlers from framework.postcommit_tasks.handlers import get_task_from_postcommit_queue, postcommit_celery_queue -from framework.exceptions import PermissionsError, HTTPError +from framework.exceptions import PermissionsError from framework.auth.core import Auth from addons.osfstorage.models import OsfStorageFile from addons.base import views -from osf.models import Tag, Preprint, PreprintLog, PreprintContributor, Subject +from osf.models import Tag, Preprint, PreprintLog, PreprintContributor from osf.exceptions import PreprintStateError, ValidationError, ValidationValueError from osf.utils.permissions import READ, WRITE, ADMIN diff --git a/tests/test_registrations/test_embargoes.py b/tests/test_registrations/test_embargoes.py index 4e147b760d0..adc3c6d9f7b 100644 --- a/tests/test_registrations/test_embargoes.py +++ b/tests/test_registrations/test_embargoes.py @@ -19,13 +19,12 @@ ) from tests import utils -from framework.exceptions import PermissionsError, HTTPError +from framework.exceptions import PermissionsError from framework.auth import Auth from osf.exceptions import ( InvalidSanctionRejectionToken, InvalidSanctionApprovalToken, NodeStateError, ) from osf.utils import tokens -from osf.models import AbstractNode from osf.models.sanctions import SanctionCallbackMixin, Embargo from osf.utils import permissions from osf.models import Registration, Contributor, OSFUser, SpamStatus @@ -73,7 +72,7 @@ def test__initiate_embargo_does_not_create_tokens_for_unregistered_admin(self): for_existing_registration=True ) assert self.user._id in embargo.approval_state - assert not unconfirmed_user._id in embargo.approval_state + assert unconfirmed_user._id not in embargo.approval_state def test__initiate_embargo_adds_admins_on_child_nodes(self): project_admin = UserFactory() diff --git a/tests/test_registrations/test_registration_approvals.py b/tests/test_registrations/test_registration_approvals.py index 014a311996f..24f5ecd1024 100644 --- a/tests/test_registrations/test_registration_approvals.py +++ b/tests/test_registrations/test_registration_approvals.py @@ -1,13 +1,11 @@ -import datetime from unittest import mock import pytest -from django.utils import timezone from tests.base import fake, OsfTestCase from osf_tests.factories import ( - EmbargoFactory, NodeFactory, ProjectFactory, - RegistrationFactory, RegistrationApprovalFactory, UserFactory, + NodeFactory, ProjectFactory, + RegistrationFactory, UserFactory, UnconfirmedUserFactory ) @@ -55,7 +53,7 @@ def test__initiate_approval_does_not_create_tokens_for_unregistered_admin(self): self.user ) assert self.user._id in approval.approval_state - assert not unconfirmed_user._id in approval.approval_state + assert unconfirmed_user._id not in approval.approval_state def test__initiate_approval_adds_admins_on_child_nodes(self): project_admin = UserFactory() diff --git a/tests/test_registrations/test_retractions.py b/tests/test_registrations/test_retractions.py index fb0f752884c..a96d7a16591 100644 --- a/tests/test_registrations/test_retractions.py +++ b/tests/test_registrations/test_retractions.py @@ -57,7 +57,7 @@ def test__initiate_retraction_does_not_create_tokens_for_unregistered_admin(self retraction = self.registration._initiate_retraction(self.user) assert self.user._id in retraction.approval_state - assert not unconfirmed_user._id in retraction.approval_state + assert unconfirmed_user._id not in retraction.approval_state def test__initiate_retraction_adds_admins_on_child_nodes(self): project_admin = UserFactory() diff --git a/tests/test_sanitize.py b/tests/test_sanitize.py index d6c1d5e381a..4056c458ff3 100644 --- a/tests/test_sanitize.py +++ b/tests/test_sanitize.py @@ -1,4 +1,3 @@ -import datetime import unittest from django.utils import timezone diff --git a/tests/test_serializers.py b/tests/test_serializers.py index f86e983cbd3..ec2e1a7fa3e 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -1,4 +1,3 @@ -from unittest import mock import datetime as dt import pytest @@ -12,7 +11,7 @@ ) from osf.models import NodeRelation from osf.utils import permissions -from tests.base import OsfTestCase, get_default_metaschema +from tests.base import OsfTestCase from framework.auth import Auth from website.project.views.node import _view_project, _serialize_node_search, _get_children, _get_readable_descendants diff --git a/tests/test_spam_mixin.py b/tests/test_spam_mixin.py index a97bc288e44..c16aa492423 100644 --- a/tests/test_spam_mixin.py +++ b/tests/test_spam_mixin.py @@ -1,4 +1,3 @@ -import abc import pytest from django.core.exceptions import ValidationError diff --git a/tests/test_test_utils.py b/tests/test_test_utils.py index a2cf51baa3a..495e5549402 100644 --- a/tests/test_test_utils.py +++ b/tests/test_test_utils.py @@ -1,10 +1,8 @@ -from unittest import mock -import unittest import pytest from framework.auth import Auth -from osf.models import AbstractNode, NodeLog +from osf.models import NodeLog from tests.base import OsfTestCase from osf_tests.factories import ProjectFactory diff --git a/tests/test_views.py b/tests/test_views.py index 406ffb66ede..b5f99b12bb7 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -20,7 +20,6 @@ from lxml import html from pytest import approx from rest_framework import status as http_status -from werkzeug.test import ClientRedirectError from addons.github.tests.factories import GitHubAccountFactory from addons.osfstorage import settings as osfstorage_settings @@ -4910,7 +4909,7 @@ def test_can_reset_password_if_form_success(self, mock_service_validate): # check verification_key_v2 for OSF is destroyed and verification_key for CAS is in place self.user.reload() assert self.user.verification_key_v2 == {} - assert not self.user.verification_key is None + assert self.user.verification_key is not None # check redirection to CAS login with username and the new verification_key(CAS) assert res.status_code == 302 diff --git a/tests/test_websitefiles.py b/tests/test_websitefiles.py index 35415ea9d08..aba711b0dd4 100644 --- a/tests/test_websitefiles.py +++ b/tests/test_websitefiles.py @@ -6,7 +6,6 @@ from addons.s3.models import S3File from osf.models import BaseFileNode, File, Folder from tests.base import OsfTestCase -import osf.models.files from osf_tests.factories import AuthUserFactory, ProjectFactory from website.files import exceptions from website.files.utils import attach_versions diff --git a/website/settings/local-travis.py b/website/settings/local-travis.py index 0f3174f3ba9..b55ef4ce8ee 100644 --- a/website/settings/local-travis.py +++ b/website/settings/local-travis.py @@ -3,7 +3,6 @@ NOTE: local.py will not be added to source control. ''' -import inspect import logging from . import defaults From 3e1ad4c88479ddd92d558bf7213e6d4b4ebf389a Mon Sep 17 00:00:00 2001 From: Mariia Lychko Date: Mon, 12 Aug 2024 16:18:57 +0300 Subject: [PATCH 04/18] - ruff fixed with PEP8; --- addons/base/__init__.py | 9 +- addons/base/apps.py | 64 +- addons/base/exceptions.py | 1 + addons/base/generic_views.py | 70 +- addons/base/logger.py | 48 +- addons/base/models.py | 340 +- addons/base/serializer.py | 164 +- addons/base/signals.py | 6 +- addons/base/tests/base.py | 38 +- addons/base/tests/logger.py | 19 +- addons/base/tests/models.py | 267 +- addons/base/tests/serializers.py | 161 +- addons/base/tests/utils.py | 45 +- addons/base/tests/views.py | 296 +- addons/base/utils.py | 86 +- addons/base/views.py | 1028 ++- addons/bitbucket/api.py | 115 +- addons/bitbucket/apps.py | 109 +- addons/bitbucket/models.py | 215 +- addons/bitbucket/routes.py | 66 +- addons/bitbucket/serializer.py | 21 +- addons/bitbucket/settings/__init__.py | 2 +- addons/bitbucket/settings/defaults.py | 12 +- addons/bitbucket/settings/local-dist.py | 4 +- addons/bitbucket/tests/factories.py | 14 +- addons/bitbucket/tests/test_models.py | 174 +- addons/bitbucket/tests/test_serializer.py | 6 +- addons/bitbucket/tests/test_views.py | 226 +- addons/bitbucket/tests/utils.py | 39 +- addons/bitbucket/utils.py | 19 +- addons/bitbucket/views.py | 100 +- addons/boa/apps.py | 30 +- addons/boa/boa_error_code.py | 25 +- addons/boa/models.py | 34 +- addons/boa/routes.py | 46 +- addons/boa/serializer.py | 40 +- addons/boa/settings/__init__.py | 2 +- addons/boa/settings/defaults.py | 42 +- addons/boa/tasks.py | 314 +- addons/boa/tests/factories.py | 27 +- addons/boa/tests/test_models.py | 29 +- addons/boa/tests/test_serializer.py | 21 +- addons/boa/tests/test_tasks.py | 406 +- addons/boa/tests/test_views.py | 341 +- addons/boa/tests/utils.py | 20 +- addons/boa/views.py | 77 +- addons/box/apps.py | 35 +- addons/box/models.py | 199 +- addons/box/routes.py | 39 +- addons/box/serializer.py | 32 +- addons/box/settings/__init__.py | 2 +- addons/box/settings/defaults.py | 6 +- addons/box/settings/local-dist.py | 1 + addons/box/tests/factories.py | 13 +- addons/box/tests/test_client.py | 9 +- addons/box/tests/test_models.py | 31 +- addons/box/tests/test_serializer.py | 9 +- addons/box/tests/test_views.py | 160 +- addons/box/tests/utils.py | 258 +- addons/box/views.py | 47 +- addons/dataverse/apps.py | 71 +- addons/dataverse/client.py | 69 +- addons/dataverse/models.py | 108 +- addons/dataverse/routes.py | 64 +- addons/dataverse/serializer.py | 73 +- addons/dataverse/settings/__init__.py | 2 +- addons/dataverse/settings/defaults.py | 4 +- addons/dataverse/settings/local-dist.py | 4 +- addons/dataverse/settings/local-travis.py | 4 +- addons/dataverse/tests/factories.py | 20 +- addons/dataverse/tests/test_client.py | 148 +- addons/dataverse/tests/test_logger.py | 9 +- addons/dataverse/tests/test_model.py | 67 +- addons/dataverse/tests/test_serializer.py | 32 +- addons/dataverse/tests/test_utils.py | 40 +- addons/dataverse/tests/test_views.py | 242 +- addons/dataverse/tests/utils.py | 122 +- addons/dataverse/utils.py | 21 +- addons/dataverse/views.py | 220 +- addons/dropbox/apps.py | 34 +- addons/dropbox/models.py | 142 +- addons/dropbox/routes.py | 70 +- addons/dropbox/serializer.py | 26 +- addons/dropbox/settings/__init__.py | 2 +- addons/dropbox/settings/defaults.py | 6 +- addons/dropbox/settings/local-dist.py | 5 +- addons/dropbox/tests/factories.py | 15 +- addons/dropbox/tests/test_client.py | 10 +- addons/dropbox/tests/test_models.py | 35 +- addons/dropbox/tests/test_serializers.py | 3 +- addons/dropbox/tests/test_views.py | 177 +- addons/dropbox/tests/utils.py | 40 +- addons/dropbox/views.py | 41 +- addons/figshare/apps.py | 61 +- addons/figshare/client.py | 135 +- addons/figshare/messages.py | 18 +- addons/figshare/models.py | 152 +- addons/figshare/routes.py | 38 +- addons/figshare/serializer.py | 31 +- addons/figshare/settings/__init__.py | 2 +- addons/figshare/settings/defaults.py | 39 +- addons/figshare/settings/local-dist.py | 4 +- addons/figshare/tests/factories.py | 13 +- addons/figshare/tests/test_models.py | 60 +- addons/figshare/tests/test_serializer.py | 10 +- addons/figshare/tests/test_views.py | 55 +- addons/figshare/tests/utils.py | 208 +- addons/figshare/views.py | 35 +- addons/forward/apps.py | 27 +- addons/forward/models.py | 21 +- addons/forward/routes.py | 23 +- addons/forward/settings/__init__.py | 2 +- addons/forward/tests/factories.py | 2 +- addons/forward/tests/test_models.py | 17 +- addons/forward/tests/test_utils.py | 14 +- addons/forward/tests/test_views.py | 25 +- addons/forward/tests/utils.py | 7 +- addons/forward/utils.py | 13 +- addons/forward/views/config.py | 18 +- addons/github/api.py | 53 +- addons/github/apps.py | 112 +- addons/github/models.py | 240 +- addons/github/routes.py | 88 +- addons/github/serializer.py | 26 +- addons/github/settings/__init__.py | 2 +- addons/github/settings/defaults.py | 10 +- addons/github/tests/factories.py | 14 +- addons/github/tests/test_models.py | 418 +- addons/github/tests/test_serializer.py | 12 +- addons/github/tests/test_utils.py | 22 +- addons/github/tests/test_views.py | 691 +- addons/github/tests/utils.py | 385 +- addons/github/utils.py | 75 +- addons/github/views.py | 211 +- addons/gitlab/api.py | 59 +- addons/gitlab/apps.py | 110 +- addons/gitlab/exceptions.py | 3 + addons/gitlab/models.py | 238 +- addons/gitlab/routes.py | 81 +- addons/gitlab/serializer.py | 32 +- addons/gitlab/settings/__init__.py | 2 +- addons/gitlab/settings/defaults.py | 6 +- addons/gitlab/tests/factories.py | 14 +- addons/gitlab/tests/test_models.py | 127 +- addons/gitlab/tests/test_serializer.py | 25 +- addons/gitlab/tests/test_utils.py | 20 +- addons/gitlab/tests/test_views.py | 802 +- addons/gitlab/tests/utils.py | 162 +- addons/gitlab/utils.py | 67 +- addons/gitlab/views.py | 235 +- addons/googledrive/apps.py | 35 +- addons/googledrive/client.py | 58 +- addons/googledrive/models.py | 154 +- addons/googledrive/routes.py | 70 +- addons/googledrive/serializer.py | 27 +- addons/googledrive/settings/__init__.py | 2 +- addons/googledrive/settings/defaults.py | 14 +- addons/googledrive/settings/local-dist.py | 4 +- addons/googledrive/tests/factories.py | 21 +- addons/googledrive/tests/test_models.py | 71 +- addons/googledrive/tests/test_serializer.py | 21 +- addons/googledrive/tests/test_views.py | 49 +- addons/googledrive/tests/utils.py | 4711 ++++++----- addons/googledrive/utils.py | 30 +- addons/googledrive/views.py | 40 +- addons/mendeley/api.py | 3 +- addons/mendeley/apps.py | 35 +- addons/mendeley/models.py | 263 +- addons/mendeley/provider.py | 9 +- addons/mendeley/routes.py | 42 +- addons/mendeley/serializer.py | 3 +- addons/mendeley/settings/__init__.py | 2 +- addons/mendeley/settings/local-dist.py | 5 +- addons/mendeley/tests/factories.py | 14 +- addons/mendeley/tests/test_api.py | 22 +- addons/mendeley/tests/test_models.py | 52 +- addons/mendeley/tests/test_serializer.py | 7 +- addons/mendeley/tests/test_views.py | 23 +- addons/mendeley/tests/utils.py | 355 +- addons/mendeley/views.py | 2 +- addons/onedrive/apps.py | 52 +- addons/onedrive/client.py | 76 +- addons/onedrive/models.py | 149 +- addons/onedrive/routes.py | 39 +- addons/onedrive/serializer.py | 21 +- addons/onedrive/settings/__init__.py | 2 +- addons/onedrive/settings/defaults.py | 20 +- addons/onedrive/settings/local-dist.py | 1 + addons/onedrive/tests/factories.py | 22 +- addons/onedrive/tests/test_client.py | 55 +- addons/onedrive/tests/test_models.py | 77 +- addons/onedrive/tests/test_serializer.py | 18 +- addons/onedrive/tests/test_views.py | 43 +- addons/onedrive/tests/utils.py | 1253 ++- addons/onedrive/views.py | 37 +- addons/osfstorage/admin.py | 4 +- addons/osfstorage/apps.py | 37 +- addons/osfstorage/decorators.py | 70 +- addons/osfstorage/listeners.py | 40 +- addons/osfstorage/models.py | 347 +- addons/osfstorage/routes.py | 80 +- addons/osfstorage/settings/__init__.py | 2 +- addons/osfstorage/settings/defaults.py | 24 +- addons/osfstorage/settings/staging.py | 2 +- addons/osfstorage/tests/factories.py | 8 +- addons/osfstorage/tests/test_models.py | 576 +- addons/osfstorage/tests/test_utils.py | 41 +- addons/osfstorage/tests/test_views.py | 1265 +-- addons/osfstorage/tests/utils.py | 36 +- addons/osfstorage/utils.py | 79 +- addons/osfstorage/views.py | 305 +- addons/owncloud/apps.py | 36 +- addons/owncloud/models.py | 141 +- addons/owncloud/routes.py | 58 +- addons/owncloud/serializer.py | 33 +- addons/owncloud/settings/__init__.py | 2 +- addons/owncloud/settings/local-dist.py | 2 +- addons/owncloud/tests/factories.py | 22 +- addons/owncloud/tests/test_models.py | 34 +- addons/owncloud/tests/test_serializer.py | 11 +- addons/owncloud/tests/test_views.py | 61 +- addons/owncloud/tests/utils.py | 22 +- addons/owncloud/views.py | 74 +- addons/s3/apps.py | 62 +- addons/s3/models.py | 132 +- addons/s3/provider.py | 9 +- addons/s3/routes.py | 50 +- addons/s3/serializer.py | 28 +- addons/s3/settings/__init__.py | 2 +- addons/s3/settings/defaults.py | 52 +- addons/s3/tests/factories.py | 23 +- addons/s3/tests/test_model.py | 63 +- addons/s3/tests/test_serializer.py | 6 +- addons/s3/tests/test_view.py | 324 +- addons/s3/tests/utils.py | 10 +- addons/s3/utils.py | 72 +- addons/s3/views.py | 98 +- addons/twofactor/apps.py | 25 +- addons/twofactor/models.py | 21 +- addons/twofactor/routes.py | 50 +- addons/twofactor/tests/test_models.py | 58 +- addons/twofactor/tests/test_utils.py | 36 +- addons/twofactor/tests/test_views.py | 16 +- addons/twofactor/utils.py | 22 +- addons/twofactor/views.py | 52 +- addons/wiki/apps.py | 28 +- addons/wiki/exceptions.py | 16 + addons/wiki/models.py | 285 +- addons/wiki/routes.py | 222 +- addons/wiki/settings/__init__.py | 2 +- addons/wiki/settings/defaults.py | 11 +- addons/wiki/tests/config.py | 332 +- addons/wiki/tests/factories.py | 6 +- addons/wiki/tests/test_models.py | 45 +- addons/wiki/tests/test_views.py | 331 +- addons/wiki/tests/test_wiki.py | 1215 +-- addons/wiki/utils.py | 158 +- addons/wiki/views.py | 522 +- addons/zotero/apps.py | 40 +- addons/zotero/models.py | 173 +- addons/zotero/provider.py | 100 +- addons/zotero/routes.py | 49 +- addons/zotero/serializer.py | 11 +- addons/zotero/settings/__init__.py | 2 +- addons/zotero/settings/local-dist.py | 5 +- addons/zotero/tests/factories.py | 16 +- addons/zotero/tests/test_models.py | 147 +- addons/zotero/tests/test_serializer.py | 5 +- addons/zotero/tests/test_views.py | 104 +- addons/zotero/tests/utils.py | 492 +- addons/zotero/views.py | 57 +- admin/banners/forms.py | 37 +- admin/banners/urls.py | 18 +- admin/banners/views.py | 82 +- admin/base/__init__.py | 2 +- admin/base/apps.py | 4 +- admin/base/db/router.py | 1 + admin/base/forms.py | 10 +- admin/base/settings/__init__.py | 8 +- admin/base/settings/defaults.py | 233 +- admin/base/templatetags/filters.py | 1 + admin/base/urls.py | 158 +- admin/base/utils.py | 112 +- admin/base/views.py | 6 +- admin/base/wsgi.py | 6 +- admin/brands/forms.py | 28 +- admin/brands/urls.py | 10 +- admin/brands/views.py | 83 +- admin/cedar/forms.py | 28 +- admin/cedar/urls.py | 11 +- admin/cedar/views.py | 22 +- admin/collection_providers/forms.py | 313 +- admin/collection_providers/urls.py | 54 +- admin/collection_providers/views.py | 502 +- admin/comments/templatetags/comment_extras.py | 14 +- admin/comments/urls.py | 22 +- admin/comments/views.py | 135 +- admin/common_auth/admin.py | 51 +- admin/common_auth/forms.py | 21 +- admin/common_auth/urls.py | 20 +- admin/common_auth/views.py | 78 +- admin/institution_asset_files/forms.py | 23 +- admin/institution_asset_files/urls.py | 20 +- admin/institution_asset_files/views.py | 98 +- admin/institutions/forms.py | 8 +- admin/institutions/urls.py | 56 +- admin/institutions/views.py | 225 +- admin/internet_archive/urls.py | 26 +- admin/internet_archive/views.py | 54 +- admin/maintenance/forms.py | 2 +- admin/maintenance/urls.py | 6 +- admin/maintenance/views.py | 38 +- admin/management/urls.py | 40 +- admin/management/views.py | 149 +- admin/meetings/forms.py | 100 +- admin/meetings/serializers.py | 78 +- admin/meetings/urls.py | 15 +- admin/meetings/views.py | 87 +- admin/metrics/__init__.py | 2 +- admin/metrics/apps.py | 4 +- admin/metrics/urls.py | 4 +- admin/metrics/views.py | 8 +- admin/nodes/templatetags/node_extras.py | 49 +- admin/nodes/urls.py | 153 +- admin/nodes/views.py | 491 +- admin/osf_groups/forms.py | 4 +- admin/osf_groups/urls.py | 12 +- admin/osf_groups/views.py | 54 +- admin/preprint_providers/forms.py | 130 +- admin/preprint_providers/urls.py | 90 +- admin/preprint_providers/views.py | 563 +- admin/preprints/forms.py | 2 +- admin/preprints/urls.py | 109 +- admin/preprints/views.py | 370 +- admin/provider_asset_files/forms.py | 23 +- admin/provider_asset_files/urls.py | 20 +- admin/provider_asset_files/views.py | 116 +- admin/providers/views.py | 121 +- admin/registration_providers/forms.py | 95 +- admin/registration_providers/urls.py | 77 +- admin/registration_providers/views.py | 434 +- admin/registration_schemas/forms.py | 4 +- admin/registration_schemas/urls.py | 22 +- admin/registration_schemas/views.py | 104 +- admin/schema_responses/urls.py | 10 +- admin/schema_responses/views.py | 24 +- admin/subjects/forms.py | 2 +- admin/subjects/urls.py | 9 +- admin/subjects/views.py | 20 +- admin/tasks.py | 45 +- admin/users/forms.py | 25 +- admin/users/urls.py | 111 +- admin/users/views.py | 387 +- admin_tests/banners/test_views.py | 57 +- admin_tests/base/test_forms.py | 14 +- admin_tests/base/test_utils.py | 135 +- admin_tests/brands/test_views.py | 38 +- .../collection_providers/test_views.py | 20 +- admin_tests/comments/test_views.py | 69 +- admin_tests/common_auth/test_forms.py | 4 +- admin_tests/common_auth/test_logs.py | 4 +- admin_tests/common_auth/test_views.py | 6 +- admin_tests/conftest.py | 3 +- admin_tests/factories.py | 4 +- admin_tests/institutions/test_views.py | 92 +- admin_tests/maintenance/test_views.py | 36 +- admin_tests/meetings/test_forms.py | 97 +- admin_tests/meetings/test_serializers.py | 18 +- admin_tests/meetings/test_views.py | 101 +- admin_tests/mixins/providers.py | 95 +- admin_tests/nodes/test_views.py | 161 +- admin_tests/osf_groups/test_views.py | 37 +- admin_tests/preprint_providers/test_views.py | 309 +- admin_tests/preprints/test_views.py | 397 +- admin_tests/regisration_schemas/test_views.py | 122 +- .../registration_providers/test_views.py | 152 +- admin_tests/subjects/test_views.py | 10 +- admin_tests/users/test_views.py | 229 +- admin_tests/utilities.py | 1 + api/actions/permissions.py | 33 +- api/actions/serializers.py | 315 +- api/actions/urls.py | 26 +- api/actions/views.py | 123 +- api/addons/forward/test_views.py | 40 +- api/addons/serializers.py | 54 +- api/addons/urls.py | 4 +- api/addons/views.py | 80 +- api/alerts/serializers.py | 10 +- api/alerts/urls.py | 12 +- api/alerts/views.py | 21 +- api/applications/serializers.py | 99 +- api/applications/urls.py | 20 +- api/applications/views.py | 73 +- api/banners/serializers.py | 27 +- api/banners/urls.py | 14 +- api/banners/views.py | 25 +- api/base/api_globals.py | 1 + api/base/authentication/backends.py | 1 - api/base/authentication/drf.py | 62 +- api/base/content_negotiation.py | 3 +- api/base/exceptions.py | 233 +- api/base/filters.py | 443 +- api/base/generic_bulk_views.py | 98 +- api/base/metrics.py | 79 +- api/base/middleware.py | 23 +- api/base/middleware_cors_signal.py | 16 +- api/base/pagination.py | 424 +- api/base/parsers.py | 280 +- api/base/permissions.py | 56 +- api/base/renderers.py | 49 +- api/base/requests.py | 19 +- api/base/schemas/utils.py | 29 +- api/base/serializers.py | 1270 +-- api/base/settings/__init__.py | 45 +- api/base/settings/defaults.py | 390 +- api/base/settings/local-dist.py | 50 +- api/base/settings/local-travis.py | 32 +- api/base/storage.py | 8 +- api/base/throttling.py | 50 +- api/base/urls.py | 333 +- api/base/utils.py | 172 +- api/base/versioning.py | 141 +- api/base/views.py | 496 +- api/base/waffle_decorators.py | 44 +- api/base/wsgi.py | 29 +- api/brands/serializers.py | 17 +- api/brands/urls.py | 10 +- api/brands/views.py | 25 +- api/caching/listeners.py | 7 +- api/caching/settings.py | 2 +- api/caching/tasks.py | 118 +- api/caching/tests/test_caching.py | 172 +- api/cedar_metadata_records/permissions.py | 10 +- api/cedar_metadata_records/serializers.py | 98 +- api/cedar_metadata_records/urls.py | 20 +- api/cedar_metadata_records/utils.py | 22 +- api/cedar_metadata_records/views.py | 48 +- api/cedar_metadata_templates/serializers.py | 14 +- api/cedar_metadata_templates/urls.py | 14 +- api/cedar_metadata_templates/views.py | 22 +- api/chronos/permissions.py | 32 +- api/chronos/serializers.py | 71 +- api/chronos/urls.py | 26 +- api/chronos/views.py | 73 +- api/citations/serializers.py | 22 +- api/citations/urls.py | 14 +- api/citations/utils.py | 245 +- api/citations/views.py | 27 +- api/collection_submission_actions/schemas.py | 77 +- .../serializers.py | 61 +- api/collection_submission_actions/urls.py | 14 +- api/collection_submission_actions/views.py | 26 +- api/collection_submissions/permissions.py | 17 +- api/collection_submissions/urls.py | 8 +- api/collection_submissions/views.py | 22 +- api/collection_subscriptions/urls.py | 4 +- api/collections/permissions.py | 148 +- api/collections/serializers.py | 534 +- api/collections/urls.py | 98 +- api/collections/views.py | 362 +- api/comments/permissions.py | 15 +- api/comments/serializers.py | 275 +- api/comments/urls.py | 20 +- api/comments/views.py | 71 +- api/crossref/permissions.py | 19 +- api/crossref/urls.py | 8 +- api/crossref/views.py | 79 +- api/custom_metadata/file_urls.py | 8 +- api/custom_metadata/item_urls.py | 8 +- api/custom_metadata/serializers.py | 83 +- api/custom_metadata/views.py | 25 +- api/draft_nodes/serializers.py | 41 +- api/draft_nodes/urls.py | 38 +- api/draft_nodes/views.py | 49 +- api/draft_registrations/permissions.py | 16 +- api/draft_registrations/serializers.py | 204 +- api/draft_registrations/urls.py | 56 +- api/draft_registrations/views.py | 92 +- api/files/annotations.py | 70 +- api/files/permissions.py | 32 +- api/files/serializers.py | 485 +- api/files/urls.py | 26 +- api/files/views.py | 126 +- api/guids/serializers.py | 98 +- api/guids/urls.py | 8 +- api/guids/views.py | 23 +- api/ia/urls.py | 8 +- api/ia/views.py | 15 +- api/identifiers/serializers.py | 78 +- api/identifiers/urls.py | 14 +- api/identifiers/views.py | 32 +- api/institutions/authentication.py | 251 +- api/institutions/permissions.py | 8 +- api/institutions/renderers.py | 35 +- api/institutions/serializers.py | 284 +- api/institutions/urls.py | 68 +- api/institutions/views.py | 265 +- api/licenses/serializers.py | 41 +- api/licenses/urls.py | 12 +- api/licenses/views.py | 24 +- api/logs/permissions.py | 7 +- api/logs/serializers.py | 218 +- api/logs/urls.py | 8 +- api/logs/views.py | 12 +- api/meetings/serializers.py | 145 +- api/meetings/urls.py | 24 +- api/meetings/views.py | 161 +- api/metaschemas/urls.py | 26 +- api/metaschemas/views.py | 53 +- api/metrics/permissions.py | 14 +- api/metrics/renderers.py | 33 +- api/metrics/serializers.py | 154 +- api/metrics/urls.py | 41 +- api/metrics/utils.py | 118 +- api/metrics/views.py | 380 +- api/nodes/filters.py | 114 +- api/nodes/permissions.py | 111 +- api/nodes/serializers.py | 1782 +++-- api/nodes/urls.py | 274 +- api/nodes/utils.py | 173 +- api/nodes/views.py | 1181 ++- api/osf_groups/permissions.py | 8 +- api/osf_groups/serializers.py | 153 +- api/osf_groups/urls.py | 22 +- api/osf_groups/views.py | 89 +- api/preprint_providers/serializers.py | 32 +- api/preprint_providers/urls.py | 57 +- api/preprint_providers/views.py | 71 +- api/preprints/permissions.py | 56 +- api/preprints/serializers.py | 466 +- api/preprints/urls.py | 90 +- api/preprints/views.py | 305 +- api/providers/permissions.py | 20 +- api/providers/serializers.py | 451 +- api/providers/tasks.py | 681 +- api/providers/urls.py | 257 +- api/providers/views.py | 607 +- api/providers/workflows.py | 11 +- api/regions/serializers.py | 12 +- api/regions/urls.py | 12 +- api/regions/views.py | 29 +- api/registration_subscriptions/urls.py | 4 +- api/registrations/annotations.py | 6 +- api/registrations/permissions.py | 7 +- api/registrations/serializers.py | 739 +- api/registrations/urls.py | 230 +- api/registrations/views.py | 577 +- api/requests/permissions.py | 74 +- api/requests/serializers.py | 180 +- api/requests/urls.py | 12 +- api/requests/views.py | 111 +- api/resources/annotations.py | 26 +- api/resources/permissions.py | 40 +- api/resources/serializers.py | 125 +- api/resources/urls.py | 9 +- api/resources/views.py | 33 +- api/schema_responses/annotations.py | 22 +- api/schema_responses/permissions.py | 80 +- api/schema_responses/schemas.py | 69 +- api/schema_responses/serializers.py | 130 +- api/schema_responses/urls.py | 14 +- api/schema_responses/views.py | 86 +- api/schemas/serializers.py | 54 +- api/schemas/urls.py | 38 +- api/schemas/views.py | 67 +- api/scopes/permissions.py | 6 +- api/scopes/serializers.py | 12 +- api/scopes/urls.py | 10 +- api/scopes/views.py | 20 +- api/search/permissions.py | 6 +- api/search/serializers.py | 25 +- api/search/urls.py | 47 +- api/search/views.py | 99 +- api/share/utils.py | 395 +- api/sparse/serializers.py | 231 +- api/sparse/urls.py | 28 +- api/sparse/views.py | 19 +- api/subjects/serializers.py | 80 +- api/subjects/urls.py | 18 +- api/subjects/views.py | 91 +- api/subscriptions/permissions.py | 13 +- api/subscriptions/serializers.py | 73 +- api/subscriptions/urls.py | 14 +- api/subscriptions/views.py | 56 +- api/taxonomies/serializers.py | 96 +- api/taxonomies/urls.py | 12 +- api/taxonomies/utils.py | 7 +- api/taxonomies/views.py | 48 +- api/test/urls.py | 4 +- api/test/views.py | 4 +- api/tokens/serializers.py | 112 +- api/tokens/urls.py | 14 +- api/tokens/views.py | 78 +- api/users/permissions.py | 29 +- api/users/serializers.py | 584 +- api/users/urls.py | 130 +- api/users/views.py | 599 +- api/view_only_links/serializers.py | 91 +- api/view_only_links/urls.py | 20 +- api/view_only_links/views.py | 59 +- api/waffle/serializers.py | 30 +- api/waffle/urls.py | 4 +- api/waffle/utils.py | 2 +- api/waffle/views.py | 35 +- api/wb/serializers.py | 50 +- api/wb/urls.py | 14 +- api/wb/views.py | 33 +- api/wikis/permissions.py | 21 +- api/wikis/serializers.py | 190 +- api/wikis/urls.py | 32 +- api/wikis/views.py | 49 +- api_tests/actions/views/test_action_detail.py | 47 +- api_tests/actions/views/test_action_list.py | 204 +- .../test_schema_response_action_detail.py | 218 +- .../views/test_schema_response_action_list.py | 550 +- api_tests/addons_tests/test_addons_list.py | 15 +- api_tests/alerts/views/test_alerts_detail.py | 20 +- api_tests/alerts/views/test_alerts_list.py | 83 +- .../views/test_application_detail.py | 193 +- .../views/test_application_list.py | 111 +- .../views/test_application_reset.py | 120 +- .../banners/views/test_current_banner.py | 20 +- api_tests/base/test_auth.py | 397 +- api_tests/base/test_filters.py | 429 +- api_tests/base/test_middleware.py | 85 +- api_tests/base/test_pagination.py | 54 +- api_tests/base/test_parsers.py | 139 +- api_tests/base/test_root.py | 94 +- api_tests/base/test_serializers.py | 743 +- api_tests/base/test_throttling.py | 88 +- api_tests/base/test_utils.py | 63 +- api_tests/base/test_versioning.py | 110 +- api_tests/base/test_views.py | 108 +- api_tests/brands/views/test_brands.py | 45 +- .../serializers/test_serializers.py | 667 +- .../views/test_record.py | 241 +- .../views/test_record_create_post.py | 594 +- .../views/test_record_detail_delete.py | 166 +- .../views/test_record_detail_get.py | 2203 ++++-- .../views/test_record_detail_patch.py | 274 +- .../test_record_metadata_download_get.py | 806 +- .../serializers/test_serializers.py | 45 +- .../views/test_template.py | 1 - .../views/test_template_detail.py | 64 +- .../views/test_template_list.py | 8 +- .../views/test_chronos_journal_detail.py | 11 +- .../views/test_chronos_journal_list.py | 37 +- .../views/test_chronos_submission_detail.py | 83 +- .../views/test_chronos_submission_list.py | 298 +- ...t_collection_submissions_actions_detail.py | 150 +- ...est_collection_submissions_actions_list.py | 381 +- ...test_collection_submission_list_actions.py | 138 +- api_tests/collections/test_serializers.py | 56 +- api_tests/collections/test_views.py | 4795 ++++++----- .../comments/views/test_comment_detail.py | 753 +- .../views/test_comment_report_detail.py | 310 +- .../views/test_comment_report_list.py | 319 +- api_tests/conftest.py | 3 +- .../views/test_crossref_email_response.py | 159 +- .../views/test_draft_node_detail.py | 26 +- ...est_draft_node_draft_registrations_list.py | 43 +- .../views/test_draft_node_files_lists.py | 716 +- ...t_draft_registration_contributor_detail.py | 191 +- ...est_draft_registration_contributor_list.py | 448 +- .../views/test_draft_registration_detail.py | 752 +- ...st_draft_registration_institutions_list.py | 34 +- .../views/test_draft_registration_list.py | 450 +- ..._registration_relationship_institutions.py | 359 +- ...raft_registration_relationship_subjects.py | 34 +- .../test_draft_registration_subjects_list.py | 7 +- .../files/serializers/test_file_serializer.py | 205 +- .../test_file_cedar_metadata_record_list.py | 177 +- .../views/test_file_cedar_metdata_record.py | 29 +- api_tests/files/views/test_file_detail.py | 736 +- api_tests/files/views/test_file_list.py | 263 +- api_tests/guids/views/test_guid_detail.py | 130 +- .../managment_commands/test_sync_dois.py | 96 +- .../views/test_identifier_detail.py | 112 +- .../identifiers/views/test_identifier_list.py | 408 +- .../views/test_institution_auth.py | 1592 ++-- .../views/test_institution_department_list.py | 161 +- .../views/test_institution_detail.py | 50 +- .../views/test_institution_list.py | 19 +- .../views/test_institution_nodes_list.py | 112 +- .../test_institution_registrations_list.py | 71 +- .../test_institution_relationship_nodes.py | 368 +- .../views/test_institution_summary_metrics.py | 35 +- .../test_institution_user_metric_list.py | 249 +- .../views/test_institution_users_list.py | 7 +- .../licenses/views/test_license_detail.py | 15 +- api_tests/licenses/views/test_license_list.py | 26 +- .../logs/serializers/test_serializers.py | 30 +- api_tests/logs/views/test_log_detail.py | 160 +- api_tests/logs/views/test_log_embeds.py | 49 +- api_tests/logs/views/test_log_params.py | 68 +- .../meetings/views/test_meetings_detail.py | 94 +- .../meetings/views/test_meetings_list.py | 161 +- .../views/test_meetings_submissions_detail.py | 104 +- .../views/test_meetings_submissions_list.py | 322 +- api_tests/metadata_records/__init__.py | 2 +- .../test_custom_file_metadata.py | 116 +- .../test_custom_item_metadata.py | 486 +- api_tests/metadata_records/utils.py | 18 +- api_tests/metrics/test_composite_query.py | 58 +- api_tests/metrics/test_counted_usage.py | 242 +- api_tests/metrics/test_parse_datetimes.py | 73 +- api_tests/metrics/test_preprint_metrics.py | 211 +- api_tests/metrics/test_queries.py | 123 +- api_tests/metrics/test_raw_metrics.py | 129 +- .../test_registries_moderation_metrics.py | 47 +- api_tests/metrics/test_reports.py | 117 +- api_tests/nodes/filters/test_filters.py | 311 +- .../nodes/serializers/test_serializers.py | 320 +- api_tests/nodes/views/test_node_addons.py | 1386 ++-- ...st_node_bibliographic_contributors_list.py | 63 +- .../views/test_node_cedar_metadata_record.py | 29 +- .../test_node_cedar_metadata_record_list.py | 128 +- .../nodes/views/test_node_children_list.py | 814 +- api_tests/nodes/views/test_node_citations.py | 126 +- .../nodes/views/test_node_comments_list.py | 1659 ++-- ...ode_contributors_and_group_members_list.py | 54 +- .../views/test_node_contributors_detail.py | 1156 +-- .../views/test_node_contributors_list.py | 1857 +++-- api_tests/nodes/views/test_node_detail.py | 3204 +++++--- .../test_node_draft_registration_detail.py | 853 +- .../test_node_draft_registration_list.py | 968 ++- api_tests/nodes/views/test_node_embeds.py | 144 +- api_tests/nodes/views/test_node_exceptions.py | 203 +- api_tests/nodes/views/test_node_files_list.py | 830 +- api_tests/nodes/views/test_node_forks_list.py | 506 +- api_tests/nodes/views/test_node_groups.py | 519 +- .../test_node_implicit_contributors_list.py | 53 +- .../views/test_node_institutions_list.py | 42 +- .../nodes/views/test_node_linked_by_list.py | 128 +- .../nodes/views/test_node_linked_nodes.py | 546 +- .../views/test_node_linked_registrations.py | 628 +- .../nodes/views/test_node_links_detail.py | 205 +- api_tests/nodes/views/test_node_links_list.py | 1430 ++-- api_tests/nodes/views/test_node_list.py | 4238 +++++----- api_tests/nodes/views/test_node_logs.py | 394 +- api_tests/nodes/views/test_node_preprints.py | 177 +- .../views/test_node_registrations_list.py | 91 +- .../test_node_relationship_institutions.py | 370 +- .../views/test_node_relationship_subjects.py | 10 +- api_tests/nodes/views/test_node_settings.py | 414 +- .../nodes/views/test_node_sparse_fieldsets.py | 342 +- api_tests/nodes/views/test_node_storage.py | 79 +- .../nodes/views/test_node_subjects_list.py | 7 +- .../views/test_node_view_only_links_detail.py | 153 +- .../views/test_node_view_only_links_list.py | 180 +- api_tests/nodes/views/test_node_wiki_list.py | 544 +- .../views/test_view_only_query_parameter.py | 535 +- .../osf_groups/views/test_osf_group_detail.py | 167 +- .../views/test_osf_group_members_detail.py | 235 +- .../views/test_osf_group_members_list.py | 811 +- .../osf_groups/views/test_osf_groups_list.py | 112 +- api_tests/preprints/filters/test_filters.py | 296 +- .../preprints/views/test_preprint_actions.py | 17 +- ...reprint_bibliographic_contributors_list.py | 63 +- .../views/test_preprint_citations.py | 662 +- .../test_preprint_contributors_detail.py | 1165 +-- .../views/test_preprint_contributors_list.py | 3592 +++++---- .../preprints/views/test_preprint_detail.py | 1841 +++-- .../views/test_preprint_files_list.py | 178 +- .../preprints/views/test_preprint_list.py | 935 ++- .../views/test_preprint_list_mixin.py | 230 +- .../views/test_preprint_node_relationship.py | 71 +- .../views/test_preprint_subjects_list.py | 29 +- .../views/test_collection_provider_detail.py | 14 +- .../test_collection_provider_licenses.py | 5 +- .../views/test_collection_provider_list.py | 2 +- .../test_collection_provider_subjects_list.py | 16 +- ...st_collection_provider_submissions_list.py | 3 +- ...est_collections_provider_moderator_list.py | 185 +- api_tests/providers/mixins.py | 946 ++- .../preprints/serializers/test_serializers.py | 172 +- .../views/test_preprint_provider_detail.py | 135 +- .../views/test_preprint_provider_licenses.py | 58 +- .../views/test_preprint_provider_list.py | 85 +- ...test_preprint_provider_moderator_detail.py | 223 +- .../test_preprint_provider_moderator_list.py | 191 +- .../test_preprint_provider_preprints_list.py | 209 +- .../test_preprint_provider_request_list.py | 88 +- .../test_preprint_provider_subjects_list.py | 374 +- .../views/test_preprint_providers_list.py | 10 +- .../test_registration_provider_bulk_upload.py | 194 +- .../test_registration_provider_detail.py | 34 +- .../test_registration_provider_licenses.py | 5 +- .../views/test_registration_provider_list.py | 12 +- ..._registration_provider_moderator_detail.py | 13 +- ...st_registration_provider_moderator_list.py | 9 +- .../test_registration_provider_schemas.py | 133 +- ...est_registration_provider_subjects_list.py | 28 +- api_tests/providers/tasks/test_bulk_upload.py | 552 +- api_tests/providers/test_reindex_provider.py | 19 +- api_tests/regions/views/test_region_detail.py | 18 +- api_tests/regions/views/test_region_list.py | 22 +- .../registrations/filters/test_filters.py | 94 +- ...tration_bibliographic_contributors_list.py | 24 +- ...test_registration_cedar_metadata_record.py | 21 +- ...registration_cedar_metadata_record_list.py | 84 +- .../views/test_registration_detail.py | 1630 ++-- .../views/test_registration_embeds.py | 62 +- .../views/test_registration_files_list.py | 26 +- .../views/test_registration_forks.py | 508 +- .../test_registration_institutions_list.py | 7 +- .../views/test_registration_linked_nodes.py | 336 +- .../test_registration_linked_registrations.py | 244 +- .../views/test_registration_list.py | 2157 +++-- ..._registration_relationship_institutions.py | 81 +- ...test_registration_relationship_subjects.py | 16 +- .../views/test_registration_resource_list.py | 172 +- .../views/test_registration_subjects_list.py | 11 +- ...est_registration_view_only_links_detail.py | 18 +- .../test_registration_view_only_links_list.py | 15 +- .../test_registrations_childrens_list.py | 139 +- .../views/test_view_only_query_parameter.py | 123 +- .../views/test_withdrawn_registrations.py | 275 +- .../registries_moderation/test_submissions.py | 894 ++- api_tests/requests/mixins.py | 68 +- .../views/test_request_action_list.py | 80 +- .../views/test_request_actions_create.py | 370 +- .../requests/views/test_request_detail.py | 49 +- .../views/test_request_list_create.py | 299 +- .../test_open_practice_badge_annotations.py | 297 +- api_tests/resources/utils.py | 34 +- .../resources/views/test_resource_detail.py | 574 +- .../resources/views/test_resource_list.py | 171 +- api_tests/reviews/mixins/comment_settings.py | 34 +- api_tests/reviews/mixins/filter_mixins.py | 122 +- ...est_registrations_schema_responses_list.py | 207 +- .../views/test_schema_response_detail.py | 611 +- .../views/test_schema_response_list.py | 289 +- .../views/test_file_metadata_schema_detail.py | 13 +- .../views/test_file_metadata_schema_list.py | 9 +- .../views/test_registration_schemas_detail.py | 55 +- .../views/test_registration_schemas_list.py | 36 +- api_tests/scopes/views/test_scope_detail.py | 19 +- api_tests/scopes/views/test_scope_list.py | 13 +- .../search/serializers/test_serializers.py | 23 +- api_tests/search/views/test_views.py | 867 +- api_tests/share/_utils.py | 60 +- api_tests/share/test_share_node.py | 270 +- api_tests/share/test_share_preprint.py | 104 +- .../sparse/test_node_sparse_fieldsets.py | 345 +- api_tests/sparse/test_sparse_node_detail.py | 68 +- api_tests/sparse/test_sparse_node_list.py | 191 +- .../sparse/test_sparse_registration_list.py | 37 +- api_tests/subjects/mixins.py | 602 +- .../subjects/views/test_subject_detail.py | 60 +- .../views/test_subscriptions_detail.py | 73 +- .../views/test_subscriptions_list.py | 32 +- .../taxonomies/views/test_taxonomy_list.py | 85 +- api_tests/test/views/test_throttling.py | 18 +- api_tests/tokens/views/test_token_detail.py | 548 +- api_tests/tokens/views/test_token_list.py | 342 +- api_tests/tokens/views/test_token_scopes.py | 26 +- .../users/serializers/test_serializers.py | 173 +- api_tests/users/views/test_user_actions.py | 195 +- api_tests/users/views/test_user_addons.py | 239 +- api_tests/users/views/test_user_can_review.py | 10 +- api_tests/users/views/test_user_claim.py | 178 +- api_tests/users/views/test_user_detail.py | 1800 +++-- .../test_user_draft_registration_list.py | 153 +- api_tests/users/views/test_user_exceptions.py | 90 +- .../views/test_user_external_identities.py | 74 +- .../users/views/test_user_institutions.py | 6 +- .../test_user_institutions_relationship.py | 167 +- api_tests/users/views/test_user_list.py | 453 +- api_tests/users/views/test_user_nodes_list.py | 299 +- .../users/views/test_user_osf_groups_list.py | 92 +- .../users/views/test_user_preprints_list.py | 213 +- .../views/test_user_registrations_list.py | 142 +- api_tests/users/views/test_user_settings.py | 563 +- .../users/views/test_user_settings_detail.py | 263 +- api_tests/utils.py | 67 +- .../views/test_view_only_link_detail.py | 28 +- .../views/test_view_only_link_nodes.py | 560 +- api_tests/waffle/views/test_waffle_cookies.py | 23 +- api_tests/waffle/views/test_waffle_list.py | 77 +- api_tests/wb/views/test_wb_hooks.py | 1150 +-- api_tests/wikis/views/test_wiki_content.py | 48 +- api_tests/wikis/views/test_wiki_detail.py | 542 +- .../wikis/views/test_wiki_version_content.py | 59 +- .../wikis/views/test_wiki_version_detail.py | 300 +- .../wikis/views/test_wiki_versions_list.py | 351 +- conftest.py | 281 +- framework/addons/utils.py | 71 +- framework/analytics/__init__.py | 21 +- framework/auth/__init__.py | 95 +- framework/auth/campaigns.py | 172 +- framework/auth/cas.py | 246 +- framework/auth/core.py | 102 +- framework/auth/decorators.py | 70 +- framework/auth/exceptions.py | 18 +- framework/auth/forms.py | 117 +- framework/auth/oauth_scopes.py | 680 +- framework/auth/signals.py | 14 +- framework/auth/signing.py | 23 +- framework/auth/tasks.py | 144 +- framework/auth/utils.py | 87 +- framework/auth/views.py | 756 +- framework/bcrypt/__init__.py | 14 +- framework/celery_tasks/__init__.py | 15 +- framework/celery_tasks/handlers.py | 17 +- framework/celery_tasks/routers.py | 10 +- framework/celery_tasks/utils.py | 43 +- framework/csrf/handlers.py | 14 +- framework/database/__init__.py | 57 +- framework/django/handlers.py | 8 +- framework/email/tasks.py | 53 +- framework/encryption/__init__.py | 7 +- framework/exceptions/__init__.py | 89 +- framework/flask/__init__.py | 14 +- framework/forms/__init__.py | 29 +- framework/forms/utils.py | 30 +- framework/logging/__init__.py | 6 +- framework/postcommit_tasks/handlers.py | 57 +- framework/routing/__init__.py | 201 +- framework/sentry/__init__.py | 41 +- framework/sessions/__init__.py | 115 +- framework/sessions/utils.py | 5 +- framework/status/__init__.py | 77 +- framework/transactions/handlers.py | 13 +- framework/utils.py | 15 +- main.py | 17 +- manage.py | 20 +- osf/admin.py | 110 +- osf/apps.py | 25 +- osf/exceptions.py | 108 +- osf/external/askismet/client.py | 86 +- osf/external/askismet/exceptions.py | 1 - osf/external/askismet/tasks.py | 26 +- osf/external/cedar/client.py | 28 +- osf/external/cedar/exceptions.py | 3 +- osf/external/chronos/chronos.py | 248 +- osf/external/chronos/tasks.py | 7 +- osf/external/gravy_valet/auth_helpers.py | 72 +- osf/external/gravy_valet/request_helpers.py | 156 +- osf/external/gravy_valet/translations.py | 31 +- osf/external/internet_archive/tasks.py | 55 +- osf/external/messages/celery_publishers.py | 28 +- osf/external/oopspam/client.py | 27 +- osf/external/oopspam/exceptions.py | 1 - osf/external/spam/tasks.py | 110 +- osf/features.py | 8 +- .../commands/add_colon_delim_to_s3_buckets.py | 56 +- .../commands/add_egap_registration_schema.py | 24 +- .../commands/add_institution_perm_groups.py | 2 +- .../commands/add_notification_subscription.py | 61 +- osf/management/commands/addon_deleted_date.py | 97 +- .../approve_pending_schema_responses.py | 25 +- .../commands/archive_registrations_on_IA.py | 51 +- .../commands/backfill_date_retracted.py | 75 +- .../commands/backfill_domain_references.py | 98 +- .../commands/check_crossref_dois.py | 63 +- osf/management/commands/check_deploy_ready.py | 5 +- osf/management/commands/check_ia_metadata.py | 92 +- osf/management/commands/check_spam.py | 46 +- osf/management/commands/checkmigrations.py | 17 +- .../commands/clear_expired_sessions.py | 31 +- ...ect_user_nodes_exceeding_storage_limits.py | 61 +- osf/management/commands/confirm_spam.py | 48 +- .../correct_registration_moderation_states.py | 31 +- .../commands/count_preregistrations.py | 23 +- .../commands/create_fake_preprint_actions.py | 16 +- .../commands/cumulative_plos_metrics.py | 133 +- osf/management/commands/daily_reporters_go.py | 37 +- osf/management/commands/data_storage_usage.py | 447 +- .../commands/deactivate_requested_accounts.py | 35 +- .../delete_legacy_quickfiles_nodes.py | 28 +- ..._withdrawn_or_failed_registration_files.py | 75 +- osf/management/commands/email_all_users.py | 70 +- .../commands/export_user_account.py | 191 +- .../commands/fake_metrics_reports.py | 6 +- .../fetch_cedar_metadata_templates.py | 43 +- osf/management/commands/find_spammy_files.py | 79 +- .../fix_quickfiles_waterbutler_logs.py | 143 +- .../commands/fix_registration_file_domains.py | 56 +- osf/management/commands/force_archive.py | 702 +- osf/management/commands/gdpr_delete_user.py | 26 +- .../commands/handle_duplicate_files.py | 157 +- .../make_dummy_pageviews_for_metrics.py | 94 +- .../commands/make_taxonomy_custom.py | 31 +- .../commands/manage_switch_flags.py | 34 +- .../commands/metrics_backfill_pageviews.py | 144 +- .../commands/metrics_backfill_summaries.py | 414 +- .../commands/metrics_backfill_user_domains.py | 79 +- .../commands/migrate_deleted_date.py | 82 +- .../commands/migrate_pagecounter_data.py | 65 +- .../migrate_registration_responses.py | 106 +- .../migrate_user_institution_affiliation.py | 64 +- .../commands/monthly_reporters_go.py | 18 +- .../commands/move_egap_regs_to_provider.py | 30 +- osf/management/commands/osf_shell.py | 98 +- .../commands/populate_branched_from_node.py | 30 +- ...ion_provider_notification_subscriptions.py | 24 +- .../commands/populate_custom_taxonomies.py | 428 +- .../commands/populate_fake_providers.py | 444 +- .../populate_impact_institution_metrics.py | 61 +- .../populate_impact_preprint_metrics.py | 60 +- .../populate_initial_schema_responses.py | 59 +- .../populate_internet_archives_collections.py | 58 +- ...ion_provider_notification_subscriptions.py | 30 +- ..._to_draft_registration_contributor_sync.py | 30 +- .../commands/publish_account_changes.py | 30 +- osf/management/commands/purge_test_node.py | 46 +- .../commands/rebrand_preprint_provider.py | 133 +- osf/management/commands/recatalog_metadata.py | 157 +- .../commands/registration_schema_metrics.py | 64 +- osf/management/commands/reindex_es6.py | 70 +- osf/management/commands/reindex_provider.py | 30 +- osf/management/commands/reindex_quickfiles.py | 7 +- .../commands/restore_deleted_root_folders.py | 33 +- .../send_storage_exceeded_announcement.py | 35 +- .../set_institution_storage_regions.py | 96 +- .../strip_trailing_subject_whitespace.py | 17 +- .../commands/sync_citation_styles.py | 169 +- .../sync_collection_provider_indices.py | 25 +- osf/management/commands/sync_databases.py | 6 +- .../commands/sync_datacite_doi_metadata.py | 51 +- osf/management/commands/sync_doi_metadata.py | 87 +- osf/management/commands/sync_ia_metadata.py | 47 +- ...gistration_creator_bibliographic_status.py | 15 +- .../transfer_quickfiles_to_projects.py | 117 +- osf/management/commands/update_auth_groups.py | 17 +- .../commands/update_bepress_version.py | 130 +- .../update_institution_project_counts.py | 36 +- .../update_institution_sso_email_domain.py | 120 +- .../commands/update_mailchimp_email.py | 10 +- .../commands/update_old_sanction_states.py | 32 +- .../commands/update_preprint_share_dates.py | 21 +- .../commands/update_registration_schemas.py | 18 +- .../commands/update_storage_usage.py | 38 +- osf/management/commands/vacuum.py | 45 +- .../withdraw_all_preprints_from_provider.py | 57 +- osf/management/utils.py | 11 +- osf/metadata/definitions/__init__.py | 10 +- osf/metadata/gather/__init__.py | 2 +- osf/metadata/gather/basket.py | 39 +- osf/metadata/gather/focus.py | 14 +- osf/metadata/gather/gatherer.py | 29 +- osf/metadata/osf_gathering.py | 510 +- osf/metadata/rdfutils.py | 123 +- osf/metadata/serializers/__init__.py | 24 +- osf/metadata/serializers/datacite/__init__.py | 2 +- .../serializers/datacite/datacite_json.py | 28 +- .../datacite/datacite_tree_walker.py | 493 +- .../serializers/datacite/datacite_xml.py | 36 +- .../serializers/google_dataset_json_ld.py | 81 +- osf/metadata/serializers/turtle.py | 6 +- osf/metadata/tools.py | 16 +- osf/metrics/__init__.py | 14 +- osf/metrics/counted_usage.py | 79 +- osf/metrics/institution_metrics.py | 136 +- osf/metrics/metric_mixin.py | 84 +- osf/metrics/openapi.py | 431 +- osf/metrics/preprint_metrics.py | 30 +- osf/metrics/registry_metrics.py | 208 +- osf/metrics/reporters/__init__.py | 4 +- osf/metrics/reporters/_base.py | 27 +- osf/metrics/reporters/download_count.py | 6 +- osf/metrics/reporters/institution_summary.py | 106 +- osf/metrics/reporters/new_user_domain.py | 9 +- osf/metrics/reporters/node_count.py | 129 +- .../reporters/osfstorage_file_count.py | 35 +- osf/metrics/reporters/preprint_count.py | 50 +- osf/metrics/reporters/spam_count.py | 15 +- osf/metrics/reporters/storage_addon_usage.py | 171 +- osf/metrics/reporters/user_count.py | 45 +- osf/metrics/reports.py | 45 +- osf/metrics/utils.py | 16 +- osf/models/action.py | 89 +- osf/models/admin_log_entry.py | 11 +- osf/models/admin_profile.py | 31 +- osf/models/analytics.py | 130 +- osf/models/archive.py | 113 +- osf/models/banner.py | 37 +- osf/models/base.py | 258 +- osf/models/brand.py | 4 +- osf/models/cedar_metadata.py | 17 +- osf/models/chronos.py | 29 +- osf/models/citation.py | 24 +- osf/models/collection.py | 316 +- osf/models/collection_submission.py | 276 +- osf/models/comment.py | 179 +- osf/models/conference.py | 43 +- osf/models/contributor.py | 59 +- osf/models/dismissed_alerts.py | 20 +- osf/models/draft_node.py | 28 +- osf/models/external.py | 170 +- osf/models/files.py | 582 +- osf/models/identifiers.py | 46 +- osf/models/institution.py | 157 +- osf/models/institution_affiliation.py | 44 +- osf/models/institution_storage_region.py | 24 +- osf/models/licenses.py | 40 +- osf/models/maintenance_state.py | 8 +- osf/models/metadata.py | 90 +- osf/models/metaschema.py | 191 +- osf/models/mixins.py | 1261 ++- osf/models/node.py | 1509 ++-- osf/models/node_relation.py | 23 +- osf/models/nodelog.py | 397 +- osf/models/notable_domain.py | 25 +- osf/models/notifications.py | 83 +- osf/models/oauth.py | 76 +- osf/models/osf_group.py | 221 +- osf/models/osf_grouplog.py | 65 +- osf/models/outcome_artifacts.py | 133 +- osf/models/outcomes.py | 53 +- osf/models/preprint.py | 962 ++- osf/models/preprintlog.py | 121 +- osf/models/private_link.py | 63 +- osf/models/provider.py | 413 +- osf/models/queued_mail.py | 96 +- osf/models/quickfiles.py | 53 +- osf/models/registration_bulk_upload_job.py | 40 +- osf/models/registration_bulk_upload_row.py | 48 +- osf/models/registrations.py | 893 ++- osf/models/request.py | 24 +- osf/models/sanctions.py | 737 +- osf/models/schema_response.py | 277 +- osf/models/schema_response_block.py | 55 +- osf/models/session.py | 9 +- osf/models/spam.py | 73 +- osf/models/storage.py | 41 +- osf/models/subject.py | 65 +- osf/models/tag.py | 12 +- osf/models/user.py | 1021 ++- osf/models/validators.py | 266 +- osf/registrations/utils.py | 517 +- osf/utils/caching.py | 2 +- osf/utils/datetime_aware_jsonfield.py | 66 +- osf/utils/fields.py | 32 +- osf/utils/functional.py | 5 +- osf/utils/identifiers.py | 34 +- osf/utils/machines.py | 249 +- osf/utils/migrations.py | 462 +- osf/utils/names.py | 20 +- osf/utils/notifications.py | 114 +- osf/utils/order_apps.py | 49 +- osf/utils/outcomes.py | 20 +- osf/utils/permissions.py | 88 +- osf/utils/registrations.py | 179 +- osf/utils/requests.py | 41 +- osf/utils/sanitize.py | 38 +- osf/utils/storage.py | 12 +- osf/utils/tokens/__init__.py | 62 +- osf/utils/tokens/handlers.py | 115 +- osf/utils/workflows.py | 582 +- osf_tests/conftest.py | 19 +- osf_tests/default_test_schema.py | 204 +- osf_tests/embargoes/test_embargoes.py | 22 +- osf_tests/external/akismet/test_akismet.py | 159 +- osf_tests/external/gravy_valet/gv_fakes.py | 293 +- osf_tests/external/oopspam/test_oopspam.py | 103 +- osf_tests/factories.py | 704 +- .../test_approve_pending_schema_responses.py | 39 +- .../test_backfill_domain_references.py | 233 +- .../test_check_crossref_dois.py | 41 +- ..._correct_registration_moderation_states.py | 53 +- ..._withdrawn_or_failed_registration_files.py | 65 +- .../test_email_all_users.py | 29 +- .../test_fix_quickfiles_waterbutler_logs.py | 111 +- .../test_make_taxonomy_custom.py | 8 +- .../test_manage_switch_flags.py | 20 +- .../test_migrate_deleted_date.py | 32 +- .../test_migration_registration_responses.py | 3489 ++++---- .../test_move_egap_regs_to_provider.py | 22 +- .../test_populate_initial_schema_responses.py | 57 +- .../test_recatalog_metadata.py | 176 +- .../management_commands/test_reindex_es6.py | 71 +- ...gistration_creator_bibliographic_status.py | 42 +- .../test_transfer_quickfiles_to_projects.py | 13 +- .../test_update_old_sanction_states.py | 40 +- .../test_update_registration_schemas.py | 60 +- ...st_withdraw_all_preprints_from_provider.py | 32 +- osf_tests/metadata/_utils.py | 36 +- osf_tests/metadata/test_basket.py | 34 +- osf_tests/metadata/test_gatherer_registry.py | 32 +- osf_tests/metadata/test_google_datasets.py | 40 +- .../metadata/test_guid_metadata_records.py | 129 +- osf_tests/metadata/test_osf_gathering.py | 1607 +++- osf_tests/metadata/test_rdfutils.py | 129 +- .../metadata/test_serialized_metadata.py | 257 +- osf_tests/metrics/test_daily_report.py | 30 +- osf_tests/metrics/test_metric_mixin.py | 12 +- osf_tests/metrics/test_utils.py | 40 +- osf_tests/settings.py | 8 +- osf_tests/test_analytics.py | 145 +- osf_tests/test_archive_target.py | 7 +- osf_tests/test_archiver.py | 1123 +-- osf_tests/test_auth_utils.py | 8 +- osf_tests/test_banner.py | 27 +- osf_tests/test_collection.py | 152 +- osf_tests/test_collection_submission.py | 858 +- osf_tests/test_comment.py | 1375 ++-- osf_tests/test_draft_node.py | 129 +- osf_tests/test_draft_registration.py | 444 +- osf_tests/test_elastic_search.py | 1138 +-- osf_tests/test_exceptions.py | 11 +- osf_tests/test_external_accounts.py | 83 +- osf_tests/test_fields.py | 48 +- osf_tests/test_files.py | 84 +- osf_tests/test_framework_utils.py | 16 +- osf_tests/test_generate_sitemap.py | 213 +- osf_tests/test_guid.py | 416 +- osf_tests/test_gv_utils.py | 470 +- osf_tests/test_handle_duplicate_files.py | 167 +- osf_tests/test_handlers.py | 34 +- osf_tests/test_identifiers.py | 40 +- osf_tests/test_institution.py | 92 +- osf_tests/test_maintenance.py | 19 +- osf_tests/test_management_commands.py | 366 +- osf_tests/test_migration_sql.py | 25 +- osf_tests/test_node.py | 2437 +++--- osf_tests/test_node_license.py | 111 +- osf_tests/test_node_license_record.py | 3 +- osf_tests/test_notable_domains.py | 663 +- osf_tests/test_oauth_application.py | 28 +- osf_tests/test_osfgroup.py | 527 +- osf_tests/test_outcomes.py | 382 +- osf_tests/test_pigeon.py | 71 +- osf_tests/test_private_link.py | 35 +- osf_tests/test_project_decorators.py | 24 +- osf_tests/test_queued_mail.py | 60 +- osf_tests/test_region.py | 32 +- .../test_registration_bulk_upload_job.py | 22 +- .../test_registration_bulk_upload_parser.py | 543 +- .../test_registration_bulk_upload_row.py | 57 +- ...t_registration_moderation_notifications.py | 367 +- osf_tests/test_registrations.py | 566 +- osf_tests/test_reviewable.py | 14 +- osf_tests/test_s3_folder_migration.py | 58 +- osf_tests/test_sanctions.py | 206 +- osf_tests/test_schema_responses.py | 1000 ++- osf_tests/test_schemas.py | 208 +- osf_tests/test_search_views.py | 441 +- osf_tests/test_session.py | 421 +- osf_tests/test_storage_usage_limits.py | 34 +- osf_tests/test_tag.py | 45 +- osf_tests/test_user.py | 1691 ++-- osf_tests/test_utils.py | 20 +- osf_tests/test_validators.py | 9 +- osf_tests/users/test_last_login_date.py | 38 +- osf_tests/utils.py | 126 +- scripts/add_global_subscriptions.py | 35 +- .../add_missing_identifiers_to_preprints.py | 32 +- scripts/add_taxonomies_to_paleoarxiv.py | 35 +- scripts/approve_embargo_terminations.py | 55 +- scripts/approve_registrations.py | 30 +- ...urce_tags_for_unregistered_contributors.py | 287 +- .../clear_chronos_user_id_and_submissions.py | 21 +- scripts/create_fakes.py | 825 +- scripts/embargo_registrations.py | 35 +- scripts/find_spammy_content.py | 105 +- scripts/fix_merged_user_quickfiles.py | 29 +- .../fix_nodes_templated_from_registration.py | 18 +- scripts/fix_nodes_with_no_creator.py | 21 +- scripts/fix_registration_unclaimed_records.py | 63 +- scripts/fix_root.py | 11 +- scripts/fix_user_mailchimp.py | 33 +- scripts/generate_sitemap.py | 190 +- .../size_insitutions_images_for_circle.py | 62 +- scripts/meta/gatherer.py | 42 +- .../migration/migrate_share_preprint_data.py | 26 +- .../migrate_share_registration_data.py | 20 +- scripts/normalize_user_tags.py | 218 +- scripts/osfstorage/usage_audit.py | 97 +- scripts/parse_citation_styles.py | 27 +- scripts/populate_institutions.py | 4497 ++++++----- .../populate_new_and_noteworthy_projects.py | 103 +- ...late_popular_projects_and_registrations.py | 55 +- scripts/populate_preprint_providers.py | 7047 ++++++++++++++--- scripts/premigrate_created_modified.py | 358 +- scripts/purge_trashed_files.py | 26 +- scripts/refresh_addon_tokens.py | 32 +- scripts/register_oauth_scopes.py | 21 +- ...cation_subscriptions_from_registrations.py | 37 +- ...ve_qa_test_items_from_elastic_and_share.py | 30 +- scripts/retract_registrations.py | 30 +- scripts/send_queued_mails.py | 29 +- ...end_specific_registration_data_to_share.py | 28 +- scripts/stuck_registration_audit.py | 63 +- scripts/tests/test_add_preprint_providers.py | 21 +- .../test_approve_embargo_terminations.py | 54 +- scripts/tests/test_approve_registrations.py | 41 +- .../test_deactivate_requested_accounts.py | 46 +- scripts/tests/test_embargo_registrations.py | 38 +- scripts/tests/test_find_spammy_content.py | 111 +- .../tests/test_fix_draft_node_permissions.py | 38 +- ...test_fix_registration_unclaimed_records.py | 62 +- scripts/tests/test_fix_unmerged_preprints.py | 46 +- .../tests/test_populate_new_and_noteworthy.py | 19 +- ...late_popular_projects_and_registrations.py | 55 +- scripts/tests/test_refresh_addon_tokens.py | 97 +- scripts/tests/test_retract_registrations.py | 30 +- scripts/tests/test_send_queued_mails.py | 24 +- scripts/tests/test_triggered_mails.py | 42 +- .../test_user_system_tag_normalization.py | 66 +- scripts/triggered_mails.py | 38 +- scripts/unpurge_trashed_files.py | 28 +- scripts/update_taxonomies.py | 70 +- scripts/utils.py | 23 +- tasks/__init__.py | 801 +- tasks/local-dist.py | 2 +- tasks/utils.py | 17 +- tests/base.py | 146 +- tests/exceptions.py | 7 +- tests/framework_tests/test_email.py | 47 +- tests/framework_tests/test_encryption.py | 22 +- .../framework_tests/test_modular_templates.py | 83 +- tests/framework_tests/test_oauth_scopes.py | 2 +- tests/framework_tests/test_routing.py | 21 +- tests/framework_tests/test_sentry.py | 35 +- tests/framework_tests/test_url_mapping.py | 76 +- tests/identifiers/conftest.py | 5 +- tests/identifiers/test_crossref.py | 405 +- tests/identifiers/test_datacite.py | 297 +- tests/identifiers/test_identifiers.py | 33 +- tests/json_api_test_app.py | 55 +- tests/modular_templates/test_routing.py | 41 +- tests/response.py | 32 +- tests/test_addons.py | 1875 +++-- tests/test_auth.py | 585 +- tests/test_auth_basic_auth.py | 73 +- tests/test_auth_forms.py | 10 +- tests/test_campaigns.py | 112 +- tests/test_cas_authentication.py | 248 +- tests/test_citations.py | 137 +- tests/test_citeprocpy.py | 190 +- tests/test_conferences.py | 507 +- tests/test_contributors_views.py | 58 +- tests/test_csrf.py | 3 +- tests/test_ember_osf_web.py | 45 +- tests/test_events.py | 1068 ++- tests/test_features.py | 3 +- .../test_affiliation_via_orcid.py | 393 +- tests/test_mailchimp.py | 32 +- tests/test_metadata.py | 50 +- tests/test_node_licenses.py | 107 +- tests/test_notifications.py | 2219 ++++-- tests/test_oauth.py | 483 +- tests/test_permissions.py | 21 +- tests/test_preprints.py | 1727 ++-- tests/test_registrations/base.py | 59 +- tests/test_registrations/test_embargoes.py | 796 +- .../test_registration_approvals.py | 281 +- tests/test_registrations/test_retractions.py | 642 +- tests/test_registrations/test_review_flows.py | 719 +- tests/test_registrations/test_views.py | 436 +- tests/test_rubeus.py | 368 +- tests/test_sanitize.py | 83 +- tests/test_search/__init__.py | 4 +- tests/test_security.py | 2 +- tests/test_serializers.py | 465 +- tests/test_spam_mixin.py | 196 +- tests/test_subjects.py | 141 +- tests/test_test_utils.py | 32 +- tests/test_tokens.py | 113 +- tests/test_utils.py | 492 +- tests/test_views.py | 4378 +++++----- tests/test_websitefiles.py | 494 +- tests/test_webtests.py | 841 +- tests/utils.py | 102 +- website/app.py | 60 +- website/archiver/__init__.py | 66 +- website/archiver/decorators.py | 9 +- website/archiver/listeners.py | 14 +- website/archiver/signals.py | 2 +- website/archiver/tasks.py | 165 +- website/archiver/utils.py | 206 +- website/citations/providers.py | 106 +- website/citations/utils.py | 2 +- website/citations/views.py | 107 +- website/collections/tasks.py | 5 +- website/conferences/message.py | 118 +- website/conferences/signals.py | 2 +- website/conferences/utils.py | 37 +- website/conferences/views.py | 137 +- website/discovery/views.py | 2 +- website/ember_osf_web/decorators.py | 3 + website/ember_osf_web/views.py | 31 +- website/files/exceptions.py | 1 + website/files/utils.py | 12 +- website/filters/__init__.py | 23 +- website/identifiers/clients/base.py | 11 +- website/identifiers/clients/crossref.py | 206 +- website/identifiers/clients/datacite.py | 57 +- website/identifiers/clients/exceptions.py | 6 +- website/identifiers/listeners.py | 2 +- website/identifiers/tasks.py | 14 +- website/identifiers/utils.py | 29 +- website/identifiers/views.py | 11 +- website/institutions/views.py | 2 + website/landing_pages.py | 4 +- website/language.py | 94 +- website/mailchimp_utils.py | 40 +- website/mails/__init__.py | 2 +- website/mails/listeners.py | 20 +- website/mails/mails.py | 440 +- website/mails/presends.py | 42 +- website/maintenance.py | 13 +- website/notifications/constants.py | 48 +- website/notifications/emails.py | 175 +- website/notifications/events/base.py | 5 +- website/notifications/events/files.py | 273 +- website/notifications/events/utils.py | 93 +- website/notifications/exceptions.py | 2 + website/notifications/listeners.py | 28 +- website/notifications/tasks.py | 94 +- website/notifications/utils.py | 368 +- website/notifications/views.py | 68 +- website/oauth/signals.py | 2 +- website/oauth/utils.py | 11 +- website/oauth/views.py | 22 +- website/osf_groups/signals.py | 6 +- website/osf_groups/views.py | 84 +- website/preprints/tasks.py | 33 +- website/preprints/views.py | 3 +- website/profile/utils.py | 215 +- website/profile/views.py | 574 +- website/project/__init__.py | 51 +- website/project/commentable.py | 2 +- website/project/decorators.py | 295 +- website/project/forms.py | 24 +- website/project/licenses/__init__.py | 54 +- website/project/metadata/schemas.py | 124 +- website/project/metadata/utils.py | 352 +- website/project/model.py | 42 +- website/project/signals.py | 22 +- website/project/tasks.py | 25 +- website/project/utils.py | 157 +- website/project/views/comment.py | 185 +- website/project/views/contributor.py | 618 +- website/project/views/drafts.py | 182 +- website/project/views/file.py | 20 +- website/project/views/node.py | 1317 +-- website/project/views/register.py | 199 +- website/project/views/tag.py | 33 +- website/registries/utils.py | 21 +- website/registries/views.py | 20 +- website/reviews/listeners.py | 188 +- website/reviews/signals.py | 18 +- website/routes.py | 3358 ++++---- website/search/elastic_search.py | 1158 ++- website/search/exceptions.py | 3 + website/search/search.py | 118 +- website/search/util.py | 49 +- website/search/views.py | 173 +- website/search_migration/migrate.py | 215 +- website/security.py | 8 +- website/settings/__init__.py | 30 +- website/settings/defaults.py | 3393 ++++---- website/settings/local-dist.py | 98 +- website/settings/local-travis.py | 98 +- website/signals.py | 2 +- website/templates/__init__.py | 2 +- website/util/__init__.py | 85 +- website/util/client.py | 32 +- website/util/metrics.py | 37 +- website/util/paths.py | 26 +- website/util/rubeus.py | 233 +- website/util/sanitize.py | 12 +- website/views.py | 395 +- 1466 files changed, 165160 insertions(+), 102169 deletions(-) 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 e92ed1114e6..752eca784ca 100644 --- a/addons/base/tests/models.py +++ b/addons/base/tests/models.py @@ -17,7 +17,6 @@ class OAuthAddonModelTestSuiteMixinBase: - ___metaclass__ = abc.ABCMeta @property @@ -37,7 +36,6 @@ def ExternalAccountFactory(self): class OAuthAddonUserSettingTestSuiteMixin(OAuthAddonModelTestSuiteMixinBase): - def setUp(self): self.node = ProjectFactory() self.user = self.node.creator @@ -58,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 @@ -85,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"} }, } @@ -109,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() @@ -167,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): @@ -185,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 @@ -219,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 @@ -229,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 @@ -241,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 @@ -257,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 @@ -290,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 @@ -307,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() @@ -325,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() @@ -356,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): @@ -377,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): @@ -387,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() @@ -411,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)) @@ -461,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- # @@ -515,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): @@ -536,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() @@ -554,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): @@ -629,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 @@ -645,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 33fefb999ec..90cd79220f6 100644 --- a/addons/base/tests/utils.py +++ b/addons/base/tests/utils.py @@ -7,24 +7,34 @@ 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 @@ -33,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": """