From 7a1b1f000f8df352fc6a4e5faf06864a334dd966 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Tue, 13 Jun 2023 10:07:07 -0400 Subject: [PATCH 01/82] physionet-build-test.yml: run tests on Debian 12 and 11. Debian 12 (bookworm) has been released, and PhysioNet will likely upgrade in the near future. In preparation for that, run automated tests on both Debian 11 and 12. --- .github/workflows/physionet-build-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/physionet-build-test.yml b/.github/workflows/physionet-build-test.yml index 60947c8206..ea0f4afa11 100644 --- a/.github/workflows/physionet-build-test.yml +++ b/.github/workflows/physionet-build-test.yml @@ -14,7 +14,7 @@ jobs: test: name: Test runs-on: ubuntu-latest - container: debian:11 + container: ${{ matrix.container }} env: DJANGO_SETTINGS_MODULE: physionet.settings.settings TEST_GCS_INTEGRATION: false @@ -26,6 +26,7 @@ jobs: fail-fast: false matrix: pip3: ['poetry', 'requirements.txt'] + container: ['debian:11', 'debian:12'] steps: - name: Update packages From 8a22afafd5cdb423e7dd8b1d3e9ad1efd20a32f4 Mon Sep 17 00:00:00 2001 From: rutvikrj26 Date: Mon, 24 Jul 2023 21:53:42 -0400 Subject: [PATCH 02/82] adding oauth test cases --- physionet-django/oauth/tests.py | 55 ++++++++++++++++++++++++++++++++- physionet-django/oauth/urls.py | 4 +-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/physionet-django/oauth/tests.py b/physionet-django/oauth/tests.py index 7ce503c2dd..2b04231971 100644 --- a/physionet-django/oauth/tests.py +++ b/physionet-django/oauth/tests.py @@ -1,3 +1,56 @@ from django.test import TestCase +from user.models import User +from oauth2_provider.models import get_application_model, AccessToken +from django.utils import timezone +from datetime import timedelta -# Create your tests here. +Application = get_application_model() + +class OAuthTestCase(TestCase): + def setUp(self): + self.test_user = User.objects.create_user("test_user", "test@user.com", "123456") + self.application = Application.objects.create( + name="Test Application", + redirect_uris="http://localhost:8000/oauth/hello/", + user=self.test_user, + client_type=Application.CLIENT_CONFIDENTIAL, + authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE, + ) + +def test_authorize(self): + # Set up test data + query_params = { + 'client_id': self.application.client_id, + 'response_type': 'code', + 'state': 'random_state_string', + 'redirect_uri': self.application.redirect_uris, + } + + # Log in the test user + self.client.login(username='test_user', password='123456') + + # Make a GET request to the authorize endpoint with the query parameters + response = self.client.get('/oauth/authorize/', data=query_params) + + # Assert that the response status code is 200 (OK) + self.assertEqual(response.status_code, 200) + +def test_token(self): + # Set up test data + token = AccessToken.objects.create( + user=self.test_user, + token='1234567890', + application=self.application, + scope='read write', + expires=timezone.now() + timedelta(days=1) + ) + auth_headers = { + 'HTTP_AUTHORIZATION': 'Bearer ' + token.token, + } + + # Make a GET request to the token endpoint with the authorization headers + response = self.client.get('/oauth/token/', **auth_headers) + + # Assert that the response status code is 200 (OK) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['access_token'], token.token) diff --git a/physionet-django/oauth/urls.py b/physionet-django/oauth/urls.py index 7dff8c31e1..e01a093f68 100644 --- a/physionet-django/oauth/urls.py +++ b/physionet-django/oauth/urls.py @@ -8,13 +8,13 @@ path('authorize/', oauth2_views.AuthorizationView.as_view(), name="authorize"), path('token/', oauth2_views.TokenView.as_view(), name="token"), path('revoke-token/', oauth2_views.RevokeTokenView.as_view(), name="revoke-token"), + path('applications/register/', oauth2_views.ApplicationRegistration.as_view(), name="register"), ] if settings.DEBUG: # OAuth2 Application Management endpoints oauth2_endpoint_views += [ path('applications/', oauth2_views.ApplicationList.as_view(), name="list"), - path('applications/register/', oauth2_views.ApplicationRegistration.as_view(), name="register"), path('applications//', oauth2_views.ApplicationDetail.as_view(), name="detail"), path('applications//delete/', oauth2_views.ApplicationDelete.as_view(), name="delete"), path('applications//update/', oauth2_views.ApplicationUpdate.as_view(), name="update"), @@ -30,5 +30,5 @@ urlpatterns = [ # OAuth 2 endpoints: path('', include((oauth2_endpoint_views, 'oauth2_provider'), namespace="oauth2_provider")), - path('hello', ApiEndpoint.as_view()), # an example resource endpoint + path('hello', ApiEndpoint.as_view(), name='hello'), # an example resource endpoint ] From 9be2153164d0d3e623cce0b23d25572dc51c2e7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 20:54:33 +0000 Subject: [PATCH 03/82] Bump certifi from 2022.12.7 to 2023.7.22 Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.12.7 to 2023.7.22. - [Commits](https://github.com/certifi/python-certifi/compare/2022.12.07...2023.07.22) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4bc3a802b3..d37c8a1455 100644 --- a/poetry.lock +++ b/poetry.lock @@ -43,13 +43,13 @@ files = [ [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -1453,4 +1453,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "5dd3b6f971fe4c7d126bae05aa658814469b37cd78bd58f55ddb1dc850515301" +content-hash = "561e8a00c19a83f3669ce71ca8f61eb986a5cb4c9e27b65a6d21bc6456d34720" diff --git a/pyproject.toml b/pyproject.toml index bb670bcf80..49571fe3b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ cffi = "^1.15.1" pytz = "^2022.1" django-sass = "1.1.0" zxcvbn = "^4.4.28" -certifi = "^2022.12.7" +certifi = "^2023.7.22" setuptools = "65.5.1" django-js-asset = "2.0.0" hdn-research-environment = "^1.4.0" From 0c42a3293437dbda61e3ac397e23e9fef11b8c6d Mon Sep 17 00:00:00 2001 From: rutvikrj26 Date: Wed, 26 Jul 2023 13:39:39 -0400 Subject: [PATCH 04/82] Updated tests to acccess a protected resource --- physionet-django/oauth/tests.py | 77 +++++++++++++++------------------ physionet-django/oauth/urls.py | 4 +- physionet-django/oauth/views.py | 2 +- 3 files changed, 38 insertions(+), 45 deletions(-) diff --git a/physionet-django/oauth/tests.py b/physionet-django/oauth/tests.py index 2b04231971..f72727c161 100644 --- a/physionet-django/oauth/tests.py +++ b/physionet-django/oauth/tests.py @@ -1,56 +1,49 @@ +from datetime import timedelta from django.test import TestCase -from user.models import User -from oauth2_provider.models import get_application_model, AccessToken from django.utils import timezone -from datetime import timedelta +from user.models import User +from oauth2_provider.models import get_access_token_model, get_application_model Application = get_application_model() +AccessToken = get_access_token_model() + -class OAuthTestCase(TestCase): +class TestOAuth2Authentication(TestCase): def setUp(self): - self.test_user = User.objects.create_user("test_user", "test@user.com", "123456") + """ + Create a demo user, an OAuth Application and an access token for use in testing. + """ + self.test_user = User.objects.create_user("oauth_test_user", "oauth_test@example.com", "123456") self.application = Application.objects.create( name="Test Application", - redirect_uris="http://localhost:8000/oauth/hello/", + redirect_uris="http://localhost http://example.com http://example.org", user=self.test_user, client_type=Application.CLIENT_CONFIDENTIAL, authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE, ) -def test_authorize(self): - # Set up test data - query_params = { - 'client_id': self.application.client_id, - 'response_type': 'code', - 'state': 'random_state_string', - 'redirect_uri': self.application.redirect_uris, - } - - # Log in the test user - self.client.login(username='test_user', password='123456') - - # Make a GET request to the authorize endpoint with the query parameters - response = self.client.get('/oauth/authorize/', data=query_params) - - # Assert that the response status code is 200 (OK) - self.assertEqual(response.status_code, 200) - -def test_token(self): - # Set up test data - token = AccessToken.objects.create( - user=self.test_user, - token='1234567890', - application=self.application, - scope='read write', - expires=timezone.now() + timedelta(days=1) - ) - auth_headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + token.token, - } - - # Make a GET request to the token endpoint with the authorization headers - response = self.client.get('/oauth/token/', **auth_headers) + self.access_token = AccessToken.objects.create( + user=self.test_user, + scope="read write", + expires=timezone.now() + timedelta(seconds=300), + token="secret-access-token-key", + application=self.application, + ) - # Assert that the response status code is 200 (OK) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()['access_token'], token.token) + def _create_authorization_header(self, token): + return "Bearer {0}".format(token) + + def test_unauthenticated(self): + """ + Hello is a demo resource endpoint that requires authentication. This test verifies that + """ + response = self.client.get("/oauth/hello") + self.assertEqual(response.status_code, 403) + + def test_authentication_allow(self): + """ + This test verifies that a request with a valid access token is allowed. + """ + auth = self._create_authorization_header(self.access_token.token) + response = self.client.get("/oauth/hello", HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 200) diff --git a/physionet-django/oauth/urls.py b/physionet-django/oauth/urls.py index e01a093f68..ea85772bc7 100644 --- a/physionet-django/oauth/urls.py +++ b/physionet-django/oauth/urls.py @@ -1,7 +1,7 @@ from django.urls import path, include import oauth2_provider.views as oauth2_views from django.conf import settings -from .views import ApiEndpoint +from .views import hello # OAuth2 provider endpoints oauth2_endpoint_views = [ @@ -30,5 +30,5 @@ urlpatterns = [ # OAuth 2 endpoints: path('', include((oauth2_endpoint_views, 'oauth2_provider'), namespace="oauth2_provider")), - path('hello', ApiEndpoint.as_view(), name='hello'), # an example resource endpoint + path('hello', hello.as_view(), name='hello'), # an example resource endpoint ] diff --git a/physionet-django/oauth/views.py b/physionet-django/oauth/views.py index ef18988845..7e34da4d89 100644 --- a/physionet-django/oauth/views.py +++ b/physionet-django/oauth/views.py @@ -2,6 +2,6 @@ from oauth2_provider.views.generic import ProtectedResourceView -class ApiEndpoint(ProtectedResourceView): +class hello(ProtectedResourceView): def get(self, request, *args, **kwargs): return HttpResponse('Hello, OAuth2!') From 81b197258c9af9ae3ff4346d52abd2613fbae2d6 Mon Sep 17 00:00:00 2001 From: rutvikrj26 Date: Wed, 26 Jul 2023 17:51:42 -0400 Subject: [PATCH 05/82] Updated Tests for PKCE Authentication & Token workflow --- physionet-django/oauth/tests.py | 147 ++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 5 deletions(-) diff --git a/physionet-django/oauth/tests.py b/physionet-django/oauth/tests.py index f72727c161..d9bf48e28f 100644 --- a/physionet-django/oauth/tests.py +++ b/physionet-django/oauth/tests.py @@ -1,25 +1,36 @@ +import base64, random, hashlib from datetime import timedelta from django.test import TestCase from django.utils import timezone from user.models import User from oauth2_provider.models import get_access_token_model, get_application_model +from django.urls import reverse +from urllib.parse import parse_qs, urlparse +from oauth2_provider.settings import oauth2_settings Application = get_application_model() AccessToken = get_access_token_model() +CLEARTEXT_SECRET = "1234567890abcdefghijklmnopqrstuvwxyz" -class TestOAuth2Authentication(TestCase): - def setUp(self): + +class BaseTest(TestCase): + def setUp(self, oauth2_settings=oauth2_settings): """ Create a demo user, an OAuth Application and an access token for use in testing. """ - self.test_user = User.objects.create_user("oauth_test_user", "oauth_test@example.com", "123456") + self.test_user = User.objects.create_user(username="oauth_test_user",email= "oauth_test@example.com", password="123456") + self.dev_user = User.objects.create_user(username="oauth_dev_user",email= "oauth_dev@example.com", password="123456") + + self.oauth2_settings = oauth2_settings + self.application = Application.objects.create( name="Test Application", redirect_uris="http://localhost http://example.com http://example.org", - user=self.test_user, + user=self.dev_user, client_type=Application.CLIENT_CONFIDENTIAL, authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE, + client_secret = CLEARTEXT_SECRET, ) self.access_token = AccessToken.objects.create( @@ -28,11 +39,34 @@ def setUp(self): expires=timezone.now() + timedelta(seconds=300), token="secret-access-token-key", application=self.application, - ) + ) def _create_authorization_header(self, token): return "Bearer {0}".format(token) + def get_basic_auth_header(self, user, password): + """ + Return a dict containing the correct headers to set to make HTTP Basic + Auth request + """ + user_pass = "{0}:{1}".format(user, password) + auth_string = base64.b64encode(user_pass.encode("utf-8")) + auth_headers = { + "HTTP_AUTHORIZATION": "Basic " + auth_string.decode("utf-8"), + } + + return auth_headers + + def get_random_string(self, length=12, allowed_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"): + """ + Return a securely generated random string. + The default length of 12 with the a-z, A-Z, 0-9 character set returns + a 71-bit value. log_2((26+26+10)^12) =~ 71 bits + """ + return "".join(random.choice(allowed_chars) for i in range(length)) + + +class TestOAuth2Authentication(BaseTest): def test_unauthenticated(self): """ Hello is a demo resource endpoint that requires authentication. This test verifies that @@ -47,3 +81,106 @@ def test_authentication_allow(self): auth = self._create_authorization_header(self.access_token.token) response = self.client.get("/oauth/hello", HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) + + +class BaseAuthorizationCodeTokenView(BaseTest): + def generate_pkce_codes(self, algorithm, length=43): + """ + Generate a code verifier and a code challenge according to the PKCE + """ + verifier = self.get_random_string(length=length) + if algorithm == "S256": + challenge = ( + base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=") + ) + elif algorithm == "plain": + challenge = verifier + else: + raise ValueError("Unsupported code challenge method.") + + return verifier, challenge + + def get_auth(self): + """ + Helper method to retrieve a valid authorization code + """ + + authcode_data = { + "client_id": self.application.client_id, + "state": "random_state_string", + "scope": "read write", + "redirect_uri": "http://example.org", + "response_type": "code", + "allow": True, + } + + response = self.client.post(reverse("oauth2_provider:authorize"), data=authcode_data) + query_dict = parse_qs(urlparse(response["Location"]).query) + return query_dict["code"].pop() + + def get_auth_pkce(self, code_challenge, code_challenge_method): + """ + Helper method to retrieve a valid authorization code using pkce + """ + authcode_data = { + "client_id": self.application.client_id, + "state": "random_state_string", + "scope": "read write", + "redirect_uri": "http://example.org", + "response_type": "code", + "allow": True, + "code_challenge": code_challenge, + "code_challenge_method": code_challenge_method, + } + + response = self.client.post(reverse("oauth2_provider:authorize"), data=authcode_data) + query_dict = parse_qs(urlparse(response["Location"]).query) + return query_dict["code"].pop() + + +class TestAuthorizationCodeTokenView(BaseAuthorizationCodeTokenView): + def test_basic_auth(self): + """ + Request an access token using basic authentication for client authentication + """ + self.client.login(username="oauth_test_user", password="123456") + + # Disabled PKCE removes the need for a code_verifier + # Checkout details on PKCE : https://oauth.net/2/pkce/ + self.oauth2_settings.PKCE_REQUIRED = False + + authorization_code = self.get_auth() + + token_request_data = { + "grant_type": "authorization_code", + "code": authorization_code, + "redirect_uri": "http://example.org", + } + auth_headers = self.get_basic_auth_header(self.application.client_id, CLEARTEXT_SECRET) + + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + self.assertEqual(response.status_code, 200) + + def test_secure_auth_pkce(self): + """ + Request an access token using client_type: public + and PKCE enabled with the S256 algorithm + """ + self.client.login(username="oauth_test_user", password="123456") + + self.application.client_type = Application.CLIENT_PUBLIC + self.application.save() + + code_verifier, code_challenge = self.generate_pkce_codes("S256") + authorization_code = self.get_auth_pkce(code_challenge, "S256") + + token_request_data = { + "grant_type": "authorization_code", + "code": authorization_code, + "redirect_uri": "http://example.org", + "code_verifier": code_verifier, + } + auth_headers = self.get_basic_auth_header(self.application.client_id, CLEARTEXT_SECRET) + + response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + self.assertEqual(response.status_code, 200) \ No newline at end of file From f7c8854746d1b2846535e543ba908ca1d7b39c68 Mon Sep 17 00:00:00 2001 From: rutvikrj26 Date: Wed, 26 Jul 2023 18:17:26 -0400 Subject: [PATCH 06/82] Fixed all Styling Issues Fixed Styling issues --- physionet-django/oauth/tests.py | 57 ++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/physionet-django/oauth/tests.py b/physionet-django/oauth/tests.py index d9bf48e28f..1fd6bded67 100644 --- a/physionet-django/oauth/tests.py +++ b/physionet-django/oauth/tests.py @@ -1,4 +1,6 @@ -import base64, random, hashlib +import base64 +import random +import hashlib from datetime import timedelta from django.test import TestCase from django.utils import timezone @@ -19,8 +21,15 @@ def setUp(self, oauth2_settings=oauth2_settings): """ Create a demo user, an OAuth Application and an access token for use in testing. """ - self.test_user = User.objects.create_user(username="oauth_test_user",email= "oauth_test@example.com", password="123456") - self.dev_user = User.objects.create_user(username="oauth_dev_user",email= "oauth_dev@example.com", password="123456") + self.test_user = User.objects.create_user( + username="oauth_test_user", + email="oauth_test@example.com", + password="123456", + ) + + self.dev_user = User.objects.create_user( + username="oauth_dev_user", email="oauth_dev@example.com", password="123456" + ) self.oauth2_settings = oauth2_settings @@ -30,7 +39,7 @@ def setUp(self, oauth2_settings=oauth2_settings): user=self.dev_user, client_type=Application.CLIENT_CONFIDENTIAL, authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE, - client_secret = CLEARTEXT_SECRET, + client_secret=CLEARTEXT_SECRET, ) self.access_token = AccessToken.objects.create( @@ -39,7 +48,7 @@ def setUp(self, oauth2_settings=oauth2_settings): expires=timezone.now() + timedelta(seconds=300), token="secret-access-token-key", application=self.application, - ) + ) def _create_authorization_header(self, token): return "Bearer {0}".format(token) @@ -56,8 +65,12 @@ def get_basic_auth_header(self, user, password): } return auth_headers - - def get_random_string(self, length=12, allowed_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"): + + def get_random_string( + self, + length=12, + allowed_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + ): """ Return a securely generated random string. The default length of 12 with the a-z, A-Z, 0-9 character set returns @@ -91,7 +104,9 @@ def generate_pkce_codes(self, algorithm, length=43): verifier = self.get_random_string(length=length) if algorithm == "S256": challenge = ( - base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=") + base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()) + .decode() + .rstrip("=") ) elif algorithm == "plain": challenge = verifier @@ -114,7 +129,9 @@ def get_auth(self): "allow": True, } - response = self.client.post(reverse("oauth2_provider:authorize"), data=authcode_data) + response = self.client.post( + reverse("oauth2_provider:authorize"), data=authcode_data + ) query_dict = parse_qs(urlparse(response["Location"]).query) return query_dict["code"].pop() @@ -133,7 +150,9 @@ def get_auth_pkce(self, code_challenge, code_challenge_method): "code_challenge_method": code_challenge_method, } - response = self.client.post(reverse("oauth2_provider:authorize"), data=authcode_data) + response = self.client.post( + reverse("oauth2_provider:authorize"), data=authcode_data + ) query_dict = parse_qs(urlparse(response["Location"]).query) return query_dict["code"].pop() @@ -156,9 +175,13 @@ def test_basic_auth(self): "code": authorization_code, "redirect_uri": "http://example.org", } - auth_headers = self.get_basic_auth_header(self.application.client_id, CLEARTEXT_SECRET) + auth_headers = self.get_basic_auth_header( + self.application.client_id, CLEARTEXT_SECRET + ) - response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) + response = self.client.post( + reverse("oauth2_provider:token"), data=token_request_data, **auth_headers + ) self.assertEqual(response.status_code, 200) def test_secure_auth_pkce(self): @@ -180,7 +203,11 @@ def test_secure_auth_pkce(self): "redirect_uri": "http://example.org", "code_verifier": code_verifier, } - auth_headers = self.get_basic_auth_header(self.application.client_id, CLEARTEXT_SECRET) + auth_headers = self.get_basic_auth_header( + self.application.client_id, CLEARTEXT_SECRET + ) - response = self.client.post(reverse("oauth2_provider:token"), data=token_request_data, **auth_headers) - self.assertEqual(response.status_code, 200) \ No newline at end of file + response = self.client.post( + reverse("oauth2_provider:token"), data=token_request_data, **auth_headers + ) + self.assertEqual(response.status_code, 200) From 45803e02711b537c6982cd4e52945815b478f921 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Tue, 13 Jun 2023 10:11:51 -0400 Subject: [PATCH 07/82] physionet-upgrade-test.yml: run tests on Debian 12 and 11. Debian 12 (bookworm) has been released, and PhysioNet will likely upgrade in the near future. In preparation for that, run automated tests on both Debian 11 and 12. --- .github/workflows/physionet-upgrade-test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/physionet-upgrade-test.yml b/.github/workflows/physionet-upgrade-test.yml index 0464bcd650..caa03333af 100644 --- a/.github/workflows/physionet-upgrade-test.yml +++ b/.github/workflows/physionet-upgrade-test.yml @@ -12,7 +12,10 @@ jobs: testupgrade: name: Upgrade Test runs-on: ubuntu-latest - container: debian:11 + container: ${{ matrix.container }} + strategy: + matrix: + container: ['debian:11', 'debian:12'] steps: - name: Install dependencies run: | From 3b80dda664bca4590e65985bdd59673a84bb7ab4 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 28 Jul 2023 16:25:21 -0400 Subject: [PATCH 08/82] SubmissionStatus: new class. This enumeration class provides symbolic names (and a little bit of documentation) for the magic numbers that have long been used to indicate the submission status of an active project. --- .../project/modelcomponents/activeproject.py | 76 ++++++++++++++++--- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 2ece76d93c..0d8090e742 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -1,3 +1,4 @@ +from enum import IntEnum import logging import os import uuid @@ -71,21 +72,76 @@ def move_files_as_readonly(pid, dir_from, dir_to, make_zip): published_project.make_zip() +class SubmissionStatus(IntEnum): + """ + Numeric codes to indicate submission status of a project. + + These codes are stored in the submission_status field of + ActiveProject. + + 0: UNSUBMITTED + -------------- + The project has not been submitted. In this stage, the + submitting author may edit the project content. When they are + ready, the submitting author may submit the project, which moves + it to NEEDS_ASSIGNMENT. + + 10: NEEDS_ASSIGNMENT ("Awaiting Editor Assignment") + --------------------------------------------------- + The project has been submitted, but has no editor assigned. A + managing editor may assign the project to an editor, which moves + it to NEEDS_DECISION. + + 20: NEEDS_DECISION ("Awaiting Decision") + ---------------------------------------- + An editor has been assigned and needs to review the project. The + editor may accept the project, which moves it to NEEDS_COPYEDIT; + may request resubmission, which moves the project to + NEEDS_RESUBMISSION; or may reject the project, which deletes the + ActiveProject and transfers its content to an ArchivedProject. + + 30: NEEDS_RESUBMISSION ("Awaiting Author Revisions") + ------------------------------------------------- + The editor has requested a resubmission with revisions. In this + stage, the submitting author may edit the project content. When + they are ready, the submitting author may resubmit the project, + which moves it back to NEEDS_DECISION. + + 40: NEEDS_COPYEDIT ("Awaiting Copyedit") + ---------------------------------------- + The editor has accepted the project. In this stage, the editor + may edit the project content. When they are ready, the editor may + complete copyediting, which moves the project to NEEDS_APPROVAL. + + 50: NEEDS_APPROVAL ("Awaiting Author Approval") + ----------------------------------------------- + The editor has copyedited the project. Each author needs to + approve the final version. When all authors have done so, this + moves the project to NEEDS_PUBLICATION; alternatively, the editor + may reopen copyediting, which moves the project back to + NEEDS_COPYEDIT. + + 60: NEEDS_PUBLICATION ("Awaiting Publication") + ---------------------------------------------- + All authors have approved the project. The editor may publish the + project, which deletes the ActiveProject and transfers its content + to a PublishedProject. + """ + UNSUBMITTED = 0 + NEEDS_ASSIGNMENT = 10 + NEEDS_DECISION = 20 + NEEDS_RESUBMISSION = 30 + NEEDS_COPYEDIT = 40 + NEEDS_APPROVAL = 50 + NEEDS_PUBLICATION = 60 + class ActiveProject(Metadata, UnpublishedProject, SubmissionInfo): """ The project used for submitting - The submission_status field: - - 0 : Not submitted - - 10 : Submitting author submits. Awaiting editor assignment. - - 20 : Editor assigned. Awaiting editor decision. - - 30 : Revisions requested. Waiting for resubmission. Loops back - to 20 when author resubmits. - - 40 : Accepted. In copyedit stage. Awaiting editor to copyedit. - - 50 : Editor completes copyedit. Awaiting authors to approve. - - 60 : Authors approve copyedit. Ready for editor to publish - + The submission_status field is a number indicating the current + "phase" of submission; see SubmissionStatus. """ submission_status = models.PositiveSmallIntegerField(default=0) From 52e9bd5e96f9641f2cc51803637bd5766f53ba86 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 28 Jul 2023 16:26:57 -0400 Subject: [PATCH 09/82] Allow using SubmissionStatus in templates. By defining SubmissionStatus using a context processor, it is possible to use symbolic names like "SubmissionStatus.UNSUBMITTED" within Django templates. --- physionet-django/physionet/context_processors.py | 12 +++++++++--- physionet-django/physionet/settings/base.py | 2 +- .../project/modelcomponents/activeproject.py | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/physionet-django/physionet/context_processors.py b/physionet-django/physionet/context_processors.py index 647078433e..5f8f7c5921 100644 --- a/physionet-django/physionet/context_processors.py +++ b/physionet-django/physionet/context_processors.py @@ -1,10 +1,16 @@ from django.conf import settings -from project.models import AccessPolicy +from project.models import ( + AccessPolicy, + SubmissionStatus, +) -def access_policy(request): - return {'AccessPolicy': AccessPolicy} +def project_enums(request): + return { + 'AccessPolicy': AccessPolicy, + 'SubmissionStatus': SubmissionStatus, + } def storage_type(request): diff --git a/physionet-django/physionet/settings/base.py b/physionet-django/physionet/settings/base.py index bf5a4192ad..5e5b0b63b1 100644 --- a/physionet-django/physionet/settings/base.py +++ b/physionet-django/physionet/settings/base.py @@ -116,7 +116,7 @@ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', - 'physionet.context_processors.access_policy', + 'physionet.context_processors.project_enums', 'physionet.context_processors.storage_type', 'physionet.context_processors.platform_config', 'sso.context_processors.sso_enabled', diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 0d8090e742..3024003fe1 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -135,6 +135,8 @@ class SubmissionStatus(IntEnum): NEEDS_APPROVAL = 50 NEEDS_PUBLICATION = 60 + do_not_call_in_templates = True + class ActiveProject(Metadata, UnpublishedProject, SubmissionInfo): """ From a85542388fd652cbc08203670c4bd382036b293e Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 28 Jul 2023 17:30:38 -0400 Subject: [PATCH 10/82] Use symbolic names for SubmissionStatus. --- physionet-django/console/forms.py | 9 +++-- .../console/submission_info_card.html | 2 +- physionet-django/console/test_views.py | 5 ++- physionet-django/console/views.py | 39 ++++++++++--------- .../management/commands/list_projects.py | 6 +-- .../project/modelcomponents/activeproject.py | 36 ++++++++--------- .../project/active_submission_timeline.html | 10 ++--- .../templates/project/project_home.html | 2 +- physionet-django/project/views.py | 15 +++---- 9 files changed, 64 insertions(+), 60 deletions(-) diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index 74f86d7923..b72ea0e5b9 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -27,6 +27,7 @@ PublishedAffiliation, PublishedAuthor, PublishedProject, + SubmissionStatus, exists_project_slug, ) from project.projectfiles import ProjectFiles @@ -93,7 +94,7 @@ def __init__(self, *args, **kwargs): def clean_project(self): pid = self.cleaned_data['project'] validate_integer(pid) - if ActiveProject.objects.get(id=pid) not in ActiveProject.objects.filter(submission_status=10): + if ActiveProject.objects.get(id=pid) not in ActiveProject.objects.filter(submission_status=SubmissionStatus.NEEDS_ASSIGNMENT): raise forms.ValidationError('Incorrect project selected.') return pid @@ -230,13 +231,13 @@ def save(self): edit_log = EditLog.objects.get(id=edit_log.id) # Resubmit with revisions elif edit_log.decision == 1: - project.submission_status = 30 + project.submission_status = SubmissionStatus.NEEDS_RESUBMISSION project.revision_request_datetime = now project.latest_reminder = now project.save() # Accept else: - project.submission_status = 40 + project.submission_status = SubmissionStatus.NEEDS_COPYEDIT project.editor_accept_datetime = now project.latest_reminder = now @@ -289,7 +290,7 @@ def save(self): project = copyedit_log.project now = timezone.now() copyedit_log.complete_datetime = now - project.submission_status = 50 + project.submission_status = SubmissionStatus.NEEDS_APPROVAL project.copyedit_completion_datetime = now project.latest_reminder = now copyedit_log.save() diff --git a/physionet-django/console/templates/console/submission_info_card.html b/physionet-django/console/templates/console/submission_info_card.html index d82dac8b2e..7ea3d0ef9e 100644 --- a/physionet-django/console/templates/console/submission_info_card.html +++ b/physionet-django/console/templates/console/submission_info_card.html @@ -22,7 +22,7 @@ Embargo {% endif %} - {% if project.submission_status >= 40 %} + {% if project.submission_status >= SubmissionStatus.NEEDS_COPYEDIT %} diff --git a/physionet-django/console/test_views.py b/physionet-django/console/test_views.py index cdbe89679c..c5be229c41 100644 --- a/physionet-django/console/test_views.py +++ b/physionet-django/console/test_views.py @@ -17,6 +17,7 @@ License, PublishedProject, StorageRequest, + SubmissionStatus, ) from user.models import User from physionet.models import FrontPageButton, StaticPage @@ -55,7 +56,7 @@ def test_assign_editor(self): 'editor':editor.id}) project = ActiveProject.objects.get(title='MIT-BIH Arrhythmia Database') self.assertTrue(project.editor, editor) - self.assertEqual(project.submission_status, 20) + self.assertEqual(project.submission_status, SubmissionStatus.NEEDS_DECISION) def test_reassign_editor(self): """ @@ -71,7 +72,7 @@ def test_reassign_editor(self): 'project': project.id, 'editor': editor.id}) project = ActiveProject.objects.get(title='MIT-BIH Arrhythmia Database') self.assertTrue(project.editor, editor) - self.assertEqual(project.submission_status, 20) + self.assertEqual(project.submission_status, SubmissionStatus.NEEDS_DECISION) # Reassign editor editor = User.objects.get(username='amitupreti') diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index c7a370354d..f0734d345f 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -52,6 +52,7 @@ PublishedProject, Reference, StorageRequest, + SubmissionStatus, Topic, exists_project_slug, ) @@ -147,21 +148,21 @@ def submitted_projects(request): messages.success(request, 'The editor has been assigned') # Submitted projects - projects = ActiveProject.objects.filter(submission_status__gt=0).order_by( + projects = ActiveProject.objects.filter(submission_status__gt=SubmissionStatus.UNSUBMITTED).order_by( 'submission_datetime') # Separate projects by submission status # Awaiting editor assignment - assignment_projects = projects.filter(submission_status=10) + assignment_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_ASSIGNMENT) # Awaiting editor decision - decision_projects = projects.filter(submission_status=20) + decision_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_DECISION) # Awaiting author revisions - revision_projects = projects.filter(submission_status=30) + revision_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_RESUBMISSION) # Awaiting editor copyedit - copyedit_projects = projects.filter(submission_status=40) + copyedit_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_COPYEDIT) # Awaiting author approval - approval_projects = projects.filter(submission_status=50) + approval_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_APPROVAL) # Awaiting editor publish - publish_projects = projects.filter(submission_status=60) + publish_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_PUBLICATION) assign_editor_form = forms.AssignEditorForm() @@ -209,15 +210,15 @@ def editor_home(request): 'submission_datetime') # Awaiting editor decision - decision_projects = projects.filter(submission_status=20) + decision_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_DECISION) # Awaiting author revisions - revision_projects = projects.filter(submission_status=30) + revision_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_RESUBMISSION) # Awaiting editor copyedit - copyedit_projects = projects.filter(submission_status=40) + copyedit_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_COPYEDIT) # Awaiting author approval - approval_projects = projects.filter(submission_status=50) + approval_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_APPROVAL) # Awaiting editor publish - publish_projects = projects.filter(submission_status=60) + publish_projects = projects.filter(submission_status=SubmissionStatus.NEEDS_PUBLICATION) # Time to check if the reminder email can be sent yesterday = timezone.now() + timezone.timedelta(days=-1) @@ -320,7 +321,7 @@ def edit_submission(request, project_slug, *args, **kwargs): embargo_form = forms.EmbargoFilesDaysForm() # The user must be the editor - if project.submission_status not in [20, 30]: + if project.submission_status not in [SubmissionStatus.NEEDS_DECISION, SubmissionStatus.NEEDS_RESUBMISSION]: return redirect('editor_home') if request.method == 'POST': @@ -365,7 +366,7 @@ def copyedit_submission(request, project_slug, *args, **kwargs): Page to copyedit the submission """ project = kwargs['project'] - if project.submission_status != 40: + if project.submission_status != SubmissionStatus.NEEDS_COPYEDIT: return redirect('editor_home') copyedit_log = project.copyedit_logs.get(complete_datetime=None) @@ -551,7 +552,7 @@ def awaiting_authors(request, project_slug, *args, **kwargs): """ project = kwargs['project'] - if project.submission_status != 50: + if project.submission_status != SubmissionStatus.NEEDS_APPROVAL: return redirect('editor_home') authors, author_emails, storage_info, edit_logs, copyedit_logs, latest_version = project.info_card() @@ -613,7 +614,7 @@ def publish_submission(request, project_slug, *args, **kwargs): """ project = kwargs['project'] - if project.submission_status != 60: + if project.submission_status != SubmissionStatus.NEEDS_PUBLICATION: return redirect('editor_home') if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: raise ServiceUnavailable() @@ -727,7 +728,7 @@ def unsubmitted_projects(request): """ List of unsubmitted projects """ - projects = ActiveProject.objects.filter(submission_status=0).order_by( + projects = ActiveProject.objects.filter(submission_status=SubmissionStatus.UNSUBMITTED).order_by( 'creation_datetime') projects = paginate(request, projects, 50) return render(request, 'console/unsubmitted_projects.html', @@ -1102,9 +1103,9 @@ def user_management(request, username): projects = {} projects['Unsubmitted'] = ActiveProject.objects.filter(authors__user=user, - submission_status=0).order_by('-creation_datetime') + submission_status=SubmissionStatus.UNSUBMITTED).order_by('-creation_datetime') projects['Submitted'] = ActiveProject.objects.filter(authors__user=user, - submission_status__gt=0).order_by('-submission_datetime') + submission_status__gt=SubmissionStatus.UNSUBMITTED).order_by('-submission_datetime') projects['Archived'] = ArchivedProject.objects.filter(authors__user=user).order_by('-archive_datetime') projects['Published'] = PublishedProject.objects.filter(authors__user=user).order_by('-publish_datetime') diff --git a/physionet-django/project/management/commands/list_projects.py b/physionet-django/project/management/commands/list_projects.py index f229de7ec7..7bc5cfb9d3 100644 --- a/physionet-django/project/management/commands/list_projects.py +++ b/physionet-django/project/management/commands/list_projects.py @@ -8,7 +8,7 @@ from django.db.models import Q import html2text -from project.models import ActiveProject +from project.models import ActiveProject, SubmissionStatus from user.models import AssociatedEmail, User @@ -68,10 +68,10 @@ def add_arguments(self, parser): def handle(self, *args, **options): projects = ActiveProject.objects if options['unsubmitted']: - projects = projects.filter(submission_status__lt=10) + projects = projects.filter(submission_status__lt=SubmissionStatus.NEEDS_ASSIGNMENT) order = 'creation_datetime' else: - projects = projects.filter(submission_status__gte=10) + projects = projects.filter(submission_status__gte=SubmissionStatus.NEEDS_ASSIGNMENT) order = 'submission_datetime' if options['title']: diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 3024003fe1..188c4805cb 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -193,13 +193,13 @@ class ActiveProject(Metadata, UnpublishedProject, SubmissionInfo): ) SUBMISSION_STATUS_LABELS = { - 0: 'Not submitted.', - 10: 'Awaiting editor assignment.', - 20: 'Awaiting editor decision.', - 30: 'Revisions requested.', - 40: 'Submission accepted; awaiting editor copyedits.', - 50: 'Awaiting authors to approve publication.', - 60: 'Awaiting editor to publish.', + SubmissionStatus.UNSUBMITTED: 'Not submitted.', + SubmissionStatus.NEEDS_ASSIGNMENT: 'Awaiting editor assignment.', + SubmissionStatus.NEEDS_DECISION: 'Awaiting editor decision.', + SubmissionStatus.NEEDS_RESUBMISSION: 'Revisions requested.', + SubmissionStatus.NEEDS_COPYEDIT: 'Submission accepted; awaiting editor copyedits.', + SubmissionStatus.NEEDS_APPROVAL: 'Awaiting authors to approve publication.', + SubmissionStatus.NEEDS_PUBLICATION: 'Awaiting editor to publish.', } class Meta: @@ -277,14 +277,14 @@ def author_editable(self): """ Whether the project can be edited by its authors """ - if self.submission_status in [0, 30]: + if self.submission_status in [SubmissionStatus.UNSUBMITTED, SubmissionStatus.NEEDS_RESUBMISSION]: return True def copyeditable(self): """ Whether the project can be copyedited """ - if self.submission_status == 40: + if self.submission_status == SubmissionStatus.NEEDS_COPYEDIT: return True def archive(self, archive_reason): @@ -429,7 +429,7 @@ def submit(self, author_comments): if not self.is_submittable(): raise Exception('ActiveProject is not submittable') - self.submission_status = 10 + self.submission_status = SubmissionStatus.NEEDS_ASSIGNMENT self.submission_datetime = timezone.now() self.author_comments = author_comments self.save() @@ -448,7 +448,7 @@ def assign_editor(self, editor): edit stage. """ self.editor = editor - self.submission_status = 20 + self.submission_status = SubmissionStatus.NEEDS_DECISION self.editor_assignment_datetime = timezone.now() self.save() @@ -469,7 +469,7 @@ def is_resubmittable(self): """ Submit the project for review. """ - return (self.submission_status == 30 and self.check_integrity()) + return (self.submission_status == SubmissionStatus.NEEDS_RESUBMISSION and self.check_integrity()) def resubmit(self, author_comments): """ @@ -478,7 +478,7 @@ def resubmit(self, author_comments): raise Exception('ActiveProject is not resubmittable') with transaction.atomic(): - self.submission_status = 20 + self.submission_status = SubmissionStatus.NEEDS_DECISION self.resubmission_datetime = timezone.now() self.save() # Create a new edit log @@ -489,8 +489,8 @@ def reopen_copyedit(self): """ Reopen the project for copyediting """ - if self.submission_status == 50: - self.submission_status = 40 + if self.submission_status == SubmissionStatus.NEEDS_APPROVAL: + self.submission_status = SubmissionStatus.NEEDS_COPYEDIT self.copyedit_completion_datetime = None self.save() CopyeditLog.objects.create(project=self, is_reedit=True) @@ -502,13 +502,13 @@ def approve_author(self, author): author is the final outstanding one. Return whether the process was successful. """ - if self.submission_status == 50 and not author.approval_datetime: + if self.submission_status == SubmissionStatus.NEEDS_APPROVAL and not author.approval_datetime: now = timezone.now() author.approval_datetime = now author.save() if self.all_authors_approved(): self.author_approval_datetime = now - self.submission_status = 60 + self.submission_status = SubmissionStatus.NEEDS_PUBLICATION self.save() return True @@ -524,7 +524,7 @@ def is_publishable(self): """ Check whether a project may be published """ - if self.submission_status == 60 and self.check_integrity() and self.all_authors_approved(): + if self.submission_status == SubmissionStatus.NEEDS_PUBLICATION and self.check_integrity() and self.all_authors_approved(): return True return False diff --git a/physionet-django/project/templates/project/active_submission_timeline.html b/physionet-django/project/templates/project/active_submission_timeline.html index 6d5a4e260c..9c6a09943d 100644 --- a/physionet-django/project/templates/project/active_submission_timeline.html +++ b/physionet-django/project/templates/project/active_submission_timeline.html @@ -3,7 +3,7 @@ {# Awaiting authors to approve final project #} - {% if project.submission_status == 50 %} + {% if project.submission_status == SubmissionStatus.NEEDS_APPROVAL %}
  • Currently
    @@ -44,7 +44,7 @@
  • - {% elif project.submission_status == 60 %} + {% elif project.submission_status == SubmissionStatus.NEEDS_PUBLICATION %}
  • Currently
    @@ -61,7 +61,7 @@ {# Waiting for revisions #} - {% if project.submission_status == 30 %} + {% if project.submission_status == SubmissionStatus.NEEDS_RESUBMISSION %}
  • Currently
    @@ -96,7 +96,7 @@ {% endif %}
  • - {% elif project.submission_status >= 40 %} + {% elif project.submission_status >= SubmissionStatus.NEEDS_COPYEDIT %} {# At this point, there may have been any number of copyedits #} {% for c in copyedit_logs reversed %} {% if c.is_reedit %} @@ -180,7 +180,7 @@ {# Waiting for editor #} - {% if project.submission_status == 10 %} + {% if project.submission_status == SubmissionStatus.NEEDS_ASSIGNMENT %}
  • Currently
    diff --git a/physionet-django/project/templates/project/project_home.html b/physionet-django/project/templates/project/project_home.html index 4a3fe40df8..1431f08770 100644 --- a/physionet-django/project/templates/project/project_home.html +++ b/physionet-django/project/templates/project/project_home.html @@ -174,7 +174,7 @@

    {{ project.title }}<

    Submitting Author: {{ project.submitting_author.get_full_name }}
    Created: {{ project.creation_datetime|date }}. Modified: {{ project.modified_datetime|date }}.
    - Status: {{ project.submission_status_label }} {% if project.submission_status == 0 %}Deadline: {{ project.submission_deadline|date }}.{% endif %} + Status: {{ project.submission_status_label }} {% if project.submission_status == SubmissionStatus.UNSUBMITTED %}Deadline: {{ project.submission_deadline|date }}.{% endif %}

  • {% endfor %} diff --git a/physionet-django/project/views.py b/physionet-django/project/views.py index b63ff39c1a..22a1fde4a5 100644 --- a/physionet-django/project/views.py +++ b/physionet-django/project/views.py @@ -47,6 +47,7 @@ PublishedProject, Reference, StorageRequest, + SubmissionStatus, Topic, UploadedDocument, ) @@ -248,16 +249,16 @@ def project_home(request): missing_affiliations = [] pending_revisions = [] for p in projects: - if (p.submission_status == 50 + if (p.submission_status == SubmissionStatus.NEEDS_APPROVAL and not p.all_authors_approved()): if p.authors.get(user=user).is_submitting: pending_author_approvals.append(p) elif not p.authors.get(user=user).approval_datetime: pending_author_approvals.append(p) - if (p.submission_status == 30 + if (p.submission_status == SubmissionStatus.NEEDS_RESUBMISSION and p.authors.get(user=user).is_submitting): pending_revisions.append(p) - if p.submission_status == 0 and p.authors.get(user=user).affiliations.count() == 0: + if p.submission_status == SubmissionStatus.UNSUBMITTED and p.authors.get(user=user).affiliations.count() == 0: missing_affiliations.append([p, p.authors.get(user=user).creation_date]) archived_projects = [a.project for a in archived_authors] @@ -485,7 +486,7 @@ def edit_affiliation(request, project_slug, **kwargs): project, authors = kwargs['project'], kwargs['authors'] author = authors.get(user=request.user) - if project.submission_status not in [0, 30]: + if project.submission_status not in [SubmissionStatus.UNSUBMITTED, SubmissionStatus.NEEDS_RESUBMISSION]: raise Http404() # Reload the formset with the first empty form @@ -1332,7 +1333,7 @@ def project_submission(request, project_slug, **kwargs): pass # Register the approval if valid if project.approve_author(author): - if project.submission_status == 60: + if project.submission_status == SubmissionStatus.NEEDS_PUBLICATION: messages.success(request, 'You have approved the project for publication. The editor will publish it shortly') notification.authors_approved_notify(request, project) else: @@ -1349,7 +1350,7 @@ def project_submission(request, project_slug, **kwargs): for e in edit_logs: e.set_quality_assurance_results() # Awaiting authors - if project.submission_status == 50: + if project.submission_status == SubmissionStatus.NEEDS_APPROVAL: authors = authors.order_by('approval_datetime') for a in authors: a.set_display_info() @@ -1420,7 +1421,7 @@ def project_ethics(request, project_slug, **kwargs): def edit_ethics(request, project_slug, **kwargs): project = kwargs['project'] - if project.submission_status not in [0, 30]: + if project.submission_status not in [SubmissionStatus.UNSUBMITTED, SubmissionStatus.NEEDS_RESUBMISSION]: raise Http404() # Reload the formset with the first empty form From 43ea40f9315ef08779e6338b1c84f097a7ce9692 Mon Sep 17 00:00:00 2001 From: rutvikrj26 Date: Fri, 28 Jul 2023 18:12:55 -0400 Subject: [PATCH 11/82] Removed the Register endpoint from openly available APIs --- physionet-django/oauth/urls.py | 54 ++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/physionet-django/oauth/urls.py b/physionet-django/oauth/urls.py index ea85772bc7..2c3f8ca4ed 100644 --- a/physionet-django/oauth/urls.py +++ b/physionet-django/oauth/urls.py @@ -5,30 +5,58 @@ # OAuth2 provider endpoints oauth2_endpoint_views = [ - path('authorize/', oauth2_views.AuthorizationView.as_view(), name="authorize"), - path('token/', oauth2_views.TokenView.as_view(), name="token"), - path('revoke-token/', oauth2_views.RevokeTokenView.as_view(), name="revoke-token"), - path('applications/register/', oauth2_views.ApplicationRegistration.as_view(), name="register"), + path("authorize/", oauth2_views.AuthorizationView.as_view(), name="authorize"), + path("token/", oauth2_views.TokenView.as_view(), name="token"), + path("revoke-token/", oauth2_views.RevokeTokenView.as_view(), name="revoke-token"), ] if settings.DEBUG: # OAuth2 Application Management endpoints oauth2_endpoint_views += [ - path('applications/', oauth2_views.ApplicationList.as_view(), name="list"), - path('applications//', oauth2_views.ApplicationDetail.as_view(), name="detail"), - path('applications//delete/', oauth2_views.ApplicationDelete.as_view(), name="delete"), - path('applications//update/', oauth2_views.ApplicationUpdate.as_view(), name="update"), + path("applications/", oauth2_views.ApplicationList.as_view(), name="list"), + path( + "applications//", + oauth2_views.ApplicationDetail.as_view(), + name="detail", + ), + path( + "applications//delete/", + oauth2_views.ApplicationDelete.as_view(), + name="delete", + ), + path( + "applications//update/", + oauth2_views.ApplicationUpdate.as_view(), + name="update", + ), + path( + "applications/register/", + oauth2_views.ApplicationRegistration.as_view(), + name="register", + ), ] # OAuth2 Token Management endpoints oauth2_endpoint_views += [ - path('authorized-tokens/', oauth2_views.AuthorizedTokensListView.as_view(), name="authorized-token-list"), - path('authorized-tokens//delete/', oauth2_views.AuthorizedTokenDeleteView.as_view(), - name="authorized-token-delete"), + path( + "authorized-tokens/", + oauth2_views.AuthorizedTokensListView.as_view(), + name="authorized-token-list", + ), + path( + "authorized-tokens//delete/", + oauth2_views.AuthorizedTokenDeleteView.as_view(), + name="authorized-token-delete", + ), ] urlpatterns = [ # OAuth 2 endpoints: - path('', include((oauth2_endpoint_views, 'oauth2_provider'), namespace="oauth2_provider")), - path('hello', hello.as_view(), name='hello'), # an example resource endpoint + path( + "", + include( + (oauth2_endpoint_views, "oauth2_provider"), namespace="oauth2_provider" + ), + ), + path("hello", hello.as_view(), name="hello"), # an example resource endpoint ] From 4e836bc0ae1da05f29aa5ee7dfb4d979d0633a29 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Tue, 1 Aug 2023 10:51:43 -0400 Subject: [PATCH 12/82] Bump certifi from 2022.12.7 to 2023.7.22 --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 39832dcac3..12936a0837 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,9 @@ bleach==3.3.0 ; python_version >= "3.9" and python_version < "4.0" \ cachetools==4.2.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001 \ --hash=sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff -certifi==2022.12.7 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ - --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 +certifi==2023.7.22 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 cffi==1.15.1 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \ From 59bcdf86dac5470239b7f740ab9ca0345d695489 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Wed, 2 Aug 2023 10:39:34 -0400 Subject: [PATCH 13/82] Reduce line length for formatting check. --- physionet-django/console/forms.py | 8 +++++--- physionet-django/console/views.py | 10 ++++++---- .../project/modelcomponents/activeproject.py | 6 +++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index b72ea0e5b9..7ba479339c 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -92,10 +92,12 @@ def __init__(self, *args, **kwargs): .order_by('username') def clean_project(self): - pid = self.cleaned_data['project'] + pid = self.cleaned_data["project"] validate_integer(pid) - if ActiveProject.objects.get(id=pid) not in ActiveProject.objects.filter(submission_status=SubmissionStatus.NEEDS_ASSIGNMENT): - raise forms.ValidationError('Incorrect project selected.') + if ActiveProject.objects.get(id=pid) not in ActiveProject.objects.filter( + submission_status=SubmissionStatus.NEEDS_ASSIGNMENT + ): + raise forms.ValidationError("Incorrect project selected.") return pid diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index f0734d345f..bb34af6c88 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -1102,10 +1102,12 @@ def user_management(request, username): is_verified=False) projects = {} - projects['Unsubmitted'] = ActiveProject.objects.filter(authors__user=user, - submission_status=SubmissionStatus.UNSUBMITTED).order_by('-creation_datetime') - projects['Submitted'] = ActiveProject.objects.filter(authors__user=user, - submission_status__gt=SubmissionStatus.UNSUBMITTED).order_by('-submission_datetime') + projects["Unsubmitted"] = ActiveProject.objects.filter( + authors__user=user, submission_status=SubmissionStatus.UNSUBMITTED + ).order_by("-creation_datetime") + projects["Submitted"] = ActiveProject.objects.filter( + authors__user=user, submission_status__gt=SubmissionStatus.UNSUBMITTED + ).order_by("-submission_datetime") projects['Archived'] = ArchivedProject.objects.filter(authors__user=user).order_by('-archive_datetime') projects['Published'] = PublishedProject.objects.filter(authors__user=user).order_by('-publish_datetime') diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 188c4805cb..9cec57f4db 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -524,7 +524,11 @@ def is_publishable(self): """ Check whether a project may be published """ - if self.submission_status == SubmissionStatus.NEEDS_PUBLICATION and self.check_integrity() and self.all_authors_approved(): + if ( + self.submission_status == SubmissionStatus.NEEDS_PUBLICATION + and self.check_integrity() + and self.all_authors_approved() + ): return True return False From a9981bc98e9e5d1345f64201edaea4bc5790e204 Mon Sep 17 00:00:00 2001 From: rutvikrj26 Date: Wed, 2 Aug 2023 15:23:23 -0400 Subject: [PATCH 14/82] Added Token Verifiaction Tests --- physionet-django/oauth/tests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/physionet-django/oauth/tests.py b/physionet-django/oauth/tests.py index 1fd6bded67..d9c275a8b0 100644 --- a/physionet-django/oauth/tests.py +++ b/physionet-django/oauth/tests.py @@ -2,6 +2,7 @@ import random import hashlib from datetime import timedelta +import re from django.test import TestCase from django.utils import timezone from user.models import User @@ -183,6 +184,14 @@ def test_basic_auth(self): reverse("oauth2_provider:token"), data=token_request_data, **auth_headers ) self.assertEqual(response.status_code, 200) + token = response.json()["access_token"] + + # Testing the Token Acquired through the above request + self.client.logout() + + auth = self._create_authorization_header(token) + response = self.client.get("/oauth/hello", HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 200) def test_secure_auth_pkce(self): """ @@ -211,3 +220,11 @@ def test_secure_auth_pkce(self): reverse("oauth2_provider:token"), data=token_request_data, **auth_headers ) self.assertEqual(response.status_code, 200) + token = response.json()["access_token"] + + # Testing the Token Acquired through the above request + self.client.logout() + + auth = self._create_authorization_header(token) + response = self.client.get("/oauth/hello", HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 200) From ac699ea2c9c78af83e54b34d23d527d3c0b7c95a Mon Sep 17 00:00:00 2001 From: rutvikrj26 Date: Wed, 2 Aug 2023 15:42:37 -0400 Subject: [PATCH 15/82] removed redundant get_random_string function --- physionet-django/oauth/tests.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/physionet-django/oauth/tests.py b/physionet-django/oauth/tests.py index d9c275a8b0..0ccaac1529 100644 --- a/physionet-django/oauth/tests.py +++ b/physionet-django/oauth/tests.py @@ -10,6 +10,7 @@ from django.urls import reverse from urllib.parse import parse_qs, urlparse from oauth2_provider.settings import oauth2_settings +from django.utils.crypto import get_random_string Application = get_application_model() AccessToken = get_access_token_model() @@ -67,18 +68,6 @@ def get_basic_auth_header(self, user, password): return auth_headers - def get_random_string( - self, - length=12, - allowed_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", - ): - """ - Return a securely generated random string. - The default length of 12 with the a-z, A-Z, 0-9 character set returns - a 71-bit value. log_2((26+26+10)^12) =~ 71 bits - """ - return "".join(random.choice(allowed_chars) for i in range(length)) - class TestOAuth2Authentication(BaseTest): def test_unauthenticated(self): @@ -102,7 +91,7 @@ def generate_pkce_codes(self, algorithm, length=43): """ Generate a code verifier and a code challenge according to the PKCE """ - verifier = self.get_random_string(length=length) + verifier = get_random_string(length=length) if algorithm == "S256": challenge = ( base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()) From 41b01a8e37c9b551b2984ae5efa760b853d83ae7 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 3 Aug 2023 15:27:03 -0400 Subject: [PATCH 16/82] Use a colon in "chown $USER:GROUP". The "$USER.$GROUP" syntax supported by GNU chown is non-standard and potentially ambiguous (there could be a user named "pn.pn"). Recent versions of coreutils have added a warning. Use the standard (POSIX) colon syntax instead. --- deploy/README.md | 6 +++--- deploy/test-server/install-pn-test-server | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/deploy/README.md b/deploy/README.md index 3860114ab6..c381fc3dd2 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -100,8 +100,8 @@ cd /physionet # Copy over the .env file into /physionet/physionet-build scp /.env /physionet/physionet-build/ # The software folder should be owned by the dedicated user. The socket file directory should be accessible by nginx. -chown -R pn.pn /physionet -chown pn.www-data /physionet/deploy +chown -R pn:pn /physionet +chown pn:www-data /physionet/deploy chmod g+w /physionet/deploy # Make the static and media roots mkdir /data @@ -109,7 +109,7 @@ mkdir /data/pn-static mkdir /data/pn-static/published-projects mkdir /data/pn-media mkdir /data/pn-media/{active-projects,archived-projects,credential-applications,published-projects,users} -chown -R pn.pn /data/{pn-media,pn-static} +chown -R pn:pn /data/{pn-media,pn-static} ``` The directory structure for the site's software and files will be: diff --git a/deploy/test-server/install-pn-test-server b/deploy/test-server/install-pn-test-server index 87a43ac253..68e9c9d23c 100755 --- a/deploy/test-server/install-pn-test-server +++ b/deploy/test-server/install-pn-test-server @@ -90,7 +90,7 @@ printf '%s\n%s\n' "$DBPASSWORD" "$DBPASSWORD" | su postgres -c 'createdb physionet -O physionet' mkdir -p /physionet /data/pn-static /data/pn-media -chown pn.pn /physionet /data/pn-static /data/pn-media +chown pn:pn /physionet /data/pn-static /data/pn-media su pn -c ' set -e umask 0002 @@ -153,17 +153,17 @@ su pn -c ' yes | python3 manage.py loaddemo ' -chown www-data.www-data -R /physionet/deploy -chown www-data.www-data -R /data/pn-media -chown www-data.www-data -R /data/pn-static/published-projects +chown www-data:www-data -R /physionet/deploy +chown www-data:www-data -R /data/pn-media +chown www-data:www-data -R /data/pn-static/published-projects rm /etc/nginx/sites-enabled/default ln -s ../sites-available/physionet_nginx.conf \ /etc/nginx/sites-enabled/physionet_nginx.conf mkdir /data/log /data/log/nginx /data/log/uwsgi /data/log/pn -chown www-data.root /data/log/uwsgi -chown pn.root /data/log/pn +chown www-data:root /data/log/uwsgi +chown pn:root /data/log/pn ( cd /physionet/physionet-build/deploy/common From fc3fac1ff248074b30c9c5aed22ba40ecb0ffeac Mon Sep 17 00:00:00 2001 From: Karol Szuster Date: Thu, 10 Aug 2023 10:24:57 +0200 Subject: [PATCH 17/82] Do not check if downloads are allowed when authorizing access to project --- physionet-django/project/authorization/access.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/physionet-django/project/authorization/access.py b/physionet-django/project/authorization/access.py index d63b8446f1..d5ea555da7 100644 --- a/physionet-django/project/authorization/access.py +++ b/physionet-django/project/authorization/access.py @@ -96,9 +96,6 @@ def can_access_project(project, user): if project.deprecated_files: return False - if not project.allow_file_downloads: - return False - if project.access_policy == AccessPolicy.OPEN: return True elif project.access_policy == AccessPolicy.RESTRICTED: From 98f6025f995bf58dc489e1d6043caef45ed078ed Mon Sep 17 00:00:00 2001 From: Michael Scanlan Date: Thu, 10 Aug 2023 13:23:06 -0400 Subject: [PATCH 18/82] remove unused fields from credential reivew model --- physionet-django/user/fixtures/demo-user.json | 18 ----------- physionet-django/user/models.py | 30 ------------------- 2 files changed, 48 deletions(-) diff --git a/physionet-django/user/fixtures/demo-user.json b/physionet-django/user/fixtures/demo-user.json index c7bcc14692..0929a080a8 100644 --- a/physionet-django/user/fixtures/demo-user.json +++ b/physionet-django/user/fixtures/demo-user.json @@ -14339,25 +14339,7 @@ "fields": { "application": 109, "status": 30, - "fields_complete": null, - "appears_correct": null, - "lang_understandable": null, - "user_searchable": null, - "user_has_papers": null, - "research_summary_clear": null, - "course_name_provided": null, - "user_understands_privacy": null, - "user_org_known": null, - "user_details_consistent": null, - "ref_appropriate": null, - "ref_searchable": null, - "ref_has_papers": null, - "ref_is_supervisor": null, - "ref_course_list": null, "ref_skipped": null, - "ref_knows_applicant": null, - "ref_approves": null, - "ref_understands_privacy": null, "responder_comments": "" } }, diff --git a/physionet-django/user/models.py b/physionet-django/user/models.py index 0aaab5aaee..886a1b53ca 100644 --- a/physionet-django/user/models.py +++ b/physionet-django/user/models.py @@ -1079,39 +1079,9 @@ class CredentialReview(models.Model): status = models.PositiveSmallIntegerField(default=10, choices=REVIEW_STATUS_LABELS) - # Initial review questions - # No longer checked. Consider removing these. - fields_complete = models.BooleanField(null=True) - appears_correct = models.BooleanField(null=True) - lang_understandable = models.BooleanField(null=True) - - # ID check questions - # No longer checked. Consider removing these. - user_searchable = models.BooleanField(null=True) - user_has_papers = models.BooleanField(null=True) - research_summary_clear = models.BooleanField(null=True) - course_name_provided = models.BooleanField(null=True) - user_understands_privacy = models.BooleanField(null=True) - user_org_known = models.BooleanField(null=True) - user_details_consistent = models.BooleanField(null=True) - - # Reference check questions - # No longer checked. Consider removing these. - ref_appropriate = models.BooleanField(null=True) - ref_searchable = models.BooleanField(null=True) - ref_has_papers = models.BooleanField(null=True) - ref_is_supervisor = models.BooleanField(null=True) - ref_course_list = models.BooleanField(null=True) - # Log skipped reference ref_skipped = models.BooleanField(null=True) - # Reference response check questions - # No longer checked. Consider removing these. - ref_knows_applicant = models.BooleanField(null=True) - ref_approves = models.BooleanField(null=True) - ref_understands_privacy = models.BooleanField(null=True) - # Reference response check questions responder_comments = models.CharField(max_length=500, default='', blank=True) From e56a3260825963e0e1e48caa51b866adff21fafa Mon Sep 17 00:00:00 2001 From: Michael Scanlan Date: Thu, 10 Aug 2023 13:23:32 -0400 Subject: [PATCH 19/82] add migration --- ...edentialreview_appears_correct_and_more.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 physionet-django/user/migrations/0056_remove_credentialreview_appears_correct_and_more.py diff --git a/physionet-django/user/migrations/0056_remove_credentialreview_appears_correct_and_more.py b/physionet-django/user/migrations/0056_remove_credentialreview_appears_correct_and_more.py new file mode 100644 index 0000000000..fbd9c02a1b --- /dev/null +++ b/physionet-django/user/migrations/0056_remove_credentialreview_appears_correct_and_more.py @@ -0,0 +1,86 @@ +# Generated by Django 4.1.9 on 2023-07-05 16:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + MIGRATE_AFTER_INSTALL = True + + dependencies = [ + ('user', '0055_auto_20230330_1723'), + ] + + operations = [ + migrations.RemoveField( + model_name='credentialreview', + name='appears_correct', + ), + migrations.RemoveField( + model_name='credentialreview', + name='course_name_provided', + ), + migrations.RemoveField( + model_name='credentialreview', + name='fields_complete', + ), + migrations.RemoveField( + model_name='credentialreview', + name='lang_understandable', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_appropriate', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_approves', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_course_list', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_has_papers', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_is_supervisor', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_knows_applicant', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_searchable', + ), + migrations.RemoveField( + model_name='credentialreview', + name='ref_understands_privacy', + ), + migrations.RemoveField( + model_name='credentialreview', + name='research_summary_clear', + ), + migrations.RemoveField( + model_name='credentialreview', + name='user_details_consistent', + ), + migrations.RemoveField( + model_name='credentialreview', + name='user_has_papers', + ), + migrations.RemoveField( + model_name='credentialreview', + name='user_org_known', + ), + migrations.RemoveField( + model_name='credentialreview', + name='user_searchable', + ), + migrations.RemoveField( + model_name='credentialreview', + name='user_understands_privacy', + ), + ] From d681cbddd59a1179acd5e6bbdc854016b26b1e6d Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 18 Aug 2023 14:55:36 -0400 Subject: [PATCH 20/82] SubmissionInfo: add 'files' property. We want to modularize project file storage, and in the future, use different backends for different projects. I'm not sure exactly how this will look, but at a minimum, each part of the site that interacts with project files will need to be aware of *which* project it is dealing with. Consequently, any function that needs a ProjectFiles instance must obtain it from a project object (ActiveProject, PublishedProject, or ArchivedProject); the ProjectFiles pseudo-constructor is deprecated and should not be used. --- .../project/modelcomponents/submission.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/physionet-django/project/modelcomponents/submission.py b/physionet-django/project/modelcomponents/submission.py index f18cf1387e..6bf37b8073 100644 --- a/physionet-django/project/modelcomponents/submission.py +++ b/physionet-django/project/modelcomponents/submission.py @@ -1,9 +1,15 @@ +import functools + from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models from project.quota import DemoQuotaManager from django.conf import settings +from physionet.settings.base import StorageTypes +from project.projectfiles.gcs import GCSProjectFiles +from project.projectfiles.local import LocalProjectFiles + class EditLog(models.Model): """ @@ -255,3 +261,20 @@ def quota_manager(self): creation_time=self.creation_datetime) quota_manager.set_limits(bytes_hard=limit, bytes_soft=limit) return quota_manager + + @functools.cached_property + def files(self): + """ + Return a ProjectFiles instance for this project. + + This object can be used to manipulate the project's files; see + project.projectfiles.base.BaseProjectFiles. + + Currently there is only a single ProjectFiles implementation + per site, but in future each project may have its own storage + backend. + """ + if settings.STORAGE_TYPE == StorageTypes.LOCAL: + return LocalProjectFiles() + elif settings.STORAGE_TYPE == StorageTypes.GCP: + return GCSProjectFiles() From 6318570b39287007ba3d8f86ce79a6d99e4729af Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 18 Aug 2023 16:29:23 -0400 Subject: [PATCH 21/82] Replace numerous uses of ProjectFiles with project.files. We want to modularize project file storage, and in the future, use different backends for different projects. Functions that need a ProjectFiles instance will need to obtain it from a project object rather than calling the ProjectFiles pseudo-constructor. Fix this for existing cases where the usage is linked to an existing project. --- physionet-django/console/forms.py | 3 +-- physionet-django/console/views.py | 5 ++--- physionet-django/project/fileviews/base.py | 5 ++--- physionet-django/project/fileviews/main.py | 3 +-- physionet-django/project/forms.py | 17 +++++++++-------- .../project/modelcomponents/activeproject.py | 12 ++++++------ .../project/modelcomponents/metadata.py | 5 ++--- .../project/modelcomponents/publishedproject.py | 14 +++++++------- .../modelcomponents/unpublishedproject.py | 3 +-- physionet-django/project/test_projectfiles.py | 8 +++++--- physionet-django/project/views.py | 8 ++++---- 11 files changed, 40 insertions(+), 43 deletions(-) diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index 7ba479339c..ad34f67ad8 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -30,7 +30,6 @@ SubmissionStatus, exists_project_slug, ) -from project.projectfiles import ProjectFiles from project.validators import MAX_PROJECT_SLUG_LENGTH, validate_doi, validate_slug from user.models import CodeOfConduct, CredentialApplication, CredentialReview, User, TrainingQuestion @@ -318,7 +317,7 @@ def __init__(self, project, *args, **kwargs): else: self.fields['slug'].initial = project.slug - if not ProjectFiles().can_make_zip(): + if not project.files.can_make_zip(): self.fields['make_zip'].disabled = True self.fields['make_zip'].required = False self.fields['make_zip'].initial = 0 diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index bb34af6c88..68ec4dfac8 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -57,7 +57,6 @@ exists_project_slug, ) from project.authorization.access import can_view_project_files -from project.projectfiles import ProjectFiles from project.utility import readable_size from project.validators import MAX_PROJECT_SLUG_LENGTH from project.views import get_file_forms, get_project_file_info, process_files_post @@ -958,8 +957,8 @@ def manage_published_project(request, project_slug, version): 'bulk_url_prefix': bulk_url_prefix, 'contact_form': contact_form, 'legacy_author_form': legacy_author_form, - 'can_make_zip': ProjectFiles().can_make_zip(), - 'can_make_checksum': ProjectFiles().can_make_checksum(), + 'can_make_zip': project.files.can_make_zip(), + 'can_make_checksum': project.files.can_make_checksum(), }, ) diff --git a/physionet-django/project/fileviews/base.py b/physionet-django/project/fileviews/base.py index 84b3a14780..70e583a920 100644 --- a/physionet-django/project/fileviews/base.py +++ b/physionet-django/project/fileviews/base.py @@ -4,7 +4,6 @@ from django.shortcuts import redirect, render from django.urls import reverse from physionet.utility import file_content_type -from project.projectfiles import ProjectFiles from project.utility import get_dir_breadcrumbs MAX_PLAIN_SIZE = 5 * 1024 * 1024 @@ -89,7 +88,7 @@ def download_url(self): parameter indicating that we should try to force the browser to save the file rather than displaying it. """ - return ProjectFiles().download_url(self.project, self.path) + return self.project.files.download_url(self.project, self.path) def raw_url(self): """ @@ -99,7 +98,7 @@ def raw_url(self): according to the browser's default settings for the corresponding content type. """ - return ProjectFiles().raw_url(self.project, self.path) + return self.project.files.raw_url(self.project, self.path) def size(self): """ diff --git a/physionet-django/project/fileviews/main.py b/physionet-django/project/fileviews/main.py index 15d12ae293..ba1b964855 100644 --- a/physionet-django/project/fileviews/main.py +++ b/physionet-django/project/fileviews/main.py @@ -8,7 +8,6 @@ from project.fileviews.image import ImageFileView from project.fileviews.inline import InlineFileView from project.fileviews.text import TextFileView -from project.projectfiles import ProjectFiles _suffixes = { '.bmp': ImageFileView, @@ -35,7 +34,7 @@ def display_project_file(request, project, file_path): try: abs_path = os.path.join(project.file_root(), file_path) - infile = ProjectFiles().open(abs_path) + infile = project.files.open(abs_path) except IsADirectoryError: return redirect(request.path + '/') except (FileNotFoundError, NotADirectoryError): diff --git a/physionet-django/project/forms.py b/physionet-django/project/forms.py index 7dae854b23..238d9c333c 100644 --- a/physionet-django/project/forms.py +++ b/physionet-django/project/forms.py @@ -40,7 +40,6 @@ exists_project_slug, UploadedDocument, ) -from project.projectfiles import ProjectFiles from user.models import User, TrainingType from user.validators import validate_affiliation @@ -176,7 +175,7 @@ def perform_action(self): errors = ErrorList() for file in self.files.getlist('file_field'): try: - ProjectFiles().fput(self.file_dir, file) + self.project.files.fput(self.file_dir, file) except FileExistsError: errors.append(format_html( 'Item named {} already exists', file.name)) @@ -202,7 +201,7 @@ def perform_action(self): file_path = os.path.join(self.file_dir, name) try: - ProjectFiles().mkdir(file_path) + self.project.files.mkdir(file_path) except FileExistsError: errors.append(format_html( 'Item named {} already exists', name)) @@ -238,7 +237,7 @@ def perform_action(self): for item in self.cleaned_data['items']: path = os.path.join(self.file_dir, item) try: - ProjectFiles().rm(path) + self.project.files.rm(path) except OSError as e: if not os.path.exists(path): errors.append(format_html( @@ -274,7 +273,7 @@ def perform_action(self): old_path = os.path.join(self.file_dir, old_name) new_path = os.path.join(self.file_dir, new_name) try: - ProjectFiles().rename(old_path, new_path) + self.project.files.rename(old_path, new_path) except FileExistsError: errors.append(format_html( 'Item named {} already exists', new_name)) @@ -349,7 +348,7 @@ def perform_action(self): for item in self.cleaned_data['items']: path = os.path.join(self.file_dir, item) try: - ProjectFiles().mv(path, self.dest_dir) + self.project.files.mv(path, self.dest_dir) except FileExistsError: errors.append(format_html( 'Item named {} already exists in {}', @@ -392,7 +391,7 @@ def save(self): is_submitting=True, is_corresponding=True) author.import_profile_info() # Create file directory - ProjectFiles().mkdir(project.file_root()) + project.files.mkdir(project.file_root()) return project @@ -507,7 +506,9 @@ def save(self): ignored_files = ('SHA256SUMS.txt', 'LICENSE.txt') if settings.COPY_FILES_TO_NEW_VERSION: - ProjectFiles().cp_dir(older_file_root, current_file_root, ignored_files=ignored_files) + # NOTE: This assumes the new active project is using the + # same storage backend as the existing published project. + project.files.cp_dir(older_file_root, current_file_root, ignored_files=ignored_files) return project diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 9cec57f4db..b59a168509 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -218,7 +218,7 @@ def storage_used(self): versions of this CoreProject. (The QuotaManager should ensure that the same file is not counted twice in this total.) """ - current = ProjectFiles().active_project_storage_used(self) + current = self.files.active_project_storage_used(self) published = self.core_project.total_published_size return current + published @@ -339,7 +339,7 @@ def archive(self, archive_reason): self.clear_files() else: # Move over files - ProjectFiles().rename(self.file_root(), archived_project.file_root()) + self.files.rename(self.file_root(), archived_project.file_root()) # Copy the ActiveProject timestamp to the ArchivedProject. # Since this is an auto_now field, save() doesn't allow @@ -536,7 +536,7 @@ def clear_files(self): """ Delete the project file directory """ - ProjectFiles().rmtree(self.file_root()) + self.files.rmtree(self.file_root()) def publish(self, slug=None, make_zip=True, title=None): """ @@ -562,7 +562,7 @@ def publish(self, slug=None, make_zip=True, title=None): # Create project file root if this is first version or the first # version with a different access policy - ProjectFiles().publish_initial(self, published_project) + self.files.publish_initial(self, published_project) try: with transaction.atomic(): @@ -671,11 +671,11 @@ def publish(self, slug=None, make_zip=True, title=None): self.delete() except BaseException: - ProjectFiles().publish_rollback(self, published_project) + self.files.publish_rollback(self, published_project) raise - ProjectFiles().publish_complete(self, published_project) + self.files.publish_complete(self, published_project) return published_project diff --git a/physionet-django/project/modelcomponents/metadata.py b/physionet-django/project/modelcomponents/metadata.py index f2af10ceb5..37eccef5c0 100644 --- a/physionet-django/project/modelcomponents/metadata.py +++ b/physionet-django/project/modelcomponents/metadata.py @@ -11,7 +11,6 @@ from project.modelcomponents.access import AccessPolicy, AnonymousAccess from project.modelcomponents.fields import SafeHTMLField from project.modelcomponents.authors import Affiliation -from project.projectfiles import ProjectFiles from project.utility import LinkFilter, get_directory_info, get_file_info, list_items from project.validators import validate_title, validate_topic, validate_version @@ -311,7 +310,7 @@ def create_license_file(self): project directory, replacing any existing file with that name. """ fname = os.path.join(self.file_root(), 'LICENSE.txt') - ProjectFiles().fwrite(fname, self.license_content(fmt='text')) + self.files.fwrite(fname, self.license_content(fmt='text')) def get_directory_content(self, subdir=''): """ @@ -319,7 +318,7 @@ def get_directory_content(self, subdir=''): the project's file root. """ inspect_dir = self.get_inspect_dir(subdir) - return ProjectFiles().get_project_directory_content(inspect_dir, subdir, self.file_display_url, self.file_url) + return self.files.get_project_directory_content(inspect_dir, subdir, self.file_display_url, self.file_url) def schema_org_resource_type(self): """ diff --git a/physionet-django/project/modelcomponents/publishedproject.py b/physionet-django/project/modelcomponents/publishedproject.py index ffc65a9ec0..b6c6f549dc 100644 --- a/physionet-django/project/modelcomponents/publishedproject.py +++ b/physionet-django/project/modelcomponents/publishedproject.py @@ -89,20 +89,20 @@ def project_file_root(self): This is the parent directory of the main and special file directories. """ - return ProjectFiles().get_project_file_root(self.slug, self.version, self.access_policy, PublishedProject) + return self.files.get_project_file_root(self.slug, self.version, self.access_policy, PublishedProject) def file_root(self): """ Root directory where the main user uploaded files are located """ - return ProjectFiles().get_file_root(self.slug, self.version, self.access_policy, PublishedProject) + return self.files.get_file_root(self.slug, self.version, self.access_policy, PublishedProject) def storage_used(self): """ Bytes of storage used by main files and compressed file if any """ - storage_used = ProjectFiles().published_project_storage_used(self) - zip_file_size = ProjectFiles().get_zip_file_size(self) + storage_used = self.files.published_project_storage_used(self) + zip_file_size = self.files.get_zip_file_size(self) return storage_used, zip_file_size @@ -134,7 +134,7 @@ def make_zip(self): """ Make a (new) zip file of the main files. """ - return ProjectFiles().make_zip(project=self) + return self.files.make_zip(project=self) def remove_zip(self): fname = self.zip_name(full=True) @@ -157,13 +157,13 @@ def make_checksum_file(self): """ Make the checksums file for the main files """ - return ProjectFiles().make_checksum_file(self) + return self.files.make_checksum_file(self) def remove_files(self): """ Remove files of this project """ - ProjectFiles().rm_dir(self.file_root(), remove_zip=self.remove_zip) + self.files.rm_dir(self.file_root(), remove_zip=self.remove_zip) self.set_storage_info() def deprecate_files(self, delete_files): diff --git a/physionet-django/project/modelcomponents/unpublishedproject.py b/physionet-django/project/modelcomponents/unpublishedproject.py index 055b274500..505f78f03f 100644 --- a/physionet-django/project/modelcomponents/unpublishedproject.py +++ b/physionet-django/project/modelcomponents/unpublishedproject.py @@ -7,7 +7,6 @@ from django.db import models from physionet.settings.base import StorageTypes from project.modelcomponents.metadata import Metadata -from project.projectfiles import ProjectFiles from project.utility import StorageInfo from project.validators import MAX_PROJECT_SLUG_LENGTH @@ -99,7 +98,7 @@ def has_wfdb(self): """ Whether the project has wfdb files. """ - return ProjectFiles().has_wfdb_files(self) + return self.files.has_wfdb_files(self) def content_modified(self): """ diff --git a/physionet-django/project/test_projectfiles.py b/physionet-django/project/test_projectfiles.py index a1eb017c54..74efb3a115 100644 --- a/physionet-django/project/test_projectfiles.py +++ b/physionet-django/project/test_projectfiles.py @@ -2,7 +2,7 @@ from django.test import override_settings from physionet.settings.base import StorageTypes -from project.projectfiles import ProjectFiles +from project.models import ActiveProject from project.projectfiles.gcs import GCSProjectFiles from project.projectfiles.local import LocalProjectFiles @@ -10,8 +10,10 @@ class TestProjectFiles(TestCase): @override_settings(STORAGE_TYPE=StorageTypes.LOCAL) def test_project_files_if_local_storage_type(self): - self.assertIsInstance(ProjectFiles(), LocalProjectFiles) + project = ActiveProject() + self.assertIsInstance(project.files, LocalProjectFiles) @override_settings(STORAGE_TYPE=StorageTypes.GCP) def test_project_files_if_google_cloud_storage_type(self): - self.assertIsInstance(ProjectFiles(), GCSProjectFiles) + project = ActiveProject() + self.assertIsInstance(project.files, GCSProjectFiles) diff --git a/physionet-django/project/views.py b/physionet-django/project/views.py index 22a1fde4a5..2d4688464a 100644 --- a/physionet-django/project/views.py +++ b/physionet-django/project/views.py @@ -1096,7 +1096,7 @@ def project_files(request, project_slug, subdir='', **kwargs): 'file_warning': file_warning, 'files_editable': files_editable, 'maintenance_message': maintenance_message, - 'is_lightwave_supported': ProjectFiles().is_lightwave_supported(), + 'is_lightwave_supported': project.files.is_lightwave_supported(), 'storage_type': settings.STORAGE_TYPE, }, ) @@ -1221,7 +1221,7 @@ def project_preview(request, project_slug, subdir='', **kwargs): 'platform_citations': platform_citations, 'parent_projects': parent_projects, 'has_passphrase': has_passphrase, - 'is_lightwave_supported': ProjectFiles().is_lightwave_supported(), + 'is_lightwave_supported': project.files.is_lightwave_supported(), 'show_platform_wide_citation': show_platform_wide_citation, 'main_platform_citation': main_platform_citation, }, @@ -1851,8 +1851,8 @@ def published_project(request, project_slug, version, subdir=''): 'data_access': data_access, 'messages': messages.get_messages(request), 'platform_citations': platform_citations, - 'is_lightwave_supported': ProjectFiles().is_lightwave_supported(), - 'is_wget_supported': ProjectFiles().is_wget_supported(), + 'is_lightwave_supported': project.files.is_lightwave_supported(), + 'is_wget_supported': project.files.is_wget_supported(), 'show_platform_wide_citation': show_platform_wide_citation, 'main_platform_citation': main_platform_citation, } From db722e67ca742808ff6ec6b34eb7a10bc5ae3bfa Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 18 Aug 2023 16:32:02 -0400 Subject: [PATCH 22/82] Remove an unused import of ProjectFiles. --- physionet-django/user/migrations/0036_training_2.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/physionet-django/user/migrations/0036_training_2.py b/physionet-django/user/migrations/0036_training_2.py index 56023894de..ee0ad6010b 100644 --- a/physionet-django/user/migrations/0036_training_2.py +++ b/physionet-django/user/migrations/0036_training_2.py @@ -5,8 +5,6 @@ from django.core.management import call_command from django.db import migrations -from project.projectfiles import ProjectFiles - def migrate_forward(apps, schema_editor): CredentialReview = apps.get_model('user', 'CredentialReview') From cfdc04d64dc286d424f7236e5ef5dce6c894ee16 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 18 Aug 2023 16:34:50 -0400 Subject: [PATCH 23/82] ProjectFiles: add a deprecation warning. This function should not be used because we don't want to assume every project uses the same storage backend. --- physionet-django/project/projectfiles/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/physionet-django/project/projectfiles/__init__.py b/physionet-django/project/projectfiles/__init__.py index 52b7806f98..80b425cd19 100644 --- a/physionet-django/project/projectfiles/__init__.py +++ b/physionet-django/project/projectfiles/__init__.py @@ -1,3 +1,5 @@ +import warnings + from django.conf import settings from physionet.settings.base import StorageTypes from project.projectfiles.gcs import GCSProjectFiles @@ -6,6 +8,8 @@ class ProjectFiles: def __new__(cls): + warnings.warn("use project.files instead of ProjectFiles()", + DeprecationWarning, stacklevel=2) if settings.STORAGE_TYPE == StorageTypes.LOCAL: return LocalProjectFiles() elif settings.STORAGE_TYPE == StorageTypes.GCP: From 4de10d8faeaeca7027244018a2dd55215b71e284 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Mon, 21 Aug 2023 16:18:17 -0400 Subject: [PATCH 24/82] search/content_list.html: remove is_lightwave_supported parameter. Whether or not LightWAVE is available depends on the project storage mechanism. To determine whether to show a link to LightWAVE for a given project, look at the files attribute for that particular project, rather than a global parameter. --- physionet-django/physionet/views.py | 2 -- physionet-django/search/templates/search/content_list.html | 2 +- physionet-django/search/views.py | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/physionet-django/physionet/views.py b/physionet-django/physionet/views.py index 83f5d295a0..d36d7f7346 100644 --- a/physionet-django/physionet/views.py +++ b/physionet-django/physionet/views.py @@ -13,7 +13,6 @@ from django.http import Http404, HttpResponse from django.shortcuts import render, get_object_or_404, redirect from notification.models import News -from project.projectfiles import ProjectFiles from physionet.models import FrontPageButton, Section, StaticPage from physionet.middleware.maintenance import allow_post_during_maintenance from project.models import AccessPolicy, DUA, License, ProjectType, PublishedProject @@ -38,7 +37,6 @@ def home(request): 'news_pieces': news_pieces, 'front_page_buttons': front_page_buttons, 'front_page_banner': front_page_banner, - 'is_lightwave_supported': ProjectFiles().is_lightwave_supported(), }, ) diff --git a/physionet-django/search/templates/search/content_list.html b/physionet-django/search/templates/search/content_list.html index 8f381d22a1..119d280a28 100644 --- a/physionet-django/search/templates/search/content_list.html +++ b/physionet-django/search/templates/search/content_list.html @@ -27,7 +27,7 @@

    Published: {{ published_project.publish_datetime|date }}. Version: {{ published_project.version }}

    - {% if is_lightwave_supported %} + {% if published_project.files.is_lightwave_supported %} {% can_view_project_files published_project request.user as user_can_view_project_files %} {% if published_project.has_wfdb and user_can_view_project_files %} Visualize waveforms diff --git a/physionet-django/search/views.py b/physionet-django/search/views.py index 4f54f24945..de990710b3 100644 --- a/physionet-django/search/views.py +++ b/physionet-django/search/views.py @@ -11,7 +11,6 @@ from django.templatetags.static import static from physionet.utility import paginate from project.models import PublishedProject, PublishedTopic -from project.projectfiles import ProjectFiles from search import forms @@ -223,7 +222,6 @@ def content_index(request, resource_type=None): 'form_type': form_type, 'form_topic': form_topic, 'querystring': querystring, - 'is_lightwave_supported': ProjectFiles().is_lightwave_supported(), }, ) From bdf257746cef15a2d78eaf73f65e204cb30a1b9d Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Mon, 21 Aug 2023 16:28:08 -0400 Subject: [PATCH 25/82] physionet.urls: avoid use of ProjectFiles. We don't want to have a global ProjectFiles function, and there is no purpose in checking the global ProjectFiles, in *addition* to a settings variable, to enable or disable lightwave URLs. --- physionet-django/physionet/urls.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/physionet-django/physionet/urls.py b/physionet-django/physionet/urls.py index 08172db5e8..2cabf5d9aa 100644 --- a/physionet-django/physionet/urls.py +++ b/physionet-django/physionet/urls.py @@ -9,7 +9,6 @@ from django.urls import path from physionet import views from physionet.settings.base import StorageTypes -from project.projectfiles import ProjectFiles from export.views import database_list @@ -85,7 +84,7 @@ name='database_list') ] -if settings.ENABLE_LIGHTWAVE and ProjectFiles().is_lightwave_supported: +if settings.ENABLE_LIGHTWAVE: urlpatterns.append(path('lightwave/', include('lightwave.urls'))) # backward compatibility for LightWAVE urlpatterns.append(path('cgi-bin/lightwave', From 5ca508f14d8b3620a71457aab7bc951615398089 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Mon, 21 Aug 2023 16:40:08 -0400 Subject: [PATCH 26/82] UnpublishedProject: remove class attribute FILE_ROOT. We don't want to have a global ProjectFiles function, and the storage locations of individual projects (whether these are real filesystem paths, or virtual "cloud paths") may vary. The FILE_ROOT class attribute is currently only used by the file_root() and bucket() instance methods - in both cases, the path should be dependent on the project's own 'files' attribute. --- physionet-django/project/modelcomponents/activeproject.py | 5 ++--- .../project/modelcomponents/archivedproject.py | 5 ++--- .../project/modelcomponents/unpublishedproject.py | 7 +++++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index b59a168509..aec6a30b1d 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -29,7 +29,6 @@ from project.modelcomponents.publishedproject import PublishedProject from project.modelcomponents.submission import CopyeditLog, EditLog, SubmissionInfo from project.modelcomponents.unpublishedproject import UnpublishedProject -from project.projectfiles import ProjectFiles from project.validators import validate_subdir LOGGER = logging.getLogger(__name__) @@ -150,9 +149,9 @@ class ActiveProject(Metadata, UnpublishedProject, SubmissionInfo): # Max number of active submitting projects a user is allowed to have MAX_SUBMITTING_PROJECTS = 10 INDIVIDUAL_FILE_SIZE_LIMIT = 10 * 1024**3 - # Where all the active project files are kept - FILE_ROOT = os.path.join(ProjectFiles().file_root, 'active-projects') + # Subdirectory (under self.files.file_root) where files are stored + FILE_STORAGE_SUBDIR = 'active-projects' REQUIRED_FIELDS = ( # 0: Database diff --git a/physionet-django/project/modelcomponents/archivedproject.py b/physionet-django/project/modelcomponents/archivedproject.py index af49997556..7c65d50204 100644 --- a/physionet-django/project/modelcomponents/archivedproject.py +++ b/physionet-django/project/modelcomponents/archivedproject.py @@ -3,7 +3,6 @@ from django.conf import settings from django.db import models -from project.projectfiles import ProjectFiles from project.modelcomponents.metadata import Metadata from project.modelcomponents.unpublishedproject import UnpublishedProject from project.modelcomponents.submission import SubmissionInfo @@ -20,8 +19,8 @@ class ArchivedProject(Metadata, UnpublishedProject, SubmissionInfo): archive_datetime = models.DateTimeField(auto_now_add=True) archive_reason = models.PositiveSmallIntegerField() - # Where all the archived project files are kept - FILE_ROOT = os.path.join(ProjectFiles().file_root, 'archived-projects') + # Subdirectory (under self.files.file_root) where files are stored + FILE_STORAGE_SUBDIR = 'archived-projects' class Meta: default_permissions = ('change',) diff --git a/physionet-django/project/modelcomponents/unpublishedproject.py b/physionet-django/project/modelcomponents/unpublishedproject.py index 505f78f03f..610c0802ce 100644 --- a/physionet-django/project/modelcomponents/unpublishedproject.py +++ b/physionet-django/project/modelcomponents/unpublishedproject.py @@ -51,13 +51,16 @@ def file_root(self): """ Root directory containing the project's files """ - return os.path.join(self.__class__.FILE_ROOT, self.slug) + return os.path.join(self.files.file_root, + self.FILE_STORAGE_SUBDIR, + self.slug) def bucket(self): """ Object storage bucket name """ - return self.__class__.FILE_ROOT + return os.path.join(self.files.file_root, + self.FILE_STORAGE_SUBDIR) def get_storage_info(self, force_calculate=True): """ From 1c9adecfa6a8b0bfdd3d250abd1d8b866b48a92b Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Mon, 21 Aug 2023 16:56:39 -0400 Subject: [PATCH 27/82] publishedproject.py: remove unused import of ProjectFiles. --- physionet-django/project/modelcomponents/publishedproject.py | 1 - 1 file changed, 1 deletion(-) diff --git a/physionet-django/project/modelcomponents/publishedproject.py b/physionet-django/project/modelcomponents/publishedproject.py index b6c6f549dc..9fd52b2ff3 100644 --- a/physionet-django/project/modelcomponents/publishedproject.py +++ b/physionet-django/project/modelcomponents/publishedproject.py @@ -14,7 +14,6 @@ from project.modelcomponents.metadata import Metadata, PublishedTopic from project.modelcomponents.submission import SubmissionInfo from project.models import AccessPolicy -from project.projectfiles import ProjectFiles from project.utility import StorageInfo, clear_directory, get_tree_size from project.validators import MAX_PROJECT_SLUG_LENGTH, validate_slug, validate_subdir from user.models import Training From c5449a60776bb32a7efe777b60be3a5514f3197f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 20:21:01 +0000 Subject: [PATCH 28/82] Bump uwsgi from 2.0.21 to 2.0.22 Bumps [uwsgi](https://github.com/unbit/uwsgi-docs) from 2.0.21 to 2.0.22. - [Commits](https://github.com/unbit/uwsgi-docs/commits) --- updated-dependencies: - dependency-name: uwsgi dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index d37c8a1455..13457880cc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -594,8 +594,8 @@ files = [ [package.dependencies] google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} proto-plus = [ - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -1337,12 +1337,12 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uwsgi" -version = "2.0.21" +version = "2.0.22" description = "The uWSGI server" optional = false python-versions = "*" files = [ - {file = "uwsgi-2.0.21.tar.gz", hash = "sha256:35a30d83791329429bc04fe44183ce4ab512fcf6968070a7bfba42fc5a0552a9"}, + {file = "uwsgi-2.0.22.tar.gz", hash = "sha256:4cc4727258671ac5fa17ab422155e9aaef8a2008ebb86e4404b66deaae965db2"}, ] [[package]] @@ -1453,4 +1453,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "561e8a00c19a83f3669ce71ca8f61eb986a5cb4c9e27b65a6d21bc6456d34720" +content-hash = "1c680953888a101ede203b84ea510fdb24cd5f1020e668881919f06b59b94cca" diff --git a/pyproject.toml b/pyproject.toml index 49571fe3b1..fafa8f8e48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ google-cloud-storage = "^1.41.1" html2text = "^2018.1.9" Pillow = "^9.3.0" python-decouple = "^3.1" -uWSGI = "2.0.21" +uWSGI = "2.0.22" pyOpenSSL = "^23.1.1" google-api-python-client = "^1.7.9" psycopg2 = "2.9.5" From a19d5522de0e5cc92eaf12655071fb3cecb45d4a Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Fri, 25 Aug 2023 12:38:08 -0400 Subject: [PATCH 29/82] Bump uwsgi from 2.0.21 to 2.0.22 --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 12936a0837..7653dbc6da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -395,7 +395,7 @@ pillow==9.3.0 ; python_version >= "3.9" and python_version < "4.0" \ proto-plus==1.22.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:0e8cda3d5a634d9895b75c573c9352c16486cb75deb0e078b5fda34db4243165 \ --hash=sha256:de34e52d6c9c6fcd704192f09767cb561bb4ee64e70eede20b0834d841f0be4d -protobuf==3.20.3 ; python_version < "4.0" and python_version >= "3.9" \ +protobuf==3.20.3 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ @@ -492,8 +492,8 @@ uritemplate==3.0.1 ; python_version >= "3.9" and python_version < "4.0" \ urllib3==1.26.15 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305 \ --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 -uwsgi==2.0.21 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:35a30d83791329429bc04fe44183ce4ab512fcf6968070a7bfba42fc5a0552a9 +uwsgi==2.0.22 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:4cc4727258671ac5fa17ab422155e9aaef8a2008ebb86e4404b66deaae965db2 webencodings==0.5.1 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 From c9b6ce2f5b97170b6575e0eb68fb0a9e66febb0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:58:07 +0000 Subject: [PATCH 30/82] Bump grpcio from 1.51.1 to 1.53.0 Bumps [grpcio](https://github.com/grpc/grpc) from 1.51.1 to 1.53.0. - [Release notes](https://github.com/grpc/grpc/releases) - [Changelog](https://github.com/grpc/grpc/blob/master/doc/grpc_release_schedule.md) - [Commits](https://github.com/grpc/grpc/compare/v1.51.1...v1.53.0) --- updated-dependencies: - dependency-name: grpcio dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 94 ++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/poetry.lock b/poetry.lock index 13457880cc..cd8c1f3332 100644 --- a/poetry.lock +++ b/poetry.lock @@ -681,60 +681,60 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] [[package]] name = "grpcio" -version = "1.51.1" +version = "1.53.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.7" files = [ - {file = "grpcio-1.51.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:cc2bece1737b44d878cc1510ea04469a8073dbbcdd762175168937ae4742dfb3"}, - {file = "grpcio-1.51.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:e223a9793522680beae44671b9ed8f6d25bbe5ddf8887e66aebad5e0686049ef"}, - {file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:24ac1154c4b2ab4a0c5326a76161547e70664cd2c39ba75f00fc8a2170964ea2"}, - {file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4ef09f8997c4be5f3504cefa6b5c6cc3cf648274ce3cede84d4342a35d76db6"}, - {file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0b77e992c64880e6efbe0086fe54dfc0bbd56f72a92d9e48264dcd2a3db98"}, - {file = "grpcio-1.51.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:eacad297ea60c72dd280d3353d93fb1dcca952ec11de6bb3c49d12a572ba31dd"}, - {file = "grpcio-1.51.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:16c71740640ba3a882f50b01bf58154681d44b51f09a5728180a8fdc66c67bd5"}, - {file = "grpcio-1.51.1-cp310-cp310-win32.whl", hash = "sha256:29cb97d41a4ead83b7bcad23bdb25bdd170b1e2cba16db6d3acbb090bc2de43c"}, - {file = "grpcio-1.51.1-cp310-cp310-win_amd64.whl", hash = "sha256:9ff42c5620b4e4530609e11afefa4a62ca91fa0abb045a8957e509ef84e54d30"}, - {file = "grpcio-1.51.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:bc59f7ba87972ab236f8669d8ca7400f02a0eadf273ca00e02af64d588046f02"}, - {file = "grpcio-1.51.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3c2b3842dcf870912da31a503454a33a697392f60c5e2697c91d133130c2c85d"}, - {file = "grpcio-1.51.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b011674090594f1f3245960ced7386f6af35485a38901f8afee8ad01541dbd"}, - {file = "grpcio-1.51.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d680356a975d9c66a678eb2dde192d5dc427a7994fb977363634e781614f7c"}, - {file = "grpcio-1.51.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:094e64236253590d9d4075665c77b329d707b6fca864dd62b144255e199b4f87"}, - {file = "grpcio-1.51.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:257478300735ce3c98d65a930bbda3db172bd4e00968ba743e6a1154ea6edf10"}, - {file = "grpcio-1.51.1-cp311-cp311-win32.whl", hash = "sha256:5a6ebcdef0ef12005d56d38be30f5156d1cb3373b52e96f147f4a24b0ddb3a9d"}, - {file = "grpcio-1.51.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f9b0023c2c92bebd1be72cdfca23004ea748be1813a66d684d49d67d836adde"}, - {file = "grpcio-1.51.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:cd3baccea2bc5c38aeb14e5b00167bd4e2373a373a5e4d8d850bd193edad150c"}, - {file = "grpcio-1.51.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:17ec9b13cec4a286b9e606b48191e560ca2f3bbdf3986f91e480a95d1582e1a7"}, - {file = "grpcio-1.51.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:fbdbe9a849854fe484c00823f45b7baab159bdd4a46075302281998cb8719df5"}, - {file = "grpcio-1.51.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31bb6bc7ff145e2771c9baf612f4b9ebbc9605ccdc5f3ff3d5553de7fc0e0d79"}, - {file = "grpcio-1.51.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e473525c28251558337b5c1ad3fa969511e42304524a4e404065e165b084c9e4"}, - {file = "grpcio-1.51.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f0b89967ee11f2b654c23b27086d88ad7bf08c0b3c2a280362f28c3698b2896"}, - {file = "grpcio-1.51.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7942b32a291421460d6a07883033e392167d30724aa84987e6956cd15f1a21b9"}, - {file = "grpcio-1.51.1-cp37-cp37m-win32.whl", hash = "sha256:f96ace1540223f26fbe7c4ebbf8a98e3929a6aa0290c8033d12526847b291c0f"}, - {file = "grpcio-1.51.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f1fec3abaf274cdb85bf3878167cfde5ad4a4d97c68421afda95174de85ba813"}, - {file = "grpcio-1.51.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:0e1a9e1b4a23808f1132aa35f968cd8e659f60af3ffd6fb00bcf9a65e7db279f"}, - {file = "grpcio-1.51.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:6df3b63538c362312bc5fa95fb965069c65c3ea91d7ce78ad9c47cab57226f54"}, - {file = "grpcio-1.51.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:172405ca6bdfedd6054c74c62085946e45ad4d9cec9f3c42b4c9a02546c4c7e9"}, - {file = "grpcio-1.51.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506b9b7a4cede87d7219bfb31014d7b471cfc77157da9e820a737ec1ea4b0663"}, - {file = "grpcio-1.51.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb93051331acbb75b49a2a0fd9239c6ba9528f6bdc1dd400ad1cb66cf864292"}, - {file = "grpcio-1.51.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5dca372268c6ab6372d37d6b9f9343e7e5b4bc09779f819f9470cd88b2ece3c3"}, - {file = "grpcio-1.51.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:471d39d3370ca923a316d49c8aac66356cea708a11e647e3bdc3d0b5de4f0a40"}, - {file = "grpcio-1.51.1-cp38-cp38-win32.whl", hash = "sha256:75e29a90dc319f0ad4d87ba6d20083615a00d8276b51512e04ad7452b5c23b04"}, - {file = "grpcio-1.51.1-cp38-cp38-win_amd64.whl", hash = "sha256:f1158bccbb919da42544a4d3af5d9296a3358539ffa01018307337365a9a0c64"}, - {file = "grpcio-1.51.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:59dffade859f157bcc55243714d57b286da6ae16469bf1ac0614d281b5f49b67"}, - {file = "grpcio-1.51.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:dad6533411d033b77f5369eafe87af8583178efd4039c41d7515d3336c53b4f1"}, - {file = "grpcio-1.51.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:4c4423ea38a7825b8fed8934d6d9aeebdf646c97e3c608c3b0bcf23616f33877"}, - {file = "grpcio-1.51.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0dc5354e38e5adf2498312f7241b14c7ce3484eefa0082db4297189dcbe272e6"}, - {file = "grpcio-1.51.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d67983189e2e45550eac194d6234fc38b8c3b5396c153821f2d906ed46e0ce"}, - {file = "grpcio-1.51.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:538d981818e49b6ed1e9c8d5e5adf29f71c4e334e7d459bf47e9b7abb3c30e09"}, - {file = "grpcio-1.51.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9235dcd5144a83f9ca6f431bd0eccc46b90e2c22fe27b7f7d77cabb2fb515595"}, - {file = "grpcio-1.51.1-cp39-cp39-win32.whl", hash = "sha256:aacb54f7789ede5cbf1d007637f792d3e87f1c9841f57dd51abf89337d1b8472"}, - {file = "grpcio-1.51.1-cp39-cp39-win_amd64.whl", hash = "sha256:2b170eaf51518275c9b6b22ccb59450537c5a8555326fd96ff7391b5dd75303c"}, - {file = "grpcio-1.51.1.tar.gz", hash = "sha256:e6dfc2b6567b1c261739b43d9c59d201c1b89e017afd9e684d85aa7a186c9f7a"}, + {file = "grpcio-1.53.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:752d2949b40e12e6ad3ed8cc552a65b54d226504f6b1fb67cab2ccee502cc06f"}, + {file = "grpcio-1.53.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:8a48fd3a7222be226bb86b7b413ad248f17f3101a524018cdc4562eeae1eb2a3"}, + {file = "grpcio-1.53.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f3e837d29f0e1b9d6e7b29d569e2e9b0da61889e41879832ea15569c251c303a"}, + {file = "grpcio-1.53.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef7d30242409c3aa5839b501e877e453a2c8d3759ca8230dd5a21cda029f046"}, + {file = "grpcio-1.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6f90698b5d1c5dd7b3236cd1fa959d7b80e17923f918d5be020b65f1c78b173"}, + {file = "grpcio-1.53.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a96c3c7f564b263c5d7c0e49a337166c8611e89c4c919f66dba7b9a84abad137"}, + {file = "grpcio-1.53.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ee81349411648d1abc94095c68cd25e3c2812e4e0367f9a9355be1e804a5135c"}, + {file = "grpcio-1.53.0-cp310-cp310-win32.whl", hash = "sha256:fdc6191587de410a184550d4143e2b24a14df495c86ca15e59508710681690ac"}, + {file = "grpcio-1.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:658ffe1e39171be00490db5bd3b966f79634ac4215a1eb9a85c6cd6783bf7f6e"}, + {file = "grpcio-1.53.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:1b172e6d497191940c4b8d75b53de82dc252e15b61de2951d577ec5b43316b29"}, + {file = "grpcio-1.53.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:82434ba3a5935e47908bc861ce1ebc43c2edfc1001d235d6e31e5d3ed55815f7"}, + {file = "grpcio-1.53.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:1c734a2d4843e4e14ececf5600c3c4750990ec319e1299db7e4f0d02c25c1467"}, + {file = "grpcio-1.53.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a2ead3de3b2d53119d473aa2f224030257ef33af1e4ddabd4afee1dea5f04c"}, + {file = "grpcio-1.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34d6e905f071f9b945cabbcc776e2055de1fdb59cd13683d9aa0a8f265b5bf9"}, + {file = "grpcio-1.53.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eaf8e3b97caaf9415227a3c6ca5aa8d800fecadd526538d2bf8f11af783f1550"}, + {file = "grpcio-1.53.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:da95778d37be8e4e9afca771a83424f892296f5dfb2a100eda2571a1d8bbc0dc"}, + {file = "grpcio-1.53.0-cp311-cp311-win32.whl", hash = "sha256:e4f513d63df6336fd84b74b701f17d1bb3b64e9d78a6ed5b5e8a198bbbe8bbfa"}, + {file = "grpcio-1.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:ddb2511fbbb440ed9e5c9a4b9b870f2ed649b7715859fd6f2ebc585ee85c0364"}, + {file = "grpcio-1.53.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:2a912397eb8d23c177d6d64e3c8bc46b8a1c7680b090d9f13a640b104aaec77c"}, + {file = "grpcio-1.53.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:55930c56b8f5b347d6c8c609cc341949a97e176c90f5cbb01d148d778f3bbd23"}, + {file = "grpcio-1.53.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:6601d812105583948ab9c6e403a7e2dba6e387cc678c010e74f2d6d589d1d1b3"}, + {file = "grpcio-1.53.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c705e0c21acb0e8478a00e7e773ad0ecdb34bd0e4adc282d3d2f51ba3961aac7"}, + {file = "grpcio-1.53.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba074af9ca268ad7b05d3fc2b920b5fb3c083da94ab63637aaf67f4f71ecb755"}, + {file = "grpcio-1.53.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:14817de09317dd7d3fbc8272864288320739973ef0f4b56bf2c0032349da8cdf"}, + {file = "grpcio-1.53.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c7ad9fbedb93f331c2e9054e202e95cf825b885811f1bcbbdfdc301e451442db"}, + {file = "grpcio-1.53.0-cp37-cp37m-win_amd64.whl", hash = "sha256:dad5b302a4c21c604d88a5d441973f320134e6ff6a84ecef9c1139e5ffd466f6"}, + {file = "grpcio-1.53.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fa8eaac75d3107e3f5465f2c9e3bbd13db21790c6e45b7de1756eba16b050aca"}, + {file = "grpcio-1.53.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:104a2210edd3776c38448b4f76c2f16e527adafbde171fc72a8a32976c20abc7"}, + {file = "grpcio-1.53.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:dbc1ba968639c1d23476f75c356e549e7bbf2d8d6688717dcab5290e88e8482b"}, + {file = "grpcio-1.53.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95952d3fe795b06af29bb8ec7bbf3342cdd867fc17b77cc25e6733d23fa6c519"}, + {file = "grpcio-1.53.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f144a790f14c51b8a8e591eb5af40507ffee45ea6b818c2482f0457fec2e1a2e"}, + {file = "grpcio-1.53.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0698c094688a2dd4c7c2f2c0e3e142cac439a64d1cef6904c97f6cde38ba422f"}, + {file = "grpcio-1.53.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6b6d60b0958be711bab047e9f4df5dbbc40367955f8651232bfdcdd21450b9ab"}, + {file = "grpcio-1.53.0-cp38-cp38-win32.whl", hash = "sha256:1948539ce78805d4e6256ab0e048ec793956d54787dc9d6777df71c1d19c7f81"}, + {file = "grpcio-1.53.0-cp38-cp38-win_amd64.whl", hash = "sha256:df9ba1183b3f649210788cf80c239041dddcb375d6142d8bccafcfdf549522cd"}, + {file = "grpcio-1.53.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:19caa5b7282a89b799e63776ff602bb39604f7ca98db6df27e2de06756ae86c3"}, + {file = "grpcio-1.53.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b5bd026ac928c96cc23149e6ef79183125542062eb6d1ccec34c0a37e02255e7"}, + {file = "grpcio-1.53.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:7dc8584ca6c015ad82e186e82f4c0fe977394588f66b8ecfc4ec873285314619"}, + {file = "grpcio-1.53.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2eddaae8af625e45b5c8500dcca1043264d751a6872cde2eda5022df8a336959"}, + {file = "grpcio-1.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fb6f3d7824696c1c9f2ad36ddb080ba5a86f2d929ef712d511b4d9972d3d27"}, + {file = "grpcio-1.53.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8270d1dc2c98ab57e6dbf36fa187db8df4c036f04a398e5d5e25b4e01a766d70"}, + {file = "grpcio-1.53.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:976a7f24eb213e8429cab78d5e120500dfcdeb01041f1f5a77b17b9101902615"}, + {file = "grpcio-1.53.0-cp39-cp39-win32.whl", hash = "sha256:9c84a481451e7174f3a764a44150f93b041ab51045aa33d7b5b68b6979114e48"}, + {file = "grpcio-1.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:6beb84f83360ff29a3654f43f251ec11b809dcb5524b698d711550243debd289"}, + {file = "grpcio-1.53.0.tar.gz", hash = "sha256:a4952899b4931a6ba12951f9a141ef3e74ff8a6ec9aa2dc602afa40f63595e33"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.51.1)"] +protobuf = ["grpcio-tools (>=1.53.0)"] [[package]] name = "grpcio-status" From d0401c101d34f49fd17082198171ec396365ed3f Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Fri, 25 Aug 2023 12:59:56 -0400 Subject: [PATCH 31/82] Bump grpcio from 1.51.1 to 1.53.0 --- requirements.txt | 92 ++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7653dbc6da..de39cc6030 100644 --- a/requirements.txt +++ b/requirements.txt @@ -251,52 +251,52 @@ googleapis-common-protos==1.58.0 ; python_version >= "3.9" and python_version < grpcio-status==1.48.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:2c33bbdbe20188b2953f46f31af669263b6ee2a9b2d38fa0d36ee091532e21bf \ --hash=sha256:53695f45da07437b7c344ee4ef60d370fd2850179f5a28bb26d8e2aa1102ec11 -grpcio==1.51.1 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:094e64236253590d9d4075665c77b329d707b6fca864dd62b144255e199b4f87 \ - --hash=sha256:0dc5354e38e5adf2498312f7241b14c7ce3484eefa0082db4297189dcbe272e6 \ - --hash=sha256:0e1a9e1b4a23808f1132aa35f968cd8e659f60af3ffd6fb00bcf9a65e7db279f \ - --hash=sha256:0fb93051331acbb75b49a2a0fd9239c6ba9528f6bdc1dd400ad1cb66cf864292 \ - --hash=sha256:16c71740640ba3a882f50b01bf58154681d44b51f09a5728180a8fdc66c67bd5 \ - --hash=sha256:172405ca6bdfedd6054c74c62085946e45ad4d9cec9f3c42b4c9a02546c4c7e9 \ - --hash=sha256:17ec9b13cec4a286b9e606b48191e560ca2f3bbdf3986f91e480a95d1582e1a7 \ - --hash=sha256:22b011674090594f1f3245960ced7386f6af35485a38901f8afee8ad01541dbd \ - --hash=sha256:24ac1154c4b2ab4a0c5326a76161547e70664cd2c39ba75f00fc8a2170964ea2 \ - --hash=sha256:257478300735ce3c98d65a930bbda3db172bd4e00968ba743e6a1154ea6edf10 \ - --hash=sha256:29cb97d41a4ead83b7bcad23bdb25bdd170b1e2cba16db6d3acbb090bc2de43c \ - --hash=sha256:2b170eaf51518275c9b6b22ccb59450537c5a8555326fd96ff7391b5dd75303c \ - --hash=sha256:31bb6bc7ff145e2771c9baf612f4b9ebbc9605ccdc5f3ff3d5553de7fc0e0d79 \ - --hash=sha256:3c2b3842dcf870912da31a503454a33a697392f60c5e2697c91d133130c2c85d \ - --hash=sha256:3f9b0023c2c92bebd1be72cdfca23004ea748be1813a66d684d49d67d836adde \ - --hash=sha256:471d39d3370ca923a316d49c8aac66356cea708a11e647e3bdc3d0b5de4f0a40 \ - --hash=sha256:49d680356a975d9c66a678eb2dde192d5dc427a7994fb977363634e781614f7c \ - --hash=sha256:4c4423ea38a7825b8fed8934d6d9aeebdf646c97e3c608c3b0bcf23616f33877 \ - --hash=sha256:506b9b7a4cede87d7219bfb31014d7b471cfc77157da9e820a737ec1ea4b0663 \ - --hash=sha256:538d981818e49b6ed1e9c8d5e5adf29f71c4e334e7d459bf47e9b7abb3c30e09 \ - --hash=sha256:59dffade859f157bcc55243714d57b286da6ae16469bf1ac0614d281b5f49b67 \ - --hash=sha256:5a6ebcdef0ef12005d56d38be30f5156d1cb3373b52e96f147f4a24b0ddb3a9d \ - --hash=sha256:5dca372268c6ab6372d37d6b9f9343e7e5b4bc09779f819f9470cd88b2ece3c3 \ - --hash=sha256:6df3b63538c362312bc5fa95fb965069c65c3ea91d7ce78ad9c47cab57226f54 \ - --hash=sha256:6f0b89967ee11f2b654c23b27086d88ad7bf08c0b3c2a280362f28c3698b2896 \ - --hash=sha256:75e29a90dc319f0ad4d87ba6d20083615a00d8276b51512e04ad7452b5c23b04 \ - --hash=sha256:7942b32a291421460d6a07883033e392167d30724aa84987e6956cd15f1a21b9 \ - --hash=sha256:9235dcd5144a83f9ca6f431bd0eccc46b90e2c22fe27b7f7d77cabb2fb515595 \ - --hash=sha256:97d67983189e2e45550eac194d6234fc38b8c3b5396c153821f2d906ed46e0ce \ - --hash=sha256:9ff42c5620b4e4530609e11afefa4a62ca91fa0abb045a8957e509ef84e54d30 \ - --hash=sha256:a8a0b77e992c64880e6efbe0086fe54dfc0bbd56f72a92d9e48264dcd2a3db98 \ - --hash=sha256:aacb54f7789ede5cbf1d007637f792d3e87f1c9841f57dd51abf89337d1b8472 \ - --hash=sha256:bc59f7ba87972ab236f8669d8ca7400f02a0eadf273ca00e02af64d588046f02 \ - --hash=sha256:cc2bece1737b44d878cc1510ea04469a8073dbbcdd762175168937ae4742dfb3 \ - --hash=sha256:cd3baccea2bc5c38aeb14e5b00167bd4e2373a373a5e4d8d850bd193edad150c \ - --hash=sha256:dad6533411d033b77f5369eafe87af8583178efd4039c41d7515d3336c53b4f1 \ - --hash=sha256:e223a9793522680beae44671b9ed8f6d25bbe5ddf8887e66aebad5e0686049ef \ - --hash=sha256:e473525c28251558337b5c1ad3fa969511e42304524a4e404065e165b084c9e4 \ - --hash=sha256:e4ef09f8997c4be5f3504cefa6b5c6cc3cf648274ce3cede84d4342a35d76db6 \ - --hash=sha256:e6dfc2b6567b1c261739b43d9c59d201c1b89e017afd9e684d85aa7a186c9f7a \ - --hash=sha256:eacad297ea60c72dd280d3353d93fb1dcca952ec11de6bb3c49d12a572ba31dd \ - --hash=sha256:f1158bccbb919da42544a4d3af5d9296a3358539ffa01018307337365a9a0c64 \ - --hash=sha256:f1fec3abaf274cdb85bf3878167cfde5ad4a4d97c68421afda95174de85ba813 \ - --hash=sha256:f96ace1540223f26fbe7c4ebbf8a98e3929a6aa0290c8033d12526847b291c0f \ - --hash=sha256:fbdbe9a849854fe484c00823f45b7baab159bdd4a46075302281998cb8719df5 +grpcio==1.53.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:0698c094688a2dd4c7c2f2c0e3e142cac439a64d1cef6904c97f6cde38ba422f \ + --hash=sha256:104a2210edd3776c38448b4f76c2f16e527adafbde171fc72a8a32976c20abc7 \ + --hash=sha256:14817de09317dd7d3fbc8272864288320739973ef0f4b56bf2c0032349da8cdf \ + --hash=sha256:1948539ce78805d4e6256ab0e048ec793956d54787dc9d6777df71c1d19c7f81 \ + --hash=sha256:19caa5b7282a89b799e63776ff602bb39604f7ca98db6df27e2de06756ae86c3 \ + --hash=sha256:1b172e6d497191940c4b8d75b53de82dc252e15b61de2951d577ec5b43316b29 \ + --hash=sha256:1c734a2d4843e4e14ececf5600c3c4750990ec319e1299db7e4f0d02c25c1467 \ + --hash=sha256:2a912397eb8d23c177d6d64e3c8bc46b8a1c7680b090d9f13a640b104aaec77c \ + --hash=sha256:2eddaae8af625e45b5c8500dcca1043264d751a6872cde2eda5022df8a336959 \ + --hash=sha256:55930c56b8f5b347d6c8c609cc341949a97e176c90f5cbb01d148d778f3bbd23 \ + --hash=sha256:658ffe1e39171be00490db5bd3b966f79634ac4215a1eb9a85c6cd6783bf7f6e \ + --hash=sha256:6601d812105583948ab9c6e403a7e2dba6e387cc678c010e74f2d6d589d1d1b3 \ + --hash=sha256:6b6d60b0958be711bab047e9f4df5dbbc40367955f8651232bfdcdd21450b9ab \ + --hash=sha256:6beb84f83360ff29a3654f43f251ec11b809dcb5524b698d711550243debd289 \ + --hash=sha256:752d2949b40e12e6ad3ed8cc552a65b54d226504f6b1fb67cab2ccee502cc06f \ + --hash=sha256:7dc8584ca6c015ad82e186e82f4c0fe977394588f66b8ecfc4ec873285314619 \ + --hash=sha256:82434ba3a5935e47908bc861ce1ebc43c2edfc1001d235d6e31e5d3ed55815f7 \ + --hash=sha256:8270d1dc2c98ab57e6dbf36fa187db8df4c036f04a398e5d5e25b4e01a766d70 \ + --hash=sha256:8a48fd3a7222be226bb86b7b413ad248f17f3101a524018cdc4562eeae1eb2a3 \ + --hash=sha256:95952d3fe795b06af29bb8ec7bbf3342cdd867fc17b77cc25e6733d23fa6c519 \ + --hash=sha256:976a7f24eb213e8429cab78d5e120500dfcdeb01041f1f5a77b17b9101902615 \ + --hash=sha256:9c84a481451e7174f3a764a44150f93b041ab51045aa33d7b5b68b6979114e48 \ + --hash=sha256:a34d6e905f071f9b945cabbcc776e2055de1fdb59cd13683d9aa0a8f265b5bf9 \ + --hash=sha256:a4952899b4931a6ba12951f9a141ef3e74ff8a6ec9aa2dc602afa40f63595e33 \ + --hash=sha256:a96c3c7f564b263c5d7c0e49a337166c8611e89c4c919f66dba7b9a84abad137 \ + --hash=sha256:aef7d30242409c3aa5839b501e877e453a2c8d3759ca8230dd5a21cda029f046 \ + --hash=sha256:b5bd026ac928c96cc23149e6ef79183125542062eb6d1ccec34c0a37e02255e7 \ + --hash=sha256:b6a2ead3de3b2d53119d473aa2f224030257ef33af1e4ddabd4afee1dea5f04c \ + --hash=sha256:ba074af9ca268ad7b05d3fc2b920b5fb3c083da94ab63637aaf67f4f71ecb755 \ + --hash=sha256:c5fb6f3d7824696c1c9f2ad36ddb080ba5a86f2d929ef712d511b4d9972d3d27 \ + --hash=sha256:c705e0c21acb0e8478a00e7e773ad0ecdb34bd0e4adc282d3d2f51ba3961aac7 \ + --hash=sha256:c7ad9fbedb93f331c2e9054e202e95cf825b885811f1bcbbdfdc301e451442db \ + --hash=sha256:da95778d37be8e4e9afca771a83424f892296f5dfb2a100eda2571a1d8bbc0dc \ + --hash=sha256:dad5b302a4c21c604d88a5d441973f320134e6ff6a84ecef9c1139e5ffd466f6 \ + --hash=sha256:dbc1ba968639c1d23476f75c356e549e7bbf2d8d6688717dcab5290e88e8482b \ + --hash=sha256:ddb2511fbbb440ed9e5c9a4b9b870f2ed649b7715859fd6f2ebc585ee85c0364 \ + --hash=sha256:df9ba1183b3f649210788cf80c239041dddcb375d6142d8bccafcfdf549522cd \ + --hash=sha256:e4f513d63df6336fd84b74b701f17d1bb3b64e9d78a6ed5b5e8a198bbbe8bbfa \ + --hash=sha256:e6f90698b5d1c5dd7b3236cd1fa959d7b80e17923f918d5be020b65f1c78b173 \ + --hash=sha256:eaf8e3b97caaf9415227a3c6ca5aa8d800fecadd526538d2bf8f11af783f1550 \ + --hash=sha256:ee81349411648d1abc94095c68cd25e3c2812e4e0367f9a9355be1e804a5135c \ + --hash=sha256:f144a790f14c51b8a8e591eb5af40507ffee45ea6b818c2482f0457fec2e1a2e \ + --hash=sha256:f3e837d29f0e1b9d6e7b29d569e2e9b0da61889e41879832ea15569c251c303a \ + --hash=sha256:fa8eaac75d3107e3f5465f2c9e3bbd13db21790c6e45b7de1756eba16b050aca \ + --hash=sha256:fdc6191587de410a184550d4143e2b24a14df495c86ca15e59508710681690ac hdn-research-environment==1.4.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:296a336847c1078c149baa6773654a2e2a5b16cc1b03ca25f6f19f8606980e5b html2text==2018.1.9 ; python_version >= "3.9" and python_version < "4.0" \ From c149a662f763cb2cb00c03ad40fdd7571e1283af Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Tue, 29 Aug 2023 14:51:27 -0400 Subject: [PATCH 32/82] Do not use the ActiveProject.version_order field. The version_order field of the ActiveProject model (as well as ArchivedProject) does not mean what it suggests. At present, this field is only set when the ActiveProject is created, and its only significance is that it is zero for a new project (in which case is_new_version is False) or non-zero for a new version (in which case is_new_version is True). (This is in contrast to the version_order field of *PublishedProject*, which indicates the version sorting order of the published versions. There is no real relationship between ActiveProject's version_order and PublishedProject's version_order.) The ActiveProject version_order field is therefore confusing and redundant - everywhere it is currently used, is_new_version can and should be used instead. (Note that has_other_versions is not an ActiveProject field.) --- physionet-django/console/forms.py | 2 +- .../console/templates/console/copyedit_submission.html | 2 +- .../console/templates/console/submission_info_card.html | 2 +- physionet-django/console/views.py | 2 +- physionet-django/project/modelcomponents/activeproject.py | 4 ++-- .../project/modelcomponents/unpublishedproject.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index 7ba479339c..e821e095b1 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -313,7 +313,7 @@ def __init__(self, project, *args, **kwargs): super().__init__(*args, **kwargs) self.project = project # No option to set slug if publishing new version - if self.project.version_order: + if self.project.is_new_version: del(self.fields['slug']) else: self.fields['slug'].initial = project.slug diff --git a/physionet-django/console/templates/console/copyedit_submission.html b/physionet-django/console/templates/console/copyedit_submission.html index 419df43f76..afbf06822c 100644 --- a/physionet-django/console/templates/console/copyedit_submission.html +++ b/physionet-django/console/templates/console/copyedit_submission.html @@ -94,7 +94,7 @@

    Discovery

    -{% if project.has_other_versions or project.version_order %} +{% if project.is_new_version %} {% endif %} diff --git a/physionet-django/console/templates/console/submission_info_card.html b/physionet-django/console/templates/console/submission_info_card.html index 7ea3d0ef9e..561899bea2 100644 --- a/physionet-django/console/templates/console/submission_info_card.html +++ b/physionet-django/console/templates/console/submission_info_card.html @@ -40,7 +40,7 @@

    {{ project.title }}

    Created: {{ project.creation_datetime|date }}. Submitted: {{ project.submission_datetime|date }}.
    Storage Used: {{ storage_info.readable_used }} / {{ storage_info.readable_allowance }}
    Version: {{ project.version }} - {% if project.version_order %}
    Latest Published Version: {{ latest_version.version }}{% endif %} + {% if project.is_new_version %}
    Latest Published Version: {{ latest_version.version }}{% endif %} {% if project.latest_reminder %}
    Latest reminder email sent on: {{ project.latest_reminder }} {% endif %} diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index bb34af6c88..9d7dfaef5c 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -625,7 +625,7 @@ def publish_submission(request, project_slug, *args, **kwargs): if request.method == 'POST': publish_form = forms.PublishForm(project=project, data=request.POST) if project.is_publishable() and publish_form.is_valid(): - if project.version_order: + if project.is_new_version: slug = project.get_previous_slug() else: slug = publish_form.cleaned_data['slug'] diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 9cec57f4db..65964671b6 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -568,7 +568,7 @@ def publish(self, slug=None, make_zip=True, title=None): with transaction.atomic(): # If this is a new version, previous fields need to be updated # and slug needs to be carried over - if self.version_order: + if self.is_new_version: previous_published_projects = self.core_project.publishedprojects.all() slug = previous_published_projects.first().slug @@ -590,7 +590,7 @@ def publish(self, slug=None, make_zip=True, title=None): published_project.save() # If this is a new version, all version fields have to be updated - if self.version_order > 0: + if self.is_new_version: published_project.set_version_order() # Same content, different objects. diff --git a/physionet-django/project/modelcomponents/unpublishedproject.py b/physionet-django/project/modelcomponents/unpublishedproject.py index 055b274500..bf4a87798a 100644 --- a/physionet-django/project/modelcomponents/unpublishedproject.py +++ b/physionet-django/project/modelcomponents/unpublishedproject.py @@ -81,7 +81,7 @@ def get_previous_slug(self): If this is a new version of a project, get the slug of the published versions. """ - if self.version_order: + if self.is_new_version: return self.core_project.publishedprojects.all().get( version_order=0).slug else: From 417f0ef494af9740a2281a9e27de9d98b49059fb Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Tue, 29 Aug 2023 15:21:58 -0400 Subject: [PATCH 33/82] Remove version_order field from UnpublishedProject. The version_order field of the ActiveProject and ArchivedProject models is confusing (it isn't directly related to the published project version order) and redundant (everywhere it was previously used, is_new_version can and should be used instead.) Remove this field from ActiveProject and ArchivedProject (move it from the SubmissionInfo class to the PublishedProject class) accordingly. --- .../project/fixtures/demo-project.json | 7 ---- physionet-django/project/forms.py | 1 - ...0069_unpublishedproject_version_order_1.py | 40 +++++++++++++++++++ ...0070_unpublishedproject_version_order_2.py | 22 ++++++++++ .../modelcomponents/publishedproject.py | 3 ++ .../project/modelcomponents/submission.py | 3 -- 6 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 physionet-django/project/migrations/0069_unpublishedproject_version_order_1.py create mode 100644 physionet-django/project/migrations/0070_unpublishedproject_version_order_2.py diff --git a/physionet-django/project/fixtures/demo-project.json b/physionet-django/project/fixtures/demo-project.json index dd9eee62bd..856c58ba33 100644 --- a/physionet-django/project/fixtures/demo-project.json +++ b/physionet-django/project/fixtures/demo-project.json @@ -946,7 +946,6 @@ "copyedit_completion_datetime": "2020-04-21T16:07:36.681Z", "author_approval_datetime": "2020-04-22T16:08:02Z", "creation_datetime": "2020-04-10T15:54:54.860Z", - "version_order": 0, "modified_datetime": "2020-05-11T16:07:36.681Z", "is_new_version": false, "slug": "t2ASGLbIBoWaTJvPrM2A", @@ -992,7 +991,6 @@ "copyedit_completion_datetime": null, "author_approval_datetime": null, "creation_datetime": "2018-09-04T17:26:24.820Z", - "version_order": 0, "modified_datetime": "2018-09-04T17:26:24.820Z", "is_new_version": false, "slug": "SHuKI1APLrwWCqxSQnSk", @@ -1038,7 +1036,6 @@ "copyedit_completion_datetime": null, "author_approval_datetime": null, "creation_datetime": "2018-11-04T17:26:24.820Z", - "version_order": 0, "modified_datetime": "2018-12-04T17:26:24.820Z", "is_new_version": false, "slug": "T108xFtYkRAxiRiuOLEJ", @@ -1084,7 +1081,6 @@ "copyedit_completion_datetime": null, "author_approval_datetime": null, "creation_datetime": "2018-11-05T17:26:24.820Z", - "version_order": 0, "modified_datetime": "2018-12-05T17:26:24.820Z", "is_new_version": false, "slug": "Revkx24XL77vUm2KTFYU", @@ -1130,7 +1126,6 @@ "copyedit_completion_datetime": null, "author_approval_datetime": null, "creation_datetime": "2020-04-10T15:51:57.150Z", - "version_order": 0, "modified_datetime": "2020-04-10T15:52:40.413Z", "is_new_version": false, "slug": "AGlSZa5VTv5Dg09CSpoh", @@ -1178,7 +1173,6 @@ "copyedit_completion_datetime": null, "author_approval_datetime": null, "creation_datetime": "2020-04-10T16:03:02.306Z", - "version_order": 0, "modified_datetime": "2020-04-10T16:03:02.306Z", "is_new_version": false, "slug": "omAiNz9yC3MXvZTRe9sF", @@ -1226,7 +1220,6 @@ "copyedit_completion_datetime": null, "author_approval_datetime": null, "creation_datetime": "2020-04-10T16:08:49.323Z", - "version_order": 1, "modified_datetime": "2020-04-10T16:08:49.323Z", "is_new_version": true, "slug": "p7TCIMkltNswuOB9FZH1", diff --git a/physionet-django/project/forms.py b/physionet-django/project/forms.py index 7dae854b23..cdff08e1ec 100644 --- a/physionet-django/project/forms.py +++ b/physionet-django/project/forms.py @@ -435,7 +435,6 @@ def save(self): # Set new fields project.creation_datetime = timezone.now() - project.version_order = self.latest_project.version_order + 1 project.is_new_version = True # Change internal links (that point to files within the diff --git a/physionet-django/project/migrations/0069_unpublishedproject_version_order_1.py b/physionet-django/project/migrations/0069_unpublishedproject_version_order_1.py new file mode 100644 index 0000000000..60aeed1ead --- /dev/null +++ b/physionet-django/project/migrations/0069_unpublishedproject_version_order_1.py @@ -0,0 +1,40 @@ +# Generated by Django 4.1.10 on 2023-08-29 19:13 + +from django.db import migrations, models + + +def migrate_forward(apps, schema_editor): + pass + + +def migrate_backward(apps, schema_editor): + ActiveProject = apps.get_model('project', 'ActiveProject') + ArchivedProject = apps.get_model('project', 'ArchivedProject') + for model in (ActiveProject, ArchivedProject): + for project in model.objects.all(): + if project.is_new_version: + project.version_order = 1 + else: + project.version_order = 0 + project.save(update_fields=['version_order']) + + +class Migration(migrations.Migration): + + dependencies = [ + ("project", "0068_auto_20230711_1651"), + ] + + operations = [ + migrations.AlterField( + model_name="activeproject", + name="version_order", + field=models.PositiveSmallIntegerField(null=True), + ), + migrations.AlterField( + model_name="archivedproject", + name="version_order", + field=models.PositiveSmallIntegerField(null=True), + ), + migrations.RunPython(migrate_forward, reverse_code=migrate_backward), + ] diff --git a/physionet-django/project/migrations/0070_unpublishedproject_version_order_2.py b/physionet-django/project/migrations/0070_unpublishedproject_version_order_2.py new file mode 100644 index 0000000000..e8168582aa --- /dev/null +++ b/physionet-django/project/migrations/0070_unpublishedproject_version_order_2.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.10 on 2023-08-29 19:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + MIGRATE_AFTER_INSTALL = True + + dependencies = [ + ("project", "0069_unpublishedproject_version_order_1"), + ] + + operations = [ + migrations.RemoveField( + model_name="activeproject", + name="version_order", + ), + migrations.RemoveField( + model_name="archivedproject", + name="version_order", + ), + ] diff --git a/physionet-django/project/modelcomponents/publishedproject.py b/physionet-django/project/modelcomponents/publishedproject.py index ffc65a9ec0..f15672b233 100644 --- a/physionet-django/project/modelcomponents/publishedproject.py +++ b/physionet-django/project/modelcomponents/publishedproject.py @@ -42,7 +42,10 @@ class PublishedProject(Metadata, SubmissionInfo): is_legacy = models.BooleanField(default=False) full_description = SafeHTMLField(default='') + # For ordering projects with multiple versions + version_order = models.PositiveSmallIntegerField(default=0) is_latest_version = models.BooleanField(default=True) + # Featured content featured = models.PositiveSmallIntegerField(null=True) has_wfdb = models.BooleanField(default=False) diff --git a/physionet-django/project/modelcomponents/submission.py b/physionet-django/project/modelcomponents/submission.py index f18cf1387e..9897bb3c01 100644 --- a/physionet-django/project/modelcomponents/submission.py +++ b/physionet-django/project/modelcomponents/submission.py @@ -223,9 +223,6 @@ class SubmissionInfo(models.Model): copyedit_logs = GenericRelation('project.CopyeditLog') logs = GenericRelation('project.Log') - # For ordering projects with multiple versions - version_order = models.PositiveSmallIntegerField(default=0) - # Anonymous access anonymous = GenericRelation('project.AnonymousAccess') From b0c3686c95d564b70bebc3d4540ebaa48998d761 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 31 Aug 2023 15:25:26 -0400 Subject: [PATCH 34/82] validate_version: forbid version numbers consisting of all zeroes. An "all-zeroes" version number doesn't make sense semantically (following standard conventions, it's impossible for any future version number to be "compatible" with version 0.0.0). Disallow this for new projects. --- physionet-django/project/validators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/physionet-django/project/validators.py b/physionet-django/project/validators.py index 7a7c4455a6..c9544134ca 100644 --- a/physionet-django/project/validators.py +++ b/physionet-django/project/validators.py @@ -88,6 +88,8 @@ def validate_version(value): """ if not re.fullmatch(r'[0-9]+(\.[0-9]+)+', value) or '..' in value: raise ValidationError('Version may only contain numbers and dots, and must begin and end with a number.') + if re.fullmatch(r'[0.]+', value): + raise ValidationError('Version must not be entirely zeroes.') def validate_slug(value): From f9dffb9225ea279b9454441ac21a0bd8539b27bf Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 31 Aug 2023 15:38:35 -0400 Subject: [PATCH 35/82] validate_version: forbid leading zeroes in version numbers. It should be possible to compare published project version numbers using conventional version-number comparisons (as is done by PublishedProject.set_version_order, for example.) If leading zeroes are allowed, it's impossible to tell whether "1.0.1" is greater or less than "1.00.01". (Version numbers are not decimal numbers.) Disallow version numbers for new projects that don't follow this rule. --- physionet-django/project/validators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/physionet-django/project/validators.py b/physionet-django/project/validators.py index c9544134ca..6348c9d292 100644 --- a/physionet-django/project/validators.py +++ b/physionet-django/project/validators.py @@ -90,6 +90,9 @@ def validate_version(value): raise ValidationError('Version may only contain numbers and dots, and must begin and end with a number.') if re.fullmatch(r'[0.]+', value): raise ValidationError('Version must not be entirely zeroes.') + if re.search(r'\b0[0-9]', value): + raise ValidationError('Version must not contain leading zeroes. ' + 'For example, write "1.0.1" instead of "1.00.01".') def validate_slug(value): From 57e0b0bc8ccb5f6890d24b0b0d9c9774c1185ced Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 31 Aug 2023 17:00:49 -0400 Subject: [PATCH 36/82] demo-user.json: allow members of Admin group to be project editors. For convenience in testing, the demo "admin" user should be allowed both to assign editors to projects, and to serve as an editor (i.e., they should be able to assign a project to themselves.) --- physionet-django/user/fixtures/demo-user.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/physionet-django/user/fixtures/demo-user.json b/physionet-django/user/fixtures/demo-user.json index 0929a080a8..23fc251e0d 100644 --- a/physionet-django/user/fixtures/demo-user.json +++ b/physionet-django/user/fixtures/demo-user.json @@ -14379,6 +14379,11 @@ "project", "activeproject" ], + [ + "can_edit_activeprojects", + "project", + "activeproject" + ], [ "change_activeproject", "project", From 8a16e71e8fe7b4c4e46755c5cab0c5c3914d287f Mon Sep 17 00:00:00 2001 From: Karol Szuster Date: Tue, 8 Aug 2023 10:24:48 +0200 Subject: [PATCH 37/82] Bump version `hdn-research-environment` to `2.3.6` --- .env.example | 4 +--- physionet-django/physionet/settings/base.py | 5 ----- poetry.lock | 6 +++--- pyproject.toml | 2 +- requirements.txt | 4 ++-- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index ff738bf0f4..bdb593a99e 100644 --- a/.env.example +++ b/.env.example @@ -98,8 +98,6 @@ GCS_SIGNED_URL_LIFETIME_IN_MINUTES=1440 # GCP Research Environments ENABLE_CLOUD_RESEARCH_ENVIRONMENTS="False" CLOUD_RESEARCH_ENVIRONMENTS_API_URL="https://example.api" -CLOUD_RESEARCH_ENVIRONMENTS_API_JWT_SERVICE_ACCOUNT_PATH="environment/api/tests/service_account.json" -CLOUD_RESEARCH_ENVIRONMENTS_API_JWT_AUDIENCE="https://example.com" # Site-specific content SITE_NAME="DataShare" @@ -187,7 +185,7 @@ MIN_WORDS_RESEARCH_SUMMARY_CREDENTIALING = 20 # CITISOAPService API # This is the WebServices username and password to access the CITI SOAP Service to obtain users training report details # The account can be created at https://webservices.citiprogram.org/login/CreateAccount.aspx -# The SOAP Service Access can be tested at https://webservices.citiprogram.org/Client/CITISOAPClient_Simple.aspx +# The SOAP Service Access can be tested at https://webservices.citiprogram.org/Client/CITISOAPClient_Simple.aspx CITI_USERNAME= CITI_PASSWORD= CITI_SOAP_URL="https://webservices.citiprogram.org/SOAP/CITISOAPService.asmx" diff --git a/physionet-django/physionet/settings/base.py b/physionet-django/physionet/settings/base.py index 5e5b0b63b1..ca7cb457f7 100644 --- a/physionet-django/physionet/settings/base.py +++ b/physionet-django/physionet/settings/base.py @@ -576,12 +576,7 @@ class StorageTypes: ENABLE_CLOUD_RESEARCH_ENVIRONMENTS = config('ENABLE_CLOUD_RESEARCH_ENVIRONMENTS', default=False, cast=bool) if ENABLE_CLOUD_RESEARCH_ENVIRONMENTS: - CLOUD_RESEARCH_ENVIRONMENTS_API_JWT_SERVICE_ACCOUNT_PATH = os.path.join( - BASE_DIR, - config('CLOUD_RESEARCH_ENVIRONMENTS_API_JWT_SERVICE_ACCOUNT_PATH') - ) CLOUD_RESEARCH_ENVIRONMENTS_API_URL = config('CLOUD_RESEARCH_ENVIRONMENTS_API_URL') - CLOUD_RESEARCH_ENVIRONMENTS_API_JWT_AUDIENCE = config('CLOUD_RESEARCH_ENVIRONMENTS_API_JWT_AUDIENCE') INSTALLED_APPS.append('environment.apps.EnvironmentConfig') diff --git a/poetry.lock b/poetry.lock index cd8c1f3332..42d230d59c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -754,12 +754,12 @@ protobuf = ">=3.12.0" [[package]] name = "hdn-research-environment" -version = "1.4.0" +version = "2.3.6" description = "A Django app for supporting cloud-native research environments" optional = false python-versions = ">=3.7" files = [ - {file = "hdn-research-environment-1.4.0.tar.gz", hash = "sha256:296a336847c1078c149baa6773654a2e2a5b16cc1b03ca25f6f19f8606980e5b"}, + {file = "hdn-research-environment-2.3.6.tar.gz", hash = "sha256:b28d67db8ba4cf82e1362aefde47d483b549e8f7b96d1d4f56baebe0b76dfc31"}, ] [package.dependencies] @@ -1453,4 +1453,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "1c680953888a101ede203b84ea510fdb24cd5f1020e668881919f06b59b94cca" +content-hash = "2297d8c676de9b2ac1455227f408f5a45f51a249993c1f72ea56221882937464" diff --git a/pyproject.toml b/pyproject.toml index fafa8f8e48..4a3fbe318c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ zxcvbn = "^4.4.28" certifi = "^2023.7.22" setuptools = "65.5.1" django-js-asset = "2.0.0" -hdn-research-environment = "^1.4.0" +hdn-research-environment = "2.3.6" django-oauth-toolkit = "^2.2.0" django-cors-headers = "^3.14.0" diff --git a/requirements.txt b/requirements.txt index de39cc6030..8bb56ed7d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -297,8 +297,8 @@ grpcio==1.53.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:f3e837d29f0e1b9d6e7b29d569e2e9b0da61889e41879832ea15569c251c303a \ --hash=sha256:fa8eaac75d3107e3f5465f2c9e3bbd13db21790c6e45b7de1756eba16b050aca \ --hash=sha256:fdc6191587de410a184550d4143e2b24a14df495c86ca15e59508710681690ac -hdn-research-environment==1.4.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:296a336847c1078c149baa6773654a2e2a5b16cc1b03ca25f6f19f8606980e5b +hdn-research-environment==2.3.6 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:b28d67db8ba4cf82e1362aefde47d483b549e8f7b96d1d4f56baebe0b76dfc31 html2text==2018.1.9 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:490db40fe5b2cd79c461cf56be4d39eb8ca68191ae41ba3ba79f6cb05b7dd662 \ --hash=sha256:627514fb30e7566b37be6900df26c2c78a030cc9e6211bda604d8181233bcdd4 From 8c3789b8b35d5307b8e6031352bd7886667e8d21 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 5 Sep 2023 14:02:02 -0400 Subject: [PATCH 38/82] Rearraged the trainings frontend into course & trainings --- .../user/templates/user/edit_course.html | 51 +++++++++++++++++++ .../user/templates/user/edit_training.html | 32 +----------- .../user/templatetags/user_templatetags.py | 2 +- physionet-django/user/urls.py | 1 + physionet-django/user/views.py | 50 +++++++++++++++--- 5 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 physionet-django/user/templates/user/edit_course.html diff --git a/physionet-django/user/templates/user/edit_course.html b/physionet-django/user/templates/user/edit_course.html new file mode 100644 index 0000000000..93e35632be --- /dev/null +++ b/physionet-django/user/templates/user/edit_course.html @@ -0,0 +1,51 @@ +{% extends "user/settings.html" %} + +{% block title %}{{ SITE_NAME }} Courses{% endblock %} + +{% block main_content %} +

    Courses

    +
    +

    To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

    +
    +

    Submit evidence of a completed course

    + +

    Please use the form below to submit a new completion report.

    +

    For CITI training, please refer to our step-by-step instructions and + be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or + Specimens Only Research" course (with HIPAA module). + {% if ticket_system_url %}To raise a support request, please click here. + {% endif %}

    + +
    + {% include "descriptive_inline_form_snippet.html" with form=training_form %} + + +
    + +{% endblock %} + +{% block local_js_bottom %} + +{% endblock %} diff --git a/physionet-django/user/templates/user/edit_training.html b/physionet-django/user/templates/user/edit_training.html index 8791e0199f..d8a5ac6f48 100644 --- a/physionet-django/user/templates/user/edit_training.html +++ b/physionet-django/user/templates/user/edit_training.html @@ -28,7 +28,6 @@

    {{ status|capfirst }}

    {% endfor %}
    -

    Submit evidence of a completed course

    {% for status, group in training_by_status.items %} {% if status == 'under review' and group %} @@ -50,36 +49,7 @@

    Submit evidence of a completed course

    {% endif %} {% endfor %} -

    Please use the form below to submit a new completion report.

    -

    For CITI training, please refer to our step-by-step instructions and - be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or - Specimens Only Research" course (with HIPAA module). - {% if ticket_system_url %}To raise a support request, please click here. - {% endif %}

    - -
    - {% include "descriptive_inline_form_snippet.html" with form=training_form %} - - -
    +
    To submit a new completion report, please go to the Courses page.
    {% endblock %} diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index c680e53a27..f2bd99344d 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -6,7 +6,7 @@ @register.inclusion_tag('user/settings_tabs.html') def settings_tabs(hide_password_settings: bool): - default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Training', 'Agreements'] + default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Course', 'Training', 'Agreements'] if not hide_password_settings: default_tabs.insert(1, 'Password') return {'settings_tabs': default_tabs} diff --git a/physionet-django/user/urls.py b/physionet-django/user/urls.py index 7e9dbf832c..15e8cc6918 100644 --- a/physionet-django/user/urls.py +++ b/physionet-django/user/urls.py @@ -23,6 +23,7 @@ path('settings/credentialing/applications/', views.user_credential_applications, name='user_credential_applications'), path('settings/training/', views.edit_training, name='edit_training'), + path('settings/course/', views.edit_course, name='edit_course'), path('settings/training//', views.edit_training_detail, name='edit_training_detail'), path('settings/agreements/', views.view_agreements, name='edit_agreements'), path('settings/agreements//', views.view_signed_agreement, name='view_signed_agreement'), diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 9303e9e066..12fb75e4c6 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -716,7 +716,42 @@ def credential_application(request): 'code_of_conduct': code_of_conduct, }, ) +@login_required +def edit_course(request): + """ + Courses settings page. + """ + if settings.TICKET_SYSTEM_URL: + ticket_system_url = settings.TICKET_SYSTEM_URL + else: + ticket_system_url = None + if request.method == 'POST': + training_form = forms.TrainingForm( + user=request.user, data=request.POST, files=request.FILES, training_type=request.POST.get('training_type') + ) + if training_form.is_valid(): + training_form.save() + messages.success(request, 'The training has been submitted successfully.') + training_application_request(request, training_form) + training_form = forms.TrainingForm(user=request.user) + else: + messages.error(request, 'Invalid submission. Check the errors below.') + + else: + training_type = request.GET.get('trainingType') + if training_type: + training_form = forms.TrainingForm(user=request.user, training_type=training_type) + else: + training_form = forms.TrainingForm(user=request.user) + + return render( + request, + 'user/edit_course.html', + { + 'training_form': training_form, + 'ticket_system_url': ticket_system_url}, + ) @login_required def edit_training(request): @@ -749,18 +784,17 @@ def edit_training(request): training = Training.objects.select_related('training_type').filter(user=request.user).order_by('-status') training_by_status = { - 'under review': training.get_review(), - 'active': training.get_valid(), - 'expired': training.get_expired(), - 'rejected': training.get_rejected(), - } + 'under review': training.get_review(), + 'active': training.get_valid(), + 'expired': training.get_expired(), + 'rejected': training.get_rejected(), + } return render( request, 'user/edit_training.html', - {'training_form': training_form, - 'training_by_status': training_by_status, - 'ticket_system_url': ticket_system_url}, + { + 'training_by_status': training_by_status}, ) From 14ef23879d62055d44638c9da6d50784d976ae5c Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 5 Sep 2023 15:56:54 -0400 Subject: [PATCH 39/82] Styling changes --- .../user/templatetags/user_templatetags.py | 20 +- physionet-django/user/urls.py | 176 +++-- physionet-django/user/views.py | 616 +++++++++++------- 3 files changed, 497 insertions(+), 315 deletions(-) diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index f2bd99344d..f86c6e4108 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -4,14 +4,24 @@ register = template.Library() -@register.inclusion_tag('user/settings_tabs.html') +@register.inclusion_tag("user/settings_tabs.html") def settings_tabs(hide_password_settings: bool): - default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Course', 'Training', 'Agreements'] + default_tabs = [ + "Profile", + "Emails", + "Username", + "Cloud", + "ORCID", + "Credentialing", + "Course", + "Training", + "Agreements", + ] if not hide_password_settings: - default_tabs.insert(1, 'Password') - return {'settings_tabs': default_tabs} + default_tabs.insert(1, "Password") + return {"settings_tabs": default_tabs} -@register.filter(name='has_group') +@register.filter(name="has_group") def has_group(user, group_name): return user.groups.filter(name=group_name).exists() diff --git a/physionet-django/user/urls.py b/physionet-django/user/urls.py index 15e8cc6918..8547e7bd11 100644 --- a/physionet-django/user/urls.py +++ b/physionet-django/user/urls.py @@ -7,90 +7,130 @@ urlpatterns = [ - path('login/', login_view, name='login'), - - path('logout/', views.logout, name='logout'), - + path("login/", login_view, name="login"), + path("logout/", views.logout, name="logout"), # Settings - path('settings/', views.user_settings, name='user_settings'), - path('settings/profile/', views.edit_profile, name='edit_profile'), - path('settings/emails/', views.edit_emails, name='edit_emails'), - path('settings/username/', views.edit_username, name='edit_username'), - path('settings/cloud/', views.edit_cloud, name='edit_cloud'), - path('settings/orcid/', views.edit_orcid, name='edit_orcid'), - path('authorcid/', views.auth_orcid, name='auth_orcid'), - path('settings/credentialing/', views.edit_credentialing, name='edit_credentialing'), - path('settings/credentialing/applications/', - views.user_credential_applications, name='user_credential_applications'), - path('settings/training/', views.edit_training, name='edit_training'), - path('settings/course/', views.edit_course, name='edit_course'), - path('settings/training//', views.edit_training_detail, name='edit_training_detail'), - path('settings/agreements/', views.view_agreements, name='edit_agreements'), - path('settings/agreements//', views.view_signed_agreement, name='view_signed_agreement'), - + path("settings/", views.user_settings, name="user_settings"), + path("settings/profile/", views.edit_profile, name="edit_profile"), + path("settings/emails/", views.edit_emails, name="edit_emails"), + path("settings/username/", views.edit_username, name="edit_username"), + path("settings/cloud/", views.edit_cloud, name="edit_cloud"), + path("settings/orcid/", views.edit_orcid, name="edit_orcid"), + path("authorcid/", views.auth_orcid, name="auth_orcid"), + path( + "settings/credentialing/", views.edit_credentialing, name="edit_credentialing" + ), + path( + "settings/credentialing/applications/", + views.user_credential_applications, + name="user_credential_applications", + ), + path("settings/training/", views.edit_training, name="edit_training"), + path("settings/course/", views.edit_course, name="edit_course"), + path( + "settings/training//", + views.edit_training_detail, + name="edit_training_detail", + ), + path("settings/agreements/", views.view_agreements, name="edit_agreements"), + path( + "settings/agreements//", + views.view_signed_agreement, + name="view_signed_agreement", + ), # Current tokens are 20 characters long and consist of 0-9A-Za-z # Obsolete tokens are 34 characters long and also include a hyphen - re_path(r'^verify/(?P[0-9A-Za-z_\-]+)/(?P[-0-9A-Za-z]{1,34})/$', - views.verify_email, name='verify_email'), - - + re_path( + r"^verify/(?P[0-9A-Za-z_\-]+)/(?P[-0-9A-Za-z]{1,34})/$", + views.verify_email, + name="verify_email", + ), # Public user profile - path('users//', views.public_profile, - name='public_profile'), - path('users//profile-photo/', views.profile_photo, - name='profile_photo'), - path('credential-application/', views.credential_application, - name='credential_application'), + path("users//", views.public_profile, name="public_profile"), + path("users//profile-photo/", views.profile_photo, name="profile_photo"), + path( + "credential-application/", + views.credential_application, + name="credential_application", + ), # TODO: remove this after 30 days of commit merge, we want let the old links that was sent to the referees work - path('credential-reference//', - views.credential_reference, name='credential_reference'), - path('credential-reference///', - views.credential_reference_verification, name='credential_reference_verification'), - path('trainings//report/', views.training_report, name='training_report'), + path( + "credential-reference//", + views.credential_reference, + name="credential_reference", + ), + path( + "credential-reference///", + views.credential_reference_verification, + name="credential_reference_verification", + ), + path( + "trainings//report/", + views.training_report, + name="training_report", + ), ] if not settings.ENABLE_SSO: - urlpatterns.extend([ - path('register/', views.register, name='register'), - path('settings/password/', views.edit_password, name='edit_password'), - path('settings/password/changed/', views.edit_password_complete, name='edit_password_complete'), - re_path(r'^activate/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$', + urlpatterns.extend( + [ + path("register/", views.register, name="register"), + path("settings/password/", views.edit_password, name="edit_password"), + path( + "settings/password/changed/", + views.edit_password_complete, + name="edit_password_complete", + ), + re_path( + r"^activate/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$", views.activate_user, - name='activate_user'), - # Request password reset - path('reset-password/', views.reset_password_request, - name='reset_password_request'), - # Page shown after reset email has been sent - path('reset-password/sent/', views.reset_password_sent, - name='reset_password_sent'), - # Prompt user to enter new password and carry out password reset (if url is valid) - re_path(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$', - views.reset_password_confirm, name='reset_password_confirm'), - # Password reset successfully carried out - path('reset/complete/', views.reset_password_complete, - name='reset_password_complete'), - ]) + name="activate_user", + ), + # Request password reset + path( + "reset-password/", + views.reset_password_request, + name="reset_password_request", + ), + # Page shown after reset email has been sent + path( + "reset-password/sent/", + views.reset_password_sent, + name="reset_password_sent", + ), + # Prompt user to enter new password and carry out password reset (if url is valid) + re_path( + r"^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$", + views.reset_password_confirm, + name="reset_password_confirm", + ), + # Password reset successfully carried out + path( + "reset/complete/", + views.reset_password_complete, + name="reset_password_complete", + ), + ] + ) # Parameters for testing URLs (see physionet/test_urls.py) TEST_DEFAULTS = { - '_user_': 'aewj', - 'training_id': 106, - 'dua_signature_id': 1, - 'application_slug': 'Osm0FMaavviixpsL26v2', - 'verification_token': 'rJ2i7vlzh6AgZ1Wwtcz8zCoI5BqxH0kU', - 'username': 'rgmark', + "_user_": "aewj", + "training_id": 106, + "dua_signature_id": 1, + "application_slug": "Osm0FMaavviixpsL26v2", + "verification_token": "rJ2i7vlzh6AgZ1Wwtcz8zCoI5BqxH0kU", + "username": "rgmark", } TEST_CASES = { - 'verify_email': { - 'uidb64': 'MjEx', - 'token': 'oax3ZcG47GYUhAobbJyp', + "verify_email": { + "uidb64": "MjEx", + "token": "oax3ZcG47GYUhAobbJyp", }, - # Testing activate_user and reset_password_confirm requires a # dynamically-generated token. Skip these URLs for now. - 'activate_user': {'uidb64': 'x', 'token': 'x', '_skip_': True}, - 'reset_password_confirm': {'uidb64': 'x', 'token': 'x', '_skip_': True}, - + "activate_user": {"uidb64": "x", "token": "x", "_skip_": True}, + "reset_password_confirm": {"uidb64": "x", "token": "x", "_skip_": True}, # Testing auth_orcid requires a mock oauth server. Skip this URL. - 'auth_orcid': {'_skip_': True}, + "auth_orcid": {"_skip_": True}, } diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 12fb75e4c6..4875a02fd8 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -63,30 +63,30 @@ logger = logging.getLogger(__name__) -@method_decorator(allow_post_during_maintenance, 'dispatch') +@method_decorator(allow_post_during_maintenance, "dispatch") class LoginView(auth_views.LoginView): - template_name = 'user/login.html' + template_name = "user/login.html" authentication_form = forms.LoginForm redirect_authenticated_user = True -@method_decorator(allow_post_during_maintenance, 'dispatch') +@method_decorator(allow_post_during_maintenance, "dispatch") class SSOLoginView(auth_views.LoginView): - template_name = 'sso/login.html' + template_name = "sso/login.html" authentication_form = forms.LoginForm redirect_authenticated_user = True def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) try: - login_static_page = StaticPage.objects.get(url='/about/login/') + login_static_page = StaticPage.objects.get(url="/about/login/") instruction_sections = Section.objects.filter(static_page=login_static_page) except StaticPage.DoesNotExist: instruction_sections = [] sso_extra_context = { - 'sso_login_button_text': settings.SSO_LOGIN_BUTTON_TEXT, - 'login_instruction_sections': instruction_sections, + "sso_login_button_text": settings.SSO_LOGIN_BUTTON_TEXT, + "login_instruction_sections": instruction_sections, } return {**context, **sso_extra_context} @@ -97,33 +97,33 @@ class LogoutView(auth_views.LogoutView): # Request password reset class PasswordResetView(auth_views.PasswordResetView): - template_name = 'user/reset_password_request.html' - success_url = reverse_lazy('reset_password_sent') - email_template_name = 'user/email/reset_password_email.html' - extra_email_context = {'SITE_NAME': settings.SITE_NAME} + template_name = "user/reset_password_request.html" + success_url = reverse_lazy("reset_password_sent") + email_template_name = "user/email/reset_password_email.html" + extra_email_context = {"SITE_NAME": settings.SITE_NAME} # Page shown after reset email has been sent class PasswordResetDoneView(auth_views.PasswordResetDoneView): - template_name = 'user/reset_password_sent.html' + template_name = "user/reset_password_sent.html" # Prompt user to enter new password and carry out password reset (if # url is valid) -@method_decorator(disallow_during_maintenance, 'dispatch') +@method_decorator(disallow_during_maintenance, "dispatch") class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): - template_name = 'user/reset_password_confirm.html' - success_url = reverse_lazy('reset_password_complete') + template_name = "user/reset_password_confirm.html" + success_url = reverse_lazy("reset_password_complete") # Password reset successfully carried out class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): - template_name = 'user/reset_password_complete.html' + template_name = "user/reset_password_complete.html" class PasswordChangeView(auth_views.PasswordChangeView): - success_url = reverse_lazy('edit_password_complete') - template_name = 'user/edit_password.html' + success_url = reverse_lazy("edit_password_complete") + template_name = "user/edit_password.html" login = LoginView.as_view() @@ -136,7 +136,7 @@ class PasswordChangeView(auth_views.PasswordChangeView): edit_password = PasswordChangeView.as_view() -@sensitive_post_parameters('password1', 'password2') +@sensitive_post_parameters("password1", "password2") @disallow_during_maintenance def activate_user(request, uidb64, token): """ @@ -144,10 +144,10 @@ def activate_user(request, uidb64, token): The user will create the password at this stage and then logged in. """ - activation_session_token = '_activation_reset_token' - activation_url_token = 'user-activation' + activation_session_token = "_activation_reset_token" + activation_url_token = "user-activation" title = "Account activation" - context = {'title': 'Invalid Activation Link', 'isvalid': False} + context = {"title": "Invalid Activation Link", "isvalid": False} try: uid = force_str(urlsafe_base64_decode(uidb64)) @@ -156,17 +156,18 @@ def activate_user(request, uidb64, token): user = None if user and user.is_active: - messages.success(request, 'The account is active.') - return redirect('login') + messages.success(request, "The account is active.") + return redirect("login") - if request.method == 'GET': + if request.method == "GET": if token == activation_url_token: session_token = request.session.get(activation_session_token) if default_token_generator.check_token(user, session_token): # If the token is valid, display the password reset form. form = forms.ActivationForm(user=user) - return render(request, 'user/activate_user.html', { - 'form': form, 'title': title}) + return render( + request, "user/activate_user.html", {"form": form, "title": title} + ) else: if default_token_generator.check_token(user, token): # Store the token in the session and redirect to the @@ -180,9 +181,11 @@ def activate_user(request, uidb64, token): if token == activation_url_token: session_token = request.session.get(activation_session_token) form = forms.ActivationForm(user=user, data=request.POST) - if form.is_valid() and default_token_generator.check_token(user, session_token): + if form.is_valid() and default_token_generator.check_token( + user, session_token + ): with transaction.atomic(): - user.set_password(form.cleaned_data['password1']) + user.set_password(form.cleaned_data["password1"]) user.is_active = True # Check legacy credentials check_legacy_credentials(user, user.email) @@ -192,14 +195,15 @@ def activate_user(request, uidb64, token): email.is_verified = True email.save() request.session.pop(activation_session_token) - logger.info('User activated - {0}'.format(user.email)) - messages.success(request, 'The account has been activated.') + logger.info("User activated - {0}".format(user.email)) + messages.success(request, "The account has been activated.") login(request, user) - return redirect('project_home') - return render(request, 'user/activate_user.html', {'form': form, - 'title': title}) + return redirect("project_home") + return render( + request, "user/activate_user.html", {"form": form, "title": title} + ) - return render(request, 'user/activate_user_complete.html', context) + return render(request, "user/activate_user_complete.html", context) def check_legacy_credentials(user, email): @@ -207,14 +211,13 @@ def check_legacy_credentials(user, email): Check whether a user has already beeen credentialed on the old pn site. If so, credential their account and mark the migration. """ - legacy_credential = LegacyCredential.objects.filter(email=email, - migrated=False) + legacy_credential = LegacyCredential.objects.filter(email=email, migrated=False) if legacy_credential: legacy_credential = legacy_credential.get() user.is_credentialed = True # All of them are mimic credentialed - month, day, year = legacy_credential.mimic_approval_date.split('/') - dt = datetime(int(year), int(month), int(day)) + month, day, year = legacy_credential.mimic_approval_date.split("/") + dt = datetime(int(year), int(month), int(day)) dt = pytz.timezone(timezone.get_default_timezone_name()).localize(dt) user.credential_datetime = dt legacy_credential.migrated = True @@ -223,6 +226,7 @@ def check_legacy_credentials(user, email): legacy_credential.save() user.save() + def remove_email(request, email_id): "Remove a non-primary email associated with a user" user = request.user @@ -232,36 +236,53 @@ def remove_email(request, email_id): email = associated_email.email associated_email.delete() logger.info(f"Removed email {email} from user {user.id}") - messages.success(request, f"The email address ({email}) has been removed from your account.") + messages.success( + request, + f"The email address ({email}) has been removed from your account.", + ) except AssociatedEmail.DoesNotExist: - messages.success(request, "The email address has been removed from your account.") + messages.success( + request, "The email address has been removed from your account." + ) def set_primary_email(request, primary_email_form): "Set the selected email as the primary email" user = request.user if primary_email_form.is_valid(): - associated_email = primary_email_form.cleaned_data['associated_email'] + associated_email = primary_email_form.cleaned_data["associated_email"] # Only do something if they selected a different email if associated_email.email != user.email: - logger.info('Primary email changed from: {0} to {1}'.format(user.email, associated_email.email)) + logger.info( + "Primary email changed from: {0} to {1}".format( + user.email, associated_email.email + ) + ) user.email = associated_email.email - user.save(update_fields=['email']) + user.save(update_fields=["email"]) # Change the email field of author objects belonging to # the user. Warn them if they are the corresponding # author of any projects authors = Author.objects.filter(user=user) authors.update(corresponding_email=associated_email) - messages.success(request, 'Your email: {0} has been set as your new primary email.'.format(user.email)) + messages.success( + request, + "Your email: {0} has been set as your new primary email.".format( + user.email + ), + ) if authors.filter(is_corresponding=True): - messages.info(request, 'The corresponding email in all your authoring projects has been set to your new primary email.') + messages.info( + request, + "The corresponding email in all your authoring projects has been set to your new primary email.", + ) def set_public_email(request, public_email_form): "Set the selected email as the public email" user = request.user if public_email_form.is_valid(): - associated_email = public_email_form.cleaned_data['associated_email'] + associated_email = public_email_form.cleaned_data["associated_email"] current_public_email = user.associated_emails.filter(is_public=True).first() # Only do something if they selected a different email if associated_email != current_public_email: @@ -272,33 +293,55 @@ def set_public_email(request, public_email_form): if associated_email: associated_email.is_public = True associated_email.save() - messages.success(request, 'Your email: {0} has been set to public.'.format(associated_email.email)) + messages.success( + request, + "Your email: {0} has been set to public.".format( + associated_email.email + ), + ) else: - messages.success(request, 'Your email: {0} has been set to private.'.format(current_public_email.email)) + messages.success( + request, + "Your email: {0} has been set to private.".format( + current_public_email.email + ), + ) + def add_email(request, add_email_form): user = request.user if add_email_form.is_valid(): token = get_random_string(20) - associated_email = AssociatedEmail.objects.create(user=user, - email=add_email_form.cleaned_data['email'], - verification_token=token) + associated_email = AssociatedEmail.objects.create( + user=user, + email=add_email_form.cleaned_data["email"], + verification_token=token, + ) # Send an email to the newly added email with a verification link uidb64 = force_str(urlsafe_base64_encode(force_bytes(associated_email.pk))) subject = f"{settings.SITE_NAME} Email Verification" context = { - 'name': user.get_full_name(), - 'domain': get_current_site(request), - 'url_prefix': get_url_prefix(request), - 'uidb64': uidb64, - 'token': token, - 'SITE_NAME': settings.SITE_NAME, + "name": user.get_full_name(), + "domain": get_current_site(request), + "url_prefix": get_url_prefix(request), + "uidb64": uidb64, + "token": token, + "SITE_NAME": settings.SITE_NAME, } - body = loader.render_to_string('user/email/verify_email_email.html', context) - send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, - [add_email_form.cleaned_data['email']], fail_silently=False) - messages.success(request, 'A verification link has been sent to: {0}'.format(associated_email.email)) + body = loader.render_to_string("user/email/verify_email_email.html", context) + send_mail( + subject, + body, + settings.DEFAULT_FROM_EMAIL, + [add_email_form.cleaned_data["email"]], + fail_silently=False, + ) + messages.success( + request, + "A verification link has been sent to: {0}".format(associated_email.email), + ) + @login_required def edit_emails(request): @@ -307,56 +350,65 @@ def edit_emails(request): """ user = request.user - associated_emails = AssociatedEmail.objects.filter( - user=user).order_by('-is_verified', '-is_primary_email') + associated_emails = AssociatedEmail.objects.filter(user=user).order_by( + "-is_verified", "-is_primary_email" + ) total_associated_emails = associated_emails.count() max_associated_emails_allowed = settings.MAX_EMAILS_PER_USER - primary_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='primary') - public_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='public') + primary_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="primary" + ) + public_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="public" + ) add_email_form = forms.AddEmailForm() - if request.method == 'POST': - if 'remove_email' in request.POST: + if request.method == "POST": + if "remove_email" in request.POST: # No form. Just get button value. - email_id = int(request.POST['remove_email']) + email_id = int(request.POST["remove_email"]) remove_email(request, email_id) # Update the associated_emails count after removing an email total_associated_emails = AssociatedEmail.objects.filter(user=user).count() - elif 'set_primary_email' in request.POST: - primary_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='primary', data=request.POST) + elif "set_primary_email" in request.POST: + primary_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="primary", data=request.POST + ) set_primary_email(request, primary_email_form) - elif 'set_public_email' in request.POST: - public_email_form = forms.AssociatedEmailChoiceForm(user=user, - selection_type='public', data=request.POST) + elif "set_public_email" in request.POST: + public_email_form = forms.AssociatedEmailChoiceForm( + user=user, selection_type="public", data=request.POST + ) set_public_email(request, public_email_form) - elif 'add_email' in request.POST: + elif "add_email" in request.POST: if associated_emails.count() >= settings.MAX_EMAILS_PER_USER: messages.error( request, - 'You have reached the maximum number of email addresses allowed.' + "You have reached the maximum number of email addresses allowed.", ) else: add_email_form = forms.AddEmailForm(request.POST) add_email(request, add_email_form) # Update the associated_emails count after adding a new email - total_associated_emails = AssociatedEmail.objects.filter(user=user).count() + total_associated_emails = AssociatedEmail.objects.filter( + user=user + ).count() - context = {'associated_emails': associated_emails, - 'primary_email_form': primary_email_form, - 'add_email_form': add_email_form, - 'public_email_form': public_email_form, - 'total_associated_emails': total_associated_emails, - 'max_associated_emails_allowed': max_associated_emails_allowed} + context = { + "associated_emails": associated_emails, + "primary_email_form": primary_email_form, + "add_email_form": add_email_form, + "public_email_form": public_email_form, + "total_associated_emails": total_associated_emails, + "max_associated_emails_allowed": max_associated_emails_allowed, + } - context['messages'] = messages.get_messages(request) + context["messages"] = messages.get_messages(request) - return render(request, 'user/edit_emails.html', context) + return render(request, "user/edit_emails.html", context) @login_required @@ -367,29 +419,31 @@ def edit_profile(request): profile = request.user.profile form = forms.ProfileForm(instance=profile) - if request.method == 'POST': + if request.method == "POST": if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: # Allow submitting the form, but do not allow the photo to # be modified. - if 'delete_photo' in request.POST or request.FILES: + if "delete_photo" in request.POST or request.FILES: raise ServiceUnavailable() - if 'edit_profile' in request.POST: + if "edit_profile" in request.POST: # Update the profile and return to the same page. Place a message # at the top of the page: 'your profile has been updated' - form = forms.ProfileForm(data=request.POST, files=request.FILES, - instance=profile) + form = forms.ProfileForm( + data=request.POST, files=request.FILES, instance=profile + ) if form.is_valid(): form.save() - messages.success(request, 'Your profile has been updated.') - elif 'delete_photo' in request.POST: + messages.success(request, "Your profile has been updated.") + elif "delete_photo" in request.POST: profile.delete_photo() - messages.success(request, 'Your profile photo has been deleted.') + messages.success(request, "Your profile photo has been deleted.") if not form.errors: form = forms.ProfileForm(instance=profile) - return render(request, 'user/edit_profile.html', {'form':form}) + return render(request, "user/edit_profile.html", {"form": form}) + @login_required def edit_orcid(request): @@ -399,8 +453,8 @@ def edit_orcid(request): ORCID account from their account. """ - if request.method == 'POST': - if 'request_orcid' in request.POST: + if request.method == "POST": + if "request_orcid" in request.POST: client_id = settings.ORCID_CLIENT_ID redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) @@ -409,12 +463,15 @@ def edit_orcid(request): return redirect(authorization_url) - if 'remove_orcid' in request.POST: + if "remove_orcid" in request.POST: try: Orcid.objects.get(user=request.user).delete() orcid_html = None except ObjectDoesNotExist: - messages.error(request, 'Object Does Not Exist Error: tried to unlink an object which does not exist.') + messages.error( + request, + "Object Does Not Exist Error: tried to unlink an object which does not exist.", + ) orcid_html = None else: @@ -423,7 +480,8 @@ def edit_orcid(request): except ObjectDoesNotExist: orcid_html = None - return render(request, 'user/edit_orcid.html', {'orcid': orcid_html}) + return render(request, "user/edit_orcid.html", {"orcid": orcid_html}) + @login_required @disallow_during_maintenance @@ -441,37 +499,44 @@ def auth_orcid(request): client_secret = settings.ORCID_CLIENT_SECRET redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) - oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, - scope=scope) + oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope) params = request.GET.copy() - code = params['code'] + code = params["code"] try: - token = oauth.fetch_token(settings.ORCID_TOKEN_URL, code=code, - include_client_id=True, client_secret=client_secret) + token = oauth.fetch_token( + settings.ORCID_TOKEN_URL, + code=code, + include_client_id=True, + client_secret=client_secret, + ) try: - validators.validate_orcid_token(token['access_token']) + validators.validate_orcid_token(token["access_token"]) token_valid = True except ValidationError: - messages.error(request, 'Validation Error: ORCID token validation failed.') + messages.error(request, "Validation Error: ORCID token validation failed.") token_valid = False except InvalidGrantError: - messages.error(request, 'Invalid Grant Error: authorization code may be expired or invalid.') + messages.error( + request, + "Invalid Grant Error: authorization code may be expired or invalid.", + ) token_valid = False if token_valid: orcid_profile, _ = Orcid.objects.get_or_create(user=request.user) - orcid_profile.orcid_id = token.get('orcid') - orcid_profile.name = token.get('name') - orcid_profile.access_token = token.get('access_token') - orcid_profile.refresh_token = token.get('refresh_token') - orcid_profile.token_type = token.get('token_type') - orcid_profile.token_scope = token.get('scope') - orcid_profile.token_expiration = token.get('expires_at') + orcid_profile.orcid_id = token.get("orcid") + orcid_profile.name = token.get("name") + orcid_profile.access_token = token.get("access_token") + orcid_profile.refresh_token = token.get("refresh_token") + orcid_profile.token_type = token.get("token_type") + orcid_profile.token_scope = token.get("scope") + orcid_profile.token_expiration = token.get("expires_at") orcid_profile.full_clean() orcid_profile.save() - return redirect('edit_orcid') + return redirect("edit_orcid") + @login_required def edit_password_complete(request): @@ -479,7 +544,7 @@ def edit_password_complete(request): After password has successfully been changed. Need this view because we can't control the edit password view to show a success message. """ - return render(request, 'user/edit_password_complete.html') + return render(request, "user/edit_password_complete.html") def public_profile(request, username): @@ -493,12 +558,20 @@ def public_profile(request, username): raise Http404() # get list of projects - projects = PublishedProject.objects.filter(authors__user=public_user).order_by('-publish_datetime') - + projects = PublishedProject.objects.filter(authors__user=public_user).order_by( + "-publish_datetime" + ) - return render(request, 'user/public_profile.html', { - 'public_user':public_user, 'profile':public_user.profile, - 'public_email':public_email, 'projects':projects}) + return render( + request, + "user/public_profile.html", + { + "public_user": public_user, + "profile": public_user.profile, + "public_email": public_email, + "projects": projects, + }, + ) def profile_photo(request, username): @@ -522,9 +595,9 @@ def register(request): """ user = request.user if user.is_authenticated: - return redirect('home') + return redirect("home") - if request.method == 'POST': + if request.method == "POST": form = forms.RegistrationForm(request=request, data=request.POST) if form.is_valid(): # Create the new user @@ -534,19 +607,17 @@ def register(request): form.full_clean() if form.is_valid(): raise - user = User.objects.get(username=form.data['username']) + user = User.objects.get(username=form.data["username"]) else: - uidb64 = force_str(urlsafe_base64_encode(force_bytes( - user.pk))) + uidb64 = force_str(urlsafe_base64_encode(force_bytes(user.pk))) token = default_token_generator.make_token(user) notify_account_registration(request, user, uidb64, token) - return render(request, 'user/register_done.html', { - 'email': user.email}) + return render(request, "user/register_done.html", {"email": user.email}) else: form = forms.RegistrationForm(request=request) - response = render(request, 'user/register.html', {'form': form}) + response = render(request, "user/register.html", {"form": form}) form.set_response_cookies(response) return response @@ -557,7 +628,7 @@ def user_settings(request): Settings. Redirect to default - settings/profile Don't call this 'settings' because there's an import called 'settings' """ - return redirect('edit_profile') + return redirect("edit_profile") @login_required @@ -581,14 +652,21 @@ def verify_email(request, uidb64, token): associated_email.save() if not user.is_credentialed: check_legacy_credentials(user, associated_email.email) - logger.info('User {0} verified another email {1}'.format(user.id, associated_email)) - messages.success(request, 'The email address {} has been verified.'.format( - associated_email)) - return redirect('edit_emails') + logger.info( + "User {0} verified another email {1}".format(user.id, associated_email) + ) + messages.success( + request, + "The email address {} has been verified.".format(associated_email), + ) + return redirect("edit_emails") - logger.warning('Invalid Verification Link') - return render(request, 'user/verify_email.html', - {'title':'Invalid Verification Link', 'isvalid':False}) + logger.warning("Invalid Verification Link") + return render( + request, + "user/verify_email.html", + {"title": "Invalid Verification Link", "isvalid": False}, + ) @login_required @@ -599,17 +677,16 @@ def edit_username(request): user = request.user form = forms.UsernameChangeForm(instance=user) - if request.method == 'POST': + if request.method == "POST": form = forms.UsernameChangeForm(instance=user, data=request.POST) if form.is_valid(): form.save() - messages.success(request, 'Your username has been updated.') + messages.success(request, "Your username has been updated.") else: user = User.objects.get(id=user.id) - return render(request, 'user/edit_username.html', {'form':form, - 'user':user}) + return render(request, "user/edit_username.html", {"form": form, "user": user}) @login_required @@ -633,21 +710,28 @@ def edit_credentialing(request): ticket_system_url = None applications = CredentialApplication.objects.filter(user=request.user) - current_application = applications.filter(status=CredentialApplication.Status.PENDING).first() + current_application = applications.filter( + status=CredentialApplication.Status.PENDING + ).first() - if request.method == 'POST' and 'withdraw_credentialing' in request.POST: + if request.method == "POST" and "withdraw_credentialing" in request.POST: if current_application: current_application.withdraw(responder=request.user) - return render(request, 'user/withdraw_credentialing_success.html') + return render(request, "user/withdraw_credentialing_success.html") else: - messages.error(request, 'The application has already been processed.') + messages.error(request, "The application has already been processed.") - return render(request, 'user/edit_credentialing.html', { - 'applications': applications, - 'pause_applications': pause_applications, - 'pause_message': pause_message, - 'current_application': current_application, - 'ticket_system_url': ticket_system_url}) + return render( + request, + "user/edit_credentialing.html", + { + "applications": applications, + "pause_applications": pause_applications, + "pause_message": pause_message, + "current_application": current_application, + "ticket_system_url": ticket_system_url, + }, + ) @login_required @@ -655,11 +739,15 @@ def user_credential_applications(request): """ All the credential applications made by a user """ - applications = CredentialApplication.objects.filter( - user=request.user).order_by('-application_datetime') + applications = CredentialApplication.objects.filter(user=request.user).order_by( + "-application_datetime" + ) - return render(request, 'user/user_credential_applications.html', - {'applications': applications, 'CredentialApplication': CredentialApplication}) + return render( + request, + "user/user_credential_applications.html", + {"applications": applications, "CredentialApplication": CredentialApplication}, + ) @login_required @@ -669,23 +757,31 @@ def credential_application(request): """ user = request.user if user.is_credentialed or CredentialApplication.objects.filter( - user=user, status=CredentialApplication.Status.PENDING): - return redirect('edit_credentialing') + user=user, status=CredentialApplication.Status.PENDING + ): + return redirect("edit_credentialing") if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: raise ServiceUnavailable() - if request.method == 'POST': + if request.method == "POST": # We use the individual forms to render the errors in the template # if not all valid - personal_form = forms.PersonalCAF(user=user, data=request.POST, prefix="application") + personal_form = forms.PersonalCAF( + user=user, data=request.POST, prefix="application" + ) research_form = forms.ResearchCAF(data=request.POST, prefix="application") - reference_form = forms.ReferenceCAF(data=request.POST, prefix="application", user=user) + reference_form = forms.ReferenceCAF( + data=request.POST, prefix="application", user=user + ) - form = forms.CredentialApplicationForm(user=user, data=request.POST, - files=request.FILES, prefix="application") + form = forms.CredentialApplicationForm( + user=user, data=request.POST, files=request.FILES, prefix="application" + ) - if (personal_form.is_valid() and reference_form.is_valid() and form.is_valid()) and research_form.is_valid(): + if ( + personal_form.is_valid() and reference_form.is_valid() and form.is_valid() + ) and research_form.is_valid(): application = form.save() credential_application_request(request, application) @@ -694,9 +790,9 @@ def credential_application(request): user=request.user, ) - return render(request, 'user/credential_application_complete.html') + return render(request, "user/credential_application_complete.html") else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") else: personal_form = forms.PersonalCAF(user=user, prefix="application") reference_form = forms.ReferenceCAF(prefix="application", user=user) @@ -707,15 +803,17 @@ def credential_application(request): return render( request, - 'user/credential_application.html', + "user/credential_application.html", { - 'form': form, - 'personal_form': personal_form, - 'reference_form': reference_form, - 'research_form': research_form, - 'code_of_conduct': code_of_conduct, + "form": form, + "personal_form": personal_form, + "reference_form": reference_form, + "research_form": research_form, + "code_of_conduct": code_of_conduct, }, ) + + @login_required def edit_course(request): """ @@ -726,75 +824,83 @@ def edit_course(request): else: ticket_system_url = None - if request.method == 'POST': + if request.method == "POST": training_form = forms.TrainingForm( - user=request.user, data=request.POST, files=request.FILES, training_type=request.POST.get('training_type') + user=request.user, + data=request.POST, + files=request.FILES, + training_type=request.POST.get("training_type"), ) if training_form.is_valid(): training_form.save() - messages.success(request, 'The training has been submitted successfully.') + messages.success(request, "The training has been submitted successfully.") training_application_request(request, training_form) training_form = forms.TrainingForm(user=request.user) else: - messages.error(request, 'Invalid submission. Check the errors below.') + messages.error(request, "Invalid submission. Check the errors below.") else: - training_type = request.GET.get('trainingType') + training_type = request.GET.get("trainingType") if training_type: - training_form = forms.TrainingForm(user=request.user, training_type=training_type) + training_form = forms.TrainingForm( + user=request.user, training_type=training_type + ) else: training_form = forms.TrainingForm(user=request.user) return render( request, - 'user/edit_course.html', - { - 'training_form': training_form, - 'ticket_system_url': ticket_system_url}, + "user/edit_course.html", + {"training_form": training_form, "ticket_system_url": ticket_system_url}, ) + @login_required def edit_training(request): """ Training settings page. """ - if settings.TICKET_SYSTEM_URL: - ticket_system_url = settings.TICKET_SYSTEM_URL - else: - ticket_system_url = None - if request.method == 'POST': + if request.method == "POST": training_form = forms.TrainingForm( - user=request.user, data=request.POST, files=request.FILES, training_type=request.POST.get('training_type') + user=request.user, + data=request.POST, + files=request.FILES, + training_type=request.POST.get("training_type"), ) if training_form.is_valid(): training_form.save() - messages.success(request, 'The training has been submitted successfully.') + messages.success(request, "The training has been submitted successfully.") training_application_request(request, training_form) training_form = forms.TrainingForm(user=request.user) else: - messages.error(request, 'Invalid submission. Check the errors below.') + messages.error(request, "Invalid submission. Check the errors below.") else: - training_type = request.GET.get('trainingType') + training_type = request.GET.get("trainingType") if training_type: - training_form = forms.TrainingForm(user=request.user, training_type=training_type) + training_form = forms.TrainingForm( + user=request.user, training_type=training_type + ) else: training_form = forms.TrainingForm(user=request.user) - training = Training.objects.select_related('training_type').filter(user=request.user).order_by('-status') + training = ( + Training.objects.select_related("training_type") + .filter(user=request.user) + .order_by("-status") + ) training_by_status = { - 'under review': training.get_review(), - 'active': training.get_valid(), - 'expired': training.get_expired(), - 'rejected': training.get_rejected(), - } + "under review": training.get_review(), + "active": training.get_valid(), + "expired": training.get_expired(), + "rejected": training.get_rejected(), + } return render( request, - 'user/edit_training.html', - { - 'training_by_status': training_by_status}, + "user/edit_training.html", + {"training_by_status": training_by_status}, ) @@ -802,12 +908,12 @@ def edit_training(request): def edit_training_detail(request, training_id): training = get_object_or_404(Training, pk=training_id, user=request.user) - if request.method == 'POST': - if request.POST.get('withdraw') is not None and not training.is_withdrawn(): + if request.method == "POST": + if request.POST.get("withdraw") is not None and not training.is_withdrawn(): training.withdraw() - messages.success(request, 'The training has been withdrawn.') + messages.success(request, "The training has been withdrawn.") - return render(request, 'user/edit_training_detail.html', {'training': training}) + return render(request, "user/edit_training_detail.html", {"training": training}) @login_required @@ -816,7 +922,7 @@ def training_report(request, training_id): Serve a training report file """ trainings = Training.objects.all() - if not request.user.has_perm('user.change_credentialapplication'): + if not request.user.has_perm("user.change_credentialapplication"): trainings = trainings.filter(user=request.user) training = get_object_or_404(trainings, id=training_id) @@ -834,31 +940,39 @@ def credential_reference(request, application_slug): Page for a reference to verify or reject a credential application """ application = CredentialApplication.objects.filter( - slug=application_slug, reference_response_datetime=None) + slug=application_slug, reference_response_datetime=None + ) if not application: - return redirect('/') + return redirect("/") application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == 'POST': + if request.method == "POST": form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete(request, application, - include_comments=False) + process_credential_complete( + request, application, include_comments=False + ) - response = 'verifying' if application.reference_response == 2 else 'denying' - return render(request, 'user/credential_reference_complete.html', - {'response': response, 'application': application}) + response = "verifying" if application.reference_response == 2 else "denying" + return render( + request, + "user/credential_reference_complete.html", + {"response": response, "application": application}, + ) else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") - return render(request, 'user/credential_reference.html', - {'form': form, 'application': application}) + return render( + request, + "user/credential_reference.html", + {"form": form, "application": application}, + ) def credential_reference_verification(request, application_slug, verification_token): @@ -867,31 +981,42 @@ def credential_reference_verification(request, application_slug, verification_to `credential_reference` that uses an additional verification token. """ application = CredentialApplication.objects.filter( - slug=application_slug, reference_response_datetime=None, status=0, - reference_verification_token=verification_token) + slug=application_slug, + reference_response_datetime=None, + status=0, + reference_verification_token=verification_token, + ) if not application: - return redirect('/') + return redirect("/") application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == 'POST': + if request.method == "POST": form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete(request, application, - include_comments=False) + process_credential_complete( + request, application, include_comments=False + ) - response = 'verifying' if application.reference_response == 2 else 'denying' - return render(request, 'user/credential_reference_complete.html', - {'response': response, 'application': application}) + response = "verifying" if application.reference_response == 2 else "denying" + return render( + request, + "user/credential_reference_complete.html", + {"response": response, "application": application}, + ) else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") - return render(request, 'user/credential_reference.html', {'form': form, 'application': application}) + return render( + request, + "user/credential_reference.html", + {"form": form, "application": application}, + ) @login_required @@ -902,15 +1027,16 @@ def edit_cloud(request): user = request.user cloud_info = CloudInformation.objects.get_or_create(user=user)[0] form = forms.CloudForm(instance=cloud_info) - if request.method == 'POST': + if request.method == "POST": form = forms.CloudForm(instance=cloud_info, data=request.POST) if form.is_valid(): form.save() - messages.success(request, 'Your cloud information has been saved.') + messages.success(request, "Your cloud information has been saved.") else: - messages.error(request, 'Invalid submission. See errors below.') + messages.error(request, "Invalid submission. See errors below.") + + return render(request, "user/edit_cloud.html", {"form": form, "user": user}) - return render(request, 'user/edit_cloud.html', {'form':form, 'user':user}) @login_required def view_agreements(request): @@ -918,19 +1044,24 @@ def view_agreements(request): View a list of signed agreements in the user profile. """ user = request.user - signed_agreements = DUASignature.objects.filter(user=user).order_by('-sign_datetime') - signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by('-sign_datetime') + signed_agreements = DUASignature.objects.filter(user=user).order_by( + "-sign_datetime" + ) + signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by( + "-sign_datetime" + ) return render( request, - 'user/view_agreements.html', + "user/view_agreements.html", { - 'user': user, - 'signed_agreements': signed_agreements, - 'signed_code_of_conducts': signed_code_of_conducts, + "user": user, + "signed_agreements": signed_agreements, + "signed_code_of_conducts": signed_code_of_conducts, }, ) + @login_required def view_signed_agreement(request, dua_signature_id): """ @@ -939,5 +1070,6 @@ def view_signed_agreement(request, dua_signature_id): user = request.user signed = get_object_or_404(DUASignature, user=user, id=dua_signature_id) - return render(request, 'user/view_signed_agreement.html', - {'user': user, 'signed': signed}) + return render( + request, "user/view_signed_agreement.html", {"user": user, "signed": signed} + ) From ae10aa7cb81e2dfffaf16c31047efbf1196db3fd Mon Sep 17 00:00:00 2001 From: Brian Gow Date: Wed, 6 Sep 2023 16:56:54 -0400 Subject: [PATCH 40/82] default title assignment --- physionet-django/project/modelcomponents/activeproject.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 397809af9e..4b69658547 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -571,14 +571,13 @@ def publish(self, slug=None, make_zip=True, title=None): previous_published_projects = self.core_project.publishedprojects.all() slug = previous_published_projects.first().slug - title = previous_published_projects.first().title if slug != published_project.slug: raise ValueError( {"message": "The published project has different slugs."}) # Set the slug if specified published_project.slug = slug or self.slug - published_project.title = title or self.title + published_project.title = self.title published_project.doi = self.doi # Change internal links (that point to files within From 82aeaa3478031d6776de6c1107fea05b7c5a8067 Mon Sep 17 00:00:00 2001 From: Brian Gow Date: Thu, 7 Sep 2023 10:57:00 -0400 Subject: [PATCH 41/82] remove unused parameter from activeproject publish --- physionet-django/project/modelcomponents/activeproject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 4b69658547..7eed7e6f21 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -537,7 +537,7 @@ def clear_files(self): """ self.files.rmtree(self.file_root()) - def publish(self, slug=None, make_zip=True, title=None): + def publish(self, slug=None, make_zip=True): """ Create a published version of this project and update the submission status. From 3b87352e2e80428ca0c96a2b89dfc3b86c51f202 Mon Sep 17 00:00:00 2001 From: Brian Gow Date: Thu, 7 Sep 2023 12:38:53 -0400 Subject: [PATCH 42/82] allow access policy changes --- physionet-django/project/forms.py | 2 ++ physionet-django/project/modelcomponents/activeproject.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/physionet-django/project/forms.py b/physionet-django/project/forms.py index 7dae854b23..9a6812d672 100644 --- a/physionet-django/project/forms.py +++ b/physionet-django/project/forms.py @@ -869,11 +869,13 @@ def __init__(self, *args, **kwargs): self.fields['required_trainings'].disabled = True self.fields['required_trainings'].required = False self.fields['required_trainings'].widget = forms.HiddenInput() + self.initial['required_trainings'] = '' if self.access_policy == AccessPolicy.OPEN: self.fields['dua'].disabled = True self.fields['dua'].required = False self.fields['dua'].widget = forms.HiddenInput() + self.initial['dua'] = '' if not self.editable: for field in self.fields.values(): diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 9cec57f4db..b4f9a0afdb 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -355,7 +355,6 @@ def fake_delete(self): """ self.archive(archive_reason=1) - def check_integrity(self): """ Run integrity tests on metadata fields and return whether the @@ -411,6 +410,10 @@ def check_integrity(self): if self.access_policy != AccessPolicy.OPEN and self.dua is None: self.integrity_errors.append('You have to choose one of the data use agreements.') + if self.access_policy in {AccessPolicy.CREDENTIALED, + AccessPolicy.CONTRIBUTOR_REVIEW} and self.required_trainings is None: + self.integrity_errors.append('You have to choose a required training.') + if self.integrity_errors: return False else: From 41c9439f2af0082b58b411c85d97d7c919ed4357 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Mon, 11 Sep 2023 12:51:46 -0400 Subject: [PATCH 43/82] Restructred the training information into training and certification --- .../templates/user/edit_certification.html | 30 ++++++++ .../user/templates/user/edit_course.html | 51 ------------- .../user/templates/user/edit_training.html | 73 ++++++++----------- .../user/templatetags/user_templatetags.py | 2 +- physionet-django/user/urls.py | 4 +- physionet-django/user/views.py | 12 +-- 6 files changed, 71 insertions(+), 101 deletions(-) create mode 100644 physionet-django/user/templates/user/edit_certification.html delete mode 100644 physionet-django/user/templates/user/edit_course.html diff --git a/physionet-django/user/templates/user/edit_certification.html b/physionet-django/user/templates/user/edit_certification.html new file mode 100644 index 0000000000..584fb045df --- /dev/null +++ b/physionet-django/user/templates/user/edit_certification.html @@ -0,0 +1,30 @@ +{% extends "user/settings.html" %} + +{% block title %}{{ SITE_NAME }} Training{% endblock %} + +{% block main_content %} +

    Certification

    +
    +

    To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

    +
    To submit a new completion report, please go to the Training page.
    +
    + + {% for status, group in training_by_status.items %} +
    +

    {{ status|capfirst }}

    +
      + {% for course in group %} +
    • +
      + {{ course.training_type.name }} +
      + View +
    • + {% empty %} +

      N/A

      + {% endfor %} +
    +
    + {% endfor %} + +{% endblock %} diff --git a/physionet-django/user/templates/user/edit_course.html b/physionet-django/user/templates/user/edit_course.html deleted file mode 100644 index 93e35632be..0000000000 --- a/physionet-django/user/templates/user/edit_course.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "user/settings.html" %} - -{% block title %}{{ SITE_NAME }} Courses{% endblock %} - -{% block main_content %} -

    Courses

    -
    -

    To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

    -
    -

    Submit evidence of a completed course

    - -

    Please use the form below to submit a new completion report.

    -

    For CITI training, please refer to our step-by-step instructions and - be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or - Specimens Only Research" course (with HIPAA module). - {% if ticket_system_url %}To raise a support request, please click here. - {% endif %}

    - -
    - {% include "descriptive_inline_form_snippet.html" with form=training_form %} - - -
    - -{% endblock %} - -{% block local_js_bottom %} - -{% endblock %} diff --git a/physionet-django/user/templates/user/edit_training.html b/physionet-django/user/templates/user/edit_training.html index d8a5ac6f48..807b626995 100644 --- a/physionet-django/user/templates/user/edit_training.html +++ b/physionet-django/user/templates/user/edit_training.html @@ -6,50 +6,39 @@

    Training


    To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

    - - {% for status, group in training_by_status.items %} - {% if not status == 'under review' %} -
    -

    {{ status|capfirst }}

    -
      - {% for course in group %} -
    • -
      - {{ course.training_type.name }} -
      - View -
    • - {% empty %} -

      N/A

      - {% endfor %} -
    -
    - {% endif %} - {% endfor %} -
    - - {% for status, group in training_by_status.items %} - {% if status == 'under review' and group %} -
    -
    - The following training is under review: -
      - {% for course in group %} -
    • -
      - {{ course.training_type.name }} -
      - View -
    • - {% endfor %} +

      Submit evidence of a completed Training

      + +

      Please use the form below to submit a new completion report.

      +

      For CITI training, please refer to our step-by-step instructions and + be sure to upload the training report, not the certificate. Please ensure that you complete the "Data or + Specimens Only Research" course (with HIPAA module). + {% if ticket_system_url %}To raise a support request, please click here. + {% endif %}

      + +
      + {% include "descriptive_inline_form_snippet.html" with form=training_form %} + +
    -
    - {% endif %} - {% endfor %} - -
    To submit a new completion report, please go to the Courses page.
    +
    + {% endblock %} diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index f86c6e4108..c24199d2f2 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -13,8 +13,8 @@ def settings_tabs(hide_password_settings: bool): "Cloud", "ORCID", "Credentialing", - "Course", "Training", + "Certification", "Agreements", ] if not hide_password_settings: diff --git a/physionet-django/user/urls.py b/physionet-django/user/urls.py index 8547e7bd11..8723f37e62 100644 --- a/physionet-django/user/urls.py +++ b/physionet-django/user/urls.py @@ -25,8 +25,10 @@ views.user_credential_applications, name="user_credential_applications", ), + path( + "settings/certification/", views.edit_certification, name="edit_certification" + ), path("settings/training/", views.edit_training, name="edit_training"), - path("settings/course/", views.edit_course, name="edit_course"), path( "settings/training//", views.edit_training_detail, diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 4875a02fd8..7888f83ca4 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -815,9 +815,9 @@ def credential_application(request): @login_required -def edit_course(request): +def edit_training(request): """ - Courses settings page. + Trainings settings page. """ if settings.TICKET_SYSTEM_URL: ticket_system_url = settings.TICKET_SYSTEM_URL @@ -850,15 +850,15 @@ def edit_course(request): return render( request, - "user/edit_course.html", + "user/edit_training.html", {"training_form": training_form, "ticket_system_url": ticket_system_url}, ) @login_required -def edit_training(request): +def edit_certification(request): """ - Training settings page. + Certifications page. """ if request.method == "POST": @@ -899,7 +899,7 @@ def edit_training(request): return render( request, - "user/edit_training.html", + "user/edit_certification.html", {"training_by_status": training_by_status}, ) From fc9bea6d2f93e211f5281f5a9ae94acb4add6741 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Mon, 11 Sep 2023 12:57:13 -0400 Subject: [PATCH 44/82] Changinh site header for cerification tab --- physionet-django/user/templates/user/edit_certification.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physionet-django/user/templates/user/edit_certification.html b/physionet-django/user/templates/user/edit_certification.html index 584fb045df..715a0cfebb 100644 --- a/physionet-django/user/templates/user/edit_certification.html +++ b/physionet-django/user/templates/user/edit_certification.html @@ -1,6 +1,6 @@ {% extends "user/settings.html" %} -{% block title %}{{ SITE_NAME }} Training{% endblock %} +{% block title %}{{ SITE_NAME }} Certification{% endblock %} {% block main_content %}

    Certification

    From 40ddfca730d905f09cc5e0e1f4cec4c9877911fe Mon Sep 17 00:00:00 2001 From: Karol Szuster Date: Tue, 12 Sep 2023 09:39:10 +0200 Subject: [PATCH 45/82] Bump `hdn-research-environment` to `2.3.8` --- poetry.lock | 6 +++--- pyproject.toml | 2 +- requirements.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 42d230d59c..b9917a877c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -754,12 +754,12 @@ protobuf = ">=3.12.0" [[package]] name = "hdn-research-environment" -version = "2.3.6" +version = "2.3.8" description = "A Django app for supporting cloud-native research environments" optional = false python-versions = ">=3.7" files = [ - {file = "hdn-research-environment-2.3.6.tar.gz", hash = "sha256:b28d67db8ba4cf82e1362aefde47d483b549e8f7b96d1d4f56baebe0b76dfc31"}, + {file = "hdn-research-environment-2.3.8.tar.gz", hash = "sha256:371b33950e3c1598b650edefd42fa19a3532d2ca815dd42f495f0460c57df97f"}, ] [package.dependencies] @@ -1453,4 +1453,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "2297d8c676de9b2ac1455227f408f5a45f51a249993c1f72ea56221882937464" +content-hash = "68860ca96b3262f70f6624a0504b6f9509464a66b8cc0fe087fba6a15f97e14c" diff --git a/pyproject.toml b/pyproject.toml index 4a3fbe318c..fed11d39d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ zxcvbn = "^4.4.28" certifi = "^2023.7.22" setuptools = "65.5.1" django-js-asset = "2.0.0" -hdn-research-environment = "2.3.6" +hdn-research-environment = "2.3.8" django-oauth-toolkit = "^2.2.0" django-cors-headers = "^3.14.0" diff --git a/requirements.txt b/requirements.txt index 8bb56ed7d4..873d4dfda5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -297,8 +297,8 @@ grpcio==1.53.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:f3e837d29f0e1b9d6e7b29d569e2e9b0da61889e41879832ea15569c251c303a \ --hash=sha256:fa8eaac75d3107e3f5465f2c9e3bbd13db21790c6e45b7de1756eba16b050aca \ --hash=sha256:fdc6191587de410a184550d4143e2b24a14df495c86ca15e59508710681690ac -hdn-research-environment==2.3.6 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:b28d67db8ba4cf82e1362aefde47d483b549e8f7b96d1d4f56baebe0b76dfc31 +hdn-research-environment==2.3.8 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:371b33950e3c1598b650edefd42fa19a3532d2ca815dd42f495f0460c57df97f html2text==2018.1.9 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:490db40fe5b2cd79c461cf56be4d39eb8ca68191ae41ba3ba79f6cb05b7dd662 \ --hash=sha256:627514fb30e7566b37be6900df26c2c78a030cc9e6211bda604d8181233bcdd4 From d74fa69a510d7c9520d2307dfff564efe139a27f Mon Sep 17 00:00:00 2001 From: Michael Scanlan Date: Tue, 12 Sep 2023 09:33:21 -0400 Subject: [PATCH 46/82] temp --- .../templates/console/console_navbar.html | 1 + .../console/event_archive_event.html | 55 +++++++++++++++++++ physionet-django/console/urls.py | 2 + physionet-django/console/views.py | 14 +++++ 4 files changed, 72 insertions(+) create mode 100644 physionet-django/console/templates/console/event_archive_event.html diff --git a/physionet-django/console/templates/console/console_navbar.html b/physionet-django/console/templates/console/console_navbar.html index 119ec20b83..caca07bb20 100644 --- a/physionet-django/console/templates/console/console_navbar.html +++ b/physionet-django/console/templates/console/console_navbar.html @@ -130,6 +130,7 @@ {% endif %} diff --git a/physionet-django/console/templates/console/event_archive_event.html b/physionet-django/console/templates/console/event_archive_event.html new file mode 100644 index 0000000000..0ea5dc01b0 --- /dev/null +++ b/physionet-django/console/templates/console/event_archive_event.html @@ -0,0 +1,55 @@ +{% extends "console/base_console.html" %} + +{% load static %} + +{% block title %}Archived Events{% endblock %} + +{% block local_css %} + +{% endblock %} + +{% load console_templatetags %} + +{% block content %} +
    +
    + Archived Events {{ events.paginator.count }} +
    +
    +
    + + + + + + + + + + + + + + + + {% for event in events %} + + + + + + + + + + + + {% endfor %} + +
    EventEvent TypeHostCreatedStartedEndedCredentialingTrainingManage
    {{ event.title }}{{ event.get_category_display|title }}{{ event.host }}{{ event.added_datetime|date }}{{ event.start_date }}{{ event.end_date }}View listView listManage
    + {% include "console/pagination.html" with pagination=events %} +
    +
    +
    +{% endblock %} + diff --git a/physionet-django/console/urls.py b/physionet-django/console/urls.py index 4ce629754f..be1da76e2a 100644 --- a/physionet-django/console/urls.py +++ b/physionet-django/console/urls.py @@ -148,6 +148,8 @@ # Lists of event components path('event/', views.event, name='event'), + path('archive_event/', views.archive_event, + name='archive_event'), path('event/manage/', views.event_management, name='event_management'), path('event_agreements/', views.event_agreement_list, name='event_agreement_list'), path('event_agreements//', views.event_agreement_detail, name='event_agreement_detail'), diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index bb34af6c88..8fa1f9b7e2 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -2952,6 +2952,20 @@ def event(request): }) +@permission_required('user.view_archive_event', raise_exception=True) +def archive_event(request): + """ + List of archived events + """ + archive_event = Event.objects.filter(all).order_by('archive_datetime') + archive_event = paginate(request, archive_event, 50) + + return render(request, 'console/event_archive_event.html', + {'event_archive_event': archive_event, + 'events_nav': True + }) + + @permission_required('user.view_all_events', raise_exception=True) def event_management(request, event_slug): """ From 8cd66603eaec0efe0af3eb4eb65276e694a889cb Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 12 Sep 2023 10:39:35 -0400 Subject: [PATCH 47/82] Added the ref to certification page --- physionet-django/user/templates/user/edit_training.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/physionet-django/user/templates/user/edit_training.html b/physionet-django/user/templates/user/edit_training.html index 807b626995..f397848c8d 100644 --- a/physionet-django/user/templates/user/edit_training.html +++ b/physionet-django/user/templates/user/edit_training.html @@ -5,7 +5,9 @@ {% block main_content %}

    Training


    -

    To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

    +

    To gain access to certain datasets on {{ SITE_NAME }}, you are required to demonstrate that you have completed relevant training. You can find specific training requirements in the "Files" section of the project description.

    + +
    You can view the status of your training submissions on the Certification page.

    Submit evidence of a completed Training

    From d4c93d78fa8c376b07fe586717c9669926692605 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Tue, 12 Sep 2023 11:53:25 -0400 Subject: [PATCH 48/82] notification.utility: avoid load-time dependencies on models. The notification.utility module is imported by console.tasks, and we want to be able to import that module early (for the associated_task decorator). Avoid using "from" imports to avoid import loops. --- physionet-django/notification/utility.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/physionet-django/notification/utility.py b/physionet-django/notification/utility.py index 078181e548..0904283dd6 100644 --- a/physionet-django/notification/utility.py +++ b/physionet-django/notification/utility.py @@ -12,8 +12,8 @@ from django.utils import timezone from django.urls import reverse -from project.models import DataAccessRequest, PublishedProject, AccessPolicy -from user.models import CredentialApplication +import project.models +import user.models RESPONSE_ACTIONS = {0:'rejected', 1:'accepted'} @@ -211,8 +211,9 @@ def resubmit_notify(project, comments): @cache def example_credentialed_access_project(): - return PublishedProject.objects.filter( - access_policy=AccessPolicy.CREDENTIALED, is_latest_version=True, + return project.models.PublishedProject.objects.filter( + access_policy=project.models.AccessPolicy.CREDENTIALED, + is_latest_version=True, ).first() @@ -644,7 +645,7 @@ def process_credential_complete(request, application, include_comments=True): body = loader.render_to_string( 'notification/email/process_credential_complete.html', { 'application': application, - 'CredentialApplication': CredentialApplication, + 'CredentialApplication': user.models.CredentialApplication, 'applicant_name': applicant_name, 'domain': get_current_site(request), 'example_project': example_credentialed_access_project(), @@ -812,7 +813,7 @@ def confirm_user_data_access_request(data_access_request, request_protocol, subject = f"{settings.SITE_NAME} Data Access Request" due_date = timezone.now() + timezone.timedelta( - days=DataAccessRequest.DATA_ACCESS_REQUESTS_DAY_LIMIT) + days=project.models.DataAccessRequest.DATA_ACCESS_REQUESTS_DAY_LIMIT) body = loader.render_to_string( 'notification/email/confirm_user_data_access_request.html', { From c50abf78369d6a72e5c9a2c667e8e8f2136b1a30 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Tue, 12 Sep 2023 11:11:21 -0400 Subject: [PATCH 49/82] move_files_as_readonly: mark as an associated task. When a project is published, certain tasks need to be performed (setting read-only permissions and creating the list of SHA-256 hashes.) These are handled by the background task move_files_as_readonly which runs *after* the project is "published". (This is not a great design and should be reconsidered.) We should not be triggering other background tasks on the project while this task is running (or if the task has been delayed for some reason.) In particular, we don't want to send files to cloud mirrors or generate a zip archive until after the SHA256SUMS.txt file has been generated. Mark the move_files_as_readonly task as an "associated task" linked to the PublishedProject, so that these actions will be disabled in the admin console (see console.views.manage_published_project) while the task is pending. --- physionet-django/project/modelcomponents/activeproject.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index 397809af9e..ae7befc7bf 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -15,6 +15,8 @@ from django.urls import reverse from django.utils import timezone from django.utils.html import strip_tags + +from console.tasks import associated_task from physionet.settings.base import StorageTypes from project.modelcomponents.access import AccessPolicy from project.modelcomponents.archivedproject import ArchivedProject @@ -34,6 +36,7 @@ LOGGER = logging.getLogger(__name__) +@associated_task(PublishedProject, 'pid') @background() def move_files_as_readonly(pid, dir_from, dir_to, make_zip): """ From 122b71ec1d63c7c127faf570c79e60731052eeb9 Mon Sep 17 00:00:00 2001 From: Rutvik Solanki Date: Tue, 12 Sep 2023 13:44:00 -0400 Subject: [PATCH 50/82] Fixed the styling changes Fixed the styling changes --- .../user/templatetags/user_templatetags.py | 21 +- physionet-django/user/views.py | 550 +++++++----------- 2 files changed, 220 insertions(+), 351 deletions(-) diff --git a/physionet-django/user/templatetags/user_templatetags.py b/physionet-django/user/templatetags/user_templatetags.py index c24199d2f2..ff6efa1fe7 100644 --- a/physionet-django/user/templatetags/user_templatetags.py +++ b/physionet-django/user/templatetags/user_templatetags.py @@ -4,24 +4,15 @@ register = template.Library() -@register.inclusion_tag("user/settings_tabs.html") +@register.inclusion_tag('user/settings_tabs.html') def settings_tabs(hide_password_settings: bool): - default_tabs = [ - "Profile", - "Emails", - "Username", - "Cloud", - "ORCID", - "Credentialing", - "Training", - "Certification", - "Agreements", - ] + default_tabs = ['Profile', 'Emails', 'Username', 'Cloud', 'ORCID', 'Credentialing', 'Training', + 'Certification', 'Agreements'] if not hide_password_settings: - default_tabs.insert(1, "Password") - return {"settings_tabs": default_tabs} + default_tabs.insert(1, 'Password') + return {'settings_tabs': default_tabs} -@register.filter(name="has_group") +@register.filter(name='has_group') def has_group(user, group_name): return user.groups.filter(name=group_name).exists() diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 7888f83ca4..fab5386d72 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -63,30 +63,30 @@ logger = logging.getLogger(__name__) -@method_decorator(allow_post_during_maintenance, "dispatch") +@method_decorator(allow_post_during_maintenance, 'dispatch') class LoginView(auth_views.LoginView): - template_name = "user/login.html" + template_name = 'user/login.html' authentication_form = forms.LoginForm redirect_authenticated_user = True -@method_decorator(allow_post_during_maintenance, "dispatch") +@method_decorator(allow_post_during_maintenance, 'dispatch') class SSOLoginView(auth_views.LoginView): - template_name = "sso/login.html" + template_name = 'sso/login.html' authentication_form = forms.LoginForm redirect_authenticated_user = True def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) try: - login_static_page = StaticPage.objects.get(url="/about/login/") + login_static_page = StaticPage.objects.get(url='/about/login/') instruction_sections = Section.objects.filter(static_page=login_static_page) except StaticPage.DoesNotExist: instruction_sections = [] sso_extra_context = { - "sso_login_button_text": settings.SSO_LOGIN_BUTTON_TEXT, - "login_instruction_sections": instruction_sections, + 'sso_login_button_text': settings.SSO_LOGIN_BUTTON_TEXT, + 'login_instruction_sections': instruction_sections, } return {**context, **sso_extra_context} @@ -97,33 +97,33 @@ class LogoutView(auth_views.LogoutView): # Request password reset class PasswordResetView(auth_views.PasswordResetView): - template_name = "user/reset_password_request.html" - success_url = reverse_lazy("reset_password_sent") - email_template_name = "user/email/reset_password_email.html" - extra_email_context = {"SITE_NAME": settings.SITE_NAME} + template_name = 'user/reset_password_request.html' + success_url = reverse_lazy('reset_password_sent') + email_template_name = 'user/email/reset_password_email.html' + extra_email_context = {'SITE_NAME': settings.SITE_NAME} # Page shown after reset email has been sent class PasswordResetDoneView(auth_views.PasswordResetDoneView): - template_name = "user/reset_password_sent.html" + template_name = 'user/reset_password_sent.html' # Prompt user to enter new password and carry out password reset (if # url is valid) -@method_decorator(disallow_during_maintenance, "dispatch") +@method_decorator(disallow_during_maintenance, 'dispatch') class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): - template_name = "user/reset_password_confirm.html" - success_url = reverse_lazy("reset_password_complete") + template_name = 'user/reset_password_confirm.html' + success_url = reverse_lazy('reset_password_complete') # Password reset successfully carried out class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): - template_name = "user/reset_password_complete.html" + template_name = 'user/reset_password_complete.html' class PasswordChangeView(auth_views.PasswordChangeView): - success_url = reverse_lazy("edit_password_complete") - template_name = "user/edit_password.html" + success_url = reverse_lazy('edit_password_complete') + template_name = 'user/edit_password.html' login = LoginView.as_view() @@ -136,7 +136,7 @@ class PasswordChangeView(auth_views.PasswordChangeView): edit_password = PasswordChangeView.as_view() -@sensitive_post_parameters("password1", "password2") +@sensitive_post_parameters('password1', 'password2') @disallow_during_maintenance def activate_user(request, uidb64, token): """ @@ -144,10 +144,10 @@ def activate_user(request, uidb64, token): The user will create the password at this stage and then logged in. """ - activation_session_token = "_activation_reset_token" - activation_url_token = "user-activation" + activation_session_token = '_activation_reset_token' + activation_url_token = 'user-activation' title = "Account activation" - context = {"title": "Invalid Activation Link", "isvalid": False} + context = {'title': 'Invalid Activation Link', 'isvalid': False} try: uid = force_str(urlsafe_base64_decode(uidb64)) @@ -156,18 +156,17 @@ def activate_user(request, uidb64, token): user = None if user and user.is_active: - messages.success(request, "The account is active.") - return redirect("login") + messages.success(request, 'The account is active.') + return redirect('login') - if request.method == "GET": + if request.method == 'GET': if token == activation_url_token: session_token = request.session.get(activation_session_token) if default_token_generator.check_token(user, session_token): # If the token is valid, display the password reset form. form = forms.ActivationForm(user=user) - return render( - request, "user/activate_user.html", {"form": form, "title": title} - ) + return render(request, 'user/activate_user.html', { + 'form': form, 'title': title}) else: if default_token_generator.check_token(user, token): # Store the token in the session and redirect to the @@ -181,11 +180,9 @@ def activate_user(request, uidb64, token): if token == activation_url_token: session_token = request.session.get(activation_session_token) form = forms.ActivationForm(user=user, data=request.POST) - if form.is_valid() and default_token_generator.check_token( - user, session_token - ): + if form.is_valid() and default_token_generator.check_token(user, session_token): with transaction.atomic(): - user.set_password(form.cleaned_data["password1"]) + user.set_password(form.cleaned_data['password1']) user.is_active = True # Check legacy credentials check_legacy_credentials(user, user.email) @@ -195,15 +192,14 @@ def activate_user(request, uidb64, token): email.is_verified = True email.save() request.session.pop(activation_session_token) - logger.info("User activated - {0}".format(user.email)) - messages.success(request, "The account has been activated.") + logger.info('User activated - {0}'.format(user.email)) + messages.success(request, 'The account has been activated.') login(request, user) - return redirect("project_home") - return render( - request, "user/activate_user.html", {"form": form, "title": title} - ) + return redirect('project_home') + return render(request, 'user/activate_user.html', {'form': form, + 'title': title}) - return render(request, "user/activate_user_complete.html", context) + return render(request, 'user/activate_user_complete.html', context) def check_legacy_credentials(user, email): @@ -211,13 +207,14 @@ def check_legacy_credentials(user, email): Check whether a user has already beeen credentialed on the old pn site. If so, credential their account and mark the migration. """ - legacy_credential = LegacyCredential.objects.filter(email=email, migrated=False) + legacy_credential = LegacyCredential.objects.filter(email=email, + migrated=False) if legacy_credential: legacy_credential = legacy_credential.get() user.is_credentialed = True # All of them are mimic credentialed - month, day, year = legacy_credential.mimic_approval_date.split("/") - dt = datetime(int(year), int(month), int(day)) + month, day, year = legacy_credential.mimic_approval_date.split('/') + dt = datetime(int(year), int(month), int(day)) dt = pytz.timezone(timezone.get_default_timezone_name()).localize(dt) user.credential_datetime = dt legacy_credential.migrated = True @@ -226,7 +223,6 @@ def check_legacy_credentials(user, email): legacy_credential.save() user.save() - def remove_email(request, email_id): "Remove a non-primary email associated with a user" user = request.user @@ -236,53 +232,36 @@ def remove_email(request, email_id): email = associated_email.email associated_email.delete() logger.info(f"Removed email {email} from user {user.id}") - messages.success( - request, - f"The email address ({email}) has been removed from your account.", - ) + messages.success(request, f"The email address ({email}) has been removed from your account.") except AssociatedEmail.DoesNotExist: - messages.success( - request, "The email address has been removed from your account." - ) + messages.success(request, "The email address has been removed from your account.") def set_primary_email(request, primary_email_form): "Set the selected email as the primary email" user = request.user if primary_email_form.is_valid(): - associated_email = primary_email_form.cleaned_data["associated_email"] + associated_email = primary_email_form.cleaned_data['associated_email'] # Only do something if they selected a different email if associated_email.email != user.email: - logger.info( - "Primary email changed from: {0} to {1}".format( - user.email, associated_email.email - ) - ) + logger.info('Primary email changed from: {0} to {1}'.format(user.email, associated_email.email)) user.email = associated_email.email - user.save(update_fields=["email"]) + user.save(update_fields=['email']) # Change the email field of author objects belonging to # the user. Warn them if they are the corresponding # author of any projects authors = Author.objects.filter(user=user) authors.update(corresponding_email=associated_email) - messages.success( - request, - "Your email: {0} has been set as your new primary email.".format( - user.email - ), - ) + messages.success(request, 'Your email: {0} has been set as your new primary email.'.format(user.email)) if authors.filter(is_corresponding=True): - messages.info( - request, - "The corresponding email in all your authoring projects has been set to your new primary email.", - ) + messages.info(request, 'The corresponding email in all your authoring projects has been set to your new primary email.') def set_public_email(request, public_email_form): "Set the selected email as the public email" user = request.user if public_email_form.is_valid(): - associated_email = public_email_form.cleaned_data["associated_email"] + associated_email = public_email_form.cleaned_data['associated_email'] current_public_email = user.associated_emails.filter(is_public=True).first() # Only do something if they selected a different email if associated_email != current_public_email: @@ -293,55 +272,33 @@ def set_public_email(request, public_email_form): if associated_email: associated_email.is_public = True associated_email.save() - messages.success( - request, - "Your email: {0} has been set to public.".format( - associated_email.email - ), - ) + messages.success(request, 'Your email: {0} has been set to public.'.format(associated_email.email)) else: - messages.success( - request, - "Your email: {0} has been set to private.".format( - current_public_email.email - ), - ) - + messages.success(request, 'Your email: {0} has been set to private.'.format(current_public_email.email)) def add_email(request, add_email_form): user = request.user if add_email_form.is_valid(): token = get_random_string(20) - associated_email = AssociatedEmail.objects.create( - user=user, - email=add_email_form.cleaned_data["email"], - verification_token=token, - ) + associated_email = AssociatedEmail.objects.create(user=user, + email=add_email_form.cleaned_data['email'], + verification_token=token) # Send an email to the newly added email with a verification link uidb64 = force_str(urlsafe_base64_encode(force_bytes(associated_email.pk))) subject = f"{settings.SITE_NAME} Email Verification" context = { - "name": user.get_full_name(), - "domain": get_current_site(request), - "url_prefix": get_url_prefix(request), - "uidb64": uidb64, - "token": token, - "SITE_NAME": settings.SITE_NAME, + 'name': user.get_full_name(), + 'domain': get_current_site(request), + 'url_prefix': get_url_prefix(request), + 'uidb64': uidb64, + 'token': token, + 'SITE_NAME': settings.SITE_NAME, } - body = loader.render_to_string("user/email/verify_email_email.html", context) - send_mail( - subject, - body, - settings.DEFAULT_FROM_EMAIL, - [add_email_form.cleaned_data["email"]], - fail_silently=False, - ) - messages.success( - request, - "A verification link has been sent to: {0}".format(associated_email.email), - ) - + body = loader.render_to_string('user/email/verify_email_email.html', context) + send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, + [add_email_form.cleaned_data['email']], fail_silently=False) + messages.success(request, 'A verification link has been sent to: {0}'.format(associated_email.email)) @login_required def edit_emails(request): @@ -350,65 +307,56 @@ def edit_emails(request): """ user = request.user - associated_emails = AssociatedEmail.objects.filter(user=user).order_by( - "-is_verified", "-is_primary_email" - ) + associated_emails = AssociatedEmail.objects.filter( + user=user).order_by('-is_verified', '-is_primary_email') total_associated_emails = associated_emails.count() max_associated_emails_allowed = settings.MAX_EMAILS_PER_USER - primary_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="primary" - ) - public_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="public" - ) + primary_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='primary') + public_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='public') add_email_form = forms.AddEmailForm() - if request.method == "POST": - if "remove_email" in request.POST: + if request.method == 'POST': + if 'remove_email' in request.POST: # No form. Just get button value. - email_id = int(request.POST["remove_email"]) + email_id = int(request.POST['remove_email']) remove_email(request, email_id) # Update the associated_emails count after removing an email total_associated_emails = AssociatedEmail.objects.filter(user=user).count() - elif "set_primary_email" in request.POST: - primary_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="primary", data=request.POST - ) + elif 'set_primary_email' in request.POST: + primary_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='primary', data=request.POST) set_primary_email(request, primary_email_form) - elif "set_public_email" in request.POST: - public_email_form = forms.AssociatedEmailChoiceForm( - user=user, selection_type="public", data=request.POST - ) + elif 'set_public_email' in request.POST: + public_email_form = forms.AssociatedEmailChoiceForm(user=user, + selection_type='public', data=request.POST) set_public_email(request, public_email_form) - elif "add_email" in request.POST: + elif 'add_email' in request.POST: if associated_emails.count() >= settings.MAX_EMAILS_PER_USER: messages.error( request, - "You have reached the maximum number of email addresses allowed.", + 'You have reached the maximum number of email addresses allowed.' ) else: add_email_form = forms.AddEmailForm(request.POST) add_email(request, add_email_form) # Update the associated_emails count after adding a new email - total_associated_emails = AssociatedEmail.objects.filter( - user=user - ).count() - - context = { - "associated_emails": associated_emails, - "primary_email_form": primary_email_form, - "add_email_form": add_email_form, - "public_email_form": public_email_form, - "total_associated_emails": total_associated_emails, - "max_associated_emails_allowed": max_associated_emails_allowed, - } + total_associated_emails = AssociatedEmail.objects.filter(user=user).count() + + context = {'associated_emails': associated_emails, + 'primary_email_form': primary_email_form, + 'add_email_form': add_email_form, + 'public_email_form': public_email_form, + 'total_associated_emails': total_associated_emails, + 'max_associated_emails_allowed': max_associated_emails_allowed} - context["messages"] = messages.get_messages(request) + context['messages'] = messages.get_messages(request) - return render(request, "user/edit_emails.html", context) + return render(request, 'user/edit_emails.html', context) @login_required @@ -419,31 +367,29 @@ def edit_profile(request): profile = request.user.profile form = forms.ProfileForm(instance=profile) - if request.method == "POST": + if request.method == 'POST': if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: # Allow submitting the form, but do not allow the photo to # be modified. - if "delete_photo" in request.POST or request.FILES: + if 'delete_photo' in request.POST or request.FILES: raise ServiceUnavailable() - if "edit_profile" in request.POST: + if 'edit_profile' in request.POST: # Update the profile and return to the same page. Place a message # at the top of the page: 'your profile has been updated' - form = forms.ProfileForm( - data=request.POST, files=request.FILES, instance=profile - ) + form = forms.ProfileForm(data=request.POST, files=request.FILES, + instance=profile) if form.is_valid(): form.save() - messages.success(request, "Your profile has been updated.") - elif "delete_photo" in request.POST: + messages.success(request, 'Your profile has been updated.') + elif 'delete_photo' in request.POST: profile.delete_photo() - messages.success(request, "Your profile photo has been deleted.") + messages.success(request, 'Your profile photo has been deleted.') if not form.errors: form = forms.ProfileForm(instance=profile) - return render(request, "user/edit_profile.html", {"form": form}) - + return render(request, 'user/edit_profile.html', {'form':form}) @login_required def edit_orcid(request): @@ -453,8 +399,8 @@ def edit_orcid(request): ORCID account from their account. """ - if request.method == "POST": - if "request_orcid" in request.POST: + if request.method == 'POST': + if 'request_orcid' in request.POST: client_id = settings.ORCID_CLIENT_ID redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) @@ -463,15 +409,12 @@ def edit_orcid(request): return redirect(authorization_url) - if "remove_orcid" in request.POST: + if 'remove_orcid' in request.POST: try: Orcid.objects.get(user=request.user).delete() orcid_html = None except ObjectDoesNotExist: - messages.error( - request, - "Object Does Not Exist Error: tried to unlink an object which does not exist.", - ) + messages.error(request, 'Object Does Not Exist Error: tried to unlink an object which does not exist.') orcid_html = None else: @@ -480,8 +423,7 @@ def edit_orcid(request): except ObjectDoesNotExist: orcid_html = None - return render(request, "user/edit_orcid.html", {"orcid": orcid_html}) - + return render(request, 'user/edit_orcid.html', {'orcid': orcid_html}) @login_required @disallow_during_maintenance @@ -499,44 +441,37 @@ def auth_orcid(request): client_secret = settings.ORCID_CLIENT_SECRET redirect_uri = settings.ORCID_REDIRECT_URI scope = list(settings.ORCID_SCOPE.split(",")) - oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope) + oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, + scope=scope) params = request.GET.copy() - code = params["code"] + code = params['code'] try: - token = oauth.fetch_token( - settings.ORCID_TOKEN_URL, - code=code, - include_client_id=True, - client_secret=client_secret, - ) + token = oauth.fetch_token(settings.ORCID_TOKEN_URL, code=code, + include_client_id=True, client_secret=client_secret) try: - validators.validate_orcid_token(token["access_token"]) + validators.validate_orcid_token(token['access_token']) token_valid = True except ValidationError: - messages.error(request, "Validation Error: ORCID token validation failed.") + messages.error(request, 'Validation Error: ORCID token validation failed.') token_valid = False except InvalidGrantError: - messages.error( - request, - "Invalid Grant Error: authorization code may be expired or invalid.", - ) + messages.error(request, 'Invalid Grant Error: authorization code may be expired or invalid.') token_valid = False if token_valid: orcid_profile, _ = Orcid.objects.get_or_create(user=request.user) - orcid_profile.orcid_id = token.get("orcid") - orcid_profile.name = token.get("name") - orcid_profile.access_token = token.get("access_token") - orcid_profile.refresh_token = token.get("refresh_token") - orcid_profile.token_type = token.get("token_type") - orcid_profile.token_scope = token.get("scope") - orcid_profile.token_expiration = token.get("expires_at") + orcid_profile.orcid_id = token.get('orcid') + orcid_profile.name = token.get('name') + orcid_profile.access_token = token.get('access_token') + orcid_profile.refresh_token = token.get('refresh_token') + orcid_profile.token_type = token.get('token_type') + orcid_profile.token_scope = token.get('scope') + orcid_profile.token_expiration = token.get('expires_at') orcid_profile.full_clean() orcid_profile.save() - return redirect("edit_orcid") - + return redirect('edit_orcid') @login_required def edit_password_complete(request): @@ -544,7 +479,7 @@ def edit_password_complete(request): After password has successfully been changed. Need this view because we can't control the edit password view to show a success message. """ - return render(request, "user/edit_password_complete.html") + return render(request, 'user/edit_password_complete.html') def public_profile(request, username): @@ -558,20 +493,12 @@ def public_profile(request, username): raise Http404() # get list of projects - projects = PublishedProject.objects.filter(authors__user=public_user).order_by( - "-publish_datetime" - ) + projects = PublishedProject.objects.filter(authors__user=public_user).order_by('-publish_datetime') - return render( - request, - "user/public_profile.html", - { - "public_user": public_user, - "profile": public_user.profile, - "public_email": public_email, - "projects": projects, - }, - ) + + return render(request, 'user/public_profile.html', { + 'public_user':public_user, 'profile':public_user.profile, + 'public_email':public_email, 'projects':projects}) def profile_photo(request, username): @@ -595,9 +522,9 @@ def register(request): """ user = request.user if user.is_authenticated: - return redirect("home") + return redirect('home') - if request.method == "POST": + if request.method == 'POST': form = forms.RegistrationForm(request=request, data=request.POST) if form.is_valid(): # Create the new user @@ -607,17 +534,19 @@ def register(request): form.full_clean() if form.is_valid(): raise - user = User.objects.get(username=form.data["username"]) + user = User.objects.get(username=form.data['username']) else: - uidb64 = force_str(urlsafe_base64_encode(force_bytes(user.pk))) + uidb64 = force_str(urlsafe_base64_encode(force_bytes( + user.pk))) token = default_token_generator.make_token(user) notify_account_registration(request, user, uidb64, token) - return render(request, "user/register_done.html", {"email": user.email}) + return render(request, 'user/register_done.html', { + 'email': user.email}) else: form = forms.RegistrationForm(request=request) - response = render(request, "user/register.html", {"form": form}) + response = render(request, 'user/register.html', {'form': form}) form.set_response_cookies(response) return response @@ -628,7 +557,7 @@ def user_settings(request): Settings. Redirect to default - settings/profile Don't call this 'settings' because there's an import called 'settings' """ - return redirect("edit_profile") + return redirect('edit_profile') @login_required @@ -652,21 +581,14 @@ def verify_email(request, uidb64, token): associated_email.save() if not user.is_credentialed: check_legacy_credentials(user, associated_email.email) - logger.info( - "User {0} verified another email {1}".format(user.id, associated_email) - ) - messages.success( - request, - "The email address {} has been verified.".format(associated_email), - ) - return redirect("edit_emails") + logger.info('User {0} verified another email {1}'.format(user.id, associated_email)) + messages.success(request, 'The email address {} has been verified.'.format( + associated_email)) + return redirect('edit_emails') - logger.warning("Invalid Verification Link") - return render( - request, - "user/verify_email.html", - {"title": "Invalid Verification Link", "isvalid": False}, - ) + logger.warning('Invalid Verification Link') + return render(request, 'user/verify_email.html', + {'title':'Invalid Verification Link', 'isvalid':False}) @login_required @@ -677,16 +599,17 @@ def edit_username(request): user = request.user form = forms.UsernameChangeForm(instance=user) - if request.method == "POST": + if request.method == 'POST': form = forms.UsernameChangeForm(instance=user, data=request.POST) if form.is_valid(): form.save() - messages.success(request, "Your username has been updated.") + messages.success(request, 'Your username has been updated.') else: user = User.objects.get(id=user.id) - return render(request, "user/edit_username.html", {"form": form, "user": user}) + return render(request, 'user/edit_username.html', {'form':form, + 'user':user}) @login_required @@ -710,28 +633,21 @@ def edit_credentialing(request): ticket_system_url = None applications = CredentialApplication.objects.filter(user=request.user) - current_application = applications.filter( - status=CredentialApplication.Status.PENDING - ).first() + current_application = applications.filter(status=CredentialApplication.Status.PENDING).first() - if request.method == "POST" and "withdraw_credentialing" in request.POST: + if request.method == 'POST' and 'withdraw_credentialing' in request.POST: if current_application: current_application.withdraw(responder=request.user) - return render(request, "user/withdraw_credentialing_success.html") + return render(request, 'user/withdraw_credentialing_success.html') else: - messages.error(request, "The application has already been processed.") + messages.error(request, 'The application has already been processed.') - return render( - request, - "user/edit_credentialing.html", - { - "applications": applications, - "pause_applications": pause_applications, - "pause_message": pause_message, - "current_application": current_application, - "ticket_system_url": ticket_system_url, - }, - ) + return render(request, 'user/edit_credentialing.html', { + 'applications': applications, + 'pause_applications': pause_applications, + 'pause_message': pause_message, + 'current_application': current_application, + 'ticket_system_url': ticket_system_url}) @login_required @@ -739,15 +655,11 @@ def user_credential_applications(request): """ All the credential applications made by a user """ - applications = CredentialApplication.objects.filter(user=request.user).order_by( - "-application_datetime" - ) + applications = CredentialApplication.objects.filter( + user=request.user).order_by('-application_datetime') - return render( - request, - "user/user_credential_applications.html", - {"applications": applications, "CredentialApplication": CredentialApplication}, - ) + return render(request, 'user/user_credential_applications.html', + {'applications': applications, 'CredentialApplication': CredentialApplication}) @login_required @@ -757,31 +669,23 @@ def credential_application(request): """ user = request.user if user.is_credentialed or CredentialApplication.objects.filter( - user=user, status=CredentialApplication.Status.PENDING - ): - return redirect("edit_credentialing") + user=user, status=CredentialApplication.Status.PENDING): + return redirect('edit_credentialing') if settings.SYSTEM_MAINTENANCE_NO_UPLOAD: raise ServiceUnavailable() - if request.method == "POST": + if request.method == 'POST': # We use the individual forms to render the errors in the template # if not all valid - personal_form = forms.PersonalCAF( - user=user, data=request.POST, prefix="application" - ) + personal_form = forms.PersonalCAF(user=user, data=request.POST, prefix="application") research_form = forms.ResearchCAF(data=request.POST, prefix="application") - reference_form = forms.ReferenceCAF( - data=request.POST, prefix="application", user=user - ) + reference_form = forms.ReferenceCAF(data=request.POST, prefix="application", user=user) - form = forms.CredentialApplicationForm( - user=user, data=request.POST, files=request.FILES, prefix="application" - ) + form = forms.CredentialApplicationForm(user=user, data=request.POST, + files=request.FILES, prefix="application") - if ( - personal_form.is_valid() and reference_form.is_valid() and form.is_valid() - ) and research_form.is_valid(): + if (personal_form.is_valid() and reference_form.is_valid() and form.is_valid()) and research_form.is_valid(): application = form.save() credential_application_request(request, application) @@ -790,9 +694,9 @@ def credential_application(request): user=request.user, ) - return render(request, "user/credential_application_complete.html") + return render(request, 'user/credential_application_complete.html') else: - messages.error(request, "Invalid submission. See errors below.") + messages.error(request, 'Invalid submission. See errors below.') else: personal_form = forms.PersonalCAF(user=user, prefix="application") reference_form = forms.ReferenceCAF(prefix="application", user=user) @@ -803,13 +707,13 @@ def credential_application(request): return render( request, - "user/credential_application.html", + 'user/credential_application.html', { - "form": form, - "personal_form": personal_form, - "reference_form": reference_form, - "research_form": research_form, - "code_of_conduct": code_of_conduct, + 'form': form, + 'personal_form': personal_form, + 'reference_form': reference_form, + 'research_form': research_form, + 'code_of_conduct': code_of_conduct, }, ) @@ -908,12 +812,12 @@ def edit_certification(request): def edit_training_detail(request, training_id): training = get_object_or_404(Training, pk=training_id, user=request.user) - if request.method == "POST": - if request.POST.get("withdraw") is not None and not training.is_withdrawn(): + if request.method == 'POST': + if request.POST.get('withdraw') is not None and not training.is_withdrawn(): training.withdraw() - messages.success(request, "The training has been withdrawn.") + messages.success(request, 'The training has been withdrawn.') - return render(request, "user/edit_training_detail.html", {"training": training}) + return render(request, 'user/edit_training_detail.html', {'training': training}) @login_required @@ -922,7 +826,7 @@ def training_report(request, training_id): Serve a training report file """ trainings = Training.objects.all() - if not request.user.has_perm("user.change_credentialapplication"): + if not request.user.has_perm('user.change_credentialapplication'): trainings = trainings.filter(user=request.user) training = get_object_or_404(trainings, id=training_id) @@ -940,39 +844,31 @@ def credential_reference(request, application_slug): Page for a reference to verify or reject a credential application """ application = CredentialApplication.objects.filter( - slug=application_slug, reference_response_datetime=None - ) + slug=application_slug, reference_response_datetime=None) if not application: - return redirect("/") + return redirect('/') application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == "POST": + if request.method == 'POST': form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete( - request, application, include_comments=False - ) + process_credential_complete(request, application, + include_comments=False) - response = "verifying" if application.reference_response == 2 else "denying" - return render( - request, - "user/credential_reference_complete.html", - {"response": response, "application": application}, - ) + response = 'verifying' if application.reference_response == 2 else 'denying' + return render(request, 'user/credential_reference_complete.html', + {'response': response, 'application': application}) else: - messages.error(request, "Invalid submission. See errors below.") + messages.error(request, 'Invalid submission. See errors below.') - return render( - request, - "user/credential_reference.html", - {"form": form, "application": application}, - ) + return render(request, 'user/credential_reference.html', + {'form': form, 'application': application}) def credential_reference_verification(request, application_slug, verification_token): @@ -981,42 +877,31 @@ def credential_reference_verification(request, application_slug, verification_to `credential_reference` that uses an additional verification token. """ application = CredentialApplication.objects.filter( - slug=application_slug, - reference_response_datetime=None, - status=0, - reference_verification_token=verification_token, - ) + slug=application_slug, reference_response_datetime=None, status=0, + reference_verification_token=verification_token) if not application: - return redirect("/") + return redirect('/') application = application.get() form = forms.CredentialReferenceForm(instance=application) - if request.method == "POST": + if request.method == 'POST': form = forms.CredentialReferenceForm(data=request.POST, instance=application) if form.is_valid(): application = form.save() # Automated email notifying that their reference has denied # their application. if application.reference_response == 1: - process_credential_complete( - request, application, include_comments=False - ) + process_credential_complete(request, application, + include_comments=False) - response = "verifying" if application.reference_response == 2 else "denying" - return render( - request, - "user/credential_reference_complete.html", - {"response": response, "application": application}, - ) + response = 'verifying' if application.reference_response == 2 else 'denying' + return render(request, 'user/credential_reference_complete.html', + {'response': response, 'application': application}) else: - messages.error(request, "Invalid submission. See errors below.") + messages.error(request, 'Invalid submission. See errors below.') - return render( - request, - "user/credential_reference.html", - {"form": form, "application": application}, - ) + return render(request, 'user/credential_reference.html', {'form': form, 'application': application}) @login_required @@ -1027,16 +912,15 @@ def edit_cloud(request): user = request.user cloud_info = CloudInformation.objects.get_or_create(user=user)[0] form = forms.CloudForm(instance=cloud_info) - if request.method == "POST": + if request.method == 'POST': form = forms.CloudForm(instance=cloud_info, data=request.POST) if form.is_valid(): form.save() - messages.success(request, "Your cloud information has been saved.") + messages.success(request, 'Your cloud information has been saved.') else: - messages.error(request, "Invalid submission. See errors below.") - - return render(request, "user/edit_cloud.html", {"form": form, "user": user}) + messages.error(request, 'Invalid submission. See errors below.') + return render(request, 'user/edit_cloud.html', {'form':form, 'user':user}) @login_required def view_agreements(request): @@ -1044,24 +928,19 @@ def view_agreements(request): View a list of signed agreements in the user profile. """ user = request.user - signed_agreements = DUASignature.objects.filter(user=user).order_by( - "-sign_datetime" - ) - signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by( - "-sign_datetime" - ) + signed_agreements = DUASignature.objects.filter(user=user).order_by('-sign_datetime') + signed_code_of_conducts = CodeOfConductSignature.objects.filter(user=user).order_by('-sign_datetime') return render( request, - "user/view_agreements.html", + 'user/view_agreements.html', { - "user": user, - "signed_agreements": signed_agreements, - "signed_code_of_conducts": signed_code_of_conducts, + 'user': user, + 'signed_agreements': signed_agreements, + 'signed_code_of_conducts': signed_code_of_conducts, }, ) - @login_required def view_signed_agreement(request, dua_signature_id): """ @@ -1070,6 +949,5 @@ def view_signed_agreement(request, dua_signature_id): user = request.user signed = get_object_or_404(DUASignature, user=user, id=dua_signature_id) - return render( - request, "user/view_signed_agreement.html", {"user": user, "signed": signed} - ) + return render(request, 'user/view_signed_agreement.html', + {'user': user, 'signed': signed}) From 30ee856ca3bbbed30be70f166b87ee3e3ee531de Mon Sep 17 00:00:00 2001 From: Michael Scanlan Date: Wed, 13 Sep 2023 08:36:16 -0400 Subject: [PATCH 51/82] Archive page and adjusted navbar to make the tab functional --- .../console/templates/console/console_navbar.html | 6 ++++-- .../console/templates/console/event_archive_event.html | 6 +++--- physionet-django/console/views.py | 9 +++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/physionet-django/console/templates/console/console_navbar.html b/physionet-django/console/templates/console/console_navbar.html index caca07bb20..de4bdb782d 100644 --- a/physionet-django/console/templates/console/console_navbar.html +++ b/physionet-django/console/templates/console/console_navbar.html @@ -123,14 +123,16 @@ Events - {% if events_nav %} + {% if events_nav or archive_event_nav %}
      {% else %} diff --git a/physionet-django/console/templates/console/event_archive_event.html b/physionet-django/console/templates/console/event_archive_event.html index 0ea5dc01b0..7e37ab99ac 100644 --- a/physionet-django/console/templates/console/event_archive_event.html +++ b/physionet-django/console/templates/console/event_archive_event.html @@ -13,7 +13,7 @@ {% block content %}
      - Archived Events {{ events.paginator.count }} + Archived Events {{ archive_event.paginator.count }}
      @@ -32,9 +32,9 @@ - {% for event in events %} + {% for event in archive_event %} - {{ event.title }} + {{ event.title }} {{ event.get_category_display|title }} {{ event.host }} {{ event.added_datetime|date }} diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index 8fa1f9b7e2..2df6b2202c 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -2952,17 +2952,18 @@ def event(request): }) -@permission_required('user.view_archive_event', raise_exception=True) +@permission_required('user.view_all_events', raise_exception=True) def archive_event(request): """ List of archived events """ - archive_event = Event.objects.filter(all).order_by('archive_datetime') + now = timezone.now() + archive_event = Event.objects.filter(end_date__lte=now) archive_event = paginate(request, archive_event, 50) return render(request, 'console/event_archive_event.html', - {'event_archive_event': archive_event, - 'events_nav': True + {'archive_event': archive_event, + 'archive_event_nav': True }) From cad461c1955714e93f78ba2e27efb5e8300c5a98 Mon Sep 17 00:00:00 2001 From: Michael Scanlan Date: Wed, 13 Sep 2023 15:53:42 -0400 Subject: [PATCH 52/82] Cleaned up variable names to be consistent. Changed submenu names. Changed url link name. --- .../templates/console/console_navbar.html | 10 ++++---- .../console/{event.html => event_active.html} | 4 +-- ..._archive_event.html => event_archive.html} | 4 +-- physionet-django/console/urls.py | 8 +++--- physionet-django/console/views.py | 25 +++++++++---------- 5 files changed, 25 insertions(+), 26 deletions(-) rename physionet-django/console/templates/console/{event.html => event_active.html} (92%) rename physionet-django/console/templates/console/{event_archive_event.html => event_archive.html} (95%) diff --git a/physionet-django/console/templates/console/console_navbar.html b/physionet-django/console/templates/console/console_navbar.html index de4bdb782d..3ffd116272 100644 --- a/physionet-django/console/templates/console/console_navbar.html +++ b/physionet-django/console/templates/console/console_navbar.html @@ -123,16 +123,16 @@ Events - {% if events_nav or archive_event_nav %} + {% if nav_event_active or nav_event_archive %}
        {% else %} diff --git a/physionet-django/console/templates/console/event.html b/physionet-django/console/templates/console/event_active.html similarity index 92% rename from physionet-django/console/templates/console/event.html rename to physionet-django/console/templates/console/event_active.html index 2c07f7512f..9f7add0068 100644 --- a/physionet-django/console/templates/console/event.html +++ b/physionet-django/console/templates/console/event_active.html @@ -13,7 +13,7 @@ {% block content %}
        - Events {{ events.paginator.count }} + Active Events {{ event_active.paginator.count }}
        @@ -32,7 +32,7 @@ - {% for event in events %} + {% for event in event_active %} {{ event.title }} {{ event.get_category_display|title }} diff --git a/physionet-django/console/templates/console/event_archive_event.html b/physionet-django/console/templates/console/event_archive.html similarity index 95% rename from physionet-django/console/templates/console/event_archive_event.html rename to physionet-django/console/templates/console/event_archive.html index 7e37ab99ac..178dc045d0 100644 --- a/physionet-django/console/templates/console/event_archive_event.html +++ b/physionet-django/console/templates/console/event_archive.html @@ -13,7 +13,7 @@ {% block content %}
        - Archived Events {{ archive_event.paginator.count }} + Archived Events {{ event_archive.paginator.count }}
        @@ -32,7 +32,7 @@ - {% for event in archive_event %} + {% for event in event_archive %} {{ event.title }} {{ event.get_category_display|title }} diff --git a/physionet-django/console/urls.py b/physionet-django/console/urls.py index be1da76e2a..9f8922aa97 100644 --- a/physionet-django/console/urls.py +++ b/physionet-django/console/urls.py @@ -146,10 +146,10 @@ ), path('code-of-conducts//activate/', views.code_of_conduct_activate, name='code_of_conduct_activate'), # Lists of event components - path('event/', views.event, - name='event'), - path('archive_event/', views.archive_event, - name='archive_event'), + path('event/active/', views.event_active, + name='event_active'), + path('event/archived/', views.event_archive, + name='event_archive'), path('event/manage/', views.event_management, name='event_management'), path('event_agreements/', views.event_agreement_list, name='event_agreement_list'), path('event_agreements//', views.event_agreement_detail, name='event_agreement_detail'), diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index 8b801cc79b..769ba2b0b4 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -2938,31 +2938,30 @@ def code_of_conduct_activate(request, pk): @permission_required('user.view_all_events', raise_exception=True) -def event(request): +def event_active(request): """ List of events """ - events = Event.objects.all() - events = paginate(request, events, 50) + event_active = Event.objects.filter(end_date__gte=timezone.now()) + event_active = paginate(request, event_active, 50) - return render(request, 'console/event.html', - {'events': events, - 'events_nav': True + return render(request, 'console/event_active.html', + {'event_active': event_active, + 'nav_event_active': True }) @permission_required('user.view_all_events', raise_exception=True) -def archive_event(request): +def event_archive(request): """ List of archived events """ - now = timezone.now() - archive_event = Event.objects.filter(end_date__lte=now) - archive_event = paginate(request, archive_event, 50) + event_archive = Event.objects.filter(end_date__lte=timezone.now()) + event_archive = paginate(request, event_archive, 50) - return render(request, 'console/event_archive_event.html', - {'archive_event': archive_event, - 'archive_event_nav': True + return render(request, 'console/event_archive.html', + {'event_archive': event_archive, + 'nav_event_archive': True }) From 17b1355170cbfada8e038eb39fb67eb6132989bd Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 14 Sep 2023 13:45:19 -0400 Subject: [PATCH 53/82] create_bucket: avoid use of deprecated property. The bucket_policy_only_enabled property (in google.cloud.storage.bucket.IAMConfiguration) has been renamed to uniform_bucket_level_access_enabled; the old name is deprecated. --- physionet-django/console/utility.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/physionet-django/console/utility.py b/physionet-django/console/utility.py index 0d789f0944..ac3a28ef28 100644 --- a/physionet-django/console/utility.py +++ b/physionet-django/console/utility.py @@ -55,7 +55,8 @@ def create_bucket(project, version, title, protected=True): bucket_name, email = bucket_info(project, version) storage_client.create_bucket(bucket_name) bucket = storage_client.bucket(bucket_name) - bucket.iam_configuration.bucket_policy_only_enabled = True + # Only bucket-level permissions are enforced; there are no per-file ACLs. + bucket.iam_configuration.uniform_bucket_level_access_enabled = True bucket.patch() LOGGER.info("Created bucket {0} for project {1}".format( bucket_name.lower(), project)) From d54f2cbbd92c9c41d241185ddaea6397c34e9072 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 14 Sep 2023 13:45:45 -0400 Subject: [PATCH 54/82] create_bucket: create the bucket in a single request. When creating a GCS bucket, we need to set various bucket properties in addition to the bucket name. The google.cloud.storage.Client.create_bucket method allows setting some properties via keyword arguments. However, the underlying HTTP API allows setting all of the properties at once - which means we can create the bucket and set its properties with a single request, rather than making two requests and risking that the second one fails. Note that the same pattern is used by physionet.gcs.create_bucket. (It might be better if we also could set the IAM policy at the same time - I'm not sure if this is possible or not - but on the other hand it might be better *not* to make the bucket accessible until after the project files have been uploaded.) --- physionet-django/console/utility.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/physionet-django/console/utility.py b/physionet-django/console/utility.py index ac3a28ef28..1b2f00e0d8 100644 --- a/physionet-django/console/utility.py +++ b/physionet-django/console/utility.py @@ -53,11 +53,12 @@ def create_bucket(project, version, title, protected=True): """ storage_client = storage.Client() bucket_name, email = bucket_info(project, version) - storage_client.create_bucket(bucket_name) + bucket = storage_client.bucket(bucket_name) # Only bucket-level permissions are enforced; there are no per-file ACLs. bucket.iam_configuration.uniform_bucket_level_access_enabled = True - bucket.patch() + storage_client.create_bucket(bucket) + LOGGER.info("Created bucket {0} for project {1}".format( bucket_name.lower(), project)) if protected: From e12721783223bf2844d9dfbbad28ecdd4ca47740 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 14 Sep 2023 13:58:06 -0400 Subject: [PATCH 55/82] create_bucket: mark all buckets as requester-pays by default. GCS buckets may be either "requester-pays" (the client accessing the data must specify a "project" that will be billed for egress costs), or "non-requester-pays" (any authorized client can access the data and the bucket's owner will be billed for egress costs.) This option can be switched on or off at any time, and it would be nice to have a way to do so in the PhysioNet console. For the time being, we want to set all buckets as "requester-pays" by default. --- physionet-django/console/utility.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/physionet-django/console/utility.py b/physionet-django/console/utility.py index 1b2f00e0d8..867043faf9 100644 --- a/physionet-django/console/utility.py +++ b/physionet-django/console/utility.py @@ -57,6 +57,8 @@ def create_bucket(project, version, title, protected=True): bucket = storage_client.bucket(bucket_name) # Only bucket-level permissions are enforced; there are no per-file ACLs. bucket.iam_configuration.uniform_bucket_level_access_enabled = True + # Clients accessing this bucket will be billed for download costs. + bucket.requester_pays = True storage_client.create_bucket(bucket) LOGGER.info("Created bucket {0} for project {1}".format( From 2c88043757b5178fa2ba643d6b11b1ce014a3881 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 21 Sep 2023 14:16:19 -0400 Subject: [PATCH 56/82] GCSQuotaManager: new class. This class implements the basic QuotaManager API for the Google Cloud Storage backend. (Note the SubmissionInfo.quota_manager method is currently broken for GCS - and always has been, since GCS was introduced.) Note that GCS doesn't (as far as I know) provide any way to limit the storage usage of a bucket or a project, let alone the usage of a particular prefix within a bucket. (I also don't know how Google calculates the cost of storing objects in GCS, but obviously there is some storage used for metadata in addition to the object content.) I also don't know if there is any way to report the storage usage of a bucket or prefix in any meaningful way, other than by actually iterating over all the blobs in the bucket as GCSObject.size does. So GCSQuotaManager calculates bytes_used in the same inefficient way as GCSProjectFiles.active_project_storage_used (which this class is intended to replace.) Currently it does *not* calculate inodes_used (though that should be easy enough to do, it should ideally be done by GCSObject and avoid making more unnecessary requests.) GCSQuotaManager also implements the other QuotaManager methods, such as create_toplevel_directory and check_create_file, in much the same way that DemoQuotaManager does, so that we can eventually use the QuotaManager API in other areas of the code. --- physionet-django/project/quota.py | 101 ++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/physionet-django/project/quota.py b/physionet-django/project/quota.py index 18b049dab4..506f69695b 100644 --- a/physionet-django/project/quota.py +++ b/physionet-django/project/quota.py @@ -1,6 +1,8 @@ import errno import os +from physionet.gcs import GCSObject + class QuotaManager: """ @@ -282,3 +284,102 @@ def check_delete_directory(self, path): if not self._cache_valid: self.refresh() self._inodes_used -= 1 + + +class GCSQuotaManager(QuotaManager): + """ + QuotaManager for Google Cloud storage. + + This implementation, like DemoQuotaManager, is not robust or + scalable. As far as I know, as of 2023, there is no way to + implement robust, scalable storage quotas using GCS. + + - As with DemoQuotaManager, there is nothing to stop multiple + authorized clients from uploading multiple files at once and + exceeding the storage limit, even if all of them are + well-behaved. + + - Usage is calculated by listing all objects in the specified + prefix (which is only slightly more efficient than a "directory + traversal" as DemoQuotaManager does) and adding up their sizes. + + There is no way to create hard links, so every new project version + must have a copy of every file; therefore, all files are counted + equally against the quota. + + The check_create_file function will raise an OSError if the + specified hard limits would be exceeded, simulating the behavior + of a filesystem that enforces quota. + + Currently this does not attempt to track or limit the number of + objects, and inodes_used will be zero. + """ + def __init__(self, project_path): + # _project_path must be a directory name (ending with a + # slash); see GCSObject.is_dir(). + self._project_path = project_path.rstrip('/') + '/' + self._cache_valid = False + self._block_size = 1 + self._bytes_used = 0 + self._bytes_soft = 0 + self._bytes_hard = 0 + self._inodes_used = 0 + self._inodes_soft = 0 + self._inodes_hard = 0 + + def refresh(self): + """ + Refresh the current usage and limits from the backend. + + This is done by listing objects underneath the project prefix + and counting the total number of bytes. + """ + # FIXME: it'd probably be a *good idea* to try to limit the + # number of files. GCSObject doesn't have a way to calculate + # total size and number of objects all at once, but it'd be + # easy to do. + self._bytes_used = GCSObject(self._project_path).size() + self._cache_valid = True + + def set_limits(self, bytes_soft=None, bytes_hard=None, + inodes_soft=None, inodes_hard=None): + """ + Set limits on the number of bytes and/or inodes. + """ + if bytes_soft is not None: + self._bytes_soft = bytes_soft + if bytes_hard is not None: + self._bytes_hard = bytes_hard + if inodes_soft is not None: + self._inodes_soft = inodes_soft + if inodes_hard is not None: + self._inodes_hard = inodes_hard + + def create_toplevel_directory(self): + """ + Create the top-level project directory. + """ + GCSObject(self._project_path).mkdir() + + def check_create_file(self, path, size): + """ + Update usage when creating a file. + + If creating a file of the given size would cause the hard + limits to be exceeded, this raises an OSError with errno = + EDQUOT, and bytes_used is unchanged. Otherwise, bytes_used is + increased accordingly. + """ + if not self._cache_valid: + self.refresh() + + if self._bytes_used + size > self._bytes_hard > 0: + raise OSError(errno.EDQUOT, 'Quota exceeded') + + self._bytes_used += size + + def check_delete_file(self, path, size): + """ + Update usage when deleting a file. + """ + self._bytes_used -= size From e5e64d2d0ed2ad2e52e45879b43f40679b911957 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 21 Sep 2023 15:14:42 -0400 Subject: [PATCH 57/82] Move logic of QuotaManager creation into ProjectFiles. Quota management behavior depends on the project file storage. For GCS storage, a GCSQuotaManager is required. (Until now, the SubmissionInfo.quota_manager method has been broken on GCS, and the GCS backend has carefully avoided using this API - which unfortunately has also blocked development of the quota system.) Rather than including this logic in SubmissionInfo, it seemingly makes more sense for quota policy to be part of ProjectFiles; so a new abstract method 'project_quota_manager' is defined. --- .../project/modelcomponents/submission.py | 15 +-------------- physionet-django/project/projectfiles/base.py | 5 +++++ physionet-django/project/projectfiles/gcs.py | 10 ++++++++++ physionet-django/project/projectfiles/local.py | 16 ++++++++++++++++ 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/physionet-django/project/modelcomponents/submission.py b/physionet-django/project/modelcomponents/submission.py index 13bb853a8e..1117f9b6d3 100644 --- a/physionet-django/project/modelcomponents/submission.py +++ b/physionet-django/project/modelcomponents/submission.py @@ -3,7 +3,6 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models -from project.quota import DemoQuotaManager from django.conf import settings from physionet.settings.base import StorageTypes @@ -245,19 +244,7 @@ def quota_manager(self): (represented by the bytes_used and inodes_used properties of the QuotaManager object.) """ - allowance = self.core_project.storage_allowance - published = self.core_project.total_published_size - limit = allowance - published - - # DemoQuotaManager needs to know the project's toplevel - # directory as well as its creation time (so that files - # present in multiple versions can be correctly attributed to - # the version where they first appeared.) - quota_manager = DemoQuotaManager( - project_path=self.file_root(), - creation_time=self.creation_datetime) - quota_manager.set_limits(bytes_hard=limit, bytes_soft=limit) - return quota_manager + return self.files.project_quota_manager(self) @functools.cached_property def files(self): diff --git a/physionet-django/project/projectfiles/base.py b/physionet-django/project/projectfiles/base.py index 07c4801c16..b0716e7276 100644 --- a/physionet-django/project/projectfiles/base.py +++ b/physionet-django/project/projectfiles/base.py @@ -109,6 +109,11 @@ def get_file_root(self, slug, version, access_policy, klass): """Project directory.""" raise NotImplementedError + @abc.abstractmethod + def project_quota_manager(self, project): + """Create a quota manager for a project.""" + raise NotImplementedError + @abc.abstractmethod def active_project_storage_used(self, project): """Total storage used in bytes - active project.""" diff --git a/physionet-django/project/projectfiles/gcs.py b/physionet-django/project/projectfiles/gcs.py index ed1d215f77..29deac6c55 100644 --- a/physionet-django/project/projectfiles/gcs.py +++ b/physionet-django/project/projectfiles/gcs.py @@ -5,6 +5,7 @@ from google.cloud.exceptions import Conflict, NotFound from physionet.gcs import GCSObject, GCSObjectException, create_bucket, delete_bucket from project.projectfiles.base import BaseProjectFiles +from project.quota import GCSQuotaManager from project.utility import DirectoryInfo, FileInfo, readable_size @@ -127,6 +128,15 @@ def get_project_file_root(self, slug, version, access_policy, klass): def get_file_root(self, slug, version, access_policy, klass): return self.get_project_file_root(slug, version, access_policy, klass) + def project_quota_manager(self, project): + allowance = project.core_project.storage_allowance + published = project.core_project.total_published_size + limit = allowance - published + + quota_manager = GCSQuotaManager(project.file_root()) + quota_manager.set_limits(bytes_hard=limit, bytes_soft=limit) + return quota_manager + def active_project_storage_used(self, project): return self._storage_used(project) diff --git a/physionet-django/project/projectfiles/local.py b/physionet-django/project/projectfiles/local.py index a7a87ad398..03995127f4 100644 --- a/physionet-django/project/projectfiles/local.py +++ b/physionet-django/project/projectfiles/local.py @@ -5,6 +5,7 @@ from django.conf import settings from physionet.utility import serve_file, sorted_tree_files, zip_dir from project.projectfiles.base import BaseProjectFiles +from project.quota import DemoQuotaManager from project.utility import ( clear_directory, get_directory_info, @@ -131,6 +132,21 @@ def get_project_file_root(self, slug, version, access_policy, klass): def get_file_root(self, slug, version, access_policy, klass): return os.path.join(self.get_project_file_root(slug, version, access_policy, klass), version) + def project_quota_manager(self, project): + allowance = project.core_project.storage_allowance + published = project.core_project.total_published_size + limit = allowance - published + + # DemoQuotaManager needs to know the project's toplevel + # directory as well as its creation time (so that files + # present in multiple versions can be correctly attributed to + # the version where they first appeared.) + quota_manager = DemoQuotaManager( + project_path=project.file_root(), + creation_time=project.creation_datetime) + quota_manager.set_limits(bytes_hard=limit, bytes_soft=limit) + return quota_manager + def active_project_storage_used(self, project): return project.quota_manager().bytes_used From eb87ee5d84cb97cc6fa4ec00451801499254dbb0 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 21 Sep 2023 15:20:26 -0400 Subject: [PATCH 58/82] Use quota manager to calculate "active project storage used". The 'active_project_storage_used' method is used to report "how much of the storage allowance for the given project submission has been used." (The name suggests that this is somehow similar to 'published_project_storage_used', but this is not the case: *that* method is used to inform visitors "how large will the project files be if I download all of them", which is only tangentially related to the resource usage that a particular submission is responsible for.) Reporting (as well as limiting) resource usage is the task of the QuotaManager. Use the QuotaManager API and remove the redundant ProjectFiles API. --- physionet-django/project/modelcomponents/activeproject.py | 2 +- physionet-django/project/projectfiles/base.py | 5 ----- physionet-django/project/projectfiles/gcs.py | 3 --- physionet-django/project/projectfiles/local.py | 3 --- 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/physionet-django/project/modelcomponents/activeproject.py b/physionet-django/project/modelcomponents/activeproject.py index c61ce0ea73..3bdd73e426 100644 --- a/physionet-django/project/modelcomponents/activeproject.py +++ b/physionet-django/project/modelcomponents/activeproject.py @@ -220,7 +220,7 @@ def storage_used(self): versions of this CoreProject. (The QuotaManager should ensure that the same file is not counted twice in this total.) """ - current = self.files.active_project_storage_used(self) + current = self.quota_manager().bytes_used published = self.core_project.total_published_size return current + published diff --git a/physionet-django/project/projectfiles/base.py b/physionet-django/project/projectfiles/base.py index b0716e7276..9614117ac8 100644 --- a/physionet-django/project/projectfiles/base.py +++ b/physionet-django/project/projectfiles/base.py @@ -114,11 +114,6 @@ def project_quota_manager(self, project): """Create a quota manager for a project.""" raise NotImplementedError - @abc.abstractmethod - def active_project_storage_used(self, project): - """Total storage used in bytes - active project.""" - raise NotImplementedError - @abc.abstractmethod def published_project_storage_used(self, project): """Total storage used in bytes - published project.""" diff --git a/physionet-django/project/projectfiles/gcs.py b/physionet-django/project/projectfiles/gcs.py index 29deac6c55..1d8515d1bf 100644 --- a/physionet-django/project/projectfiles/gcs.py +++ b/physionet-django/project/projectfiles/gcs.py @@ -137,9 +137,6 @@ def project_quota_manager(self, project): quota_manager.set_limits(bytes_hard=limit, bytes_soft=limit) return quota_manager - def active_project_storage_used(self, project): - return self._storage_used(project) - def published_project_storage_used(self, project): return self._storage_used(project) diff --git a/physionet-django/project/projectfiles/local.py b/physionet-django/project/projectfiles/local.py index 03995127f4..d0ab375313 100644 --- a/physionet-django/project/projectfiles/local.py +++ b/physionet-django/project/projectfiles/local.py @@ -147,9 +147,6 @@ def project_quota_manager(self, project): quota_manager.set_limits(bytes_hard=limit, bytes_soft=limit) return quota_manager - def active_project_storage_used(self, project): - return project.quota_manager().bytes_used - def published_project_storage_used(self, project): return get_tree_size(project.file_root()) From 6d7b8b25276822d53c343ec086d932201c4cf4e1 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Fri, 22 Sep 2023 17:25:00 -0400 Subject: [PATCH 59/82] bump pyOpenSSL from 23.1.1 to 23.2.0. bump cryptography from 40.0.1 to 41.0.4. --- poetry.lock | 164 +++++++++++++++++++++++++++++++++++++---------- pyproject.toml | 3 +- requirements.txt | 82 ++++++++++++++++-------- 3 files changed, 188 insertions(+), 61 deletions(-) diff --git a/poetry.lock b/poetry.lock index b9917a877c..ddba94d174 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "asgiref" version = "3.5.2" description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -18,6 +19,7 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "bleach" version = "3.3.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -34,6 +36,7 @@ webencodings = "*" name = "cachetools" version = "4.2.2" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = "~=3.5" files = [ @@ -45,6 +48,7 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -56,6 +60,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -132,6 +137,7 @@ pycparser = "*" name = "chardet" version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = "*" files = [ @@ -143,6 +149,7 @@ files = [ name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -157,6 +164,7 @@ unicode-backport = ["unicodedata2"] name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -218,30 +226,35 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "40.0.1" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cryptography-40.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917"}, - {file = "cryptography-40.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356"}, - {file = "cryptography-40.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122"}, - {file = "cryptography-40.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2"}, - {file = "cryptography-40.0.1-cp36-abi3-win32.whl", hash = "sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db"}, - {file = "cryptography-40.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c"}, - {file = "cryptography-40.0.1.tar.gz", hash = "sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -250,17 +263,18 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "check-manifest", "mypy", "ruff"] -sdist = ["setuptools-rust (>=0.11.4)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] -tox = ["tox"] [[package]] name = "deprecated" version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -278,6 +292,7 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version name = "django" version = "4.1.10" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -298,6 +313,7 @@ bcrypt = ["bcrypt"] name = "django-autocomplete-light" version = "3.9.4" description = "Fresh autocompletes for Django" +category = "main" optional = false python-versions = "*" files = [ @@ -317,6 +333,7 @@ tags = ["django-taggit"] name = "django-background-tasks-updated" version = "1.2.7" description = "Database backed asynchronous task queue" +category = "main" optional = false python-versions = "*" files = [ @@ -332,6 +349,7 @@ six = "*" name = "django-ckeditor" version = "6.5.1" description = "Django admin CKEditor integration." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -347,6 +365,7 @@ django-js-asset = ">=2.0" name = "django-cors-headers" version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -361,6 +380,7 @@ Django = ">=3.2" name = "django-debug-toolbar" version = "3.2.4" description = "A configurable set of panels that display various debug information about the current request/response." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -376,6 +396,7 @@ sqlparse = ">=0.2.0" name = "django-js-asset" version = "2.0.0" description = "script tag with additional attributes for django.forms.Media" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -393,6 +414,7 @@ tests = ["coverage"] name = "django-oauth-toolkit" version = "2.2.0" description = "OAuth2 Provider for Django" +category = "main" optional = false python-versions = "*" files = [ @@ -410,6 +432,7 @@ requests = ">=2.13.0" name = "django-sass" version = "1.1.0" description = "The absolute simplest way to use Sass with Django. Pure Python, minimal dependencies, and no special configuration required!" +category = "main" optional = false python-versions = "*" files = [ @@ -425,6 +448,7 @@ libsass = "*" name = "django-storages" version = "1.12.3" description = "Support for many storage backends in Django" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -448,6 +472,7 @@ sftp = ["paramiko"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -463,6 +488,7 @@ pytz = "*" name = "google-api-core" version = "1.34.0" description = "Google API client core library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -487,6 +513,7 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] name = "google-api-python-client" version = "1.12.8" description = "Google API Client Library for Python" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -506,6 +533,7 @@ uritemplate = ">=3.0.0,<4dev" name = "google-auth" version = "1.32.0" description = "Google Authentication Library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -529,6 +557,7 @@ reauth = ["pyu2f (>=0.1.5)"] name = "google-auth-httplib2" version = "0.1.0" description = "Google Authentication Library: httplib2 transport" +category = "main" optional = false python-versions = "*" files = [ @@ -545,6 +574,7 @@ six = "*" name = "google-cloud-core" version = "1.7.0" description = "Google Cloud API client core library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -564,6 +594,7 @@ grpc = ["grpcio (>=1.8.2,<2.0dev)"] name = "google-cloud-storage" version = "1.42.3" description = "Google Cloud Storage API client library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -584,6 +615,7 @@ six = "*" name = "google-cloud-workflows" version = "1.9.1" description = "Google Cloud Workflows API client library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -592,10 +624,10 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-api-core = {version = ">=1.34.0,<2.0.0 || >=2.11.0,<3.0.0dev", extras = ["grpc"]} proto-plus = [ - {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, + {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -603,6 +635,7 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 name = "google-crc32c" version = "1.1.2" description = "A python wrapper of the C library 'Google CRC32C'" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -647,6 +680,7 @@ testing = ["pytest"] name = "google-resumable-media" version = "1.3.1" description = "Utilities for Google Media Downloads and Resumable Uploads" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -666,6 +700,7 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] name = "googleapis-common-protos" version = "1.58.0" description = "Common protobufs used in Google APIs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -683,6 +718,7 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] name = "grpcio" version = "1.53.0" description = "HTTP/2-based RPC framework" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -740,6 +776,7 @@ protobuf = ["grpcio-tools (>=1.53.0)"] name = "grpcio-status" version = "1.48.2" description = "Status proto mapping for gRPC" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -756,6 +793,7 @@ protobuf = ">=3.12.0" name = "hdn-research-environment" version = "2.3.8" description = "A Django app for supporting cloud-native research environments" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -771,6 +809,7 @@ google-cloud-workflows = ">=1.6.1" name = "html2text" version = "2018.1.9" description = "Turn HTML into equivalent Markdown-structured text." +category = "main" optional = false python-versions = "*" files = [ @@ -782,6 +821,7 @@ files = [ name = "httplib2" version = "0.19.1" description = "A comprehensive HTTP client library." +category = "main" optional = false python-versions = "*" files = [ @@ -796,6 +836,7 @@ pyparsing = ">=2.4.2,<3" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -807,6 +848,7 @@ files = [ name = "jwcrypto" version = "1.4.2" description = "Implementation of JOSE Web standards" +category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -821,6 +863,7 @@ deprecated = "*" name = "libsass" version = "0.21.0" description = "Sass for Python: A straightforward binding of libsass for Python." +category = "main" optional = false python-versions = "*" files = [ @@ -843,6 +886,7 @@ six = "*" name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -859,6 +903,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] name = "packaging" version = "20.9" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -873,6 +918,7 @@ pyparsing = ">=2.0.2" name = "pdfminer.six" version = "20211012" description = "PDF parser and analyzer" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -892,6 +938,7 @@ docs = ["sphinx", "sphinx-argparse"] name = "pillow" version = "9.3.0" description = "Python Imaging Library (Fork)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -966,6 +1013,7 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "proto-plus" version = "1.22.2" description = "Beautiful, Pythonic protocol buffers." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -983,9 +1031,11 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "3.20.3" description = "Protocol Buffers" +category = "main" optional = false python-versions = ">=3.7" files = [ + {file = "protobuf-3.20.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, @@ -1014,6 +1064,7 @@ files = [ name = "psycopg2" version = "2.9.5" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1036,10 +1087,22 @@ files = [ name = "pyasn1" version = "0.4.8" description = "ASN.1 types and codecs" +category = "main" optional = false python-versions = "*" files = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] @@ -1047,11 +1110,23 @@ files = [ name = "pyasn1-modules" version = "0.2.8" description = "A collection of ASN.1-based protocols modules." +category = "main" optional = false python-versions = "*" files = [ {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, + {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, + {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, + {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, + {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, + {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, + {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, + {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, + {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, + {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, + {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, + {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] [package.dependencies] @@ -1061,6 +1136,7 @@ pyasn1 = ">=0.4.6,<0.5.0" name = "pycparser" version = "2.20" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1070,17 +1146,18 @@ files = [ [[package]] name = "pyopenssl" -version = "23.1.1" +version = "23.2.0" description = "Python wrapper module around the OpenSSL library" +category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "pyOpenSSL-23.1.1-py3-none-any.whl", hash = "sha256:9e0c526404a210df9d2b18cd33364beadb0dc858a739b885677bc65e105d4a4c"}, - {file = "pyOpenSSL-23.1.1.tar.gz", hash = "sha256:841498b9bec61623b1b6c47ebbc02367c07d60e0e195f19790817f10cc8db0b7"}, + {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, + {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, ] [package.dependencies] -cryptography = ">=38.0.0,<41" +cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" [package.extras] docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] @@ -1090,6 +1167,7 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pyparsing" version = "2.4.7" description = "Python parsing module" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1101,6 +1179,7 @@ files = [ name = "python-decouple" version = "3.4" description = "Strict separation of settings from code." +category = "main" optional = false python-versions = "*" files = [ @@ -1112,6 +1191,7 @@ files = [ name = "python-json-logger" version = "2.0.2" description = "A python library adding a json log formatter" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1123,6 +1203,7 @@ files = [ name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -1134,6 +1215,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1155,6 +1237,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.9.3" description = "Mock out responses from the requests package" +category = "dev" optional = false python-versions = "*" files = [ @@ -1174,11 +1257,13 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1 name = "requests-oauthlib" version = "1.3.0" description = "OAuthlib authentication support for Requests." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"}, + {file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"}, ] [package.dependencies] @@ -1192,6 +1277,7 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" +category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1206,6 +1292,7 @@ pyasn1 = ">=0.1.3" name = "selenium" version = "3.141.0" description = "Python bindings for Selenium" +category = "dev" optional = false python-versions = "*" files = [ @@ -1220,6 +1307,7 @@ urllib3 = "*" name = "sentry-sdk" version = "1.14.0" description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" files = [ @@ -1258,6 +1346,7 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1274,6 +1363,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1285,6 +1375,7 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1301,6 +1392,7 @@ test = ["pytest", "pytest-cov"] name = "tzdata" version = "2022.7" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -1312,6 +1404,7 @@ files = [ name = "uritemplate" version = "3.0.1" description = "URI templates" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1323,6 +1416,7 @@ files = [ name = "urllib3" version = "1.26.15" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1339,6 +1433,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "uwsgi" version = "2.0.22" description = "The uWSGI server" +category = "main" optional = false python-versions = "*" files = [ @@ -1349,6 +1444,7 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "main" optional = false python-versions = "*" files = [ @@ -1360,6 +1456,7 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1444,6 +1541,7 @@ files = [ name = "zxcvbn" version = "4.4.28" description = "" +category = "main" optional = false python-versions = "*" files = [ @@ -1453,4 +1551,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "68860ca96b3262f70f6624a0504b6f9509464a66b8cc0fe087fba6a15f97e14c" +content-hash = "da2b20a52af5d1f2b22adb4d2552609c3e764870e009098cca1cebb6244196f7" diff --git a/pyproject.toml b/pyproject.toml index fed11d39d7..9788924147 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ authors = [] python = "^3.9" bleach = "^3.3.0" chardet = "^3.0.4" +cryptography = "^41.0.4" Django = "4.1.10" django-autocomplete-light = "^3.9.4" django-background-tasks-updated = "=1.2.7" @@ -18,7 +19,7 @@ html2text = "^2018.1.9" Pillow = "^9.3.0" python-decouple = "^3.1" uWSGI = "2.0.22" -pyOpenSSL = "^23.1.1" +pyOpenSSL = "^23.2.0" google-api-python-client = "^1.7.9" psycopg2 = "2.9.5" httplib2 = "^0.19.0" diff --git a/requirements.txt b/requirements.txt index 873d4dfda5..892b5409b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -133,26 +133,30 @@ coverage==7.2.3 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910 \ --hash=sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859 \ --hash=sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312 -cryptography==40.0.1 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405 \ - --hash=sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356 \ - --hash=sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472 \ - --hash=sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41 \ - --hash=sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a \ - --hash=sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88 \ - --hash=sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554 \ - --hash=sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db \ - --hash=sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778 \ - --hash=sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c \ - --hash=sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917 \ - --hash=sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797 \ - --hash=sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160 \ - --hash=sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c \ - --hash=sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c \ - --hash=sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2 \ - --hash=sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4 \ - --hash=sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122 \ - --hash=sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94 +cryptography==41.0.4 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ + --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ + --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ + --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ + --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ + --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ + --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ + --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ + --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ + --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ + --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ + --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ + --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ + --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ + --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ + --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ + --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ + --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ + --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ + --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ + --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ + --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ + --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f deprecated==1.2.13 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d \ --hash=sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d @@ -395,7 +399,7 @@ pillow==9.3.0 ; python_version >= "3.9" and python_version < "4.0" \ proto-plus==1.22.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:0e8cda3d5a634d9895b75c573c9352c16486cb75deb0e078b5fda34db4243165 \ --hash=sha256:de34e52d6c9c6fcd704192f09767cb561bb4ee64e70eede20b0834d841f0be4d -protobuf==3.20.3 ; python_version >= "3.9" and python_version < "4.0" \ +protobuf==3.20.3 ; python_version < "4.0" and python_version >= "3.9" \ --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ @@ -416,6 +420,7 @@ protobuf==3.20.3 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ + --hash=sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1 \ --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee psycopg2==2.9.5 ; python_version >= "3.9" and python_version < "4.0" \ @@ -433,17 +438,39 @@ psycopg2==2.9.5 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:f5b6320dbc3cf6cfb9f25308286f9f7ab464e65cfb105b64cc9c52831748ced2 \ --hash=sha256:fc04dd5189b90d825509caa510f20d1d504761e78b8dfb95a0ede180f71d50e5 pyasn1-modules==0.2.8 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8 \ + --hash=sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199 \ + --hash=sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811 \ + --hash=sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed \ + --hash=sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4 \ --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 + --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 \ + --hash=sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb \ + --hash=sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45 \ + --hash=sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd \ + --hash=sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0 \ + --hash=sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d \ + --hash=sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405 pyasn1==0.4.8 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359 \ + --hash=sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576 \ + --hash=sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf \ + --hash=sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7 \ --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba + --hash=sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00 \ + --hash=sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8 \ + --hash=sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86 \ + --hash=sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12 \ + --hash=sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776 \ + --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba \ + --hash=sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2 \ + --hash=sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3 pycparser==2.20 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 -pyopenssl==23.1.1 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:841498b9bec61623b1b6c47ebbc02367c07d60e0e195f19790817f10cc8db0b7 \ - --hash=sha256:9e0c526404a210df9d2b18cd33364beadb0dc858a739b885677bc65e105d4a4c +pyopenssl==23.2.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2 \ + --hash=sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac pyparsing==2.4.7 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b @@ -461,7 +488,8 @@ requests-mock==1.9.3 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba requests-oauthlib==1.3.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ - --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc requests==2.31.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 From 2c856de7dc0554929686f690f9036425733509bb Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Fri, 22 Sep 2023 17:47:31 -0400 Subject: [PATCH 60/82] add boto3 1.28.53 and dependencies. ref #2086 --- poetry.lock | 87 +++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 ++ requirements.txt | 15 +++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index ddba94d174..52522b279e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,6 +32,46 @@ packaging = "*" six = ">=1.9.0" webencodings = "*" +[[package]] +name = "boto3" +version = "1.28.53" +description = "The AWS SDK for Python" +category = "main" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.28.53-py3-none-any.whl", hash = "sha256:dc2da9aff7de359774030a243a09b74568664117e2afb77c6e4b90572ae3a6c3"}, + {file = "boto3-1.28.53.tar.gz", hash = "sha256:b95b0cc39f08402029c3a2bb141e1775cfa46576ebe9f9916f79bde90e27f53f"}, +] + +[package.dependencies] +botocore = ">=1.31.53,<1.32.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.6.0,<0.7.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.31.53" +description = "Low-level, data-driven core of boto 3." +category = "main" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.31.53-py3-none-any.whl", hash = "sha256:aa647f94039d21de97c969df21ce8c5186b68234eb5c53148f0d8bbd708e375d"}, + {file = "botocore-1.31.53.tar.gz", hash = "sha256:905580ea724d74f11652bab63fcec6bf0d32f1cf8b2963f7388efc0ea406b69b"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.16.26)"] + [[package]] name = "cachetools" version = "4.2.2" @@ -844,6 +884,18 @@ files = [ {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + [[package]] name = "jwcrypto" version = "1.4.2" @@ -1175,6 +1227,21 @@ files = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-decouple" version = "3.4" @@ -1288,6 +1355,24 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "s3transfer" +version = "0.6.2" +description = "An Amazon S3 Transfer Manager" +category = "main" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.6.2-py3-none-any.whl", hash = "sha256:b014be3a8a2aab98cfe1abc7229cc5a9a0cf05eb9c1f2b86b230fd8df3f78084"}, + {file = "s3transfer-0.6.2.tar.gz", hash = "sha256:cab66d3380cca3e70939ef2255d01cd8aece6a4907a9528740f668c4b0611861"}, +] + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + [[package]] name = "selenium" version = "3.141.0" @@ -1551,4 +1636,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "da2b20a52af5d1f2b22adb4d2552609c3e764870e009098cca1cebb6244196f7" +content-hash = "4624cf0a93b4de6190ad28b43fa155522019505f8e7ced5afaa093abe26f895e" diff --git a/pyproject.toml b/pyproject.toml index 9788924147..76e24cd408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ authors = [] [tool.poetry.dependencies] python = "^3.9" bleach = "^3.3.0" +boto3 = "^1.28.53" +botocore = "^1.31.53" chardet = "^3.0.4" cryptography = "^41.0.4" Django = "4.1.10" diff --git a/requirements.txt b/requirements.txt index 892b5409b2..f3ec205aef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,12 @@ asgiref==3.5.2 ; python_version >= "3.9" and python_version < "4.0" \ bleach==3.3.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125 \ --hash=sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433 +boto3==1.28.53 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:b95b0cc39f08402029c3a2bb141e1775cfa46576ebe9f9916f79bde90e27f53f \ + --hash=sha256:dc2da9aff7de359774030a243a09b74568664117e2afb77c6e4b90572ae3a6c3 +botocore==1.31.53 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:905580ea724d74f11652bab63fcec6bf0d32f1cf8b2963f7388efc0ea406b69b \ + --hash=sha256:aa647f94039d21de97c969df21ce8c5186b68234eb5c53148f0d8bbd708e375d cachetools==4.2.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001 \ --hash=sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff @@ -312,6 +318,9 @@ httplib2==0.19.1 ; python_version >= "3.9" and python_version < "4.0" \ idna==2.10 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +jmespath==1.0.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ + --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe jwcrypto==1.4.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:80a35e9ed1b3b2c43ce03d92c5d48e6d0b6647e2aa2618e4963448923d78a37b libsass==0.21.0 ; python_version >= "3.9" and python_version < "4.0" \ @@ -474,6 +483,9 @@ pyopenssl==23.2.0 ; python_version >= "3.9" and python_version < "4.0" \ pyparsing==2.4.7 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +python-dateutil==2.8.2 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ + --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 python-decouple==3.4 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:2e5adb0263a4f963b58d7407c4760a2465d464ee212d733e2a2c179e54c08d8f \ --hash=sha256:a8268466e6389a639a20deab9d880faee186eb1eb6a05e54375bdf158d691981 @@ -496,6 +508,9 @@ requests==2.31.0 ; python_version >= "3.9" and python_version < "4.0" \ rsa==4.7.2 ; python_version >= "3.9" and python_version < "4" \ --hash=sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2 \ --hash=sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9 +s3transfer==0.6.2 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:b014be3a8a2aab98cfe1abc7229cc5a9a0cf05eb9c1f2b86b230fd8df3f78084 \ + --hash=sha256:cab66d3380cca3e70939ef2255d01cd8aece6a4907a9528740f668c4b0611861 selenium==3.141.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c \ --hash=sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d From 9876cf2160493b671d671e0dfc63a77c922826fb Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Fri, 14 Jul 2023 17:33:38 -0400 Subject: [PATCH 61/82] Allow associated publications to be added on the management page for published projects. --- physionet-django/console/forms.py | 17 ++++++++++++++ .../console/manage_published_project.html | 23 +++++++++++++++++++ physionet-django/console/views.py | 8 +++++++ 3 files changed, 48 insertions(+) diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index 6d66637160..b87549f046 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -27,6 +27,7 @@ PublishedAffiliation, PublishedAuthor, PublishedProject, + PublishedPublication, SubmissionStatus, exists_project_slug, ) @@ -707,6 +708,22 @@ def save(self): return contact +class PublishedProjectAddPublication(forms.ModelForm): + class Meta: + model = PublishedPublication + fields = ('citation', 'url') + + def __init__(self, project, *args, **kwargs): + super().__init__(*args, **kwargs) + self.project = project + + def save(self): + publication = super().save(commit=False) + publication.project = self.project + publication.save() + return publication + + class CreateLegacyAuthorForm(forms.ModelForm): """ Create an author for a legacy project. diff --git a/physionet-django/console/templates/console/manage_published_project.html b/physionet-django/console/templates/console/manage_published_project.html index 7b3902ea2e..de933752d6 100644 --- a/physionet-django/console/templates/console/manage_published_project.html +++ b/physionet-django/console/templates/console/manage_published_project.html @@ -49,6 +49,7 @@

        @@ -67,6 +68,28 @@

        Add an author

        {% endif %} + +
        +
        + Associated publication +
        +
        + {% if project.publications.all %} + {% for publication in project.publications.all %} +

        Citation: {{ publication.citation }}
        + URL: {{ publication.url }}

        + {% endfor %} + {% else %} +
        + {% include "inline_form_snippet.html" with form=publication_form %} + +
        + {% endif %} + +
        +
        + +
        Contact information diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index 769ba2b0b4..5298ff4baa 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -833,6 +833,7 @@ def manage_published_project(request, project_slug, version): contact_form = forms.PublishedProjectContactForm(project=project, instance=project.contact) legacy_author_form = forms.CreateLegacyAuthorForm(project=project) + publication_form = forms.PublishedProjectAddPublication(project=project) if request.method == 'POST': if any(x in request.POST for x in ['create_doi_core', @@ -914,6 +915,12 @@ def manage_published_project(request, project_slug, version): if contact_form.is_valid(): contact_form.save() messages.success(request, 'The contact information has been updated') + elif 'set_publication' in request.POST: + publication_form = forms.PublishedProjectAddPublication( + project=project, data=request.POST) + if publication_form.is_valid(): + publication_form.save() + messages.success(request, 'The associated publication has been added') elif 'set_legacy_author' in request.POST: legacy_author_form = forms.CreateLegacyAuthorForm(project=project, data=request.POST) @@ -957,6 +964,7 @@ def manage_published_project(request, project_slug, version): 'bulk_url_prefix': bulk_url_prefix, 'contact_form': contact_form, 'legacy_author_form': legacy_author_form, + 'publication_form': publication_form, 'can_make_zip': project.files.can_make_zip(), 'can_make_checksum': project.files.can_make_checksum(), }, From 86ffb33d57efbc8a172f3c34e1b45f5924147247 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Sat, 23 Sep 2023 18:07:58 -0400 Subject: [PATCH 62/82] add clean method to prevent multiple publications. the PublishedPublication model allows multiple publications to be added to a single project, but by policy we only allow a single publication to be added. --- physionet-django/console/forms.py | 16 +++++++++++++--- physionet-django/console/views.py | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index b87549f046..03e18881bf 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -708,7 +708,7 @@ def save(self): return contact -class PublishedProjectAddPublication(forms.ModelForm): +class AddPublishedPublicationForm(forms.ModelForm): class Meta: model = PublishedPublication fields = ('citation', 'url') @@ -717,10 +717,20 @@ def __init__(self, project, *args, **kwargs): super().__init__(*args, **kwargs) self.project = project - def save(self): + def clean(self): + cleaned_data = super().clean() + existing_publication = PublishedPublication.objects.filter(project=self.project).first() + + if existing_publication: + raise forms.ValidationError("A publication already exists for this project.") + + return cleaned_data + + def save(self, commit=True): publication = super().save(commit=False) publication.project = self.project - publication.save() + if commit: + publication.save() return publication diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index 5298ff4baa..c03ec5725f 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -833,7 +833,7 @@ def manage_published_project(request, project_slug, version): contact_form = forms.PublishedProjectContactForm(project=project, instance=project.contact) legacy_author_form = forms.CreateLegacyAuthorForm(project=project) - publication_form = forms.PublishedProjectAddPublication(project=project) + publication_form = forms.AddPublishedPublicationForm(project=project) if request.method == 'POST': if any(x in request.POST for x in ['create_doi_core', @@ -916,7 +916,7 @@ def manage_published_project(request, project_slug, version): contact_form.save() messages.success(request, 'The contact information has been updated') elif 'set_publication' in request.POST: - publication_form = forms.PublishedProjectAddPublication( + publication_form = forms.AddPublishedPublicationForm( project=project, data=request.POST) if publication_form.is_valid(): publication_form.save() From 2832a819c352987da875ad9300f5e96f44d3024a Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Sat, 23 Sep 2023 22:03:38 -0400 Subject: [PATCH 63/82] remove unused and repeated imports. --- physionet-django/console/forms.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/physionet-django/console/forms.py b/physionet-django/console/forms.py index 03e18881bf..c085efaf12 100644 --- a/physionet-django/console/forms.py +++ b/physionet-django/console/forms.py @@ -1,10 +1,5 @@ -import pdb import re -from django.forms.widgets import RadioSelect - -from django.forms.widgets import RadioSelect - from console.utility import generate_doi_payload, register_doi from dal import autocomplete from django import forms From 464719b041f78cf2ea6fe11c14f388eb3c020df9 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Mon, 25 Sep 2023 12:13:19 -0400 Subject: [PATCH 64/82] Display DOI for current version and latest version. Every version of a published project is assigned a DOI. An additional DOI is assigned to the 'core project' and this resolves to the latest published version. --- .../project/templates/project/project_preview.html | 8 ++++++-- .../project/templates/project/published_project.html | 9 ++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/physionet-django/project/templates/project/project_preview.html b/physionet-django/project/templates/project/project_preview.html index 3b2d46c04b..ee5a711345 100644 --- a/physionet-django/project/templates/project/project_preview.html +++ b/physionet-django/project/templates/project/project_preview.html @@ -84,7 +84,7 @@

        [Preview]: {{ project.title }} {% if project.version_clash %} {{ project.version}} * Clashing Version {% else %} - {{ project.version}} + {{ project.version }} {% endif %} {% else %} * Required field missing @@ -177,7 +177,11 @@

        Access
        Discovery
        -

        DOI:
        +

        DOI (version {{ project.version }}):
        + https://doi.org/10.13026/***** +

        + +

        DOI (latest version):
        https://doi.org/10.13026/*****

        diff --git a/physionet-django/project/templates/project/published_project.html b/physionet-django/project/templates/project/published_project.html index 7975abf812..cba490d00e 100644 --- a/physionet-django/project/templates/project/published_project.html +++ b/physionet-django/project/templates/project/published_project.html @@ -280,12 +280,19 @@
        Access
        Discovery
        {% if project.doi %} -

        DOI: +

        DOI (version {{ project.version }}):
        https://doi.org/{{ project.doi }}

        {% endif %} + {% if project.core_project.doi %} +

        DOI (latest version): +
        + https://doi.org/{{ project.core_project.doi }} +

        + {% endif %} + {% if languages %}

        Programming Languages: From 7ed3de430a7f17349f9e8f60cba104b875b4ebaa Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Tue, 26 Sep 2023 16:34:36 -0400 Subject: [PATCH 65/82] task_*_notify: use task_name if name is unspecified. When notifying about a failed or rescheduled task, if the task was created without a "verbose_name", use its "task_name" instead. (See console.tasks.task_rescheduled_handler and console.tasks.task_failed_handler, and note that verbose_name is nullable whereas task_name isn't.) When you create a task, specifying a verbose_name is recommended, as it can provide more human-readable information than the task_name and task_params. However, if no verbose_name is provided, we still need something to put in the email subject. --- physionet-django/notification/utility.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/physionet-django/notification/utility.py b/physionet-django/notification/utility.py index 0904283dd6..d62d293e86 100644 --- a/physionet-django/notification/utility.py +++ b/physionet-django/notification/utility.py @@ -888,6 +888,7 @@ def task_failed_notify(name, attempts, last_error, date_time, task_name, task_pa """ Notify when a task has failed and not rescheduled """ + name = name or task_name body = loader.render_to_string( 'notification/email/notify_failed_task.html', { 'name': name, @@ -906,6 +907,7 @@ def task_rescheduled_notify(name, attempts, last_error, date_time, task_name, ta """ Notify when a task has been rescheduled """ + name = name or task_name body = loader.render_to_string( 'notification/email/notify_rescheduled_task.html', { 'name': name, From d43b92245989bc8c5130a32cfaa71978f915f0ae Mon Sep 17 00:00:00 2001 From: Brian Gow Date: Wed, 27 Sep 2023 12:18:08 -0400 Subject: [PATCH 66/82] prevent embargo from being carried forward for new versions --- physionet-django/project/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/physionet-django/project/forms.py b/physionet-django/project/forms.py index 2ab8bdcb5d..7ee1985572 100644 --- a/physionet-django/project/forms.py +++ b/physionet-django/project/forms.py @@ -429,7 +429,7 @@ def save(self): # Direct copy over fields for field in (field.name for field in Metadata._meta.fields): - if field not in ['slug', 'version', 'creation_datetime']: + if field not in ['slug', 'version', 'creation_datetime', 'embargo_files_days']: setattr(project, field, getattr(self.latest_project, field)) # Set new fields From 7a5a86756d42d9e9302dc5d43369b9b96fa56a5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 03:12:18 +0000 Subject: [PATCH 67/82] Bump urllib3 from 1.26.15 to 1.26.17 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.15 to 1.26.17. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.15...1.26.17) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 113 ++++------------------------------------------------ 1 file changed, 7 insertions(+), 106 deletions(-) diff --git a/poetry.lock b/poetry.lock index 52522b279e..2292f08335 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "asgiref" version = "3.5.2" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -19,7 +18,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "bleach" version = "3.3.0" description = "An easy safelist-based HTML-sanitizing tool." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -36,7 +34,6 @@ webencodings = "*" name = "boto3" version = "1.28.53" description = "The AWS SDK for Python" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -56,7 +53,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.31.53" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -76,7 +72,6 @@ crt = ["awscrt (==0.16.26)"] name = "cachetools" version = "4.2.2" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = "~=3.5" files = [ @@ -88,7 +83,6 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -100,7 +94,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -177,7 +170,6 @@ pycparser = "*" name = "chardet" version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" -category = "main" optional = false python-versions = "*" files = [ @@ -189,7 +181,6 @@ files = [ name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -204,7 +195,6 @@ unicode-backport = ["unicodedata2"] name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -268,7 +258,6 @@ toml = ["tomli"] name = "cryptography" version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -314,7 +303,6 @@ test-randomorder = ["pytest-randomly"] name = "deprecated" version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -332,7 +320,6 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version name = "django" version = "4.1.10" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -353,7 +340,6 @@ bcrypt = ["bcrypt"] name = "django-autocomplete-light" version = "3.9.4" description = "Fresh autocompletes for Django" -category = "main" optional = false python-versions = "*" files = [ @@ -373,7 +359,6 @@ tags = ["django-taggit"] name = "django-background-tasks-updated" version = "1.2.7" description = "Database backed asynchronous task queue" -category = "main" optional = false python-versions = "*" files = [ @@ -389,7 +374,6 @@ six = "*" name = "django-ckeditor" version = "6.5.1" description = "Django admin CKEditor integration." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -405,7 +389,6 @@ django-js-asset = ">=2.0" name = "django-cors-headers" version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -420,7 +403,6 @@ Django = ">=3.2" name = "django-debug-toolbar" version = "3.2.4" description = "A configurable set of panels that display various debug information about the current request/response." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -436,7 +418,6 @@ sqlparse = ">=0.2.0" name = "django-js-asset" version = "2.0.0" description = "script tag with additional attributes for django.forms.Media" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -454,7 +435,6 @@ tests = ["coverage"] name = "django-oauth-toolkit" version = "2.2.0" description = "OAuth2 Provider for Django" -category = "main" optional = false python-versions = "*" files = [ @@ -472,7 +452,6 @@ requests = ">=2.13.0" name = "django-sass" version = "1.1.0" description = "The absolute simplest way to use Sass with Django. Pure Python, minimal dependencies, and no special configuration required!" -category = "main" optional = false python-versions = "*" files = [ @@ -488,7 +467,6 @@ libsass = "*" name = "django-storages" version = "1.12.3" description = "Support for many storage backends in Django" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -512,7 +490,6 @@ sftp = ["paramiko"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -528,7 +505,6 @@ pytz = "*" name = "google-api-core" version = "1.34.0" description = "Google API client core library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -553,7 +529,6 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] name = "google-api-python-client" version = "1.12.8" description = "Google API Client Library for Python" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -573,7 +548,6 @@ uritemplate = ">=3.0.0,<4dev" name = "google-auth" version = "1.32.0" description = "Google Authentication Library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -597,7 +571,6 @@ reauth = ["pyu2f (>=0.1.5)"] name = "google-auth-httplib2" version = "0.1.0" description = "Google Authentication Library: httplib2 transport" -category = "main" optional = false python-versions = "*" files = [ @@ -614,7 +587,6 @@ six = "*" name = "google-cloud-core" version = "1.7.0" description = "Google Cloud API client core library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -634,7 +606,6 @@ grpc = ["grpcio (>=1.8.2,<2.0dev)"] name = "google-cloud-storage" version = "1.42.3" description = "Google Cloud Storage API client library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -655,7 +626,6 @@ six = "*" name = "google-cloud-workflows" version = "1.9.1" description = "Google Cloud Workflows API client library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -664,10 +634,10 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.0 || >=2.11.0,<3.0.0dev", extras = ["grpc"]} +google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} proto-plus = [ - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -675,7 +645,6 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 name = "google-crc32c" version = "1.1.2" description = "A python wrapper of the C library 'Google CRC32C'" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -720,7 +689,6 @@ testing = ["pytest"] name = "google-resumable-media" version = "1.3.1" description = "Utilities for Google Media Downloads and Resumable Uploads" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -740,7 +708,6 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] name = "googleapis-common-protos" version = "1.58.0" description = "Common protobufs used in Google APIs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -758,7 +725,6 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] name = "grpcio" version = "1.53.0" description = "HTTP/2-based RPC framework" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -816,7 +782,6 @@ protobuf = ["grpcio-tools (>=1.53.0)"] name = "grpcio-status" version = "1.48.2" description = "Status proto mapping for gRPC" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -833,7 +798,6 @@ protobuf = ">=3.12.0" name = "hdn-research-environment" version = "2.3.8" description = "A Django app for supporting cloud-native research environments" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -849,7 +813,6 @@ google-cloud-workflows = ">=1.6.1" name = "html2text" version = "2018.1.9" description = "Turn HTML into equivalent Markdown-structured text." -category = "main" optional = false python-versions = "*" files = [ @@ -861,7 +824,6 @@ files = [ name = "httplib2" version = "0.19.1" description = "A comprehensive HTTP client library." -category = "main" optional = false python-versions = "*" files = [ @@ -876,7 +838,6 @@ pyparsing = ">=2.4.2,<3" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -888,7 +849,6 @@ files = [ name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -900,7 +860,6 @@ files = [ name = "jwcrypto" version = "1.4.2" description = "Implementation of JOSE Web standards" -category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -915,7 +874,6 @@ deprecated = "*" name = "libsass" version = "0.21.0" description = "Sass for Python: A straightforward binding of libsass for Python." -category = "main" optional = false python-versions = "*" files = [ @@ -938,7 +896,6 @@ six = "*" name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -955,7 +912,6 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] name = "packaging" version = "20.9" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -970,7 +926,6 @@ pyparsing = ">=2.0.2" name = "pdfminer.six" version = "20211012" description = "PDF parser and analyzer" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -990,7 +945,6 @@ docs = ["sphinx", "sphinx-argparse"] name = "pillow" version = "9.3.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1065,7 +1019,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "proto-plus" version = "1.22.2" description = "Beautiful, Pythonic protocol buffers." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1083,11 +1036,9 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "3.20.3" description = "Protocol Buffers" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "protobuf-3.20.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, @@ -1116,7 +1067,6 @@ files = [ name = "psycopg2" version = "2.9.5" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1139,22 +1089,10 @@ files = [ name = "pyasn1" version = "0.4.8" description = "ASN.1 types and codecs" -category = "main" optional = false python-versions = "*" files = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] @@ -1162,23 +1100,11 @@ files = [ name = "pyasn1-modules" version = "0.2.8" description = "A collection of ASN.1-based protocols modules." -category = "main" optional = false python-versions = "*" files = [ {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, - {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, - {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, - {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, - {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, - {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, - {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, - {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, - {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, - {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, - {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, - {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] [package.dependencies] @@ -1188,7 +1114,6 @@ pyasn1 = ">=0.4.6,<0.5.0" name = "pycparser" version = "2.20" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1200,7 +1125,6 @@ files = [ name = "pyopenssl" version = "23.2.0" description = "Python wrapper module around the OpenSSL library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1219,7 +1143,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1231,7 +1154,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1246,7 +1168,6 @@ six = ">=1.5" name = "python-decouple" version = "3.4" description = "Strict separation of settings from code." -category = "main" optional = false python-versions = "*" files = [ @@ -1258,7 +1179,6 @@ files = [ name = "python-json-logger" version = "2.0.2" description = "A python library adding a json log formatter" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1270,7 +1190,6 @@ files = [ name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -1282,7 +1201,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1304,7 +1222,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.9.3" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = "*" files = [ @@ -1324,13 +1241,11 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1 name = "requests-oauthlib" version = "1.3.0" description = "OAuthlib authentication support for Requests." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"}, - {file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"}, ] [package.dependencies] @@ -1344,7 +1259,6 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1359,7 +1273,6 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.6.2" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -1377,7 +1290,6 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] name = "selenium" version = "3.141.0" description = "Python bindings for Selenium" -category = "dev" optional = false python-versions = "*" files = [ @@ -1392,7 +1304,6 @@ urllib3 = "*" name = "sentry-sdk" version = "1.14.0" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = "*" files = [ @@ -1431,7 +1342,6 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1448,7 +1358,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1460,7 +1369,6 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1477,7 +1385,6 @@ test = ["pytest", "pytest-cov"] name = "tzdata" version = "2022.7" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -1489,7 +1396,6 @@ files = [ name = "uritemplate" version = "3.0.1" description = "URI templates" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1499,18 +1405,17 @@ files = [ [[package]] name = "urllib3" -version = "1.26.15" +version = "1.26.17" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, - {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, + {file = "urllib3-1.26.17-py2.py3-none-any.whl", hash = "sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b"}, + {file = "urllib3-1.26.17.tar.gz", hash = "sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1518,7 +1423,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "uwsgi" version = "2.0.22" description = "The uWSGI server" -category = "main" optional = false python-versions = "*" files = [ @@ -1529,7 +1433,6 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" files = [ @@ -1541,7 +1444,6 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1626,7 +1528,6 @@ files = [ name = "zxcvbn" version = "4.4.28" description = "" -category = "main" optional = false python-versions = "*" files = [ From f5dd8b70b3ffb60a97d744c674459b09f29cb6c4 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Tue, 3 Oct 2023 14:33:31 -0400 Subject: [PATCH 68/82] Bump urllib3 from 1.26.15 to 1.26.17 --- poetry.lock | 107 +++++++++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + requirements.txt | 6 +-- 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2292f08335..dd85774ff9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "asgiref" version = "3.5.2" description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -18,6 +19,7 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "bleach" version = "3.3.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -34,6 +36,7 @@ webencodings = "*" name = "boto3" version = "1.28.53" description = "The AWS SDK for Python" +category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -53,6 +56,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.31.53" description = "Low-level, data-driven core of boto 3." +category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -72,6 +76,7 @@ crt = ["awscrt (==0.16.26)"] name = "cachetools" version = "4.2.2" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = "~=3.5" files = [ @@ -83,6 +88,7 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -94,6 +100,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -170,6 +177,7 @@ pycparser = "*" name = "chardet" version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = "*" files = [ @@ -181,6 +189,7 @@ files = [ name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -195,6 +204,7 @@ unicode-backport = ["unicodedata2"] name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -258,6 +268,7 @@ toml = ["tomli"] name = "cryptography" version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -303,6 +314,7 @@ test-randomorder = ["pytest-randomly"] name = "deprecated" version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -320,6 +332,7 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version name = "django" version = "4.1.10" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -340,6 +353,7 @@ bcrypt = ["bcrypt"] name = "django-autocomplete-light" version = "3.9.4" description = "Fresh autocompletes for Django" +category = "main" optional = false python-versions = "*" files = [ @@ -359,6 +373,7 @@ tags = ["django-taggit"] name = "django-background-tasks-updated" version = "1.2.7" description = "Database backed asynchronous task queue" +category = "main" optional = false python-versions = "*" files = [ @@ -374,6 +389,7 @@ six = "*" name = "django-ckeditor" version = "6.5.1" description = "Django admin CKEditor integration." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -389,6 +405,7 @@ django-js-asset = ">=2.0" name = "django-cors-headers" version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -403,6 +420,7 @@ Django = ">=3.2" name = "django-debug-toolbar" version = "3.2.4" description = "A configurable set of panels that display various debug information about the current request/response." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -418,6 +436,7 @@ sqlparse = ">=0.2.0" name = "django-js-asset" version = "2.0.0" description = "script tag with additional attributes for django.forms.Media" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -435,6 +454,7 @@ tests = ["coverage"] name = "django-oauth-toolkit" version = "2.2.0" description = "OAuth2 Provider for Django" +category = "main" optional = false python-versions = "*" files = [ @@ -452,6 +472,7 @@ requests = ">=2.13.0" name = "django-sass" version = "1.1.0" description = "The absolute simplest way to use Sass with Django. Pure Python, minimal dependencies, and no special configuration required!" +category = "main" optional = false python-versions = "*" files = [ @@ -467,6 +488,7 @@ libsass = "*" name = "django-storages" version = "1.12.3" description = "Support for many storage backends in Django" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -490,6 +512,7 @@ sftp = ["paramiko"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -505,6 +528,7 @@ pytz = "*" name = "google-api-core" version = "1.34.0" description = "Google API client core library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -529,6 +553,7 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] name = "google-api-python-client" version = "1.12.8" description = "Google API Client Library for Python" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -548,6 +573,7 @@ uritemplate = ">=3.0.0,<4dev" name = "google-auth" version = "1.32.0" description = "Google Authentication Library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -571,6 +597,7 @@ reauth = ["pyu2f (>=0.1.5)"] name = "google-auth-httplib2" version = "0.1.0" description = "Google Authentication Library: httplib2 transport" +category = "main" optional = false python-versions = "*" files = [ @@ -587,6 +614,7 @@ six = "*" name = "google-cloud-core" version = "1.7.0" description = "Google Cloud API client core library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -606,6 +634,7 @@ grpc = ["grpcio (>=1.8.2,<2.0dev)"] name = "google-cloud-storage" version = "1.42.3" description = "Google Cloud Storage API client library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -626,6 +655,7 @@ six = "*" name = "google-cloud-workflows" version = "1.9.1" description = "Google Cloud Workflows API client library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -634,10 +664,10 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-api-core = {version = ">=1.34.0,<2.0.0 || >=2.11.0,<3.0.0dev", extras = ["grpc"]} proto-plus = [ - {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, + {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -645,6 +675,7 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 name = "google-crc32c" version = "1.1.2" description = "A python wrapper of the C library 'Google CRC32C'" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -689,6 +720,7 @@ testing = ["pytest"] name = "google-resumable-media" version = "1.3.1" description = "Utilities for Google Media Downloads and Resumable Uploads" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -708,6 +740,7 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] name = "googleapis-common-protos" version = "1.58.0" description = "Common protobufs used in Google APIs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -725,6 +758,7 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] name = "grpcio" version = "1.53.0" description = "HTTP/2-based RPC framework" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -782,6 +816,7 @@ protobuf = ["grpcio-tools (>=1.53.0)"] name = "grpcio-status" version = "1.48.2" description = "Status proto mapping for gRPC" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -798,6 +833,7 @@ protobuf = ">=3.12.0" name = "hdn-research-environment" version = "2.3.8" description = "A Django app for supporting cloud-native research environments" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -813,6 +849,7 @@ google-cloud-workflows = ">=1.6.1" name = "html2text" version = "2018.1.9" description = "Turn HTML into equivalent Markdown-structured text." +category = "main" optional = false python-versions = "*" files = [ @@ -824,6 +861,7 @@ files = [ name = "httplib2" version = "0.19.1" description = "A comprehensive HTTP client library." +category = "main" optional = false python-versions = "*" files = [ @@ -838,6 +876,7 @@ pyparsing = ">=2.4.2,<3" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -849,6 +888,7 @@ files = [ name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -860,6 +900,7 @@ files = [ name = "jwcrypto" version = "1.4.2" description = "Implementation of JOSE Web standards" +category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -874,6 +915,7 @@ deprecated = "*" name = "libsass" version = "0.21.0" description = "Sass for Python: A straightforward binding of libsass for Python." +category = "main" optional = false python-versions = "*" files = [ @@ -896,6 +938,7 @@ six = "*" name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -912,6 +955,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] name = "packaging" version = "20.9" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -926,6 +970,7 @@ pyparsing = ">=2.0.2" name = "pdfminer.six" version = "20211012" description = "PDF parser and analyzer" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -945,6 +990,7 @@ docs = ["sphinx", "sphinx-argparse"] name = "pillow" version = "9.3.0" description = "Python Imaging Library (Fork)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1019,6 +1065,7 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "proto-plus" version = "1.22.2" description = "Beautiful, Pythonic protocol buffers." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1036,9 +1083,11 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "3.20.3" description = "Protocol Buffers" +category = "main" optional = false python-versions = ">=3.7" files = [ + {file = "protobuf-3.20.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, @@ -1067,6 +1116,7 @@ files = [ name = "psycopg2" version = "2.9.5" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1089,10 +1139,22 @@ files = [ name = "pyasn1" version = "0.4.8" description = "ASN.1 types and codecs" +category = "main" optional = false python-versions = "*" files = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] @@ -1100,11 +1162,23 @@ files = [ name = "pyasn1-modules" version = "0.2.8" description = "A collection of ASN.1-based protocols modules." +category = "main" optional = false python-versions = "*" files = [ {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, + {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, + {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, + {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, + {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, + {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, + {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, + {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, + {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, + {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, + {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, + {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] [package.dependencies] @@ -1114,6 +1188,7 @@ pyasn1 = ">=0.4.6,<0.5.0" name = "pycparser" version = "2.20" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1125,6 +1200,7 @@ files = [ name = "pyopenssl" version = "23.2.0" description = "Python wrapper module around the OpenSSL library" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1143,6 +1219,7 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pyparsing" version = "2.4.7" description = "Python parsing module" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1154,6 +1231,7 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1168,6 +1246,7 @@ six = ">=1.5" name = "python-decouple" version = "3.4" description = "Strict separation of settings from code." +category = "main" optional = false python-versions = "*" files = [ @@ -1179,6 +1258,7 @@ files = [ name = "python-json-logger" version = "2.0.2" description = "A python library adding a json log formatter" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1190,6 +1270,7 @@ files = [ name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -1201,6 +1282,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1222,6 +1304,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.9.3" description = "Mock out responses from the requests package" +category = "dev" optional = false python-versions = "*" files = [ @@ -1241,11 +1324,13 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1 name = "requests-oauthlib" version = "1.3.0" description = "OAuthlib authentication support for Requests." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"}, + {file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"}, ] [package.dependencies] @@ -1259,6 +1344,7 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" +category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1273,6 +1359,7 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.6.2" description = "An Amazon S3 Transfer Manager" +category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -1290,6 +1377,7 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] name = "selenium" version = "3.141.0" description = "Python bindings for Selenium" +category = "dev" optional = false python-versions = "*" files = [ @@ -1304,6 +1392,7 @@ urllib3 = "*" name = "sentry-sdk" version = "1.14.0" description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" files = [ @@ -1342,6 +1431,7 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1358,6 +1448,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1369,6 +1460,7 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1385,6 +1477,7 @@ test = ["pytest", "pytest-cov"] name = "tzdata" version = "2022.7" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -1396,6 +1489,7 @@ files = [ name = "uritemplate" version = "3.0.1" description = "URI templates" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1407,6 +1501,7 @@ files = [ name = "urllib3" version = "1.26.17" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1423,6 +1518,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "uwsgi" version = "2.0.22" description = "The uWSGI server" +category = "main" optional = false python-versions = "*" files = [ @@ -1433,6 +1529,7 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "main" optional = false python-versions = "*" files = [ @@ -1444,6 +1541,7 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1528,6 +1626,7 @@ files = [ name = "zxcvbn" version = "4.4.28" description = "" +category = "main" optional = false python-versions = "*" files = [ @@ -1537,4 +1636,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "4624cf0a93b4de6190ad28b43fa155522019505f8e7ced5afaa093abe26f895e" +content-hash = "8e86e3e4a3097fa76a528c553f0ac8ed61cdc1179283439c42b317e2a62c07c5" diff --git a/pyproject.toml b/pyproject.toml index 76e24cd408..306c4abe87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ django-js-asset = "2.0.0" hdn-research-environment = "2.3.8" django-oauth-toolkit = "^2.2.0" django-cors-headers = "^3.14.0" +urllib3 = "^1.26.17" [tool.poetry.dev-dependencies] coverage = "^7.2.3" diff --git a/requirements.txt b/requirements.txt index f3ec205aef..92b28243f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -532,9 +532,9 @@ tzdata==2022.7 ; python_version >= "3.9" and python_version < "4.0" and sys_plat uritemplate==3.0.1 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f \ --hash=sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae -urllib3==1.26.15 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305 \ - --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 +urllib3==1.26.17 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ + --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b uwsgi==2.0.22 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:4cc4727258671ac5fa17ab422155e9aaef8a2008ebb86e4404b66deaae965db2 webencodings==0.5.1 ; python_version >= "3.9" and python_version < "4.0" \ From 3f2515e2795a773c214e647e8b7c615543545ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 00:56:39 +0000 Subject: [PATCH 69/82] Bump pillow from 9.3.0 to 10.0.1 Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.3.0 to 10.0.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.3.0...10.0.1) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 228 +++++++++++++------------------------------------ pyproject.toml | 2 +- 2 files changed, 62 insertions(+), 168 deletions(-) diff --git a/poetry.lock b/poetry.lock index dd85774ff9..bc2f1d24f6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "asgiref" version = "3.5.2" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -19,7 +18,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "bleach" version = "3.3.0" description = "An easy safelist-based HTML-sanitizing tool." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -36,7 +34,6 @@ webencodings = "*" name = "boto3" version = "1.28.53" description = "The AWS SDK for Python" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -56,7 +53,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.31.53" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -76,7 +72,6 @@ crt = ["awscrt (==0.16.26)"] name = "cachetools" version = "4.2.2" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = "~=3.5" files = [ @@ -88,7 +83,6 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -100,7 +94,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -177,7 +170,6 @@ pycparser = "*" name = "chardet" version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" -category = "main" optional = false python-versions = "*" files = [ @@ -189,7 +181,6 @@ files = [ name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -204,7 +195,6 @@ unicode-backport = ["unicodedata2"] name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -268,7 +258,6 @@ toml = ["tomli"] name = "cryptography" version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -314,7 +303,6 @@ test-randomorder = ["pytest-randomly"] name = "deprecated" version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -332,7 +320,6 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version name = "django" version = "4.1.10" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -353,7 +340,6 @@ bcrypt = ["bcrypt"] name = "django-autocomplete-light" version = "3.9.4" description = "Fresh autocompletes for Django" -category = "main" optional = false python-versions = "*" files = [ @@ -373,7 +359,6 @@ tags = ["django-taggit"] name = "django-background-tasks-updated" version = "1.2.7" description = "Database backed asynchronous task queue" -category = "main" optional = false python-versions = "*" files = [ @@ -389,7 +374,6 @@ six = "*" name = "django-ckeditor" version = "6.5.1" description = "Django admin CKEditor integration." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -405,7 +389,6 @@ django-js-asset = ">=2.0" name = "django-cors-headers" version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -420,7 +403,6 @@ Django = ">=3.2" name = "django-debug-toolbar" version = "3.2.4" description = "A configurable set of panels that display various debug information about the current request/response." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -436,7 +418,6 @@ sqlparse = ">=0.2.0" name = "django-js-asset" version = "2.0.0" description = "script tag with additional attributes for django.forms.Media" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -454,7 +435,6 @@ tests = ["coverage"] name = "django-oauth-toolkit" version = "2.2.0" description = "OAuth2 Provider for Django" -category = "main" optional = false python-versions = "*" files = [ @@ -472,7 +452,6 @@ requests = ">=2.13.0" name = "django-sass" version = "1.1.0" description = "The absolute simplest way to use Sass with Django. Pure Python, minimal dependencies, and no special configuration required!" -category = "main" optional = false python-versions = "*" files = [ @@ -488,7 +467,6 @@ libsass = "*" name = "django-storages" version = "1.12.3" description = "Support for many storage backends in Django" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -512,7 +490,6 @@ sftp = ["paramiko"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -528,7 +505,6 @@ pytz = "*" name = "google-api-core" version = "1.34.0" description = "Google API client core library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -553,7 +529,6 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] name = "google-api-python-client" version = "1.12.8" description = "Google API Client Library for Python" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -573,7 +548,6 @@ uritemplate = ">=3.0.0,<4dev" name = "google-auth" version = "1.32.0" description = "Google Authentication Library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -597,7 +571,6 @@ reauth = ["pyu2f (>=0.1.5)"] name = "google-auth-httplib2" version = "0.1.0" description = "Google Authentication Library: httplib2 transport" -category = "main" optional = false python-versions = "*" files = [ @@ -614,7 +587,6 @@ six = "*" name = "google-cloud-core" version = "1.7.0" description = "Google Cloud API client core library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -634,7 +606,6 @@ grpc = ["grpcio (>=1.8.2,<2.0dev)"] name = "google-cloud-storage" version = "1.42.3" description = "Google Cloud Storage API client library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -655,7 +626,6 @@ six = "*" name = "google-cloud-workflows" version = "1.9.1" description = "Google Cloud Workflows API client library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -664,10 +634,10 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.0 || >=2.11.0,<3.0.0dev", extras = ["grpc"]} +google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} proto-plus = [ - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -675,7 +645,6 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 name = "google-crc32c" version = "1.1.2" description = "A python wrapper of the C library 'Google CRC32C'" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -720,7 +689,6 @@ testing = ["pytest"] name = "google-resumable-media" version = "1.3.1" description = "Utilities for Google Media Downloads and Resumable Uploads" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -740,7 +708,6 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] name = "googleapis-common-protos" version = "1.58.0" description = "Common protobufs used in Google APIs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -758,7 +725,6 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] name = "grpcio" version = "1.53.0" description = "HTTP/2-based RPC framework" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -816,7 +782,6 @@ protobuf = ["grpcio-tools (>=1.53.0)"] name = "grpcio-status" version = "1.48.2" description = "Status proto mapping for gRPC" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -833,7 +798,6 @@ protobuf = ">=3.12.0" name = "hdn-research-environment" version = "2.3.8" description = "A Django app for supporting cloud-native research environments" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -849,7 +813,6 @@ google-cloud-workflows = ">=1.6.1" name = "html2text" version = "2018.1.9" description = "Turn HTML into equivalent Markdown-structured text." -category = "main" optional = false python-versions = "*" files = [ @@ -861,7 +824,6 @@ files = [ name = "httplib2" version = "0.19.1" description = "A comprehensive HTTP client library." -category = "main" optional = false python-versions = "*" files = [ @@ -876,7 +838,6 @@ pyparsing = ">=2.4.2,<3" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -888,7 +849,6 @@ files = [ name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -900,7 +860,6 @@ files = [ name = "jwcrypto" version = "1.4.2" description = "Implementation of JOSE Web standards" -category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -915,7 +874,6 @@ deprecated = "*" name = "libsass" version = "0.21.0" description = "Sass for Python: A straightforward binding of libsass for Python." -category = "main" optional = false python-versions = "*" files = [ @@ -938,7 +896,6 @@ six = "*" name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -955,7 +912,6 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] name = "packaging" version = "20.9" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -970,7 +926,6 @@ pyparsing = ">=2.0.2" name = "pdfminer.six" version = "20211012" description = "PDF parser and analyzer" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -988,84 +943,75 @@ docs = ["sphinx", "sphinx-argparse"] [[package]] name = "pillow" -version = "9.3.0" +version = "10.0.1" description = "Python Imaging Library (Fork)" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, - {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, - {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, - {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, - {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, - {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, - {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, - {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, - {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, - {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, - {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, - {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, - {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, - {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, - {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, - {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, - {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, - {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, + {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, + {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, + {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, + {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, + {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, + {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "proto-plus" version = "1.22.2" description = "Beautiful, Pythonic protocol buffers." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1083,11 +1029,9 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "3.20.3" description = "Protocol Buffers" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "protobuf-3.20.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, @@ -1116,7 +1060,6 @@ files = [ name = "psycopg2" version = "2.9.5" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1139,22 +1082,10 @@ files = [ name = "pyasn1" version = "0.4.8" description = "ASN.1 types and codecs" -category = "main" optional = false python-versions = "*" files = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] @@ -1162,23 +1093,11 @@ files = [ name = "pyasn1-modules" version = "0.2.8" description = "A collection of ASN.1-based protocols modules." -category = "main" optional = false python-versions = "*" files = [ {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, - {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, - {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, - {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, - {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, - {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, - {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, - {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, - {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, - {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, - {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, - {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] [package.dependencies] @@ -1188,7 +1107,6 @@ pyasn1 = ">=0.4.6,<0.5.0" name = "pycparser" version = "2.20" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1200,7 +1118,6 @@ files = [ name = "pyopenssl" version = "23.2.0" description = "Python wrapper module around the OpenSSL library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1219,7 +1136,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1231,7 +1147,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1246,7 +1161,6 @@ six = ">=1.5" name = "python-decouple" version = "3.4" description = "Strict separation of settings from code." -category = "main" optional = false python-versions = "*" files = [ @@ -1258,7 +1172,6 @@ files = [ name = "python-json-logger" version = "2.0.2" description = "A python library adding a json log formatter" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1270,7 +1183,6 @@ files = [ name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -1282,7 +1194,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1304,7 +1215,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.9.3" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = "*" files = [ @@ -1324,13 +1234,11 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1 name = "requests-oauthlib" version = "1.3.0" description = "OAuthlib authentication support for Requests." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"}, - {file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"}, ] [package.dependencies] @@ -1344,7 +1252,6 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1359,7 +1266,6 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.6.2" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -1377,7 +1283,6 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] name = "selenium" version = "3.141.0" description = "Python bindings for Selenium" -category = "dev" optional = false python-versions = "*" files = [ @@ -1392,7 +1297,6 @@ urllib3 = "*" name = "sentry-sdk" version = "1.14.0" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = "*" files = [ @@ -1431,7 +1335,6 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1448,7 +1351,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1460,7 +1362,6 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1477,7 +1378,6 @@ test = ["pytest", "pytest-cov"] name = "tzdata" version = "2022.7" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -1489,7 +1389,6 @@ files = [ name = "uritemplate" version = "3.0.1" description = "URI templates" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1501,7 +1400,6 @@ files = [ name = "urllib3" version = "1.26.17" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1518,7 +1416,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "uwsgi" version = "2.0.22" description = "The uWSGI server" -category = "main" optional = false python-versions = "*" files = [ @@ -1529,7 +1426,6 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" files = [ @@ -1541,7 +1437,6 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1626,7 +1521,6 @@ files = [ name = "zxcvbn" version = "4.4.28" description = "" -category = "main" optional = false python-versions = "*" files = [ @@ -1636,4 +1530,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "8e86e3e4a3097fa76a528c553f0ac8ed61cdc1179283439c42b317e2a62c07c5" +content-hash = "5539b76d78c8f2f162056f7f70c13573bfed0b32c949aa13a63cf054efd1b185" diff --git a/pyproject.toml b/pyproject.toml index 306c4abe87..7838dc9e69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ django-ckeditor = "6.5.1" djangorestframework = "3.14.0" google-cloud-storage = "^1.41.1" html2text = "^2018.1.9" -Pillow = "^9.3.0" +Pillow = "^10.0.1" python-decouple = "^3.1" uWSGI = "2.0.22" pyOpenSSL = "^23.2.0" From b0b98ccc028a76fd7d6bbac73384e33b50dc2484 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Tue, 3 Oct 2023 22:18:35 -0400 Subject: [PATCH 70/82] Bump pillow from 9.3.0 to 10.0.1 --- poetry.lock | 105 ++++++++++++++++++++++++++++++++++++++++-- requirements.txt | 117 ++++++++++++++++++++++------------------------- 2 files changed, 157 insertions(+), 65 deletions(-) diff --git a/poetry.lock b/poetry.lock index bc2f1d24f6..faaab257e5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "asgiref" version = "3.5.2" description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -18,6 +19,7 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "bleach" version = "3.3.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -34,6 +36,7 @@ webencodings = "*" name = "boto3" version = "1.28.53" description = "The AWS SDK for Python" +category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -53,6 +56,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.31.53" description = "Low-level, data-driven core of boto 3." +category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -72,6 +76,7 @@ crt = ["awscrt (==0.16.26)"] name = "cachetools" version = "4.2.2" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = "~=3.5" files = [ @@ -83,6 +88,7 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -94,6 +100,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -170,6 +177,7 @@ pycparser = "*" name = "chardet" version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = "*" files = [ @@ -181,6 +189,7 @@ files = [ name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -195,6 +204,7 @@ unicode-backport = ["unicodedata2"] name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -258,6 +268,7 @@ toml = ["tomli"] name = "cryptography" version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -303,6 +314,7 @@ test-randomorder = ["pytest-randomly"] name = "deprecated" version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -320,6 +332,7 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version name = "django" version = "4.1.10" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -340,6 +353,7 @@ bcrypt = ["bcrypt"] name = "django-autocomplete-light" version = "3.9.4" description = "Fresh autocompletes for Django" +category = "main" optional = false python-versions = "*" files = [ @@ -359,6 +373,7 @@ tags = ["django-taggit"] name = "django-background-tasks-updated" version = "1.2.7" description = "Database backed asynchronous task queue" +category = "main" optional = false python-versions = "*" files = [ @@ -374,6 +389,7 @@ six = "*" name = "django-ckeditor" version = "6.5.1" description = "Django admin CKEditor integration." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -389,6 +405,7 @@ django-js-asset = ">=2.0" name = "django-cors-headers" version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -403,6 +420,7 @@ Django = ">=3.2" name = "django-debug-toolbar" version = "3.2.4" description = "A configurable set of panels that display various debug information about the current request/response." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -418,6 +436,7 @@ sqlparse = ">=0.2.0" name = "django-js-asset" version = "2.0.0" description = "script tag with additional attributes for django.forms.Media" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -435,6 +454,7 @@ tests = ["coverage"] name = "django-oauth-toolkit" version = "2.2.0" description = "OAuth2 Provider for Django" +category = "main" optional = false python-versions = "*" files = [ @@ -452,6 +472,7 @@ requests = ">=2.13.0" name = "django-sass" version = "1.1.0" description = "The absolute simplest way to use Sass with Django. Pure Python, minimal dependencies, and no special configuration required!" +category = "main" optional = false python-versions = "*" files = [ @@ -467,6 +488,7 @@ libsass = "*" name = "django-storages" version = "1.12.3" description = "Support for many storage backends in Django" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -490,6 +512,7 @@ sftp = ["paramiko"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -505,6 +528,7 @@ pytz = "*" name = "google-api-core" version = "1.34.0" description = "Google API client core library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -529,6 +553,7 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] name = "google-api-python-client" version = "1.12.8" description = "Google API Client Library for Python" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -548,6 +573,7 @@ uritemplate = ">=3.0.0,<4dev" name = "google-auth" version = "1.32.0" description = "Google Authentication Library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -571,6 +597,7 @@ reauth = ["pyu2f (>=0.1.5)"] name = "google-auth-httplib2" version = "0.1.0" description = "Google Authentication Library: httplib2 transport" +category = "main" optional = false python-versions = "*" files = [ @@ -587,6 +614,7 @@ six = "*" name = "google-cloud-core" version = "1.7.0" description = "Google Cloud API client core library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -606,6 +634,7 @@ grpc = ["grpcio (>=1.8.2,<2.0dev)"] name = "google-cloud-storage" version = "1.42.3" description = "Google Cloud Storage API client library" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -626,6 +655,7 @@ six = "*" name = "google-cloud-workflows" version = "1.9.1" description = "Google Cloud Workflows API client library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -634,10 +664,10 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-api-core = {version = ">=1.34.0,<2.0.0 || >=2.11.0,<3.0.0dev", extras = ["grpc"]} proto-plus = [ - {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, + {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -645,6 +675,7 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 name = "google-crc32c" version = "1.1.2" description = "A python wrapper of the C library 'Google CRC32C'" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -689,6 +720,7 @@ testing = ["pytest"] name = "google-resumable-media" version = "1.3.1" description = "Utilities for Google Media Downloads and Resumable Uploads" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -708,6 +740,7 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] name = "googleapis-common-protos" version = "1.58.0" description = "Common protobufs used in Google APIs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -725,6 +758,7 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] name = "grpcio" version = "1.53.0" description = "HTTP/2-based RPC framework" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -782,6 +816,7 @@ protobuf = ["grpcio-tools (>=1.53.0)"] name = "grpcio-status" version = "1.48.2" description = "Status proto mapping for gRPC" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -798,6 +833,7 @@ protobuf = ">=3.12.0" name = "hdn-research-environment" version = "2.3.8" description = "A Django app for supporting cloud-native research environments" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -813,6 +849,7 @@ google-cloud-workflows = ">=1.6.1" name = "html2text" version = "2018.1.9" description = "Turn HTML into equivalent Markdown-structured text." +category = "main" optional = false python-versions = "*" files = [ @@ -824,6 +861,7 @@ files = [ name = "httplib2" version = "0.19.1" description = "A comprehensive HTTP client library." +category = "main" optional = false python-versions = "*" files = [ @@ -838,6 +876,7 @@ pyparsing = ">=2.4.2,<3" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -849,6 +888,7 @@ files = [ name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -860,6 +900,7 @@ files = [ name = "jwcrypto" version = "1.4.2" description = "Implementation of JOSE Web standards" +category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -874,6 +915,7 @@ deprecated = "*" name = "libsass" version = "0.21.0" description = "Sass for Python: A straightforward binding of libsass for Python." +category = "main" optional = false python-versions = "*" files = [ @@ -896,6 +938,7 @@ six = "*" name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -912,6 +955,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] name = "packaging" version = "20.9" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -926,6 +970,7 @@ pyparsing = ">=2.0.2" name = "pdfminer.six" version = "20211012" description = "PDF parser and analyzer" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -945,6 +990,7 @@ docs = ["sphinx", "sphinx-argparse"] name = "pillow" version = "10.0.1" description = "Python Imaging Library (Fork)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1012,6 +1058,7 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "proto-plus" version = "1.22.2" description = "Beautiful, Pythonic protocol buffers." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1029,9 +1076,11 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "3.20.3" description = "Protocol Buffers" +category = "main" optional = false python-versions = ">=3.7" files = [ + {file = "protobuf-3.20.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, @@ -1060,6 +1109,7 @@ files = [ name = "psycopg2" version = "2.9.5" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1082,10 +1132,22 @@ files = [ name = "pyasn1" version = "0.4.8" description = "ASN.1 types and codecs" +category = "main" optional = false python-versions = "*" files = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] @@ -1093,11 +1155,23 @@ files = [ name = "pyasn1-modules" version = "0.2.8" description = "A collection of ASN.1-based protocols modules." +category = "main" optional = false python-versions = "*" files = [ {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, + {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, + {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, + {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, + {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, + {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, + {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, + {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, + {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, + {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, + {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, + {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] [package.dependencies] @@ -1107,6 +1181,7 @@ pyasn1 = ">=0.4.6,<0.5.0" name = "pycparser" version = "2.20" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1118,6 +1193,7 @@ files = [ name = "pyopenssl" version = "23.2.0" description = "Python wrapper module around the OpenSSL library" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1136,6 +1212,7 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pyparsing" version = "2.4.7" description = "Python parsing module" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1147,6 +1224,7 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1161,6 +1239,7 @@ six = ">=1.5" name = "python-decouple" version = "3.4" description = "Strict separation of settings from code." +category = "main" optional = false python-versions = "*" files = [ @@ -1172,6 +1251,7 @@ files = [ name = "python-json-logger" version = "2.0.2" description = "A python library adding a json log formatter" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1183,6 +1263,7 @@ files = [ name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -1194,6 +1275,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1215,6 +1297,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.9.3" description = "Mock out responses from the requests package" +category = "dev" optional = false python-versions = "*" files = [ @@ -1234,11 +1317,13 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1 name = "requests-oauthlib" version = "1.3.0" description = "OAuthlib authentication support for Requests." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"}, + {file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"}, ] [package.dependencies] @@ -1252,6 +1337,7 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" +category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1266,6 +1352,7 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.6.2" description = "An Amazon S3 Transfer Manager" +category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -1283,6 +1370,7 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] name = "selenium" version = "3.141.0" description = "Python bindings for Selenium" +category = "dev" optional = false python-versions = "*" files = [ @@ -1297,6 +1385,7 @@ urllib3 = "*" name = "sentry-sdk" version = "1.14.0" description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" files = [ @@ -1335,6 +1424,7 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1351,6 +1441,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1362,6 +1453,7 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1378,6 +1470,7 @@ test = ["pytest", "pytest-cov"] name = "tzdata" version = "2022.7" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -1389,6 +1482,7 @@ files = [ name = "uritemplate" version = "3.0.1" description = "URI templates" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1400,6 +1494,7 @@ files = [ name = "urllib3" version = "1.26.17" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1416,6 +1511,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "uwsgi" version = "2.0.22" description = "The uWSGI server" +category = "main" optional = false python-versions = "*" files = [ @@ -1426,6 +1522,7 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "main" optional = false python-versions = "*" files = [ @@ -1437,6 +1534,7 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1521,6 +1619,7 @@ files = [ name = "zxcvbn" version = "4.4.28" description = "" +category = "main" optional = false python-versions = "*" files = [ diff --git a/requirements.txt b/requirements.txt index 92b28243f5..3fba22021f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -343,68 +343,61 @@ packaging==20.9 ; python_version >= "3.9" and python_version < "4.0" \ pdfminer-six==20211012 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:0351f17d362ee2d48b158be52bcde6576d96460efd038a3e89a043fba6d634d7 \ --hash=sha256:d3efb75c0249b51c1bf795e3a8bddf1726b276c77bf75fb136adea471ee2825b -pillow==9.3.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040 \ - --hash=sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8 \ - --hash=sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65 \ - --hash=sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2 \ - --hash=sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627 \ - --hash=sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07 \ - --hash=sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef \ - --hash=sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535 \ - --hash=sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c \ - --hash=sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc \ - --hash=sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3 \ - --hash=sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1 \ - --hash=sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c \ - --hash=sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa \ - --hash=sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32 \ - --hash=sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502 \ - --hash=sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4 \ - --hash=sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f \ - --hash=sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812 \ - --hash=sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636 \ - --hash=sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20 \ - --hash=sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c \ - --hash=sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91 \ - --hash=sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe \ - --hash=sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b \ - --hash=sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad \ - --hash=sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9 \ - --hash=sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72 \ - --hash=sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4 \ - --hash=sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de \ - --hash=sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29 \ - --hash=sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee \ - --hash=sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c \ - --hash=sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7 \ - --hash=sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11 \ - --hash=sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c \ - --hash=sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c \ - --hash=sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448 \ - --hash=sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b \ - --hash=sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20 \ - --hash=sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228 \ - --hash=sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd \ - --hash=sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699 \ - --hash=sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b \ - --hash=sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2 \ - --hash=sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4 \ - --hash=sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c \ - --hash=sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f \ - --hash=sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2 \ - --hash=sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c \ - --hash=sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3 \ - --hash=sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193 \ - --hash=sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48 \ - --hash=sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02 \ - --hash=sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8 \ - --hash=sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e \ - --hash=sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f \ - --hash=sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b \ - --hash=sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74 \ - --hash=sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb \ - --hash=sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0 +pillow==10.0.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff \ + --hash=sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f \ + --hash=sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21 \ + --hash=sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635 \ + --hash=sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a \ + --hash=sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f \ + --hash=sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1 \ + --hash=sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d \ + --hash=sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db \ + --hash=sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849 \ + --hash=sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7 \ + --hash=sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876 \ + --hash=sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3 \ + --hash=sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317 \ + --hash=sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91 \ + --hash=sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d \ + --hash=sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b \ + --hash=sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd \ + --hash=sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed \ + --hash=sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500 \ + --hash=sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7 \ + --hash=sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a \ + --hash=sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a \ + --hash=sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0 \ + --hash=sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf \ + --hash=sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f \ + --hash=sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1 \ + --hash=sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088 \ + --hash=sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971 \ + --hash=sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a \ + --hash=sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205 \ + --hash=sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54 \ + --hash=sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08 \ + --hash=sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21 \ + --hash=sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d \ + --hash=sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08 \ + --hash=sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e \ + --hash=sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf \ + --hash=sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b \ + --hash=sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145 \ + --hash=sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2 \ + --hash=sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d \ + --hash=sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d \ + --hash=sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf \ + --hash=sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad \ + --hash=sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d \ + --hash=sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1 \ + --hash=sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4 \ + --hash=sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2 \ + --hash=sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19 \ + --hash=sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37 \ + --hash=sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4 \ + --hash=sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68 \ + --hash=sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1 proto-plus==1.22.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:0e8cda3d5a634d9895b75c573c9352c16486cb75deb0e078b5fda34db4243165 \ --hash=sha256:de34e52d6c9c6fcd704192f09767cb561bb4ee64e70eede20b0834d841f0be4d From 0a440af694c5d42d86e016e92333b7ad805beb80 Mon Sep 17 00:00:00 2001 From: chrystinne Date: Thu, 5 Oct 2023 10:23:13 -0400 Subject: [PATCH 71/82] Validate AWS IDs to ensure they match AWS account IDs, which should be 12-digit numerical values. --- physionet-django/user/models.py | 8 +++++++- physionet-django/user/validators.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/physionet-django/user/models.py b/physionet-django/user/models.py index 886a1b53ca..2ad2e396f2 100644 --- a/physionet-django/user/models.py +++ b/physionet-django/user/models.py @@ -1218,7 +1218,13 @@ class CloudInformation(models.Model): on_delete=models.CASCADE) gcp_email = models.OneToOneField('user.AssociatedEmail', related_name='gcp_email', on_delete=models.SET_NULL, null=True) - aws_id = models.CharField(max_length=60, null=True, blank=True, default=None) + aws_id = models.CharField( + max_length=60, + null=True, + blank=True, + default=None, + validators=[validators.validate_aws_id], + ) class Meta: default_permissions = () diff --git a/physionet-django/user/validators.py b/physionet-django/user/validators.py index dbdd4e17cc..455a8619c2 100644 --- a/physionet-django/user/validators.py +++ b/physionet-django/user/validators.py @@ -271,3 +271,14 @@ def is_institutional_email(value): return True except ValidationError: return False + + +def validate_aws_id(value): + """" + Validate an AWS ID. + """ + aws_id_pattern = r"\b\d{12}\b" + if value is not None and not re.search(aws_id_pattern, value): + raise ValidationError( + "Invalid AWS ID. Please provide a valid AWS ID, which should be a 12-digit number." + ) From 4accd0337b9667a2f3e6858d5a5883c366e43b84 Mon Sep 17 00:00:00 2001 From: chrystinne Date: Thu, 5 Oct 2023 10:23:32 -0400 Subject: [PATCH 72/82] Add a migration file that alters the 'aws_id' field in the CloudInformation model. The modification includes the addition of the 'validate_aws_id' validator to ensure AWS ID validity at the model level. --- .../0057_alter_cloudinformation_aws_id.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 physionet-django/user/migrations/0057_alter_cloudinformation_aws_id.py diff --git a/physionet-django/user/migrations/0057_alter_cloudinformation_aws_id.py b/physionet-django/user/migrations/0057_alter_cloudinformation_aws_id.py new file mode 100644 index 0000000000..74735b2313 --- /dev/null +++ b/physionet-django/user/migrations/0057_alter_cloudinformation_aws_id.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.10 on 2023-10-04 21:14 + +from django.db import migrations, models +import user.validators + + +class Migration(migrations.Migration): + dependencies = [ + ("user", "0056_remove_credentialreview_appears_correct_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="cloudinformation", + name="aws_id", + field=models.CharField( + blank=True, + default=None, + max_length=60, + null=True, + validators=[user.validators.validate_aws_id], + ), + ), + ] From 6c59e37b5dabf4ac2ba2afbd86cbbe92013ee98e Mon Sep 17 00:00:00 2001 From: Michael Scanlan Date: Thu, 5 Oct 2023 14:38:00 -0400 Subject: [PATCH 73/82] updated text on credential and training request emails --- .../notification/email/notify_credential_request.html | 2 +- .../templates/notification/email/notify_training_request.html | 2 +- physionet-django/notification/utility.py | 4 ++++ physionet-django/user/templates/user/edit_credentialing.html | 2 +- physionet-django/user/views.py | 2 ++ 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/physionet-django/notification/templates/notification/email/notify_credential_request.html b/physionet-django/notification/templates/notification/email/notify_credential_request.html index 755df1035c..0d3925538d 100644 --- a/physionet-django/notification/templates/notification/email/notify_credential_request.html +++ b/physionet-django/notification/templates/notification/email/notify_credential_request.html @@ -7,7 +7,7 @@ Please note that each dataset may have its own data usage terms and/or training requirements. The specific training requirements and data usage terms for each dataset are listed in the project description. -It may take several weeks to process your request. Thank you for your understanding and patience. You can follow the status of your application at: {{ url_prefix }}{% url 'credential_application' %}. +Please allow {{ estimated_time_for_credentialing }} for your application to be reviewed and processed. Thank you for your understanding and patience. You can follow the status of your application at: {{ url_prefix }}{% url 'credential_application' %}. {{ signature }} diff --git a/physionet-django/notification/templates/notification/email/notify_training_request.html b/physionet-django/notification/templates/notification/email/notify_training_request.html index f5594d28bd..22d53515a7 100644 --- a/physionet-django/notification/templates/notification/email/notify_training_request.html +++ b/physionet-django/notification/templates/notification/email/notify_training_request.html @@ -5,7 +5,7 @@ Please note that each dataset may have its own data usage terms and/or training requirements. The specific training requirements and data usage terms for each dataset are listed in the project description. -It may take several weeks to process your request. Thank you for your understanding and patience. You can follow the status of your application at: {{ url_prefix }}{% url 'edit_training' %}. +Please allow {{ estimated_time_for_credentialing }} for your application to be reviewed and processed. Thank you for your understanding and patience. You can follow the status of your application at: {{ url_prefix }}{% url 'edit_training' %}. {{ signature }} diff --git a/physionet-django/notification/utility.py b/physionet-django/notification/utility.py index d62d293e86..4960b58e22 100644 --- a/physionet-django/notification/utility.py +++ b/physionet-django/notification/utility.py @@ -670,11 +670,13 @@ def process_training_complete(request, training, include_comments=True): Notify user of training decision """ subject = f'Your application for {settings.SITE_NAME} training' + estimated_time = 'one week' body = loader.render_to_string( 'notification/email/process_training_complete.html', { 'training': training, 'applicant_name': training.user.get_full_name(), 'domain': get_current_site(request), + 'estimated_time_for_credentialing': estimated_time, 'example_project': example_credentialed_access_project(), 'url_prefix': get_url_prefix(request), 'include_comments': training.reviewer_comments, @@ -715,11 +717,13 @@ def credential_application_request(request, application): """ applicant_name = application.get_full_name() subject = f'{settings.SITE_NAME} credentialing application notification' + estimated_time = 'one week' body = loader.render_to_string( 'notification/email/notify_credential_request.html', { 'application': application, 'applicant_name': applicant_name, 'domain': get_current_site(request), + 'estimated_time_for_credentialing': estimated_time, 'url_prefix': get_url_prefix(request), 'signature': settings.EMAIL_SIGNATURE, 'footer': email_footer(), 'SITE_NAME': settings.SITE_NAME diff --git a/physionet-django/user/templates/user/edit_credentialing.html b/physionet-django/user/templates/user/edit_credentialing.html index 193594d307..706aceabd8 100644 --- a/physionet-django/user/templates/user/edit_credentialing.html +++ b/physionet-django/user/templates/user/edit_credentialing.html @@ -25,7 +25,7 @@

        Credentialing

        {% elif current_application %}

        Your credentialing application was submitted on {{ current_application.application_datetime }}.

        Status of your application: {{ current_application.get_review_status }}.

        -

        We aim to reach a decision within four weeks. If you have not received a decision within this time, it is likely that we are awaiting a response from your reference.

        +

        We aim to reach a decision within {{ estimated_time_for_credentialing }}. If you have not received a decision within this time, it is likely that we are awaiting a response from your reference.

        {% else %}

        Your account is not credentialed. diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index fab5386d72..05628b8699 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -634,6 +634,7 @@ def edit_credentialing(request): applications = CredentialApplication.objects.filter(user=request.user) current_application = applications.filter(status=CredentialApplication.Status.PENDING).first() + estimated_time = 'one week' if request.method == 'POST' and 'withdraw_credentialing' in request.POST: if current_application: @@ -645,6 +646,7 @@ def edit_credentialing(request): return render(request, 'user/edit_credentialing.html', { 'applications': applications, 'pause_applications': pause_applications, + 'estimated_time_for_credentialing': estimated_time, 'pause_message': pause_message, 'current_application': current_application, 'ticket_system_url': ticket_system_url}) From ccc141a9866e531177715013db242126f6cbecb6 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 12 Oct 2023 16:06:36 -0400 Subject: [PATCH 74/82] credentialing_stats: don't crash if denominator is zero. If applications have been submitted during a particular year, but no applications from that year have (yet) been accepted or rejected, then it is impossible to compute the percentage of accepted applications (since pending and withdrawn applications aren't counted.) (For example, the demo data currently includes a pending application from year 2022, but no accepted or rejected applications.) Set this value to None, as is done with other metrics that cannot be computed. --- physionet-django/console/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/physionet-django/console/views.py b/physionet-django/console/views.py index c03ec5725f..12991c989d 100644 --- a/physionet-django/console/views.py +++ b/physionet-django/console/views.py @@ -1991,7 +1991,10 @@ def credentialing_stats(request): a = acc_and_rej.filter(status=CredentialApplication.Status.ACCEPTED).count() r = acc_and_rej.filter(status=CredentialApplication.Status.REJECTED).count() stats[y]['processed'] = a + r - stats[y]['approved'] = round((100 * a) / (a + r)) + try: + stats[y]['approved'] = round((100 * a) / (a + r)) + except ZeroDivisionError: + stats[y]['approved'] = None # Time taken to contact the reference time_to_ref = apps.annotate(tm=Cast(F('reference_contact_datetime') From 87f68dc7f635c6165157fff08d506bda4fd2c057 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Thu, 12 Oct 2023 16:52:07 -0400 Subject: [PATCH 75/82] console/urls.py: add parameters for TestURLs. A few URLs in this app cannot currently be tested due to a lack of demo data; in particular, testing the project submission pipeline would require creating a demo project for each submission stage. Many URLs in this app use a parameter called, uninformatively, "pk". In most such cases, setting "pk" to 1 will work; but it'd be wiser to use a descriptive name for the parameter. It'd also be wiser not to use integer IDs in URLs to begin with, and it would be wiser not to assume that integer IDs are also primary keys. Also note that user_access_logs_detail misleadingly names its parameter "pid" (it's a user ID, not any kind of project ID.) --- physionet-django/console/urls.py | 103 +++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/physionet-django/console/urls.py b/physionet-django/console/urls.py index 9f8922aa97..ace43bdac5 100644 --- a/physionet-django/console/urls.py +++ b/physionet-django/console/urls.py @@ -157,3 +157,106 @@ path('event_agreements//new-version/', views.event_agreement_new_version, name='event_agreement_new_version'), ] + +# Parameters for testing URLs (see physionet/test_urls.py) +TEST_DEFAULTS = { + '_user_': 'admin', + 'button_pk': 1, + 'event_slug': 'iLII4L9jSDFh', + 'page_pk': 2, + 'pk': 1, + 'pid': 1, + 'section_pk': 1, + 'news_id': 1, + 'username': 'rgmark', +} +TEST_CASES = { + 'manage_published_project': { + 'project_slug': 'demoeicu', + 'version': '2.0.0', + }, + + 'project_access_requests_detail': { + # id of a PublishedProject with access_policy=CONTRIBUTOR_REVIEW + 'pk': 4, + }, + + 'submission_info_redirect': { + 'project_slug': 'p7TCIMkltNswuOB9FZH1', + }, + 'submission_info': { + 'project_slug': 'p7TCIMkltNswuOB9FZH1', + }, + # Missing demo data (projects in appropriate submission states) + 'edit_submission': { + 'project_slug': 'xxxxxxxxxxxxxxxxxxxx', + '_skip_': True, + }, + 'copyedit_submission': { + 'project_slug': 'xxxxxxxxxxxxxxxxxxxx', + '_skip_': True, + }, + 'awaiting_authors': { + 'project_slug': 'xxxxxxxxxxxxxxxxxxxx', + '_skip_': True, + }, + 'publish_submission': { + 'project_slug': 'xxxxxxxxxxxxxxxxxxxx', + '_skip_': True, + }, + 'publish_slug_available': { + '_user_': 'tompollard', + 'project_slug': 'p7TCIMkltNswuOB9FZH1', + '_query_': {'desired_slug': 'note-parser'}, + }, + + 'credential_applications': [ + # categories defined in views.credential_applications() + {'status': 'successful'}, + {'status': 'unsucccessful'}, + {'status': 'pending'}, + ], + + 'view_credential_application': { + # slug of a CredentialApplication with any status + 'application_slug': '5rDeC8Om9dBJTeJN91y2', + }, + 'process_credential_application': { + # slug of a CredentialApplication with status=PENDING + 'application_slug': '5rDeC8Om9dBJTeJN91y2', + }, + + 'training_list': [ + # categories defined in views.training_list() + {'status': 'review'}, + {'status': 'valid'}, + {'status': 'expired'}, + {'status': 'rejected'}, + ], + 'training_process': { + # id of a Training with status=REVIEW + 'pk': 2, + }, + + 'user_group': { + # name of a Group + 'group': 'Managing%20Editor', + }, + 'users': [ + # categories defined in views.users() + {'group': 'all'}, + {'group': 'admin'}, + {'group': 'active'}, + {'group': 'inactive'}, + ], + + # Missing demo data + 'event_agreement_detail': {'_skip_': True}, + 'event_agreement_delete': {'_skip_': True}, + 'event_agreement_new_version': {'_skip_': True}, + + # Broken views: POST required for no reason + 'users_list_search': {'group': 'all', '_skip_': True}, + 'known_references_search': {'_skip_': True}, + 'news_search': {'_skip_': True}, +} From 9f63ec061df7ea68621cea66716bb3ff5469dbd1 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Mon, 16 Oct 2023 11:50:13 -0400 Subject: [PATCH 76/82] files_panel: fix escaping in inline scripts. When text is included as a string in an inline JavaScript expression, it must be escaped. Currently, the upload form does not permit creating files or directories with names containing quotes or control characters, but the templates should nonetheless protect against that possibility. --- physionet-django/project/templates/project/files_panel.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/physionet-django/project/templates/project/files_panel.html b/physionet-django/project/templates/project/files_panel.html index 1abedc9d03..f4875a9f29 100644 --- a/physionet-django/project/templates/project/files_panel.html +++ b/physionet-django/project/templates/project/files_panel.html @@ -43,14 +43,14 @@ {% if subdir %} - Parent Directory + Parent Directory {% endif %} {% for dir in display_dirs %} - {{ dir.name }} + {{ dir.name }} @@ -77,7 +77,7 @@ function navigateDir(subdir){ $.ajax({ type: "GET", - url: "{{ files_panel_url }}", + url: "{{ files_panel_url|escapejs }}", data: {'subdir':subdir }, success: function reloadSection(result){ From b58038e09cb3e8e4b31e9beaecc884ba94bd4b5a Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 29 Sep 2023 22:18:12 -0400 Subject: [PATCH 77/82] files_panel: fix internal links and browser history. In the files section of a preview or published project, clicking a link to a subdirectory or parent directory should behave like a normal link: - The browser's window.location should be updated, so that you can bookmark it. - The former location should be saved in the browser's history stack, so that you can navigate with Back and Forward buttons. Doing these things requires calling history.pushState when we navigate to a new location, and setting window.onpopstate to a handler function that restores the former location. Moreover, it is cleaner and safer to avoid using inline scripts. When a directory link is clicked, we will asynchronously load the directory contents; once they're loaded, call pushState to update the page URL and then parse the new directory HTML. (pushState must be called first so that relative links are handled correctly.) When the back button is clicked ("onpopstate" function is called), we likewise load the previous HTML directory contents, but in this case call replaceState instead of pushState (again, we need to update the page URL before parsing the HTML.) (You might think this replaceState is unnecessary, but because the HTML is loaded asynchronously, there could be multiple attempts in flight to change the page location, so it's better to keep the DOM and page URL and history state all in sync with each other.) To avoid overly complicated transition logic, the existing preview_files_panel and published_files_panel views are kept as-is. Only when somebody loads the new project_preview page or published_project page will they use the new script, and only when using the new script (which adds a "v=2" query parameter) will preview_files_panel and published_files_panel return the new inline-script-free responses. Eventually, preview_files_panel and published_files_panel can be changed to return an error if the v=2 parameter is missing, and the old files_panel.html can be removed. --- .../static/project/js/dynamic-files-panel.js | 38 ++++++++++ .../templates/project/files_panel.html | 1 + .../templates/project/files_panel_v2.html | 75 +++++++++++++++++++ .../templates/project/project_preview.html | 3 +- .../templates/project/published_project.html | 3 +- physionet-django/project/views.py | 14 +++- 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 physionet-django/project/static/project/js/dynamic-files-panel.js create mode 100644 physionet-django/project/templates/project/files_panel_v2.html diff --git a/physionet-django/project/static/project/js/dynamic-files-panel.js b/physionet-django/project/static/project/js/dynamic-files-panel.js new file mode 100644 index 0000000000..902f750f48 --- /dev/null +++ b/physionet-django/project/static/project/js/dynamic-files-panel.js @@ -0,0 +1,38 @@ +(function() { + 'use strict'; + + var panel = $('#files-panel'); + var cur_dir = panel.find('[data-dfp-cur-dir]').data('dfp-cur-dir'); + var panel_url = panel.find('[data-dfp-panel-url]').data('dfp-panel-url'); + + function navigateDir(subdir, page_url, push_history) { + $.ajax({ + type: 'GET', + url: panel_url, + data: {'subdir': subdir, 'v': '2'}, + success: function(result) { + if (push_history) + history.pushState(subdir, '', page_url); + else + history.replaceState(subdir, '', page_url); + panel.html(result); + setClickHandlers(); + }, + }); + } + + function setClickHandlers() { + panel.find('a[data-dfp-dir]').click(function(event) { + navigateDir($(this).data('dfp-dir'), this.href, true); + event.preventDefault(); + }); + } + setClickHandlers(); + + window.onpopstate = function(event) { + if (event.state !== null) { + navigateDir(event.state, window.location, false); + } + }; + history.replaceState(cur_dir, ''); +})(); diff --git a/physionet-django/project/templates/project/files_panel.html b/physionet-django/project/templates/project/files_panel.html index f4875a9f29..5caff7f0dd 100644 --- a/physionet-django/project/templates/project/files_panel.html +++ b/physionet-django/project/templates/project/files_panel.html @@ -1,3 +1,4 @@ +{# Note: This template is obsolescent. Use files_panel_v2 instead. #}

        Folder Navigation: {% spaceless %} diff --git a/physionet-django/project/templates/project/files_panel_v2.html b/physionet-django/project/templates/project/files_panel_v2.html new file mode 100644 index 0000000000..be59e07d75 --- /dev/null +++ b/physionet-django/project/templates/project/files_panel_v2.html @@ -0,0 +1,75 @@ +{# Note: This template is used together with dynamic-files-panel.js. #} +
        + Folder Navigation: + {% spaceless %} + + {% for breadcrumb in dir_breadcrumbs %} + {% if forloop.counter == dir_breadcrumbs|length %} + {{ breadcrumb.name }} + {% else %} + {{ breadcrumb.name }} + / + {% endif %} + {% endfor %} + + {% endspaceless %} +
        +{% if file_error %} +
        + +
        +{% else %} + {% if file_warning %} +
        + +
        + {% endif %} + + + + + + + + + + + + + {% if subdir %} + + + + + + {% endif %} + {% for dir in display_dirs %} + + + + + + {% endfor %} + {% for file in display_files %} + + + + + + {% endfor %} + +
        NameSizeModified
        Parent Directory
        {{ dir.name }}
        {{ file.name }} + + (download) + + {{ file.size }}{{ file.last_modified }}
        +{% endif %} diff --git a/physionet-django/project/templates/project/project_preview.html b/physionet-django/project/templates/project/project_preview.html index ee5a711345..7a65407beb 100644 --- a/physionet-django/project/templates/project/project_preview.html +++ b/physionet-django/project/templates/project/project_preview.html @@ -236,7 +236,7 @@

        Files

        {% endif %} {% endif %}
        - {% include "project/files_panel.html" %} + {% include "project/files_panel_v2.html" %}

        @@ -244,6 +244,7 @@

        Files

        {% endblock %} {% block local_js_bottom %} + {% endblock %} diff --git a/physionet-django/project/templates/project/published_project.html b/physionet-django/project/templates/project/published_project.html index cba490d00e..3ec8ddccd7 100644 --- a/physionet-django/project/templates/project/published_project.html +++ b/physionet-django/project/templates/project/published_project.html @@ -442,7 +442,7 @@
        Access the files
        {% endif %}
        - {% include "project/files_panel.html" %} + {% include "project/files_panel_v2.html" %}
        {% else %} {% include "project/published_project_denied_downloads.html" %} @@ -472,6 +472,7 @@
        Access the files
        {% endblock %} {% block local_js_bottom %} + {% endblock %} diff --git a/physionet-django/project/views.py b/physionet-django/project/views.py index 2d4688464a..a680ab43f5 100644 --- a/physionet-django/project/views.py +++ b/physionet-django/project/views.py @@ -1151,7 +1151,12 @@ def preview_files_panel(request, project_slug, **kwargs): file_warning = get_project_file_warning(display_files, display_dirs, subdir) - return render(request, 'project/files_panel.html', + if request.GET.get('v', '1') == '1': + template = 'project/files_panel.html' + else: + template = 'project/files_panel_v2.html' + + return render(request, template, {'project':project, 'subdir':subdir, 'file_error':file_error, 'dir_breadcrumbs':dir_breadcrumbs, 'parent_dir':parent_dir, 'display_files':display_files, 'display_dirs':display_dirs, @@ -1574,7 +1579,12 @@ def published_files_panel(request, project_slug, version): files_panel_url = reverse('published_files_panel', args=(project.slug, project.version)) - return render(request, 'project/files_panel.html', + if request.GET.get('v', '1') == '1': + template = 'project/files_panel.html' + else: + template = 'project/files_panel_v2.html' + + return render(request, template, {'project':project, 'subdir':subdir, 'dir_breadcrumbs':dir_breadcrumbs, 'parent_dir':parent_dir, 'display_files':display_files, 'display_dirs':display_dirs, From 7c2310539ae32b1b87a0e3fb2597706b71850c57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 01:21:41 +0000 Subject: [PATCH 78/82] Bump urllib3 from 1.26.17 to 1.26.18 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.17 to 1.26.18. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.17...1.26.18) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 113 +++---------------------------------------------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 107 deletions(-) diff --git a/poetry.lock b/poetry.lock index faaab257e5..9c88d618de 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "asgiref" version = "3.5.2" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -19,7 +18,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "bleach" version = "3.3.0" description = "An easy safelist-based HTML-sanitizing tool." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -36,7 +34,6 @@ webencodings = "*" name = "boto3" version = "1.28.53" description = "The AWS SDK for Python" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -56,7 +53,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.31.53" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -76,7 +72,6 @@ crt = ["awscrt (==0.16.26)"] name = "cachetools" version = "4.2.2" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = "~=3.5" files = [ @@ -88,7 +83,6 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -100,7 +94,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -177,7 +170,6 @@ pycparser = "*" name = "chardet" version = "3.0.4" description = "Universal encoding detector for Python 2 and 3" -category = "main" optional = false python-versions = "*" files = [ @@ -189,7 +181,6 @@ files = [ name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -204,7 +195,6 @@ unicode-backport = ["unicodedata2"] name = "coverage" version = "7.2.3" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -268,7 +258,6 @@ toml = ["tomli"] name = "cryptography" version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -314,7 +303,6 @@ test-randomorder = ["pytest-randomly"] name = "deprecated" version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -332,7 +320,6 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version name = "django" version = "4.1.10" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -353,7 +340,6 @@ bcrypt = ["bcrypt"] name = "django-autocomplete-light" version = "3.9.4" description = "Fresh autocompletes for Django" -category = "main" optional = false python-versions = "*" files = [ @@ -373,7 +359,6 @@ tags = ["django-taggit"] name = "django-background-tasks-updated" version = "1.2.7" description = "Database backed asynchronous task queue" -category = "main" optional = false python-versions = "*" files = [ @@ -389,7 +374,6 @@ six = "*" name = "django-ckeditor" version = "6.5.1" description = "Django admin CKEditor integration." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -405,7 +389,6 @@ django-js-asset = ">=2.0" name = "django-cors-headers" version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -420,7 +403,6 @@ Django = ">=3.2" name = "django-debug-toolbar" version = "3.2.4" description = "A configurable set of panels that display various debug information about the current request/response." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -436,7 +418,6 @@ sqlparse = ">=0.2.0" name = "django-js-asset" version = "2.0.0" description = "script tag with additional attributes for django.forms.Media" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -454,7 +435,6 @@ tests = ["coverage"] name = "django-oauth-toolkit" version = "2.2.0" description = "OAuth2 Provider for Django" -category = "main" optional = false python-versions = "*" files = [ @@ -472,7 +452,6 @@ requests = ">=2.13.0" name = "django-sass" version = "1.1.0" description = "The absolute simplest way to use Sass with Django. Pure Python, minimal dependencies, and no special configuration required!" -category = "main" optional = false python-versions = "*" files = [ @@ -488,7 +467,6 @@ libsass = "*" name = "django-storages" version = "1.12.3" description = "Support for many storage backends in Django" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -512,7 +490,6 @@ sftp = ["paramiko"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -528,7 +505,6 @@ pytz = "*" name = "google-api-core" version = "1.34.0" description = "Google API client core library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -553,7 +529,6 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] name = "google-api-python-client" version = "1.12.8" description = "Google API Client Library for Python" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -573,7 +548,6 @@ uritemplate = ">=3.0.0,<4dev" name = "google-auth" version = "1.32.0" description = "Google Authentication Library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -597,7 +571,6 @@ reauth = ["pyu2f (>=0.1.5)"] name = "google-auth-httplib2" version = "0.1.0" description = "Google Authentication Library: httplib2 transport" -category = "main" optional = false python-versions = "*" files = [ @@ -614,7 +587,6 @@ six = "*" name = "google-cloud-core" version = "1.7.0" description = "Google Cloud API client core library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -634,7 +606,6 @@ grpc = ["grpcio (>=1.8.2,<2.0dev)"] name = "google-cloud-storage" version = "1.42.3" description = "Google Cloud Storage API client library" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -655,7 +626,6 @@ six = "*" name = "google-cloud-workflows" version = "1.9.1" description = "Google Cloud Workflows API client library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -664,10 +634,10 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.0 || >=2.11.0,<3.0.0dev", extras = ["grpc"]} +google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} proto-plus = [ - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" @@ -675,7 +645,6 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 name = "google-crc32c" version = "1.1.2" description = "A python wrapper of the C library 'Google CRC32C'" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -720,7 +689,6 @@ testing = ["pytest"] name = "google-resumable-media" version = "1.3.1" description = "Utilities for Google Media Downloads and Resumable Uploads" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -740,7 +708,6 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] name = "googleapis-common-protos" version = "1.58.0" description = "Common protobufs used in Google APIs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -758,7 +725,6 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] name = "grpcio" version = "1.53.0" description = "HTTP/2-based RPC framework" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -816,7 +782,6 @@ protobuf = ["grpcio-tools (>=1.53.0)"] name = "grpcio-status" version = "1.48.2" description = "Status proto mapping for gRPC" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -833,7 +798,6 @@ protobuf = ">=3.12.0" name = "hdn-research-environment" version = "2.3.8" description = "A Django app for supporting cloud-native research environments" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -849,7 +813,6 @@ google-cloud-workflows = ">=1.6.1" name = "html2text" version = "2018.1.9" description = "Turn HTML into equivalent Markdown-structured text." -category = "main" optional = false python-versions = "*" files = [ @@ -861,7 +824,6 @@ files = [ name = "httplib2" version = "0.19.1" description = "A comprehensive HTTP client library." -category = "main" optional = false python-versions = "*" files = [ @@ -876,7 +838,6 @@ pyparsing = ">=2.4.2,<3" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -888,7 +849,6 @@ files = [ name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -900,7 +860,6 @@ files = [ name = "jwcrypto" version = "1.4.2" description = "Implementation of JOSE Web standards" -category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -915,7 +874,6 @@ deprecated = "*" name = "libsass" version = "0.21.0" description = "Sass for Python: A straightforward binding of libsass for Python." -category = "main" optional = false python-versions = "*" files = [ @@ -938,7 +896,6 @@ six = "*" name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -955,7 +912,6 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] name = "packaging" version = "20.9" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -970,7 +926,6 @@ pyparsing = ">=2.0.2" name = "pdfminer.six" version = "20211012" description = "PDF parser and analyzer" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -990,7 +945,6 @@ docs = ["sphinx", "sphinx-argparse"] name = "pillow" version = "10.0.1" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1058,7 +1012,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "proto-plus" version = "1.22.2" description = "Beautiful, Pythonic protocol buffers." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1076,11 +1029,9 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "3.20.3" description = "Protocol Buffers" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "protobuf-3.20.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, @@ -1109,7 +1060,6 @@ files = [ name = "psycopg2" version = "2.9.5" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1132,22 +1082,10 @@ files = [ name = "pyasn1" version = "0.4.8" description = "ASN.1 types and codecs" -category = "main" optional = false python-versions = "*" files = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] @@ -1155,23 +1093,11 @@ files = [ name = "pyasn1-modules" version = "0.2.8" description = "A collection of ASN.1-based protocols modules." -category = "main" optional = false python-versions = "*" files = [ {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, - {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, - {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, - {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, - {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, - {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, - {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, - {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, - {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, - {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, - {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, - {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] [package.dependencies] @@ -1181,7 +1107,6 @@ pyasn1 = ">=0.4.6,<0.5.0" name = "pycparser" version = "2.20" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1193,7 +1118,6 @@ files = [ name = "pyopenssl" version = "23.2.0" description = "Python wrapper module around the OpenSSL library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1212,7 +1136,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1224,7 +1147,6 @@ files = [ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1239,7 +1161,6 @@ six = ">=1.5" name = "python-decouple" version = "3.4" description = "Strict separation of settings from code." -category = "main" optional = false python-versions = "*" files = [ @@ -1251,7 +1172,6 @@ files = [ name = "python-json-logger" version = "2.0.2" description = "A python library adding a json log formatter" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1263,7 +1183,6 @@ files = [ name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -1275,7 +1194,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1297,7 +1215,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.9.3" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = "*" files = [ @@ -1317,13 +1234,11 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1 name = "requests-oauthlib" version = "1.3.0" description = "OAuthlib authentication support for Requests." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"}, - {file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"}, ] [package.dependencies] @@ -1337,7 +1252,6 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1352,7 +1266,6 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.6.2" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -1370,7 +1283,6 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] name = "selenium" version = "3.141.0" description = "Python bindings for Selenium" -category = "dev" optional = false python-versions = "*" files = [ @@ -1385,7 +1297,6 @@ urllib3 = "*" name = "sentry-sdk" version = "1.14.0" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = "*" files = [ @@ -1424,7 +1335,6 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1441,7 +1351,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1453,7 +1362,6 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1470,7 +1378,6 @@ test = ["pytest", "pytest-cov"] name = "tzdata" version = "2022.7" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -1482,7 +1389,6 @@ files = [ name = "uritemplate" version = "3.0.1" description = "URI templates" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1492,14 +1398,13 @@ files = [ [[package]] name = "urllib3" -version = "1.26.17" +version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.17-py2.py3-none-any.whl", hash = "sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b"}, - {file = "urllib3-1.26.17.tar.gz", hash = "sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21"}, + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] @@ -1511,7 +1416,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "uwsgi" version = "2.0.22" description = "The uWSGI server" -category = "main" optional = false python-versions = "*" files = [ @@ -1522,7 +1426,6 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" files = [ @@ -1534,7 +1437,6 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1619,7 +1521,6 @@ files = [ name = "zxcvbn" version = "4.4.28" description = "" -category = "main" optional = false python-versions = "*" files = [ @@ -1629,4 +1530,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "5539b76d78c8f2f162056f7f70c13573bfed0b32c949aa13a63cf054efd1b185" +content-hash = "e61f939f4000f94213fc7bfb6be961299d04d920f94172670df1900868e0d09d" diff --git a/pyproject.toml b/pyproject.toml index 7838dc9e69..8caf5ce5e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ django-js-asset = "2.0.0" hdn-research-environment = "2.3.8" django-oauth-toolkit = "^2.2.0" django-cors-headers = "^3.14.0" -urllib3 = "^1.26.17" +urllib3 = "^1.26.18" [tool.poetry.dev-dependencies] coverage = "^7.2.3" From 8902250c2216e71df5c34ca529eadf2b87a09ff1 Mon Sep 17 00:00:00 2001 From: Tom Pollard Date: Tue, 17 Oct 2023 21:58:14 -0400 Subject: [PATCH 79/82] Bump urllib3 from 1.26.17 to 1.26.18 --- requirements.txt | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3fba22021f..83dbd6ea5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -401,7 +401,7 @@ pillow==10.0.1 ; python_version >= "3.9" and python_version < "4.0" \ proto-plus==1.22.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:0e8cda3d5a634d9895b75c573c9352c16486cb75deb0e078b5fda34db4243165 \ --hash=sha256:de34e52d6c9c6fcd704192f09767cb561bb4ee64e70eede20b0834d841f0be4d -protobuf==3.20.3 ; python_version < "4.0" and python_version >= "3.9" \ +protobuf==3.20.3 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ @@ -422,7 +422,6 @@ protobuf==3.20.3 ; python_version < "4.0" and python_version >= "3.9" \ --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ - --hash=sha256:e67f9af1b607eb3a89aafc9bc68a9d1172aae788b2445cb9fd781bd97531f1f1 \ --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee psycopg2==2.9.5 ; python_version >= "3.9" and python_version < "4.0" \ @@ -440,33 +439,11 @@ psycopg2==2.9.5 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:f5b6320dbc3cf6cfb9f25308286f9f7ab464e65cfb105b64cc9c52831748ced2 \ --hash=sha256:fc04dd5189b90d825509caa510f20d1d504761e78b8dfb95a0ede180f71d50e5 pyasn1-modules==0.2.8 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8 \ - --hash=sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199 \ - --hash=sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811 \ - --hash=sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed \ - --hash=sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4 \ --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 \ - --hash=sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb \ - --hash=sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45 \ - --hash=sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd \ - --hash=sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0 \ - --hash=sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d \ - --hash=sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405 + --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 pyasn1==0.4.8 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359 \ - --hash=sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576 \ - --hash=sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf \ - --hash=sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7 \ --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00 \ - --hash=sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8 \ - --hash=sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86 \ - --hash=sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12 \ - --hash=sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776 \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba \ - --hash=sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2 \ - --hash=sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3 + --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba pycparser==2.20 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 @@ -493,8 +470,7 @@ requests-mock==1.9.3 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba requests-oauthlib==1.3.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ - --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ - --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a requests==2.31.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 @@ -525,9 +501,9 @@ tzdata==2022.7 ; python_version >= "3.9" and python_version < "4.0" and sys_plat uritemplate==3.0.1 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f \ --hash=sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae -urllib3==1.26.17 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ - --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b +urllib3==1.26.18 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07 \ + --hash=sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0 uwsgi==2.0.22 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:4cc4727258671ac5fa17ab422155e9aaef8a2008ebb86e4404b66deaae965db2 webencodings==0.5.1 ; python_version >= "3.9" and python_version < "4.0" \ From 5fa40bb720e1f44cd30c0165c6b4ec6ace384e11 Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Wed, 18 Oct 2023 16:11:37 -0400 Subject: [PATCH 80/82] edit_files_panel: fix escaping in inline scripts. When text is included as a string in an inline JavaScript expression, it must be escaped. Currently, the upload form does not permit creating files or directories with names containing quotes or control characters, but the templates should nonetheless protect against that possibility. --- .../project/templates/project/edit_files_panel.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/physionet-django/project/templates/project/edit_files_panel.html b/physionet-django/project/templates/project/edit_files_panel.html index 53e1bd8197..724eee3cfd 100644 --- a/physionet-django/project/templates/project/edit_files_panel.html +++ b/physionet-django/project/templates/project/edit_files_panel.html @@ -26,7 +26,7 @@ {{ breadcrumb.name }} {% else %} {{ breadcrumb.name }} / {% endif %} @@ -64,7 +64,7 @@ {% if subdir %} - Parent Directory + Parent Directory @@ -72,7 +72,7 @@ {% endif %} {% for dir in display_dirs %} - {{ dir.name }} + {{ dir.name }} {% if files_editable %}{% endif %} @@ -117,7 +117,7 @@ }; }, accept: (file, done) => { - const payload = {size: file.size, filename: `{{ subdir }}/${file.upload.filename}`, csrfmiddlewaretoken: "{{ csrf_token }}"}; + const payload = {size: file.size, filename: "{{ subdir|escapejs }}/" + file.upload.filename, csrfmiddlewaretoken: "{{ csrf_token }}"}; $.post("{% url 'generate_signed_url' project_slug=project.slug %}", payload, "json") .done(data => { From a0ff2f57e29555e164127d4a9251b25344e282c2 Mon Sep 17 00:00:00 2001 From: Michael Scanlan Date: Thu, 12 Oct 2023 07:31:31 -0400 Subject: [PATCH 81/82] added group affiliation to user management page --- .../templates/console/user_management.html | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/physionet-django/console/templates/console/user_management.html b/physionet-django/console/templates/console/user_management.html index 64900684ae..f82f3863ea 100644 --- a/physionet-django/console/templates/console/user_management.html +++ b/physionet-django/console/templates/console/user_management.html @@ -111,21 +111,27 @@

        Profile

        {% endfor %} - {% if groups %} -
        -
        - User Groups: -
        -
        - {% for group in groups %} - {{ group.name }} - {% empty %} - N/A - {% endfor %} -
        +
        +

        Permission groups

        +
        +
        + {% if groups %} +
        + The user is a member of the following groups:
        - - {% endif %} +
        + +
        + {% else %} +
        + The user does not belong to a permission group. +
        + {% endif %} +

        Projects

        From a302aa122f7daf7965b0af2426cf7077909319ec Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Mon, 23 Oct 2023 13:44:54 -0400 Subject: [PATCH 82/82] Add "IsDerivedFrom" to DOI metadata. If a published project is derived from other published projects on the site, include that information in the DOI metadata. Formally, "IsDerivedFrom indicates B [the related resource] is a source upon which A [the resource being registered] is based. IsDerivedFrom should be used for a resource that is a derivative of an original resource." https://schema.datacite.org/meta/kernel-4.4/doc/DataCite-MetadataKernel_v4.4.pdf (page 63) --- physionet-django/console/utility.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/physionet-django/console/utility.py b/physionet-django/console/utility.py index 867043faf9..e3f531c90d 100644 --- a/physionet-django/console/utility.py +++ b/physionet-django/console/utility.py @@ -516,6 +516,24 @@ def generate_doi_payload(project, core_project=False, event="draft"): else: relation = [] + # projects from which this project is derived + for parent_project in project.parent_projects.all(): + if parent_project.doi: + relation.append({ + "relationType": "IsDerivedFrom", + "relatedIdentifier": parent_project.doi, + "relatedIdentifierType": "DOI", + }) + else: + url = "https://{0}{1}".format(current_site, reverse( + 'published_project', + args=(parent_project.slug, parent_project.version))) + relation.append({ + "relationType": "IsDerivedFrom", + "relatedIdentifier": url, + "relatedIdentifierType": "URL", + }) + resource_type = 'Dataset' if project.resource_type.name == 'Software': resource_type = 'Software'