diff --git a/.github/workflows/cli-integration.yml b/.github/workflows/cli-integration.yml index 57fb3b7c8..cd07791f4 100644 --- a/.github/workflows/cli-integration.yml +++ b/.github/workflows/cli-integration.yml @@ -78,9 +78,43 @@ jobs: if: matrix.dandi-version == 'release' run: pip install "dandi[test]" - - name: Install dev dandi - if: matrix.dandi-version == 'master' - run: pip install "dandi[test] @ git+https://github.com/dandi/dandi-cli" + - name: Extract CLI PR reference from description + if: matrix.dandi-version == 'master' && github.event_name == 'pull_request' + id: cli-pr + run: | + if echo "${{ github.event.pull_request.body }}" | grep -i "Requires_CLI_PR:"; then + CLI_PR_URL=$(echo "${{ github.event.pull_request.body }}" | grep -i "Requires_CLI_PR:" | sed 's/.*Requires_CLI_PR:[[:space:]]*//' | head -1 | tr -d '\r\n') + echo "CLI PR URL found: $CLI_PR_URL" + # Extract PR number from URL (e.g., https://github.com/dandi/dandi-cli/pull/123) + CLI_PR_NUM=$(echo "$CLI_PR_URL" | grep -o '[0-9]\+' | tail -1) + echo "CLI PR number: $CLI_PR_NUM" + echo "cli_pr_num=$CLI_PR_NUM" >> $GITHUB_OUTPUT + else + echo "No CLI PR reference found in description" + echo "cli_pr_num=" >> $GITHUB_OUTPUT + fi + + - name: Get CLI PR branch name + if: matrix.dandi-version == 'master' && steps.cli-pr.outputs.cli_pr_num != '' + id: cli-branch + run: | + CLI_PR_NUM="${{ steps.cli-pr.outputs.cli_pr_num }}" + echo "Getting branch name for CLI PR #$CLI_PR_NUM" + BRANCH_NAME=$(curl -s "https://api.github.com/repos/dandi/dandi-cli/pulls/$CLI_PR_NUM" | jq -r '.head.ref') + echo "Branch name: $BRANCH_NAME" + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + + - name: Install dev dandi from specific PR branch + if: matrix.dandi-version == 'master' && steps.cli-branch.outputs.branch_name != '' && steps.cli-branch.outputs.branch_name != 'null' + run: | + echo "Installing dandi from PR branch: ${{ steps.cli-branch.outputs.branch_name }}" + pip install "dandi[test] @ git+https://github.com/dandi/dandi-cli@${{ steps.cli-branch.outputs.branch_name }}" + + - name: Install dev dandi from master + if: matrix.dandi-version == 'master' && (steps.cli-branch.outputs.branch_name == '' || steps.cli-branch.outputs.branch_name == 'null') + run: | + echo "Installing dandi from master branch" + pip install "dandi[test] @ git+https://github.com/dandi/dandi-cli" - name: Run dandi-api tests in dandi-cli run: | diff --git a/dandiapi/api/management/commands/createsuperuser.py b/dandiapi/api/management/commands/createsuperuser.py index e21975112..94dc37fcc 100644 --- a/dandiapi/api/management/commands/createsuperuser.py +++ b/dandiapi/api/management/commands/createsuperuser.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from typing import TYPE_CHECKING from composed_configuration._allauth_support.management.commands import createsuperuser @@ -11,8 +12,12 @@ from composed_configuration._allauth_support.createsuperuser import EmailAsUsernameProxyUser -def create_usermetadata(instance: EmailAsUsernameProxyUser, *args, **kwargs): - UserMetadata.objects.create(user=instance, status=UserMetadata.Status.APPROVED) +def create_usermetadata(instance: EmailAsUsernameProxyUser, created: bool, **kwargs): # noqa: FBT001 + if created and not hasattr(instance, '_usermetadata_created'): + UserMetadata.objects.get_or_create( + user=instance, defaults={'status': UserMetadata.Status.APPROVED} + ) + instance._usermetadata_created = True # noqa: SLF001 class Command(createsuperuser.Command): @@ -25,6 +30,24 @@ def handle(self, *args, **kwargs) -> str | None: # Save the return value of the parent class function so we can return it later return_value = super().handle(*args, **kwargs) + # Set first_name and last_name from environment variables if provided + first_name = os.environ.get('DJANGO_SUPERUSER_FIRST_NAME') + last_name = os.environ.get('DJANGO_SUPERUSER_LAST_NAME') + + if first_name or last_name: + # Find the user that was just created + email = kwargs.get('email') or os.environ.get('DJANGO_SUPERUSER_EMAIL') + if email: + try: + user = createsuperuser.user_model.objects.get(email=email) + if first_name: + user.first_name = first_name + if last_name: + user.last_name = last_name + user.save() + except createsuperuser.user_model.DoesNotExist: + pass + # Disconnect the signal handler. This isn't strictly necessary, but this avoids any # unexpected behavior if, for example, someone extends this command and doesn't # realize there's a signal handler attached dynamically. diff --git a/dandiapi/api/services/version/metadata.py b/dandiapi/api/services/version/metadata.py index bd82bcc40..1a52154cd 100644 --- a/dandiapi/api/services/version/metadata.py +++ b/dandiapi/api/services/version/metadata.py @@ -22,7 +22,14 @@ def _normalize_version_metadata( version_metadata = { 'schemaKey': 'Dandiset', 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'contributor': [ + 'contributor': [], + **version_metadata, + } + # if function did not receive already a record for that name + # we create one: + if not any(r.get('name') == name for r in version_metadata['contributor']): + version_metadata['contributor'].insert( + 0, { 'name': name, 'email': email, @@ -31,9 +38,7 @@ def _normalize_version_metadata( 'affiliation': [], 'includeInCitation': True, }, - ], - **version_metadata, - } + ) # Run the version_metadata through the pydantic model to automatically include any boilerplate # like the access or repository fields return PydanticDandiset.model_construct(**version_metadata).model_dump( diff --git a/dandiapi/api/tests/test_dandiset.py b/dandiapi/api/tests/test_dandiset.py index 6f1c3cfb1..3dfd8b290 100644 --- a/dandiapi/api/tests/test_dandiset.py +++ b/dandiapi/api/tests/test_dandiset.py @@ -541,13 +541,13 @@ def test_dandiset_rest_create_with_contributor(api_client, admin_user): # This contributor is different from the admin_user 'contributor': [ { - 'name': 'Jane Doe', + 'name': 'Doe, Jane', 'email': 'jane.doe@kitware.com', 'roleName': ['dcite:ContactPerson'], 'schemaKey': 'Person', 'affiliation': [], 'includeInCitation': True, - } + }, ], } @@ -571,7 +571,7 @@ def test_dandiset_rest_create_with_contributor(api_client, admin_user): 'created': TIMESTAMP_RE, 'modified': TIMESTAMP_RE, }, - 'contact_person': 'Jane Doe', + 'contact_person': 'Doe, John', 'embargo_status': 'OPEN', 'star_count': 0, 'is_starred': False, @@ -603,7 +603,10 @@ def test_dandiset_rest_create_with_contributor(api_client, admin_user): 'version': 'draft', 'url': url, 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': (f'Jane Doe ({year}) {name} (Version draft) [Data set]. DANDI Archive. {url}'), + 'citation': ( + f'Doe, John; Doe, Jane ({year}) {name} ' + f'(Version draft) [Data set]. DANDI Archive. {url}' + ), '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', 'schemaVersion': settings.DANDI_SCHEMA_VERSION, 'schemaKey': 'Dandiset', @@ -611,13 +614,21 @@ def test_dandiset_rest_create_with_contributor(api_client, admin_user): 'repository': settings.DANDI_WEB_APP_URL, 'contributor': [ { - 'name': 'Jane Doe', + 'affiliation': [], + 'email': 'admin@example.com', + 'includeInCitation': True, + 'name': 'Doe, John', + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + }, + { + 'name': 'Doe, Jane', 'email': 'jane.doe@kitware.com', 'roleName': ['dcite:ContactPerson'], 'schemaKey': 'Person', 'affiliation': [], 'includeInCitation': True, - } + }, ], 'assetsSummary': { 'schemaKey': 'AssetsSummary',